UNPKG

@tshifhiwa/ohrm-ui-automation-framework

Version:

Playwright and TypeScript–based test automation framework for validating core UI features and workflows of the OrangeHRM demo application.

303 lines (275 loc) 11.3 kB
import * as crypto from "crypto"; import { CRYPTO_CONFIG, CRYPTO_TYPE, OUTPUT_FORMAT } from "../types/crypto.config.js"; import type { CryptoGenerationOptions } from "../types/crypto.types.js"; import { FileEncoding } from "../../fileManager/internal/file-encoding.enum.js"; import ErrorHandler from "../../errorHandling/errorHandler.js"; export default class SecureKeyGenerator { // Set base64 as buffer encoding private static readonly BASE_64: BufferEncoding = FileEncoding.BASE64; private static readonly IV_LENGTH = CRYPTO_CONFIG.BYTE_LENGTHS.IV; private static readonly WEB_CRYPTO_IV_LENGTH = CRYPTO_CONFIG.BYTE_LENGTHS.WEB_CRYPTO_IV; private static readonly SALT_LENGTH = CRYPTO_CONFIG.BYTE_LENGTHS.SALT; private static readonly SECRET_KEY_LENGTH = CRYPTO_CONFIG.BYTE_LENGTHS.SECRET_KEY; private static readonly MIN_VALIDATION_LIMIT = CRYPTO_CONFIG.VALIDATION_LIMITS.MIN_SECURE_LENGTH; private static readonly MAX_VALIDATION_LIMIT = CRYPTO_CONFIG.VALIDATION_LIMITS.MAX_REASONABLE_LENGTH; /** * Generates a cryptographically secure initialization vector (IV) as a base64-encoded string. * @param length The IV length in bytes. Defaults to the configured IV length. * @returns A base64-encoded string containing the IV. * @throws {Error} If the length is invalid or IV generation fails. */ public static generateBase64IV(length: number = this.IV_LENGTH): string { return this.generate({ type: CRYPTO_TYPE.IV, outputFormat: OUTPUT_FORMAT.BASE64, length, }) as string; } /** * Generates a cryptographically secure initialization vector (IV) as a Buffer. * @param length The IV length in bytes. Defaults to the configured IV length. * @returns A Buffer containing the IV. * @throws {Error} If the length is invalid or IV generation fails. */ public static generateBufferIV(length: number = this.IV_LENGTH): Buffer { return this.generate({ type: CRYPTO_TYPE.IV, outputFormat: OUTPUT_FORMAT.BUFFER, length, }) as Buffer; } /** * Generates a cryptographically secure IV as Uint8Array for Web Crypto API. * This is the preferred method for Web Crypto API operations. * @param length The IV length in bytes. Defaults to the configured Web Crypto IV length. * @returns A Uint8Array containing the secure IV. * @throws {Error} If the length is invalid or IV generation fails. */ public static generateWebCryptoIV(length: number = this.WEB_CRYPTO_IV_LENGTH): Uint8Array { this.validateLength(length, "generateWebCryptoIV"); try { return this.generateSecureUint8Array(length); } catch (error) { ErrorHandler.captureError( error, "generateWebCryptoIV", `Failed to generate Web Crypto IV of length ${length}`, ); throw error; } } /** * Unified method to generate cryptographically secure random values with flexible options. * @param options Configuration object specifying type, format, and optional length * @returns Generated value in the specified format (string or Buffer) * @throws {Error} If the options are invalid or generation fails */ public static generate(options: CryptoGenerationOptions): string | Buffer { const { type, outputFormat, length } = options; // Determine default length based on type let defaultLength: number; switch (type) { case CRYPTO_TYPE.SALT: defaultLength = this.SALT_LENGTH; break; case CRYPTO_TYPE.SECRET_KEY: defaultLength = this.SECRET_KEY_LENGTH; break; case CRYPTO_TYPE.IV: defaultLength = this.IV_LENGTH; break; case CRYPTO_TYPE.RANDOM: defaultLength = 32; break; default: throw new Error(`Invalid crypto type: ${type}`); } const finalLength = length ?? defaultLength; const methodName = `generate_${type}_${outputFormat}`; // Generate the secure bytes const buffer = this.generateSecureBytes(finalLength, methodName); // Return in the requested format switch (outputFormat) { case OUTPUT_FORMAT.BASE64: return buffer.toString(this.BASE_64); case OUTPUT_FORMAT.HEX: return buffer.toString(FileEncoding.HEX); case OUTPUT_FORMAT.BUFFER: return buffer; default: throw new Error(`Invalid output format: ${outputFormat}`); } } /** * Generates a cryptographically secure random salt as a base64 string. * @param length The salt length in bytes. Defaults to the configured salt length. * @returns A base64-encoded string containing the salt. * @throws {Error} If the length is invalid or salt generation fails. */ public static generateBase64Salt(length: number = this.SALT_LENGTH): string { return this.generate({ type: CRYPTO_TYPE.SALT, outputFormat: OUTPUT_FORMAT.BASE64, length, }) as string; } /** * Generates a cryptographically secure random salt as a Buffer. * @param length The salt length in bytes. Defaults to the configured salt length. * @returns A Buffer containing the salt. * @throws {Error} If the length is invalid or salt generation fails. */ public static generateBufferSalt(length: number = this.SALT_LENGTH): Buffer { return this.generate({ type: CRYPTO_TYPE.SALT, outputFormat: OUTPUT_FORMAT.BUFFER, length, }) as Buffer; } /** * Generates a cryptographically secure random secret key as a base64 string. * @param length The length of the secret key in bytes. Defaults to the configured secret key length. * @returns A base64-encoded string containing the secret key. * @throws {Error} If the length is invalid or key generation fails. */ public static generateBase64SecretKey(length: number = this.SECRET_KEY_LENGTH): string { return this.generate({ type: CRYPTO_TYPE.SECRET_KEY, outputFormat: OUTPUT_FORMAT.BASE64, length, }) as string; } /** * Generates a cryptographically secure random secret key as a Buffer. * @param length The length of the secret key in bytes. Defaults to the configured secret key length. * @returns A Buffer containing the secret key. * @throws {Error} If the length is invalid or key generation fails. */ public static generateBufferSecretKey(length: number = this.SECRET_KEY_LENGTH): Buffer { return this.generate({ type: CRYPTO_TYPE.SECRET_KEY, outputFormat: OUTPUT_FORMAT.BUFFER, length, }) as Buffer; } /** * Generates a hex-encoded cryptographically secure random value. * @param length The length in bytes. Defaults to 32 bytes. * @returns A hex-encoded string. * @throws {Error} If the length is invalid or generation fails. */ public static generateHexString(length: number = 32): string { return this.generate({ type: CRYPTO_TYPE.RANDOM, outputFormat: OUTPUT_FORMAT.HEX, length, }) as string; } /** * Securely wipes a Buffer by filling it with random data, then setting all bytes to 0. * This is a simple, but reasonable approach to securely erasing sensitive data. * @param buffer The Buffer to wipe */ public static secureWipe(buffer: Buffer): void { if (buffer && buffer.length > 0) { crypto.randomFillSync(buffer); buffer.fill(0); } } // PRIVATE METHODS /** * Core method to generate cryptographically secure random bytes with validation and error handling * @param length The length in bytes * @param methodName The calling method name for error context * @returns A Buffer containing cryptographically secure random bytes * @throws {Error} If the length is invalid or generation fails */ private static generateSecureBytes(length: number, methodName: string): Buffer { this.validateLength(length, methodName); try { return crypto.randomBytes(length); } catch (error) { ErrorHandler.captureError( error, methodName, `Failed to generate secure bytes of length ${length}`, ); throw error; } } /** * Checks if Web Crypto API is available in the current environment. * @returns {boolean} True if Web Crypto API is available, false otherwise. */ private static isWebCryptoAvailable(): boolean { return ( typeof globalThis !== "undefined" && typeof globalThis?.crypto !== "undefined" && typeof globalThis.crypto?.subtle !== "undefined" && typeof globalThis.crypto.getRandomValues === "function" ); } /** * Generates a cryptographically secure Uint8Array using Web Crypto API or Node.js crypto. * This method ensures compatibility with Web Crypto API operations. * @param length The length in bytes for the Uint8Array. * @returns A Uint8Array containing cryptographically secure random values. * @throws {Error} If the length is invalid or generation fails. */ private static generateSecureUint8Array(length: number): Uint8Array { this.validateLength(length, "generateSecureUint8Array"); try { if (this.isWebCryptoAvailable()) { const array = new Uint8Array(length); globalThis.crypto.getRandomValues(array); return array; } else { // Fallback to Node.js crypto for environments without Web Crypto return new Uint8Array(crypto.randomBytes(length)); } } catch (error) { ErrorHandler.captureError( error, "generateSecureUint8Array", `Failed to generate secure Uint8Array of length ${length}`, ); throw error; } } /** * Validates that generated values meet minimum entropy requirements. * This is a basic check - for production, consider more sophisticated entropy analysis. * @param buffer The buffer to validate * @returns True if the buffer appears to have sufficient entropy */ private static hasMinimumEntropy(buffer: Buffer): boolean { if (buffer.length === 0) return false; // Simple entropy check: ensure not all bytes are the same const firstByte = buffer[0]; return !buffer.every((byte) => byte === firstByte); } /** * Validates that a length parameter is within secure bounds * @param length The length to validate * @param methodName The calling method name for error context * @throws {Error} If the length is invalid */ private static validateLength(length: number, methodName: string): void { if (!Number.isInteger(length)) { ErrorHandler.logAndThrow(methodName, `Length must be an integer, got ${length}`); } if (length < this.MIN_VALIDATION_LIMIT) { ErrorHandler.logAndThrow( methodName, `Length must be at least ${this.MIN_VALIDATION_LIMIT} bytes for security, got ${length}`, ); } if (length > this.MAX_VALIDATION_LIMIT) { ErrorHandler.logAndThrow( methodName, `Length ${length} exceeds maximum reasonable length of ${this.MAX_VALIDATION_LIMIT} bytes`, ); } } }