capability

package module
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 18 Imported by: 1

README

capability

CI CodeQL Coverage Mutation Documentation Go Reference Release Go License

capability issues and verifies narrowly scoped, tamper-evident, expiring Go capabilities and signed URLs. It is designed for downloads, uploads, invitations, callbacks, one-time actions, delegated access, and service-to-service handoffs.

The module is not an authentication system, a policy engine, a session format, a JWT or PASETO replacement, payload encryption, DRM, or legal non-repudiation. Applications remain responsible for authenticating callers and authorizing each attempted use of a verified grant.

Install

go get github.com/faustbrian/go-capability@v1

The core module has no non-standard-library runtime dependencies.

Use github.com/faustbrian/go-capability/adapters/http for net/http integration and github.com/faustbrian/go-capability/adapters/memory for process-local replay and revocation state. The released caphttp and memory paths remain compatibility facades during the documented migration interval.

Run make clean-consumer to compile the complete public surface from a fresh external module with no repository workspace assistance. make interoperability reproduces the HMAC golden token with Python's independent standard-library implementation.

Issue and verify

key := []byte("a 32-byte-or-longer secret belongs outside source")
signer, _ := capability.NewHMACSHA256Signer("2026-08", key)
verifier, _ := capability.NewHMACSHA256Verifier(key)

payload := capability.Payload{
    Version: 1,
    Issuer: "https://issuer.example",
    Audiences: []string{"download-service"},
    Bearer: true,
    Resource: "documents/report-42",
    Operation: "download",
    IssuedAt: now,
    NotBefore: now,
    ExpiresAt: now.Add(5 * time.Minute),
    ID: "cap-42",
    MaxUses: 1,
}
token, _ := capability.Issue(ctx, payload, signer, capability.DefaultLimits())

keys, _ := capability.NewKeySet([]capability.Key{{ID: "2026-08", Verifier: verifier}})
grant, _ := capability.Verify(ctx, token, keys, capability.VerifyOptions{
    Now: now, Skew: time.Minute, Limits: capability.DefaultLimits(),
})

err := grant.Authorize(capability.Use{
    Audience: "download-service",
    Resource: "documents/report-42",
    Operation: "download",
})

Parse only establishes canonical structure. Verify authenticates the token, checks time, key lifecycle, and optional revocation policy. Authorize checks the concrete resource operation. A bounded capability must additionally call Grant.Consume against an atomic store before performing the protected side effect.

Signed URLs

Signed URLs use the same capability payload and key policy. The URL profile fixes schemes, authorities, allowed query names, the signature parameter, the method, and whether a SHA-256 body digest is required. Duplicate parameters, fragments, traversal segments, encoded slashes, userinfo, authority changes, and insecure scheme changes are rejected.

profile := capability.URLProfile{
    Name: "download-v1",
    SignatureParameter: "cap",
    AllowedSchemes: []string{"https"},
    AllowedAuthorities: []string{"files.example"},
    QueryParameters: []string{"download"},
}

payload.Resource = ""
payload.Operation = ""
signed, _ := capability.SignURL(ctx, payload, capability.URLRequest{
    Method: "GET",
    RawURL: "https://files.example/report/42?download=1",
}, profile, signer, capability.DefaultLimits())

Absolute URL schemes and authorities are allowlisted. Relative URLs are accepted only when AllowRelative is explicit. Query names and values are encoded with url.Values.Encode; exactly one value per query name is allowed.

Replay and revocation

MaxUses == 0 means reusable. Positive limits require a ConsumptionStore whose Consume operation atomically commits only while the count remains below the signed maximum. Any storage error has an unknown outcome and is returned as ErrConsumptionUnknown; do not retry the business side effect blindly.

adapters/memory.ConsumptionStore and adapters/memory.Revocations are process-local adapters. They are suitable only when one process owns all decisions. They do not provide cluster coordination or instant global revocation.

Revocation checks can match capability ID, signing key ID, subject, exact issuer/tenant/resource, or an issuer-wide issued-before cutoff. Remote stores must document their consistency and maximum stale-acceptance window.

Key rotation and remote resolution

KeySet binds every key ID to exactly one algorithm and preserves explicit disabled, revoked, not-before, and not-after state. Include old and new keys during a planned overlap, issue only with the new signer, then disable or remove the old verifier after all old capabilities expire.

BoundedResolver constrains a caller-provided remote source by algorithm, key-ID length, and deadline. The source must honor context cancellation. Key material must never be placed in tokens, URLs, errors, logs, traces, fixtures, or metrics.

Canonical v1 contract

Tokens use canonical JSON, unpadded base64url, explicit algorithms and key IDs, bounded parsing, and byte-preserving UTF-8 semantics. See the protocol and threat model for the complete wire contract.

Documentation

Use the documentation index for the protocol, API, deployment, conformance, replay, revocation, security, and adoption guidance. Material standards interpretations and local protocol policy are recorded in the specification decision register.

For ecosystem-wide package selection, construction, ownership, and lifecycle guidance, see the versioned Golib ecosystem index and its Service edge family.

License

MIT. See LICENSE.

Documentation

Overview

Package capability issues and verifies narrowly scoped, tamper-evident grants. Verification authenticates encoded authority; applications must separately authorize each attempted use against the returned Grant.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidConfiguration reports an unsafe or incomplete caller policy.
	ErrInvalidConfiguration = errors.New("capability: invalid configuration")
	// ErrInvalidPayload reports a malformed or semantically invalid payload.
	ErrInvalidPayload = errors.New("capability: invalid payload")
	// ErrNonCanonical reports a valid representation that is not the unique v1 encoding.
	ErrNonCanonical = errors.New("capability: non-canonical encoding")
	// ErrInvalidToken reports malformed token framing or protected metadata.
	ErrInvalidToken = errors.New("capability: invalid token")
	// ErrInvalidSignature reports that the protected token did not authenticate.
	ErrInvalidSignature = errors.New("capability: invalid signature")
	// ErrSigningFailed reports a signer failure without exposing its diagnostic.
	ErrSigningFailed = errors.New("capability: signing failed")
	// ErrKeyResolution reports a resolver failure without exposing its diagnostic.
	ErrKeyResolution = errors.New("capability: key resolution failed")
	// ErrAlgorithmMismatch reports an algorithm and key-type mismatch.
	ErrAlgorithmMismatch = errors.New("capability: algorithm mismatch")
	// ErrUnknownKey reports an unresolved key identifier.
	ErrUnknownKey = errors.New("capability: unknown key")
	// ErrKeyDisabled reports a key that policy has disabled without revoking it.
	ErrKeyDisabled = errors.New("capability: key disabled")
	// ErrKeyRevoked reports a key that policy has revoked.
	ErrKeyRevoked = errors.New("capability: key revoked")
	// ErrKeyNotActive reports a key outside its configured activation interval.
	ErrKeyNotActive = errors.New("capability: key not active")
	// ErrNotYetValid reports a capability used before its validity interval.
	ErrNotYetValid = errors.New("capability: not yet valid")
	// ErrExpired reports a capability used after its validity interval.
	ErrExpired = errors.New("capability: expired")
	// ErrUnauthorized reports that a verified grant does not cover a requested use.
	ErrUnauthorized = errors.New("capability: unauthorized use")
	// ErrInvalidURL reports a URL or profile with ambiguous or unsafe semantics.
	ErrInvalidURL = errors.New("capability: invalid URL")
	// ErrURLBinding reports a valid capability presented for a different URL use.
	ErrURLBinding = errors.New("capability: URL binding mismatch")
	// ErrReplayExhausted reports a capability whose bounded uses were consumed.
	ErrReplayExhausted = errors.New("capability: use limit exhausted")
	// ErrReplayConflict reports inconsistent state for one capability identity.
	ErrReplayConflict = errors.New("capability: replay state conflict")
	// ErrConsumptionUnknown reports a store failure whose commit outcome is unknown.
	ErrConsumptionUnknown = errors.New("capability: consumption outcome unknown")
	// ErrRevoked reports authority matched by a configured revocation boundary.
	ErrRevoked = errors.New("capability: revoked")
	// ErrRevocationUnknown reports a revocation check that could not complete.
	ErrRevocationUnknown = errors.New("capability: revocation status unknown")
	// ErrAdapterProtocol reports a malformed durable-adapter response.
	ErrAdapterProtocol = errors.New("capability: adapter protocol violation")
)

Functions

func CanonicalPayload

func CanonicalPayload(payload Payload, limits Limits) ([]byte, error)

CanonicalPayload validates payload and returns its unique UTF-8 JSON encoding. Strings are preserved byte-for-byte: canonically equivalent Unicode forms remain distinct authority strings and are never normalized implicitly.

func Issue

func Issue(ctx context.Context, payload Payload, signer Signer, limits Limits) (string, error)

Issue validates and signs one canonical v1 payload.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/faustbrian/go-capability"
)

func main() {
	now := time.Date(2026, 8, 9, 12, 0, 0, 0, time.UTC)
	key := []byte("0123456789abcdef0123456789abcdef")
	signer, _ := capability.NewHMACSHA256Signer("example-key", key)
	verifier, _ := capability.NewHMACSHA256Verifier(key)
	payload := capability.Payload{
		Version: 1, Issuer: "https://issuer.example",
		Audiences: []string{"download"}, Bearer: true,
		Resource: "reports/42", Operation: "download",
		IssuedAt: now, NotBefore: now, ExpiresAt: now.Add(time.Minute),
		ID: "example-capability",
	}
	token, _ := capability.Issue(context.Background(), payload, signer, capability.DefaultLimits())
	keys, _ := capability.NewKeySet([]capability.Key{{ID: "example-key", Verifier: verifier}})
	grant, _ := capability.Verify(context.Background(), token, keys, capability.VerifyOptions{
		Now: now, Limits: capability.DefaultLimits(),
	})
	err := grant.Authorize(capability.Use{
		Audience: "download", Resource: "reports/42", Operation: "download",
	})
	fmt.Println(err == nil)
}
Output:
true

func SignURL

func SignURL(
	ctx context.Context,
	payload Payload,
	request URLRequest,
	profile URLProfile,
	signer Signer,
	limits Limits,
) (string, error)

SignURL binds a capability to a canonical URL and inserts the token in the profile's signature parameter. Payload Resource and Operation must be empty because this function owns those authority fields.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/faustbrian/go-capability"
)

func main() {
	now := time.Date(2026, 8, 9, 12, 0, 0, 0, time.UTC)
	signer, _ := capability.NewHMACSHA256Signer(
		"example-key", []byte("0123456789abcdef0123456789abcdef"),
	)
	payload := capability.Payload{
		Version: 1, Issuer: "https://issuer.example",
		Audiences: []string{"download"}, Bearer: true,
		IssuedAt: now, NotBefore: now, ExpiresAt: now.Add(time.Minute),
		ID: "example-url",
	}
	profile := capability.URLProfile{
		Name: "download-v1", SignatureParameter: "cap",
		AllowedSchemes:     []string{"https"},
		AllowedAuthorities: []string{"files.example"},
		QueryParameters:    []string{"download"},
	}
	signed, _ := capability.SignURL(context.Background(), payload, capability.URLRequest{
		Method: "GET", RawURL: "https://files.example/report/42?download=1",
	}, profile, signer, capability.DefaultLimits())
	fmt.Println(len(signed) > len("https://files.example/report/42?download=1"))
}
Output:
true

Types

type Algorithm

type Algorithm string

Algorithm identifies a reviewed signature construction and its required key type.

const (
	// HMACSHA256 is HMAC with SHA-256 and keys of at least 256 bits.
	HMACSHA256 Algorithm = "hmac-sha256"
	// Ed25519 is the standard-library Ed25519 signature scheme.
	Ed25519 Algorithm = "ed25519"
)

type BoundedResolver

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

BoundedResolver constrains remote key lookups by algorithm, identifier size, and deadline.

func NewBoundedResolver

func NewBoundedResolver(options BoundedResolverOptions) (*BoundedResolver, error)

NewBoundedResolver validates remote resolution policy.

func (*BoundedResolver) Resolve

func (resolver *BoundedResolver) Resolve(ctx context.Context, keyID string, algorithm Algorithm) (ResolvedKey, error)

Resolve calls the remote source under the configured deadline and rechecks algorithm binding.

type BoundedResolverOptions

type BoundedResolverOptions struct {
	Source            Resolver
	Timeout           time.Duration
	AllowedAlgorithms []Algorithm
	MaxKeyIDBytes     int
}

BoundedResolverOptions limits a caller-provided remote key source. Source must honor context cancellation; this adapter creates no hidden goroutines.

type Consumption

type Consumption struct {
	CapabilityID string
	ExpiresAt    time.Time
	MaxUses      uint32
}

Consumption is the immutable identity and bound for one atomic use attempt.

type ConsumptionResult

type ConsumptionResult struct {
	Use       uint32
	Remaining uint32
	Reusable  bool
}

ConsumptionResult is the committed use ordinal and remaining allowance.

type ConsumptionStore

type ConsumptionStore interface {
	Consume(context.Context, Consumption) (ConsumptionResult, error)
}

ConsumptionStore atomically increments a capability only when its committed use count remains below MaxUses. Any non-policy error may represent an unknown commit outcome and callers must fail closed rather than retry blindly.

type ConsumptionStoreFunc

type ConsumptionStoreFunc func(context.Context, Consumption) (ConsumptionResult, error)

ConsumptionStoreFunc adapts a function to ConsumptionStore.

func (ConsumptionStoreFunc) Consume

func (function ConsumptionStoreFunc) Consume(ctx context.Context, consumption Consumption) (ConsumptionResult, error)

Consume implements ConsumptionStore.

type Grant

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

Grant is authenticated capability authority. Its contents are copied so caller mutation cannot widen a previously verified grant.

func Verify

func Verify(ctx context.Context, token string, resolver Resolver, options VerifyOptions) (Grant, error)

Verify authenticates and validates a token. It does not authorize an attempted resource operation; use Grant.Authorize for that separate step.

func VerifyURL

func VerifyURL(
	ctx context.Context,
	request URLRequest,
	profile URLProfile,
	resolver Resolver,
	options VerifyOptions,
) (Grant, error)

VerifyURL authenticates the embedded capability and independently checks its method, authority, path, query, profile, and optional body-digest binding.

func (Grant) Authorize

func (grant Grant) Authorize(use Use) error

Authorize checks exact encoded authority separately from parsing and signature verification.

func (Grant) Consume

func (grant Grant) Consume(ctx context.Context, store ConsumptionStore) (ConsumptionResult, error)

Consume atomically records one bounded use. Reusable grants do not require a store.

func (Grant) Header

func (grant Grant) Header() Header

Header returns the authenticated key and algorithm metadata.

func (Grant) Payload

func (grant Grant) Payload() Payload

Payload returns a defensive copy of the authenticated payload.

type Header struct {
	Version   int       `json:"v"`
	Type      string    `json:"typ"`
	Algorithm Algorithm `json:"alg"`
	KeyID     string    `json:"kid"`
}

Header is protected token metadata. Algorithm is authenticated but is also checked against the trusted verifier returned by Resolver.

type Key

type Key struct {
	ID        string
	Verifier  Verifier
	Disabled  bool
	Revoked   bool
	NotBefore time.Time
	NotAfter  time.Time
}

Key is immutable verifier policy for one globally unique key ID.

type KeySet

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

KeySet is an immutable in-process resolver suitable for explicit rotation overlap.

func NewKeySet

func NewKeySet(keys []Key) (*KeySet, error)

NewKeySet validates and copies key lifecycle policy. A key ID binds exactly one algorithm, so duplicate IDs are rejected even when key types differ.

func (*KeySet) Resolve

func (set *KeySet) Resolve(ctx context.Context, keyID string, algorithm Algorithm) (ResolvedKey, error)

Resolve returns immutable local key policy.

type Limits

type Limits struct {
	MaxTokenBytes  int
	MaxFieldBytes  int
	MaxAudiences   int
	MaxCaveats     int
	MaxCaveatBytes int
	MaxLifetime    time.Duration
	MaxUses        uint32
}

Limits bounds every attacker-controlled token and payload collection.

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits returns the reviewed v1 resource limits.

type Parsed

type Parsed struct {
	Header  Header
	Payload Payload
	// contains filtered or unexported fields
}

Parsed is a structurally valid canonical token. It is not authenticated and grants no authority until passed to Verify.

func Parse

func Parse(token string, limits Limits) (Parsed, error)

Parse validates token framing and canonical protected fields without authenticating them or authorizing resource use.

type Payload

type Payload struct {
	Version       int
	Issuer        string
	Audiences     []string
	Subject       string
	Bearer        bool
	Resource      string
	Operation     string
	IssuedAt      time.Time
	NotBefore     time.Time
	ExpiresAt     time.Time
	ID            string
	Tenant        string
	CorrelationID string
	MaxUses       uint32
	Caveats       map[string]string
}

Payload is the authority encoded by a v1 capability. Subject and Bearer are mutually exclusive. MaxUses zero means reusable; a positive value is an upper bound enforced by a replay store, not by signature verification alone.

func ParsePayload

func ParsePayload(encoded []byte, limits Limits) (Payload, error)

ParsePayload accepts only the exact canonical v1 byte representation.

type ResolvedKey

type ResolvedKey struct {
	Verifier  Verifier
	Disabled  bool
	Revoked   bool
	NotBefore time.Time
	NotAfter  time.Time
}

ResolvedKey is trusted key-policy state returned by a Resolver.

type Resolver

type Resolver interface {
	Resolve(context.Context, string, Algorithm) (ResolvedKey, error)
}

Resolver finds a trusted verifier and its current lifecycle policy.

type ResolverFunc

type ResolverFunc func(context.Context, string, Algorithm) (ResolvedKey, error)

ResolverFunc adapts a function to Resolver.

func (ResolverFunc) Resolve

func (function ResolverFunc) Resolve(ctx context.Context, keyID string, algorithm Algorithm) (ResolvedKey, error)

Resolve implements Resolver.

type RevocationChecker

type RevocationChecker interface {
	Check(context.Context, RevocationQuery) (bool, error)
}

RevocationChecker checks capability, key, subject, resource, and issued-before policy.

type RevocationCheckerFunc

type RevocationCheckerFunc func(context.Context, RevocationQuery) (bool, error)

RevocationCheckerFunc adapts a function to RevocationChecker.

func (RevocationCheckerFunc) Check

func (function RevocationCheckerFunc) Check(ctx context.Context, query RevocationQuery) (bool, error)

Check implements RevocationChecker.

type RevocationQuery

type RevocationQuery struct {
	Issuer       string
	Tenant       string
	CapabilityID string
	KeyID        string
	Subject      string
	Resource     string
	IssuedAt     time.Time
}

RevocationQuery is the complete authenticated identity checked after signature and time validation.

type Signer

type Signer interface {
	Algorithm() Algorithm
	KeyID() string
	Sign(context.Context, []byte) ([]byte, error)
}

Signer signs canonical protected bytes with an explicitly bound algorithm and key ID.

func NewEd25519Signer

func NewEd25519Signer(keyID string, key ed25519.PrivateKey) (Signer, error)

NewEd25519Signer copies a standard-library private key into an Ed25519 signer.

func NewHMACSHA256Signer

func NewHMACSHA256Signer(keyID string, key []byte) (Signer, error)

NewHMACSHA256Signer copies key material into an HMAC-SHA-256 signer.

type URLProfile

type URLProfile struct {
	Name               string
	SignatureParameter string
	AllowRelative      bool
	AllowedSchemes     []string
	AllowedAuthorities []string
	QueryParameters    []string
	RequireBodyDigest  bool
}

URLProfile fixes every URL component whose interpretation affects authority. QueryParameters is an allowlist; duplicate parameters are always rejected.

func (URLProfile) Validate

func (profile URLProfile) Validate(limits Limits) error

Validate checks that a profile is deterministic and bounded under limits.

type URLRequest

type URLRequest struct {
	Method     string
	RawURL     string
	BodyDigest []byte
}

URLRequest contains the already selected external request representation. BodyDigest, when required, is exactly a SHA-256 digest rather than body bytes.

type Use

type Use struct {
	Audience  string
	Subject   string
	Resource  string
	Operation string
	Tenant    string
	Caveats   map[string]string
}

Use describes one concrete application operation to authorize.

type Verifier

type Verifier interface {
	Algorithm() Algorithm
	Verify(context.Context, []byte, []byte) error
}

Verifier authenticates canonical protected bytes with one bound algorithm.

func NewEd25519Verifier

func NewEd25519Verifier(key ed25519.PublicKey) (Verifier, error)

NewEd25519Verifier copies a standard-library public key into an Ed25519 verifier.

func NewHMACSHA256Verifier

func NewHMACSHA256Verifier(key []byte) (Verifier, error)

NewHMACSHA256Verifier copies key material into an HMAC-SHA-256 verifier.

type VerifyOptions

type VerifyOptions struct {
	Now         time.Time
	Skew        time.Duration
	Limits      Limits
	Revocations RevocationChecker
}

VerifyOptions defines the current time, accepted clock skew, and parser limits.

Directories

Path Synopsis
adapters
http
Package capabilityhttp integrates verified signed-URL grants with net/http without hiding application authorization or bounded-use consumption.
Package capabilityhttp integrates verified signed-URL grants with net/http without hiding application authorization or bounded-use consumption.
memory
Package capabilitymemory provides process-local replay and revocation adapters.
Package capabilitymemory provides process-local replay and revocation adapters.
Package caphttp is the compatibility facade for the target-oriented HTTP adapter.
Package caphttp is the compatibility facade for the target-oriented HTTP adapter.
Package memory is the compatibility facade for the target-oriented process-local replay and revocation adapter.
Package memory is the compatibility facade for the target-oriented process-local replay and revocation adapter.
Package postgres provides durable atomic capability consumption through database/sql and PostgreSQL row locks.
Package postgres provides durable atomic capability consumption through database/sql and PostgreSQL row locks.
identity module
Package valkey provides atomic bounded-use consumption through one Valkey EVAL operation.
Package valkey provides atomic bounded-use consumption through one Valkey EVAL operation.

Jump to

Keyboard shortcuts

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