feat: implement CryptoModule with comprehensive cryptographic utilities and testing

Added CryptoModule providing services for Argon2 hashing, JWT handling, JWE encryption/decryption, JWS signing/verification, and post-quantum cryptography (ML-KEM). Includes extensive unit tests for all features.
This commit is contained in:
Mathis HERRIOT
2026-01-06 12:09:44 +01:00
parent adceada1b6
commit 810acd8ed4
3 changed files with 251 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
import {Injectable, Logger} from "@nestjs/common";
import {ConfigService} from "@nestjs/config";
import {ml_kem768} from "@noble/post-quantum/ml-kem.js";
import {hash, verify} from "@node-rs/argon2";
import * as jose from "jose";
@Injectable()
export class CryptoService {
private readonly logger = new Logger(CryptoService.name);
private readonly jwtSecret: Uint8Array;
private readonly encryptionKey: Uint8Array;
constructor(private configService: ConfigService) {
const secret = this.configService.get<string>("JWT_SECRET");
if (!secret) {
this.logger.warn(
"JWT_SECRET is not defined, using a default insecure secret for development",
);
}
this.jwtSecret = new TextEncoder().encode(
secret || "default-secret-change-me-in-production",
);
const encKey = this.configService.get<string>("ENCRYPTION_KEY");
if (!encKey) {
this.logger.warn(
"ENCRYPTION_KEY is not defined, using a default insecure key for development",
);
}
// Pour AES-GCM 256, on a besoin de 32 octets (256 bits)
const rawKey = encKey || "default-encryption-key-32-chars-";
this.encryptionKey = new TextEncoder().encode(
rawKey.padEnd(32, "0").substring(0, 32),
);
}
// --- Argon2 Hashing ---
async hashPassword(password: string): Promise<string> {
return hash(password, {
algorithm: 2,
});
}
async verifyPassword(password: string, hash: string): Promise<boolean> {
return verify(hash, password);
}
// --- JWT Operations via jose ---
async generateJwt(
payload: jose.JWTPayload,
expiresIn = "2h",
): Promise<string> {
return new jose.SignJWT(payload)
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime(expiresIn)
.sign(this.jwtSecret);
}
async verifyJwt<T extends jose.JWTPayload>(token: string): Promise<T> {
const { payload } = await jose.jwtVerify(token, this.jwtSecret);
return payload as T;
}
// --- Encryption & Decryption (JWE) ---
/**
* Chiffre un contenu textuel en utilisant JWE (Compact Serialization)
* Algorithme: A256GCMKW pour la gestion des clés, A256GCM pour le chiffrement de contenu
*/
async encryptContent(content: string): Promise<string> {
const data = new TextEncoder().encode(content);
return new jose.CompactEncrypt(data)
.setProtectedHeader({ alg: "dir", enc: "A256GCM" })
.encrypt(this.encryptionKey);
}
/**
* Déchiffre un contenu JWE
*/
async decryptContent(jwe: string): Promise<string> {
const { plaintext } = await jose.compactDecrypt(jwe, this.encryptionKey);
return new TextDecoder().decode(plaintext);
}
// --- Signature & Verification (JWS) ---
/**
* Signe un contenu textuel en utilisant JWS (Compact Serialization)
* Algorithme: HS256 (HMAC-SHA256)
*/
async signContent(content: string): Promise<string> {
const data = new TextEncoder().encode(content);
return new jose.CompactSign(data)
.setProtectedHeader({ alg: "HS256" })
.sign(this.jwtSecret);
}
/**
* Vérifie la signature JWS d'un contenu
*/
async verifyContentSignature(jws: string): Promise<string> {
const { payload } = await jose.compactVerify(jws, this.jwtSecret);
return new TextDecoder().decode(payload);
}
// --- Post-Quantum Cryptography via @noble/post-quantum ---
// Example: Kyber (ML-KEM) key encapsulation
generatePostQuantumKeyPair() {
const seed = new Uint8Array(64);
crypto.getRandomValues(seed);
const { publicKey, secretKey } = ml_kem768.keygen(seed);
return { publicKey, secretKey };
}
encapsulate(publicKey: Uint8Array) {
return ml_kem768.encapsulate(publicKey);
}
decapsulate(cipherText: Uint8Array, secretKey: Uint8Array) {
return ml_kem768.decapsulate(cipherText, secretKey);
}
}