Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions go/login/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# go/login

Package `login` implements the **client and server sides of the GLOME Login protocol** in Go. It builds on top of the [`glome`](../glome) package to handle URL construction, handshake parsing, tag verification, and authorization code validation as described in the [GLOME Login protocol spec](../../docs/glome-login.md).

> **Note:** This API is Alpha and may be subject to breaking changes.

## Overview

GLOME Login is a challenge-response authentication mechanism. A client (e.g. a machine at a serial console) generates a URL encoding a *handshake* and a *message* (host identity + requested action). An operator forwards this URL to a GLOME Login server, which validates the request and returns an authorization code. The client verifies the code and grants access if it matches.

This package provides Go types for both sides of that exchange:

| Role | Type | Responsibility |
|--------|----------|-------------------------------------------------------------------|
| Client | `Client` | Build the challenge URL, validate the server's authorization code |
| Server | `Server` | Parse an incoming challenge URL, verify the client's tag |

## Package Layout

```
go/login/
├── login.go # Core types: Client, Server, URLResponse, Message, Handshake
└── server/ # Higher-level HTTP server framework (see go/login/server)
```

## Key Types

### `Message`

Encodes what is being authorized: a target host and an action.

```go
type Message struct {
HostIDType string // optional type qualifier (e.g. "serial", "hostname")
HostID string // identity of the target host
Action string // action being requested (e.g. "shell/root")
}
```

`Message.Construct(esc bool)` serializes this to the wire format `[<hostid-type>:]<hostid>[/<action>]`, with optional URL-escaping.

### `Handshake`

Contains the cryptographic material exchanged in the URL path.

```go
type Handshake struct {
Prefix byte // encodes the server key ID (or its lower 7 bits)
UserKey glome.PublicKey // client's ephemeral public key
MessageTagPrefix []byte // prefix of the tag computed over the Message
}
```

### `URLResponse`

Represents a fully constructed GLOME Login URL. Created by either `NewResponse` (server-side) or internally by `Client.Construct`.

```go
type URLResponse struct {
V byte // URL format version (currently 1)
HandshakeInfo Handshake
Msg Message
// unexported: dialog, sendingKey
}
```

Useful methods:
- `Tag(len uint) []byte` — compute the GLOME tag over the message (used by the server to produce an auth code).
- `EncToken() string` — base64url-encoded response token (the authorization code to return to the client).
- `ValidateAuthCode(tag []byte) bool` — check whether a received tag is correct.
- `String() string` — render the full URL string.

### `Client`

Implements the **client side** of the protocol (e.g. embedded in `glome-login`).

```go
client := login.NewClient(serverPublicKey, userPrivateKey, serverKeyID, tagLen)

// Build the challenge URL to display to the operator
url, err := client.Construct(1, "", "myhost", "shell/root")

// After receiving the authorization code from the operator:
valid, err := client.ValidateAuthCode(receivedTag)
```

### `Server`

Implements the **server side** of the protocol. Requires a `KeyFetcher` callback to look up private keys by version ID.

```go
srv := login.Server{
KeyFetcher: func(id uint8) (*glome.PrivateKey, error) {
return keyStore.Lookup(id)
},
}

response, err := srv.ParseURLResponse(incomingURL)
if err != nil {
// handle: ErrInvalidURLFormat, ErrServerKeyNotFound, ErrIncorrectTag, ...
}

// Send the authorization code back to the operator:
authCode := response.EncToken()
```

`ParseURLResponse` validates:
1. The URL is well-formed (returns `ErrInvalidURLFormat` otherwise).
2. The server key corresponding to the handshake prefix exists (returns `ErrServerKeyNotFound` otherwise).
3. The client's embedded tag is valid (returns `ErrIncorrectTag` otherwise).

## Error Types

| Error | Meaning |
|-------------------------|----------------------------------------------------------|
| `ErrInvalidURLFormat` | The URL does not conform to the GLOME Login URL format |
| `ErrServerKeyNotFound` | No server private key matches the prefix in the URL |
| `ErrVersionNotSupported`| The URL format version `V` is not supported |
| `ErrInvalidHandshakeLen`| The handshake segment is too short |
| `ErrInvalidPrefixType` | The prefix type byte is invalid |
| `ErrIncorrectTag` | The client's embedded tag failed verification |
| `ErrResponseNotInitialized` | `ValidateAuthCode` called before `Construct` |

## Running the Tests

```sh
cd go
go test ./login/...
```

## Related Packages

- [`go/glome`](../glome) — core GLOME cryptographic primitives (key generation, tag computation)
- [`go/login/server`](./server) — HTTP server framework built on top of this package
- [`login/`](../../login) — C implementation of `glome-login` (the `login(1)` replacement)
- [GLOME Login protocol spec](../../docs/glome-login.md)
165 changes: 165 additions & 0 deletions go/login/server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# go/login/server

Package `server` is an HTTP server framework for the **GLOME Login server side**. It builds on top of [`go/login`](../) to provide a ready-to-use `http.Handler` that validates incoming GLOME Login challenge URLs, authorizes users, and returns authorization codes.

> **Note:** This API is Alpha and may be subject to breaking changes.

## Overview

In the GLOME Login flow, the *server* receives a challenge URL (forwarded by a human operator from a target machine), verifies the embedded handshake, checks whether the requesting user is authorized to perform the action, and responds with an authorization code.

This package handles the HTTP plumbing so you only need to supply:
1. A **key manager** with your service private keys.
2. An **authorizer** that decides whether a given `(user, host, action)` tuple is permitted.

## Package Layout

```
go/login/server/
├── server.go # LoginServer, Authorizer interface, option functions
└── keymanager.go # KeyManager, PrivateKey, PublicKey types
```

## Quick Start

```go
import (
"net/http"
"github.com/google/glome/go/login/server"
)

func main() {
// 1. Create a key manager and load your service key(s).
km := server.NewKeyManager()
km.Add(myPrivateKey, 1 /* key index 0-127 */)

// 2. Define an authorization policy.
authz := server.AuthorizerFunc(func(user, hostID, hostIDType, action string) (bool, error) {
// Allow root shell on any host for all authenticated users.
return action == "shell/root" && user != "", nil
})

// 3. Build the server.
srv, err := server.NewLoginServer(authz,
server.ResponseLen(16), // shorter auth codes (optional)
server.UserHeader("x-user-id"), // custom header name (optional)
)
if err != nil {
panic(err)
}
srv.Keys = km

// 4. Register as a standard http.Handler.
http.ListenAndServe(":8080", srv)
}
```

## Key Types

### `LoginServer`

The central type. Implements `http.Handler`.

```go
type LoginServer struct {
Keys *KeyManager
// ...
}
```

**Endpoints served by `ServeHTTP`:**

| Path | Behaviour |
|------|-----------|
| `/` | Returns a list of the server's service public keys (so clients can configure target machines). |
| `/v1/<handshake>/<message>/` | Validates the GLOME Login URL, runs the authorizer, and returns the authorization code or an error. |

**Constructor:**

```go
srv, err := server.NewLoginServer(authorizer, ...options)
```

**Options** (functional-options pattern):

| Option | Default | Description |
|--------|---------|-------------|
| `ResponseLen(n uint8)` | `44` (32 bytes, base64) | Length of the returned auth code in base64 characters. Must be in `[1, 44]`. |
| `UserHeader(name string)` | `"authenticated-user"` | HTTP request header from which the authenticated username is read. |

**Updating the authorizer at runtime:**

```go
srv.Authorizer(newAuthorizerImpl) // concurrency-safe swap
```

### `KeyManager`

A concurrency-safe store for the server's private keys, indexed by an integer ID in `[0, 127]`.

```go
km := server.NewKeyManager()

// Add a key.
err := km.Add(privateKey, 1)

// Atomically replace all keys (e.g. after a key rotation).
err = km.DropAllReplace([]server.PrivateKey{
{Value: newPrivKey, Index: 2},
})

// Look up a key by index.
privKey, ok := km.Read(2)

// List all current public keys (for publishing to clients).
pubKeys := km.ServiceKeys()
```

Key indexes must be unique and in `[0, 127]`. The `LoginServer` uses `KeyManager` to resolve the key ID embedded in an incoming challenge URL.

### `Authorizer` interface

You implement this to encode your authorization policy:

```go
type Authorizer interface {
GrantLogin(user, hostID, hostIDType, action string) (bool, error)
}
```

For simple cases, `AuthorizerFunc` lets you use a plain function:

```go
authz := server.AuthorizerFunc(func(user, hostID, hostIDType, action string) (bool, error) {
return myPolicyCheck(user, hostID, action)
})
```

**Contract for implementors:**
- An empty `user` string means no user identity could be extracted from the request (treat as unauthenticated).
- An empty `action` is a valid input; decide whether to allow it explicitly.
- Both `hostID` and `hostIDType` may be empty strings.
- The returned `bool` is acted upon even when a non-nil `error` is also returned.

### Key and error types

| Type | Description |
|------|-------------|
| `PrivateKey` | A `{Value glome.PrivateKey, Index uint8}` pair |
| `PublicKey` | A `{Value glome.PublicKey, Index uint8}` pair |
| `ErrInvalidKeyIndex` | Key index is outside `[0, 127]` |
| `ErrDuplicatedKeyIndex` | Key index is already registered |
| `ErrInvalidResponseLen` | Response length is outside `[1, 44]` |

## Running the Tests

```sh
cd go
go test ./login/server/...
```

## Related Packages

- [`go/login`](../) — core protocol types (`Client`, `Server`, `URLResponse`)
- [`go/glome`](../../glome) — GLOME cryptographic primitives
- [GLOME Login protocol spec](../../../docs/glome-login.md)