# Authentication Service Internals

The Authentication Service (AS) stores and distributes single-use MLS
KeyPackages. It is one of the two logical services exposed through the unified
`NodeService` RPC interface. The AS also stores hybrid (X25519 + ML-KEM-768)
public keys for post-quantum envelope encryption.

This page covers the server-side implementation of KeyPackage storage, the
`Auth` struct validation logic, and the hybrid key endpoints.

**Sources:**
- `crates/quicproquo-server/src/main.rs` (RPC handlers, auth validation)
- `crates/quicproquo-server/src/storage.rs` (FileBackedStore)
- `schemas/node.capnp` (wire schema)

---

## KeyPackage Storage

### Data Model

KeyPackages are stored in a `FileBackedStore` using a `Mutex`-protected
`HashMap`:

```text
key_packages: Mutex<HashMap<Vec<u8>, VecDeque<Vec<u8>>>>
                            ^                ^
                            |                |
                       identity_key     FIFO queue of
                       (32-byte Ed25519  TLS-encoded
                        public key)      KeyPackage bytes
```

Each identity can have multiple KeyPackages queued. This is essential because
KeyPackages are single-use (per RFC 9420): once fetched by a peer, they are
permanently removed. Clients should upload several KeyPackages to handle
concurrent group invitations.

The map is persisted to `data/keypackages.bin` using bincode serialization,
wrapped in the `QueueMapV1` struct. See [Storage Backend](storage-backend.md)
for persistence details.

### uploadKeyPackage

```capnp
uploadKeyPackage @0 (identityKey :Data, package :Data, auth :Auth)
    -> (fingerprint :Data);
```

**Handler logic:**

1. **Parse parameters.** Extract `identityKey`, `package`, and `auth`.

2. **Validate auth.** Call `validate_auth()` (see [Auth Validation](#auth-validation)
   below).

3. **Validate inputs:**

   | Check | Constraint | Error Message |
   |-------|------------|---------------|
   | Identity key length | Exactly 32 bytes | `"identityKey must be exactly 32 bytes, got {n}"` |
   | Package non-empty | `package.len() > 0` | `"package must not be empty"` |
   | Package size cap | `package.len() <= 1,048,576` | `"package exceeds max size (1048576 bytes)"` |

4. **Compute fingerprint.** `SHA-256(package_bytes)` produces a 32-byte digest.

5. **Store.** `FileBackedStore::upload_key_package(identity_key, package)` pushes
   the package to the back of the identity's `VecDeque` and flushes to disk.

6. **Return fingerprint.** The SHA-256 hash is set in the response.

The fingerprint allows the uploading client to verify that the server stored the
exact bytes it sent. See [KeyPackage Exchange Flow](keypackage-exchange.md) for
the client-side verification logic.

### fetchKeyPackage

```capnp
fetchKeyPackage @1 (identityKey :Data, auth :Auth) -> (package :Data);
```

**Handler logic:**

1. **Parse and validate** `identityKey` (32 bytes) and `auth`.

2. **Pop from queue.** `FileBackedStore::fetch_key_package(identity_key)` calls
   `VecDeque::pop_front()` on the identity's queue, removing and returning the
   oldest KeyPackage. The updated map is flushed to disk.

3. **Return.** If a KeyPackage was available, set it in the response. If the
   queue was empty (or the identity has no entry), return empty `Data`.

**Single-use semantics:** The `pop_front()` operation ensures each KeyPackage is
returned exactly once. This is critical for MLS security -- reusing a KeyPackage
would allow conflicting group states. The removal is atomic with respect to the
`Mutex` lock, so concurrent fetch requests will not receive the same package.

**Empty response handling:** The client checks `package.is_empty()` to
distinguish between "no packages available" and "package fetched." An empty
response is not an error -- it means the target identity has exhausted their
KeyPackage supply and needs to upload more.

---

## Auth Validation

All `NodeService` RPC methods accept an `Auth` struct:

```capnp
struct Auth {
  version     @0 :UInt16;  # 0 = legacy/none, 1 = token-based
  accessToken @1 :Data;    # opaque bearer token
  deviceId    @2 :Data;    # optional UUID for auditing
}
```

The server validates this struct through the `validate_auth` function:

```text
validate_auth(cfg, auth)
    |
    +-- version == 0?
    |     +-- cfg.allow_legacy_v0 == true?  -> OK
    |     +-- cfg.allow_legacy_v0 == false? -> ERROR "auth version 0 disabled"
    |
    +-- version == 1?
    |     +-- accessToken empty?            -> ERROR "requires non-empty accessToken"
    |     +-- cfg.required_token is Some?
    |     |     +-- token matches?          -> OK
    |     |     +-- token mismatch?         -> ERROR "invalid accessToken"
    |     +-- cfg.required_token is None?   -> OK (any non-empty token accepted)
    |
    +-- version >= 2?                       -> ERROR "unsupported auth version"
```

### AuthConfig

The server's auth behavior is controlled by `AuthConfig`:

```rust
struct AuthConfig {
    required_token: Option<Vec<u8>>,  // None = accept any token
    allow_legacy_v0: bool,            // true = accept version 0 (no auth)
}
```

Configured via CLI flags / environment variables:

| Flag / Env Var                    | Default | Purpose |
|-----------------------------------|---------|---------|
| `--auth-token` / `QPQ_AUTH_TOKEN` | None | Required bearer token. If unset, any non-empty token is accepted for version 1. |
| `--allow-auth-v0` / `QPQ_ALLOW_AUTH_V0` | `true` | Whether to accept `auth.version=0` (legacy, unauthenticated) requests. |

### Version Semantics

| Version | Meaning | Token Required? |
|---------|---------|-----------------|
| 0 | Legacy / unauthenticated | No. Token is ignored. Server must have `allow_legacy_v0 = true`. |
| 1 | Token-based authentication | Yes. Must be non-empty. Must match `required_token` if configured. |
| 2+ | Reserved for future use | Rejected. |

### Current Limitations

The current auth implementation is intentionally minimal:

- **No identity binding.** The access token is not tied to a specific Ed25519
  identity. Any valid token can upload or fetch KeyPackages for any identity.
- **No rate limiting.** There is no per-identity or per-IP rate limiting.
- **No token rotation.** Tokens are static strings configured at server startup.
- **No device management.** The `deviceId` field is accepted but not used for
  authorization decisions.

The [Auth, Devices, and Tokens](../roadmap/authz-plan.md) roadmap item
addresses these gaps with a proper token issuance and validation system.

---

## Hybrid Key Endpoints

The AS also stores hybrid (X25519 + ML-KEM-768) public keys for post-quantum
envelope encryption. Unlike KeyPackages, hybrid keys are **not single-use** --
they are stored persistently and can be fetched multiple times.

### uploadHybridKey

```capnp
uploadHybridKey @6 (identityKey :Data, hybridPublicKey :Data) -> ();
```

**Handler logic:**

1. Validate `identityKey` (32 bytes) and `hybridPublicKey` (non-empty).
2. `FileBackedStore::upload_hybrid_key(identity_key, hybrid_pk)` stores the key,
   overwriting any previous value for this identity.
3. Flushes to `data/hybridkeys.bin`.

The storage model is simpler than KeyPackages: a flat
`HashMap<Vec<u8>, Vec<u8>>` (identity key to hybrid public key bytes). There is
no queue -- each identity has at most one hybrid public key.

### fetchHybridKey

```capnp
fetchHybridKey @7 (identityKey :Data) -> (hybridPublicKey :Data);
```

**Handler logic:**

1. Validate `identityKey` (32 bytes).
2. Look up the hybrid public key in the store. Unlike `fetchKeyPackage`, this
   does **not** remove the key -- it can be fetched repeatedly.
3. Return the key bytes, or empty `Data` if none is stored.

See [Hybrid KEM](../protocol-layers/hybrid-kem.md) for how the client uses
these keys to wrap MLS payloads in post-quantum envelopes.

---

## NodeServiceImpl Structure

The server-side implementation struct:

```rust
struct NodeServiceImpl {
    store:    Arc<FileBackedStore>,                  // shared across connections
    waiters:  Arc<DashMap<Vec<u8>, Arc<Notify>>>,    // long-poll notification
    auth_cfg: Arc<AuthConfig>,                       // auth policy
}
```

All connections share the same `store` and `waiters` via `Arc`. The
`DashMap<Vec<u8>, Arc<Notify>>` is keyed by recipient key and provides the
push-notification mechanism for `fetchWait`. See
[Delivery Service Internals](delivery-service.md) for the long-polling
implementation.

---

## Connection Model

```text
QUIC endpoint (port 7000)
  +-- TLS 1.3 handshake (self-signed cert by default)
       +-- Accept bidirectional stream
            +-- capnp-rpc VatNetwork (Side::Server)
                 +-- NodeServiceImpl { store, waiters, auth_cfg }
```

Each QUIC connection opens one bidirectional stream for Cap'n Proto RPC. The
`capnp-rpc` crate uses `Rc<RefCell<>>` internally, making it `!Send`. All RPC
tasks run on a `tokio::task::LocalSet` to satisfy this constraint.

The server generates a self-signed TLS certificate on first start if no
certificate files exist. Certificate and key paths are configurable via
`--tls-cert` and `--tls-key`.

---

## Health Endpoint

```capnp
health @5 () -> (status :Text);
```

A simple readiness probe. Returns `"ok"` unconditionally. No auth validation is
performed. Useful for infrastructure health checks and measuring QUIC round-trip
time.

---

## Related Pages

- [KeyPackage Exchange Flow](keypackage-exchange.md) -- end-to-end upload and fetch flow including client-side logic
- [Delivery Service Internals](delivery-service.md) -- the DS half of NodeService
- [Storage Backend](storage-backend.md) -- FileBackedStore persistence model
- [GroupMember Lifecycle](group-member-lifecycle.md) -- how KeyPackages are generated and consumed
- [Auth, Devices, and Tokens](../roadmap/authz-plan.md) -- planned auth improvements
- [NodeService Schema](../wire-format/node-service-schema.md) -- Cap'n Proto schema reference
- [Hybrid KEM](../protocol-layers/hybrid-kem.md) -- post-quantum envelope encryption
