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 ¶
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.
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
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
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
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
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 WithOverlap ¶
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
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
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.
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 (*Set) Current ¶
func (s *Set) Current() (*ecdsa.PrivateKey, string, error)
Current returns the key to sign with, and its id.
func (*Set) 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.
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.