{"version":3,"file":"crypto-utils.mjs","sources":["../../../src/lib/utils/crypto-utils.ts"],"sourcesContent":["/**\n * @fileoverview Shared Cryptographic Utilities\n * @module @/lib/utils/crypto-utils\n *\n * Centralized cryptographic utilities for the Harbor React Framework.\n * This module consolidates previously duplicated crypto functions from:\n * - security/csrf-protection.ts\n * - security/csp-manager.ts\n * - security/secure-storage.ts\n *\n * All cryptographic operations use the Web Crypto API for maximum security.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API\n * @see https://owasp.org/www-project-web-security-testing-guide/\n */\n\n// ============================================================================\n// Random Bytes Generation\n// ============================================================================\n\n/**\n * Generate cryptographically secure random bytes\n *\n * Uses crypto.getRandomValues() which provides cryptographically strong\n * random values suitable for key generation and token creation.\n *\n * @param length - Number of random bytes to generate\n * @returns Uint8Array of random bytes\n *\n * @example\n * const bytes = getRandomBytes(32); // 256 bits of entropy\n */\nexport function getRandomBytes(length: number): Uint8Array {\n  if (length <= 0) {\n    throw new Error('Length must be a positive integer');\n  }\n  const bytes = new Uint8Array(length);\n  crypto.getRandomValues(bytes);\n  return bytes;\n}\n\n/**\n * Generate a random hex string of specified byte length\n *\n * @param byteLength - Number of random bytes (output will be 2x characters)\n * @returns Hex-encoded random string\n *\n * @example\n * const hex = generateRandomHex(16); // 32-character hex string\n */\nexport function generateRandomHex(byteLength: number): string {\n  const bytes = getRandomBytes(byteLength);\n  return bytesToHex(bytes);\n}\n\n/**\n * Generate a random base64url-encoded string of specified byte length\n *\n * @param byteLength - Number of random bytes\n * @returns Base64url-encoded random string\n *\n * @example\n * const token = generateRandomBase64Url(32); // 43-character base64url string\n */\nexport function generateRandomBase64Url(byteLength: number): string {\n  const bytes = getRandomBytes(byteLength);\n  return bytesToBase64Url(bytes);\n}\n\n// ============================================================================\n// Encoding Utilities\n// ============================================================================\n\n/**\n * Convert bytes to hexadecimal string\n *\n * @param bytes - Uint8Array to convert\n * @returns Lowercase hex-encoded string\n *\n * @example\n * bytesToHex(new Uint8Array([255, 0, 128])) // \"ff0080\"\n */\nexport function bytesToHex(bytes: Uint8Array): string {\n  return Array.from(bytes)\n    .map((b) => b.toString(16).padStart(2, '0'))\n    .join('');\n}\n\n/**\n * Convert hexadecimal string to bytes\n *\n * @param hex - Hex-encoded string\n * @returns Uint8Array of bytes\n * @throws Error if hex string is invalid\n *\n * @example\n * hexToBytes(\"ff0080\") // Uint8Array([255, 0, 128])\n */\nexport function hexToBytes(hex: string): Uint8Array {\n  if (hex.length % 2 !== 0) {\n    throw new Error('Hex string must have even length');\n  }\n  if (!/^[0-9a-fA-F]*$/.test(hex)) {\n    throw new Error('Invalid hex string');\n  }\n  const bytes = new Uint8Array(hex.length / 2);\n  for (let i = 0; i < bytes.length; i++) {\n    bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n  }\n  return bytes;\n}\n\n/**\n * Convert bytes to standard base64 string\n *\n * @param bytes - Uint8Array to convert\n * @returns Base64-encoded string\n *\n * @example\n * bytesToBase64(new Uint8Array([72, 101, 108, 108, 111])) // \"SGVsbG8=\"\n */\nexport function bytesToBase64(bytes: Uint8Array): string {\n  const binString = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');\n  return btoa(binString);\n}\n\n/**\n * Convert base64 string to bytes\n *\n * @param base64 - Base64-encoded string\n * @returns Uint8Array of bytes\n * @throws Error if base64 string is invalid\n *\n * @example\n * base64ToBytes(\"SGVsbG8=\") // Uint8Array([72, 101, 108, 108, 111])\n */\nexport function base64ToBytes(base64: string): Uint8Array {\n  const binString = atob(base64);\n  return Uint8Array.from(binString, (char) => char.charCodeAt(0));\n}\n\n/**\n * Convert bytes to URL-safe base64 string (base64url)\n *\n * URL-safe base64 replaces + with -, / with _, and removes padding.\n * Safe for use in URLs, cookies, and HTML attributes.\n *\n * @param bytes - Uint8Array to convert\n * @returns Base64url-encoded string (no padding)\n *\n * @example\n * bytesToBase64Url(new Uint8Array([255, 255])) // \"__8\"\n */\nexport function bytesToBase64Url(bytes: Uint8Array): string {\n  const base64 = bytesToBase64(bytes);\n  return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Convert URL-safe base64 string to bytes\n *\n * @param base64url - Base64url-encoded string\n * @returns Uint8Array of bytes\n *\n * @example\n * base64UrlToBytes(\"__8\") // Uint8Array([255, 255])\n */\nexport function base64UrlToBytes(base64url: string): Uint8Array {\n  // Restore standard base64 format\n  let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');\n  // Add padding if needed\n  const padding = (4 - (base64.length % 4)) % 4;\n  base64 += '='.repeat(padding);\n  return base64ToBytes(base64);\n}\n\n// ============================================================================\n// Secure Comparison\n// ============================================================================\n\n/**\n * Constant-time string comparison to prevent timing attacks\n *\n * Timing attacks can reveal secret values by measuring comparison time.\n * This function always takes the same amount of time regardless of\n * where strings differ.\n *\n * @param a - First string to compare\n * @param b - Second string to compare\n * @returns true if strings are equal, false otherwise\n *\n * @example\n * constantTimeCompare(userToken, storedToken)\n */\nexport function constantTimeCompare(a: string, b: string): boolean {\n  if (a.length !== b.length) {\n    // Still perform comparison to maintain constant time\n    // Use a dummy comparison of same length\n    const dummy = 'x'.repeat(Math.max(a.length, b.length));\n    let dummyResult = 0;\n    for (let i = 0; i < dummy.length; i++) {\n      dummyResult |= dummy.charCodeAt(i) ^ dummy.charCodeAt(i);\n    }\n    // Suppress unused variable warning\n    void dummyResult;\n    return false;\n  }\n\n  let result = 0;\n  for (let i = 0; i < a.length; i++) {\n    result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n  }\n  return result === 0;\n}\n\n/**\n * Constant-time byte array comparison\n *\n * @param a - First byte array to compare\n * @param b - Second byte array to compare\n * @returns true if arrays are equal, false otherwise\n */\nexport function constantTimeCompareBytes(a: Uint8Array, b: Uint8Array): boolean {\n  if (a.length !== b.length) {\n    return false;\n  }\n\n  let result = 0;\n  for (let i = 0; i < a.length; i++) {\n    result |= (a[i] ?? 0) ^ (b[i] ?? 0);\n  }\n  return result === 0;\n}\n\n// ============================================================================\n// Hashing Utilities\n// ============================================================================\n\n/**\n * Compute SHA-256 hash of data\n *\n * @param data - String or bytes to hash\n * @returns Promise resolving to hex-encoded hash\n *\n * @example\n * const hash = await sha256(\"hello world\");\n */\nexport async function sha256(data: string | Uint8Array): Promise<string> {\n  const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;\n  const buffer = bytes.buffer instanceof ArrayBuffer ? bytes.buffer : new Uint8Array(bytes).buffer;\n  const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);\n  return bytesToHex(new Uint8Array(hashBuffer));\n}\n\n/**\n * Compute SHA-512 hash of data\n *\n * @param data - String or bytes to hash\n * @returns Promise resolving to hex-encoded hash\n */\nexport async function sha512(data: string | Uint8Array): Promise<string> {\n  const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;\n  const buffer = bytes.buffer instanceof ArrayBuffer ? bytes.buffer : new Uint8Array(bytes).buffer;\n  const hashBuffer = await crypto.subtle.digest('SHA-512', buffer);\n  return bytesToHex(new Uint8Array(hashBuffer));\n}\n\n// ============================================================================\n// Token Generation\n// ============================================================================\n\n/**\n * Generate a cryptographically secure token\n *\n * Default generates 32 bytes (256 bits) of entropy, base64url encoded.\n *\n * @param byteLength - Number of random bytes (default: 32)\n * @returns Secure random token string\n *\n * @example\n * const csrfToken = generateSecureToken();\n * const sessionId = generateSecureToken(64);\n */\nexport function generateSecureToken(byteLength: number = 32): string {\n  return generateRandomBase64Url(byteLength);\n}\n\n/**\n * Generate a nonce for Content Security Policy\n *\n * Generates a 16-byte (128-bit) base64-encoded nonce suitable for CSP.\n *\n * @returns Base64-encoded nonce string\n *\n * @example\n * const nonce = generateCSPNonce();\n * // Use in CSP header: script-src 'nonce-{nonce}'\n */\nexport function generateCSPNonce(): string {\n  const bytes = getRandomBytes(16);\n  return bytesToBase64(bytes);\n}\n\n// ============================================================================\n// Key Derivation\n// ============================================================================\n\n/**\n * Derive a key from a password using PBKDF2\n *\n * Uses PBKDF2-SHA256 with configurable iterations for password-based\n * key derivation. Suitable for encrypting user data with user passwords.\n *\n * @param password - User password\n * @param salt - Salt bytes (should be random per-user)\n * @param iterations - Number of PBKDF2 iterations (default: 100000)\n * @param keyLength - Output key length in bytes (default: 32)\n * @returns Promise resolving to derived key bytes\n *\n * @example\n * const salt = getRandomBytes(16);\n * const key = await deriveKey(password, salt);\n */\nexport async function deriveKey(\n  password: string,\n  salt: Uint8Array,\n  iterations: number = 100000,\n  keyLength: number = 32\n): Promise<Uint8Array> {\n  const encoder = new TextEncoder();\n  const passwordKey = await crypto.subtle.importKey(\n    'raw',\n    encoder.encode(password),\n    'PBKDF2',\n    false,\n    ['deriveBits']\n  );\n\n  const saltBuffer = salt.buffer instanceof ArrayBuffer ? salt.buffer : new Uint8Array(salt).buffer;\n  const derivedBits = await crypto.subtle.deriveBits(\n    {\n      name: 'PBKDF2',\n      hash: 'SHA-256',\n      salt: saltBuffer,\n      iterations,\n    },\n    passwordKey,\n    keyLength * 8\n  );\n\n  return new Uint8Array(derivedBits);\n}\n\n// ============================================================================\n// Type Exports\n// ============================================================================\n\nexport type {};\n"],"names":["getRandomBytes","length","bytes","generateRandomHex","byteLength","bytesToHex","generateRandomBase64Url","bytesToBase64Url","b","hexToBytes","hex","i","bytesToBase64","binString","byte","base64ToBytes","base64","char","base64UrlToBytes","base64url","padding","constantTimeCompare","a","result","constantTimeCompareBytes","sha256","data","buffer","hashBuffer","sha512","generateSecureToken","generateCSPNonce","deriveKey","password","salt","iterations","keyLength","encoder","passwordKey","saltBuffer","derivedBits"],"mappings":"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"}