sdjwt

package
v1.3.6 Latest Latest
Warning

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

Go to latest
Published: Jan 29, 2026 License: Apache-2.0 Imports: 0 Imported by: 0

Documentation

Overview

Package sdjwt implements creating JSON Web Token (JWT) documents that support selective disclosure of JWT claims.

In an SD-JWT, claims can be hidden, but cryptographically protected against undetected modification.

When issuing the SD-JWT to the Holder, the Issuer also sends the cleartext counterparts of all hidden claims, the so-called Disclosures, separate from the SD-JWT itself.

The Holder decides which claims to disclose to a Verifier and forwards the respective Disclosures together with the SD-JWT to the Verifier.

The Verifier has to verify that all disclosed claim values were part of the original, Issuer-signed SD-JWT. The Verifier will not, however, learn any claim values not disclosed in the Disclosures.

This implementation supports:

- selectively disclosable claims in flat data structures as well as more complex, nested data structures

- combining selectively disclosable claims with clear-text claims that are always disclosed

- options for specifying registered claim names that will be included in plaintext (e.g. iss, exp, or nbf)

- option for configuring clear-text claims

For selectively disclosable claims, claim names are always blinded.

This implementation also supports an optional mechanism for Holder Binding, the concept of binding an SD-JWT to key material controlled by the Holder. The strength of the Holder Binding is conditional upon the trust in the protection of the private key of the key pair an SD-JWT is bound to.

Example (ComplexClaimsWithHolderBinding)
{ //nolint:govet
	signer, signatureVerifier, err := setUp()
	if err != nil {
		fmt.Println("failed to set-up test: %w", err.Error())
	}

	holderSigner, holderJWK, err := setUpHolderBinding()
	if err != nil {
		fmt.Println("failed to set-up test: %w", err.Error())
	}

	claims := map[string]interface{}{
		"sub":          "john_doe_42",
		"given_name":   "John",
		"family_name":  "Doe",
		"email":        "johndoe@example.com",
		"phone_number": "+1-202-555-0101",
		"birthdate":    "1940-01-01",
		"address": map[string]interface{}{
			"street_address": "123 Main St",
			"locality":       "Anytown",
			"region":         "Anystate",
			"country":        "US",
		},
	}

	// Issuer will issue SD-JWT for specified claims. Structured claims not selected as an option hence complex object
	// address will be treated as an object not as a set of properties. Holder public key is provided therefore it will
	// be added as "cnf" claim.
	token, err := issuer.New(testIssuer, claims, nil, signer,
		issuer.WithHolderPublicKey(holderJWK),
	)
	if err != nil {
		fmt.Println("failed to issue SD-JWT: %w", err.Error())
	}

	combinedFormatForIssuance, err := token.Serialize(false)
	if err != nil {
		fmt.Println("failed to issue SD-JWT: %w", err.Error())
	}

	// Holder will parse combined format for issuance and hold on to that
	// combined format for issuance and the claims that can be selected.
	holderClaims, err := holder.Parse(combinedFormatForIssuance, holder.WithSignatureVerifier(signatureVerifier))
	if err != nil {
		fmt.Println("holder failed to parse SD-JWT: %w", err.Error())
	}

	// The Holder will only select given_name, address
	selectedDisclosures := getDisclosuresFromClaimNames([]string{"given_name", "address"}, holderClaims)

	// Holder will disclose only sub-set of claims to verifier.
	combinedFormatForPresentation, err := holder.CreatePresentation(combinedFormatForIssuance, selectedDisclosures,
		holder.WithHolderVerification(&holder.BindingInfo{
			Payload: holder.BindingPayload{
				Nonce:    "nonce",
				Audience: "https://test.com/verifier",
				IssuedAt: jwt.NewNumericDate(time.Now()),
			},
			Signer: holderSigner,
		}))
	if err != nil {
		fmt.Println("holder failed to create presentation: %w", err.Error())
	}

	// Verifier will validate combined format for presentation and create verified claims.
	verifiedClaims, err := verifier.Parse(combinedFormatForPresentation,
		verifier.WithSignatureVerifier(signatureVerifier))
	if err != nil {
		fmt.Println("verifier failed to parse holder presentation: %w", err.Error())
	}

	addressClaimsJSON, err := marshalObj(verifiedClaims["address"])
	if err != nil {
		fmt.Println("verifier failed to marshal verified claims: %w", err.Error())
	}

	fmt.Println(addressClaimsJSON)

	
Output:

{
	"country": "US",
	"locality": "Anytown",
	"region": "Anystate",
	"street_address": "123 Main St"
}
Example (ComplexObjectWithStructuredClaims)
{ //nolint:govet
	signer, signatureVerifier, err := setUp()
	if err != nil {
		fmt.Println("failed to set-up test: %w", err.Error())
	}

	claims := map[string]interface{}{
		"sub":          "john_doe_42",
		"given_name":   "John",
		"family_name":  "Doe",
		"email":        "johndoe@example.com",
		"phone_number": "+1-202-555-0101",
		"birthdate":    "1940-01-01",
		"address": map[string]interface{}{
			"street_address": "123 Main St",
			"locality":       "Anytown",
			"region":         "Anystate",
			"country":        "US",
		},
	}

	// Issuer will issue SD-JWT for specified claims.
	token, err := issuer.New(testIssuer, claims, nil, signer,
		issuer.WithStructuredClaims(true),
		issuer.WithNonSelectivelyDisclosableClaims([]string{"address.country"}),
	)
	if err != nil {
		fmt.Println("failed to issue SD-JWT: %w", err.Error())
	}

	combinedFormatForIssuance, err := token.Serialize(false)
	if err != nil {
		fmt.Println("failed to issue SD-JWT: %w", err.Error())
	}

	// Holder will parse combined format for issuance and hold on to that
	// combined format for issuance and the claims that can be selected.
	holderClaims, err := holder.Parse(combinedFormatForIssuance, holder.WithSignatureVerifier(signatureVerifier))
	if err != nil {
		fmt.Println("holder failed to parse SD-JWT: %w", err.Error())
	}

	// The Holder will only select given_name, street_address
	selectedDisclosures := getDisclosuresFromClaimNames([]string{"given_name", "street_address"}, holderClaims)

	// Holder will disclose only sub-set of claims to verifier.
	combinedFormatForPresentation, err := holder.CreatePresentation(combinedFormatForIssuance, selectedDisclosures)
	if err != nil {
		fmt.Println("holder failed to create presentation: %w", err.Error())
	}

	// Verifier will validate combined format for presentation and create verified claims.
	verifiedClaims, err := verifier.Parse(combinedFormatForPresentation,
		verifier.WithSignatureVerifier(signatureVerifier))
	if err != nil {
		fmt.Println("verifier failed to parse holder presentation: %w", err.Error())
	}

	verifiedClaimsJSON, err := marshalObj(verifiedClaims)
	if err != nil {
		fmt.Println("verifier failed to marshal verified claims: %w", err.Error())
	}

	fmt.Println(verifiedClaimsJSON)

	
Output:

{
	"address": {
		"country": "US",
		"street_address": "123 Main St"
	},
	"given_name": "John",
	"iss": "https://example.com/issuer"
}
Example (SimpleClaims)
{ //nolint:govet
	signer, signatureVerifier, err := setUp()
	if err != nil {
		fmt.Println("failed to set-up test: %w", err.Error())
	}

	claims := map[string]interface{}{
		"given_name": "Albert",
		"last_name":  "Smith",
	}

	// Issuer will issue SD-JWT for specified claims.
	token, err := issuer.New(testIssuer, claims, nil, signer)
	if err != nil {
		fmt.Println("failed to issue SD-JWT: %w", err.Error())
	}

	combinedFormatForIssuance, err := token.Serialize(false)
	if err != nil {
		fmt.Println("failed to issue SD-JWT: %w", err.Error())
	}

	// Holder will parse combined format for issuance and hold on to that
	// combined format for issuance and the claims that can be selected.
	holderClaims, err := holder.Parse(combinedFormatForIssuance, holder.WithSignatureVerifier(signatureVerifier))
	if err != nil {
		fmt.Println("holder failed to parse SD-JWT: %w", err.Error())
	}

	// The Holder will only select given_name
	selectedDisclosures := getDisclosuresFromClaimNames([]string{"given_name"}, holderClaims)

	// Holder will disclose only sub-set of claims to verifier.
	combinedFormatForPresentation, err := holder.CreatePresentation(combinedFormatForIssuance, selectedDisclosures)
	if err != nil {
		fmt.Println("holder failed to create presentation: %w", err.Error())
	}

	// Verifier will validate combined format for presentation and create verified claims.
	verifiedClaims, err := verifier.Parse(combinedFormatForPresentation,
		verifier.WithSignatureVerifier(signatureVerifier))
	if err != nil {
		fmt.Println("verifier failed to parse holder presentation: %w", err.Error())
	}

	verifiedClaimsJSON, err := marshalObj(verifiedClaims)
	if err != nil {
		fmt.Println("verifier failed to marshal verified claims: %w", err.Error())
	}

	fmt.Println(verifiedClaimsJSON)

	
Output:

{
	"given_name": "Albert",
	"iss": "https://example.com/issuer"
}

Directories

Path Synopsis
Package holder enables the Holder: an entity that receives SD-JWTs from the Issuer and has control over them.
Package holder enables the Holder: an entity that receives SD-JWTs from the Issuer and has control over them.
Package issuer enables the Issuer: An entity that creates SD-JWTs.
Package issuer enables the Issuer: An entity that creates SD-JWTs.
Package verifier enables the Verifier: An entity that requests, checks and extracts the claims from an SD-JWT and respective Disclosures.
Package verifier enables the Verifier: An entity that requests, checks and extracts the claims from an SD-JWT and respective Disclosures.

Jump to

Keyboard shortcuts

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