Documentation
¶
Index ¶
- Variables
- func Fingerprint(signingPublicKey, ageRecipient string) (string, error)
- func FingerprintEqual(a, b string) bool
- func IsKeychainUnavailable(err error) bool
- func NormalizeFingerprint(s string) string
- func Sign(privateKey, domain string, message []byte) (string, error)
- func Verify(publicKey, signature, domain string, message []byte) error
- type BackupEpoch
- type Custody
- type FileStore
- func (s FileStore) DeleteHubS3Credentials(workspaceID string) error
- func (s FileStore) Ensure(deviceID string) (Identity, bool, error)
- func (s FileStore) EnsureSigning(deviceID string) (SigningIdentity, bool, error)
- func (s FileStore) Read(deviceID string) (Identity, error)
- func (s FileStore) ReadHubS3Credentials(workspaceID string) (HubS3Credentials, error)
- func (s FileStore) ReadSigning(deviceID string) (SigningIdentity, error)
- func (s FileStore) ReadWCK(workspaceID string, epoch int64, kid string) ([]byte, error)
- func (s FileStore) WriteHubS3Credentials(workspaceID string, creds HubS3Credentials) error
- func (s FileStore) WriteWCK(workspaceID string, epoch int64, kid string, wck []byte) error
- type HubS3Credentials
- type HybridStore
- func (s HybridStore) DeleteHubS3Credentials(ctx context.Context, workspaceID string) error
- func (s HybridStore) Ensure(ctx context.Context, deviceID, publishedRecipient string) (Identity, bool, error)
- func (s HybridStore) EnsureSigning(ctx context.Context, deviceID, publishedPub string) (SigningIdentity, bool, error)
- func (s HybridStore) ExportForBackup(ctx context.Context, dstDir, deviceID, workspaceID string, ...) ([]string, error)
- func (s HybridStore) LoadHubS3Credentials(ctx context.Context, workspaceID string) (HubS3Credentials, error)
- func (s HybridStore) LoadWCK(ctx context.Context, workspaceID string, epoch int64, kid string) ([]byte, error)
- func (s HybridStore) Probe(ctx context.Context) Custody
- func (s HybridStore) Read(ctx context.Context, deviceID string) (Identity, error)
- func (s HybridStore) ReadSigning(ctx context.Context, deviceID string) (SigningIdentity, error)
- func (s HybridStore) StoreHubS3Credentials(ctx context.Context, workspaceID string, creds HubS3Credentials) (string, error)
- func (s HybridStore) StoreWCK(ctx context.Context, workspaceID string, epoch int64, kid string, wck []byte) error
- func (s HybridStore) WithCustody(c Custody) HybridStore
- type Identity
- type SecretBackend
- type SigningIdentity
Constants ¶
This section is empty.
Variables ¶
var ErrKeychainUnreachable = errors.New("keychain backend unreachable")
ErrKeychainUnreachable wraps a keychain error that means the backend could not be reached (unsupported platform, or a missing Secret Service / D-Bus session), as opposed to a secret that is genuinely absent. Load paths return it so callers can distinguish "custody unreachable" from "key not found", and mint paths refuse to generate a divergent identity when they see it (P6-XP-04).
Functions ¶
func Fingerprint ¶
Fingerprint derives a full-strength (256-bit) device fingerprint that binds a device's Ed25519 signing public key to its age X25519 recipient. Both inputs are canonicalized by parse-then-re-encode so that cosmetically different encodings of the same keys (whitespace, non-canonical base64) yield the same fingerprint. The digest is SHA-256 over
"devstrap/device-fp/v1" || 0x00 || canonicalSigning || 0x00 || canonicalRecipient
encoded as unpadded, uppercase, standard-alphabet base32 (exactly 52 characters for 32 bytes) and returned as 13 dash-separated groups of 4.
This is deliberately a full fingerprint, never a short authentication string: the pairing channel is untrusted, and a truncated code would let an attacker brute-force a colliding key pair. Compare the whole value out-of-band.
func FingerprintEqual ¶
FingerprintEqual reports whether two fingerprints are equal after normalization, using a constant-time comparison to avoid leaking how much of a mistyped value matched.
func IsKeychainUnavailable ¶
IsKeychainUnavailable reports whether a keychain error means the backend is missing/not-present (so an operation may tolerate it and use the file store) rather than a live-backend hard failure. It is the coarse predicate used by tolerant paths (e.g. delete); mint/store paths use the finer keychainUnreachable / keychainSecretMissing split so a divergent identity is never minted (P6-XP-04).
func NormalizeFingerprint ¶
NormalizeFingerprint strips grouping dashes and whitespace and uppercases the remainder so fingerprints copied with varied formatting compare equal.
Types ¶
type BackupEpoch ¶
BackupEpoch names one held Workspace Content Key by its epoch and kid so a full backup can escrow it (P6-DATA-04). It mirrors the (epoch, kid) tuple the store records in workspace_keys.
type Custody ¶
type Custody string
Custody names where a HybridStore keeps its secret material. It is a per-machine decision recorded once at init (P6-XP-04) and honored on every later run so a store never silently migrates custody backends:
- CustodyKeychain: the OS keychain is authoritative. If it is unreachable, operations fail closed rather than falling back to the file store — that silent downgrade is exactly the split-custody wedge P6-XP-04 fixes.
- CustodyFile: the 0600 file store is authoritative and the keychain is never consulted (headless/CI hosts, or DEVSTRAP_NO_KEYCHAIN=1).
- CustodyUnset: no decision recorded yet (a pre-P6-XP-04 store). Legacy hybrid behavior applies: prefer the keychain, fall back to the file store when the backend is unavailable. The published-key mint guard still holds, so even this path can never mint a divergent identity.
type FileStore ¶
type FileStore struct {
Dir string
}
func NewFileStore ¶
func (FileStore) DeleteHubS3Credentials ¶
DeleteHubS3Credentials removes the credential blob from the file fallback store; a missing file is not an error.
func (FileStore) EnsureSigning ¶
func (s FileStore) EnsureSigning(deviceID string) (SigningIdentity, bool, error)
func (FileStore) ReadHubS3Credentials ¶
func (s FileStore) ReadHubS3Credentials(workspaceID string) (HubS3Credentials, error)
ReadHubS3Credentials loads the credential blob from the file fallback store.
func (FileStore) ReadSigning ¶
func (s FileStore) ReadSigning(deviceID string) (SigningIdentity, error)
func (FileStore) WriteHubS3Credentials ¶
func (s FileStore) WriteHubS3Credentials(workspaceID string, creds HubS3Credentials) error
WriteHubS3Credentials persists the credential blob to the file fallback store with mode 0600.
type HubS3Credentials ¶
type HubS3Credentials struct {
AccessKeyID string `json:"access_key_id"`
SecretAccessKey string `json:"secret_access_key"`
}
HubS3Credentials is the hub S3/R2 credential pair kept in local custody (P6-HUB-02): one JSON blob per workspace under the device-identity keychain service, with a 0600 file fallback. The secret access key never enters SQLite, config.yaml, or logs — plaintext env remains only a CI/override fallback resolved by the CLI layer.
type HybridStore ¶
type HybridStore struct {
File FileStore
Secret SecretBackend
Custody Custody
}
func NewHybridStore ¶
func NewHybridStore(dir string, secret SecretBackend) HybridStore
func (HybridStore) DeleteHubS3Credentials ¶
func (s HybridStore) DeleteHubS3Credentials(ctx context.Context, workspaceID string) error
DeleteHubS3Credentials removes the stored pair from both custody backends (hub logout). Missing entries are not errors.
func (HybridStore) Ensure ¶
func (s HybridStore) Ensure(ctx context.Context, deviceID, publishedRecipient string) (Identity, bool, error)
Ensure ensures a device age identity exists, minting one only when none is stored. publishedRecipient is the device's already-published age recipient ("" if none); when it is set and the keychain is unreachable, Ensure refuses to mint a divergent identity rather than wedge custody (P6-XP-04).
func (HybridStore) EnsureSigning ¶
func (s HybridStore) EnsureSigning(ctx context.Context, deviceID, publishedPub string) (SigningIdentity, bool, error)
EnsureSigning ensures a device signing identity exists, minting one only when none is stored. publishedPub is the device's already-published signing public key ("" if none). When the keychain is unreachable, or when a key is already published but its private half is absent, EnsureSigning refuses to mint a divergent identity — the split-custody wedge P6-XP-04 fixes.
func (HybridStore) ExportForBackup ¶
func (s HybridStore) ExportForBackup(ctx context.Context, dstDir, deviceID, workspaceID string, epochs []BackupEpoch) ([]string, error)
ExportForBackup reads every secret this device holds for the given device and workspace — the device age identity, the device signing identity, each held WCK epoch, and the hub S3 credentials when present — from the active custody backend and writes them into dstDir using the FileStore on-disk format with mode 0600 (P6-DATA-04). Because it reads through the recorded custody backend (via HybridStore.Read/ReadSigning/LoadWCK/LoadHubS3Credentials), it captures keychain-held material as portable escrow files for a full backup, not just the file-custody KeyDir. A basename already present in dstDir is left untouched, so it is safe to run over a directory the caller has pre-populated.
It returns the basenames written (sorted). A REQUIRED secret — the device age or signing identity, or any held WCK epoch — that cannot be read is a hard error naming the secret: a "full" backup must never silently omit key material. Hub S3 credentials are optional (a workspace may have no hub configured): a genuinely-absent entry is skipped, but any other read failure is fatal. No secret bytes are logged.
func (HybridStore) LoadHubS3Credentials ¶
func (s HybridStore) LoadHubS3Credentials(ctx context.Context, workspaceID string) (HubS3Credentials, error)
LoadHubS3Credentials loads the stored hub S3 credential pair, or an error wrapping os.ErrNotExist when none is stored.
func (HybridStore) LoadWCK ¶
func (s HybridStore) LoadWCK(ctx context.Context, workspaceID string, epoch int64, kid string) ([]byte, error)
LoadWCK loads the WCK for a workspace+epoch+kid, or an error wrapping os.ErrNotExist if this device does not hold it. The decorator calls this during Pull to obtain the key for an enc.v1 event's epoch.
func (HybridStore) Probe ¶
func (s HybridStore) Probe(ctx context.Context) Custody
Probe classifies the current keychain reachability into a custody decision with a single read-only lookup, without side effects. It is used once at init to record the custody backend (P6-XP-04). A nil backend or an unreachable Secret Service resolves to file custody; a reachable backend (the probe account is expectedly absent) resolves to keychain custody.
func (HybridStore) ReadSigning ¶
func (s HybridStore) ReadSigning(ctx context.Context, deviceID string) (SigningIdentity, error)
func (HybridStore) StoreHubS3Credentials ¶
func (s HybridStore) StoreHubS3Credentials(ctx context.Context, workspaceID string, creds HubS3Credentials) (string, error)
StoreHubS3Credentials persists the hub S3 credential pair (P6-HUB-02), reusing the keychain/file custody path used for device identities and WCKs: keychain preferred, 0600 file fallback only when the keychain is genuinely unavailable (DEVSTRAP_NO_KEYCHAIN or a missing Secret Service); a present-but-failing keychain fails closed (SECR-04/SECU-01). Returns where the credentials landed ("keychain" or "file") so the CLI can tell the user.
func (HybridStore) StoreWCK ¶
func (s HybridStore) StoreWCK(ctx context.Context, workspaceID string, epoch int64, kid string, wck []byte) error
StoreWCK persists a 32-byte Workspace Content Key for the given workspace, epoch, and kid (P4-SEC-07 / P6-SEC-02), reusing the keychain/file custody path used for device identities. kid is the non-secret fingerprint hex(sha256(wck)); pass "" only for the legacy bare-epoch form. The keychain is preferred; a 0600 file fallback is used only when the keychain is genuinely unavailable (DEVSTRAP_NO_KEYCHAIN or a missing Secret Service), so headless/CI runs remain usable. A present-but-failing keychain fails closed (SECR-04/SECU-01).
func (HybridStore) WithCustody ¶
func (s HybridStore) WithCustody(c Custody) HybridStore
WithCustody returns a copy of the store pinned to the recorded custody backend (P6-XP-04). Callers resolve the recorded decision (and any DEVSTRAP_NO_KEYCHAIN override) and stamp it here before minting or reading.
type Identity ¶
func NewIdentity ¶
type SecretBackend ¶
type SigningIdentity ¶
func NewSigningIdentity ¶
func NewSigningIdentity() (SigningIdentity, error)