Documentation
¶
Overview ¶
Package valiss implements the core of the tenant authentication scheme, a three-level chain of Ed25519 keys:
- An operator holds an Ed25519 nkey; its public key is the trust anchor servers pin.
- The operator signs each tenant an account token that the tenant's own account nkey must back. Issued account tokens are recorded in a server-side allowlist.
- A tenant may delegate: it signs user tokens with its account seed.
- The subject signs every request with its nkey; the server verifies the token chain up to the pinned operator key, the request signature against the token's subject key, and the account token against the allowlist, then hands the verified identity to the handler for data segmentation.
Tokens are this scheme's own typed claims carried in an nkey-signed JWT: the sub claim is the subject's public key and name carries the human-readable label. Key levels are strict: operator keys (SO.../O...) sign account tokens for account keys (SA.../A...), account keys sign user tokens for user keys (SU.../U...).
A user key may additionally mint per-message tokens (IssueMessage): a fourth, optional chain level binding a token to a destination and payload checksum, offline-verifiable by anyone holding the operator public key (VerifyMessage). Message tokens are proofs of origin, never credentials: possession grants nothing, and the request Verifier does not accept them.
Authorization rides named extension claims (Extension, WithExtension): typed payloads the scheme signs and transports but assigns no meaning. The contrib transports enforce their extensions (HTTP hosts/methods/paths, gRPC methods); consumers add domain extensions the same way and read them back in handlers with ExtOf.
Index ¶
- Constants
- Variables
- func Checksum(payload []byte) string
- func ContextWithIdentity(ctx context.Context, id *Identity) context.Context
- func ContextWithMessage(ctx context.Context, c *MessageClaims) context.Context
- func Covered(granted []string, required string) bool
- func ExtOf[T Extension](exts Extensions) (T, bool, error)
- func Issue(operator nkeys.KeyPair, tenantPubKey string, opts ...IssueOption) (string, error)deprecated
- func IssueAccount(operator nkeys.KeyPair, tenantPubKey string, opts ...IssueOption) (string, error)
- func IssueMessage(user nkeys.KeyPair, opts ...IssueOption) (string, error)
- func IssueOperator(operator nkeys.KeyPair, opts ...IssueOption) (string, error)
- func IssueUser(account nkeys.KeyPair, userPubKey string, opts ...IssueOption) (string, error)
- func IssuerOf(token string) (string, error)
- func NewNonce() string
- func SignRequest(subject nkeys.KeyPair, now time.Time, reqContext []byte) (timestamp, signature string, err error)
- func VerifySignature(subjectPubKey, timestamp, signature string, reqContext []byte, now time.Time, ...) error
- type AccountClaims
- type AccountTokenResolver
- type AllowAll
- type Allowlist
- type ChainCache
- type Claims
- type ClaimsValidator
- type Extension
- type Extensions
- type Identity
- type IssueOption
- func WithAudience(aud string) IssueOption
- func WithBearer() IssueOption
- func WithChain(accountToken, userToken string) IssueOption
- func WithChecksum(sum string) IssueOption
- func WithEpoch(epoch uint64) IssueOption
- func WithExpiry(t time.Time) IssueOption
- func WithExtension(v Extension) IssueOption
- func WithName(name string) IssueOption
- func WithNotBefore(t time.Time) IssueOption
- func WithTTL(ttl time.Duration) IssueOption
- type Keyring
- type MemoryChainCache
- type MemoryReplayCache
- type MessageClaims
- type OperatorClaims
- type ReplayCache
- type Request
- type StaticAllowlist
- type UserClaims
- type Verifier
- type VerifierOption
- func WithAccountTokenResolver(fn AccountTokenResolver) VerifierOption
- func WithClaimsValidator(fn ClaimsValidator) VerifierOption
- func WithClock(now func() time.Time) VerifierOption
- func WithExtensionType[T Extension]() VerifierOption
- func WithOperatorToken(token string) VerifierOption
- func WithReplayCache(cache ReplayCache) VerifierOption
- func WithSkew(d time.Duration) VerifierOption
- type VerifyMessageOption
- func At(t time.Time) VerifyMessageOption
- func ExpectAudience(aud string) VerifyMessageOption
- func RequireChecksum() VerifyMessageOption
- func WithChainTokens(accountToken, userToken string) VerifyMessageOption
- func WithMessageSkew(d time.Duration) VerifyMessageOption
- func WithOperatorPolicy(operatorToken string) VerifyMessageOption
- func WithPayload(payload []byte) VerifyMessageOption
Constants ¶
const ( HeaderAccountToken = "valiss-account-token" HeaderUserToken = "valiss-user-token" HeaderTimestamp = "valiss-timestamp" HeaderSignature = "valiss-signature" HeaderNonce = "valiss-nonce" // HeaderMessageToken carries a per-message proof of origin // (IssueMessage); it is never a request credential. HeaderMessageToken = "valiss-message-token" // HeaderChainAccountToken and HeaderChainUserToken carry a message // token's provenance chain detached from the token itself, so no single // header grows large. They are chain material for VerifyMessage, never // request credentials. HeaderChainAccountToken = "valiss-chain-account-token" HeaderChainUserToken = "valiss-chain-user-token" // HeaderChain is the chain-negotiation signal a receiver sends back // (response header on HTTP, trailing metadata on gRPC) with the value // ChainRequired when it cannot verify a chainless message token; the // emitting transport then retries once with the detached chain headers. HeaderChain = "valiss-chain" )
Header field names carrying the credential on each request. Used as gRPC metadata keys and HTTP header names alike.
const ChainRequired = "required"
ChainRequired is the HeaderChain value asking the emitter to retransmit with its provenance chain attached.
const DefaultMessageTTL = 30 * time.Second
DefaultMessageTTL is the validity window the contrib transports mint message tokens with: long enough for delivery latency and clock drift, short enough to bound capture exposure.
const DefaultSkew = 2 * time.Minute
DefaultSkew bounds request-timestamp drift and token-expiry slack.
Variables ¶
var ErrNoChain = errors.New("valiss: message token carries no chain and none was supplied (WithChainTokens)")
ErrNoChain reports a message token that neither embeds a provenance chain nor had one supplied (WithChainTokens). Receiving transports match it with errors.Is to drive chain negotiation: it is the one verification failure a retransmit with the chain can cure.
Functions ¶
func Checksum ¶
Checksum returns the lowercase-hex SHA-256 of a payload exactly as delivered: the value WithChecksum embeds and WithPayload compares.
func ContextWithIdentity ¶
ContextWithIdentity returns a context carrying the verified identity. Transport middlewares call this after verification.
func ContextWithMessage ¶
func ContextWithMessage(ctx context.Context, c *MessageClaims) context.Context
ContextWithMessage returns a context carrying verified message claims. Receiving transports call this after VerifyMessage.
func Covered ¶
Covered reports whether any granted pattern covers the required value, honoring trailing-"*" prefix wildcards. Transport extensions use it for paths and methods.
func ExtOf ¶
func ExtOf[T Extension](exts Extensions) (T, bool, error)
ExtOf decodes the extension claim named by T's zero value. An absent name yields the zero value and false.
func IssueAccount ¶
IssueAccount mints an account token signed by the operator key. The token subject is the tenant's account public key and WithName carries the tenant id; the tenant signs requests with the seed matching the subject key.
func IssueMessage ¶
func IssueMessage(user nkeys.KeyPair, opts ...IssueOption) (string, error)
IssueMessage mints a per-message proof-of-origin token signed by the emitter's user key over itself (iss == sub). WithAudience binds it to a destination, WithChecksum to the payload bytes, and WithChain embeds the provenance chain so a receiver verifies offline with only the operator public key (VerifyMessage). Message tokens must carry an expiry: they are short-lived proofs, and an eternal proof of origin only widens capture exposure.
func IssueOperator ¶
func IssueOperator(operator nkeys.KeyPair, opts ...IssueOption) (string, error)
IssueOperator mints the self-signed operator token: the trust domain's policy statement (epoch, validity window, extensions), signed by the operator key over its own public key. WithName labels the trust domain the way account and user names label theirs. Servers configured with the token via WithOperatorToken enforce the policy on every request; the pinned public key remains the trust anchor.
func IssueUser ¶
IssueUser mints a user token signed by a tenant's account key, delegating to an end user. The token subject is the user's public key and WithName carries the user id. WithBearer produces a token the server accepts without per-request signatures.
func IssuerOf ¶
IssuerOf returns the public key that signed a token, after checking the token's own signature against it. It does not establish trust: the caller must still verify the issuer's place in the chain.
func NewNonce ¶
func NewNonce() string
NewNonce returns a fresh random per-request nonce (128 bits, hex). Client transports use it when a replay cache is in play; see WithReplayCache.
func SignRequest ¶
func SignRequest(subject nkeys.KeyPair, now time.Time, reqContext []byte) (timestamp, signature string, err error)
SignRequest produces the timestamp and base64 signature a subject attaches to a request, signing the timestamp bound to reqContext with its nkey seed. reqContext is the transport's canonical description of the request (e.g. method and path); the server must reconstruct identical bytes. Pass nil to bind nothing beyond the timestamp.
func VerifySignature ¶
func VerifySignature(subjectPubKey, timestamp, signature string, reqContext []byte, now time.Time, skew time.Duration) error
VerifySignature checks a request signature against the subject public key, bounds the timestamp to a symmetric skew window around now, and confirms it was signed over reqContext. reqContext must match the bytes the client signed (see SignRequest).
Types ¶
type AccountClaims ¶
type AccountClaims struct {
Claims
// Name is the tenant's human-readable label; it segments all stored
// data. Falls back to the subject key when the token carries no name.
Name string
// Epoch is the trust-domain epoch the token was issued in (WithEpoch).
Epoch uint64
// Ext carries the named extension claims (WithExtension).
Ext Extensions
}
AccountClaims is the verified content of an account (tenant) token.
func VerifyAccount ¶
func VerifyAccount(token, operatorPubKey string) (*AccountClaims, error)
VerifyAccount decodes an account token, checks its type, signature, and issuer, and returns the claims. It does NOT check expiry, activation, or the allowlist; the Verifier layers those so callers get precise errors.
type AccountTokenResolver ¶
AccountTokenResolver supplies the operator-signed account token for an account public key, serving requests that carry only a user token (the default creds shape). The resolved token goes through the full verification: operator signature, expiry, allowlist.
func StaticAccountTokens ¶
func StaticAccountTokens(tokens ...string) (AccountTokenResolver, error)
StaticAccountTokens builds a resolver over a fixed token set, e.g. from server configuration. Tokens are indexed by their subject account key; their signatures are checked here, their trust is established per request.
type AllowAll ¶
type AllowAll struct{}
AllowAll accepts every token; for local development where no allowlist is configured. The token signature and expiry still gate access.
type Allowlist ¶
Allowlist decides whether an issued token (by jti) is still accepted. Only tokens the issuer explicitly deposited server-side pass, so a token can be revoked by removing it even before expiry.
type ChainCache ¶
type ChainCache interface {
// Get returns the cached chain tokens for an emitter's user public key.
Get(userPubKey string) (accountToken, userToken string, ok bool)
// Put stores the chain tokens for an emitter's user public key.
Put(userPubKey, accountToken, userToken string)
// Del drops the entry, e.g. when verification against it fails after a
// domain rotation.
Del(userPubKey string)
}
ChainCache stores verified provenance chains keyed by the emitter's user public key, serving the receiving side of chain negotiation: an emitter sends chainless message tokens, the receiver caches the chain from the one retransmit that carried it, and every later message verifies against the cached copy. Implementations must be safe for concurrent use and may be backed by memory or shared storage. Store only chains that survived a full VerifyMessage, so the cache never holds material an attacker could plant.
type Claims ¶
type Claims struct {
// ID is the token's unique identifier (jti), the allowlist key for
// account tokens.
ID string
// Issuer is the issuer public key that signed the token (iss).
Issuer string
// Subject is the subject's nkey public key (sub) that must sign
// requests.
Subject string
// IssuedAt is the token mint time (iat).
IssuedAt time.Time
// ExpiresAt is the token expiry (exp); zero means the token never
// expires.
ExpiresAt time.Time
// NotBefore is the token activation time (nbf); zero means immediately
// valid.
NotBefore time.Time
}
Claims is the verified RFC 7519 registered-claims content of a token. Level-specific data lives on AccountClaims and UserClaims, which embed it.
func Decode ¶
Decode parses a token of either level without establishing trust: the signature is checked against the token's own embedded issuer only. For inspection and tooling; servers must use VerifyAccount or VerifyUser.
type ClaimsValidator ¶
ClaimsValidator is custom validation logic injected into the Verifier. It runs after the token chain is verified, the identity is assembled, and the request signature has proven possession of the subject seed (a bearer user token waives the signature). A non-nil error rejects the request as unauthenticated. Running post-possession means an expensive validator is never triggered by a party that merely captured a token but cannot sign.
func ExtValidator ¶
func ExtValidator[T Extension](fn func(req Request, id *Identity, acct, user T) error) ClaimsValidator
ExtValidator adapts a typed validator over the extension claim named by T's zero value into a ClaimsValidator: the extension is decoded from both the account and user tokens before fn runs. Missing extensions pass zero values.
type Extension ¶
type Extension interface {
ExtensionName() string
}
Extension is a named claim payload carried in a token's ext field. Implementations are value struct types whose zero value reports the name.
type Extensions ¶
type Extensions map[string]json.RawMessage
Extensions is the named extension claims of a token: consumer- or transport-defined claim bodies keyed by extension name. This scheme signs and transports them; meaning is assigned by whoever registered the name (e.g. the httpauth and grpcauth packages, or the library consumer).
type Identity ¶
type Identity struct {
// Account is the tenant the request acts under; always present.
Account *AccountClaims
// User is the delegated end user; nil for account-level requests.
User *UserClaims
// Operator is the trust domain the request verified under: the keyring
// entry on a NewKeyringVerifier, the policy token under
// WithOperatorToken, nil otherwise. Consumers trusting several
// operators segment by Operator.Name — the keyring guarantees a name
// maps to exactly one operator key.
Operator *OperatorClaims
}
Identity is the verified result of a request.
type IssueOption ¶
type IssueOption func(*issueConfig)
IssueOption customizes a minted token. Without a WithTTL or WithExpiry option the token never expires.
func WithAudience ¶
func WithAudience(aud string) IssueOption
WithAudience binds a message token to its destination (the JWT aud claim): a URL, queue name, or recipient id. Receivers that verify with ExpectAudience reject tokens minted for anywhere else, closing cross-destination replay. Only IssueMessage accepts this option.
func WithBearer ¶
func WithBearer() IssueOption
WithBearer marks a user token as a bearer token: the server accepts it without per-request signatures. Bearer tokens are replayable until they expire or their account leaves the allowlist, so pair them with TLS and a short validity window. Only IssueUser accepts this option.
func WithChain ¶
func WithChain(accountToken, userToken string) IssueOption
WithChain embeds the emitter's provenance chain — the operator-signed account token and the account-signed user token — into the message token, so a receiver needs nothing but the operator public key (see also the verify-side WithChainTokens for out-of-band delivery). Only IssueMessage accepts this option.
func WithChecksum ¶
func WithChecksum(sum string) IssueOption
WithChecksum embeds the lowercase-hex SHA-256 of the message payload exactly as delivered (see Checksum), binding the token to the bytes. Receivers compare it via WithPayload or read it from MessageClaims. Only IssueMessage accepts this option.
func WithEpoch ¶
func WithEpoch(epoch uint64) IssueOption
WithEpoch stamps the trust-domain epoch on the token. On an operator token it declares the domain's current epoch; on account and user tokens it must echo it. Verifiers configured with the operator token reject tokens from any other epoch, so bumping the epoch and re-minting rotates the whole domain at once. Unstamped tokens are epoch 0.
func WithExpiry ¶
func WithExpiry(t time.Time) IssueOption
WithExpiry makes the token expire at t (the JWT exp claim).
func WithExtension ¶
func WithExtension(v Extension) IssueOption
WithExtension embeds a named extension claim into the token's ext field; the name comes from the value's ExtensionName. Repeat the option for multiple extensions; a duplicate name is an error. The scheme signs and transports the value untouched; servers read it back with ExtOf or validate it with an ExtValidator.
func WithName ¶
func WithName(name string) IssueOption
WithName labels the minted entity with a human-readable name: the trust domain on an operator token, the tenant on an account token, the user on a user token. Names are optional; an unnamed entity is represented by its public key. Names are asserted by their issuer, and nothing at issuance checks uniqueness: collections that hold several entities side by side (an anchor keyring, a tenant directory) own the uniqueness of names in their scope. Message tokens carry no name.
func WithNotBefore ¶
func WithNotBefore(t time.Time) IssueOption
WithNotBefore makes the token invalid before t (the JWT nbf claim).
func WithTTL ¶
func WithTTL(ttl time.Duration) IssueOption
WithTTL makes the token expire ttl from now (the JWT exp claim).
type Keyring ¶
type Keyring struct {
// contains filtered or unexported fields
}
Keyring is the set of trusted operators a multi-producer consumer verifies against. Every entry is a full self-signed operator token — never a bare public key — so each trust domain always carries a name, an epoch, and a validity window, and per-domain policy is enforced on every verification.
Entries are selected by issuer, not by trial: an incoming chain names its operator (the account token's issuer) and its epoch, and verification runs against exactly the entry registered under that (key, epoch) pair. An unknown pair fails immediately.
One operator key may hold entries at several epochs, which is how a receiver grants a rotation grace period: register the new-epoch token next to the old one, let producers re-mint at their own pace, and drop (or simply let expire) the old entry. Whether and how long two epochs coexist is always the receiver's choice.
A Keyring is immutable after construction and safe for concurrent use.
func NewKeyring ¶
NewKeyring builds a keyring from self-signed operator tokens. Identical tokens (same jti) collapse into one entry. Registration fails on: a different token for an already-occupied (operator key, epoch) pair, two operator keys sharing a name, or one operator key naming itself differently across entries. Unnamed operators are represented by their public key.
type MemoryChainCache ¶
type MemoryChainCache struct {
// contains filtered or unexported fields
}
MemoryChainCache is a process-local ChainCache. For fewer warmup round-trips across multiple receiver instances, back ChainCache with shared storage instead.
func NewMemoryChainCache ¶
func NewMemoryChainCache() *MemoryChainCache
func (*MemoryChainCache) Del ¶
func (c *MemoryChainCache) Del(userPubKey string)
func (*MemoryChainCache) Get ¶
func (c *MemoryChainCache) Get(userPubKey string) (string, string, bool)
func (*MemoryChainCache) Put ¶
func (c *MemoryChainCache) Put(userPubKey, accountToken, userToken string)
type MemoryReplayCache ¶
type MemoryReplayCache struct {
// contains filtered or unexported fields
}
MemoryReplayCache is an in-memory ReplayCache that retains each nonce until its expiry and prunes lazily. It is process-local: for exactly-once across multiple server instances, back WithReplayCache with shared storage instead. Safe for concurrent use.
func NewMemoryReplayCache ¶
func NewMemoryReplayCache() *MemoryReplayCache
NewMemoryReplayCache returns an empty in-memory replay cache.
func (*MemoryReplayCache) SeenBefore ¶
func (c *MemoryReplayCache) SeenBefore(nonce string, expiry time.Time) bool
SeenBefore records nonce with the given expiry and reports whether a still-valid entry was already present.
type MessageClaims ¶
type MessageClaims struct {
Claims
// Audience is the destination the token was minted for (aud); empty
// when the token is unbound.
Audience string
// Checksum is the lowercase-hex SHA-256 of the payload the token was
// minted over; empty when the token carries no payload binding.
Checksum string
// Epoch is the trust-domain epoch the token was issued in (WithEpoch).
Epoch uint64
// Ext carries the named extension claims (WithExtension).
Ext Extensions
// Account is the verified tenant identity from the chain. Offline
// receivers hold no allowlist; an online receiver that wants revocation
// checks Account.ID against its own Allowlist.
Account *AccountClaims
// User is the verified emitter identity from the chain; its subject key
// signed the message token.
User *UserClaims
// Operator is the trust domain the message verified under: the keyring
// entry on VerifyMessageKeyring, the policy token on VerifyMessage with
// WithOperatorPolicy, nil otherwise. Consumers trusting several
// operators segment by Operator.Name — the keyring guarantees a name
// maps to exactly one operator key.
Operator *OperatorClaims
}
MessageClaims is the verified content of a message token: a per-message proof of origin, together with the verified chain identities it was checked against. A message token is a proof, not a credential: possession grants nothing, and Verifier.VerifyRequest never accepts one.
func MessageFromContext ¶
func MessageFromContext(ctx context.Context) (*MessageClaims, bool)
MessageFromContext returns the verified message claims a handler uses to attribute an incoming message to its emitter. The bool is false when no message token was verified. Message claims prove origin only; they are not an identity and grant nothing.
func VerifyMessage ¶
func VerifyMessage(token, operatorPubKey string, opts ...VerifyMessageOption) (*MessageClaims, error)
VerifyMessage verifies a per-message proof of origin against the pinned operator public key: it walks the chain operator → account → user → message, requires all chain levels to agree on the epoch, checks every validity window at the verification instant (At; default now), and enforces the audience and checksum bindings the options request. On success the returned claims carry the verified tenant and emitter identities alongside the message bindings.
A verified message token proves origin only. It is not a credential: grant nothing for possession of one.
func VerifyMessageKeyring ¶
func VerifyMessageKeyring(token string, keyring *Keyring, opts ...VerifyMessageOption) (*MessageClaims, error)
VerifyMessageKeyring verifies a per-message proof of origin against a set of trusted operators (see Keyring). The chain names its trust domain — the account token's issuer and epoch select exactly one keyring entry — and verification then runs as VerifyMessage does under that entry's always-enforced policy: the entry token's validity window at the verification instant and its exact epoch. A chain from an unknown operator, or a known operator at an unregistered epoch, fails immediately. The returned claims carry the matched entry as Operator.
WithOperatorPolicy does not combine with a keyring: entries carry the policy.
type OperatorClaims ¶
type OperatorClaims struct {
Claims
// Name is the trust domain's human-readable label. It is asserted by
// the operator about itself: a consumer trusting several operators must
// not assume names are unique across domains. Falls back to the
// operator public key when the token carries no name.
Name string
// Epoch is the trust domain's current epoch. A verifier configured with
// the operator token accepts only account and user tokens that echo it.
Epoch uint64
// Ext carries the named extension claims (WithExtension).
Ext Extensions
}
OperatorClaims is the verified content of a self-signed operator token: the trust domain's policy statement, signed by the pinned anchor key.
func VerifyOperator ¶
func VerifyOperator(token, operatorPubKey string) (*OperatorClaims, error)
VerifyOperator decodes a self-signed operator token, checks its type and that it is signed by the pinned operator key over itself, and returns the claims. Expiry and activation checks belong to the Verifier.
type ReplayCache ¶
type ReplayCache interface {
// SeenBefore records nonce with the given expiry and reports whether it
// was already present. expiry is when the entry may be discarded.
SeenBefore(nonce string, expiry time.Time) bool
}
ReplayCache records request nonces and reports duplicates within a retention window, so a captured request cannot be replayed for the same operation before its signature ages out. Implementations must be safe for concurrent use and may be backed by memory or shared storage.
type Request ¶
type Request struct {
// AccountToken is the operator-signed account token.
AccountToken string
// UserToken is the account-signed user token on chain credentials; empty
// when the tenant itself makes the request.
UserToken string
// Timestamp and Signature are the per-request signing proof; both empty
// on bearer requests.
Timestamp string
Signature string
// Context is the transport's canonical description of the request (e.g.
// method and path) that the signature is bound to. The transport fills
// it from the incoming request and the client signs the identical bytes;
// a mismatch fails the signature. Nil binds nothing beyond the timestamp.
Context []byte
// Nonce is a per-request unique value, folded into Context by the
// transport so it is signed. A Verifier with a ReplayCache requires it
// and rejects a nonce it has already seen.
Nonce string
}
Request is the per-request material a transport extracts from headers.
type StaticAllowlist ¶
type StaticAllowlist struct {
// contains filtered or unexported fields
}
StaticAllowlist is an in-memory set of accepted token IDs.
func LoadAllowlistFile ¶
func LoadAllowlistFile(path string) (*StaticAllowlist, error)
LoadAllowlistFile reads a newline-delimited allowlist file of token IDs. Blank lines and lines beginning with '#' are ignored.
func NewStaticAllowlist ¶
func NewStaticAllowlist(ids ...string) *StaticAllowlist
func (*StaticAllowlist) Allowed ¶
func (a *StaticAllowlist) Allowed(jti string) bool
func (*StaticAllowlist) Set ¶
func (a *StaticAllowlist) Set(ids []string)
Set replaces the accepted set, e.g. after reloading the file.
type UserClaims ¶
type UserClaims struct {
Claims
// Name is the user's human-readable label. Falls back to the subject
// key when the token carries no name.
Name string
// Epoch is the trust-domain epoch the token was issued in (WithEpoch).
Epoch uint64
// Bearer marks a token whose holder authenticates by the token alone,
// without per-request signatures.
Bearer bool
// Ext carries the named extension claims (WithExtension).
Ext Extensions
}
UserClaims is the verified content of a user token.
func VerifyUser ¶
func VerifyUser(token, accountPubKey string) (*UserClaims, error)
VerifyUser decodes a user token, checks its type, signature, and issuer (the account public key that delegated it), and returns the claims. Expiry and activation checks belong to the Verifier.
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier checks the full per-request credential: account token signature against the pinned operator key, expiry and activation, allowlist membership, the optional user-token chain, registered extension types, custom validators, and the request signature within the skew window. Requests without a signature pass only when the effective token is a bearer user token. Transport layers (gRPC interceptor, HTTP middleware) wrap it with header extraction and error mapping.
func NewKeyringVerifier ¶
func NewKeyringVerifier(keyring *Keyring, allowlist Allowlist, opts ...VerifierOption) *Verifier
NewKeyringVerifier is NewVerifier for a server trusting several operators (see Keyring). The credential names its trust domain — the account token's issuer and epoch select exactly one keyring entry — and the request then verifies under that entry's always-enforced policy: the entry token's validity window and its exact epoch, echoed by every chain level. A credential from an unknown operator, or a known operator at an unregistered epoch, is rejected. Handlers tell trust domains apart by Identity.Operator.
WithOperatorToken does not combine with a keyring: entries carry the policy. The allowlist is shared across domains; account token jtis are content hashes, so entries cannot collide between producers.
func NewVerifier ¶
func NewVerifier(operatorPubKey string, allowlist Allowlist, opts ...VerifierOption) *Verifier
func (*Verifier) VerifyRequest ¶
VerifyRequest authenticates a request credential and returns the verified identity. Any error means the request must be rejected as unauthenticated.
A credential with a user token is verified as a chain: the account token against the operator key and the allowlist, then the user token against the account token's subject key. An empty timestamp and signature is a bearer request, accepted only when the effective token is a bearer user token; account-level requests must always sign.
type VerifierOption ¶
type VerifierOption func(*Verifier)
VerifierOption configures a Verifier.
func WithAccountTokenResolver ¶
func WithAccountTokenResolver(fn AccountTokenResolver) VerifierOption
WithAccountTokenResolver accepts requests that carry only a user token, resolving the account token server-side. Without it such requests are rejected.
func WithClaimsValidator ¶
func WithClaimsValidator(fn ClaimsValidator) VerifierOption
WithClaimsValidator injects custom validation into the verification pipeline. Validators run in registration order; the first error wins.
func WithClock ¶
func WithClock(now func() time.Time) VerifierOption
WithClock overrides the time source; for tests.
func WithExtensionType ¶
func WithExtensionType[T Extension]() VerifierOption
WithExtensionType registers an extension type for eager validation: when either token carries the extension named by T's zero value, it must decode into T or the request is rejected. Retrieval via ExtOf never requires registration; this only moves malformed-extension failures to auth time.
func WithOperatorToken ¶
func WithOperatorToken(token string) VerifierOption
WithOperatorToken supplies the trust domain's self-signed operator token and enforces its policy on every request: the operator token must be within its own validity window, and every account and user token must echo its epoch. Bumping the epoch in a fresh operator token therefore revokes everything minted in earlier epochs at once. The pinned operator public key remains the trust anchor; a token not self-signed by it poisons the verifier so every request fails rather than silently skipping policy.
func WithReplayCache ¶
func WithReplayCache(cache ReplayCache) VerifierOption
WithReplayCache rejects a signed request whose nonce the cache has already seen, suppressing replay of the same request within the skew window. Signed requests must then carry a nonce (the client transport must enable it); bearer requests, which carry no signature, are unaffected. The nonce is retained for twice the skew window, the longest a replay could still land inside a valid timestamp window.
func WithSkew ¶
func WithSkew(d time.Duration) VerifierOption
WithSkew overrides the DefaultSkew window for timestamp drift and token expiry slack.
type VerifyMessageOption ¶
type VerifyMessageOption func(*verifyMessageConfig)
VerifyMessageOption customizes VerifyMessage.
func At ¶
func At(t time.Time) VerifyMessageOption
At evaluates the validity windows and operator policy as of the supplied instant instead of now, so stored messages verify after their tokens expire. Callers keeping key and epoch history pass the instant the message was received.
func ExpectAudience ¶
func ExpectAudience(aud string) VerifyMessageOption
ExpectAudience requires the message token to be bound to exactly this destination; a token minted for anywhere else — or bound to no audience at all — is rejected. This is the receiver's lever against cross-destination replay; every receiver that knows its own identity should set it.
func RequireChecksum ¶
func RequireChecksum() VerifyMessageOption
RequireChecksum rejects message tokens that carry no checksum claim, for receivers that insist every message is payload-bound. Comparison against the actual bytes still requires WithPayload.
func WithChainTokens ¶
func WithChainTokens(accountToken, userToken string) VerifyMessageOption
WithChainTokens supplies the provenance chain out-of-band for message tokens minted without WithChain, trading self-containment for smaller tokens. A token that embeds a chain must embed this exact chain; a mismatch is an error.
func WithMessageSkew ¶
func WithMessageSkew(d time.Duration) VerifyMessageOption
WithMessageSkew overrides the DefaultSkew slack applied to the validity windows of the message token and its chain.
func WithOperatorPolicy ¶
func WithOperatorPolicy(operatorToken string) VerifyMessageOption
WithOperatorPolicy supplies the trust domain's self-signed operator token (the same token WithOperatorToken takes on a Verifier) and enforces its policy: the operator token must be within its own validity window at the verification instant, and the message token must echo the domain epoch.
func WithPayload ¶
func WithPayload(payload []byte) VerifyMessageOption
WithPayload hashes the payload exactly as received and requires the token's checksum claim to match; a token without a checksum claim is rejected. This is the receiver's lever against payload tampering.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
conformance
|
|
|
gen
command
Command gen emits the valiss spec-1 conformance vectors.
|
Command gen emits the valiss spec-1 conformance vectors. |
|
contrib
|
|
|
echoauth
Package echoauth wires the tenant authentication scheme into Echo: a middleware that verifies the per-request credential and enforces the HTTP extension claim (httpauth.Ext), built on httpauth's verification core.
|
Package echoauth wires the tenant authentication scheme into Echo: a middleware that verifies the per-request credential and enforces the HTTP extension claim (httpauth.Ext), built on httpauth's verification core. |
|
echosig
Package echosig wires per-message proofs of origin (valiss message tokens) into Echo: a middleware that verifies the token on every incoming request, built on httpsig's receiving core, including the receiving side of chain negotiation.
|
Package echosig wires per-message proofs of origin (valiss message tokens) into Echo: a middleware that verifies the token on every incoming request, built on httpsig's receiving core, including the receiving side of chain negotiation. |
|
ginauth
Package ginauth wires the tenant authentication scheme into Gin: a middleware that verifies the per-request credential and enforces the HTTP extension claim (httpauth.Ext), built on httpauth's verification core.
|
Package ginauth wires the tenant authentication scheme into Gin: a middleware that verifies the per-request credential and enforces the HTTP extension claim (httpauth.Ext), built on httpauth's verification core. |
|
ginsig
Package ginsig wires per-message proofs of origin (valiss message tokens) into Gin: a middleware that verifies the token on every incoming request, built on httpsig's receiving core, including the receiving side of chain negotiation.
|
Package ginsig wires per-message proofs of origin (valiss message tokens) into Gin: a middleware that verifies the token on every incoming request, built on httpsig's receiving core, including the receiving side of chain negotiation. |
|
grpcauth
Package grpcauth wires the tenant authentication scheme into gRPC: server interceptors that verify the per-request credential and enforce the gRPC extension claim (Ext), and a client per-RPC credential that attaches the credential.
|
Package grpcauth wires the tenant authentication scheme into gRPC: server interceptors that verify the per-request credential and enforce the gRPC extension claim (Ext), and a client per-RPC credential that attaches the credential. |
|
grpcsig
Package grpcsig wires per-message proofs of origin (valiss message tokens) into gRPC: a client unary interceptor that mints a token per call and a server unary interceptor that verifies it offline against the operator public key.
|
Package grpcsig wires per-message proofs of origin (valiss message tokens) into gRPC: a client unary interceptor that mints a token per call and a server unary interceptor that verifies it offline against the operator public key. |
|
httpauth
Package httpauth wires the tenant authentication scheme into net/http: a server middleware that verifies the per-request credential and enforces the HTTP extension claim (Ext), and a client http.RoundTripper that attaches the credential.
|
Package httpauth wires the tenant authentication scheme into net/http: a server middleware that verifies the per-request credential and enforces the HTTP extension claim (Ext), and a client http.RoundTripper that attaches the credential. |
|
httpsig
Package httpsig wires per-message proofs of origin (valiss message tokens) into net/http: a client Transport that mints a token per outgoing request and a server middleware that verifies it offline against the operator public key.
|
Package httpsig wires per-message proofs of origin (valiss message tokens) into net/http: a client Transport that mints a token per outgoing request and a server middleware that verifies it offline against the operator public key. |
|
Package creds implements the client credentials file: the subject's token plus the seed that signs its requests, in one marker-delimited text file.
|
Package creds implements the client credentials file: the subject's token plus the seed that signs its requests, in one marker-delimited text file. |
|
examples
|
|
|
echoauth
command
Example echoauth shows the full tenant-auth wiring for Echo: an operator signs an account token carrying an HTTP extension, the server installs the echoauth middleware on the app, and the client signs every request via the framework-agnostic httpauth transport.
|
Example echoauth shows the full tenant-auth wiring for Echo: an operator signs an account token carrying an HTTP extension, the server installs the echoauth middleware on the app, and the client signs every request via the framework-agnostic httpauth transport. |
|
ginauth
command
Example ginauth shows the full tenant-auth wiring for Gin: an operator signs an account token carrying an HTTP extension, the server installs the ginauth middleware on the engine, and the client signs every request via the framework-agnostic httpauth transport.
|
Example ginauth shows the full tenant-auth wiring for Gin: an operator signs an account token carrying an HTTP extension, the server installs the ginauth middleware on the engine, and the client signs every request via the framework-agnostic httpauth transport. |
|
grpcauth
command
Example grpcauth shows the full tenant-auth wiring for gRPC: an operator issues an account token, the server installs the auth interceptors, and the client attaches the credential to every call.
|
Example grpcauth shows the full tenant-auth wiring for gRPC: an operator issues an account token, the server installs the auth interceptors, and the client attaches the credential to every call. |
|
grpcsig
command
Example grpcsig shows per-message proofs of origin over gRPC: the client interceptor mints a token per unary call binding the full method and the request message, the server interceptor verifies it offline against the operator public key, and a captured token fails when replayed at another method or with a tampered message.
|
Example grpcsig shows per-message proofs of origin over gRPC: the client interceptor mints a token per unary call binding the full method and the request message, the server interceptor verifies it offline against the operator public key, and a captured token fails when replayed at another method or with a tampered message. |
|
httpauth
command
Example httpauth shows the full tenant-auth wiring for net/http: an operator signs an account token carrying an HTTP extension, the server wraps its mux with the auth middleware, and the client signs every request via the transport.
|
Example httpauth shows the full tenant-auth wiring for net/http: an operator signs an account token carrying an HTTP extension, the server wraps its mux with the auth middleware, and the client signs every request via the transport. |
|
httpsig
command
Example httpsig shows per-message proofs of origin over HTTP: an emitter posts a webhook through the httpsig transport, the receiver's middleware verifies the token offline against the operator public key, and an auditor re-verifies the stored message after the token has expired using verification-at-instant.
|
Example httpsig shows per-message proofs of origin over HTTP: an emitter posts a webhook through the httpsig transport, the receiver's middleware verifies the token offline against the operator public key, and an auditor re-verifies the stored message after the token has expired using verification-at-instant. |
|
minter
command
Command minter issues valiss tenant credentials for gRPC and HTTP services using the operator/account/user nkey model.
|
Command minter issues valiss tenant credentials for gRPC and HTTP services using the operator/account/user nkey model. |