Phase 4 : Backend NestJS - Intégration PKCS#11 CloudHSM¶
PD-36 : Client HSM Cloud PKCS#11 Phase 4 : Backend NestJS - Intégration CloudHSM Date : 2025-11-27 Statut : 📋 PLANIFICATION
📋 Vue d'ensemble¶
Objectif : Intégrer CloudHSM via PKCS#11 dans le backend NestJS pour : 1. Signature/Vérification de documents (ECDSA P-256, RSA-PSS 4096) 2. Gestion sécurisée des clés de chiffrement (K_master_hsm) 3. mTLS avec certificats X.509 stockés dans HSM 4. Horodatage probatoire (TSA)
🏗️ Architecture Cible¶
Hiérarchie des clés (PD-34 + PD-36)¶
┌──────────────────────────────────────────────────────────────┐
│ Architecture Hybride : Client + HSM │
└──────────────────────────────────────────────────────────────┘
CLIENT (Mobile/Web) :
└─→ K_master_user (dérivée Argon2id password)
└─→ K_doc (HKDF-SHA3-256 per-document)
└─→ Chiffrement documents AES-256-GCM
BACKEND + CLOUDHSM :
└─→ K_master_hsm (stockée HSM, FIPS 140-2 Level 3)
├─→ Signature documents (ECDSA P-256 / RSA-PSS 4096)
├─→ Vérification intégrité
├─→ Horodatage probatoire (TSA)
└─→ mTLS certificats X.509
Séparation des responsabilités : - Client : Chiffrement données (AES-256-GCM) avec K_doc - Backend + HSM : Signature cryptographique (ECDSA/RSA-PSS)
📦 Architecture Backend¶
Module Structure¶
src/modules/
├── crypto/
│ ├── crypto.module.ts (existant)
│ ├── encryption.service.ts (existant - AES-256-GCM)
│ ├── hash.service.ts (existant - SHA3-256)
│ ├── hkdf.service.ts (existant - HKDF)
│ ├── keys.service.ts (existant - génération clés)
│ │
│ ├── hsm/ ← NOUVEAU (PD-36)
│ │ ├── hsm.module.ts
│ │ ├── hsm.service.ts (Abstraction PKCS#11)
│ │ ├── hsm.config.ts (Configuration CloudHSM)
│ │ ├── pkcs11/
│ │ │ ├── pkcs11.provider.ts (Provider PKCS#11 library)
│ │ │ ├── pkcs11-session.service.ts
│ │ │ └── pkcs11-operations.service.ts
│ │ │
│ │ ├── services/
│ │ │ ├── hsm-signature.service.ts (Signature ECDSA/RSA)
│ │ │ ├── hsm-verification.service.ts (Vérification signatures)
│ │ │ ├── hsm-keys.service.ts (Gestion clés HSM)
│ │ │ └── hsm-certificate.service.ts (Certificats X.509)
│ │ │
│ │ ├── dto/
│ │ │ ├── sign-document.dto.ts
│ │ │ ├── verify-signature.dto.ts
│ │ │ └── create-certificate.dto.ts
│ │ │
│ │ └── entities/
│ │ ├── hsm-key.entity.ts
│ │ └── hsm-signature.entity.ts
│ │
│ └── controllers/
│ └── signature.controller.ts ← NOUVEAU
Services Crypto Existants (À conserver)¶
| Service | Rôle | Statut |
|---|---|---|
EncryptionService | AES-256-GCM (client-side) | ✅ Existant |
HashService | SHA3-256 | ✅ Existant |
HkdfService | Dérivation K_doc | ✅ Existant |
KeysService | Génération clés | ✅ Existant |
AesKwService | AES Key Wrapping | ✅ Existant |
KeyEnvelopeService | Gestion enveloppes | ✅ Existant |
Services HSM Nouveaux (À créer)¶
| Service | Rôle | Priorité |
|---|---|---|
HsmService | Abstraction PKCS#11 | 🔴 Critique |
Pkcs11SessionService | Gestion sessions HSM | 🔴 Critique |
HsmSignatureService | Signature ECDSA/RSA | 🔴 Critique |
HsmVerificationService | Vérification signatures | 🟠 Important |
HsmKeysService | Gestion clés HSM | 🟠 Important |
HsmCertificateService | Certificats X.509 | 🟡 Futur |
🔧 Dépendances¶
Bibliothèques PKCS#11¶
Option 1 : graphene-pk11 (Recommandé)
Avantages : - ✅ Wrapper TypeScript natif - ✅ Support Node.js >= 14 - ✅ Abstraction PKCS#11 v2.40 - ✅ Support AWS CloudHSM
Option 2 : pkcs11js (Alternative)
Avantages : - ✅ Plus léger - ❌ Pas de types TypeScript natifs
AWS CloudHSM Client¶
Prérequis : - CloudHSM Client 5.x installé sur VPS (via Ansible Phase 2) - Library PKCS#11 : /opt/cloudhsm/lib/libcloudhsm_pkcs11.so
Variables d'environnement :
# .env (DEV)
CLOUDHSM_ENABLED=true
CLOUDHSM_LIBRARY_PATH=/opt/cloudhsm/lib/libcloudhsm_pkcs11.so
CLOUDHSM_PIN=${CLOUDHSM_CRYPTO_USER_PIN} # Secrets Manager
CLOUDHSM_SLOT=0
CLOUDHSM_USER=crypto_user
📐 Implémentation¶
Étape 1 : Configuration CloudHSM¶
Fichier : src/modules/crypto/hsm/hsm.config.ts
export interface HsmConfig {
enabled: boolean;
libraryPath: string;
pin: string;
slot: number;
user: string;
}
export const hsmConfig = (): HsmConfig => ({
enabled: process.env.CLOUDHSM_ENABLED === 'true',
libraryPath: process.env.CLOUDHSM_LIBRARY_PATH || '/opt/cloudhsm/lib/libcloudhsm_pkcs11.so',
pin: process.env.CLOUDHSM_PIN || '',
slot: parseInt(process.env.CLOUDHSM_SLOT || '0', 10),
user: process.env.CLOUDHSM_USER || 'crypto_user',
});
Étape 2 : PKCS#11 Provider¶
Fichier : src/modules/crypto/hsm/pkcs11/pkcs11.provider.ts
import * as graphene from 'graphene-pk11';
import { ConfigService } from '@nestjs/config';
export class Pkcs11Provider {
private module: graphene.Module;
private slot: graphene.Slot;
constructor(private config: ConfigService) {}
async initialize(): Promise<void> {
const libraryPath = this.config.get<string>('hsm.libraryPath');
// Load PKCS#11 library
this.module = graphene.Module.load(libraryPath);
this.module.initialize();
// Get slot
const slotId = this.config.get<number>('hsm.slot');
const slots = this.module.getSlots(true);
this.slot = slots.items(slotId);
}
getSlot(): graphene.Slot {
return this.slot;
}
async close(): Promise<void> {
this.module.finalize();
}
}
Étape 3 : Session Service¶
Fichier : src/modules/crypto/hsm/pkcs11/pkcs11-session.service.ts
import * as graphene from 'graphene-pk11';
import { Injectable, OnModuleDestroy } from '@nestjs/common';
import { Pkcs11Provider } from './pkcs11.provider';
@Injectable()
export class Pkcs11SessionService implements OnModuleDestroy {
private session: graphene.Session;
constructor(
private pkcs11Provider: Pkcs11Provider,
private config: ConfigService,
) {}
async openSession(): Promise<graphene.Session> {
const slot = this.pkcs11Provider.getSlot();
// Open session
this.session = slot.open(
graphene.SessionFlag.SERIAL_SESSION | graphene.SessionFlag.RW_SESSION
);
// Login
const pin = this.config.get<string>('hsm.pin');
this.session.login(pin, graphene.UserType.USER);
return this.session;
}
getSession(): graphene.Session {
if (!this.session) {
throw new Error('HSM session not initialized');
}
return this.session;
}
async closeSession(): Promise<void> {
if (this.session) {
this.session.logout();
this.session.close();
}
}
async onModuleDestroy(): Promise<void> {
await this.closeSession();
}
}
Étape 4 : HSM Signature Service¶
Fichier : src/modules/crypto/hsm/services/hsm-signature.service.ts
import { Injectable } from '@nestjs/common';
import * as graphene from 'graphene-pk11';
import { Pkcs11SessionService } from '../pkcs11/pkcs11-session.service';
@Injectable()
export class HsmSignatureService {
constructor(private sessionService: Pkcs11SessionService) {}
/**
* Signer un document avec ECDSA P-256
*/
async signEcdsa(data: Buffer, keyLabel: string): Promise<Buffer> {
const session = this.sessionService.getSession();
// Find private key
const privateKey = this.findPrivateKey(session, keyLabel);
// Sign with ECDSA-SHA256
const signature = session.createSign(
graphene.MechanismEnum.ECDSA_SHA256,
privateKey
).once(data);
return Buffer.from(signature);
}
/**
* Signer un document avec RSA-PSS 4096
*/
async signRsaPss(data: Buffer, keyLabel: string): Promise<Buffer> {
const session = this.sessionService.getSession();
// Find private key
const privateKey = this.findPrivateKey(session, keyLabel);
// Sign with RSA-PSS
const signature = session.createSign(
graphene.MechanismEnum.RSA_PKCS_PSS,
privateKey,
{
hashAlgorithm: graphene.MechanismEnum.SHA256,
mgf: graphene.MechanismEnum.MGF1_SHA256,
saltLength: 32
}
).once(data);
return Buffer.from(signature);
}
private findPrivateKey(
session: graphene.Session,
label: string
): graphene.PrivateKey {
const keys = session.find({
class: graphene.ObjectClass.PRIVATE_KEY,
label: label
});
if (keys.length === 0) {
throw new Error(`Private key not found: ${label}`);
}
return keys.items(0).toType<graphene.PrivateKey>();
}
}
🧪 Tests¶
Tests Unitaires¶
Fichier : src/modules/crypto/hsm/services/hsm-signature.service.spec.ts
describe('HsmSignatureService', () => {
let service: HsmSignatureService;
let mockSession: jest.Mocked<Pkcs11SessionService>;
beforeEach(async () => {
mockSession = {
getSession: jest.fn(),
} as any;
const module = await Test.createTestingModule({
providers: [
HsmSignatureService,
{ provide: Pkcs11SessionService, useValue: mockSession },
],
}).compile();
service = module.get<HsmSignatureService>(HsmSignatureService);
});
describe('signEcdsa', () => {
it('should sign data with ECDSA P-256', async () => {
// Mock session
const mockSessionObj = {
find: jest.fn().mockReturnValue({
length: 1,
items: jest.fn().mockReturnValue({
toType: jest.fn().mockReturnValue({}),
}),
}),
createSign: jest.fn().mockReturnValue({
once: jest.fn().mockReturnValue(Buffer.from('signature')),
}),
};
mockSession.getSession.mockReturnValue(mockSessionObj as any);
const data = Buffer.from('test data');
const signature = await service.signEcdsa(data, 'test-key');
expect(signature).toBeInstanceOf(Buffer);
expect(mockSessionObj.createSign).toHaveBeenCalledWith(
expect.any(Number), // ECDSA_SHA256
expect.anything()
);
});
});
});
Tests d'intégration (CloudHSM DEV)¶
Fichier : test/integration/hsm/hsm-signature.integration.spec.ts
describe('HSM Signature Integration', () => {
let app: INestApplication;
let hsmService: HsmSignatureService;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [HsmModule],
}).compile();
app = module.createNestApplication();
await app.init();
hsmService = module.get<HsmSignatureService>(HsmSignatureService);
});
afterAll(async () => {
await app.close();
});
it('should sign and verify ECDSA signature', async () => {
const data = Buffer.from('Integration test document');
// Sign
const signature = await hsmService.signEcdsa(data, 'test-ecdsa-key');
expect(signature).toBeInstanceOf(Buffer);
expect(signature.length).toBeGreaterThan(0);
// Verify (using verification service)
const verificationService = app.get<HsmVerificationService>(HsmVerificationService);
const isValid = await verificationService.verifyEcdsa(
data,
signature,
'test-ecdsa-key'
);
expect(isValid).toBe(true);
});
});
📋 Checklist Implémentation¶
Configuration¶
- Installer
graphene-pk11package - Créer
hsm.config.ts(configuration CloudHSM) - Variables d'environnement (.env)
- Secrets Manager (CLOUDHSM_PIN)
Module HSM¶
- Créer
hsm.module.ts - Provider
Pkcs11Provider - Service
Pkcs11SessionService - Service
HsmSignatureService - Service
HsmVerificationService - Service
HsmKeysService
Intégration¶
- Importer
HsmModuledansCryptoModule - Exporter services HSM
- Controller
SignatureController(API REST)
Tests¶
- Tests unitaires (tous services HSM)
- Tests intégration (CloudHSM DEV)
- Coverage >= 85%
Documentation¶
- README module HSM
- Guide utilisation API
- Documentation Swagger
- Guide troubleshooting
🔜 Prochaines Étapes¶
Phase 4A : Services de base (Sprint actuel)¶
- Configuration CloudHSM
- PKCS#11 Provider + Session
- Service Signature (ECDSA P-256)
- Tests unitaires
Phase 4B : Services avancés (Sprint suivant)¶
- Service Vérification
- Service Gestion Clés
- Tests intégration CloudHSM DEV
- Documentation API
Phase 4C : Certificats X.509 (Futur)¶
- Service Certificats
- mTLS support
- TSA (horodatage)
Phase 4 Backend NestJS - PKCS#11 Version : 1.0 Date : 2025-11-27 Statut : 📋 PLANIFICATION COMPLÈTE