Skip to main content

Security Architecture

This page provides an overview of EUDIPLO's security architecture, including cryptographic algorithms, token validation, DPoP support, and secret handling policies.


Cryptographic Algorithmsโ€‹

EUDIPLO uses ES256 (ECDSA with P-256 curve) as the primary signing algorithm across all protocols:

OperationAlgorithmCurveNotes
Access Token SigningES256P-256JWT signed by issuer AS
SD-JWT VC SigningES256P-256Credential signed by issuer attestation key
mDOC SigningES256P-256Mobile Security Object (MSO) signed via COSE
Status List SigningES256P-256OAuth Token Status List JWT signed by issuer
Trust List SigningES256P-256ETSI TL or OpenID Federation metadata signed by trust anchor
VP Token SigningES256P-256Verifiable Presentation signed by wallet

Rationale:

ES256 is the EUDI Wallet Architecture Reference Framework (ARF) baseline requirement and is widely supported across EUDI ecosystem implementations. Alternative algorithms (RS256, EdDSA) may be added in future releases based on interoperability requirements.


Key Loading and Storageโ€‹

EUDIPLO enforces asynchronous key loading to prevent blocking the main application thread during key retrieval from external KMS providers.

Key Providersโ€‹

All signing keys are managed via pluggable KMS providers:

ProviderDescriptionUse Case
dbDatabase-stored keys (encrypted at rest)Development, testing, small-scale deployments
vaultHashiCorp Vault Transit secrets engineProduction environments with centralized key management
aws-kmsAWS Key Management ServiceCloud-native deployments on AWS
pkcs11PKCS#11 Hardware Security ModuleHigh-security environments (air-gapped, FIPS compliance)
httpRemote KMS microserviceCustom key management infrastructure
cscCloud Signature Consortium (CSC) APIRemote signature services

See Key Management for provider configuration.


Secret Handling Policyโ€‹

EUDIPLO enforces a zero-secret-export policy for private key material:

ScenarioPolicy
Private Keys in Configuration BundlesโŒ Never exported โ€” Private keys are always generated or stored in the KMS provider and are never included in configuration bundles.
Private Keys in API ResponsesโŒ Never returned โ€” The Key Chain API only returns public key material (JWK public key or X.509 certificate).
Environment Variable Placeholdersโœ… Allowed in kms.json โ€” Secret references (e.g., ${VAULT_TOKEN}) are permitted for KMS provider configuration.
Encryption Keysโš ๏ธ Database-only โ€” Encryption keys (for decrypting VP Tokens) are always stored in the database. These are never loaded from external KMS providers.

Audit Logging:

All private key operations (signing, key generation, key rotation) are logged via the AuditLogService for compliance tracking. Logs include:

  • Timestamp
  • Tenant ID
  • Key Chain ID
  • Operation type (sign, generate, rotate, delete)
  • User/service identity (if authenticated)

Token Validationโ€‹

EUDIPLO enforces strict JWT validation for all token-based flows (access tokens, VP tokens, credentials).

Required Claimsโ€‹

ClaimDescriptionValidation Rule
iss (Issuer)Token issuer identifierMust match expected issuer (tenant URL or configured external AS)
aud (Audience)Token audienceMust include EUDIPLO's tenant base URL
exp (Expiration)Expiration timestampToken must not be expired (current time < exp)
nbf (Not Before)Not-before timestampToken must be valid (current time >= nbf)
iat (Issued At)Issuance timestampToken must not be issued in the future (current time >= iat)

Clock Skew Tolerance:

EUDIPLO allows a 30-second clock skew for exp, nbf, and iat validation to account for minor time synchronization differences between systems.


Access Token Validation (OID4VCI)โ€‹

When a wallet presents an access token at the credential endpoint, EUDIPLO verifies:

  1. Signature: Verify JWT signature using the issuer's public key (from JWKS or X.509 cert)
  2. Issuer: Check that iss matches the expected AS endpoint
  3. Audience: Check that aud includes the credential issuer URL
  4. Expiration: Check that exp is in the future
  5. Session Correlation: Extract issuer_state and correlate with active session
  6. DPoP Binding (if enabled): Verify cnf.jkt matches the DPoP proof key thumbprint

Example Access Token:

{
"iss": "https://eudiplo.example.com/tenant1/issuer",
"sub": "wallet-client-id",
"aud": "https://eudiplo.example.com/tenant1",
"exp": 1234567890,
"iat": 1234567800,
"issuer_state": "session-uuid",
"client_id": "wallet-client-id",
"cnf": {
"jkt": "dpop-key-thumbprint"
}
}

VP Token Validation (OID4VP)โ€‹

When a wallet submits a VP Token, EUDIPLO verifies:

  1. Decryption: Decrypt JWE using the configured encryption key (if VP Token is encrypted)
  2. Signature: Verify each credential's signature using the issuer's public key
  3. Nonce: Verify the nonce claim matches the session's walletNonce
  4. Audience: Verify the aud claim matches the verifier's client ID
  5. Trust Validation: Verify the credential issuer is trusted (via trust list or federation)
  6. Status Check: Verify the credential is not revoked or suspended (via status list)
  7. Claims Validation: Verify presented claims match the DCQL query

See Presentation Architecture for detailed verification flow.


DPoP (Demonstrating Proof-of-Possession)โ€‹

EUDIPLO supports DPoP (RFC 9449) to bind access tokens to the wallet's public key. This prevents token theft and replay attacks.

DPoP Flowโ€‹

DPoP Proof Structureโ€‹

The DPoP proof is a signed JWT included in the DPoP HTTP header:

{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}
.
{
"jti": "unique-jti",
"htm": "POST",
"htu": "https://eudiplo.example.com/tenant1/issuer/token",
"iat": 1234567800
}

Claims:

ClaimDescription
jtiUnique JWT ID (prevents replay)
htmHTTP method (POST, GET)
htuHTTP URI (request endpoint URL)
iatIssued-at timestamp

Validation:

  1. Verify JWT signature using the jwk claim
  2. Verify htm matches the HTTP method
  3. Verify htu matches the request URL
  4. Verify iat is recent (within 60 seconds)
  5. Verify jti has not been used before (replay prevention)

Configuration:

DPoP is enabled per issuance configuration:

{
"dPopRequired": true
}

Wallet Attestationโ€‹

EUDIPLO supports Wallet Attestation (OAuth 2.0 Client Attestation PoP) to verify the wallet provider's trustworthiness before issuing credentials.

Wallet Attestation Flowโ€‹

Attestation Headersโ€‹

HeaderDescription
OAuth-Client-AttestationWallet provider's signed attestation JWT (includes wallet's public key)
OAuth-Client-Attestation-PoPWallet's proof-of-possession JWT (signed with wallet's private key)

Attestation JWT:

{
"iss": "https://wallet-provider.example.com",
"sub": "wallet-instance-id",
"iat": 1234567800,
"exp": 1234567890,
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}
}

PoP JWT:

{
"iss": "wallet-instance-id",
"aud": "https://eudiplo.example.com/tenant1",
"iat": 1234567800,
"jti": "unique-jti"
}

Validation:

  1. Verify attestation JWT signature using wallet provider's public key (from trust list or JWKS)
  2. Verify attestation is not expired (exp)
  3. Extract wallet's public key from attestation (cnf.jwk)
  4. Verify PoP JWT signature using wallet's public key
  5. Verify PoP aud matches the issuer URL
  6. Verify PoP jti has not been used before (replay prevention)

Configuration:

Wallet attestation is enabled per issuance configuration:

{
"walletAttestationRequired": true
}

Session Security (OID4VP ยง13.3)โ€‹

EUDIPLO implements the OID4VP ยง13.3 session security model to prevent session fixation and replay attacks.

Wallet Nonce Separationโ€‹

The walletNonce is a wallet-facing session identifier that is distinct from the internal session ID. This prevents attackers from enumerating or guessing session IDs.

Flow:

  1. EUDIPLO creates a presentation request with nonce: walletNonce
  2. Wallet includes the nonce in the VP Token
  3. EUDIPLO correlates the VP Token with the session via the walletNonce
  4. EUDIPLO never exposes the internal session ID to the wallet

Database Schema:

@Entity()
export class Session {
@PrimaryGeneratedColumn('uuid')
id: string; // Internal session ID (never exposed)

@Column({ unique: true })
walletNonce: string; // Wallet-facing nonce (exposed in protocol)

// ...
}

Response Code (Same-Device Redirect)โ€‹

For same-device flows (e.g., verifier and wallet on the same device), EUDIPLO generates a one-time response_code to prevent session fixation attacks.

Flow:

  1. Wallet submits VP Token to /direct_post.jwt
  2. EUDIPLO validates the VP Token
  3. EUDIPLO generates a one-time response_code and stores it in the session
  4. EUDIPLO redirects the wallet to redirect_uri?response_code=xxx
  5. Verifier exchanges the response_code for the verification result

Security Properties:

PropertyEnforcement
Single-UseResponse code is consumed after first use
Short-LivedExpires after 5 minutes
RandomCryptographically random (32 bytes)
Session-BoundOnly valid for the session that created it

Attack Prevention:

This prevents an attacker from:

  • Embedding a stolen redirect_uri in a malicious QR code
  • Correlating the wallet's session with a different verifier's session
  • Replaying a response_code from a previous presentation

Secret Handlingโ€‹

EUDIPLO enforces strict policies to prevent accidental exposure of secrets, private keys, and user PII.

Secrets in Configurationโ€‹

Secret TypeStoragePolicy
Private KeysKMS provider (never in config files)โŒ Never exported or included in config bundles
Database PasswordsEnvironment variablesโœ… Must use ${DB_PASSWORD} placeholder in config files
KMS TokensEnvironment variablesโœ… Must use ${VAULT_TOKEN} placeholder in kms.json
Webhook SecretsEnvironment variablesโœ… Must use ${WEBHOOK_SECRET} placeholder in webhook config
API Keys (Attribute Providers)Environment variablesโœ… Must use ${API_KEY} placeholder in attribute provider config

Configuration Export:

When exporting configuration bundles via the management API:

  • Private keys are never included (only public keys and certificates)
  • Secret placeholders are preserved (e.g., ${DB_PASSWORD})
  • Sensitive session data is excluded (user claims, VP tokens)

Secrets in Logsโ€‹

EUDIPLO uses Pino logger with automatic secret redaction:

Logged FieldRedaction Policy
Access TokensโŒ Never logged (even redacted)
Private KeysโŒ Never logged
User PIIโŒ Never logged unless explicitly enabled for debugging
DPoP Proofsโš ๏ธ Logged at debug level only (contains public key, not secret)
VP Tokensโš ๏ธ Logged at debug level only (for debugging failed verifications)
Credential Claimsโš ๏ธ Logged at debug level only (for debugging issuance)

Audit Logging:

The AuditLogService persists compliance events to the database. Audit logs include:

  • Timestamp
  • Tenant ID
  • User/service identity
  • Operation type
  • Success/failure status
  • Redacted request/response payloads (no secrets or PII)

HTTPS and TLSโ€‹

EUDIPLO requires HTTPS in production for all external endpoints:

Endpoint TypeHTTPS RequirementNotes
Issuer Endpointsโœ… RequiredAll OID4VCI endpoints must use HTTPS
Verifier Endpointsโœ… RequiredAll OID4VP endpoints must use HTTPS
Webhook Endpointsโœ… RequiredOutbound webhook requests use HTTPS
Management APIโœ… RequiredAll API endpoints must use HTTPS
Local Developmentโš ๏ธ OptionalHTTP allowed when NODE_ENV=development

TLS Configuration:

EUDIPLO does not terminate TLS itself. Deploy behind a reverse proxy (e.g., NGINX, Traefik, AWS ALB) to handle TLS termination.

Certificate Trust:

For external KMS providers (e.g., Vault, AWS KMS), EUDIPLO validates TLS certificates using the system's default trust store. Custom CA certificates can be added via the NODE_EXTRA_CA_CERTS environment variable.


CORS (Cross-Origin Resource Sharing)โ€‹

EUDIPLO enforces strict CORS policies for browser-based wallet interactions:

Endpoint TypeCORS Policy
Protocol Endpointsโœ… CORS enabled for all OID4VCI/OID4VP endpoints
Management APIโŒ CORS disabled (API access requires server-to-server authentication)
Digital Credentials APIโœ… CORS enabled for DC API endpoints

Allowed Origins:

By default, EUDIPLO allows CORS requests from all origins for protocol endpoints (to support wallet apps from any domain). For production deployments, configure the CORS_ORIGINS environment variable to restrict allowed origins:

CORS_ORIGINS=https://wallet.example.com,https://app.example.com

Rate Limitingโ€‹

EUDIPLO includes built-in rate limiting to prevent abuse and denial-of-service attacks:

Endpoint TypeRate LimitWindow
Token Endpoint10 requests/min per IPRolling 60-second window
Credential Endpoint20 requests/min per access tokenRolling 60-second window
Offer Endpoints100 requests/min per tenantRolling 60-second window
Management API60 requests/min per API keyRolling 60-second window

Configuration:

Rate limits can be customized via environment variables:

RATE_LIMIT_TOKEN=10
RATE_LIMIT_CREDENTIAL=20
RATE_LIMIT_OFFER=100

Security Checklistโ€‹

Before deploying EUDIPLO to production, verify:

  • โœ… HTTPS enabled for all external endpoints
  • โœ… KMS provider configured (not using db provider in production)
  • โœ… Environment variables used for all secrets (no hardcoded secrets)
  • โœ… Session cleanup enabled with appropriate retention policy
  • โœ… Rate limiting configured for protocol endpoints
  • โœ… CORS origins restricted to trusted wallet domains
  • โœ… Audit logging enabled and persisted to secure storage
  • โœ… TLS certificates valid and trusted
  • โœ… DPoP enforcement enabled for production credential issuance
  • โœ… Wallet attestation enabled for high-security use cases
  • โœ… Trust list validation configured for credential verification

Next Stepsโ€‹