softwarestatement

package module
v0.0.0-...-55f89e3 Latest Latest
Warning

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

Go to latest
Published: Dec 16, 2025 License: Apache-2.0 Imports: 7 Imported by: 0

README

authlib/softwarestatement

Go Reference CI Status

OAuth 2.0 Software Statement for Go.

Overview

This package provides utilities for creating, parsing, and verifying software statement JWTs as defined in RFC 7591 Section 2.3. Software statements are signed JWTs containing client metadata, used in dynamic client registration^dcr to prove the software publisher's identity. It also includes helpers for the Trusted Issuer Token draft so you can process SPIFFE JWT-SVIDs and Verifiable Credential JWTs as software statements.

Installation

go get github.com/matoous/authlib/softwarestatement

Example

package main

import (
    "crypto/rsa"
    "fmt"
    "log"

    "github.com/golang-jwt/jwt/v5"
    "github.com/matoous/authlib/softwarestatement"
)

func main() {
    statementJWT := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

    // Parse and validate the software statement
    claims, err := softwarestatement.ParseWithOptions(statementJWT, func(token *jwt.Token) (any, error) {
        // Return the public key for verification
        // In practice, fetch this from a JWKS endpoint or trusted source
        var publicKey *rsa.PublicKey
        // ... load public key ...
        return publicKey, nil
    }, jwt.WithAudience("https://as.example.org/register"))
    if err != nil {
        log.Fatalf("failed to parse software statement: %v", err)
    }

    fmt.Printf("Software ID: %v\n", claims.SoftwareID)
    fmt.Printf("Client Name: %v\n", claims.ClientName)
    fmt.Printf("Redirect URIs: %v\n", claims.RedirectURIs)
}

Trusted Issuer Tokens

Section 5 of the Trusted Issuer Token draft describes how SPIFFE JWT-SVIDs and VC-JWTs may be used as software statements. Use the dedicated helpers to enforce the normative requirements:

spiffeClaims, err := softwarestatement.ValidateSPIFFE(
    req.SoftwareStatement,
    jwk.Keyfunc(),
    softwarestatement.SPIFFEValidationOptions{
        TrustDomain: "spiffe://example.org",
        Audience:    "https://as.example.org/register",
        MetadataValidators: []softwarestatement.MetadataValidator{
            softwarestatement.RequireMetadata(
                softwarestatement.MetadataClientName,
                softwarestatement.MetadataRedirectURIs,
            ),
        },
    },
)

vcClaims, err := softwarestatement.ValidateVerifiableCredential(
    req.SoftwareStatement,
    jwk.Keyfunc(),
    softwarestatement.VCValidationOptions{
        TrustedIssuer: "https://issuer.example",
        Audience:      "https://as.example.org/register",
        StatusValidator: func(status map[string]any) error {
            // implement the VC revocation/status check required by your deployment
            return nil
        },
    },
)

var vcPayload struct {
    CredentialSubject map[string]any `json:"credentialSubject"`
}
if err := vcClaims.UnmarshalVC(&vcPayload); err != nil {
    log.Fatal(err)
}
Issuing trusted issuer tokens

If you operate the trusted issuer, use the helpers below to mint software statements that follow the draft's requirements before distributing them to OAuth clients:

statement, err := softwarestatement.IssueSPIFFESoftwareStatement(signingKey, softwarestatement.SPIFFEIssueOptions{
    TrustDomain: "spiffe://example.org",
    WorkloadPath: "/workload/771",
    Audience: []string{"https://as.example.org/register"},
    TTL: 10 * time.Minute,
    Metadata: &authlib.ClientRegistrationRequest{
        ClientName:   "SPIFFE Client",
        RedirectURIs: []string{"https://spiffe-app.example/callback"},
    },
})

vcStatement, err := softwarestatement.IssueVerifiableCredentialSoftwareStatement(signingKey, softwarestatement.VerifiableCredentialIssueOptions{
    Issuer:   "https://vc-issuer.example",
    Subject:  "client-123",
    Audience: []string{"https://as.example.org/register"},
    TTL:      10 * time.Minute,
    VC: map[string]any{
        "credentialSubject": map[string]any{
            "client_name": "VC Client",
        },
    },
})
SPIFFE JWT-SVID software statements

The draft's Section 5.1 defines a trust relationship between the Issuer (the SPIFFE trust domain) and the OAuth Client Registration Endpoint. ValidateSPIFFE helps you codify that relationship:

  • Anchor the registration endpoint's trust configuration to a fixed list of trust domains. Provide the expected value through SPIFFEValidationOptions.TrustDomain and ensure the JWT's iss and sub claims share the same domain.
  • Resolve the issuer's signing key through SPIFFE's control plane or a pinned JWKS reference before you call ValidateSPIFFE. The registration endpoint should only accept keys that chain back to the configured trust domain.
  • Treat the registration endpoint URL as an OAuth resource server identifier and require it in the aud claim (SPIFFEValidationOptions.Audience). Section 5.1 mandates that the Issuer targets the specific Client Registration Endpoint instance it is talking to.
  • After signature and claim validation, inspect the returned Claims to apply policy: e.g., restrict metadata to specific redirect URIs, enforce software identifiers, or map SPIFFE IDs to pre-approved client templates.
Verifiable Credential software statements

Section 5.2 pivots the trust relationship to a VC-JWT Issuer. The authorization server (or its Client Registration Endpoint) needs to pre-establish out-of-band trust in that Issuer. ValidateVerifiableCredential makes those checks explicit:

  • Capture the Issuer identifier you negotiated with in VCValidationOptions.TrustedIssuer. ValidateVerifiableCredential rejects any VC signed by an unexpected Issuer so that only trusted credential issuers can influence client onboarding.
  • Require the Client Registration Endpoint identifier in the VC audience via VCValidationOptions.Audience. This prevents credentials minted for one AS from being replayed elsewhere.
  • If your deployment issues per-client subjects, populate VCValidationOptions.Subject to make sure the VC holder is the party you expect. Use Claims.UnmarshalVC to inspect the VC payload (credentialSubject, evidence, etc.) after the initial checks succeed.
  • To honor the draft's guidance on revocation and lifecycle management, pass a StatusValidator. Common implementations dereference the credentialStatus entry, validate its signature, and ensure the status indicates the credential is active before proceeding with client registration.
  • Finally, reuse the existing metadata validators (e.g., RequireMetadata) so that a trusted Issuer cannot skip mandatory OAuth client metadata and so that everything is stored exactly as the registration endpoint policy demands.

Documentation

For complete documentation, see the Go package documentation.

Documentation

Overview

Package softwarestatement provides helpers for working with OAuth 2.0 Dynamic Client Registration software statements as described in RFC 7591 Section 2.3. It offers utilities to parse, validate, and create JWT-based statements that convey OAuth client metadata, including experimental trusted issuer tokens backed by Verifiable Credentials and SPIFFE JWT-SVIDs.

The package wraps github.com/golang-jwt/jwt/v5 so callers can parse or create a statement with familiar JWT primitives:

signed, err := softwarestatement.Create(claims, signingKey)
parsed, err := softwarestatement.ParseWithOptions(
	signed,
	keyFunc,
	jwt.WithAudience("https://as.example.org/register"),
)

For deployments adopting the OAuth 2.0 Dynamic Client Registration with Trusted Issuer Credentials draft, helper types such as SPIFFEValidationOptions and RequireMetadata enforce draft-kasselman-oauth-dcr-trusted-issuer-token requirements, including SPIFFE trust domain checks and mandatory client metadata.

References:

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Create

func Create(claims *Claims, signingKey any) (string, error)

Create creates a signed software statement JWT from the provided claims and signing key. The signing method is automatically determined from the key type.

func IssueSPIFFESoftwareStatement

func IssueSPIFFESoftwareStatement(signingKey any, opts SPIFFEIssueOptions) (string, error)

IssueSPIFFESoftwareStatement creates and signs a SPIFFE-based trusted issuer token that satisfies the requirements from Section 5.1 of the trusted issuer credential draft. It validates the trust domain, subject, TTL, and audiences before delegating to Create.

func IssueVerifiableCredentialSoftwareStatement

func IssueVerifiableCredentialSoftwareStatement(signingKey any, opts VerifiableCredentialIssueOptions) (string, error)

IssueVerifiableCredentialSoftwareStatement creates a VC-backed trusted issuer token, validating issuer/subject/audience inputs and ensuring a vc payload is embedded before signing the JWT.

Types

type Claims

type Claims struct {
	jwt.RegisteredClaims
	authlib.ClientRegistrationRequest

	// VC holds an optional Verifiable Credential payload when the software statement
	// is implemented as a VC-JWT according to the trusted issuer token draft.
	//
	// [Trusted Issuer Token Draft]: https://www.ietf.org/archive/id/draft-kasselman-oauth-dcr-trusted-issuer-token-01.html
	VC json.RawMessage `json:"vc,omitempty"`
}

Claims represents the claims in a software statement JWT. Software statements are JWTs that contain metadata about client software, signed by the software publisher. It embeds the standard ClientRegistrationRequest to inherit all client metadata fields.

func Parse

func Parse(statementJWT string, keyFunc jwt.Keyfunc) (*Claims, error)

Parse parses and validates a software statement JWT string using default validation behavior from jwt.ParseWithClaims. Use ParseWithOptions when you need to pass custom parser or claims validation options such as audience, issuer, or leeway requirements. The keyFunc is called to provide the verification key for the JWT signature. Returns the parsed claims if validation succeeds.

func ParseUnverified

func ParseUnverified(statementJWT string) (*Claims, error)

ParseUnverified parses a software statement JWT without verifying the signature. This should only be used when signature verification is not required or will be performed separately.

WARNING: This does not validate the JWT signature. Use Parse for signature verification.

func ParseWithOptions

func ParseWithOptions(statementJWT string, keyFunc jwt.Keyfunc, opts ...jwt.ParserOption) (*Claims, error)

ParseWithOptions parses and validates a software statement JWT string while honoring the supplied jwt.ParserOption set. This allows callers to rely on the upstream validator for standard checks like issuer, subject, audience, temporal leeway, and optional JSON decoding behavior. The keyFunc is called to provide the verification key for the JWT signature. Returns the parsed claims if validation succeeds.

func ValidateSPIFFE

func ValidateSPIFFE(statementJWT string, keyFunc jwt.Keyfunc, opts SPIFFEValidationOptions) (*Claims, error)

ValidateSPIFFE parses the JWT-SVID, verifies its signature, and enforces the SPIFFE requirements described in Section 5.1 of the trusted issuer token draft. Callers should supply a jwt.Keyfunc that is anchored to the OAuth Client Registration Endpoint's trust store (for example, the SPIFFE control plane or JWKS published by an approved trust domain) so that only issuers that are in a trust relationship with the registration endpoint can register new clients. In addition to signature verification, the helper asserts that iss/sub share the same trust domain, that the configured registration endpoint audience is present, and that any metadata validators succeed. This mirrors the draft's recommendation for verifying trusted issuer credentials before accepting the software statement contents.

func ValidateVerifiableCredential

func ValidateVerifiableCredential(statementJWT string, keyFunc jwt.Keyfunc, opts VCValidationOptions) (*Claims, error)

ValidateVerifiableCredential validates a VC-JWT based trusted issuer token, enforcing the processing guidance from Section 5.2 of the trusted issuer token draft. The supplied jwt.Keyfunc must be derived from the OAuth Client Registration Endpoint's set of trusted issuers so that only mutually trusted credential issuers can influence client onboarding. Beyond signature checks, the helper verifies iss/aud/sub as requested, ensures the credentialStatus block (when present) passes caller-provided status validation, and exposes the parsed Claims so the registration endpoint can inspect credentialSubject or embedded OAuth client metadata before provisioning the client.

func ValidateVerifiableCredentialSoftwareStatement

func ValidateVerifiableCredentialSoftwareStatement(statementJWT string, keyFunc jwt.Keyfunc, opts VCValidationOptions) (*Claims, error)

ValidateVerifiableCredentialSoftwareStatement is a convenience wrapper for ValidateVerifiableCredential. It exists to make the intent explicit when processing trusted issuer tokens.

func (*Claims) UnmarshalVC

func (c *Claims) UnmarshalVC(dst any) error

UnmarshalVC decodes the embedded Verifiable Credential payload (vc claim) into dst. This helper is useful when processing trusted issuer tokens where OAuth client metadata is conveyed in the VC structure.

type MetadataField

type MetadataField string

MetadataField represents a single OAuth client metadata attribute that can be required when validating trusted issuer tokens.

const (
	MetadataClientName              MetadataField = "client_name"
	MetadataClientURI               MetadataField = "client_uri"
	MetadataLogoURI                 MetadataField = "logo_uri"
	MetadataRedirectURIs            MetadataField = "redirect_uris"
	MetadataGrantTypes              MetadataField = "grant_types"
	MetadataResponseTypes           MetadataField = "response_types"
	MetadataScope                   MetadataField = "scope"
	MetadataContacts                MetadataField = "contacts"
	MetadataTOSURI                  MetadataField = "tos_uri"
	MetadataPolicyURI               MetadataField = "policy_uri"
	MetadataJWKSURI                 MetadataField = "jwks_uri"
	MetadataJWKS                    MetadataField = "jwks"
	MetadataTokenEndpointAuthMethod MetadataField = "token_endpoint_auth_method"
	MetadataSoftwareID              MetadataField = "software_id"
	MetadataSoftwareVersion         MetadataField = "software_version"
)

type MetadataValidator

type MetadataValidator func(*Claims) error

MetadataValidator allows callers to assert requirements on metadata extracted from a software statement.

func RequireMetadata

func RequireMetadata(fields ...MetadataField) MetadataValidator

RequireMetadata returns a MetadataValidator that ensures the provided metadata fields are populated.

type SPIFFEIssueOptions

type SPIFFEIssueOptions struct {
	// TrustDomain identifies the SPIFFE trust domain (for example "spiffe://example.org").
	// Required.
	TrustDomain string

	// Subject contains the SPIFFE ID of the workload that is registering a client. Either Subject or
	// WorkloadPath must be provided. When WorkloadPath is supplied, the function assembles the subject
	// from TrustDomain + WorkloadPath.
	Subject string

	// WorkloadPath is the SPIFFE workload path (for example "/workload/123"). Ignored if Subject is set.
	WorkloadPath string

	// Audience entries populate the aud claim targeting registration endpoints.
	// At least one value is required.
	Audience []string

	// TTL controls the lifetime of the JWT (exp = iat + TTL). Must be positive.
	TTL time.Duration

	// Metadata optionally carries OAuth client metadata copied into the resulting claims.
	Metadata *authlib.ClientRegistrationRequest

	// VC allows embedding a Verifiable Credential payload (vc claim) so SPIFFE issuers can supply
	// additional metadata through a VC structure. When nil, the vc claim is omitted.
	VC any

	// JWTID, when set, populates the jti claim.
	JWTID string

	// Now overrides the clock used for iat/exp. Defaults to time.Now.
	Now func() time.Time
}

SPIFFEIssueOptions configures how IssueSPIFFESoftwareStatement constructs a trusted issuer token. It mirrors the SPIFFE JWT-SVID requirements from Section 5.1 of the trusted issuer token draft.

type SPIFFEValidationOptions

type SPIFFEValidationOptions struct {
	// TrustDomain identifies the SPIFFE trust domain that is allowed to issue the JWT-SVID.
	// Example: "spiffe://example.org". If empty, the iss claim is only required to be non-empty.
	TrustDomain string

	// Audience must be present in the aud claim. When empty, the aud claim is only
	// verified to be non-empty.
	Audience string

	// ClockSkew defines an allowed clock skew when validating temporal claims. Defaults to 1 minute.
	ClockSkew time.Duration

	// Now allows injecting a custom time source (useful for tests). Defaults to time.Now.
	Now func() time.Time

	// MetadataValidators contains additional validation hooks for OAuth client metadata.
	MetadataValidators []MetadataValidator
}

SPIFFEValidationOptions configures validation for SPIFFE JWT-SVID based software statements.

Reference: Section 5.1 of the OAuth 2.0 Dynamic Client Registration with Trusted Issuer Credentials draft.

type VCStatusValidator

type VCStatusValidator func(status map[string]any) error

VCStatusValidator allows callers to plug in revocation/status verification for VC-based software statements.

type VCValidationOptions

type VCValidationOptions struct {
	// TrustedIssuer, when set, must match the iss claim of the VC-JWT.
	TrustedIssuer string

	// Audience, when set, must be included in the aud claim.
	Audience string

	// Subject, when set, must match the sub claim.
	Subject string

	// ClockSkew defines an allowed clock skew when validating exp/iat. Defaults to 1 minute.
	ClockSkew time.Duration

	// Now allows overriding the current time source (useful for tests).
	Now func() time.Time

	// MetadataValidators contains additional validation hooks for OAuth client metadata.
	MetadataValidators []MetadataValidator

	// StatusValidator optionally validates the credentialStatus entry inside the VC payload.
	StatusValidator VCStatusValidator
}

VCValidationOptions configures validation for Verifiable Credential JWT software statements as described in Section 5.2 of the trusted issuer token draft.

type VerifiableCredentialIssueOptions

type VerifiableCredentialIssueOptions struct {
	// Issuer is the VC Issuer identifier (iss claim). Required.
	Issuer string

	// Subject identifies the credential holder / client (sub claim). Required.
	Subject string

	// Audience entries populate the aud claim. At least one value is required.
	Audience []string

	// TTL controls exp relative to iat. Must be positive.
	TTL time.Duration

	// Metadata copies OAuth client metadata into the software statement.
	Metadata *authlib.ClientRegistrationRequest

	// VC contains the Verifiable Credential payload that will be embedded in the vc claim.
	// Required.
	VC any

	// JWTID, when set, populates the jti claim.
	JWTID string

	// Now overrides the clock used for iat/exp. Defaults to time.Now.
	Now func() time.Time
}

VerifiableCredentialIssueOptions configures IssueVerifiableCredentialSoftwareStatement to mint a VC-backed trusted issuer token (Section 5.2 of the draft).

Jump to

Keyboard shortcuts

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