UNPKG

fortify2-js

Version:

MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.

479 lines (475 loc) 19 kB
'use strict'; var types = require('../types.js'); /** * Entropy Augmentation Module * * This module provides methods to enhance the entropy (randomness) of * cryptographic operations by collecting additional entropy from various sources * and combining it with the system's built-in random number generator. * * This helps protect against weak or compromised random number generators * by adding multiple layers of entropy. */ /** * Entropy pool that collects and mixes entropy from multiple sources */ class EntropyPool { /** * Creates a new entropy pool * * @param poolSize - Size of the entropy pool in bytes * @param options - Entropy collection options */ constructor(poolSize = 1024, options = {}) { this.poolPosition = 0; this.reseedCounter = 0; this.lastReseed = 0; this.isInitialized = false; this.entropyCollected = 0; this.poolSize = poolSize; this.pool = new Uint8Array(poolSize); this.options = { useTiming: options.useTiming !== false, usePerformance: options.usePerformance !== false, useDevice: options.useDevice !== false, useNetwork: options.useNetwork || false, useInteraction: options.useInteraction || false, customSources: options.customSources || [], }; // Initialize the pool with system random data this.initializePool(); // Set up continuous entropy collection this.setupEntropyCollection(); } /** * Gets the singleton instance of the entropy pool * * @param poolSize - Size of the entropy pool in bytes * @param options - Entropy collection options * @returns The entropy pool instance */ static getInstance(poolSize, options) { if (!EntropyPool.instance) { EntropyPool.instance = new EntropyPool(poolSize, options); } return EntropyPool.instance; } /** * Initializes the entropy pool with system random data */ initializePool() { // Fill the pool with system random data if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") { crypto.getRandomValues(this.pool); } else if (typeof window !== "undefined" && typeof window.crypto !== "undefined" && typeof window.crypto.getRandomValues === "function") { window.crypto.getRandomValues(this.pool); } else { // Fallback to less secure random data for (let i = 0; i < this.poolSize; i++) { this.pool[i] = Math.floor(Math.random() * 256); } } this.isInitialized = true; this.entropyCollected = this.poolSize * 8; // Bits of entropy this.lastReseed = Date.now(); } /** * Sets up continuous entropy collection from various sources */ setupEntropyCollection() { // Collect timing entropy if (this.options.useTiming) { this.collectTimingEntropy(); // Set up periodic collection setInterval(() => this.collectTimingEntropy(), 100); } // Collect performance entropy if (this.options.usePerformance && typeof performance !== "undefined") { this.collectPerformanceEntropy(); // Set up periodic collection setInterval(() => this.collectPerformanceEntropy(), 500); } // Collect device entropy if (this.options.useDevice && typeof navigator !== "undefined") { this.collectDeviceEntropy(); } // Collect network entropy if (this.options.useNetwork && typeof navigator !== "undefined") { // Set up periodic collection setInterval(() => this.collectNetworkEntropy(), 2000); } // Collect user interaction entropy if (this.options.useInteraction && typeof document !== "undefined") { this.setupInteractionCollection(); } // Collect from custom sources if (this.options.customSources && this.options.customSources.length > 0) { for (const source of this.options.customSources) { try { const entropy = source(); this.addEntropy(entropy); } catch (e) { console.warn("Error collecting entropy from custom source:", e); } } } } /** * Collects entropy from timing variations */ collectTimingEntropy() { const buffer = new Uint8Array(8); const now = Date.now(); const highRes = typeof performance !== "undefined" ? performance.now() : 0; // Use the lower bits of the timestamps which have more entropy const timeDiff = now - this.lastReseed; const timeDiffHigh = Math.floor(timeDiff / 256); const timeDiffLow = timeDiff % 256; const highResInt = Math.floor(highRes * 1000); const highResHigh = Math.floor(highResInt / 256); const highResLow = highResInt % 256; buffer[0] = timeDiffLow; buffer[1] = timeDiffHigh; buffer[2] = highResLow; buffer[3] = highResHigh; // Add some CPU timing jitter let jitter = 0; const startTime = typeof performance !== "undefined" ? performance.now() : Date.now(); for (let i = 0; i < 10000; i++) { jitter = (jitter + i * 7) % 256; } const endTime = typeof performance !== "undefined" ? performance.now() : Date.now(); const jitterTime = endTime - startTime; buffer[4] = jitter & 0xff; buffer[5] = (jitterTime * 1000) & 0xff; buffer[6] = Date.now() & 0xff; buffer[7] = (Math.random() * 256) & 0xff; // Add to the pool with estimated entropy (conservative estimate) this.addEntropy(buffer, 8); // Assume 1 bit of entropy per byte } /** * Collects entropy from performance measurements */ collectPerformanceEntropy() { if (typeof performance === "undefined") return; try { // Get performance data const memory = performance.memory || {}; const buffer = new Uint8Array(8); // Mix in performance timing values using modern Performance API let value = 0; // Use performance.now() and other non-deprecated values value ^= Math.floor(performance.now()); // Use performance.timeOrigin if available if (performance.timeOrigin) { value ^= Math.floor(performance.timeOrigin); } // Use performance entries if available if (typeof performance.getEntriesByType === "function") { const navEntries = performance.getEntriesByType("navigation"); if (navEntries && navEntries.length > 0) { const nav = navEntries[0]; if (nav.connectEnd) value ^= Math.floor(nav.connectEnd); if (nav.responseStart) value ^= Math.floor(nav.responseStart); if (nav.loadEventEnd) value ^= Math.floor(nav.loadEventEnd); } } buffer[0] = value & 0xff; buffer[1] = (value >> 8) & 0xff; // Mix in memory values if available if (memory.usedJSHeapSize) { buffer[2] = memory.usedJSHeapSize & 0xff; buffer[3] = (memory.usedJSHeapSize >> 8) & 0xff; } if (memory.totalJSHeapSize) { buffer[4] = memory.totalJSHeapSize & 0xff; buffer[5] = (memory.totalJSHeapSize >> 8) & 0xff; } // Add current time const now = Date.now(); buffer[6] = now & 0xff; buffer[7] = (now >> 8) & 0xff; // Add to the pool with estimated entropy (conservative estimate) this.addEntropy(buffer, 4); // Assume 0.5 bits of entropy per byte } catch (e) { console.warn("Error collecting performance entropy:", e); } } /** * Collects entropy from device information */ collectDeviceEntropy() { if (typeof navigator === "undefined") return; try { // Collect various device properties const properties = [ navigator.userAgent, navigator.language, navigator.languages?.join(","), // Use userAgentData instead of deprecated platform if available navigator.userAgentData?.platform || // Fallback to derived info from userAgent (navigator.userAgent.indexOf("Win") !== -1 ? "Windows" : navigator.userAgent.indexOf("Mac") !== -1 ? "MacOS" : navigator.userAgent.indexOf("Linux") !== -1 ? "Linux" : "Unknown"), navigator.hardwareConcurrency?.toString(), navigator.deviceMemory?.toString(), screen.width?.toString(), screen.height?.toString(), screen.colorDepth?.toString(), screen.pixelDepth?.toString(), new Date().getTimezoneOffset().toString(), ] .filter(Boolean) .join("|"); // Hash the properties const buffer = new Uint8Array(16); let hash = 0; for (let i = 0; i < properties.length; i++) { hash = (hash << 5) - hash + properties.charCodeAt(i); hash = hash & hash; // Convert to 32-bit integer } // Spread the hash across the buffer for (let i = 0; i < 16; i += 4) { buffer[i] = (hash >> 24) & 0xff; buffer[i + 1] = (hash >> 16) & 0xff; buffer[i + 2] = (hash >> 8) & 0xff; buffer[i + 3] = hash & 0xff; // Evolve the hash hash = (hash << 5) - hash + i; } // Add to the pool with estimated entropy (conservative estimate) this.addEntropy(buffer, 16); // Assume 1 bit of entropy per byte } catch (e) { console.warn("Error collecting device entropy:", e); } } /** * Collects entropy from network information */ collectNetworkEntropy() { if (typeof navigator === "undefined") return; try { const connection = navigator.connection; if (connection) { const buffer = new Uint8Array(4); // Mix in connection properties let value = 0; if (connection.downlink) value ^= Math.floor(connection.downlink * 1000); if (connection.rtt) value ^= connection.rtt; if (connection.effectiveType) { const effectiveType = connection.effectiveType; const typeMap = { "slow-2g": 1, "2g": 2, "3g": 3, "4g": 4, "5g": 5, }; const typeValue = typeMap[effectiveType] || 0; value ^= typeValue << 16; } buffer[0] = value & 0xff; buffer[1] = (value >> 8) & 0xff; buffer[2] = (value >> 16) & 0xff; buffer[3] = (value >> 24) & 0xff; // Add to the pool with estimated entropy (conservative estimate) this.addEntropy(buffer, 2); // Assume 0.5 bits of entropy per byte } } catch (e) { console.warn("Error collecting network entropy:", e); } } /** * Sets up collection of entropy from user interactions */ setupInteractionCollection() { if (typeof document === "undefined") return; try { // Mouse movement entropy document.addEventListener("mousemove", (event) => { const buffer = new Uint8Array(6); buffer[0] = event.clientX & 0xff; buffer[1] = (event.clientX >> 8) & 0xff; buffer[2] = event.clientY & 0xff; buffer[3] = (event.clientY >> 8) & 0xff; buffer[4] = event.timeStamp & 0xff; buffer[5] = (event.timeStamp >> 8) & 0xff; this.addEntropy(buffer, 2); // Assume 0.33 bits of entropy per byte }); // Keyboard entropy document.addEventListener("keypress", (event) => { const buffer = new Uint8Array(4); buffer[0] = (event.key ? event.key.charCodeAt(0) : 0) & 0xff; buffer[1] = event.timeStamp & 0xff; buffer[2] = (event.timeStamp >> 8) & 0xff; buffer[3] = Date.now() & 0xff; this.addEntropy(buffer, 2); // Assume 0.5 bits of entropy per byte }); // Touch entropy document.addEventListener("touchmove", (event) => { if (event.touches.length > 0) { const touch = event.touches[0]; const buffer = new Uint8Array(6); buffer[0] = touch.clientX & 0xff; buffer[1] = (touch.clientX >> 8) & 0xff; buffer[2] = touch.clientY & 0xff; buffer[3] = (touch.clientY >> 8) & 0xff; buffer[4] = event.timeStamp & 0xff; buffer[5] = (event.timeStamp >> 8) & 0xff; this.addEntropy(buffer, 2); // Assume 0.33 bits of entropy per byte } }); } catch (e) { console.warn("Error setting up interaction entropy collection:", e); } } /** * Adds entropy to the pool * * @param data - Entropy data to add * @param estimatedEntropy - Estimated entropy in bits (conservative) */ addEntropy(data, estimatedEntropy = 0) { if (!this.isInitialized) { this.initializePool(); } // Mix the entropy into the pool using a simple mixing function for (let i = 0; i < data.length; i++) { const poolIndex = (this.poolPosition + i) % this.poolSize; // Mix using XOR and rotation this.pool[poolIndex] ^= data[i]; // Additional mixing const prev = (poolIndex + this.poolSize - 1) % this.poolSize; const next = (poolIndex + 1) % this.poolSize; this.pool[prev] = (this.pool[prev] + this.pool[poolIndex]) & 0xff; this.pool[next] = (this.pool[next] ^ this.pool[poolIndex]) & 0xff; } // Update pool position this.poolPosition = (this.poolPosition + data.length) % this.poolSize; // Track entropy collection this.entropyCollected += estimatedEntropy; this.reseedCounter++; // Periodically remix the entire pool if (this.reseedCounter >= 100 || Date.now() - this.lastReseed > 60000) { this.remixPool(); } } /** * Remixes the entire entropy pool to distribute entropy */ remixPool() { // Simple mixing function to distribute entropy throughout the pool for (let round = 0; round < 3; round++) { for (let i = 0; i < this.poolSize; i++) { const prev = (i + this.poolSize - 1) % this.poolSize; const next = (i + 1) % this.poolSize; this.pool[i] = (this.pool[i] ^ this.pool[prev] ^ this.pool[next]) & 0xff; this.pool[i] = ((this.pool[i] << 1) | (this.pool[i] >> 7)) & 0xff; // Rotate left by 1 } } this.reseedCounter = 0; this.lastReseed = Date.now(); } /** * Gets random bytes from the entropy pool * * @param length - Number of bytes to get * @returns Random bytes */ getRandomBytes(length) { if (!this.isInitialized) { this.initializePool(); } // Ensure we have enough entropy if (this.entropyCollected < length * 8) { console.warn(`Entropy pool has only ${this.entropyCollected} bits, but ${length * 8} bits requested`); } // Create result buffer const result = new Uint8Array(length); // Get system random bytes let systemRandom = null; if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") { systemRandom = new Uint8Array(length); crypto.getRandomValues(systemRandom); } else if (typeof window !== "undefined" && typeof window.crypto !== "undefined" && typeof window.crypto.getRandomValues === "function") { systemRandom = new Uint8Array(length); window.crypto.getRandomValues(systemRandom); } // Mix pool entropy with system entropy for (let i = 0; i < length; i++) { // Get a byte from the pool const poolIndex = (this.poolPosition + i) % this.poolSize; let value = this.pool[poolIndex]; // Mix with system random if available if (systemRandom) { value ^= systemRandom[i]; } else { // Fallback to Math.random() value ^= Math.floor(Math.random() * 256); } result[i] = value; // Update the pool (feedback) this.pool[poolIndex] = (this.pool[poolIndex] ^ result[i] ^ i) & 0xff; } // Update pool position this.poolPosition = (this.poolPosition + length) % this.poolSize; // Deduct used entropy this.entropyCollected = Math.max(0, this.entropyCollected - length * 4); // Conservative estimate // Remix if we've used a lot of entropy if (this.entropyCollected < this.poolSize * 4) { this.remixPool(); } return result; } /** * Gets the current entropy source * * @returns The current entropy source */ getEntropySource() { return types.EntropySource.CUSTOM; } /** * Gets the estimated amount of entropy collected * * @returns Estimated entropy in bits */ getEstimatedEntropy() { return this.entropyCollected; } } exports.EntropyPool = EntropyPool; //# sourceMappingURL=entropy-augmentation.js.map