fix: prevent encryption key loss and IndexedDB connection errors
Some checks failed
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled

- Disable timer-based key rotation for Double Ratchet mode
- Auto-reinitialize encryption keys when missing but ECDH available
- Preserve active keys during periodic cleanup in ratchet sessions
- Fix IndexedDB "database closing" errors with connection checking
- Add individual transactions per queue item to prevent race conditions
This commit is contained in:
lockbitchat
2025-11-04 13:14:24 -04:00
parent 207e51361c
commit 79c8d08782
12 changed files with 315 additions and 67 deletions

60
dist/app-boot.js vendored
View File

@@ -4724,7 +4724,7 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
};
this.onFileReceived = null;
this.onFileError = null;
this.keyRotationInterval = _EnhancedSecureWebRTCManager.TIMEOUTS.KEY_ROTATION_INTERVAL;
this.keyRotationInterval = null;
this.lastKeyRotation = Date.now();
this.currentKeyVersion = 0;
this.keyVersions = /* @__PURE__ */ new Map();
@@ -4761,6 +4761,7 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
packetPadding: this._config.packetPadding.enabled,
antiFingerprinting: this._config.antiFingerprinting.enabled
});
this.sessionMode = "ratchet";
this._hardenDebugModeReferences();
this._initializeUnifiedScheduler();
this._syncSecurityFeaturesWithTariff();
@@ -5357,9 +5358,6 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
if (this._keyStorageStats.activeKeys > 10) {
this._secureLog("warn", "\u26A0\uFE0F High number of active keys detected. Consider rotation.");
}
if (Date.now() - (this._keyStorageStats.lastRotation || 0) > 36e5) {
this._rotateKeys();
}
}
/**
* Send heartbeat message (called by unified scheduler)
@@ -6791,7 +6789,12 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
async _performPeriodicMemoryCleanup() {
try {
this._secureMemoryManager.isCleaning = true;
this._secureCleanupCryptographicMaterials();
const shouldPreserveActiveKeys = this.sessionMode === "ratchet" && this.isConnected && this.dataChannel && this.dataChannel.readyState === "open";
if (shouldPreserveActiveKeys) {
this._secureLog("debug", "\u{1F9F9} Skipping crypto key wipe during periodic cleanup (ratchet mode, active connection)");
} else {
this._secureCleanupCryptographicMaterials();
}
if (this.messageQueue && this.messageQueue.length > 100) {
const excessMessages = this.messageQueue.splice(0, this.messageQueue.length - 50);
excessMessages.forEach((message, index) => {
@@ -7296,6 +7299,14 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
}
const normalizedReceived = receivedFingerprint.toLowerCase().replace(/:/g, "");
const normalizedExpected = expectedFingerprint.toLowerCase().replace(/:/g, "");
if (this.sessionMode === "ratchet" && normalizedExpected === normalizedReceived) {
this._secureLog("info", "Same fingerprint detected \u2014 skip MITM warning (ratchet mode)", {
context,
timestamp: Date.now()
});
this.isVerified = true;
return true;
}
if (normalizedReceived !== normalizedExpected) {
this._secureLog("error", "DTLS fingerprint mismatch - possible MITM attack", {
context,
@@ -7664,6 +7675,38 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
}
return hasAllKeys;
}
/**
* Attempt to reinitialize encryption keys if missing
* Uses existing ECDH key pair, peer public key, and session salt
* Returns true if keys were (re)initialized successfully
*/
async _tryReinitializeEncryptionKeys() {
try {
if (this.encryptionKey && this.macKey && this.metadataKey) {
return true;
}
const hasECDH = !!(this.ecdhKeyPair?.privateKey && (this.peerPublicKey || this.peerECDHPublicKey));
const peerPublicKey = this.peerPublicKey || this.peerECDHPublicKey;
if (!hasECDH || !peerPublicKey || !this.sessionSalt) {
return false;
}
const derivedKeys = await window.EnhancedSecureCryptoUtils.deriveSharedKeys(
this.ecdhKeyPair.privateKey,
peerPublicKey,
this.sessionSalt
);
await this._setEncryptionKeys(
derivedKeys.messageKey,
derivedKeys.macKey,
derivedKeys.metadataKey,
derivedKeys.fingerprint
);
return !!(this.encryptionKey && this.macKey && this.metadataKey);
} catch (error) {
this._secureLog("error", "Failed to reinitialize encryption keys", { error: error.message });
return false;
}
}
/**
* Checks whether a message is a file-transfer message
* @param {string|object} data - message payload
@@ -9261,6 +9304,9 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
throw new Error("Data channel not ready");
}
try {
if (!(this.encryptionKey && this.macKey && this.metadataKey)) {
await this._tryReinitializeEncryptionKeys();
}
this._secureLog("debug", "sendMessage called", {
hasDataChannel: !!this.dataChannel,
dataChannelReady: this.dataChannel?.readyState === "open",
@@ -15265,7 +15311,7 @@ Right-click or Ctrl+click to disconnect`,
React.createElement("p", {
key: "subtitle",
className: "text-xs sm:text-sm text-muted hidden sm:block"
}, "End-to-end freedom v4.4.99")
}, "End-to-end freedom v4.5.22")
])
]),
// Status and Controls - Responsive
@@ -16020,7 +16066,7 @@ function Roadmap() {
},
// current and future phases
{
version: "v4.4.99",
version: "v4.5.22",
title: "Enhanced Security Edition",
status: "current",
date: "Now",

File diff suppressed because one or more lines are too long

2
dist/app.js vendored
View File

@@ -1688,7 +1688,7 @@ var EnhancedSecureP2PChat = () => {
} catch (error) {
}
}
handleMessage(" SecureBit.chat Enhanced Security Edition v4.4.99 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.", "system");
handleMessage(" SecureBit.chat Enhanced Security Edition v4.5.22 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.", "system");
const handleBeforeUnload = (event) => {
if (event.type === "beforeunload" && !isTabSwitching) {
if (webrtcManagerRef2.current && webrtcManagerRef2.current.isConnected()) {

2
dist/app.js.map vendored

File diff suppressed because one or more lines are too long