| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
SDJWT.Issuer
Description
Convenience module for SD-JWT issuers.
This module provides everything needed to create and issue SD-JWTs. It exports a focused API for the issuer role, excluding modules that issuers don't need (like Presentation and Verification).
Security Warning: EC Signing Timing Attack
⚠️ When using Elliptic Curve (EC) keys (ES256 algorithm), be aware that the
underlying jose library's EC signing implementation may be vulnerable to
timing attacks. This affects signing only, not verification.
For applications where timing attacks are a concern, consider using RSA-PSS (PS256) or Ed25519 (EdDSA) keys instead, which do not have this limitation.
Note: RS256 (RSA-PKCS#1 v1.5) is deprecated per draft-ietf-jose-deprecate-none-rsa15 due to padding oracle attack vulnerabilities. PS256 (RSA-PSS) is the recommended RSA algorithm and is used by default for RSA keys.
Usage
For issuers, import this module:
import SDJWT.Issuer
This gives you access to:
- Core data types (HashAlgorithm, SDJWT, SDJWTPayload, etc.)
- Serialization functions (
serializeSDJWT,deserializeSDJWT) - Issuance functions (
createSDJWT,createSDJWTWithDecoys) - Helper functions (
addHolderKeyToClaims)
Creating SD-JWTs
The main function for creating SD-JWTs is createSDJWT:
-- Create SD-JWT without typ or kid headers result <- createSDJWT Nothing Nothing SHA256 issuerKey ["given_name", "family_name"] claims -- Create SD-JWT with typ header (recommended) result <- createSDJWT (Just "sd-jwt") Nothing SHA256 issuerKey ["given_name", "family_name"] claims -- Create SD-JWT with typ and kid headers result <- createSDJWT (Just "sd-jwt") (Just "key-1") SHA256 issuerKey ["given_name"] claims -- Create SD-JWT with array elements (using JSON Pointer syntax) result <- createSDJWT Nothing Nothing SHA256 issuerKey ["nationalities/0", "nationalities/2"] claims -- Create SD-JWT with mixed object and array paths result <- createSDJWT Nothing Nothing SHA256 issuerKey ["address/street_address", "nationalities/1"] claims
Standard JWT Claims
Standard JWT claims (RFC 7519) can be included in the claims map and will be preserved
in the issuer-signed JWT payload. During verification, standard claims like exp (expiration time)
and nbf (not before) are automatically validated if present. See RFC 9901 Section 4.1.
-- Create SD-JWT with expiration time let expirationTime = currentTime + 3600 -- 1 hour from now let claimsWithExp = Map.insert "exp" (Aeson.Number (fromIntegral expirationTime)) claims result <- createSDJWT (Just "sd-jwt") Nothing SHA256 issuerKey ["given_name"] claimsWithExp
JWT Headers
Both typ and kid headers are supported natively through jose's API:
typ: Recommended by RFC 9901 Section 9.11 for explicit typing (e.g., "sd-jwt")kid: Key ID for key management (useful when rotating keys)
Decoy Digests
To add decoy digests (to obscure the number of selectively disclosable claims),
use createSDJWTWithDecoys:
-- Create SD-JWT with 5 decoy digests, no typ or kid headers result <- createSDJWTWithDecoys Nothing Nothing SHA256 issuerKey ["given_name", "email"] claims 5 -- Create SD-JWT with 5 decoy digests and typ header result <- createSDJWTWithDecoys (Just "sd-jwt") Nothing SHA256 issuerKey ["given_name", "email"] claims 5 -- Create SD-JWT with 5 decoy digests, typ and kid headers result <- createSDJWTWithDecoys (Just "sd-jwt") (Just "key-1") SHA256 issuerKey ["given_name"] claims 5
For advanced use cases (e.g., adding decoys to nested _sd arrays or custom
placement logic), import SDJWT.Internal.Issuance to access buildSDJWTPayload
and other low-level functions.
Key Binding Support
To include the holder's public key in the SD-JWT (for key binding), use
addHolderKeyToClaims to add the cnf claim:
let holderPublicKeyJWK = "{"kty":"EC","crv":"P-256","x":"...","y":"..."}"
let claimsWithCnf = addHolderKeyToClaims holderPublicKeyJWK claims
result <- createSDJWT (Just "sd-jwt") SHA256 issuerKey ["given_name"] claimsWithCnf
Example
>>>:set -XOverloadedStrings>>>import SDJWT.Issuer>>>import qualified Data.Map.Strict as Map>>>import qualified Data.Aeson as Aeson>>>import qualified Data.Text as T>>>let claims = Map.fromList [("sub", Aeson.String "user_123"), ("given_name", Aeson.String "John"), ("family_name", Aeson.String "Doe")]>>>-- Note: In real usage, you would load your private key here>>>-- issuerPrivateKeyJWK <- loadPrivateKeyJWK>>>-- result <- createSDJWT Nothing SHA256 issuerPrivateKeyJWK ["given_name", "family_name"] claims>>>-- case result of Right sdjwt -> serializeSDJWT sdjwt; Left err -> T.pack $ show err
Synopsis
- module SDJWT.Internal.Types
- module SDJWT.Internal.Serialization
- createSDJWT :: JWKLike jwk => Maybe Text -> Maybe Text -> HashAlgorithm -> jwk -> [Text] -> Object -> IO (Either SDJWTError SDJWT)
- createSDJWTWithDecoys :: JWKLike jwk => Maybe Text -> Maybe Text -> HashAlgorithm -> jwk -> [Text] -> Object -> Int -> IO (Either SDJWTError SDJWT)
- addHolderKeyToClaims :: Text -> Object -> Object
Core Types
module SDJWT.Internal.Types
Serialization
module SDJWT.Internal.Serialization
Creating SD-JWTs
Functions for creating SD-JWTs from claims sets.
Arguments
| :: JWKLike jwk | |
| => Maybe Text | Optional typ header value (RFC 9901 Section 9.11 recommends explicit typing). If |
| -> Maybe Text | Optional kid header value (Key ID for key management). If |
| -> HashAlgorithm | Hash algorithm for digests |
| -> jwk | Issuer private key JWK (Text or jose JWK object) |
| -> [Text] | Claim names to mark as selectively disclosable |
| -> Object | Original claims object. May include standard JWT claims such as |
| -> IO (Either SDJWTError SDJWT) |
Create a complete SD-JWT (signed).
This function creates an SD-JWT and signs it using the issuer's key. Creates a complete SD-JWT with signed JWT using jose.
Returns the created SD-JWT or an error.
Standard JWT Claims
Standard JWT claims (RFC 7519) can be included in the claims map and will be preserved
in the issuer-signed JWT payload. During verification, standard claims like exp and nbf
are automatically validated if present. See RFC 9901 Section 4.1 for details.
Example
-- Create SD-JWT without typ header result <- createSDJWT Nothing SHA256 issuerKey ["given_name", "family_name"] claims -- Create SD-JWT with typ header result <- createSDJWT (Just "sd-jwt") SHA256 issuerKey ["given_name", "family_name"] claims -- Create SD-JWT with expiration time let claimsWithExp = Map.insert "exp" (Aeson.Number (fromIntegral expirationTime)) claims result <- createSDJWT (Just "sd-jwt") SHA256 issuerKey ["given_name"] claimsWithExp
createSDJWTWithDecoys Source #
Arguments
| :: JWKLike jwk | |
| => Maybe Text | Optional typ header value (e.g., Just "sd-jwt" or Just "example+sd-jwt"). If |
| -> Maybe Text | Optional kid header value (Key ID for key management). If |
| -> HashAlgorithm | Hash algorithm for digests |
| -> jwk | Issuer private key JWK (Text or jose JWK object) |
| -> [Text] | Claim names to mark as selectively disclosable |
| -> Object | Original claims object. May include standard JWT claims such as |
| -> Int | Number of decoy digests to add (must be >= 0) |
| -> IO (Either SDJWTError SDJWT) |
Create an SD-JWT with optional typ header and decoy digests.
This function is similar to createSDJWT but automatically adds
a specified number of decoy digests to the _sd array to obscure the
actual number of selectively disclosable claims.
Returns the created SD-JWT or an error.
Standard JWT Claims
Standard JWT claims (RFC 7519) can be included in the claims map and will be preserved
in the issuer-signed JWT payload. During verification, standard claims like exp and nbf
are automatically validated if present. See RFC 9901 Section 4.1 for details.
Example
-- Create SD-JWT with 5 decoy digests, no typ header result <- createSDJWTWithDecoys Nothing SHA256 issuerKey ["given_name", "email"] claims 5 -- Create SD-JWT with 5 decoy digests and typ header result <- createSDJWTWithDecoys (Just "sd-jwt") SHA256 issuerKey ["given_name", "email"] claims 5
Helper Functions
Convenience functions for common operations.
Arguments
| :: Text | Holder's public key as a JWK JSON string |
| -> Object | Original claims object |
| -> Object | Claims object with |
Add holder's public key to claims as a cnf claim (RFC 7800).
This convenience function adds the holder's public key to the claims map in the format required by RFC 7800 for key confirmation:
{
"cnf": {
"jwk": "holderPublicKeyJWK"
}
}
The cnf claim is used during key binding to prove that the holder
possesses the corresponding private key.
Example
let holderPublicKeyJWK = "{"kty":"EC","crv":"P-256","x":"...","y":"..."}"
let claimsWithCnf = addHolderKeyToClaims holderPublicKeyJWK claims
result <- createSDJWT (Just "sd-jwt") SHA256 issuerKey ["given_name"] claimsWithCnf
See Also
- RFC 7800: Proof-of-Possession Key Semantics for JSON Web Tokens (JWT)
- RFC 9901 Section 4.3: Key Binding