From e8cab25edaaec6aca64bd72e49ee548e7b2e1cac Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Mon, 25 May 2026 17:04:56 +0400 Subject: [PATCH] fix: revert E2E AEAD wrapping (broke multi-client voice); add Android CAMERA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Voice regression: EncryptingTransport encrypts media with the pairwise client↔relay session key, but the relay forwards bytes without re-encrypting per recipient. Sender's key_A ≠ recipient's key_B → recipient cannot decrypt → silent audio between mac and android. Drop the wrapper; restore plaintext- over-QUIC-TLS to the relay. Proper E2E needs MLS group keys or relay hop-by- hop re-encryption (future PRD). Android camera: add CAMERA manifest permission + runtime request via MainActivity. NOTE: still not sufficient — Tauri/Wry's WebChromeClient does not grant getUserMedia, so video on Android needs a Tauri plugin override or native Camera2 path. Documented in MainActivity.kt. Co-Authored-By: Claude Sonnet 4.6 --- .../android/app/src/main/AndroidManifest.xml | 2 ++ .../main/java/com/wzp/desktop/MainActivity.kt | 11 +++++++- desktop/src-tauri/src/engine.rs | 25 +++++++++++-------- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/desktop/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/desktop/src-tauri/gen/android/app/src/main/AndroidManifest.xml index 97a6a67..23d83b2 100644 --- a/desktop/src-tauri/gen/android/app/src/main/AndroidManifest.xml +++ b/desktop/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,9 @@ + + diff --git a/desktop/src-tauri/gen/android/app/src/main/java/com/wzp/desktop/MainActivity.kt b/desktop/src-tauri/gen/android/app/src/main/java/com/wzp/desktop/MainActivity.kt index b6a5b9e..a0430f4 100644 --- a/desktop/src-tauri/gen/android/app/src/main/java/com/wzp/desktop/MainActivity.kt +++ b/desktop/src-tauri/gen/android/app/src/main/java/com/wzp/desktop/MainActivity.kt @@ -16,10 +16,19 @@ class MainActivity : TauriActivity() { private const val AUDIO_PERMISSIONS_REQUEST = 4242 private val REQUIRED_AUDIO_PERMISSIONS = arrayOf( Manifest.permission.RECORD_AUDIO, - Manifest.permission.MODIFY_AUDIO_SETTINGS + Manifest.permission.MODIFY_AUDIO_SETTINGS, + Manifest.permission.CAMERA ) } + // NOTE: granting CAMERA at the Android system layer is necessary but NOT + // sufficient for video on Android. Tauri/Wry's internal WebChromeClient + // does not currently grant `getUserMedia` permission requests, so the + // browser-layer getUserMedia call still fails even after the OS grants + // CAMERA. Fixing this needs either a Tauri plugin that overrides the + // WebChromeClient, or a native Camera2/CameraX capture path that bypasses + // the WebView. Tracked as a follow-up. + override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) diff --git a/desktop/src-tauri/src/engine.rs b/desktop/src-tauri/src/engine.rs index 5e986bf..d7cbd7e 100644 --- a/desktop/src-tauri/src/engine.rs +++ b/desktop/src-tauri/src/engine.rs @@ -615,10 +615,12 @@ impl CallEngine { video_codec = ?hs.video_codec, "first-join diag: connected to relay, handshake complete" ); - Arc::new(wzp_client::encrypted_transport::EncryptingTransport::new( - transport, - hs.session, - )) + // NOTE: see comment in CallEngine::start (~line 1585) — we intentionally + // do NOT wrap with EncryptingTransport. The pairwise client↔relay session + // key can't be used end-to-end without MLS or relay re-encryption. + drop(hs.session); + let _ = hs.video_codec; + transport } else { info!( t_ms = call_t0.elapsed().as_millis(), @@ -1584,6 +1586,12 @@ impl CallEngine { // accept_handshake handler. See the android branch's // comment for the full rationale. let quinn_transport = transport.clone(); + // NOTE: EncryptingTransport is intentionally NOT wrapping the transport here. + // The client↔relay handshake derives a pairwise session key, but the relay + // forwards media without decrypt+re-encrypt — so a recipient with a different + // pairwise key cannot decrypt the sender's ciphertext. True E2E for the SFU + // model needs MLS group keys (or hop-by-hop relay re-encryption); until that + // PRD lands, media goes plaintext-over-QUIC-TLS to the relay. let (_negotiated_video_codec, transport): (_, Arc) = if !is_direct_p2p { let hs = @@ -1594,13 +1602,8 @@ impl CallEngine { e })?; info!(video_codec = ?hs.video_codec, "handshake complete"); - let enc = Arc::new( - wzp_client::encrypted_transport::EncryptingTransport::new( - transport, - hs.session, - ), - ); - (hs.video_codec, enc) + drop(hs.session); + (hs.video_codec, transport) } else { info!("direct P2P — skipping relay handshake (QUIC TLS is the encryption layer)"); (None, transport)