auth

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2026 License: AGPL-3.0 Imports: 21 Imported by: 0

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

View Source
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

View Source
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

func AuthorizedKeyLine(pub ed25519.PublicKey, comment string) string

AuthorizedKeyLine formats an Ed25519 public key as an authorized_keys line.

func BodyHashHex

func BodyHashHex(b []byte) string

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

func CanonicalMessage(method, path, bodyHashHex string, timestampUnix int64, nonce string) []byte

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

func GenerateKeyPair(comment string) (privPEM []byte, authKeyLine string, err error)

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

func KeyIDForPublicKey(pub ed25519.PublicKey) string

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 NewNonce

func NewNonce() (string, error)

NewNonce generates a fresh 128-bit random nonce as a 32-character hex string.

func ParsePrivateKeyPEM

func ParsePrivateKeyPEM(pemData []byte) (ed25519.PrivateKey, error)

ParsePrivateKeyPEM decodes a PEM block containing a PKCS#8 Ed25519 private key.

func ReasonFor

func ReasonFor(err error) string

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:

  1. Looks up the key in registry.
  2. Checks the timestamp against maxSkew.
  3. Checks the nonce LRU for replay.
  4. Verifies the Ed25519 signature over the canonical message.
  5. 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

func NewNonceLRU(maxSize int, windowSeconds int) *NonceLRU

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

func (l *NonceLRU) Add(nonce string) bool

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.

func (*NonceLRU) Len

func (l *NonceLRU) Len() int

Len returns the current number of tracked nonces.

func (*NonceLRU) Seen

func (l *NonceLRU) Seen(nonce string) bool

Seen reports whether the nonce has been recorded in the cache.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL