Aller au contenu

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é)

npm install graphene-pk11 @types/graphene-pk11

Avantages : - ✅ Wrapper TypeScript natif - ✅ Support Node.js >= 14 - ✅ Abstraction PKCS#11 v2.40 - ✅ Support AWS CloudHSM

Option 2 : pkcs11js (Alternative)

npm install pkcs11js

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-pk11 package
  • 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 HsmModule dans CryptoModule
  • 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)

  1. Configuration CloudHSM
  2. PKCS#11 Provider + Session
  3. Service Signature (ECDSA P-256)
  4. Tests unitaires

Phase 4B : Services avancés (Sprint suivant)

  1. Service Vérification
  2. Service Gestion Clés
  3. Tests intégration CloudHSM DEV
  4. Documentation API

Phase 4C : Certificats X.509 (Futur)

  1. Service Certificats
  2. mTLS support
  3. TSA (horodatage)

Phase 4 Backend NestJS - PKCS#11 Version : 1.0 Date : 2025-11-27 Statut : 📋 PLANIFICATION COMPLÈTE