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