Help us shape the future of Go! Take the 2026 Go Developer Survey

clortho

package module
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: Apache-2.0 Imports: 31 Imported by: 8

README

clortho

clortho provides clientside management for cryptographic keys.

Build Status codecov.io Apache V2 License GitHub Release GoDoc

Summary

clortho manages cryptographic keys, either locally supplied or remotely hosted.

Table of Contents

Code of Conduct

This project and everyone participating in it are governed by the XMiDT Code Of Conduct. By participating, you agree to this Code.

Details

clortho fetches, caches, and refreshes the keys a service needs to verify JWS signatures, such as those on JWTs, and exposes them to jwx as a jws.KeyProvider. A KeyRing holds the keys, a Refresher keeps it current from configured sources, and NewKeyProvider wraps it for verification. An optional Resolver fetches individual keys on demand for callers that need one, and clorthofx wires all of it into a go.uber.org/fx application.

The package documentation at pkg.go.dev/github.com/xmidt-org/clortho is the full overview, including which configuration feeds verification and which does not.

Install

go get -u github.com/xmidt-org/clortho

Contributing

Refer to CONTRIBUTING.md.

Documentation

Overview

Package clortho provides key management for clients, and in particular the keys needed to verify JWS signatures such as those on JWTs.

The verification path is:

  • A KeyRing holds keys, looked up by kid (key ID).
  • A Refresher polls one or more sources, configured by Config.Refresh, on a schedule and keeps the KeyRing current.
  • NewKeyProvider wraps the KeyRing as a jws.KeyProvider for github.com/lestrrat-go/jwx/v4, so that jws.Verify and jwt.Parse can find the key named by a token's kid.

The KeyRing is the only source of keys for verification. The provider never fetches a key on demand, so an unverified token cannot cause an outbound request. A deployment therefore needs at least one refresh source; passing WithConfig to NewKeyProvider makes a missing source a startup error.

A Resolver is separate from that path. It fetches individual keys by ID from a URI template, configured by Config.Resolve, optionally using a KeyRing as a cache. It serves callers that need a specific key on demand and is not consulted during verification.

Loaders and Parsers sit underneath both. The default Loader handles http, https, and file URIs as well as plain file system paths, and the default Parser handles JWK, JWK sets, and PEM. Both accept options for custom schemes and formats.

NewKeyProvider, NewResolver, and NewRefresher return a nil value alongside a non-nil error. Errors from the key provider carry sentinels such as ErrKeyProviderKeyNotFound, intended for errors.Is rather than message matching.

Index

Constants

View Source
const (
	// DefaultRefreshInterval is used as the base interval between key refreshes when an
	// interval couldn't be determined any other way.
	DefaultRefreshInterval = time.Hour * 24

	// DefaultRefreshMinInterval is the hard minimum for the base interval between key refreshes
	// regardless of how the base interval was determined.
	DefaultRefreshMinInterval = time.Minute * 10

	// DefaultRefreshMaxInterval is the hard maximum for the interval between key refreshes
	// regardless of how the interval was determined.  In particular, a source cannot push
	// the interval past this by serving a large Cache-Control max-age.
	DefaultRefreshMaxInterval = time.Hour * 24 * 7

	// DefaultRefreshJitter is the default randomization factor for key refreshes.
	DefaultRefreshJitter = 0.1
)
View Source
const (
	// MediaTypeJSON is the media type for JSON data.  By default, content with this media type
	// may contain either a single JWK or a JWK set.
	MediaTypeJSON = "application/json"

	// SuffixJSON is the file suffix for JSON data.  By default, files with this suffix may
	// contain either a single JWK or a JWK Set.
	SuffixJSON = ".json"

	// MediaTypeJWK is the media type for a single JWK.
	MediaTypeJWK = "application/jwk+json"

	// SuffixJWK is the file suffix for a single JWK.
	SuffixJWK = ".jwk"

	// MediaTypeJWKSet is the media type for a JWK set.
	MediaTypeJWKSet = "application/jwk-set+json"

	// SuffixJWKSet is the file suffix for a JWK set.
	SuffixJWKSet = ".jwk-set"

	// MediaTypePEM is the media type for a PEM-encoded key.
	MediaTypePEM = "application/x-pem-file"

	// SuffixPEM is the file suffix for a PEM-encoded key.
	SuffixPEM = ".pem"
)
View Source
const (
	// KeyIDParameter is the name of the URI template parameter for expanding key URIs.
	KeyIDParameterName = "keyID"
)

Variables

View Source
var (
	ErrMissingConnectionDetails = errors.New("connection tls/trust details are missing")
	ErrMissingConnectionTrust   = errors.New("connection trust value is missing")
)
View Source
var (
	// ErrKeyProviderKeyNotFound indicates that the kid in the protected header is not
	// on the key ring.
	ErrKeyProviderKeyNotFound = errors.New("key provider failed to find the request kid in its keyring")

	// ErrKeyProviderMissingKeyID indicates that the protected header has no kid, so
	// there is nothing to look up on the ring.
	ErrKeyProviderMissingKeyID = errors.New(`payload must contain a "kid" field in its protected header`)

	// ErrKeyProviderMissingAlg indicates that the protected header has no alg, so the
	// key cannot be offered for verification under any algorithm.
	ErrKeyProviderMissingAlg = errors.New(`protected header must contain an "alg" field`)

	// ErrKeyProviderKeyUsage indicates that the key found on the ring is marked, via
	// its JWK "use" member, for something other than signature verification.  This
	// check is on by default; see WithIgnoreKeyUsage.
	ErrKeyProviderKeyUsage = errors.New("key is not marked for signature use")

	// ErrKeyProviderSymmetricKey indicates that the key found on the ring is a
	// symmetric (kty "oct") key.  Such keys are rejected by default: a JWKS is
	// public, so a secret published in one is known to anyone who fetched it, and
	// any HMAC token bearing its kid would verify.  See WithAllowSymmetricKeys.
	ErrKeyProviderSymmetricKey = errors.New("symmetric keys are not accepted for signature verification")

	// ErrKeyProviderKeyImport indicates that the key found on the ring could not be
	// converted into a form jwx can verify with.  It is joined with the jwx error.
	ErrKeyProviderKeyImport = errors.New("key provider could not import the key from its keyring")

	// ErrKeyProviderNoKeyRing indicates that no KeyRing was supplied to NewKeyProvider.
	// A key ring is required, as it is the only source of keys for verification.
	ErrKeyProviderNoKeyRing = errors.New("key provider requires a key ring; see WithKeyRing")

	// ErrNoRefreshSources indicates that a Config supplied to NewKeyProvider has no
	// refresh sources.  Keys are served to the verifier from the refreshed key ring,
	// not resolved per request, so without sources the ring never fills and every
	// token fails.  Config.Resolve plays no part in verification.
	ErrNoRefreshSources = errors.New("at least one refresh source is required; keys are served to the verifier from the refreshed key ring, not resolved per request")
)
View Source
var (
	// ErrRefresherStarted is returned by Refresher.Start if the Refresher is running.
	ErrRefresherStarted = errors.New("that refresher has already been started")

	// ErrRefresherStopped is returned by Refresher.Stop if the Refresher is not running.
	ErrRefresherStopped = errors.New("that refresher is not running")
)
View Source
var (
	// ErrNoTemplate indicates that no URI template is available for that Resolver's method.
	ErrNoTemplate = errors.New("no URI template expander has been configured for that method")

	// ErrKeyNotFound indicates that a key could not be resolved, e.g. a key ID did not exist.
	ErrKeyNotFound = errors.New("no such key exists")

	// ErrInvalidKeyID indicates that a key ID was rejected before being expanded
	// into a URI.  See ValidateKeyID for the default rule and WithKeyIDValidator to
	// change it.  A custom validator's error is wrapped so that this sentinel still
	// classifies it.
	ErrInvalidKeyID = errors.New("invalid key ID")

	// ErrUnsafeTemplate indicates that a URI template lets the key ID decide where
	// keys are fetched from, or has no literal origin at all, so no expansion of it
	// could be checked.  A template must begin with a literal scheme and, for http
	// and https, a literal host, and its variable must come after the path begins.
	ErrUnsafeTemplate = errors.New("URI template must have a literal scheme, host, and path before its variable")

	// ErrLocationOutsideTemplate indicates that an expanded location did not stay
	// within the origin and path prefix of the template it came from, and was not
	// fetched.  This is defense in depth behind ValidateKeyID: it holds even when a
	// custom validator allows path characters.
	ErrLocationOutsideTemplate = errors.New("resolved location is outside the configured template")

	// ErrFetchPanicked is returned to goroutines that were waiting on a fetch which
	// panicked in another goroutine.  The panic itself propagates in the goroutine
	// that performed the fetch.
	ErrFetchPanicked = errors.New("the fetch for that key panicked")
)
View Source
var ErrInvalidLocation = errors.New("location is not a valid URL")

ErrInvalidLocation indicates that a location could not be parsed as a URL and so no request was made for it. The message quotes the location with any password redacted.

Functions

func NewKeyProvider added in v0.3.0

func NewKeyProvider(opts ...KeyProviderOption) (jws.KeyProvider, error)

NewKeyProvider constructs a jws.KeyProvider that draws keys from a KeyRing, matching the kid in a JWS protected header against the keys the ring currently holds.

A KeyRing is required; supply one with WithKeyRing. Note that the ring is the only source of keys for verification: a Resolver is deliberately not consulted, so that an unverified kid cannot trigger an outbound fetch. Keys reach the ring by way of a Refresher configured from Config.Refresh.Sources. Config.Resolve is not involved.

Passing WithConfig makes this function check that the Config has at least one refresh source, returning ErrNoRefreshSources otherwise. That turns the most common misconfiguration, a resolve template with no refresh sources, into a startup error rather than a "key not found" on every token.

Two checks apply to every key the provider takes from the ring, both on by default. A key whose JWK "use" is set to something other than "sig" is rejected with ErrKeyProviderKeyUsage; see WithIgnoreKeyUsage. A symmetric (kty "oct") key is rejected with ErrKeyProviderSymmetricKey, because a secret that reached the ring through a public JWKS lets anyone who read that JWKS mint tokens; see WithAllowSymmetricKeys.

If no key ring is supplied, or if any option returns an error, this function returns a nil jws.KeyProvider along with a non-nil error. Callers must not use the returned provider when the error is non-nil.

func SetContentMeta added in v0.2.0

func SetContentMeta(ctx context.Context, meta ContentMeta) context.Context

SetContentMeta stores the ContentMeta from a previous load in the context, so that a subsequent load of the same location can be conditional (e.g. If-Modified-Since). The Refresher does this between refreshes. It is optional for all other callers.

func ValidateKeyID added in v0.4.0

func ValidateKeyID(keyID string) error

ValidateKeyID is the default rule a Resolver applies to a key ID before expanding it into a URI. The key ID is the one input to this package that arrives from an unauthenticated party, and a URI template gives it a path into a file system or an HTTP request. This rule rejects, with ErrInvalidKeyID: an empty key ID; a path separator, '/' or '\\'; the sequence ".."; the URI delimiters '?', '#' and '%', the last because a reserved expansion such as {+keyID} passes percent sequences through unencoded; and any whitespace or control character.

A deployment whose key IDs legitimately contain something on that list, such as a URL, supplies its own rule with WithKeyIDValidator.

Types

type CancelListenerFunc

type CancelListenerFunc func()

CancelListenerFunc removes the listener it's associated with and cancels any future events sent to that listener.

A CancelListenerFunc is idempotent: after the first invocation, calling this closure will have no effect.

type Config

type Config struct {
	// Resolve is the subset of configuration that establishes how individual
	// keys will be resolved (or, fetched) on demand by a Resolver.  It is not
	// used for JWT verification; see ResolveConfig.
	Resolve ResolveConfig `json:"resolve" yaml:"resolve"`

	// Refresh is the subset of configuration that configures how keys are
	// refreshed asynchronously.  This is what feeds the key ring that a
	// jws.KeyProvider verifies against; see RefreshConfig.
	Refresh RefreshConfig `json:"refresh" yaml:"refresh"`
}

Config configures clortho from (possibly) externally unmarshaled locations.

type ConfigOption added in v0.3.3

type ConfigOption interface {
	ResolverOption
	RefresherOption
	KeyProviderOption
}

ConfigOption is the return type of WithConfig, and nothing more. Each constructor takes its own option interface, and Go has no way to say "accepted by all three" except an interface that embeds all three. This is the same pattern as ResolverRefresherOption, widened to include NewKeyProvider. Callers never implement, name, or call methods on it; they pass the value straight to a constructor.

func WithConfig added in v0.0.3

func WithConfig(cfg Config) ConfigOption

WithConfig uses a Config struct to configure a Resolver, a Refresher, or a jws.KeyProvider.

For a Resolver, Config.Resolve supplies the key ID template. For a Refresher, Config.Refresh supplies the sources. For a jws.KeyProvider, the Config is not used to fetch anything; NewKeyProvider only checks that Config.Refresh has at least one source, and returns ErrNoRefreshSources otherwise. Supply it there so that a deployment configured with only a resolve template fails at startup instead of rejecting every token.

type ContentMeta

type ContentMeta struct {
	// Format describes the type of key content.  This will typically be either
	// a file suffix (e.g. .pem, .jwk) or a media type (e.g. application/json, application/json+jwk).
	// A custom Loader is free to produce its own format values, which must be
	// understood by a corresponding Parser.
	Format string

	// TTL is the length of time this content is considered current.  A Refresher will
	// use this value to determine when to load content again.
	TTL time.Duration

	// LastModified is the modification timestamp of the content.  For files, this will be
	// the FileInfo.ModTime() value.  For HTTP responses, this will be the Last-Modified header.
	//
	// In the case of HTTP, this field is also used to supply a Last-Modified header in the
	// request.
	LastModified time.Time
}

ContentMeta holds metadata about a piece of content.

func GetContentMeta added in v0.2.0

func GetContentMeta(ctx context.Context) (meta ContentMeta, ok bool)

GetContentMeta returns the ContentMeta stored in the context by SetContentMeta. The returned bool is false when none was stored. Loaders treat that case as the zero value, so callers are never required to seed one.

type Expander

type Expander interface {
	// Expand takes a value map and returns the URI resulting from that expansion.
	Expand(any) (string, error)
}

Expander is the strategy for expanding a URI template.

func NewExpander

func NewExpander(rawTemplate string) (Expander, error)

NewExpander constructs an Expander from a URI template.

type Fetcher

type Fetcher interface {
	// This method ensures that each key has a key ID.  For keys that do not have a key ID from their source,
	// a key ID is generated using a thumbprint hash.
	Fetch(ctx context.Context, location string) (keys []Key, metadata ContentMeta, err error)
}

Fetcher handles fetching keys from URI locations. This is the typical application-layer interface. Generally, clients should use this interface over Loader and Parser.

func NewFetcher

func NewFetcher(options ...FetcherOption) Fetcher

NewFetcher produces a Fetcher from a set of configuration options.

type FetcherOption

type FetcherOption interface {
	// contains filtered or unexported methods
}

FetcherOption is a configuration option passed to NewFetcher.

func WithKeyIDHash

func WithKeyIDHash(h crypto.Hash) FetcherOption

WithKeyIDHash sets the cryptographic hash used to generate key IDs for keys which do not have them. By default, crypto.SHA256 is used.

func WithLoader

func WithLoader(l Loader) FetcherOption

WithLoader defines the Loader strategy for a Fetcher. By default, a Fetcher uses a Loader created with no options.

func WithParser

func WithParser(p Parser) FetcherOption

WithParser defines the Parser strategy for a Fetcher. By default, a Fetcher uses a Parser created with no options.

type FileLoader

type FileLoader struct {
	// Root is the relative root against which all location paths are resolved.
	// This field is required.
	//
	// The Loader created by NewLoader uses os.DirFS("/") for this field, so
	// that absolute paths and file:// URIs resolve naturally.
	Root fs.FS
}

FileLoader is a Loader implementation that reads content from a file system. All location paths are relative to a supplied root.

func (FileLoader) LoadContent

func (fl FileLoader) LoadContent(ctx context.Context, location string) ([]byte, ContentMeta, error)

type HTTPClient

type HTTPClient interface {
	Do(*http.Request) (*http.Response, error)
}

HTTPClient is the minimal interface required by a component which can handle HTTP transactions with a server. *http.Client implements this interface.

type HTTPEncoder

type HTTPEncoder func(context.Context, *http.Request) error

HTTPEncoder is a strategy closure type for modifying an HTTP request prior to issuing it through a client.

type HTTPLoader

type HTTPLoader struct {
	// Client is the HTTP client used to transact with HTTP servers.
	// If unset, http.DefaultClient is used.
	Client HTTPClient

	// Encoders holds an optional slice of HTTPEncoder instances that are used
	// to modify requests prior to sending them to the Client.
	Encoders []HTTPEncoder

	// Timeout is an optional timeout for each HTTP operation.  If unset,
	// no timeout is used.
	Timeout time.Duration

	// MaxReadLimit limits how many bytes are read from responses.  A body larger
	// than this results in a ResponseTooLargeError.  If unset, no limit is applied.
	MaxReadLimit int64
}

HTTPLoader is a Loader strategy for obtaining content from HTTP servers.

func (HTTPLoader) LoadContent

func (hl HTTPLoader) LoadContent(ctx context.Context, location string) ([]byte, ContentMeta, error)

type HTTPLoaderError

type HTTPLoaderError struct {
	Location   string
	StatusCode int
}

HTTPLoaderError indicates that an error occurred when transacting with a HTTP-based source of key material. Location has any password redacted.

func (*HTTPLoaderError) Error

func (hle *HTTPLoaderError) Error() string

type InvalidFormatError added in v0.0.4

type InvalidFormatError struct {
	Format string
}

InvalidFormatError indicates that a format cannot be associated with a Parser because the format string is invalid. For example, format strings that contain semi-colons (;) are invalid because matching a Parser by MIME parameters is not supported.

func (InvalidFormatError) Error added in v0.0.4

func (ife InvalidFormatError) Error() string

Error satisfies the error interface.

type JWKKeyParser

type JWKKeyParser struct {
	Options []jwk.ParseOption
}

JWKKeyParser parses content as a single JWK.

func (JWKKeyParser) Parse

func (jkp JWKKeyParser) Parse(_ string, data []byte) ([]Key, error)

Parse expects data to be a single JWK. If data is a JWK set, this method returns an error.

type JWKSetParser

type JWKSetParser struct {
	Options []jwk.ParseOption
}

JWKSetParser parses content as a JWK set.

func (JWKSetParser) Parse

func (jsp JWKSetParser) Parse(_ string, data []byte) ([]Key, error)

Parse allows data to be either a single JWK or a JWK set. For a single JWK, a 1-element slice is returned.

type Key

type Key interface {
	Thumbprinter

	// KeyID is the identifier for this Key.  This method corresponds to the kid field of a JWK.
	// Note that a KeyID is entirely optional.  This method can return the empty string.
	KeyID() string

	// KeyType is the type of this Key, e.g. EC, RSA, etc.  This method corresponds to
	// the kty field of a JWK.
	//
	// A KeyType is required.  This method always returns a non-empty string.
	KeyType() string

	// KeyUsage describes how this key is allowed to be used.  This method corresponds to
	// the use field of a JWK.
	//
	// A KeyUsage is optional.  This method can return the empty string.
	KeyUsage() string

	// Raw is the raw key, e.g. *rsa.PublicKey, *rsa.PrivateKey, etc.  This is the actual underlying
	// cryptographic key that should be used.
	Raw() any

	// Public is the public portion of the raw key.  If this key is already a public key, this method
	// returns the same key as Raw.
	Public() crypto.PublicKey
}

Key is the minimal interface for cryptographic keys. Once created, a Key is immutable.

func EnsureKeyID

func EnsureKeyID(k Key, h crypto.Hash) (updated Key, err error)

EnsureKeyID conditionally assigns a key ID to a given key. The updated Key is returned, along with any error from the hash.

If k already has a key ID, it is returned as is with no error.

If k does not have a key ID, a thumbprint is generated using the supplied hash. The returned key will be a copy of k with the newly generated key ID, and it remembers that the key ID was generated, which a Resolver relies on. A Key implementation other than this package's own is wrapped rather than copied; everything but its key ID is still served by the original, but the returned value is the wrapper, so a type assertion to the original's type will not succeed on it. A custom Key that already has a key ID is never wrapped. If an error occurred, then k is returned as is.

type KeyAccessor

type KeyAccessor interface {
	// Get returns the Key associated with the given key identifier (kid).
	// If there is no such key, the second return is false.
	Get(keyID string) (Key, bool)

	// Len returns the number of keys currently in this collection.
	Len() int
}

KeyAccessor is a read-only interface to a set of keys.

type KeyProviderOption added in v0.3.0

type KeyProviderOption interface {
	// contains filtered or unexported methods
}

KeyProviderOption represents a configurable option for building a jws.KeyProvider.

func WithAllowSymmetricKeys added in v0.4.0

func WithAllowSymmetricKeys() KeyProviderOption

WithAllowSymmetricKeys makes a jws.KeyProvider accept symmetric (kty "oct") keys from its ring.

By default such keys are rejected with ErrKeyProviderSymmetricKey. A JWKS is public, so a secret published in one, by mistake or otherwise, is known to anyone who fetched it, and any HMAC token carrying its kid would verify. Use this option only when the ring is fed from a source that is genuinely private to the verifier, such as a local file, and the shared secret is intended.

func WithEnforceKeyUsage deprecated added in v0.3.3

func WithEnforceKeyUsage() KeyProviderOption

WithEnforceKeyUsage selects the default behavior, rejecting keys whose JWK "use" member is set to something other than "sig".

Deprecated: this has been the default since v0.4.0, so the option is a no-op. Use WithIgnoreKeyUsage to turn the check off. This option will be removed in a future release.

func WithIgnoreKeyUsage added in v0.4.0

func WithIgnoreKeyUsage() KeyProviderOption

WithIgnoreKeyUsage makes a jws.KeyProvider verify with any key on its ring regardless of the key's JWK "use" member.

By default a provider rejects a key whose "use" is set to something other than "sig", with ErrKeyProviderKeyUsage, as RFC 7517 section 4.2 intends and as jwx's own key set provider does. Releases before v0.4.0 did not check "use"; this option restores that behavior for a deployment that needs it.

type KeyRing

type KeyRing interface {
	KeyAccessor
	RefreshListener

	// Add allows ad hoc keys to be added to this ring.  Any key that has
	// no key ID will be skipped.
	//
	// This method returns the actual count of keys added.  This will include
	// keys already in the ring, since those will be overwritten with the new Key object.
	Add(...Key) int

	// Remove allows add hoc keys to be removed from this ring.  Any key ID that isn't
	// in this ring is ignored.  The actual count of deleted keys is returned.
	Remove(keyIDs ...string) int

	Clone() []Key
}

KeyRing is a client-side cache of keys. Implementations are always safe for concurrent access.

A KeyRing can consume events from a Refresher, which will update the ring's set of keys.

func NewKeyRing

func NewKeyRing(initialKeys ...Key) KeyRing

NewKeyRing constructs a KeyRing with an optional set of initial keys. Any key that has no key ID is skipped.

type KeyRingOption added in v0.3.3

type KeyRingOption interface {
	ResolverOption
	KeyProviderOption
}

KeyRingOption is the return type of WithKeyRing, and nothing more. It is an interface embedding both option types so that the one value can be passed to NewResolver or NewKeyProvider; see ConfigOption for the same pattern.

func WithKeyRing

func WithKeyRing(kr KeyRing) KeyRingOption

WithKeyRing associates a KeyRing with a Resolver or a jws.KeyProvider.

For a Resolver, the ring acts as a cache: keys found on it are returned without a fetch, and fetched keys are added to it. By default, a Resolver has no ring.

For a jws.KeyProvider, the ring is the only source of keys, and is required.

type Keys

type Keys []Key

Keys is a sortable slice of Key instances. Sorting is done by keyID, ascending. Keys with no keyID are sorted after those that have a keyID.

func (Keys) AppendKeyIDs added in v0.0.3

func (ks Keys) AppendKeyIDs(v []string) []string

AppendKeyIDs appends the key Id of each key to the supplied slice, then returns the result.

func (Keys) Len

func (ks Keys) Len() int

Len returns the count of Key instances in this collection.

func (Keys) Less

func (ks Keys) Less(i, j int) bool

Less tests if the Key at i is less than the one at j.

func (Keys) Swap

func (ks Keys) Swap(i, j int)

Swap switches the positions of the Keys at i and j.

type Loader

type Loader interface {
	// LoadContent retrieves the key content from location.  Location must be a URL parseable
	// with url.Parse.
	//
	// This method returns a ContentMeta describing useful characteristics of the content, mostly around
	// caching.  This returned metadata can be passed to subsequent calls to make key retrieval more
	// efficient.
	LoadContent(ctx context.Context, location string) ([]byte, ContentMeta, error)
}

Loader handles the retrieval of content from an external location.

func NewLoader

func NewLoader(options ...LoaderOption) (Loader, error)

NewLoader builds a Loader from a set of options.

By default, the returned Loader handles http, https, and file locations. The default loader, when there is no scheme, is a file loader.

type LoaderOption

type LoaderOption interface {
	// contains filtered or unexported methods
}

LoaderOption represents a configurable option for building a Loader.

func WithSchemes

func WithSchemes(l Loader, schemes ...string) LoaderOption

WithSchemes registers a loader as handling one or more URI schemes. Use this to add custom schemes or to override one of the schemes a loader handles by default.

By default, a Loader created with NewLoader handles the file, http, and https schemes, as well as file paths without a scheme.

type NotAFileError

type NotAFileError struct {
	Location string
}

NotAFileError indicates that a file URI didn't refer to a system file, but instead referred to a directory, pipe, etc. Location has any password redacted.

func (*NotAFileError) Error

func (nafe *NotAFileError) Error() string

type Parser

type Parser interface {
	// Parse parses data, expected to be in the given format, into zero or more Keys.
	// If only one key is present in the data, this method returns a 1-element slice.
	//
	// Format is an opaque string which used as a key to determine which parsing algorithm
	// to apply to the data.  Most commonly, format is either a file suffix (including the
	// leading '.') or a media type such as application/json.  If format contains any MIME
	// parameters, e.g. text/xml;charset=utf-8, they are ignored.
	//
	// Custom parsers should usually avoid trying to validate format.  This is because
	// a Parser might be registered with a nonstandard format.  The format is available to
	// custom parser code primarily for debugging.
	Parse(format string, data []byte) ([]Key, error)
}

Parser turns raw data into one or more Key instances.

func NewParser

func NewParser(options ...ParserOption) (Parser, error)

NewParser returns a Parser tailored with the given options.

The returned Parser handles the following formats by default:

application/json
application/jwk+json
application/jwk-set+json
application/x-pem-file
.json
.jwk
.jwk-set
.pem

A caller can use WithFormats to change the parser associated with a format or to register a Parser for a new, custom format.

type ParserOption

type ParserOption interface {
	// contains filtered or unexported methods
}

ParserOption allows tailoring of the Parser returned by NewParser.

func WithFormats

func WithFormats(p Parser, formats ...string) ParserOption

WithFormats associates a Parsers with one or more formats. Each format is an opaque string simply used as a way to look up a parsing algorithm. Typically, a format is a file suffix (including the leading '.') or a media type such as application/json.

type RefreshConfig

type RefreshConfig struct {
	// Sources are the set of refresh sources to be polled for key material.
	//
	// If this slice is empty, a Refresher is still created, but it will
	// do nothing.  NewKeyProvider rejects a Config with no sources; see
	// ErrNoRefreshSources.
	//
	// If there are multiple sources with the same URI, an error is raised.
	Sources []RefreshSource `json:"sources" yaml:"sources"`
}

RefreshConfig configures all aspects of key refresh. This is the only path by which keys reach a jws.KeyProvider: a Refresher polls these sources and fills the key ring the provider reads from.

type RefreshEvent

type RefreshEvent struct {
	// URI is the source of the keys, with any password in its userinfo redacted.
	URI string

	// Err is the error that occurred while trying to interact with the URI.
	// This field can be nil to indicate no error.  When this field is non-nil,
	// the Keys field will be populated with the last known valid set of keys
	// from the given URI.
	Err error

	// Keys represents the complete set of keys from the URI.  When Err is not nil,
	// this field will be set to the last known valid set of keys.
	//
	// This field will be sorted by KeyID.
	Keys Keys

	// New are the keys that a brand new with this event.  These keys will be
	// included in the Keys field.
	//
	// This field will be sorted by KeyID.
	New Keys

	// Deleted are the keys that are now missing from the refreshed keys.
	// These keys will not be in the Keys field.  These keys will have been present
	// in the previous event(s).
	//
	// This field will be sorted by KeyID.
	Deleted Keys
}

RefreshEvent represents a set of keys from a given URI that has been asynchronously fetched.

type RefreshListener

type RefreshListener interface {
	// OnRefreshEvent receives a refresh event.  This method must not panic.
	OnRefreshEvent(RefreshEvent)
}

RefreshListener is a sink for RefreshEvents.

type RefreshSource

type RefreshSource struct {
	// URI is the location where keys are served.  By default, clortho supports
	// file://, http://, and https:// URIs, as well as standard file system paths
	// such as /etc/foo/bar.jwk.
	//
	// This field is required and has no default.
	URI string `json:"uri" yaml:"uri"`

	// Interval is the base time between refreshing keys from this source.  This value
	// is used when the source URI doesn't specify any sort of time-to-live or expiry.
	// For example, if an http source doesn't specify a Cache-Control header, this value is used.
	//
	// If this field is not positive, DefaultRefreshInterval is used.
	Interval time.Duration `json:"interval" yaml:"interval"`

	// MinInterval specifies the absolute minimum time between key refreshes from this source.
	// Regardless of HTTP headers, the Interval field, etc, key refreshes will not occur more
	// often than this field indicates.
	//
	// If this value is not positive, DefaultRefreshMinInterval is used.
	MinInterval time.Duration `json:"minInterval" yaml:"minInterval"`

	// MaxInterval specifies the absolute maximum time between key refreshes from this source.
	// Regardless of HTTP headers, the Interval field, etc, key refreshes will occur at least
	// this often.  This bounds how long a source can make the refresher wait by serving a
	// large max-age, so that rotated or revoked keys are not trusted indefinitely.
	//
	// If this value is not positive, the larger of DefaultRefreshMaxInterval and the top
	// of the jitter window around the effective Interval, (1+Jitter)*Interval, is used, so
	// a deliberately long Interval keeps its full spread rather than being cut short by
	// the default.  If it is less than the effective MinInterval, MinInterval is used,
	// since that is the hard floor.
	MaxInterval time.Duration `json:"maxInterval" yaml:"maxInterval"`

	// Jitter is the randomization factor applied to the interval between refreshes.  No matter how
	// the interval is determined (e.g. Cache-Control, Interval field, etc), a random value between
	// [1-Jitter,1+Jitter]*interval is used as the actual time before the next attempted refresh.
	// That window is then clipped to [MinInterval, MaxInterval]; an explicit MaxInterval below
	// the top of the window therefore narrows it.
	//
	// Valid values are between 0.0 and 1.0, exclusive.  If this value is outside that range,
	// including being unset, DefaultRefreshJitter is used instead.
	Jitter float64 `json:"jitter" yaml:"jitter"`
}

RefreshSource describes a single location where keys are retrieved on a schedule.

type Refresher

type Refresher interface {
	// Start bootstraps tasks that fetch keys and dispatch events to any listeners.
	// Keys will arrive asynchronously to any registered listeners.
	//
	// If this Refresher has already been started, this method returns ErrRefresherStarted.
	Start(context.Context) error

	// Stop shuts down all refresh tasks.
	//
	// If this Refresher is not running, this method returns ErrRefresherStopped.
	Stop(context.Context) error

	// AddListener registers a channel that receives refresh events.  No caching of events
	// is done.  The supplied listener will receive events the next time any of the key
	// sources are queried.
	//
	// The returned closure can be used to cancel refreshes sent to the listener.  Clients
	// are not required to use this closure, particularly if the listener is active for the
	// life of the application.
	AddListener(l RefreshListener) CancelListenerFunc
}

Refresher handles asynchronously refreshing sets of keys from one or more sources.

func NewRefresher

func NewRefresher(options ...RefresherOption) (Refresher, error)

NewRefresher constructs a Refresher using the supplied options. Without any options, a default Loader and Parser are created and used. Whenever the returned error is non-nil, the returned Refresher is nil.

type RefresherOption

type RefresherOption interface {
	// contains filtered or unexported methods
}

RefresherOption is a configurable option passed to NewRefresher.

func WithSources

func WithSources(sources ...RefreshSource) RefresherOption

WithSources associates external sources of keys with a Refresher. This option is cumulative: all sources from each call to WithSources will be added to the configured Refresher.

type ResolveConfig

type ResolveConfig struct {
	// Template is a URI template used to fetch keys.  This template may
	// use a single parameter named keyID, e.g. http://keys.com/{keyID}.
	//
	// The template must begin with a literal scheme and host, and the keyID
	// parameter must come after the path begins; a template that lets the key ID
	// decide the host, or that is only the parameter, is rejected with
	// ErrUnsafeTemplate.  See WithKeyIDTemplate.
	//
	// If empty, a Resolver built from this configuration serves only keys
	// already on its ring, and reports ErrNoTemplate for any other key ID.
	Template string `json:"template" yaml:"template"`

	// Timeout refers to the maximum time to wait for a refresh operation.
	// There is no default for this field.  If unset, no timeout is applied.
	Timeout time.Duration `json:"timeout" yaml:"timeout"`
}

ResolveConfig configures how to fetch individual keys on demand. It is used by NewResolver, via WithConfig, and by nothing else. A Resolver serves callers that ask for a specific key by ID, such as clients that need a key to verify a particular message.

ResolveConfig plays no part in JWT verification through NewKeyProvider: the provider reads only the key ring, which is filled from RefreshConfig.Sources. A deployment that sets only a resolve template will never have a key to verify with.

type ResolveEvent

type ResolveEvent struct {
	// URI is the actual, expanded URI used to obtain the key material, with any
	// password in its userinfo redacted.  It is empty when the key ID was rejected
	// before expansion.
	URI string

	// KeyID is the key ID that was resolved.
	KeyID string

	// Key is the key material that was returned from the URI.
	Key Key

	// Err holds any error that occurred while trying to fetch key material.
	// If this field is set, Key will be nil.
	Err error
}

ResolveEvent holds information about a key ID that has been resolved.

type ResolveListener

type ResolveListener interface {
	// OnResolveEvent receives notifications for attempts to resolve keys.  This
	// method must not panic.
	OnResolveEvent(ResolveEvent)
}

ResolveListener is a sink for ResolveEvents.

type Resolver

type Resolver interface {
	// Resolve attempts to locate a key with a given keyID (kid).
	//
	// A fetched key is returned only if it is the one asked for.  In a key set,
	// that means the key whose kid matches.  A single-key response must either
	// carry the requested kid or have had no kid at all, in which case it is
	// returned under the requested kid; a single key with a different kid is
	// reported as ErrKeyNotFound and never reaches the ring.
	//
	// A key ID that is not on the ring is validated before it is expanded into a
	// URI, by ValidateKeyID unless WithKeyIDValidator was given; a rejected key ID
	// fails with ErrInvalidKeyID, causes no fetch, and is reported to listeners as
	// a ResolveEvent with the error set and no URI.  After expansion, the location
	// must stay within the template's literal scheme, host, and path prefix, or it
	// fails with ErrLocationOutsideTemplate, again with no fetch; see
	// WithKeyIDTemplate.  A custom Expander is not subject to that check.
	//
	// Every unknown kid costs a fetch, and every kid answered with a kid-less key
	// becomes its own ring entry; the ring has no eviction.  A Resolver exposed to
	// untrusted kids can therefore be made to fetch and grow without bound, which
	// is why a jws.KeyProvider never consults one.
	Resolve(ctx context.Context, keyID string) (Key, error)

	// AddListener attaches a sink for ResolveEvents.  Only events that
	// occur after this method call will be dispatched to the given listener.
	AddListener(ResolveListener) CancelListenerFunc
}

Resolver allows synchronous resolution of keys.

func NewResolver

func NewResolver(options ...ResolverOption) (Resolver, error)

NewResolver constructs a Resolver from a set of options. By default, a Resolver uses the DefaultLoader() and DefaultParser().

If no URI template option is supplied at all, this function returns ErrNoTemplate. Whenever the returned error is non-nil, the returned Resolver is nil.

An empty template, as from WithConfig with no Resolve.Template set, is accepted and yields a ring-only Resolver: keys already on the ring resolve, and any other key ID fails with ErrNoTemplate rather than an attempted fetch.

type ResolverOption

type ResolverOption interface {
	// contains filtered or unexported methods
}

ResolverOption represents a configurable option passed to NewResolver.

func WithKeyIDExpander

func WithKeyIDExpander(e Expander) ResolverOption

WithKeyIDExpander establishes the Expander strategy used for resolving individual keys. Callers may use this option to associate a custom Expander with a Resolver.

A custom Expander has no template for the Resolver to derive an origin from, so the location check described at WithKeyIDTemplate does not apply; the Expander is responsible for never letting a key ID choose the destination.

func WithKeyIDTemplate

func WithKeyIDTemplate(t string) ResolverOption

WithKeyIDTemplate establishes the URI template used for resolving individual keys. An empty template yields a ring-only Resolver whose misses report ErrNoTemplate; see NewResolver.

The template must begin with a literal scheme and, for http and https, a literal host, and its variable must come after the path begins, e.g. https://keys.example.com/{keyID} or file:///etc/keys/{keyID}.pem. Every expansion is then checked to stay within that scheme, host, and path prefix, so a key ID cannot change where keys are fetched from whatever it contains. A template that puts its variable in the host, such as https://{keyID}.example.com/, or that is only the variable, is rejected with ErrUnsafeTemplate.

func WithKeyIDValidator added in v0.4.0

func WithKeyIDValidator(v func(keyID string) error) ResolverOption

WithKeyIDValidator replaces the rule a Resolver applies to a key ID before expanding it into a URI. The default is ValidateKeyID. A validator's error is returned from Resolve wrapped with ErrInvalidKeyID, unless it already wraps it. A nil validator disables validation entirely; do that only when key IDs never come from an untrusted party.

type ResolverRefresherOption

type ResolverRefresherOption interface {
	ResolverOption
	RefresherOption
}

ResolverRefresherOption is a configurable option that applies to both a Refresher and a Resolver.

func WithFetcher

func WithFetcher(f Fetcher) ResolverRefresherOption

WithFetcher configures the Fetcher instance used by either a Resolver or a Refresher. By default, DefaultFetcher() is used.

type ResponseTooLargeError added in v0.3.3

type ResponseTooLargeError struct {
	Location string
	Limit    int64
}

ResponseTooLargeError indicates that an HTTP response body exceeded the loader's MaxReadLimit. Location has any password redacted.

func (*ResponseTooLargeError) Error added in v0.3.3

func (rtle *ResponseTooLargeError) Error() string

type Thumbprinter

type Thumbprinter interface {
	// Thumbprint produces the RFC 7638 thumbprint hash, using the supplied algorithm.  The
	// typical value to pass to this method is crypto.SHA256.
	//
	// The returned byte slice contains the raw bytes of the hash.  To convert it to a string
	// conforming to RFC 7638, use base64.RawURLEncoding.EncodeToString.
	Thumbprint(crypto.Hash) ([]byte, error)
}

Thumbprinter is implemented by anything that can produce a secure thumbprint of itself.

type UnsupportedFormatError

type UnsupportedFormatError struct {
	Format string
}

UnsupportedFormatError indicates that a Parser cannot parse a given format.

func (UnsupportedFormatError) Error

func (ufe UnsupportedFormatError) Error() string

Error implements the error interface.

type UnsupportedSchemeError

type UnsupportedSchemeError struct {
	Location string
}

UnsupportedSchemeError indicates that a URI's scheme was not registered and couldn't be handled by a Loader. Location has any password redacted.

func (*UnsupportedSchemeError) Error

func (use *UnsupportedSchemeError) Error() string

Directories

Path Synopsis
Package clorthofx provides integration with go.uber.org/fx.
Package clorthofx provides integration with go.uber.org/fx.
Package clorthometrics integrates clortho events with Prometheus metrics.
Package clorthometrics integrates clortho events with Prometheus metrics.
Package clorthozap provides basic integration with go.uber.org/zap.
Package clorthozap provides basic integration with go.uber.org/zap.

Jump to

Keyboard shortcuts

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