群聊加密模式fix6

This commit is contained in:
619dev
2026-06-12 17:31:55 +08:00
parent acf56b5b8f
commit f9ade5b31f
3 changed files with 60 additions and 11 deletions

View File

@@ -178,10 +178,20 @@ export async function distributeSenderKey(
recipientIkPub: string,
recipientKemPub: string | null | undefined
): Promise<{ encrypted_key: string; header: string }> {
const result = await encryptHybrid(recipientIkPub, recipientKemPub, senderKey)
return {
encrypted_key: result.ciphertext,
header: result.header,
try {
const result = await encryptHybrid(recipientIkPub, recipientKemPub, senderKey)
return {
encrypted_key: result.ciphertext,
header: result.header,
}
} catch (err) {
console.error('[groupCrypto] distributeSenderKey failed:', {
recipientIkPubLen: recipientIkPub?.length,
kemPubPresent: !!recipientKemPub,
senderKeyLen: senderKey?.length,
error: err,
})
throw err
}
}
@@ -201,5 +211,17 @@ export async function receiveSenderKey(
myPrivKey: string,
myKemPriv: string | null | undefined
): Promise<string> {
return await decryptHybrid(header, myPrivKey, myKemPriv, encryptedKey)
try {
const result = await decryptHybrid(header, myPrivKey, myKemPriv, encryptedKey)
return result
} catch (err) {
console.error('[groupCrypto] receiveSenderKey decryption failed:', {
encryptedKeyLen: encryptedKey?.length,
headerLen: header?.length,
myPrivKeyLen: myPrivKey?.length,
kemPrivPresent: !!myKemPriv,
error: err,
})
throw err
}
}

View File

@@ -125,12 +125,22 @@ export function useSocket() {
await fetchAndStoreSenderKeys(data.group_id)
sk = getSenderKey(data.group_id, data.from)
}
if (!sk) {
// Still no key — retry after a brief delay.
// The sender distributes keys via HTTP POST before sending the WS message,
// but there can be a race where the message arrives before the POST response
// has been fully committed to the DB.
console.log(`[useSocket] Retrying sender key fetch after 1s delay for ${data.from} in group ${data.group_id}...`)
await new Promise(r => setTimeout(r, 1000))
await fetchAndStoreSenderKeys(data.group_id)
sk = getSenderKey(data.group_id, data.from)
}
if (sk) {
const text = await decryptWithSenderKey(data.ciphertext, data.nonce, sk.senderKey)
msgToAdd = { ...data, decrypted: text }
} else {
// Still don't have sender key — store as 🔒 but keep nonce in data for retry
console.warn(`[useSocket] Still no sender key for ${data.from} in group ${data.group_id} after fetch. Message will show 🔒`)
console.warn(`[useSocket] Still no sender key for ${data.from} in group ${data.group_id} after retries. Message will show 🔒`)
msgToAdd = { ...data, decrypted: '🔒' }
}
} catch (err) {
@@ -218,11 +228,15 @@ export function useSocket() {
if (pending && data.msg_id) {
const chatId = pending.group_id || pending.to
if (chatId) {
useStore.getState().addMessage(chatId, {
// Build the message to add. For encrypted group messages, pendingMsg
// now carries encryption metadata (ciphertext, nonce, sender_key_version)
// set during the encryption step in Chat.tsx.
const msgToStore: any = {
...pending,
id: data.msg_id,
ts: data.ts || Date.now(),
})
}
useStore.getState().addMessage(chatId, msgToStore)
}
;(window as any).__pendingMsg = null
}

View File

@@ -573,13 +573,20 @@ export default function Chat() {
const memberKeys = await get(`/api/users/${m.id}`)
ikPub = memberKeys?.ik_pub
kemPub = memberKeys?.kem_pub
} catch {}
} catch (fetchKeyErr) {
console.warn(`[Chat] Failed to fetch public keys for member ${m.id}:`, fetchKeyErr)
}
}
if (ikPub && keys) {
try {
const dist = await distributeSenderKey(newKey, ikPub, kemPub)
// Pass null for kemPub — kem_pub on server is an Ed25519 signing key,
// NOT a valid Kyber KEM key. Passing it causes unnecessary Kyber encap
// failures. Using null goes directly to pure ECDH (version 1).
const dist = await distributeSenderKey(newKey, ikPub, null)
distributions.push({ to_id: m.id, encrypted_key: dist.encrypted_key, header: dist.header })
} catch {}
} catch (distMemberErr) {
console.warn(`[Chat] distributeSenderKey failed for member ${m.id}:`, distMemberErr)
}
}
}
if (distributions.length > 0) {
@@ -602,6 +609,12 @@ export default function Chat() {
}
if (sk && !sent) {
const encrypted = await encryptWithSenderKey(content, sk.senderKey)
// Update pendingMsg with actual encryption metadata so the ack handler
// stores the message with correct encrypted fields (matching server data).
// This ensures consistency when messages are later loaded from server.
pendingMsg.ciphertext = encrypted.ciphertext
pendingMsg.nonce = encrypted.nonce
pendingMsg.sender_key_version = sk.keyVersion
sent = sendWs({ type: 'message', msg_type: msgType, group_id: id, ciphertext: encrypted.ciphertext, nonce: encrypted.nonce, sender_key_version: sk.keyVersion })
}
} catch (encErr) {