feat(security): switch master key to non-extractable CryptoKey handle and remove direct access
This commit is contained in:
Vendored
+33
-79
@@ -2507,12 +2507,6 @@ var EnhancedSecureCryptoUtils = class _EnhancedSecureCryptoUtils {
|
|||||||
encoder.encode(payloadStr)
|
encoder.encode(payloadStr)
|
||||||
);
|
);
|
||||||
payload.mac = Array.from(new Uint8Array(mac));
|
payload.mac = Array.from(new Uint8Array(mac));
|
||||||
_EnhancedSecureCryptoUtils.secureLog.log("info", "Message encrypted with metadata protection", {
|
|
||||||
messageId,
|
|
||||||
sequenceNumber,
|
|
||||||
hasMetadataProtection: true,
|
|
||||||
hasPadding: true
|
|
||||||
});
|
|
||||||
return payload;
|
return payload;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
_EnhancedSecureCryptoUtils.secureLog.log("error", "Message encryption failed", {
|
_EnhancedSecureCryptoUtils.secureLog.log("error", "Message encryption failed", {
|
||||||
@@ -13781,11 +13775,10 @@ var SecureKeyStorage = class {
|
|||||||
/**
|
/**
|
||||||
* Get master key (with automatic unlock if needed)
|
* Get master key (with automatic unlock if needed)
|
||||||
*/
|
*/
|
||||||
async _getMasterKey() {
|
async _ensureMasterKeyUnlocked() {
|
||||||
if (!this._masterKeyManager.isUnlocked()) {
|
if (!this._masterKeyManager.isUnlocked()) {
|
||||||
await this._masterKeyManager.unlock();
|
await this._masterKeyManager.unlock();
|
||||||
}
|
}
|
||||||
return this._masterKeyManager.getMasterKey();
|
|
||||||
}
|
}
|
||||||
async storeKey(keyId, cryptoKey, metadata = {}) {
|
async storeKey(keyId, cryptoKey, metadata = {}) {
|
||||||
if (!(cryptoKey instanceof CryptoKey)) {
|
if (!(cryptoKey instanceof CryptoKey)) {
|
||||||
@@ -13854,27 +13847,18 @@ var SecureKeyStorage = class {
|
|||||||
const dataToEncrypt = typeof keyData === "object" ? JSON.stringify(keyData) : keyData;
|
const dataToEncrypt = typeof keyData === "object" ? JSON.stringify(keyData) : keyData;
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const data = encoder.encode(dataToEncrypt);
|
const data = encoder.encode(dataToEncrypt);
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
await this._ensureMasterKeyUnlocked();
|
||||||
const masterKey = await this._getMasterKey();
|
const { encryptedData, iv } = await this._masterKeyManager.encryptBytes(data);
|
||||||
const encryptedData = await crypto.subtle.encrypt(
|
|
||||||
{ name: "AES-GCM", iv },
|
|
||||||
masterKey,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
const result = new Uint8Array(iv.length + encryptedData.byteLength);
|
const result = new Uint8Array(iv.length + encryptedData.byteLength);
|
||||||
result.set(iv, 0);
|
result.set(iv, 0);
|
||||||
result.set(new Uint8Array(encryptedData), iv.length);
|
result.set(encryptedData, iv.length);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
async _decryptKeyData(encryptedData) {
|
async _decryptKeyData(encryptedData) {
|
||||||
const iv = encryptedData.slice(0, 12);
|
const iv = encryptedData.slice(0, 12);
|
||||||
const data = encryptedData.slice(12);
|
const data = encryptedData.slice(12);
|
||||||
const masterKey = await this._getMasterKey();
|
await this._ensureMasterKeyUnlocked();
|
||||||
const decryptedData = await crypto.subtle.decrypt(
|
const decryptedData = await this._masterKeyManager.decryptBytes(data, iv);
|
||||||
{ name: "AES-GCM", iv },
|
|
||||||
masterKey,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
const jsonString = decoder.decode(decryptedData);
|
const jsonString = decoder.decode(decryptedData);
|
||||||
try {
|
try {
|
||||||
@@ -14418,8 +14402,7 @@ var SecurePersistentKeyStorage = class {
|
|||||||
try {
|
try {
|
||||||
await this._ensureDBInitialized();
|
await this._ensureDBInitialized();
|
||||||
const jwkData = await crypto.subtle.exportKey("jwk", cryptoKey);
|
const jwkData = await crypto.subtle.exportKey("jwk", cryptoKey);
|
||||||
const masterKey = this._masterKeyManager.getMasterKey();
|
const { encryptedData, iv } = await this._encryptKeyData(jwkData);
|
||||||
const { encryptedData, iv } = await this._encryptKeyData(jwkData, masterKey);
|
|
||||||
await this._indexedDB.storeEncryptedKey(
|
await this._indexedDB.storeEncryptedKey(
|
||||||
keyId,
|
keyId,
|
||||||
encryptedData,
|
encryptedData,
|
||||||
@@ -14449,8 +14432,7 @@ var SecurePersistentKeyStorage = class {
|
|||||||
if (!keyRecord) {
|
if (!keyRecord) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const masterKey = this._masterKeyManager.getMasterKey();
|
const jwkData = await this._decryptKeyData(keyRecord.encryptedData, keyRecord.iv);
|
||||||
const jwkData = await this._decryptKeyData(keyRecord.encryptedData, keyRecord.iv, masterKey);
|
|
||||||
const restoredKey = await this._importAsNonExtractable(jwkData, keyRecord.algorithm, keyRecord.usages);
|
const restoredKey = await this._importAsNonExtractable(jwkData, keyRecord.algorithm, keyRecord.usages);
|
||||||
this._keyReferences.set(keyId, restoredKey);
|
this._keyReferences.set(keyId, restoredKey);
|
||||||
await this._indexedDB.updateKeyMetadata(keyId, { lastAccessed: Date.now() });
|
await this._indexedDB.updateKeyMetadata(keyId, { lastAccessed: Date.now() });
|
||||||
@@ -14499,29 +14481,18 @@ var SecurePersistentKeyStorage = class {
|
|||||||
/**
|
/**
|
||||||
* Encrypt key data using master key
|
* Encrypt key data using master key
|
||||||
*/
|
*/
|
||||||
async _encryptKeyData(jwkData, masterKey) {
|
async _encryptKeyData(jwkData) {
|
||||||
const jsonString = JSON.stringify(jwkData);
|
const jsonString = JSON.stringify(jwkData);
|
||||||
const data = new TextEncoder().encode(jsonString);
|
const data = new TextEncoder().encode(jsonString);
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
await this._ensureMasterKeyUnlocked();
|
||||||
const encryptedData = await crypto.subtle.encrypt(
|
return await this._masterKeyManager.encryptBytes(data);
|
||||||
{ name: "AES-GCM", iv },
|
|
||||||
masterKey,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
encryptedData: new Uint8Array(encryptedData),
|
|
||||||
iv
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Decrypt key data using master key
|
* Decrypt key data using master key
|
||||||
*/
|
*/
|
||||||
async _decryptKeyData(encryptedData, iv, masterKey) {
|
async _decryptKeyData(encryptedData, iv) {
|
||||||
const decryptedData = await crypto.subtle.decrypt(
|
await this._ensureMasterKeyUnlocked();
|
||||||
{ name: "AES-GCM", iv },
|
const decryptedData = await this._masterKeyManager.decryptBytes(encryptedData, iv);
|
||||||
masterKey,
|
|
||||||
encryptedData
|
|
||||||
);
|
|
||||||
const jsonString = new TextDecoder().decode(decryptedData);
|
const jsonString = new TextDecoder().decode(decryptedData);
|
||||||
return JSON.parse(jsonString);
|
return JSON.parse(jsonString);
|
||||||
}
|
}
|
||||||
@@ -14564,7 +14535,7 @@ var SecurePersistentKeyStorage = class {
|
|||||||
};
|
};
|
||||||
var SecureMasterKeyManager = class {
|
var SecureMasterKeyManager = class {
|
||||||
constructor(indexedDBWrapper = null) {
|
constructor(indexedDBWrapper = null) {
|
||||||
this._masterKey = null;
|
this._keyHandle = null;
|
||||||
this._isUnlocked = false;
|
this._isUnlocked = false;
|
||||||
this._sessionTimeout = null;
|
this._sessionTimeout = null;
|
||||||
this._lastActivity = null;
|
this._lastActivity = null;
|
||||||
@@ -14773,7 +14744,7 @@ var SecureMasterKeyManager = class {
|
|||||||
password = await this._requestPassword(false);
|
password = await this._requestPassword(false);
|
||||||
}
|
}
|
||||||
const salt = await this._getOrCreateSalt();
|
const salt = await this._getOrCreateSalt();
|
||||||
this._masterKey = await this._deriveKeyFromPassword(password, salt);
|
this._keyHandle = await this._deriveKeyFromPassword(password, salt);
|
||||||
this._isUnlocked = true;
|
this._isUnlocked = true;
|
||||||
this._lastActivity = Date.now();
|
this._lastActivity = Date.now();
|
||||||
this._startSessionTimer();
|
this._startSessionTimer();
|
||||||
@@ -14796,12 +14767,23 @@ var SecureMasterKeyManager = class {
|
|||||||
/**
|
/**
|
||||||
* Get master key (only if unlocked)
|
* Get master key (only if unlocked)
|
||||||
*/
|
*/
|
||||||
getMasterKey() {
|
// Prevent direct key access; provide operations only
|
||||||
if (!this._isUnlocked || !this._masterKey) {
|
async encryptBytes(plainBytes) {
|
||||||
|
if (!this._isUnlocked || !this._keyHandle) {
|
||||||
throw new Error("Master key is locked");
|
throw new Error("Master key is locked");
|
||||||
}
|
}
|
||||||
this._updateActivity();
|
this._updateActivity();
|
||||||
return this._masterKey;
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||||
|
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, this._keyHandle, plainBytes);
|
||||||
|
return { encryptedData: new Uint8Array(encrypted), iv };
|
||||||
|
}
|
||||||
|
async decryptBytes(encryptedBytes, iv) {
|
||||||
|
if (!this._isUnlocked || !this._keyHandle) {
|
||||||
|
throw new Error("Master key is locked");
|
||||||
|
}
|
||||||
|
this._updateActivity();
|
||||||
|
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, this._keyHandle, encryptedBytes);
|
||||||
|
return new Uint8Array(decrypted);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Check if master key is unlocked
|
* Check if master key is unlocked
|
||||||
@@ -14824,8 +14806,8 @@ var SecureMasterKeyManager = class {
|
|||||||
* Securely wipe master key from memory
|
* Securely wipe master key from memory
|
||||||
*/
|
*/
|
||||||
_secureWipeMasterKey() {
|
_secureWipeMasterKey() {
|
||||||
if (this._masterKey) {
|
if (this._keyHandle) {
|
||||||
this._masterKey = null;
|
this._keyHandle = null;
|
||||||
}
|
}
|
||||||
this._clearTimers();
|
this._clearTimers();
|
||||||
}
|
}
|
||||||
@@ -14896,10 +14878,8 @@ var EnhancedMinimalHeader = ({
|
|||||||
} else if (window.DEBUG_MODE) {
|
} else if (window.DEBUG_MODE) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(" Security calculation returned invalid data");
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(" Error in real security calculation:", error);
|
|
||||||
} finally {
|
} finally {
|
||||||
isUpdating = false;
|
isUpdating = false;
|
||||||
}
|
}
|
||||||
@@ -14940,10 +14920,8 @@ var EnhancedMinimalHeader = ({
|
|||||||
if (securityData && securityData.isRealData !== false) {
|
if (securityData && securityData.isRealData !== false) {
|
||||||
setRealSecurityLevel(securityData);
|
setRealSecurityLevel(securityData);
|
||||||
setLastSecurityUpdate(Date.now());
|
setLastSecurityUpdate(Date.now());
|
||||||
console.log("\u2705 Header security level force-updated");
|
|
||||||
}
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error("\u274C Force update failed:", error);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setLastSecurityUpdate(0);
|
setLastSecurityUpdate(0);
|
||||||
@@ -14971,9 +14949,6 @@ var EnhancedMinimalHeader = ({
|
|||||||
setSessionType("premium");
|
setSessionType("premium");
|
||||||
};
|
};
|
||||||
const handleConnectionCleaned = () => {
|
const handleConnectionCleaned = () => {
|
||||||
if (window.DEBUG_MODE) {
|
|
||||||
console.log("\u{1F9F9} Connection cleaned - clearing security data in header");
|
|
||||||
}
|
|
||||||
setRealSecurityLevel(null);
|
setRealSecurityLevel(null);
|
||||||
setLastSecurityUpdate(0);
|
setLastSecurityUpdate(0);
|
||||||
setHasActiveSession(false);
|
setHasActiveSession(false);
|
||||||
@@ -14981,9 +14956,6 @@ var EnhancedMinimalHeader = ({
|
|||||||
setSessionType("unknown");
|
setSessionType("unknown");
|
||||||
};
|
};
|
||||||
const handlePeerDisconnect = () => {
|
const handlePeerDisconnect = () => {
|
||||||
if (window.DEBUG_MODE) {
|
|
||||||
console.log("\u{1F44B} Peer disconnect detected - clearing security data in header");
|
|
||||||
}
|
|
||||||
setRealSecurityLevel(null);
|
setRealSecurityLevel(null);
|
||||||
setLastSecurityUpdate(0);
|
setLastSecurityUpdate(0);
|
||||||
};
|
};
|
||||||
@@ -15018,15 +14990,9 @@ var EnhancedMinimalHeader = ({
|
|||||||
if (webrtcManager && window.EnhancedSecureCryptoUtils) {
|
if (webrtcManager && window.EnhancedSecureCryptoUtils) {
|
||||||
try {
|
try {
|
||||||
realTestResults = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager);
|
realTestResults = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager);
|
||||||
console.log("\u2705 Real security tests completed:", realTestResults);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("\u274C Real security tests failed:", error);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("\u26A0\uFE0F Cannot run security tests:", {
|
|
||||||
webrtcManager: !!webrtcManager,
|
|
||||||
cryptoUtils: !!window.EnhancedSecureCryptoUtils
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (!realTestResults && !realSecurityLevel) {
|
if (!realTestResults && !realSecurityLevel) {
|
||||||
alert("Security verification in progress...\nPlease wait for real-time cryptographic verification to complete.");
|
alert("Security verification in progress...\nPlease wait for real-time cryptographic verification to complete.");
|
||||||
@@ -15045,7 +15011,6 @@ var EnhancedMinimalHeader = ({
|
|||||||
passedChecks: 0,
|
passedChecks: 0,
|
||||||
totalChecks: 0
|
totalChecks: 0
|
||||||
};
|
};
|
||||||
console.log("Using fallback security data:", securityData);
|
|
||||||
}
|
}
|
||||||
let message = `REAL-TIME SECURITY VERIFICATION
|
let message = `REAL-TIME SECURITY VERIFICATION
|
||||||
|
|
||||||
@@ -15261,18 +15226,7 @@ Right-click or Ctrl+click to disconnect`,
|
|||||||
};
|
};
|
||||||
const securityDetails = getSecurityIndicatorDetails();
|
const securityDetails = getSecurityIndicatorDetails();
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.debugHeaderSecurity = () => {
|
window.debugHeaderSecurity = void 0;
|
||||||
console.log("\u{1F50D} Header Security Debug:", {
|
|
||||||
realSecurityLevel,
|
|
||||||
lastSecurityUpdate,
|
|
||||||
isConnected,
|
|
||||||
webrtcManagerProp: !!webrtcManager,
|
|
||||||
windowWebrtcManager: !!window.webrtcManager,
|
|
||||||
cryptoUtils: !!window.EnhancedSecureCryptoUtils,
|
|
||||||
displaySecurityLevel,
|
|
||||||
securityDetails
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return () => {
|
return () => {
|
||||||
delete window.debugHeaderSecurity;
|
delete window.debugHeaderSecurity;
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
-11
@@ -2266,7 +2266,6 @@ var EnhancedSecureP2PChat = () => {
|
|||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("Processing single QR code:", scannedData.substring(0, 50) + "...");
|
|
||||||
let parsedData;
|
let parsedData;
|
||||||
if (typeof window.decodeAnyPayload === "function") {
|
if (typeof window.decodeAnyPayload === "function") {
|
||||||
const any = window.decodeAnyPayload(scannedData);
|
const any = window.decodeAnyPayload(scannedData);
|
||||||
@@ -2568,24 +2567,14 @@ var EnhancedSecureP2PChat = () => {
|
|||||||
}]);
|
}]);
|
||||||
let offer;
|
let offer;
|
||||||
try {
|
try {
|
||||||
console.log("DEBUG: Processing offer input:", offerInput.trim().substring(0, 100) + "...");
|
|
||||||
console.log("DEBUG: decodeAnyPayload available:", typeof window.decodeAnyPayload === "function");
|
|
||||||
console.log("DEBUG: decompressIfNeeded available:", typeof window.decompressIfNeeded === "function");
|
|
||||||
if (typeof window.decodeAnyPayload === "function") {
|
if (typeof window.decodeAnyPayload === "function") {
|
||||||
console.log("DEBUG: Using decodeAnyPayload...");
|
|
||||||
const any = window.decodeAnyPayload(offerInput.trim());
|
const any = window.decodeAnyPayload(offerInput.trim());
|
||||||
console.log("DEBUG: decodeAnyPayload result type:", typeof any);
|
|
||||||
console.log("DEBUG: decodeAnyPayload result:", any);
|
|
||||||
offer = typeof any === "string" ? JSON.parse(any) : any;
|
offer = typeof any === "string" ? JSON.parse(any) : any;
|
||||||
} else {
|
} else {
|
||||||
console.log("DEBUG: Using decompressIfNeeded...");
|
|
||||||
const rawText = typeof window.decompressIfNeeded === "function" ? window.decompressIfNeeded(offerInput.trim()) : offerInput.trim();
|
const rawText = typeof window.decompressIfNeeded === "function" ? window.decompressIfNeeded(offerInput.trim()) : offerInput.trim();
|
||||||
console.log("DEBUG: decompressIfNeeded result:", rawText.substring(0, 100) + "...");
|
|
||||||
offer = JSON.parse(rawText);
|
offer = JSON.parse(rawText);
|
||||||
}
|
}
|
||||||
console.log("DEBUG: Final offer:", offer);
|
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
console.error("DEBUG: Parse error:", parseError);
|
|
||||||
throw new Error(`Invalid invitation format: ${parseError.message}`);
|
throw new Error(`Invalid invitation format: ${parseError.message}`);
|
||||||
}
|
}
|
||||||
if (!offer || typeof offer !== "object") {
|
if (!offer || typeof offer !== "object") {
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+1
-12
@@ -2589,7 +2589,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Single QR code - try to decode directly
|
// Single QR code - try to decode directly
|
||||||
console.log('Processing single QR code:', scannedData.substring(0, 50) + '...');
|
// Removed verbose debug log
|
||||||
let parsedData;
|
let parsedData;
|
||||||
if (typeof window.decodeAnyPayload === 'function') {
|
if (typeof window.decodeAnyPayload === 'function') {
|
||||||
const any = window.decodeAnyPayload(scannedData);
|
const any = window.decodeAnyPayload(scannedData);
|
||||||
@@ -2939,26 +2939,15 @@
|
|||||||
|
|
||||||
let offer;
|
let offer;
|
||||||
try {
|
try {
|
||||||
console.log('DEBUG: Processing offer input:', offerInput.trim().substring(0, 100) + '...');
|
|
||||||
console.log('DEBUG: decodeAnyPayload available:', typeof window.decodeAnyPayload === 'function');
|
|
||||||
console.log('DEBUG: decompressIfNeeded available:', typeof window.decompressIfNeeded === 'function');
|
|
||||||
|
|
||||||
// Prefer binary decode first, then gzip JSON
|
// Prefer binary decode first, then gzip JSON
|
||||||
if (typeof window.decodeAnyPayload === 'function') {
|
if (typeof window.decodeAnyPayload === 'function') {
|
||||||
console.log('DEBUG: Using decodeAnyPayload...');
|
|
||||||
const any = window.decodeAnyPayload(offerInput.trim());
|
const any = window.decodeAnyPayload(offerInput.trim());
|
||||||
console.log('DEBUG: decodeAnyPayload result type:', typeof any);
|
|
||||||
console.log('DEBUG: decodeAnyPayload result:', any);
|
|
||||||
offer = (typeof any === 'string') ? JSON.parse(any) : any;
|
offer = (typeof any === 'string') ? JSON.parse(any) : any;
|
||||||
} else {
|
} else {
|
||||||
console.log('DEBUG: Using decompressIfNeeded...');
|
|
||||||
const rawText = (typeof window.decompressIfNeeded === 'function') ? window.decompressIfNeeded(offerInput.trim()) : offerInput.trim();
|
const rawText = (typeof window.decompressIfNeeded === 'function') ? window.decompressIfNeeded(offerInput.trim()) : offerInput.trim();
|
||||||
console.log('DEBUG: decompressIfNeeded result:', rawText.substring(0, 100) + '...');
|
|
||||||
offer = JSON.parse(rawText);
|
offer = JSON.parse(rawText);
|
||||||
}
|
}
|
||||||
console.log('DEBUG: Final offer:', offer);
|
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
console.error('DEBUG: Parse error:', parseError);
|
|
||||||
throw new Error(`Invalid invitation format: ${parseError.message}`);
|
throw new Error(`Invalid invitation format: ${parseError.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,11 +63,11 @@ const EnhancedMinimalHeader = ({
|
|||||||
} else if (window.DEBUG_MODE) {
|
} else if (window.DEBUG_MODE) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(' Security calculation returned invalid data');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(' Error in real security calculation:', error);
|
|
||||||
} finally {
|
} finally {
|
||||||
isUpdating = false;
|
isUpdating = false;
|
||||||
}
|
}
|
||||||
@@ -125,11 +125,11 @@ const EnhancedMinimalHeader = ({
|
|||||||
if (securityData && securityData.isRealData !== false) {
|
if (securityData && securityData.isRealData !== false) {
|
||||||
setRealSecurityLevel(securityData);
|
setRealSecurityLevel(securityData);
|
||||||
setLastSecurityUpdate(Date.now());
|
setLastSecurityUpdate(Date.now());
|
||||||
console.log('✅ Header security level force-updated');
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('❌ Force update failed:', error);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setLastSecurityUpdate(0);
|
setLastSecurityUpdate(0);
|
||||||
@@ -170,9 +170,7 @@ const EnhancedMinimalHeader = ({
|
|||||||
|
|
||||||
// Connection cleanup handler (use existing event from module)
|
// Connection cleanup handler (use existing event from module)
|
||||||
const handleConnectionCleaned = () => {
|
const handleConnectionCleaned = () => {
|
||||||
if (window.DEBUG_MODE) {
|
|
||||||
console.log('🧹 Connection cleaned - clearing security data in header');
|
|
||||||
}
|
|
||||||
|
|
||||||
setRealSecurityLevel(null);
|
setRealSecurityLevel(null);
|
||||||
setLastSecurityUpdate(0);
|
setLastSecurityUpdate(0);
|
||||||
@@ -183,9 +181,7 @@ const EnhancedMinimalHeader = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handlePeerDisconnect = () => {
|
const handlePeerDisconnect = () => {
|
||||||
if (window.DEBUG_MODE) {
|
|
||||||
console.log('👋 Peer disconnect detected - clearing security data in header');
|
|
||||||
}
|
|
||||||
|
|
||||||
setRealSecurityLevel(null);
|
setRealSecurityLevel(null);
|
||||||
setLastSecurityUpdate(0);
|
setLastSecurityUpdate(0);
|
||||||
@@ -236,15 +232,12 @@ const EnhancedMinimalHeader = ({
|
|||||||
if (webrtcManager && window.EnhancedSecureCryptoUtils) {
|
if (webrtcManager && window.EnhancedSecureCryptoUtils) {
|
||||||
try {
|
try {
|
||||||
realTestResults = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager);
|
realTestResults = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager);
|
||||||
console.log('✅ Real security tests completed:', realTestResults);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Real security tests failed:', error);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('⚠️ Cannot run security tests:', {
|
|
||||||
webrtcManager: !!webrtcManager,
|
|
||||||
cryptoUtils: !!window.EnhancedSecureCryptoUtils
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no real test results and no existing security level, show progress message
|
// If no real test results and no existing security level, show progress message
|
||||||
@@ -269,7 +262,7 @@ const EnhancedMinimalHeader = ({
|
|||||||
passedChecks: 0,
|
passedChecks: 0,
|
||||||
totalChecks: 0
|
totalChecks: 0
|
||||||
};
|
};
|
||||||
console.log('Using fallback security data:', securityData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detailed information about the REAL security check
|
// Detailed information about the REAL security check
|
||||||
@@ -501,18 +494,7 @@ const EnhancedMinimalHeader = ({
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.debugHeaderSecurity = () => {
|
window.debugHeaderSecurity = undefined;
|
||||||
console.log('🔍 Header Security Debug:', {
|
|
||||||
realSecurityLevel,
|
|
||||||
lastSecurityUpdate,
|
|
||||||
isConnected,
|
|
||||||
webrtcManagerProp: !!webrtcManager,
|
|
||||||
windowWebrtcManager: !!window.webrtcManager,
|
|
||||||
cryptoUtils: !!window.EnhancedSecureCryptoUtils,
|
|
||||||
displaySecurityLevel: displaySecurityLevel,
|
|
||||||
securityDetails: securityDetails
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
delete window.debugHeaderSecurity;
|
delete window.debugHeaderSecurity;
|
||||||
|
|||||||
@@ -2308,12 +2308,7 @@ class EnhancedSecureCryptoUtils {
|
|||||||
|
|
||||||
payload.mac = Array.from(new Uint8Array(mac));
|
payload.mac = Array.from(new Uint8Array(mac));
|
||||||
|
|
||||||
EnhancedSecureCryptoUtils.secureLog.log('info', 'Message encrypted with metadata protection', {
|
// Logging removed to avoid noisy console output
|
||||||
messageId,
|
|
||||||
sequenceNumber,
|
|
||||||
hasMetadataProtection: true,
|
|
||||||
hasPadding: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -12094,11 +12094,10 @@ class SecureKeyStorage {
|
|||||||
/**
|
/**
|
||||||
* Get master key (with automatic unlock if needed)
|
* Get master key (with automatic unlock if needed)
|
||||||
*/
|
*/
|
||||||
async _getMasterKey() {
|
async _ensureMasterKeyUnlocked() {
|
||||||
if (!this._masterKeyManager.isUnlocked()) {
|
if (!this._masterKeyManager.isUnlocked()) {
|
||||||
await this._masterKeyManager.unlock();
|
await this._masterKeyManager.unlock();
|
||||||
}
|
}
|
||||||
return this._masterKeyManager.getMasterKey();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async storeKey(keyId, cryptoKey, metadata = {}) {
|
async storeKey(keyId, cryptoKey, metadata = {}) {
|
||||||
@@ -12191,20 +12190,12 @@ class SecureKeyStorage {
|
|||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const data = encoder.encode(dataToEncrypt);
|
const data = encoder.encode(dataToEncrypt);
|
||||||
|
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
await this._ensureMasterKeyUnlocked();
|
||||||
|
const { encryptedData, iv } = await this._masterKeyManager.encryptBytes(data);
|
||||||
const masterKey = await this._getMasterKey();
|
|
||||||
const encryptedData = await crypto.subtle.encrypt(
|
|
||||||
{ name: 'AES-GCM', iv },
|
|
||||||
masterKey,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
|
|
||||||
// Return IV + encrypted data
|
// Return IV + encrypted data
|
||||||
const result = new Uint8Array(iv.length + encryptedData.byteLength);
|
const result = new Uint8Array(iv.length + encryptedData.byteLength);
|
||||||
result.set(iv, 0);
|
result.set(iv, 0);
|
||||||
result.set(new Uint8Array(encryptedData), iv.length);
|
result.set(encryptedData, iv.length);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12212,12 +12203,8 @@ class SecureKeyStorage {
|
|||||||
const iv = encryptedData.slice(0, 12);
|
const iv = encryptedData.slice(0, 12);
|
||||||
const data = encryptedData.slice(12);
|
const data = encryptedData.slice(12);
|
||||||
|
|
||||||
const masterKey = await this._getMasterKey();
|
await this._ensureMasterKeyUnlocked();
|
||||||
const decryptedData = await crypto.subtle.decrypt(
|
const decryptedData = await this._masterKeyManager.decryptBytes(data, iv);
|
||||||
{ name: 'AES-GCM', iv },
|
|
||||||
masterKey,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
const jsonString = decoder.decode(decryptedData);
|
const jsonString = decoder.decode(decryptedData);
|
||||||
@@ -12906,11 +12893,8 @@ class SecurePersistentKeyStorage {
|
|||||||
// Export key to JWK
|
// Export key to JWK
|
||||||
const jwkData = await crypto.subtle.exportKey('jwk', cryptoKey);
|
const jwkData = await crypto.subtle.exportKey('jwk', cryptoKey);
|
||||||
|
|
||||||
// Get master key for encryption
|
|
||||||
const masterKey = this._masterKeyManager.getMasterKey();
|
|
||||||
|
|
||||||
// Encrypt JWK data
|
// Encrypt JWK data
|
||||||
const { encryptedData, iv } = await this._encryptKeyData(jwkData, masterKey);
|
const { encryptedData, iv } = await this._encryptKeyData(jwkData);
|
||||||
|
|
||||||
// Store encrypted data in IndexedDB
|
// Store encrypted data in IndexedDB
|
||||||
await this._indexedDB.storeEncryptedKey(
|
await this._indexedDB.storeEncryptedKey(
|
||||||
@@ -12952,11 +12936,8 @@ class SecurePersistentKeyStorage {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get master key for decryption
|
|
||||||
const masterKey = this._masterKeyManager.getMasterKey();
|
|
||||||
|
|
||||||
// Decrypt JWK data
|
// Decrypt JWK data
|
||||||
const jwkData = await this._decryptKeyData(keyRecord.encryptedData, keyRecord.iv, masterKey);
|
const jwkData = await this._decryptKeyData(keyRecord.encryptedData, keyRecord.iv);
|
||||||
|
|
||||||
// Import as non-extractable key
|
// Import as non-extractable key
|
||||||
const restoredKey = await this._importAsNonExtractable(jwkData, keyRecord.algorithm, keyRecord.usages);
|
const restoredKey = await this._importAsNonExtractable(jwkData, keyRecord.algorithm, keyRecord.usages);
|
||||||
@@ -13029,37 +13010,21 @@ class SecurePersistentKeyStorage {
|
|||||||
/**
|
/**
|
||||||
* Encrypt key data using master key
|
* Encrypt key data using master key
|
||||||
*/
|
*/
|
||||||
async _encryptKeyData(jwkData, masterKey) {
|
async _encryptKeyData(jwkData) {
|
||||||
// Convert JWK to JSON string and then to bytes
|
// Convert JWK to JSON string and then to bytes
|
||||||
const jsonString = JSON.stringify(jwkData);
|
const jsonString = JSON.stringify(jwkData);
|
||||||
const data = new TextEncoder().encode(jsonString);
|
const data = new TextEncoder().encode(jsonString);
|
||||||
|
|
||||||
// Generate random IV
|
await this._ensureMasterKeyUnlocked();
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
return await this._masterKeyManager.encryptBytes(data);
|
||||||
|
|
||||||
// Encrypt with AES-GCM
|
|
||||||
const encryptedData = await crypto.subtle.encrypt(
|
|
||||||
{ name: 'AES-GCM', iv },
|
|
||||||
masterKey,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
encryptedData: new Uint8Array(encryptedData),
|
|
||||||
iv: iv
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt key data using master key
|
* Decrypt key data using master key
|
||||||
*/
|
*/
|
||||||
async _decryptKeyData(encryptedData, iv, masterKey) {
|
async _decryptKeyData(encryptedData, iv) {
|
||||||
// Decrypt with AES-GCM
|
await this._ensureMasterKeyUnlocked();
|
||||||
const decryptedData = await crypto.subtle.decrypt(
|
const decryptedData = await this._masterKeyManager.decryptBytes(encryptedData, iv);
|
||||||
{ name: 'AES-GCM', iv },
|
|
||||||
masterKey,
|
|
||||||
encryptedData
|
|
||||||
);
|
|
||||||
|
|
||||||
// Convert back to JWK
|
// Convert back to JWK
|
||||||
const jsonString = new TextDecoder().decode(decryptedData);
|
const jsonString = new TextDecoder().decode(decryptedData);
|
||||||
@@ -13114,7 +13079,7 @@ class SecurePersistentKeyStorage {
|
|||||||
class SecureMasterKeyManager {
|
class SecureMasterKeyManager {
|
||||||
constructor(indexedDBWrapper = null) {
|
constructor(indexedDBWrapper = null) {
|
||||||
// Session state
|
// Session state
|
||||||
this._masterKey = null;
|
this._keyHandle = null; // non-extractable CryptoKey
|
||||||
this._isUnlocked = false;
|
this._isUnlocked = false;
|
||||||
this._sessionTimeout = null;
|
this._sessionTimeout = null;
|
||||||
this._lastActivity = null;
|
this._lastActivity = null;
|
||||||
@@ -13372,8 +13337,8 @@ class SecureMasterKeyManager {
|
|||||||
// Get or create persistent salt
|
// Get or create persistent salt
|
||||||
const salt = await this._getOrCreateSalt();
|
const salt = await this._getOrCreateSalt();
|
||||||
|
|
||||||
// Derive master key
|
// Derive non-extractable key handle
|
||||||
this._masterKey = await this._deriveKeyFromPassword(password, salt);
|
this._keyHandle = await this._deriveKeyFromPassword(password, salt);
|
||||||
|
|
||||||
// Mark as unlocked
|
// Mark as unlocked
|
||||||
this._isUnlocked = true;
|
this._isUnlocked = true;
|
||||||
@@ -13408,13 +13373,24 @@ class SecureMasterKeyManager {
|
|||||||
/**
|
/**
|
||||||
* Get master key (only if unlocked)
|
* Get master key (only if unlocked)
|
||||||
*/
|
*/
|
||||||
getMasterKey() {
|
// Prevent direct key access; provide operations only
|
||||||
if (!this._isUnlocked || !this._masterKey) {
|
async encryptBytes(plainBytes) {
|
||||||
|
if (!this._isUnlocked || !this._keyHandle) {
|
||||||
throw new Error('Master key is locked');
|
throw new Error('Master key is locked');
|
||||||
}
|
}
|
||||||
|
|
||||||
this._updateActivity();
|
this._updateActivity();
|
||||||
return this._masterKey;
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||||
|
const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, this._keyHandle, plainBytes);
|
||||||
|
return { encryptedData: new Uint8Array(encrypted), iv };
|
||||||
|
}
|
||||||
|
|
||||||
|
async decryptBytes(encryptedBytes, iv) {
|
||||||
|
if (!this._isUnlocked || !this._keyHandle) {
|
||||||
|
throw new Error('Master key is locked');
|
||||||
|
}
|
||||||
|
this._updateActivity();
|
||||||
|
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, this._keyHandle, encryptedBytes);
|
||||||
|
return new Uint8Array(decrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13440,10 +13416,10 @@ class SecureMasterKeyManager {
|
|||||||
* Securely wipe master key from memory
|
* Securely wipe master key from memory
|
||||||
*/
|
*/
|
||||||
_secureWipeMasterKey() {
|
_secureWipeMasterKey() {
|
||||||
if (this._masterKey) {
|
if (this._keyHandle) {
|
||||||
// CryptoKey objects are automatically garbage collected
|
// CryptoKey objects are automatically garbage collected
|
||||||
// but we clear the reference immediately
|
// but we clear the reference immediately
|
||||||
this._masterKey = null;
|
this._keyHandle = null;
|
||||||
}
|
}
|
||||||
this._clearTimers();
|
this._clearTimers();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user