fix: cross-browser sync decryption + WebAuthn on extension pages
Sync code import between browsers was failing with "Wrong key or corrupted data" because each browser derives a different AES key from the same password (different salt). The decrypt function was using the local device's salt instead of the source device's salt embedded in the sync envelope. Fixed _decryptFromSync to always use the sync envelope's salt for decryption. If the cached key's salt doesn't match, forces re-auth so the password is re-entered and a new key is derived with the correct salt. Also fixed WebAuthn "device can't be used" error on extension pages. chrome-extension:// and moz-extension:// origins are not valid for WebAuthn. isWebAuthnAvailable() now returns false on extension pages. https://claude.ai/code/session_01SWSwDfMVij53bCTNSCLMwn
This commit is contained in:
@@ -374,6 +374,9 @@ const SilentSendCrypto = {
|
||||
* Check if WebAuthn is available in this browser.
|
||||
*/
|
||||
isWebAuthnAvailable() {
|
||||
// WebAuthn doesn't work on extension pages (chrome-extension:// origin)
|
||||
if (typeof location !== 'undefined' && location.protocol === 'chrome-extension:') return false;
|
||||
if (typeof location !== 'undefined' && location.protocol === 'moz-extension:') return false;
|
||||
return !!(window.PublicKeyCredential && navigator.credentials);
|
||||
},
|
||||
|
||||
|
||||
+27
-8
@@ -460,23 +460,42 @@ const SilentSendSync = {
|
||||
async _decryptFromSync(data) {
|
||||
if (!data?._ssEncrypted) return { data, decrypted: false };
|
||||
|
||||
// If this device has no encryption config yet, bootstrap from the sync envelope
|
||||
// Always use the sync envelope's salt/verificationBlob for decryption,
|
||||
// not the local config. Different devices have different salts, so the
|
||||
// local key won't decrypt data encrypted with another device's salt.
|
||||
let config = await this._getSyncEncryption();
|
||||
if (!config?.enabled && data._encConfig) {
|
||||
// Save minimal config so authenticate() can work
|
||||
|
||||
if (data._encConfig) {
|
||||
// Use the source's salt for this decryption, but don't overwrite
|
||||
// the local config permanently yet — only if decryption succeeds
|
||||
config = {
|
||||
...(config || {}),
|
||||
enabled: true,
|
||||
salt: data._encConfig.salt,
|
||||
verificationBlob: data._encConfig.verificationBlob,
|
||||
authMethod: 'password', // will be updated from _encMeta after decryption
|
||||
ttlDays: 90,
|
||||
webauthn: false,
|
||||
};
|
||||
await this._saveSyncEncryption(config);
|
||||
}
|
||||
|
||||
const keyInfo = await this._getEncryptionKey();
|
||||
if (!config?.enabled) {
|
||||
return { data: null, decrypted: false, needsAuth: true };
|
||||
}
|
||||
|
||||
// Try to get a key using the sync envelope's salt
|
||||
// First check if we have a cached key that matches
|
||||
let keyInfo = await SilentSendCrypto.getCachedKey();
|
||||
|
||||
// If the cached key's salt doesn't match the sync data's salt,
|
||||
// we need to re-derive from the password
|
||||
if (keyInfo && data._encConfig && keyInfo.salt !== data._encConfig.salt) {
|
||||
keyInfo = null; // force re-auth with the correct salt
|
||||
}
|
||||
|
||||
if (!keyInfo) {
|
||||
// Need the user to enter the password — save the sync salt temporarily
|
||||
// so authenticate() uses it to derive the correct key
|
||||
if (data._encConfig) {
|
||||
await this._saveSyncEncryption(config);
|
||||
}
|
||||
return { data: null, decrypted: false, needsAuth: true };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user