mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-02 10:19:43 +00:00
- Add IV validation and key sanitization for encryption - Add stale key repair for media decryption failures - Block READ_MEDIA_IMAGES/VIDEO permissions (use photo picker) - Prevent double-tap crashes in media picker - Clear media cache on logout - Persist versionCode/buildNumber in app.json - Bump Android targetSdk to 35
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
/**
|
|
* Text Message Decryption
|
|
*
|
|
* Decrypts E2EE text messages using the user's RSA private key.
|
|
*/
|
|
|
|
import { unwrapAESKey, importAESKey, decryptAES } from './crypto';
|
|
import { base64ToArrayBuffer, base64ToUint8Array } from './utils';
|
|
import type { KeysMetadata, RSAPrivateKeyJWK } from './types';
|
|
|
|
/**
|
|
* Decrypt an E2EE text message
|
|
*
|
|
* @param encryptedContent - Base64-encoded ciphertext
|
|
* @param encryptionIv - Base64-encoded initialization vector
|
|
* @param keysMetadata - The wrapped keys metadata
|
|
* @param privateKeyJwk - User's RSA private key
|
|
* @param isRecipient - Whether the current user is the recipient (true) or sender (false)
|
|
* @returns The decrypted plaintext message
|
|
*/
|
|
export async function decryptTextMessage(
|
|
encryptedContent: string,
|
|
encryptionIv: string,
|
|
keysMetadata: KeysMetadata,
|
|
privateKeyJwk: RSAPrivateKeyJWK,
|
|
isRecipient: boolean
|
|
): Promise<string> {
|
|
|
|
// 1. Select the correct wrapped key
|
|
const wrappedKeyBase64 = isRecipient
|
|
? keysMetadata.recipient_wrapped_key
|
|
: keysMetadata.sender_wrapped_key;
|
|
|
|
if (!wrappedKeyBase64) {
|
|
throw new Error('No wrapped key available for decryption');
|
|
}
|
|
|
|
// 2. Validate and decode IV (must be exactly 12 bytes for AES-GCM)
|
|
const iv = base64ToUint8Array(encryptionIv);
|
|
if (iv.length !== 12) {
|
|
throw new Error(`Invalid IV length: expected 12 bytes, got ${iv.length}`);
|
|
}
|
|
|
|
// 3. Unwrap AES key using RSA private key
|
|
const rawAesKey = await unwrapAESKey(wrappedKeyBase64, privateKeyJwk);
|
|
|
|
// 4. Import AES key
|
|
const aesKey = await importAESKey(rawAesKey);
|
|
|
|
// 5. Decrypt content
|
|
const ciphertext = base64ToArrayBuffer(encryptedContent);
|
|
|
|
const plaintext = await decryptAES(ciphertext, aesKey, iv);
|
|
|
|
// 6. Decode to string
|
|
return new TextDecoder().decode(plaintext);
|
|
}
|