Skip to content

Conversation

@warren-gallagher
Copy link

Add Signer Abstraction Interface with Azure Key Vault Example

Overview

This PR introduces a pluggable signing abstraction layer to @auth0/mdl, enabling integration with Hardware Security Modules (HSMs) and cloud-based key management services while maintaining full backward compatibility with existing code.

Addresses: #31

Key Changes

1. Signer Abstraction Interface

Added a new Signer interface (src/mdoc/signing/Signer.ts) that defines a standard contract for signing operations:

interface Signer {
  sign(data: Uint8Array): Promise<Uint8Array>;
  getKeyId(): string | Uint8Array | undefined;
  getAlgorithm(): SupportedAlgs;
}

This abstraction allows the library to support multiple signing backends without coupling to specific key storage implementations.

2. LocalKeySigner Implementation

Implemented LocalKeySigner (src/mdoc/signing/LocalKeySigner.ts) that wraps traditional JWK-based signing to conform to the new Signer interface. This provides:

  • A bridge between the new abstraction and existing signing mechanisms
  • Convenience for testing and local development
  • A reference implementation for custom signer development

3. Enhanced Document Signing

Updated Document.sign() to accept an optional signer parameter alongside the existing issuerPrivateKey:

await doc.sign({
  signer: customSigner,  // NEW: Use custom signer
  issuerCertificate: certificatePEM,
});

// OR (backward compatible)
await doc.sign({
  issuerPrivateKey: jwk,  // EXISTING: Still works!
  issuerCertificate: certificatePEM,
  alg: 'ES256',
});

Backward Compatibility: All existing code using issuerPrivateKey continues to work without any changes. The parameters are mutually exclusive with proper validation.

4. IssuerAuth.signWithSigner()

Added new static method IssuerAuth.signWithSigner() that manually constructs COSE_Sign1 structures following RFC 8152 Section 4.4, bypassing cose-kit's key handling to enable custom signers.

5. Azure Key Vault Example

Included a complete example implementation in examples/azure-keyvault-signer/ demonstrating:

  • AzureKeyVaultSigner class that implements the Signer interface
  • Integration with Azure Key Vault for HSM-backed signing
  • Support for ES256, ES384, and ES512 algorithms
  • Comprehensive documentation and usage examples
  • Test suite for the Azure Key Vault integration

Note: The Azure Key Vault implementation is provided as an example, not as part of the core library. Users can adapt this pattern for AWS KMS, Google Cloud KMS, or other signing services.

Benefits

  • Security: Private keys can remain in HSMs/KMS without exposure to application memory
  • Compliance: Meets regulatory requirements for key management (FIPS 140-2, etc.)
  • Flexibility: Easily integrate with any signing service by implementing the Signer interface
  • Backward Compatible: No breaking changes; existing code works unchanged
  • Extensible: Clean abstraction enables future signing backend additions

Files Changed

Core Library

  • src/mdoc/signing/Signer.ts - Signer interface
  • src/mdoc/signing/LocalKeySigner.ts - Local JWK signer implementation
  • src/mdoc/signing/index.ts - Module exports
  • src/mdoc/model/Document.ts - Enhanced to support custom signers
  • src/mdoc/model/IssuerAuth.ts - Added signWithSigner() method
  • src/index.ts - Export Signer and LocalKeySigner

Example Implementation

  • examples/azure-keyvault-signer/src/AzureKeyVaultSigner.ts - Azure Key Vault signer example
  • examples/azure-keyvault-signer/AZURE_KEY_VAULT_EXAMPLE.md - Usage documentation
  • examples/azure-keyvault-signer/AZURE_KEY_VAULT_INTEGRATION.md - Integration guide
  • examples/azure-keyvault-signer/README.md - Overview
  • examples/azure-keyvault-signer/__tests__/azureKeyVault.tests.ts - Tests
  • examples/azure-keyvault-signer/sample-azure-env.sh - Environment setup script

Testing

  • All existing tests pass (backward compatibility verified)
  • Added comprehensive tests for Azure Key Vault integration
  • Validated with ES256, ES384, and ES512 algorithms

Migration Guide

No migration needed! This is a purely additive change. Existing code continues to work:

// Before and After - identical
await doc.sign({
  issuerPrivateKey: myJWK,
  issuerCertificate: cert,
  alg: 'ES256',
});

To adopt the new signer interface:

// Using LocalKeySigner
const signer = new LocalKeySigner(myJWK, 'ES256');
await doc.sign({
  signer,
  issuerCertificate: cert,
});

// Using Azure Key Vault (example implementation)
const signer = new AzureKeyVaultSigner({
  keyVaultUrl: 'https://my-vault.vault.azure.net',
  keyName: 'my-key',
  algorithm: 'ES256',
});
await doc.sign({
  signer,
  issuerCertificate: cert,
});

Warren Gallagher added 5 commits October 27, 2025 15:20
Add Azure Key Vault integration for hardware-backed cryptographic signing of mobile driver's
licenses (mDL) compliant with ISO 18013-5.

Key changes:
- Introduce Signer abstraction interface for pluggable signing implementations
- Implement AzureKeyVaultSigner with @azure/keyvault-keys and @azure/identity
- Refactor existing signing logic into LocalKeySigner for backward compatibility
- Add comprehensive test suite with certificate chain validation
- Support ES256, ES384, ES512, and RSA signature algorithms
- Document EdDSA/Ed25519 limitation (not supported by Azure Key Vault HSM)
- Include setup guide and integration documentation

All 115 tests passing with 100% backward compatibility maintained.
…tations

Add Example Azure Key Vault integration for hardware-backed cryptographic signing of mobile driver's
licenses (mDL) compliant with ISO 18013-5.

Key changes:
- Introduce Signer abstraction interface for pluggable signing implementations
- Implement AzureKeyVaultSigner with @azure/keyvault-keys and @azure/identity
- Refactor existing signing logic into LocalKeySigner for backward compatibility
- Add comprehensive test suite with certificate chain validation
- Support ES256, ES384, ES512 signature algorithms
- Document EdDSA/Ed25519 limitation (not supported by Azure Key Vault HSM)
- Include setup guide and integration documentation
@warren-gallagher warren-gallagher marked this pull request as draft November 4, 2025 19:40
The LocalKeySigner had two critical bugs that prevented signatures from being verified:
Double-hashing: Used createSign() which hashes data, but the data was already a Sig_structure
Wrong signature format: Used DER encoding instead of IEEE P1363 format required by COSE
Solution Implemented
Fixed src/mdoc/signing/LocalKeySigner.ts:
Changes Made:
Replaced createSign() with crypto.sign() - The one-shot API that matches cose-kit's behavior
Added dsaEncoding: 'ieee-p1363'
  - Specifies COSE-compliant signature format (raw R||S concatenation)
Simplified digest mapping
  - RSA algorithms now correctly map to digest names only (e.g., 'sha256' not 'RSA-SHA256')
Test Results
✅ All tests pass! The new tests/issuing/signerCompatibility.tests.ts confirms:
✅ Documents are verifiable - Both old (issuerPrivateKey) and new (LocalKeySigner) methods
   produce valid, verifiable signatures
✅ Identical structure - Documents have the same COSE structure, algorithm identifiers,
   and payload
✅ Backward compatibility - Old signing method still works perfectly
✅ Proper validation - Correctly rejects invalid parameter combinations
Technical Details
The fix ensures LocalKeySigner follows the same signing process as cose-kit:
Sig_structure → hash with SHA-256/384/512 → sign with private key → IEEE P1363 format
Instead of the broken:
Sig_structure → hash → hash again → sign → DER format ❌
The implementation now correctly uses:
cryptoSign(digestAlgorithm, data, {
  key: key,
  dsaEncoding: 'ieee-p1363', // COSE format!
});
@warren-gallagher warren-gallagher marked this pull request as ready for review November 5, 2025 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant