f0fee7e193
internal/authz: production form of the Phase-4 SSHSIG signing primitive. - Verifier.New/Verify with the LOCKED pipeline (namespace → allow-list by key material → crypto over RAW bytes → target → time → nonce LAST); each post-crypto stage rejects even with a valid sig; an invalid sig never burns a nonce. - SSHSIG framing via x/crypto/ssh (no hand-rolled crypto); key-type-agnostic (ed25519 / sk-ssh-ed25519 / rsa / ecdsa via pub.Verify). Fixed namespace felhom-op-v1. Typed errors. OpBlob (fixed host_id/guest_id tags) + VerifiedOp. - NonceStore: MemoryNonceStore + durable crash-safe FileNonceStore (fsync'd append log, replay-on-open, compaction, expiry-only pruning; survives restart). - config.AuthzConfig (nonce path + pinned operational/recovery signer keys). - Tests (14): real ssh-keygen fixture, per-stage rejection, nonce-not-burned, replay, persistence-across-restart, synthetic sk, byte-exactness. Dep: golang.org/x/crypto v0.52.0 (declares go 1.25 — the Phase-4 doc's "Go 1.24.4 / x/crypto v0.52.0" pairing doesn't build; build server upgraded to go1.26.0, backward-compatible). Version 0.1.0 -> 0.2.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
28 lines
1.3 KiB
Go
28 lines
1.3 KiB
Go
package authz
|
|
|
|
import "errors"
|
|
|
|
// Typed rejection sentinels — one per pipeline stage so the reconcile layer can
|
|
// distinguish "rejected" (a real signed op that failed a check) from "malformed"
|
|
// from a future "not yet signed". All are errors.Is-friendly: Verify wraps them
|
|
// with %w plus context.
|
|
var (
|
|
// ErrMalformed: the armor/SSHSIG/blob could not be parsed (not a rejection of
|
|
// a well-formed op — bad input).
|
|
ErrMalformed = errors.New("authz: malformed signature or blob")
|
|
// ErrNamespace: SSHSIG namespace != the fixed felhom-op-v1 domain separator.
|
|
ErrNamespace = errors.New("authz: namespace mismatch")
|
|
// ErrUnknownSigner: the signing key's material is not in the pinned allow-list.
|
|
ErrUnknownSigner = errors.New("authz: signer not in allowed set")
|
|
// ErrBadSignature: cryptographic verification failed (tamper / wrong key).
|
|
ErrBadSignature = errors.New("authz: signature did not verify")
|
|
// ErrTarget: target.host_id is not this box.
|
|
ErrTarget = errors.New("authz: target mismatch")
|
|
// ErrExpired: now > expires_at.
|
|
ErrExpired = errors.New("authz: op expired")
|
|
// ErrNotYetValid: now < issued_at (minus clock-skew tolerance).
|
|
ErrNotYetValid = errors.New("authz: op not yet valid")
|
|
// ErrReplay: the nonce was already recorded in the window.
|
|
ErrReplay = errors.New("authz: replay (nonce already seen)")
|
|
)
|