Improve E2EE robustness and Android permissions

- 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
This commit is contained in:
Jamie Brunton committed 2026-01-02 21:36:26 +00:00
1 parent 198cd80dfd
commit 348ef221db
13 files changed
+777 -106

No files matched your search

@@ -49,7 +49,13 @@ export async function decryptMediaFile(
);
const encryptedData = base64ToArrayBuffer(encryptedBase64);
// 3. Unwrap AES key
// 3. 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}`);
}
// 4. Unwrap AES key
const wrappedKeyBase64 = isRecipient
? keysMetadata.recipient_wrapped_key
: keysMetadata.sender_wrapped_key;
@@ -61,11 +67,10 @@ export async function decryptMediaFile(
const rawAesKey = await unwrapAESKey(wrappedKeyBase64, privateKeyJwk);
const aesKey = await importAESKey(rawAesKey);
// 4. Decrypt file
const iv = base64ToUint8Array(encryptionIv);
// 5. Decrypt file
const decryptedData = await decryptAES(encryptedData, aesKey, iv);
// 5. Write decrypted file
// 6. Write decrypted file
const ext = mediaType === 'video' ? 'mp4' : 'jpg';
const decryptedUri = `${FileSystem.cacheDirectory}${Date.now()}_decrypted.${ext}`;
@@ -35,18 +35,23 @@ export async function decryptTextMessage(
throw new Error('No wrapped key available for decryption');
}
// 2. Unwrap AES key using RSA private key
// 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);
// 3. Import AES key
// 4. Import AES key
const aesKey = await importAESKey(rawAesKey);
// 4. Decrypt content
const iv = base64ToUint8Array(encryptionIv);
// 5. Decrypt content
const ciphertext = base64ToArrayBuffer(encryptedContent);
const plaintext = await decryptAES(ciphertext, aesKey, iv);
// 5. Decode to string
// 6. Decode to string
return new TextDecoder().decode(plaintext);
}