fix(conv-key): rotate on unwrap failure (post-reset_user_key recovery)
This commit is contained in:
@@ -125,12 +125,25 @@ export async function getOrCreateConvKey(
|
|||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
const bundle = await fetchKeyBundle(client, conversationId, own.userId, version);
|
const bundle = await fetchKeyBundle(client, conversationId, own.userId, version);
|
||||||
if (bundle) {
|
if (bundle) {
|
||||||
const key = await unwrapConvKey(
|
try {
|
||||||
bundle.encryptedKey, bundle.nonce, bundle.sender.senderPublicKey, own.privateKey,
|
const key = await unwrapConvKey(
|
||||||
);
|
bundle.encryptedKey, bundle.nonce, bundle.sender.senderPublicKey, own.privateKey,
|
||||||
const handle = { conversationId, keyVersion: version, key };
|
);
|
||||||
cache.set(cacheKey(conversationId, version), handle);
|
const handle = { conversationId, keyVersion: version, key };
|
||||||
return handle;
|
cache.set(cacheKey(conversationId, version), handle);
|
||||||
|
return handle;
|
||||||
|
} catch (err) {
|
||||||
|
// A bundle exists for us but our current private key cannot unwrap it.
|
||||||
|
// The most common cause is `reset_user_key`: a fresh user-key pair was
|
||||||
|
// generated locally while the on-server bundle is still wrapped against
|
||||||
|
// the previous public key. Treat this the same as "no bundle for me" —
|
||||||
|
// mint a fresh conv-key at version+1 wrapped to our CURRENT key. Old
|
||||||
|
// messages stay unreadable for us; new ones flow.
|
||||||
|
console.warn(
|
||||||
|
'[conv-key] unwrap own bundle failed at v' + version + ' — auto-rotating',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const { count, error: cntErr } = await rawFrom(client, 'conversation_keys')
|
const { count, error: cntErr } = await rawFrom(client, 'conversation_keys')
|
||||||
.select('recipient_user_id', { count: 'exact', head: true })
|
.select('recipient_user_id', { count: 'exact', head: true })
|
||||||
@@ -138,12 +151,13 @@ export async function getOrCreateConvKey(
|
|||||||
.eq('key_version', version);
|
.eq('key_version', version);
|
||||||
if (cntErr) throw cntErr;
|
if (cntErr) throw cntErr;
|
||||||
if ((count ?? 0) > 0) {
|
if ((count ?? 0) > 0) {
|
||||||
// Rows exist for this version, but none for me. Either I lost the device-key
|
// Rows exist for this version, but none usable for me. Either I lost the
|
||||||
// that originally received my bundle, or my own bundle was wiped by the
|
// device-key that originally received my bundle, my own bundle was wiped
|
||||||
// 0.18.0 reset_user_key bug. Either way, the only way out is to mint a fresh
|
// by the 0.18.0 reset_user_key bug, or my key was reset and the existing
|
||||||
// conv-key at version+1 and wrap it for everyone we can. Old messages stay
|
// bundle is unwrappable (handled in the try/catch above). The only way
|
||||||
// unreadable for me; new ones flow.
|
// out is to mint a fresh conv-key at version+1 and wrap it for everyone
|
||||||
console.info('[conv-key] no bundle for me at v' + version + ' — auto-rotating');
|
// we can. Old messages stay unreadable for me; new ones flow.
|
||||||
|
console.info('[conv-key] no usable bundle for me at v' + version + ' — auto-rotating');
|
||||||
return rotateConvKey(client, conversationId, own);
|
return rotateConvKey(client, conversationId, own);
|
||||||
}
|
}
|
||||||
return bootstrapConvKey(client, conversationId, own, version);
|
return bootstrapConvKey(client, conversationId, own, version);
|
||||||
@@ -248,9 +262,24 @@ export async function tryGetConvKey(
|
|||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
const bundle = await fetchKeyBundle(client, conversationId, ownUserId, keyVersion);
|
const bundle = await fetchKeyBundle(client, conversationId, ownUserId, keyVersion);
|
||||||
if (!bundle) return null;
|
if (!bundle) return null;
|
||||||
const key = await unwrapConvKey(
|
let key: Uint8Array;
|
||||||
bundle.encryptedKey, bundle.nonce, bundle.sender.senderPublicKey, ownPrivateKey,
|
try {
|
||||||
);
|
key = await unwrapConvKey(
|
||||||
|
bundle.encryptedKey, bundle.nonce, bundle.sender.senderPublicKey, ownPrivateKey,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
// Bundle exists but the current private key doesn't unwrap it (typically
|
||||||
|
// after `reset_user_key`). Return null so the caller treats the message
|
||||||
|
// as un-decryptable instead of throwing and killing the whole batch.
|
||||||
|
// The conversation will be auto-rotated to a fresh key on the next send
|
||||||
|
// or chat open via `getOrCreateConvKey`'s own recovery path.
|
||||||
|
console.warn(
|
||||||
|
'[conv-key] tryGetConvKey unwrap failed at v' + keyVersion +
|
||||||
|
' (conv=' + conversationId.slice(0, 8) + ') — marking as un-decryptable',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const handle = { conversationId, keyVersion, key };
|
const handle = { conversationId, keyVersion, key };
|
||||||
cache.set(cacheKey(conversationId, keyVersion), handle);
|
cache.set(cacheKey(conversationId, keyVersion), handle);
|
||||||
return handle;
|
return handle;
|
||||||
|
|||||||
Reference in New Issue
Block a user