keys

package
v0.9.5 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package keys manages the signing key set: generation, rotation with an overlap window, and publication as a JWKS.

Rotation is the reason this package exists. A signer holds exactly one current private key and mints with it. A verifier must accept tokens signed by the previous key until every one of them has expired. The overlap window is what makes a rotation invisible to callers.

Index

Constants

This section is empty.

Variables

View Source
var ErrConflict = errors.New("keys: the store changed under this write")

ErrConflict means the store changed under a write. The caller should reload and try again.

View Source
var ErrNoCurrent = errors.New("keys: no current signing key")

ErrNoCurrent means the set holds no key to sign with.

Functions

func MarshalEntries added in v0.3.0

func MarshalEntries(entries []Entry) ([]byte, error)

MarshalEntries and UnmarshalEntries are the wire format a Store persists. They are exported so an implementation does not have to invent one.

func PublicFromXY added in v0.9.4

func PublicFromXY(x, y string) (*ecdsa.PublicKey, error)

PublicFromXY reconstructs a P-256 public key from base64url-encoded affine coordinates, rejecting a point that is not on the curve. It is the inverse of the x/y a JWK carries.

func Thumbprint added in v0.9.4

func Thumbprint(pub *ecdsa.PublicKey) (string, error)

Thumbprint returns the RFC 7638 JWK thumbprint of pub — the value tsjwt uses as a key id. It is exported so a consumer of a published key set can confirm that a key's declared kid actually belongs to the key, instead of trusting the label supplied alongside it.

Types

type Entry added in v0.3.0

type Entry struct {
	// Kid is the RFC 7638 thumbprint, and the primary key of an entry.
	Kid string `json:"kid"`
	// X and Y are the public point, base64url, as in a JWK.
	X string `json:"x"`
	Y string `json:"y"`
	// Replica names the process that published this key, so a replica can
	// find and refresh its own entries.
	Replica string `json:"replica"`
	// Lease is when this entry stops being refreshed by its owner. A
	// replica that dies stops renewing, and its keys age out.
	Lease time.Time `json:"lease"`
}

Entry is one replica's published verification key.

Only public material appears here. A replica's private key is generated in memory and never leaves the process, so a reader of the store cannot mint anything. That is the whole reason this type exists rather than a shared private key: read access to the store grants nothing.

func UnmarshalEntries added in v0.3.0

func UnmarshalEntries(b []byte) ([]Entry, error)

UnmarshalEntries parses what MarshalEntries wrote. Empty input is an empty set, not an error: a store that has never been written is the normal state on first start.

type JWK

type JWK struct {
	Kty string `json:"kty"`
	Crv string `json:"crv"`
	Kid string `json:"kid"`
	Use string `json:"use"`
	Alg string `json:"alg"`
	X   string `json:"x"`
	Y   string `json:"y"`
}

JWK is a public key in JSON Web Key form. Only the fields needed for an EC P-256 verification key are present.

type JWKS

type JWKS struct {
	Keys []JWK `json:"keys"`
}

JWKS is a JSON Web Key Set, the document published at /.well-known/jwks.json.

type Option

type Option func(*Set)

Option configures a Set.

func WithClock

func WithClock(now func() time.Time) Option

WithClock replaces the time source. It exists for tests.

func WithOverlap

func WithOverlap(d time.Duration) Option

WithOverlap sets how long a retired key remains valid for verification. It must be longer than the longest token lifetime. The default is one hour.

type Published added in v0.3.0

type Published struct {
	// contains filtered or unexported fields
}

Published joins one replica's local signing keys to the set every replica publishes.

Signing always uses the local set. Verification uses the union, so a token minted by any replica verifies at any backend. This is what makes more than one replica possible without a shared private key.

func NewPublished added in v0.3.0

func NewPublished(local *Set, store Store, replica string, opts ...PublishedOption) (*Published, error)

NewPublished wraps a local key set with a shared store. replica must be unique per process; a pod name serves.

func (*Published) Current added in v0.3.0

func (p *Published) Current() (*ecdsa.PrivateKey, string, error)

Current returns the local signing key. Signing never uses a peer's key.

func (*Published) JWKS added in v0.3.0

func (p *Published) JWKS() JWKS

JWKS returns the union of every live replica's public keys, plus this replica's own. The local keys are always included, even if a publish has not yet landed, so a freshly started replica never mints tokens that its own published set cannot verify.

func (*Published) Publish added in v0.3.0

func (p *Published) Publish(ctx context.Context) error

Publish writes this replica's public keys to the store and reads back the union. It removes its own stale entries, and drops any replica's entries that are past lease plus grace.

On a conflict it reloads and retries, because a conflict means another replica published at the same moment, which is expected rather than exceptional.

func (*Published) Refresh added in v0.3.0

func (p *Published) Refresh(ctx context.Context) error

Refresh reloads the union without publishing, so a replica picks up a new peer's key between its own publishes.

func (*Published) Run added in v0.3.0

func (p *Published) Run(ctx context.Context, onError func(error))

Run publishes immediately, then keeps the lease alive until ctx ends.

The interval is a third of the lease, so two refreshes can fail before a healthy replica looks dead to its peers.

type PublishedOption added in v0.3.0

type PublishedOption func(*Published)

PublishedOption configures a Published.

func WithGrace added in v0.3.0

func WithGrace(d time.Duration) PublishedOption

WithGrace sets how long a lapsed entry is still served for verification. It must exceed the longest token lifetime.

func WithLease added in v0.3.0

func WithLease(d time.Duration) PublishedOption

WithLease sets how long a published entry stays fresh without a refresh.

func WithPublishedClock added in v0.3.0

func WithPublishedClock(now func() time.Time) PublishedOption

WithPublishedClock replaces the time source. For tests.

type Set

type Set struct {
	// contains filtered or unexported fields
}

Set is a rotating set of signing keys. It is safe for concurrent use.

At any moment the set holds one current key, used for signing, and zero or more retired keys still inside their overlap window, used only for verification. Keys past the window are dropped and their tokens stop verifying.

func NewSet

func NewSet(opts ...Option) (*Set, error)

NewSet returns a set holding one freshly generated key.

func (*Set) Current

func (s *Set) Current() (*ecdsa.PrivateKey, string, error)

Current returns the key to sign with, and its id.

func (*Set) JWKS

func (s *Set) JWKS() JWKS

JWKS returns the public key set: the current key plus every retired key still inside its overlap window.

Exactly one key type and one algorithm ever appear. A verifier that selects keys from this set cannot be steered onto a different algorithm.

func (*Set) PublicKey

func (s *Set) PublicKey(kid string) (*ecdsa.PublicKey, bool)

PublicKey returns the verification key for kid, if the set still holds it.

func (*Set) Rotate

func (s *Set) Rotate() error

Rotate generates a new key, makes it current, and moves the previous current key into the overlap window. Keys whose window has passed are dropped.

type Store added in v0.3.0

type Store interface {
	// Load returns the current entries and an opaque version.
	Load(ctx context.Context) ([]Entry, string, error)
	// Save replaces the entries, but only if the store is still at
	// version. It returns ErrConflict otherwise.
	Save(ctx context.Context, entries []Entry, version string) error
}

Store holds the published key entries. Implementations must support optimistic concurrency: Save fails with ErrConflict if the stored version is not the one that was loaded.

Jump to

Keyboard shortcuts

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