openid4vp

package
v0.5.9 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: BSD-2-Clause Imports: 30 Imported by: 0

README

Go implementation of OpenID4VP draft 24

Documentation

Index

Examples

Constants

View Source
const (
	// FormatJwtVCJson is the format identifier for JWT-based W3C VC without JSON-LD.
	FormatJwtVCJson = "jwt_vc_json"
	// FormatSDJWTVC is the format identifier for IETF SD-JWT VC.
	FormatSDJWTVC = "dc+sd-jwt"
	// FormatMsoMdoc is the format identifier for ISO mdoc.
	FormatMsoMdoc = "mso_mdoc"
	// FormatLdpVCDCQL is the format identifier for W3C VC Data Integrity (used in DCQL).
	// Note: This duplicates FormatLdpVC from vc20_handler.go but is needed for non-vc20 builds.
	FormatLdpVCDCQL = "ldp_vc"
)

Credential format identifiers as defined in OpenID4VP Appendix B. Note: FormatLdpVC is defined in vc20_handler.go (vc20 build tag)

View Source
const (
	// TrustedAuthorityTypeAKI is for Authority Key Identifier matching.
	// Value is base64url-encoded KeyIdentifier from AuthorityKeyIdentifier.
	TrustedAuthorityTypeAKI = "aki"

	// TrustedAuthorityTypeETSI is for ETSI Trusted List matching.
	// Value is the URL of the Trusted List (e.g., https://lotl.example.com).
	TrustedAuthorityTypeETSI = "etsi_tl"

	// TrustedAuthorityTypeOpenIDFederation is for OpenID Federation matching.
	// Value is the Entity Identifier of the Trust Anchor.
	TrustedAuthorityTypeOpenIDFederation = "openid_federation"
)

TrustedAuthorityType constants per OpenID4VP spec Section 6.1.1

View Source
const (
	// OAuth 2.0 standard errors
	ErrorInvalidScope   = "invalid_scope"   // Requested scope value is invalid, unknown, or malformed
	ErrorInvalidRequest = "invalid_request" // Request contains invalid parameters or violates requirements
	ErrorInvalidClient  = "invalid_client"  // Client authentication failed
	ErrorAccessDenied   = "access_denied"   // User denied consent or wallet lacks credentials

	// OpenID4VP specific errors
	ErrorVPFormatsNotSupported   = "vp_formats_not_supported"   // Wallet doesn't support requested VP formats
	ErrorInvalidRequestURIMethod = "invalid_request_uri_method" // Invalid request_uri_method value
	ErrorInvalidTransactionData  = "invalid_transaction_data"   // Transaction data is invalid or malformed
	ErrorWalletUnavailable       = "wallet_unavailable"         // Wallet is unavailable to respond
)

Error codes defined in OpenID4VP spec Section 8.5

View Source
const (
	FormatLdpVC    = "ldp_vc"     // VC Data Model 1.1 with Data Integrity
	FormatVC20JSON = "vc+ld+json" // VC Data Model 2.0 with Data Integrity
)

VC20Format identifiers per OpenID4VC spec Appendix A

View Source
const (
	CryptosuiteECDSA2019 = "ecdsa-rdfc-2019"
	CryptosuiteECDSASd   = "ecdsa-sd-2023"
	CryptosuiteEdDSA2022 = "eddsa-rdfc-2022"
)

Supported cryptosuites

View Source
const (
	// DefaultEphemeralKeyTTL is the default TTL for ephemeral encryption keys
	DefaultEphemeralKeyTTL = 10 * time.Minute
)
View Source
const (
	// DefaultRequestObjectTTL is the default TTL for request objects
	DefaultRequestObjectTTL = 10 * time.Minute
)

Variables

View Source
var ErrInvalidVerifierHost = errors.New("verifier host is invalid or empty")
View Source
var StandardOIDCScopes = map[string]bool{
	"openid":         true,
	"profile":        true,
	"email":          true,
	"address":        true,
	"phone":          true,
	"offline_access": true,
}

StandardOIDCScopes contains scopes defined by OpenID Connect Core. These are protocol-level scopes that are optional for credential matching. The "openid" scope is REQUIRED by the OIDC specification and will always be present, but it does not need to be mapped to a credential.

Functions

func BuildDirectPostURL

func BuildDirectPostURL(baseURL string, response *ResponseParameters) (string, error)

BuildDirectPostURL creates a URL-encoded form data string for direct_post

func FilterStandardScopes

func FilterStandardScopes(scopes []string) []string

FilterStandardScopes removes standard OIDC scopes from the list. This is a utility function that can be used when you specifically need to identify scopes that are not standard OIDC scopes. Note that credential matching logic does NOT use this - all scopes (including standard ones) are considered for matching to allow optional credential mappings.

func GenerateQRV2

func GenerateQRV2(ctx context.Context, data string) (string, error)

func GetClaimMappings

func GetClaimMappings(template PresentationRequestTemplate) map[string]string

GetClaimMappings is a helper to extract claim mappings from a template Returns nil if the template doesn't implement this method

func IsMdocFormat

func IsMdocFormat(format string) bool

IsMdocFormat returns true if the format is ISO mdoc format.

func IsSDJWTFormatIdentifier

func IsSDJWTFormatIdentifier(format string) bool

IsSDJWTFormatIdentifier returns true if the format identifier is SD-JWT VC format. Note: This is different from sdjwtvc.IsSDJWTFormat which checks the actual token format.

func IsW3CVCFormatIdentifier

func IsW3CVCFormatIdentifier(format string) bool

IsW3CVCFormatIdentifier returns true if the format identifier is a W3C Verifiable Credential format (ldp_vc or jwt_vc_json).

func MatchCryptosuite

func MatchCryptosuite(cryptosuite string, cryptosuiteValues []string) bool

MatchCryptosuite checks if a cryptosuite is supported by the format configuration. Returns true if cryptosuiteValues is empty (no constraint) or contains the cryptosuite.

func MatchProofType

func MatchProofType(proofType string, proofTypeValues []string) bool

MatchProofType checks if a proof type is supported by the format configuration. Returns true if proofTypeValues is empty (no constraint) or contains the proofType.

func MatchTrustedAuthorities

func MatchTrustedAuthorities(
	trustedAuthorities []TrustedAuthority,
	credentialCertChain [][]byte,
	issuer string,
	matcher TrustedAuthorityMatcher,
) bool

MatchTrustedAuthorities checks if a credential matches any of the trusted authorities constraints. Returns true if trustedAuthorities is empty (no constraint) or the credential matches at least one. The matcher parameter provides the actual trust verification implementation. If matcher is nil, returns true (no validation performed).

func MatchTypeValues

func MatchTypeValues(credentialTypes []string, typeValues [][]string) bool

MatchTypeValues checks if a credential's types match any of the type_values alternatives. credentialTypes should be the fully expanded type IRIs from the credential. typeValues is the query's type_values constraint (array of string arrays). Returns true if the credential matches at least one of the alternatives.

func ValidateCredentialQuery

func ValidateCredentialQuery(query CredentialQuery) error

ValidateCredentialQuery validates that a CredentialQuery has the required fields for the specified format.

func ValidateVPToken

func ValidateVPToken(vpToken, nonce, clientID string) error

ValidateVPToken is a convenience function for basic VP Token validation

Types

type ClaimQuery

type ClaimQuery struct {
	// Path REQUIRED The value MUST be a non-empty array representing a claims path pointer that specifies the path to a claim within the Credential, as defined in Section 7.
	Path []string `json:"path" yaml:"path" validate:"required,min=1,dive,required"`
}

type ClaimTransformDef

type ClaimTransformDef struct {
	Type   string            // Transform type: date_format, boolean_string, uppercase, lowercase, etc.
	Params map[string]string // Transform parameters
}

ClaimTransformDef defines a claim transformation

type ClaimsExtractor

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

ClaimsExtractor extracts and maps claims from VP tokens to OIDC claims

func NewClaimsExtractor

func NewClaimsExtractor() *ClaimsExtractor

NewClaimsExtractor creates a new claims extractor

func (*ClaimsExtractor) ApplyClaimTransforms

func (ce *ClaimsExtractor) ApplyClaimTransforms(claims map[string]any, transformDefs map[string]ClaimTransformDef) (map[string]any, error)

ApplyClaimTransforms applies transformations to claim values transformDefs: Map of OIDC claim name to transform definition

func (*ClaimsExtractor) ExtractAndMapClaims

func (ce *ClaimsExtractor) ExtractAndMapClaims(
	ctx context.Context,
	vpToken string,
	claimMappings map[string]string,
	transformDefs map[string]ClaimTransformDef,
) (map[string]any, error)

ExtractAndMapClaims is a convenience function that combines extraction, mapping, and transformation This is the main entry point for the complete claims processing pipeline

func (*ClaimsExtractor) ExtractClaimsFromVPToken

func (ce *ClaimsExtractor) ExtractClaimsFromVPToken(ctx context.Context, vpToken string) (map[string]any, error)

ExtractClaimsFromVPToken extracts claims from a VP token. Automatically detects the format:

  • DCQL response: JSON object mapping credential query IDs to individual tokens
  • mdoc: CBOR-based mobile document
  • SD-JWT: dot-separated JWT with selective disclosures

Returns a merged map of disclosed claims from all credentials.

func (*ClaimsExtractor) MapClaimsToOIDC

func (ce *ClaimsExtractor) MapClaimsToOIDC(vpClaims map[string]any, claimMappings map[string]string) (map[string]any, error)

MapClaimsToOIDC maps VP claims to OIDC claims using the template's claim mappings claimMappings: Key = VP claim path, Value = OIDC claim name Special mapping "*" : "*" means pass all claims through unchanged

type Client

type Client struct {
	EphemeralKeyCache  *EphemeralEncryptionKeyCache
	RequestObjectCache *RequestObjectCache
}

Client holds the OpenID4VP client with ephemeral key caching and request object caching

func New

func New(ctx context.Context, config *Config) (*Client, error)

New creates a new OpenID4VP client with ephemeral key cache and request object cache

Example

ExampleNew demonstrates creating an OpenID4VP client with default settings

package main

import (
	"context"
	"fmt"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	ctx := context.TODO()

	// Create a client with default TTL settings (10 minutes for both caches)
	client, err := openid4vp.New(ctx, nil)
	if err != nil {
		fmt.Printf("Error creating client: %v\n", err)
		return
	}
	defer client.Close()

	fmt.Println("Client created with default settings")
	fmt.Printf("Ephemeral key cache initialized: %t\n", client.EphemeralKeyCache != nil)
	fmt.Printf("Request object cache initialized: %t\n", client.RequestObjectCache != nil)

}
Output:
Client created with default settings
Ephemeral key cache initialized: true
Request object cache initialized: true
Example (Usage)

ExampleNew_usage demonstrates using both caches in the client

package main

import (
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"fmt"

	"github.com/SUNET/vc/pkg/openid4vp"

	"github.com/lestrrat-go/jwx/v3/jwk"
)

func main() {
	ctx := context.TODO()

	// Create client
	client, err := openid4vp.New(ctx, nil)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer client.Close()

	// 1. Store a request object in the request object cache
	requestObject := &openid4vp.RequestObject{
		ResponseType: "vp_token",
		ClientID:     "https://verifier.example.com",
		Nonce:        "n-0S6_WzA2Mj",
		State:        "af0ifjsldkj",
	}
	requestURI := "urn:ietf:params:oauth:request_uri:6eSG8FrjKQb1Qiwj"
	client.RequestObjectCache.Set(requestURI, requestObject)

	// Retrieve the request object
	retrieved, found := client.RequestObjectCache.Get(requestURI)
	if found {
		fmt.Printf("Request object - Client: %s\n", retrieved.ClientID)
		fmt.Printf("Request object - State: %s\n", retrieved.State)
	}

	// 2. Store an ephemeral encryption key in the ephemeral key cache
	// Generate a test key
	privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	key, _ := jwk.Import(privateKey)
	_ = key.Set(jwk.KeyIDKey, "ephemeral-key-123")

	client.EphemeralKeyCache.Set("ephemeral-key-123", key)

	// Retrieve the ephemeral key
	retrievedKey, found := client.EphemeralKeyCache.Get("ephemeral-key-123")
	if found {
		kid, _ := retrievedKey.KeyID()
		fmt.Printf("Ephemeral key - KID: %s\n", kid)
	}

	// Check both cache sizes
	fmt.Printf("Request objects in cache: %d\n", client.RequestObjectCache.Len())
	fmt.Printf("Ephemeral keys in cache: %d\n", client.EphemeralKeyCache.Len())

}
Output:
Request object - Client: https://verifier.example.com
Request object - State: af0ifjsldkj
Ephemeral key - KID: ephemeral-key-123
Request objects in cache: 1
Ephemeral keys in cache: 1
Example (WithCustomConfig)

ExampleNew_withCustomConfig demonstrates creating a client with custom TTL settings

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	ctx := context.TODO()

	// Create a client with custom TTL values
	config := &openid4vp.Config{
		EphemeralKeyTTL:  5 * time.Minute, // Ephemeral keys expire after 5 minutes
		RequestObjectTTL: 3 * time.Minute, // Request objects expire after 3 minutes
	}

	client, err := openid4vp.New(ctx, config)
	if err != nil {
		fmt.Printf("Error creating client: %v\n", err)
		return
	}
	defer client.Close()

	fmt.Println("Client created with custom TTL settings")
	fmt.Printf("Ephemeral key cache TTL: 5 minutes\n")
	fmt.Printf("Request object cache TTL: 3 minutes\n")

}
Output:
Client created with custom TTL settings
Ephemeral key cache TTL: 5 minutes
Request object cache TTL: 3 minutes

func (*Client) Close

func (c *Client) Close()

Close stops the ephemeral key cache and request object cache

type ClientMetadata

type ClientMetadata struct {
	// JWKS OPTIONAL. A JSON Web Key Set, as defined in [RFC7591], that contains one or more public keys, such as those used by the Wallet as an input to a key agreement that may be used for encryption of the Authorization Response (see Section 8.3), or where the Wallet will require the public key of the Verifier to generate a Verifiable Presentation. This allows the Verifier to pass ephemeral keys specific to this Authorization Request. Public keys included in this parameter MUST NOT be used to verify the signature of signed Authorization Requests. Each JWK in the set MUST have a kid (Key ID) parameter that uniquely identifies the key within the context of the request.
	JWKS *Keys `json:"jwks,omitempty" bson:"jwks,omitempty" validate:"omitempty"`

	//encrypted_response_enc_values_supported: OPTIONAL. Non-empty array of strings, where each string is a JWE [RFC7516] enc algorithm that can be used as the content encryption algorithm for encrypting the Response. When a response_mode requiring encryption of the Response (such as dc_api.jwt or direct_post.jwt) is specified, this MUST be present for anything other than the default single value of A128GCM. Otherwise, this SHOULD be absent.
	EncryptedResponseEncValuesSupported []string `` /* 189-byte string literal not displayed */

	//vp_formats_supported: REQUIRED when not available to the Wallet via another mechanism. As defined in Section 11.1.
	// Per OpenID4VP spec, this is the properly formatted field for client_metadata in authorization requests
	VPFormatsSupported *VPFormatsSupported `json:"vp_formats_supported,omitempty" bson:"vp_formats_supported,omitempty"`

	// authorization_signed_response_alg: OPTIONAL. As defined in [JARM].
	AuthorizationSignedResponseALG string `` /* 152-byte string literal not displayed */
	// authorization_encrypted_response_alg: OPTIONAL. As defined in [JARM].
	AuthorizationEncryptedResponseALG string `` /* 175-byte string literal not displayed */
	// authorization_encrypted_response_enc: OPTIONAL. As defined in [JARM].
	AuthorizationEncryptedResponseENC string `` /* 178-byte string literal not displayed */
}

type Config

type Config struct {
	// EphemeralKeyTTL specifies the TTL for ephemeral encryption keys.
	// If not set or zero, DefaultEphemeralKeyTTL (10 minutes) is used.
	EphemeralKeyTTL time.Duration

	// RequestObjectTTL specifies the TTL for request objects.
	// If not set or zero, DefaultRequestObjectTTL (10 minutes) is used.
	RequestObjectTTL time.Duration
}

Config holds configuration for the OpenID4VP client

type Constraints

type Constraints struct {
	LimitDisclosure string  `json:"limit_disclosure,omitempty"`
	Fields          []Field `json:"fields,omitempty"`
}

type CredentialQuery

type CredentialQuery struct {
	//ID REQUIRED. A string identifying the Credential in the response and, if provided, the constraints in credential_sets. The value MUST be a non-empty string consisting of alphanumeric, underscore (_), or hyphen (-) characters. Within the Authorization Request, the same id MUST NOT be present more than once.
	ID string `json:"id" yaml:"id" validate:"required"`

	// Format REQUIRED. A string that specifies the format of the requested Credential. Valid Credential Format Identifier values are defined in Appendix B.
	Format string `json:"format" yaml:"format" validate:"required"`

	// Multiple OPTIONAL. A boolean which indicates whether multiple Credentials can be returned for this Credential Query. If omitted, the default value is false.
	Multiple bool `json:"multiple,omitempty" yaml:"multiple,omitempty"`

	// Meta REQUIRED. An object defining additional properties requested by the Verifier that apply to the metadata and validity data of the Credential. The properties of this object are defined per Credential Format. Examples of those are in Appendix B.3.5 and Appendix B.2.3. If empty, no specific constraints are placed on the metadata or validity of the requested Credential.
	Meta MetaQuery `json:"meta" yaml:"meta" validate:"required"`

	// TrustedAuthorities OPTIONAL. A non-empty array of objects as defined in Section 6.1.1 that specifies expected authorities or trust frameworks that certify Issuers, that the Verifier will accept. Every Credential returned by the Wallet SHOULD match at least one of the conditions present in the corresponding trusted_authorities array if present.
	TrustedAuthorities []TrustedAuthority `json:"trusted_authorities,omitempty" yaml:"trusted_authorities,omitempty"`

	// RequireCryptographicHolderBinding OPTIONAL. A boolean which indicates whether the Verifier requires a Cryptographic Holder Binding proof. The default value is true, i.e., a Verifiable Presentation with Cryptographic Holder Binding is required. If set to false, the Verifier accepts a Credential without Cryptographic Holder Binding proof.
	RequireCryptographicHolderBinding bool `json:"require_cryptographic_holder_binding,omitempty" yaml:"require_cryptographic_holder_binding,omitempty"`

	// Claims OPTIONAL. A non-empty array of objects as defined in Section 6.3 that specifies claims in the requested Credential. Verifiers MUST NOT point to the same claim more than once in a single query. Wallets SHOULD ignore such duplicate claim queries.
	Claims []ClaimQuery `json:"claims,omitempty" yaml:"claims,omitempty"`

	// ClaimSet OPTIONAL. A non-empty array containing arrays of identifiers for elements in claims that specifies which combinations of claims for the Credential are requested. The rules for selecting claims to send are defined in Section 6.4.1.
	ClaimSet []string `json:"claim_sets,omitempty" yaml:"claim_sets,omitempty" validate:"omitnil,min=1,dive,required"`
}

CredentialQuery is an object representing a request for a presentation of one or more matching Credentials.

func NewVC20CredentialQuery

func NewVC20CredentialQuery(id string, typeValues [][]string, claims []ClaimQuery) CredentialQuery

NewVC20CredentialQuery creates a new CredentialQuery for W3C VC 2.0 Data Integrity format. typeValues should be an array of type alternatives, where each alternative is an array of fully expanded type IRIs that must all be present in the credential.

type CredentialSetQuery

type CredentialSetQuery struct {
	// Options REQUIRED A non-empty array, where each value in the array is a list of Credential Query identifiers representing one set of Credentials that satisfies the use case. The value of each element in the options array is a non-empty array of identifiers which reference elements in credentials.
	Options [][]string `json:"options" yaml:"options" validate:"required,min=1,dive,required,min=1,dive,required"`

	// Required OPTIONAL A boolean which indicates whether this set of Credentials is required to satisfy the particular use case at the Verifier. If omitted, the default value is true.
	Required bool `json:"required,omitempty" yaml:"required,omitempty"`

	// Purpose Can't find in spec, but in example from wwwallet
	Purpose string `json:"purpose,omitempty" yaml:"purpose,omitempty"`
}

type DCQL

type DCQL struct {
	// Credentials REQUIRED. A non-empty array of Credential Queries as defined in Section 6.1 that specify the requested Credentials.
	Credentials []CredentialQuery `json:"credentials" yaml:"credentials" validate:"required,min=1,dive,required"`

	// CredentialSets OPTIONAL. A non-empty array of Credential Set Queries as defined in Section 6.2 that specifies additional constraints on which of the requested Credentials to return.
	CredentialSets []CredentialSetQuery `json:"credential_sets,omitempty" yaml:"credential_sets,omitempty" validate:"omitnil,min=1,dive,required"`
}

type DCQLValidationError

type DCQLValidationError struct {
	Field   string
	Message string
}

DCQLValidationError represents a validation error in a DCQL query.

func (*DCQLValidationError) Error

func (e *DCQLValidationError) Error() string

type Descriptor

type Descriptor struct {
	ID         string      `json:"id" validate:"required"`
	Path       string      `json:"path" validate:"required"`
	PathNested *Descriptor `json:"path_nested,omitempty"`
	Format     string      `json:"format" validate:"required,oneof=jwt jwt_vc jwt_vp ldp ldp_vc ldp_vp mso_mdoc ac_vc ac_vp sd_jwt"`
}

type DirectPostJWTResponse

type DirectPostJWTResponse struct {
	Response string `json:"response"` // Encrypted JWT containing the Authorization Response
}

DirectPostJWTResponse represents response_mode=direct_post.jwt (Section 8.3.1)

type DirectPostResponse

type DirectPostResponse struct {
	RedirectURI string `json:"redirect_uri,omitempty"` // Optional per spec
}

DirectPostResponse is the response from the direct_post endpoint (Section 8.2)

type EphemeralEncryptionKeyCache

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

EphemeralEncryptionKeyCache manages short-lived encryption keys for response encryption

func NewEphemeralEncryptionKeyCache

func NewEphemeralEncryptionKeyCache(ttl time.Duration) *EphemeralEncryptionKeyCache

NewEphemeralEncryptionKeyCache creates and starts a new ephemeral encryption key cache with the specified TTL. Keys are automatically evicted after the TTL expires.

Example

ExampleNewEphemeralEncryptionKeyCache demonstrates creating an ephemeral encryption key cache

package main

import (
	"fmt"
	"time"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	// Create a cache with 10-minute TTL
	cache := openid4vp.NewEphemeralEncryptionKeyCache(10 * time.Minute)
	defer cache.Stop()

	fmt.Println("Ephemeral encryption key cache created")
	fmt.Printf("Initial cache size: %d\n", cache.Len())

}
Output:
Ephemeral encryption key cache created
Initial cache size: 0

func (*EphemeralEncryptionKeyCache) Delete

func (e *EphemeralEncryptionKeyCache) Delete(kid string)

Delete removes an ephemeral encryption key from the cache

func (*EphemeralEncryptionKeyCache) GenerateAndStore

func (e *EphemeralEncryptionKeyCache) GenerateAndStore(kid string) (privateKey jwk.Key, publicKey jwk.Key, err error)

GenerateAndStore generates a new ephemeral encryption key pair, stores the private key in the cache, and returns both private and public JWKs. The key uses ECDH P-256.

Example

ExampleEphemeralEncryptionKeyCache_GenerateAndStore demonstrates generating and storing a key pair

package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	cache := openid4vp.NewEphemeralEncryptionKeyCache(openid4vp.DefaultEphemeralKeyTTL)
	defer cache.Stop()

	// Generate ECDH P-256 key pair and store private key in cache
	kid := "ephemeral-key-abc123"
	privateKey, publicKey, err := cache.GenerateAndStore(kid)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Private key is automatically stored in cache
	retrievedPrivate, found := cache.Get(kid)
	if found {
		fmt.Printf("Private key stored in cache: %t\n", retrievedPrivate != nil)
	}

	// Get key IDs
	privateKid, _ := privateKey.KeyID()
	publicKid, _ := publicKey.KeyID()
	fmt.Printf("Private key KID: %s\n", privateKid)
	fmt.Printf("Public key KID: %s\n", publicKid)

	// Public key has "use" set to "enc"
	use, _ := publicKey.KeyUsage()
	fmt.Printf("Public key usage: %s\n", use)

}
Output:
Private key stored in cache: true
Private key KID: ephemeral-key-abc123
Public key KID: ephemeral-key-abc123
Public key usage: enc

func (*EphemeralEncryptionKeyCache) Get

Get retrieves an ephemeral encryption key by its key ID (kid)

func (*EphemeralEncryptionKeyCache) Len

Len returns the number of items currently in the cache

func (*EphemeralEncryptionKeyCache) Set

func (e *EphemeralEncryptionKeyCache) Set(kid string, key jwk.Key)

Set stores an ephemeral encryption key with the specified key ID (kid)

func (*EphemeralEncryptionKeyCache) SetWithTTL

func (e *EphemeralEncryptionKeyCache) SetWithTTL(kid string, key jwk.Key, ttl time.Duration)

SetWithTTL stores an ephemeral encryption key with a custom TTL

func (*EphemeralEncryptionKeyCache) Stop

func (e *EphemeralEncryptionKeyCache) Stop()

Stop stops the cache's automatic expiration goroutine

type ErrorResponse

type ErrorResponse struct {
	ErrorCode        string `json:"error"`
	ErrorDescription string `json:"error_description,omitempty"`
	State            string `json:"state,omitempty"`
}

ErrorResponse represents an OAuth 2.0 error response as defined in Section 8.5

func NewErrorResponse

func NewErrorResponse(code, description, state string) *ErrorResponse

NewErrorResponse creates a new ErrorResponse

Example
package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	err := openid4vp.NewErrorResponse(
		openid4vp.ErrorAccessDenied,
		"user denied consent",
		"state-abc123",
	)

	fmt.Println("error:", err.Error())
	fmt.Println("code:", err.ErrorCode)
	fmt.Println("state:", err.State)
}
Output:
error: access_denied: user denied consent
code: access_denied
state: state-abc123

func (*ErrorResponse) Error

func (e *ErrorResponse) Error() string

Error implements the error interface for ErrorResponse

Example
package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	// Error with description
	err1 := openid4vp.NewErrorResponse(openid4vp.ErrorInvalidRequest, "missing nonce", "")
	fmt.Println(err1.Error())

	// Error without description
	err2 := openid4vp.NewErrorResponse(openid4vp.ErrorWalletUnavailable, "", "")
	fmt.Println(err2.Error())
}
Output:
invalid_request: missing nonce
wallet_unavailable

func (*ErrorResponse) IsAuthorizationError

func (e *ErrorResponse) IsAuthorizationError() bool

IsAuthorizationError checks if this is an authorization-related error

Example
package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	authErr := openid4vp.NewErrorResponse(openid4vp.ErrorAccessDenied, "denied", "")
	fmt.Println("access_denied is auth error:", authErr.IsAuthorizationError())

	formatErr := openid4vp.NewErrorResponse(openid4vp.ErrorVPFormatsNotSupported, "unsupported", "")
	fmt.Println("vp_formats_not_supported is auth error:", formatErr.IsAuthorizationError())
}
Output:
access_denied is auth error: true
vp_formats_not_supported is auth error: false

type Field

type Field struct {
	Name   string   `json:"name,omitempty"`
	Path   []string `json:"path"`
	Filter *Filter  `json:"filter,omitempty"`
}

type Filter

type Filter struct {
	Type  string   `json:"type,omitempty"`
	Enum  []string `json:"enum,omitempty"`
	Const string   `json:"const,omitempty"`
}

type Format

type Format struct {
	Alg []string `json:"alg"`
}

type InputDescriptor

type InputDescriptor struct {
	ID          string                         `json:"id"`
	Name        string                         `json:"name,omitempty"`
	Purpose     string                         `json:"purpose,omitempty"`
	Format      map[string]map[string][]string `json:"format,omitempty"`
	Group       []string                       `json:"group,omitempty"`
	Constraints Constraints                    `json:"constraints"`
}
Example
package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	descriptor := openid4vp.InputDescriptor{
		ID:      "identity_credential",
		Name:    "Identity Credential",
		Purpose: "We need to verify your identity",
		Constraints: openid4vp.Constraints{
			LimitDisclosure: "required",
			Fields: []openid4vp.Field{
				{
					Path: []string{"$.vc.credentialSubject.given_name"},
				},
			},
		},
	}

	fmt.Println("id:", descriptor.ID)
	fmt.Println("name:", descriptor.Name)
	fmt.Println("limit_disclosure:", descriptor.Constraints.LimitDisclosure)
	fmt.Println("fields:", len(descriptor.Constraints.Fields))
}
Output:
id: identity_credential
name: Identity Credential
limit_disclosure: required
fields: 1

type JWK

type JWK struct {
	KTY string `json:"kty,omitempty" bson:"kty,omitempty" validate:"required,oneof=RSA EC OKP"`
	X   string `json:"x,omitempty" bson:"x,omitempty" validate:"omitempty"`
	Y   string `json:"y,omitempty" bson:"y,omitempty" validate:"omitempty"`
	CRV string `json:"crv,omitempty" bson:"crv,omitempty" validate:"omitempty,oneof=P-256 P-384 P-521 Ed25519 Ed448 X25519 X448"`
	N   string `json:"n,omitempty" bson:"n,omitempty" validate:"omitempty"`
	KID string `json:"kid,omitempty" bson:"kid,omitempty" validate:"omitempty"`
	E   string `json:"e,omitempty" bson:"e,omitempty" validate:"omitempty"`
	Use string `json:"use,omitempty" bson:"use,omitempty" validate:"omitempty,oneof=sig enc"`
	Alg string `json:"alg,omitempty" bson:"alg,omitempty"`
}

type JWTVCFormat

type JWTVCFormat struct {
	// AlgValues is a non-empty array containing identifiers of cryptographic algorithms supported.
	// If present, the alg JOSE header of the presented VC/VP MUST match one of the array values.
	AlgValues []string `json:"alg_values,omitempty" yaml:"alg_values,omitempty"`
}

JWTVCFormat defines format-specific parameters for JWT-based W3C VC (jwt_vc_json).

type Keys

type Keys struct {
	Keys []jwk.Key `json:"keys,omitempty" bson:"keys,omitempty" validate:"omitempty,dive"`
}

type LDPVCFormat

type LDPVCFormat struct {
	// ProofTypeValues is a non-empty array containing identifiers of proof types supported.
	// If present, the proof type of the presented VC/VP MUST match one of the array values.
	ProofTypeValues []string `` /* 134-byte string literal not displayed */

	// CryptosuiteValues is a non-empty array containing identifiers of crypto suites supported.
	// Used when one of the algorithms in ProofTypeValues supports multiple crypto suites.
	CryptosuiteValues []string `` /* 161-byte string literal not displayed */
}

LDPVCFormat defines format-specific parameters for W3C VC Data Integrity (ldp_vc).

type MetaQuery

type MetaQuery struct {
	// VCTValues for SD-JWT VC format (dc+sd-jwt).
	// A non-empty array of strings that specifies allowed values for the type of the requested Verifiable Credential.
	// All elements in the array MUST be valid type identifiers as defined in [I-D.ietf-oauth-sd-jwt-vc].
	// The Wallet MAY return Credentials that inherit from any of the specified types.
	VCTValues []string `json:"vct_values,omitempty" yaml:"vct_values,omitempty"`

	// TypeValues for W3C VC format (ldp_vc, jwt_vc_json).
	// A non-empty array of string arrays specifying the fully expanded types (IRIs) that the Verifier accepts.
	// Each top-level array specifies one alternative to match the fully expanded type values of the Verifiable Credential.
	// Each inner array specifies a set of fully expanded types that MUST be present in the credential's type property.
	TypeValues [][]string `json:"type_values,omitempty" yaml:"type_values,omitempty"`

	// DoctypeValue for ISO mdoc format (mso_mdoc).
	// String that specifies an allowed value for the doctype of the requested Verifiable Credential.
	DoctypeValue string `json:"doctype_value,omitempty" yaml:"doctype_value,omitempty"`
}

MetaQuery represents format-specific metadata constraints for credential queries. For SD-JWT VC format (dc+sd-jwt): use VCTValues For W3C VC format (ldp_vc): use TypeValues

type MsoMdocFormat

type MsoMdocFormat struct {
	// IssuerAuthAlgValues is a non-empty array containing cryptographic algorithm identifiers
	// supported for IssuerAuth COSE signatures.
	IssuerAuthAlgValues []int `json:"issuerauth_alg_values,omitempty" yaml:"issuerauth_alg_values,omitempty"`

	// DeviceAuthAlgValues is a non-empty array containing cryptographic algorithm identifiers
	// supported for DeviceAuth COSE signatures or MACs.
	DeviceAuthAlgValues []int `json:"deviceauth_alg_values,omitempty" yaml:"deviceauth_alg_values,omitempty"`
}

MsoMdocFormat defines format-specific parameters for ISO mdoc (mso_mdoc).

type PresentationBuilder

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

PresentationBuilder builds OpenID4VP presentation requests from templates

func NewPresentationBuilder

func NewPresentationBuilder[T PresentationRequestTemplate](templates []T) *PresentationBuilder

NewPresentationBuilder creates a new PresentationBuilder with the given templates The templates parameter accepts any slice of types that implement PresentationRequestTemplate

func (*PresentationBuilder) BuildDCQLQuery

func (pb *PresentationBuilder) BuildDCQLQuery(ctx context.Context, scopes []string) (*DCQL, error)

BuildDCQLQuery creates a DCQL query from OIDC scopes. This attempts to find matching templates, and falls back to a generic DCQL query if none are found. All scopes are considered for matching, including standard OIDC scopes like "openid". This allows standard OIDC scopes to optionally map to credentials if configured. Non-standard scopes are prioritized over standard scopes to prevent "openid" from always being selected when it appears first in the request.

func (*PresentationBuilder) BuildFromScopes

func (pb *PresentationBuilder) BuildFromScopes(ctx context.Context, scopes []string) (*DCQL, PresentationRequestTemplate, error)

BuildFromScopes creates a DCQL query from OIDC scopes using configured templates Returns the DCQL query and the template that was used

func (*PresentationBuilder) BuildFromTemplate

func (pb *PresentationBuilder) BuildFromTemplate(ctx context.Context, templateID string) (*DCQL, PresentationRequestTemplate, error)

BuildFromTemplate creates a DCQL query from a specific template ID

func (*PresentationBuilder) FindTemplateByScopes

func (pb *PresentationBuilder) FindTemplateByScopes(scopes []string) PresentationRequestTemplate

FindTemplateByScopes finds a template that matches the given OIDC scopes Returns the first template where all requested scopes are present in the template's scopes

func (*PresentationBuilder) GetTemplate

func (pb *PresentationBuilder) GetTemplate(templateID string) (PresentationRequestTemplate, error)

GetTemplate returns a specific template by ID

func (*PresentationBuilder) ListTemplates

func (pb *PresentationBuilder) ListTemplates() []PresentationRequestTemplate

ListTemplates returns all templates

type PresentationDefinitionParameter

type PresentationDefinitionParameter struct {
	// ID The Presentation Definition MUST contain an id property. The value of this property MUST be a string.
	// The string SHOULD provide a unique ID for the desired context.
	ID string `json:"id" bson:"id" validate:"required"`

	// Name The Presentation Definition MAY contain a name property. If present, its value SHOULD be a
	// human-friendly string intended to constitute a distinctive designation of the Presentation Definition.
	Name string `json:"name,omitempty" bson:"name,omitempty"`

	// Purpose The Presentation Definition MAY contain a purpose property. If present, its value MUST be a
	// string that describes the purpose for which the Presentation Definition's inputs are being used for.
	Purpose string `json:"purpose,omitempty" bson:"purpose,omitempty"`

	// InputDescriptors The Presentation Definition MUST contain an input_descriptors property.
	// Its value MUST be an array of Input Descriptor Objects.
	InputDescriptors []InputDescriptor `json:"input_descriptors" bson:"input_descriptors" validate:"required,dive"`

	// SubmissionRequirements The Presentation Definition MAY contain a submission_requirements property.
	// If present, its value MUST be an array of Submission Requirement Objects.
	SubmissionRequirements []SubmissionRequirement `json:"submission_requirements,omitempty" bson:"submission_requirements,omitempty" validate:"omitempty,dive"`

	// Format The Presentation Definition MAY contain a format property. If present, its value MUST be an object
	// with one or more properties matching the registered Claim Format Designations.
	Format map[string]Format `json:"format,omitempty" bson:"format,omitempty"`
}

type PresentationRequestTemplate

type PresentationRequestTemplate interface {
	GetID() string
	GetOIDCScopes() []string
	GetDCQLQuery() *DCQL
}

PresentationRequestTemplate represents a template for creating presentation requests. This is a minimal interface to avoid import cycles with pkg/configuration.

type PresentationSubmission

type PresentationSubmission struct {
	ID            string       `json:"id" validate:"required"`
	DefinitionID  string       `json:"definition_id" validate:"required"`
	DescriptorMap []Descriptor `json:"descriptor_map" validate:"required,dive,required"`
}
Example
package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	submission := openid4vp.PresentationSubmission{
		ID:           "submission-1",
		DefinitionID: "definition-1",
		DescriptorMap: []openid4vp.Descriptor{
			{
				ID:     "identity_credential",
				Path:   "$",
				Format: "sd_jwt",
			},
		},
	}

	fmt.Println("id:", submission.ID)
	fmt.Println("definition_id:", submission.DefinitionID)
	fmt.Println("descriptors:", len(submission.DescriptorMap))
	fmt.Println("first descriptor format:", submission.DescriptorMap[0].Format)
}
Output:
id: submission-1
definition_id: definition-1
descriptors: 1
first descriptor format: sd_jwt

type QRReply

type QRReply struct {
	Base64Image string `json:"base64_image" bson:"base64_image" validate:"required"`
	URI         string `json:"uri" bson:"uri" validate:"required"`
}

QRReply is a collection of fields representing a QRReply code TODO(masv): not sure if the type should include uri,request_uri,client_id,session_id

func GenerateQR

func GenerateQR(uri *url.URL, recoveryLevel qrcode.RecoveryLevel, size int) (*QRReply, error)

type RequestObject

type RequestObject struct {
	// ISS MAY be present in the Request Object. However, if it is present, the Wallet MUST ignore it.
	ISS string `json:"iss" uri:"iss" validate:"required,url"`

	AUD string `json:"aud,omitempty" bson:"aud,omitempty" validate:"required"`

	IAT int64 `json:"iat,omitempty" bson:"iat,omitempty" validate:"required"`

	// ResponseType        REQUIRED.  Value MUST be set to "code".
	ResponseType string `json:"response_type" uri:"response_type" validate:"required,eq=code"`

	// REQUIRED. Defined in [RFC6749]. This specification defines additional requirements to enable the use of Client Identifier Prefixes as described in Section 5.9. The Client Identifier can be created by parties other than the Wallet and it is considered unique within the context of the Wallet when used in combination with the Client Identifier Prefix.
	ClientID string `json:"client_id" uri:"client_id" validate:"required"`

	//RedirectURI OPTIONAL.  As described in Section 3.1.2.
	RedirectURI string `json:"redirect_uri,omitempty" uri:"redirect_uri" validate:"omitempty,url"`

	// Scope OPTIONAL.  The scope of the access request as described by Section 3.3.
	Scope string `json:"scope,omitempty" uri:"scope" validate:"omitempty"`

	//  State REQUIRED under the conditions defined in Section 5.3. Otherwise, state is OPTIONAL. state values MUST only contain ASCII URL safe characters (uppercase and lowercase letters, decimal digits, hyphen, period, underscore, and tilde).
	State string `json:"state,omitempty" uri:"state" validate:"omitempty"`

	// Nonce REQUIRED. A case-sensitive String representing a value to securely bind Verifiable Presentation(s) provided by the Wallet to the particular transaction. The Verifier MUST create a fresh, cryptographically random number with sufficient entropy for every Authorization Request, store it with its current session, and pass it in the nonce Authorization Request Parameter to the Wallet. See Section 14.1 for details. Values MUST only contain ASCII URL safe characters (uppercase and lowercase letters, decimal digits, hyphen, period, underscore, and tilde).
	Nonce string `json:"nonce" uri:"nonce" validate:"required"`

	// ResponseMode REQUIRED. Defined in [OAuth.Responses]. This parameter can be used (through the new Response Mode direct_post) to ask the Wallet to send the response to the Verifier via an HTTPS connection (see Section 8.2 for more details). It can also be used to request that the resulting response be encrypted (see Section 8.3 for more details).
	ResponseMode string `` /* 126-byte string literal not displayed */

	//dcql_query
	DCQLQuery *DCQL `json:"dcql_query,omitempty" bson:"dcql_query,omitempty" validate:"omitempty,dive"`

	// ClientMetadata OPTIONAL. A JSON object containing the Verifier metadata values. It MUST be UTF-8 encoded.
	ClientMetadata *ClientMetadata `json:"client_metadata,omitempty" validate:"omitempty"`

	//RequestURIMethod: OPTIONAL. A string determining the HTTP method to be used when the request_uri parameter is included in the same request. Two case-sensitive valid values are defined in this specification: get and post. If request_uri_method value is get, the Wallet MUST send the request to retrieve the Request Object using the HTTP GET method, i.e., as defined in [RFC9101]. If request_uri_method value is post, a supporting Wallet MUST send the request using the HTTP POST method as detailed in Section 5.10. If the request_uri_method parameter is not present, the Wallet MUST process the request_uri parameter as defined in [RFC9101]. Wallets not supporting the post method will send a GET request to the Request URI (default behavior as defined in [RFC9101]). request_uri_method parameter MUST NOT be present if a request_uri parameter is not present. If the Verifier set the request_uri_method parameter value to post and there is no other means to convey its capabilities to the Wallet, it SHOULD add the client_metadata parameter to the Authorization Request. This enables the Wallet to assess the Verifier's capabilities, allowing it to transmit only the relevant capabilities through the wallet_metadata parameter in the Request URI POST request.
	RequestURIMethod string `json:"request_uri_method,omitempty" bson:"request_uri_method,omitempty" validate:"omitempty,oneof=get post"`

	// TransactionData OPTIONAL. Non-empty array of strings, where each string is a base64url-encoded JSON object that contains a typed parameter set with details about the transaction that the Verifier is requesting the End-User to authorize. See Section 8.4 for details. The Wallet MUST return an error if a request contains even one unrecognized transaction data type or transaction data not conforming to the respective type definition
	TransactionData []TransactionData `json:"transaction_data,omitempty" bson:"transaction_data,omitempty" validate:"omitempty,dive,required"`

	// VerifierInfo OPTIONAL. A non-empty array of attestations about the Verifier relevant to the Credential Request. These attestations MAY include Verifier metadata, policies, trust status, or authorizations. Attestations are intended to support authorization decisions, inform Wallet policy enforcement, or enrich the End-User consent dialog.
	VerifierInfo []VerifierInfo `json:"verifier_info,omitempty" bson:"verifier_info,omitempty" validate:"omitempty,dive,required"`

	// ResponseURI REQUIRED when the Response Mode direct_post is used. The URL to which the Wallet MUST send the Authorization Response using an HTTP POST request as defined by the Response Mode direct_post. The Response URI receives all Authorization Response parameters as defined by the respective Response Type. When the response_uri parameter is present, the redirect_uri Authorization Request parameter MUST NOT be present. If the redirect_uri Authorization Request parameter is present when the Response Mode is direct_post, the Wallet MUST return an invalid_request Authorization Response error. The response_uri value MUST be a value that the client would be permitted to use as redirect_uri when following the rules defined in Section 5.9.
	ResponseURI string `json:"response_uri,omitempty" uri:"response_uri" validate:"required"`
}

RequestObject is sent by authorization request

func (*RequestObject) CreateAuthorizationRequestURI

func (r *RequestObject) CreateAuthorizationRequestURI(ctx context.Context, verifierHost, id string) (string, error)

func (*RequestObject) Sign

func (r *RequestObject) Sign(ctx context.Context, signer pki.Signer, x5c []string) (string, error)

Sign creates a signed JWT representation of the RequestObject according to OpenID4VP specification. The JWT typ header is set to "oauth-authz-req+jwt" as required by OpenID4VP Section 5.2. Uses pki.Signer interface which supports both software keys and HSM.

Parameters:

  • ctx: Context for signing operations
  • signer: The pki.Signer for signing (supports both software and HSM keys)
  • x5c: Optional X.509 certificate chain for key verification

Returns the signed JWT string or an error if signing fails.

type RequestObjectCache

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

RequestObjectCache manages short-lived request objects for authorization requests

func NewRequestObjectCache

func NewRequestObjectCache(ttl time.Duration) *RequestObjectCache

NewRequestObjectCache creates and starts a new request object cache with the specified TTL. Request objects are automatically evicted after the TTL expires.

Example

ExampleNewRequestObjectCache demonstrates creating a request object cache

package main

import (
	"fmt"
	"time"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	// Create a cache with 10-minute TTL
	cache := openid4vp.NewRequestObjectCache(10 * time.Minute)
	defer cache.Stop()

	// Store a request object
	requestURI := "urn:ietf:params:oauth:request_uri:6eSG8FrjKQb1Qiwj"
	requestObject := &openid4vp.RequestObject{
		ResponseType: "vp_token",
		ClientID:     "https://verifier.example.com",
		Nonce:        "n-0S6_WzA2Mj",
	}
	cache.Set(requestURI, requestObject)

	// Retrieve the request object
	retrieved, found := cache.Get(requestURI)
	if found {
		fmt.Printf("Found request object for client: %s\n", retrieved.ClientID)
	}

}
Output:
Found request object for client: https://verifier.example.com

func (*RequestObjectCache) Delete

func (r *RequestObjectCache) Delete(requestURI string)

Delete removes a request object from the cache

func (*RequestObjectCache) Get

func (r *RequestObjectCache) Get(requestURI string) (*RequestObject, bool)

Get retrieves a request object by its request URI

func (*RequestObjectCache) Len

func (r *RequestObjectCache) Len() int

Len returns the number of items currently in the cache

func (*RequestObjectCache) Set

func (r *RequestObjectCache) Set(requestURI string, requestObject *RequestObject)

Set stores a request object with the specified request URI

func (*RequestObjectCache) SetWithTTL

func (r *RequestObjectCache) SetWithTTL(requestURI string, requestObject *RequestObject, ttl time.Duration)

SetWithTTL stores a request object with a custom TTL

Example

ExampleRequestObjectCache_SetWithTTL demonstrates setting a request object with custom TTL

package main

import (
	"fmt"
	"time"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	cache := openid4vp.NewRequestObjectCache(openid4vp.DefaultRequestObjectTTL)
	defer cache.Stop()

	// Store a request object with a custom short TTL (e.g., for one-time use)
	requestURI := "urn:ietf:params:oauth:request_uri:short-lived"
	requestObject := &openid4vp.RequestObject{
		ResponseType: "vp_token",
		ClientID:     "https://verifier.example.com",
		Nonce:        "n-abc123",
	}

	// Set with 5-minute TTL instead of default 10 minutes
	cache.SetWithTTL(requestURI, requestObject, 5*time.Minute)

	fmt.Printf("Stored request object with 5-minute TTL\n")

}
Output:
Stored request object with 5-minute TTL

func (*RequestObjectCache) Stop

func (r *RequestObjectCache) Stop()

Stop stops the cache's automatic expiration goroutine

type ResponseParameters

type ResponseParameters struct {
	//VPToken REQUIRED. The structure of this parameter depends on the query language used to request the presentations in the Authorization Request:
	VPToken string `json:"vp_token,omitempty" bson:"vp_token" validate:"omitempty,required_if=ResponseType vp_token"`

	Code    string `json:"code,omitempty" bson:"code"`
	ISS     string `json:"iss,omitempty" bson:"iss"`
	State   string `json:"state,omitempty" bson:"state"`
	IDToken string `json:"id_token,omitempty" bson:"id_token"`

	PresentationSubmission *PresentationSubmission `json:"presentation_submission,omitempty" bson:"presentation_submission"`
}

func ResponseParametersFromJSON

func ResponseParametersFromJSON(data []byte) (*ResponseParameters, error)

ResponseParametersFromJSON deserializes the response parameters from JSON

func (*ResponseParameters) BuildCredential

func (r *ResponseParameters) BuildCredential() (map[string]any, error)

BuildCredential unwraps the VPToken from the ResponseParameters

func (*ResponseParameters) ToJSON

func (r *ResponseParameters) ToJSON() ([]byte, error)

ToJSON serializes the response parameters to JSON

func (*ResponseParameters) Validate

func (r *ResponseParameters) Validate() error

Validate validates the response parameters according to OpenID4VP spec Section 8.1

type SDJWTVCFormat

type SDJWTVCFormat struct {
	// SDJWTAlgValues is a non-empty array containing cryptographic algorithm identifiers
	// supported for the Issuer-signed JWT of an SD-JWT.
	SDJWTAlgValues []string `json:"sd-jwt_alg_values,omitempty" yaml:"sd-jwt_alg_values,omitempty"`

	// KBJWTAlgValues is a non-empty array containing cryptographic algorithm identifiers
	// supported for a Key Binding JWT (KB-JWT).
	KBJWTAlgValues []string `json:"kb-jwt_alg_values,omitempty" yaml:"kb-jwt_alg_values,omitempty"`
}

SDJWTVCFormat defines format-specific parameters for IETF SD-JWT VC (dc+sd-jwt).

type StaticVC20KeyResolver

type StaticVC20KeyResolver struct {
	Key crypto.PublicKey
}

StaticVC20KeyResolver is a simple key resolver that returns a fixed key.

func (*StaticVC20KeyResolver) ResolveKey

func (r *StaticVC20KeyResolver) ResolveKey(ctx context.Context, verificationMethod string) (crypto.PublicKey, error)

ResolveKey returns the static key regardless of verification method.

type SubmissionRequirement

type SubmissionRequirement struct {
	Name  string `json:"name,omitempty"`
	Rule  string `json:"rule"`
	Count int    `json:"count,omitempty"`
	From  string `json:"from"`
}

type TransactionData

type TransactionData struct {
	// Type REQUIRED. String that identifies the type of transaction data. This value determines parameters that can be included in the transaction_data object. The specific values are out of scope for this specification. It is RECOMMENDED to use collision-resistant names for type values.
	Type string `json:"type,omitempty" bson:"type,omitempty" validate:"required"`

	// CredentialIDS REQUIRED. Non-empty array of strings each referencing a Credential requested by the Verifier that can be used to authorize this transaction. The string matches the id field in the DCQL Credential Query. If there is more than one element in the array, the Wallet MUST use only one of the referenced Credentials for transaction authorization.
	CredentialIDS []string `json:"credential_ids,omitempty" bson:"credential_ids,omitempty" validate:"required,dive,required"`
}

func (*TransactionData) Base64Encode

func (t *TransactionData) Base64Encode() (string, error)

Base64Encode encodes the TransactionData struct into a base64 URL-encoded string.

type TrustService

type TrustService struct {
}

func (*TrustService) ExtractPublicKeyFromX5C

func (ts *TrustService) ExtractPublicKeyFromX5C(x5cBase64 string, ext ...*cryptoutil.Extensions) (any, error)

type TrustedAuthority

type TrustedAuthority struct {
	// REQUIRED. A string uniquely identifying the type of information about the issuer trust framework. Types defined by this specification are listed below.
	Type string `json:"type" yaml:"type" validate:"required,oneof=aki etsi_tl openid_federation"`

	// REQUIRED. A non-empty array of strings, where each string (value) contains information specific to the used Trusted Authorities Query type that allows the identification of an issuer, a trust framework, or a federation that an issuer belongs to.
	Values []string `json:"values" yaml:"values" validate:"required,min=1"`
}

func NewTrustedAuthorityAKI

func NewTrustedAuthorityAKI(akiValues ...string) TrustedAuthority

NewTrustedAuthorityAKI creates a TrustedAuthority for Authority Key Identifier matching.

func NewTrustedAuthorityETSI

func NewTrustedAuthorityETSI(tlURLs ...string) TrustedAuthority

NewTrustedAuthorityETSI creates a TrustedAuthority for ETSI Trusted List matching.

func NewTrustedAuthorityOpenIDFederation

func NewTrustedAuthorityOpenIDFederation(trustAnchors ...string) TrustedAuthority

NewTrustedAuthorityOpenIDFederation creates a TrustedAuthority for OpenID Federation matching.

type TrustedAuthorityMatcher

type TrustedAuthorityMatcher interface {
	// MatchAKI checks if a credential's certificate chain contains a certificate
	// with the given Authority Key Identifier (base64url-encoded).
	MatchAKI(credentialCertChain [][]byte, aki string) bool

	// MatchETSI checks if a credential's issuer is present in the ETSI Trusted List.
	// The tlURL is the URL of the Trusted List or List of Trusted Lists.
	MatchETSI(credentialCertChain [][]byte, tlURL string) bool

	// MatchOpenIDFederation checks if a credential's issuer is part of an OpenID Federation
	// with the given Trust Anchor entity identifier.
	MatchOpenIDFederation(issuer string, trustAnchorEntityID string) bool
}

TrustedAuthorityMatcher provides methods for matching credentials against trusted authorities. Implementations should integrate with trust frameworks (go-trust, ETSI TL, etc.).

type VC20CreateRequest

type VC20CreateRequest struct {
	// CredentialID is the unique ID for the credential (optional, generated if empty)
	CredentialID string
	// Types are the credential types (e.g., ["VerifiableCredential", "UniversityDegreeCredential"])
	Types []string
	// Subject is the credential subject (the entity the credential is about)
	Subject map[string]any
	// AdditionalContexts are extra JSON-LD contexts to include
	AdditionalContexts []string
	// ValidFrom is when the credential becomes valid (defaults to now)
	ValidFrom time.Time
	// ValidUntil is when the credential expires (optional)
	ValidUntil *time.Time
	// CredentialStatus for revocation (optional)
	CredentialStatus map[string]any
}

VC20CreateRequest contains parameters for creating a credential.

type VC20CreateResult

type VC20CreateResult struct {
	// CredentialJSON is the signed credential as JSON bytes
	CredentialJSON []byte
	// CredentialID is the credential's unique ID
	CredentialID string
	// Issuer is the issuer DID
	Issuer string
	// ValidFrom is when the credential is valid from
	ValidFrom time.Time
	// ValidUntil is when the credential expires (nil if no expiration)
	ValidUntil *time.Time
}

VC20CreateResult contains the signed credential and metadata.

type VC20Handler

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

VC20Handler handles W3C VC 2.0 Data Integrity credentials in OpenID4VP flows.

func NewVC20Handler

func NewVC20Handler(opts ...VC20HandlerOption) (*VC20Handler, error)

NewVC20Handler creates a new W3C VC 2.0 handler for OpenID4VP.

func (*VC20Handler) CreateCredential

func (h *VC20Handler) CreateCredential(ctx context.Context, req *VC20CreateRequest) (*VC20CreateResult, error)

CreateCredential creates and signs a new W3C VC 2.0 Data Integrity credential.

func (*VC20Handler) VerifyAndExtract

func (h *VC20Handler) VerifyAndExtract(ctx context.Context, vpToken string) (*VC20VerificationResult, error)

VerifyAndExtract verifies a W3C VC VP token and extracts claims.

type VC20HandlerOption

type VC20HandlerOption func(*VC20Handler)

VC20HandlerOption configures a VC20Handler.

func WithVC20AllowedSkew

func WithVC20AllowedSkew(skew time.Duration) VC20HandlerOption

WithVC20AllowedSkew sets the allowed clock skew for time validation.

func WithVC20Clock

func WithVC20Clock(clock func() time.Time) VC20HandlerOption

WithVC20Clock sets the clock function for time validation.

func WithVC20KeyResolver

func WithVC20KeyResolver(resolver VC20KeyResolver) VC20HandlerOption

WithVC20KeyResolver sets the key resolver for VC20 verification.

func WithVC20RevocationCheck

func WithVC20RevocationCheck(check bool) VC20HandlerOption

WithVC20RevocationCheck enables credential status checking.

func WithVC20SignerConfig

func WithVC20SignerConfig(config *VC20SignerConfig) VC20HandlerOption

WithVC20SignerConfig sets the signing configuration for credential issuance.

func WithVC20StaticKey

func WithVC20StaticKey(key crypto.PublicKey) VC20HandlerOption

WithVC20StaticKey sets a static public key for VC20 verification.

func WithVC20TrustedIssuers

func WithVC20TrustedIssuers(issuers []string) VC20HandlerOption

WithVC20TrustedIssuers sets the list of trusted issuers.

type VC20KeyResolver

type VC20KeyResolver interface {
	// ResolveKey resolves a verification method to a public key.
	// verificationMethod can be:
	//   - Full DID URL: "did:key:z6Mk...#key-1"
	//   - DID with fragment: "did:web:example.com#keys-1"
	//   - HTTP URL: "https://example.com/keys/1"
	// Returns crypto.PublicKey which can be *ecdsa.PublicKey or ed25519.PublicKey
	ResolveKey(ctx context.Context, verificationMethod string) (crypto.PublicKey, error)
}

VC20KeyResolver resolves verification method URIs to public keys. Implementations can resolve DIDs (did:key, did:web, did:jwk, etc.), fetch JWKS, or use go-trust for policy-based resolution.

type VC20SignerConfig

type VC20SignerConfig struct {
	// PrivateKey is the signing key (ecdsa.PrivateKey or ed25519.PrivateKey)
	PrivateKey crypto.PrivateKey
	// IssuerID is the DID or URI of the issuer (e.g., "did:web:example.com")
	IssuerID string
	// VerificationMethod is the full verification method URI (e.g., "did:web:example.com#key-1")
	VerificationMethod string
	// Cryptosuite specifies which suite to use: ecdsa-rdfc-2019, ecdsa-sd-2023, eddsa-rdfc-2022
	Cryptosuite string
}

VC20SignerConfig holds issuer signing configuration.

type VC20VerificationResult

type VC20VerificationResult struct {
	// Credential metadata
	ID             string     `json:"id,omitempty"`
	Issuer         string     `json:"issuer"`
	Subject        string     `json:"subject,omitempty"`
	Types          []string   `json:"type"`
	IssuanceDate   time.Time  `json:"validFrom"`
	ExpirationDate *time.Time `json:"validUntil,omitempty"`

	// Credential content
	CredentialSubject map[string]any `json:"credentialSubject"`

	// Proof metadata
	ProofType          string    `json:"proofType"`
	Cryptosuite        string    `json:"cryptosuite"`
	VerificationMethod string    `json:"verificationMethod"`
	ProofPurpose       string    `json:"proofPurpose"`
	ProofCreated       time.Time `json:"proofCreated"`

	// Selective disclosure info (for ecdsa-sd-2023)
	IsSelectiveDisclosure bool     `json:"isSelectiveDisclosure"`
	DisclosedPaths        []string `json:"disclosedPaths,omitempty"`

	// All claims as map for generic access
	Claims map[string]any `json:"claims"`

	// Raw credential JSON
	RawCredential json.RawMessage `json:"rawCredential"`
}

VC20VerificationResult contains the result of W3C VC verification.

func (*VC20VerificationResult) GetClaims

func (r *VC20VerificationResult) GetClaims() map[string]any

GetClaims returns all claims from the credential.

func (*VC20VerificationResult) GetCredentialSubject

func (r *VC20VerificationResult) GetCredentialSubject() map[string]any

GetCredentialSubject returns the credential subject claims.

type VPBuildOptions

type VPBuildOptions struct {
	// HolderDID is the DID of the presentation holder
	HolderDID string

	// VerificationMethod is the full verification method URI (e.g., did:key:z6Mk...#key-1)
	VerificationMethod string

	// Nonce is the challenge/nonce from the verifier (required for OpenID4VP)
	Nonce string

	// Domain is the audience/domain for the presentation
	Domain string

	// Cryptosuite specifies which cryptosuite to use for signing
	// Supported: "ecdsa-rdfc-2019", "eddsa-rdfc-2022"
	Cryptosuite string

	// Created timestamp for the proof (defaults to now)
	Created time.Time
}

VPBuildOptions contains options for building a Verifiable Presentation.

type VPBuilder

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

VPBuilder builds W3C Verifiable Presentations for OpenID4VP flows.

func NewVPBuilder

func NewVPBuilder(opts ...VPBuilderOption) *VPBuilder

NewVPBuilder creates a new VPBuilder with the given options.

func (*VPBuilder) BuildVC20Presentation

func (b *VPBuilder) BuildVC20Presentation(
	credentials [][]byte,
	privateKey crypto.PrivateKey,
	opts *VPBuildOptions,
) ([]byte, error)

BuildVC20Presentation creates a signed W3C Verifiable Presentation. The credentials parameter should be JSON bytes of W3C VC 2.0 credentials. The privateKey should match the cryptosuite (ed25519.PrivateKey for EdDSA, *ecdsa.PrivateKey for ECDSA).

type VPBuilderOption

type VPBuilderOption func(*VPBuilder)

VPBuilderOption configures a VPBuilder.

func WithDefaultCryptosuite

func WithDefaultCryptosuite(suite string) VPBuilderOption

WithDefaultCryptosuite sets the default cryptosuite for VP signing.

func WithHolderDID

func WithHolderDID(did string) VPBuilderOption

WithHolderDID sets the holder DID for presentations.

type VPFormatsSupported

type VPFormatsSupported struct {
	// LDPVC is the configuration for W3C VC Data Integrity format (ldp_vc)
	LDPVC *LDPVCFormat `json:"ldp_vc,omitempty" yaml:"ldp_vc,omitempty"`

	// JWTVCJson is the configuration for JWT-based W3C VC format (jwt_vc_json)
	JWTVCJson *JWTVCFormat `json:"jwt_vc_json,omitempty" yaml:"jwt_vc_json,omitempty"`

	// SDJWT is the configuration for SD-JWT VC format (dc+sd-jwt)
	SDJWT *SDJWTVCFormat `json:"dc+sd-jwt,omitempty" yaml:"dc+sd-jwt,omitempty"`

	// MsoMdoc is the configuration for ISO mdoc format (mso_mdoc)
	MsoMdoc *MsoMdocFormat `json:"mso_mdoc,omitempty" yaml:"mso_mdoc,omitempty"`
}

VPFormatsSupported defines format-specific parameters for Verifier or Wallet metadata. Used in client_metadata and Wallet metadata to indicate supported formats and algorithms.

func NewVC20VPFormatsSupported

func NewVC20VPFormatsSupported(cryptosuites []string) VPFormatsSupported

NewVC20VPFormatsSupported creates a VPFormatsSupported configuration for W3C VC 2.0 Data Integrity format with the specified cryptosuites.

type VPResponse

type VPResponse struct {
	VPToken map[string][]string `json:"vp_token,omitempty" bson:"vp_token" validate:"required"`
	State   string              `json:"state,omitempty" bson:"state" validate:"required"`
}

type VPTokenValidator

type VPTokenValidator struct {
	// Nonce from the Authorization Request
	Nonce string

	// ClientID (or Origin for DC API)
	ClientID string

	// ValidateFormat enables SD-JWT format validation (not cryptographic verification).
	// This only validates that the JWT structure is parseable — it does NOT verify
	// the actual cryptographic signature. Use TrustEvaluator for signature verification.
	ValidateFormat bool

	// CheckRevocation enables revocation status checks
	CheckRevocation bool

	// DCQLQuery is the original DCQL query from the request
	DCQLQuery *DCQL
}

VPTokenValidator validates VP Token according to Section 8.6 NOTE: This validator performs format and protocol validation only. Cryptographic signature verification must be done separately by the caller (e.g. via jwt.Parse with a resolved key). TrustEvaluator handles trust/policy evaluation — it does NOT perform signature verification itself.

func (*VPTokenValidator) Validate

func (v *VPTokenValidator) Validate(vpToken string) error

Validate validates the VP Token according to OpenID4VP spec Section 8.6

type VerificationFailedError

type VerificationFailedError struct {
	Step string
	Err  error
}
Example
package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	err := &openid4vp.VerificationFailedError{
		Step: "signature_verification",
		Err:  fmt.Errorf("invalid signature"),
	}

	fmt.Println(err.Error())
}
Output:
verification failed on 'signature_verification': invalid signature

func (*VerificationFailedError) Error

func (e *VerificationFailedError) Error() string

func (*VerificationFailedError) Unwrap

func (e *VerificationFailedError) Unwrap() error

type VerificationRejectedError

type VerificationRejectedError struct {
	Step   string
	Reason string
}
Example
package main

import (
	"fmt"

	"github.com/SUNET/vc/pkg/openid4vp"
)

func main() {
	err := &openid4vp.VerificationRejectedError{
		Step:   "trust_evaluation",
		Reason: "issuer not trusted",
	}

	fmt.Println(err.Error())
}
Output:
verification rejected on 'trust_evaluation': issuer not trusted

func (*VerificationRejectedError) Error

func (e *VerificationRejectedError) Error() string

type VerifierInfo

type VerifierInfo struct {
	//	Format: REQUIRED. A string that identifies the format of the attestation and how it is encoded. Ecosystems SHOULD use collision-resistant identifiers. Further processing of the attestation is determined by the type of the attestation, which is specified in a format-specific way.
	Format string `json:"format" bson:"format" validate:"required"`

	// Data: REQUIRED. An object or string containing an attestation (e.g. a JWT). The payload structure is defined on a per format level. It is at the discretion of the Wallet whether it uses the information from verifier_info. Factors that influence such Wallet's decision include, but are not limited to, trust framework the Wallet supports, specific policies defined by the Issuers or ecosystem, and profiles of this specification. If the Wallet uses information from verifier_info, the Wallet MUST validate the signature and ensure binding.
	Data string `json:"data" bson:"data" validate:"required"`

	// credential_ids: OPTIONAL. A non-empty array of strings each referencing a Credential requested by the Verifier for which the attestation is relevant. Each string matches the id field in a DCQL Credential Query. If omitted, the attestation is relevant to all requested Credentials.
	CredentialIDS []string `json:"credential_ids,omitempty" bson:"credential_ids,omitempty" validate:"omitempty,dive,required"`
}

Jump to

Keyboard shortcuts

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