security: fix critical vulnerabilities in crypto module

- Remove insecure key import backdoor
- Strengthen password generation (32 chars + special chars)
- Implement constant-time comparisons to prevent timing attacks
- Fix race conditions in rate limiting with atomic operations
- Add input validation and enhanced error handling

BREAKING CHANGE: Remove allowInsecureImport option - all signed packages
now require mandatory signature verification for security.
This commit is contained in:
lockbitchat
2025-08-26 19:44:34 -04:00
parent e7c6dfc3b3
commit 1e270fb4b8

View File

@@ -187,13 +187,14 @@ class EnhancedSecureCryptoUtils {
// Generate secure password for data exchange // Generate secure password for data exchange
static generateSecurePassword() { static generateSecurePassword() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?';
const randomValues = new Uint32Array(16); const length = 32;
const randomValues = new Uint32Array(length);
crypto.getRandomValues(randomValues); crypto.getRandomValues(randomValues);
let password = ''; let password = '';
for (let i = 0; i < 16; i++) { for (let i = 0; i < length; i++) {
password += chars[randomValues[i] % chars.length]; password += chars[randomValues[i] % chars.length];
} }
return password; return password;
@@ -609,10 +610,13 @@ class EnhancedSecureCryptoUtils {
static async verifyRateLimiting(securityManager) { static async verifyRateLimiting(securityManager) {
try { try {
// Check if rate limiting is active const testId = 'test_' + Date.now();
const canProceed = await EnhancedSecureCryptoUtils.rateLimiter.checkMessageRate(testId, 1, 60000);
return securityManager.rateLimiterId && return securityManager.rateLimiterId &&
EnhancedSecureCryptoUtils.rateLimiter && EnhancedSecureCryptoUtils.rateLimiter &&
typeof EnhancedSecureCryptoUtils.rateLimiter.checkMessageRate === 'function'; typeof EnhancedSecureCryptoUtils.rateLimiter.checkMessageRate === 'function' &&
canProceed === true;
} catch (error) { } catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Rate limiting verification failed', { error: error.message }); EnhancedSecureCryptoUtils.secureLog.log('error', 'Rate limiting verification failed', { error: error.message });
return false; return false;
@@ -636,12 +640,27 @@ class EnhancedSecureCryptoUtils {
// Rate limiting implementation // Rate limiting implementation
static rateLimiter = { static rateLimiter = {
messages: new Map(), messages: new Map(),
connections: new Map(), connections: new Map(),
locks: new Map(),
checkMessageRate(identifier, limit = 60, windowMs = 60000) { async checkMessageRate(identifier, limit = 60, windowMs = 60000) {
if (typeof identifier !== 'string' || identifier.length > 256) {
return false;
}
const key = `msg_${identifier}`;
if (this.locks.has(key)) {
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 10) + 5));
return this.checkMessageRate(identifier, limit, windowMs);
}
this.locks.set(key, true);
try {
const now = Date.now(); const now = Date.now();
const key = `msg_${identifier}`;
if (!this.messages.has(key)) { if (!this.messages.has(key)) {
this.messages.set(key, []); this.messages.set(key, []);
@@ -649,21 +668,36 @@ class EnhancedSecureCryptoUtils {
const timestamps = this.messages.get(key); const timestamps = this.messages.get(key);
// Remove old timestamps
const validTimestamps = timestamps.filter(ts => now - ts < windowMs); const validTimestamps = timestamps.filter(ts => now - ts < windowMs);
this.messages.set(key, validTimestamps);
if (validTimestamps.length >= limit) { if (validTimestamps.length >= limit) {
return false; // Rate limit exceeded return false;
} }
validTimestamps.push(now); validTimestamps.push(now);
this.messages.set(key, validTimestamps);
return true; return true;
}, } finally {
this.locks.delete(key);
}
},
checkConnectionRate(identifier, limit = 5, windowMs = 300000) { async checkConnectionRate(identifier, limit = 5, windowMs = 300000) {
if (typeof identifier !== 'string' || identifier.length > 256) {
return false;
}
const key = `conn_${identifier}`;
if (this.locks.has(key)) {
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 10) + 5));
return this.checkConnectionRate(identifier, limit, windowMs);
}
this.locks.set(key, true);
try {
const now = Date.now(); const now = Date.now();
const key = `conn_${identifier}`;
if (!this.connections.has(key)) { if (!this.connections.has(key)) {
this.connections.set(key, []); this.connections.set(key, []);
@@ -671,39 +705,53 @@ class EnhancedSecureCryptoUtils {
const timestamps = this.connections.get(key); const timestamps = this.connections.get(key);
const validTimestamps = timestamps.filter(ts => now - ts < windowMs); const validTimestamps = timestamps.filter(ts => now - ts < windowMs);
this.connections.set(key, validTimestamps);
if (validTimestamps.length >= limit) { if (validTimestamps.length >= limit) {
return false; return false;
} }
validTimestamps.push(now); validTimestamps.push(now);
this.connections.set(key, validTimestamps);
return true; return true;
}, } finally {
this.locks.delete(key);
}
},
cleanup() { cleanup() {
const now = Date.now(); const now = Date.now();
const maxAge = 3600000; // 1 hour const maxAge = 3600000;
for (const [key, timestamps] of this.messages.entries()) { for (const [key, timestamps] of this.messages.entries()) {
const valid = timestamps.filter(ts => now - ts < maxAge); if (this.locks.has(key)) continue;
if (valid.length === 0) {
this.messages.delete(key);
} else {
this.messages.set(key, valid);
}
}
for (const [key, timestamps] of this.connections.entries()) { const valid = timestamps.filter(ts => now - ts < maxAge);
const valid = timestamps.filter(ts => now - ts < maxAge); if (valid.length === 0) {
if (valid.length === 0) { this.messages.delete(key);
this.connections.delete(key); } else {
} else { this.messages.set(key, valid);
this.connections.set(key, valid);
}
} }
} }
};
for (const [key, timestamps] of this.connections.entries()) {
if (this.locks.has(key)) continue;
const valid = timestamps.filter(ts => now - ts < maxAge);
if (valid.length === 0) {
this.connections.delete(key);
} else {
this.connections.set(key, valid);
}
}
for (const lockKey of this.locks.keys()) {
const keyTimestamp = parseInt(lockKey.split('_').pop()) || 0;
if (now - keyTimestamp > 30000) {
this.locks.delete(lockKey);
}
}
}
};
// Secure logging without data leaks // Secure logging without data leaks
static secureLog = { static secureLog = {
@@ -1040,7 +1088,7 @@ class EnhancedSecureCryptoUtils {
throw new Error('Missing required fields in signed package'); throw new Error('Missing required fields in signed package');
} }
if (keyType !== expectedKeyType) { if (!EnhancedSecureCryptoUtils.constantTimeCompare(keyType, expectedKeyType)) {
throw new Error(`Key type mismatch: expected ${expectedKeyType}, got ${keyType}`); throw new Error(`Key type mismatch: expected ${expectedKeyType}, got ${keyType}`);
} }
@@ -1240,26 +1288,6 @@ class EnhancedSecureCryptoUtils {
securityRisk: 'HIGH - Potential MITM attack vector' securityRisk: 'HIGH - Potential MITM attack vector'
}); });
// Check if insecure mode is explicitly allowed (for debugging/testing only)
if (options.allowInsecureImport === true && options.explicitWarningAcknowledged === true) {
EnhancedSecureCryptoUtils.secureLog.log('warn', 'INSECURE MODE: Importing signed package without verification (DANGEROUS)', {
keyType: signedPackage.keyType,
securityLevel: 'COMPROMISED',
recommendation: 'This mode should NEVER be used in production'
});
// Continue with insecure import but mark the key as untrusted
const key = await EnhancedSecureCryptoUtils._importKeyUnsafe(signedPackage);
// Use WeakMap to store metadata
EnhancedSecureCryptoUtils._keyMetadata.set(key, {
trusted: false,
verificationStatus: 'UNVERIFIED_DANGEROUS',
verificationTimestamp: Date.now()
});
return key;
}
// REJECT the signed package if no verifying key provided // REJECT the signed package if no verifying key provided
throw new Error('CRITICAL SECURITY ERROR: Signed key package received without a verification key. ' + throw new Error('CRITICAL SECURITY ERROR: Signed key package received without a verification key. ' +
@@ -1713,9 +1741,9 @@ class EnhancedSecureCryptoUtils {
} }
// Verify challenge matches // Verify challenge matches
if (JSON.stringify(proof.challenge) !== JSON.stringify(challenge.challenge) || if (!EnhancedSecureCryptoUtils.constantTimeCompareArrays(proof.challenge, challenge.challenge) ||
proof.timestamp !== challenge.timestamp || proof.timestamp !== challenge.timestamp ||
JSON.stringify(proof.nonce) !== JSON.stringify(challenge.nonce)) { !EnhancedSecureCryptoUtils.constantTimeCompareArrays(proof.nonce, challenge.nonce)) {
throw new Error('Challenge mismatch - possible replay attack'); throw new Error('Challenge mismatch - possible replay attack');
} }
@@ -1727,7 +1755,7 @@ class EnhancedSecureCryptoUtils {
// Verify public key hash // Verify public key hash
const expectedHash = await EnhancedSecureCryptoUtils.hashPublicKey(publicKey); const expectedHash = await EnhancedSecureCryptoUtils.hashPublicKey(publicKey);
if (proof.publicKeyHash !== expectedHash) { if (!EnhancedSecureCryptoUtils.constantTimeCompare(proof.publicKeyHash, expectedHash)) {
throw new Error('Public key hash mismatch'); throw new Error('Public key hash mismatch');
} }
@@ -2009,6 +2037,48 @@ class EnhancedSecureCryptoUtils {
throw new Error('Failed to compute the key fingerprint'); throw new Error('Failed to compute the key fingerprint');
} }
} }
static constantTimeCompare(a, b) {
const strA = typeof a === 'string' ? a : JSON.stringify(a);
const strB = typeof b === 'string' ? b : JSON.stringify(b);
if (strA.length !== strB.length) {
let dummy = 0;
for (let i = 0; i < Math.max(strA.length, strB.length); i++) {
dummy |= (strA.charCodeAt(i % strA.length) || 0) ^ (strB.charCodeAt(i % strB.length) || 0);
}
return false;
}
let result = 0;
for (let i = 0; i < strA.length; i++) {
result |= strA.charCodeAt(i) ^ strB.charCodeAt(i);
}
return result === 0;
}
static constantTimeCompareArrays(arr1, arr2) {
if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
return false;
}
if (arr1.length !== arr2.length) {
let dummy = 0;
const maxLen = Math.max(arr1.length, arr2.length);
for (let i = 0; i < maxLen; i++) {
dummy |= (arr1[i % arr1.length] || 0) ^ (arr2[i % arr2.length] || 0);
}
return false;
}
let result = 0;
for (let i = 0; i < arr1.length; i++) {
result |= arr1[i] ^ arr2[i];
}
return result === 0;
}
} }
export { EnhancedSecureCryptoUtils }; export { EnhancedSecureCryptoUtils };