Documentation
¶
Overview ¶
Package agentprovider implements a hosted agent provider for AAuth (draft-hardt-oauth-aauth-protocol-11 §5.1; draft-hardt-aauth-bootstrap-02).
A Server holds a provider signing key separate from every agent's key, publishes /.well-known/aauth-agent.json and its JWKS, and issues aa-agent+jwt tokens:
- for any public key the hosting application authorizes, directly with Server.IssueAgentToken — so a token can be tied to the application's own records, such as a work session — or over HTTP for a key that proves possession under the hwk scheme (enrollment, single-key refresh; bootstrap §8.2);
- by the two-key refresh ceremony (bootstrap §8.1): a jkt-jwt naming JWT signed by the durable key delegates to a fresh ephemeral key, which signs the request; naming JWTs are single-use;
- for sub-agents under a hosted provider (bootstrap §9.2): the parent signs with its own agent token, and the sub-agent token has the parent's iss and ps, parent_agent naming the parent, its own key, and an exp no later than the parent's; a sub-agent cannot have sub-agents.
Every HTTP issuance goes through the caller's Registrar, and optional platform attestation through an AttestationVerifier (bootstrap §5). Server.RevokeAgentToken revokes an agent token at the agent's person server (§11.12). Client is the agent side of the same endpoints.
Index ¶
- Constants
- Variables
- func AttachHWK(req *http.Request, pub crypto.PublicKey) error
- func JKTURN(j aauth.JWK) string
- func NewNamingJWT(durable crypto.Signer, ephemeral crypto.PublicKey, ttl time.Duration) (string, error)
- func SignHWK(req *http.Request, key crypto.Signer) error
- func SignJKTJWT(req *http.Request, namingJWT string, ephemeral crypto.Signer) error
- type AgentTokenParams
- type Attestation
- type AttestationVerifier
- type Client
- func (c *Client) Issue(ctx context.Context, key crypto.Signer, body any) (*TokenResponse, error)
- func (c *Client) Refresh(ctx context.Context, durable, ephemeral crypto.Signer, body any) (*TokenResponse, error)
- func (c *Client) Subagent(ctx context.Context, parent *aauth.Agent, subPub crypto.PublicKey, ...) (*TokenResponse, error)
- func (c *Client) TokenSource(fetch func(ctx context.Context) (*TokenResponse, error)) func() (string, error)
- type Config
- type IssueRequest
- type IssuedToken
- type MemoryReplayCache
- type Paths
- type RefreshRequest
- type Registrar
- type Registration
- type ReplayCache
- type Server
- func (s *Server) IssueAgentToken(ctx context.Context, p AgentTokenParams) (string, *aauth.AgentClaims, error)
- func (s *Server) Issuer() string
- func (s *Server) JWKS() aauth.JWKS
- func (s *Server) Metadata() aauth.AgentProviderMetadata
- func (s *Server) Register(mux *http.ServeMux)
- func (s *Server) Resolver() aauth.KeyResolver
- func (s *Server) RevokeAgentToken(ctx context.Context, ps, jti string, exp time.Time) error
- func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Server) Signer() aauth.ServerSigner
- type SubagentGrant
- type SubagentRequest
- type TokenResponse
Constants ¶
const ( SchemeHWK = "hwk" SchemeJKTJWT = "jkt-jwt" // TypJKTS256 is the typ of a jkt-jwt naming JWT whose thumbprint is // SHA-256; its iss is urn:jkt:sha-256:<thumbprint>. TypJKTS256 = "jkt-s256+jwt" )
Signature-Key schemes an agent provider accepts besides jwt (signature-key §3.4, §3.5; bootstrap §8): hwk carries a public key inline, for enrollment and single-key refresh; jkt-jwt carries a JWT signed by a durable (enclave) key that delegates to an ephemeral key, for two-key refresh.
const DefaultMaxNamingJWTLifetime = 10 * time.Minute
DefaultMaxNamingJWTLifetime is the longest naming-JWT lifetime a provider accepts unless Config.MaxNamingJWTLifetime says otherwise.
const DefaultReplayCacheEntries = 100_000
DefaultReplayCacheEntries is the capacity of a MemoryReplayCache whose MaxEntries is zero.
Variables ¶
var ErrReplayCacheFull = errors.New("agentprovider: replay cache is full")
ErrReplayCacheFull means a ReplayCache cannot admit another entry. The provider fails closed: it refuses the request rather than forget an identifier a captured request could replay.
Functions ¶
func AttachHWK ¶
AttachHWK sets Signature-Key to the hwk scheme carrying pub inline (signature-key §3.4): kty, crv, x (and y), and the fully-specified alg; no kid.
func JKTURN ¶
JKTURN is the JWK Thumbprint URI of a key (signature-key §3.5): urn:jkt:sha-256:<RFC 7638 thumbprint>. It is the stable, pseudonymous identity of a durable key.
func NewNamingJWT ¶
func NewNamingJWT(durable crypto.Signer, ephemeral crypto.PublicKey, ttl time.Duration) (string, error)
NewNamingJWT creates the jkt-jwt naming JWT of a two-key refresh (bootstrap §8.1; signature-key §3.5): signed by the durable key, whose public JWK is in the header and whose thumbprint URI is iss, delegating to the ephemeral key in cnf.jwk, with iat, exp (now + ttl), and a jti for replay protection. durable may be any crypto.Signer — typically a hardware-backed key.
Types ¶
type AgentTokenParams ¶
type AgentTokenParams struct {
// Name is the agent identifier's local part; the domain is the
// provider's. A sub-agent's name carries "+discriminator".
Name string
// Key is the agent's public key (cnf.jwk); never the provider's.
Key aauth.JWK
// PS is the ps claim; ParentAgent marks a sub-agent token.
PS string
ParentAgent string
// TTL is the lifetime (default Config.TokenTTL, at most 24 hours);
// NotAfter, when set, caps exp (a sub-agent's parent token).
TTL time.Duration
NotAfter time.Time
}
AgentTokenParams describes an agent token to issue.
type Attestation ¶
type Attestation struct {
Format string // e.g. "webauthn", "app-attest", "play-integrity"
Claims map[string]any // what the verifier established
}
Attestation is a verified platform attestation result (bootstrap §5).
type AttestationVerifier ¶
type AttestationVerifier interface {
VerifyAttestation(ctx context.Context, evidence json.RawMessage, key aauth.JWK) (*Attestation, error)
}
AttestationVerifier verifies the attestation evidence a request body carries in its "attestation" member against the key being enrolled.
type Client ¶
type Client struct {
// BaseURL is the provider's issuer.
BaseURL string
// Paths are the provider's endpoint paths (default DefaultPaths).
Paths Paths
// HTTPClient makes requests; nil uses http.DefaultClient.
HTTPClient *http.Client
}
Client is the agent side of a hosted agent provider: it obtains agent tokens from a Server's endpoints. Pair it with aauth.WithTokenSource so an aauth.Agent presents provider-issued tokens:
ap := &agentprovider.Client{BaseURL: "https://ap.example"}
src := ap.TokenSource(func(ctx context.Context) (*agentprovider.TokenResponse, error) {
return ap.Issue(ctx, key, nil)
})
agent, _ := aauth.NewAgent(id, aauth.WithKey(key), aauth.WithTokenSource(src))
func (*Client) Issue ¶
Issue requests an agent token for key, proving possession by signing the request with it under the hwk scheme (enrollment, or single-key refresh; bootstrap §8.2). body carries the provider's own enrollment fields, and attestation evidence in "attestation"; nil sends {}.
func (*Client) Refresh ¶
func (c *Client) Refresh(ctx context.Context, durable, ephemeral crypto.Signer, body any) (*TokenResponse, error)
Refresh performs the two-key refresh (bootstrap §8.1): durable — the enrolled, typically hardware-bound key — names ephemeral in a jkt-jwt, and ephemeral signs the request. The new token binds ephemeral.
func (*Client) Subagent ¶
func (c *Client) Subagent(ctx context.Context, parent *aauth.Agent, subPub crypto.PublicKey, discriminator string) (*TokenResponse, error)
Subagent requests a sub-agent token for the key subPub from the provider, signed by parent with its own agent token (bootstrap §9.2). discriminator may be empty to let the provider choose.
func (*Client) TokenSource ¶
func (c *Client) TokenSource(fetch func(ctx context.Context) (*TokenResponse, error)) func() (string, error)
TokenSource caches the token fetch returns and fetches a new one when fewer than the refresh margin (aauth.DefaultRefreshMargin) remain, for use with aauth.WithTokenSource.
type Config ¶
type Config struct {
// Issuer is the provider's server identifier: the iss of the agent
// tokens it issues.
Issuer string
// Domain is the domain of the agent identifiers it issues; default the
// issuer's host.
Domain string
// Key is the provider's signing key — separate from every agent's key
// — and KeyID its kid (default: the RFC 7638 thumbprint).
// PublishedKeys are extra keys to publish, for rotation.
Key crypto.Signer
KeyID string
PublishedKeys []aauth.JWK
// Registrar enables the HTTP endpoints; without it the provider only
// issues through [Server.IssueAgentToken].
Registrar Registrar
// Attestation verifies attestation evidence; without it a request
// carrying evidence is refused.
Attestation AttestationVerifier
// Replay remembers naming-JWT identifiers; nil uses an in-process
// cache.
Replay ReplayCache
// Limiter, when set, limits issuance per presented key ("issue:"),
// refreshes per durable key ("refresh:"), and sub-agent requests per
// parent ("subagent:"), answered 429 rate_limited with Retry-After.
// It is checked after the request authenticates and before the
// Registrar is asked.
Limiter aauth.Limiter
// OnIssue is called for every token issued.
OnIssue func(ctx context.Context, t IssuedToken)
// TokenTTL is the default token lifetime (default one hour).
TokenTTL time.Duration
// Paths overrides endpoint paths; Metadata supplies display members
// and callback_endpoint, event_endpoint, and localhost_callback_allowed.
Paths Paths
Metadata aauth.AgentProviderMetadata
// SignatureWindow is the HTTP signature validity window.
SignatureWindow time.Duration
// MaxNamingJWTLifetime is the furthest ahead a refresh's naming JWT may
// expire (default [DefaultMaxNamingJWTLifetime]). A longer lifetime is
// refused, not shortened: replay state is kept until the JWT expires.
MaxNamingJWTLifetime time.Duration
// HTTPClient makes outbound requests (revocation); nil uses
// aauth.DiscoveryClient, which reaches only public https destinations.
HTTPClient *http.Client
// Logger receives operational errors; nil discards them.
Logger *slog.Logger
// Now returns the current time; nil means time.Now.
Now func() time.Time
}
Config configures a Server. Issuer and Key are required.
type IssueRequest ¶
type IssueRequest struct {
// Key is the presented public key; the token's cnf.jwk.
Key aauth.JWK
// Body is the request body (the AP's own enrollment fields).
Body json.RawMessage
// Attestation is the verified platform attestation, when the body
// carried one (bootstrap §5).
Attestation *Attestation
}
IssueRequest is an issuance request over HTTP for the key the request presented under the hwk scheme, which also signed it.
type IssuedToken ¶
type IssuedToken struct {
Token string
Claims *aauth.AgentClaims
}
IssuedToken is reported to Config.OnIssue for every agent token the provider issues: record it to revoke it later (the jti and exp) or to count a parent's live sub-agents.
type MemoryReplayCache ¶
type MemoryReplayCache struct {
// MaxEntries bounds the cache; zero means [DefaultReplayCacheEntries].
MaxEntries int
// contains filtered or unexported fields
}
MemoryReplayCache is an in-process ReplayCache. It holds at most MaxEntries unexpired identifiers; beyond that Remember fails with ErrReplayCacheFull.
func NewMemoryReplayCache ¶
func NewMemoryReplayCache() *MemoryReplayCache
NewMemoryReplayCache returns an empty MemoryReplayCache.
type Paths ¶
type Paths struct {
JWKS string
Token string // hwk: enrollment and single-key refresh (bootstrap §8.2)
Refresh string // jkt-jwt: two-key refresh (bootstrap §8.1)
Subagent string // jwt: sub-agent issuance under a hosted AP (bootstrap §9.2)
}
Paths are the URL paths the agent provider serves, relative to its issuer. The metadata document is always /.well-known/aauth-agent.json. Bootstrap defines no endpoint paths ("each AP defines its own"); these defaults live under /ap/ so an AP can share an origin with a PS and an AS.
type RefreshRequest ¶
type RefreshRequest struct {
Durable aauth.JWK
DurableID string // urn:jkt:sha-256:...
Ephemeral aauth.JWK
Body json.RawMessage
Attestation *Attestation
}
RefreshRequest is a two-key refresh (bootstrap §8.1): the durable key identified by its thumbprint URI delegated to a fresh ephemeral key.
type Registrar ¶
type Registrar interface {
// AuthorizeIssue authorizes an hwk-signed issuance request; r is the
// HTTP request, so the application can read its own authentication
// (a session, a bearer credential) from it.
AuthorizeIssue(ctx context.Context, r *http.Request, req *IssueRequest) (*Registration, error)
// AuthorizeRefresh authorizes a two-key refresh: look up the
// enrollment by DurableID and re-check posture.
//
// The provider remembers the naming JWT's identifier only after this
// returns successfully, so that a key that is not enrolled cannot fill
// the replay cache. A captured request that is replayed therefore
// reaches AuthorizeRefresh again before the replay is rejected: the
// method must be a side-effect-free lookup, or idempotent, and must not
// consume one-time state.
AuthorizeRefresh(ctx context.Context, req *RefreshRequest) (*Registration, error)
// AuthorizeSubagent applies sub-agent policy: whether this parent may
// spawn, how many may be live, for how long.
AuthorizeSubagent(ctx context.Context, req *SubagentRequest) (*SubagentGrant, error)
}
Registrar is the hosting application's issuance policy. It decides who gets an agent token for which key, so tokens can be tied to the application's own records (an enrollment, a work session). Returning an error refuses the request (403 denied).
type Registration ¶
type Registration struct {
// Name is the agent identifier's local part (the domain is the
// provider's); it must be stable for the agent install (bootstrap §6).
Name string
// PS is the person server, carried as the ps claim.
PS string
// TTL is the token lifetime (default Config.TokenTTL, at most 24h).
TTL time.Duration
}
Registration is what the provider issues an agent token with.
type ReplayCache ¶
type ReplayCache interface {
// Remember records key until exp and reports whether it was new.
Remember(ctx context.Context, key string, exp time.Time) (bool, error)
}
ReplayCache remembers naming-JWT identifiers until they expire, so a captured refresh request cannot be replayed (bootstrap §8.1).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a hosted agent provider (draft-hardt-aauth-bootstrap-02): it publishes aauth-agent.json and its JWKS, issues agent tokens for keys it has authorized, refreshes them by the two-key jkt-jwt ceremony, issues sub-agent tokens to parents, and revokes agent tokens at person servers.
func (*Server) IssueAgentToken ¶
func (s *Server) IssueAgentToken(ctx context.Context, p AgentTokenParams) (string, *aauth.AgentClaims, error)
IssueAgentToken issues an agent token for an arbitrary public key the hosting application has authorized — for example a key bound to its own session record — signed by the provider's key (draft -11 §5.3). It reports the token to Config.OnIssue.
func (*Server) Metadata ¶
func (s *Server) Metadata() aauth.AgentProviderMetadata
Metadata returns the provider's metadata document (draft -11 §11.2.1).
func (*Server) Register ¶
Register adds the provider routes to mux. The issuance endpoints are registered only with a Registrar.
func (*Server) Resolver ¶
func (s *Server) Resolver() aauth.KeyResolver
Resolver is a KeyResolver for this provider's own tokens, for verifiers collocated with it.
func (*Server) RevokeAgentToken ¶
RevokeAgentToken revokes an agent token the provider issued at the agent's person server (draft -11 §11.12.2: agent tokens are revoked at the PS only), discovering its revocation endpoint and signing as the provider. The PS cascades to the person and auth tokens it issued to the agent.
func (*Server) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP serves the provider routes.
func (*Server) Signer ¶
func (s *Server) Signer() aauth.ServerSigner
Signer returns the provider's server signer, for revocations (§11.3.2).
type SubagentGrant ¶
type SubagentGrant struct {
// Discriminator overrides the requested one; empty keeps it, or
// generates one when none was requested.
Discriminator string
// TTL is the lifetime; never later than the parent's token.
TTL time.Duration
}
SubagentGrant shapes an authorized sub-agent token.
type SubagentRequest ¶
type SubagentRequest struct {
Parent *aauth.AgentClaims
Key aauth.JWK
Discriminator string
}
SubagentRequest is a parent's request for a sub-agent token (bootstrap §9.2): the parent's verified agent token, the sub-agent's public key, and the discriminator it asked for, if any.
type TokenResponse ¶
type TokenResponse struct {
AgentToken string `json:"agent_token"`
ExpiresIn int64 `json:"expires_in"`
}
TokenResponse is the body of a successful issuance, refresh, or sub-agent response.