a73x

specs/003-key-trust-allowlist/data-model.md

Ref:   Size: 3.1 KiB

# Data Model: Key Trust Allowlist

**Date**: 2026-03-21 | **Feature**: 003-key-trust-allowlist

## Trusted Keys File

### Location

```
<repo>/.git/collab/trusted-keys
```

Project-local, not committed to any branch, not synced between collaborators.

### Format

SSH authorized_keys style. One entry per line.

```
# Trusted keys for project X
# Added 2026-03-21
dGhpcyBpcyBhIHRlc3Qga2V5IGJ5dGVzMTIzNDU= Alice
YW5vdGhlciB0ZXN0IGtleSBieXRlczEyMzQ1Njc= Bob (laptop)
c29tZSBvdGhlciBrZXkgYnl0ZXMxMjM0NTY3ODA=
```

### Parsing Rules

| Rule | Detail |
|------|--------|
| Delimiter | First space character separates key from label |
| Key field | Base64-encoded 32-byte Ed25519 public key (44 characters with padding) |
| Label field | Optional, free-form text (may contain spaces), everything after first space |
| Comments | Lines starting with `#` (after trimming) are skipped |
| Blank lines | Skipped |
| Malformed lines | Skipped with a warning to stderr (invalid base64, wrong byte length, invalid Ed25519 point) |
| Duplicates | Prevented on `add`; if present in file, deduplicated on load (last wins for label) |
| Encoding | UTF-8 |
| Line endings | LF or CRLF accepted |

### Validation on Add

A key is valid if all of the following hold:
1. Base64-decodable (standard alphabet, with or without padding)
2. Decoded bytes are exactly 32 bytes
3. Bytes represent a valid Ed25519 public key point (`VerifyingKey::from_bytes()` succeeds)

## In-Memory Representation

```rust
/// A single trusted key entry.
pub struct TrustedKey {
    /// Base64-encoded Ed25519 public key (as stored in file and in SignedEvent.pubkey).
    pub pubkey: String,
    /// Optional human-readable label.
    pub label: Option<String>,
}

/// Loaded trust policy.
pub enum TrustPolicy {
    /// No trusted keys file exists. Fall back to accepting any valid signature.
    Unconfigured,
    /// Trusted keys file exists (possibly empty). Enforce allowlist.
    Configured(Vec<TrustedKey>),
}
```

For fast lookup during sync, the `Configured` variant's keys are also loaded into a `HashSet<String>` keyed by the base64 pubkey string.

## VerifyStatus Enum (Modified)

```rust
pub enum VerifyStatus {
    Valid,      // Signature cryptographically valid
    Invalid,    // Signature present but verification failed
    Missing,    // No signature/pubkey fields
    Untrusted,  // NEW: valid signature but key not in trusted set
}
```

## Error Variants (New)

```rust
pub enum Error {
    // ... existing variants ...
    #[error("untrusted key: {0}")]
    UntrustedKey(String),
}
```

## CLI Data Flow

### `collab key add <pubkey> [--label <label>] [--self]`

1. If `--self`: read pubkey from `~/.config/git-collab/signing-key.pub`
2. Validate key (base64 + 32 bytes + valid point)
3. Load existing file (or create)
4. Check for duplicates
5. Append `<pubkey> <label>\n` (or just `<pubkey>\n`)

### `collab key list`

1. Load and parse file
2. Print each entry: `<pubkey>  <label>` (or `<pubkey>  (no label)`)

### `collab key remove <pubkey>`

1. Load file
2. Find matching entry
3. Rewrite file without the entry
4. Print removed key and its label