Documentation
¶
Overview ¶
Package auth implements Ed25519 per-client key authentication for Lookout. It provides a key registry loaded from an authorized_keys file (Model B), a nonce LRU for replay protection, and request verification helpers.
Index ¶
- Constants
- Variables
- func AuthorizedKeyLine(pub ed25519.PublicKey, comment string) string
- func BodyHashHex(b []byte) string
- func CanonicalMessage(method, path, bodyHashHex string, timestampUnix int64, nonce string) []byte
- func GenerateKeyPair(comment string) (privPEM []byte, authKeyLine string, err error)
- func KeyIDForPublicKey(pub ed25519.PublicKey) string
- func LoadPrivateKey(path string) (ed25519.PrivateKey, error)
- func MarshalPrivateKeyPEM(priv ed25519.PrivateKey) ([]byte, error)
- func NewNonce() (string, error)
- func ParsePrivateKeyPEM(pemData []byte) (ed25519.PrivateKey, error)
- func ReasonFor(err error) string
- func VerifyRequest(r *http.Request, body []byte, registry *KeyRegistry, lru *NonceLRU, ...) (keyID string, err error)
- type AuthorizedKey
- type Enroller
- type KeyRegistry
- type NonceLRU
Constants ¶
const ( HeaderKeyID = "X-Lookout-Key-ID" HeaderTimestamp = "X-Lookout-Timestamp" HeaderNonce = "X-Lookout-Nonce" HeaderSignature = "X-Lookout-Signature" HeaderReason = "X-Lookout-Reason" )
Header names used in Ed25519 request authentication.
Variables ¶
var ( ErrMissingHeaders = errors.New("ed25519: missing required signature headers") ErrUnknownKey = errors.New("ed25519: unknown key ID") ErrTimestampSkew = errors.New("ed25519: timestamp outside allowed window") ErrNonceReplay = errors.New("ed25519: nonce already seen (replay)") ErrBadSignature = errors.New("ed25519: signature verification failed") ErrInvalidNonce = errors.New("ed25519: nonce must be 32 hex characters") ErrInvalidSig = errors.New("ed25519: signature is not valid base64url") )
Sentinel errors returned by VerifyRequest. Callers can use errors.Is.
Functions ¶
func AuthorizedKeyLine ¶
AuthorizedKeyLine formats an Ed25519 public key as an authorized_keys line.
func BodyHashHex ¶
BodyHashHex computes the hex-encoded SHA-256 of b. If b is nil or zero-length, it returns the canonical empty-body hash.
func CanonicalMessage ¶
CanonicalMessage constructs the canonical byte string that is signed and verified for every authenticated request. The format (Appendix A) is:
METHOD\nPATH\nbody-sha256-hex\nunix-timestamp\nnonce
For an empty body, bodyHashHex must be the full SHA-256 of the empty string:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
func GenerateKeyPair ¶
GenerateKeyPair generates a new Ed25519 keypair and returns the private key in PKCS#8 PEM format and the public key as an authorized_keys line.
func KeyIDForPublicKey ¶
KeyIDForPublicKey computes the Key-ID (hex(SHA-256(raw pubkey)[:8])) for an Ed25519 public key.
func LoadPrivateKey ¶
func LoadPrivateKey(path string) (ed25519.PrivateKey, error)
LoadPrivateKey reads an Ed25519 private key from a PEM-encoded PKCS#8 file.
func MarshalPrivateKeyPEM ¶
func MarshalPrivateKeyPEM(priv ed25519.PrivateKey) ([]byte, error)
MarshalPrivateKeyPEM encodes an Ed25519 private key as a PKCS#8 PEM block.
func ParsePrivateKeyPEM ¶
func ParsePrivateKeyPEM(pemData []byte) (ed25519.PrivateKey, error)
ParsePrivateKeyPEM decodes a PEM block containing a PKCS#8 Ed25519 private key.
func ReasonFor ¶
ReasonFor maps a sentinel error to the value for the X-Lookout-Reason header returned with 401 responses.
func VerifyRequest ¶
func VerifyRequest( r *http.Request, body []byte, registry *KeyRegistry, lru *NonceLRU, maxSkewSeconds int, ) (keyID string, err error)
VerifyRequest verifies an incoming HTTP request's Ed25519 signature.
It reads the four Ed25519 headers (X-Lookout-Key-ID, X-Lookout-Timestamp, X-Lookout-Nonce, X-Lookout-Signature) and:
- Looks up the key in registry.
- Checks the timestamp against maxSkew.
- Checks the nonce LRU for replay.
- Verifies the Ed25519 signature over the canonical message.
- Records the nonce on success.
body is the raw request body (already read by the caller). The caller is responsible for replacing r.Body with a new reader if needed.
Returns the key ID string and nil on success. Returns ("", err) on failure.
Types ¶
type AuthorizedKey ¶
type AuthorizedKey struct {
// KeyID is hex(SHA-256(raw 32-byte pubkey)[:8]).
KeyID string
// PubKey is the raw 32-byte Ed25519 public key.
PubKey []byte
// Comment is the optional comment text from the authorized_keys line.
Comment string
}
AuthorizedKey represents a single entry in the authorized_keys file.
type Enroller ¶
type Enroller struct {
// OnResult, when non-nil, is invoked after every enrollment attempt with
// the client address, the derived key ID ("" when unavailable), and the
// outcome ("allowed" or "denied"). Used for audit logging.
OnResult func(actor, keyID, outcome string)
// contains filtered or unexported fields
}
Enroller handles one-shot Model C enrollment. It is safe for concurrent use.
func NewEnroller ¶
func NewEnroller(token, authorizedFile string, registry *KeyRegistry) *Enroller
NewEnroller creates an Enroller. token is the pre-configured enrollment secret. authorizedFile is the path to the authorized_keys file. registry is the live key registry to reload after enrollment.
func (*Enroller) ServeHTTP ¶
func (e *Enroller) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler. It accepts POST /api/lookout/enroll, validates the enrollment token, appends the public key, reloads the registry, and burns the token.
The handler must be registered OUTSIDE the auth middleware so it is reachable without a prior credential.
type KeyRegistry ¶
type KeyRegistry struct {
// contains filtered or unexported fields
}
KeyRegistry holds the in-memory map of authorized Ed25519 public keys, loaded from an authorized_keys file. It is safe for concurrent use.
func NewKeyRegistry ¶
func NewKeyRegistry(filePath string) *KeyRegistry
NewKeyRegistry creates a KeyRegistry that will load keys from filePath. Call Load to populate it. filePath may be empty, in which case the registry is permanently empty (no ed25519 auth configured).
func (*KeyRegistry) Len ¶
func (r *KeyRegistry) Len() int
Len returns the number of registered keys.
func (*KeyRegistry) Load ¶
func (r *KeyRegistry) Load() error
Load reads the authorized_keys file and replaces the in-memory key map. It logs keys that were added or removed relative to the previous load. If filePath is empty, Load is a no-op (returns nil).
func (*KeyRegistry) LookupByID ¶
func (r *KeyRegistry) LookupByID(keyID string) (*AuthorizedKey, bool)
LookupByID returns the AuthorizedKey for the given key ID, or (nil, false) if no such key is registered.
type NonceLRU ¶
type NonceLRU struct {
// contains filtered or unexported fields
}
NonceLRU is an in-memory nonce cache that provides replay protection within the timestamp window. It is modelled on RateLimiter in internal/server/middleware.go: a Mutex-protected map with a background cleanup goroutine.
Capacity is bounded to maxSize entries. When the cap is reached, new nonces are silently dropped (fail-open for tracking, not for auth: the timestamp window alone still limits replay to 60 s).
func NewNonceLRU ¶
NewNonceLRU returns a NonceLRU with the given capacity and a TTL equal to the clock-skew window (entries can be evicted after 2× the window to give some extra margin). A background goroutine starts immediately.
func (*NonceLRU) Add ¶
Add records the nonce if it has not been seen before and the cache is not full. Returns true if the nonce was freshly added (not a replay), false if it has been seen before.