| 416 | /// database; this is done by `receive_imf()` later on as needed. |
| 417 | #[expect(clippy::arithmetic_side_effects)] |
| 418 | pub(crate) async fn handle_securejoin_handshake( |
| 419 | context: &Context, |
| 420 | mime_message: &mut MimeMessage, |
| 421 | contact_id: ContactId, |
| 422 | ) -> Result<HandshakeMessage> { |
| 423 | if contact_id.is_special() { |
| 424 | return Err(Error::msg("Can not be called with special contact ID")); |
| 425 | } |
| 426 | |
| 427 | let step = get_secure_join_step(mime_message).context("Not a Secure-Join message")?; |
| 428 | |
| 429 | info!(context, "Received secure-join message {step:?}."); |
| 430 | |
| 431 | // Opportunistically protect against a theoretical 'surreptitious forwarding' attack: |
| 432 | // If Eve obtains a QR code from Alice and starts a securejoin with her, |
| 433 | // and also lets Bob scan a manipulated QR code, |
| 434 | // she could reencrypt the v*-request-with-auth message to Bob while maintaining the signature, |
| 435 | // and Bob would regard the message as valid. |
| 436 | // |
| 437 | // This attack is not actually relevant in any threat model, |
| 438 | // because if Eve can see Alice's QR code and have Bob scan a manipulated QR code, |
| 439 | // she can just do a classical MitM attack. |
| 440 | // |
| 441 | // Protecting all messages sent by Delta Chat against 'surreptitious forwarding' |
| 442 | // by checking the 'intended recipient fingerprint' |
| 443 | // will improve security (completely unrelated to the securejoin protocol) |
| 444 | // and is something we want to do in the future: |
| 445 | // https://www.rfc-editor.org/rfc/rfc9580.html#name-surreptitious-forwarding |
| 446 | if !matches!( |
| 447 | step, |
| 448 | SecureJoinStep::Request { .. } | SecureJoinStep::RequestPubkey | SecureJoinStep::Pubkey |
| 449 | ) { |
| 450 | let mut self_found = false; |
| 451 | let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint(); |
| 452 | for key in mime_message.gossiped_keys.values() { |
| 453 | if key.public_key.dc_fingerprint() == self_fingerprint { |
| 454 | self_found = true; |
| 455 | break; |
| 456 | } |
| 457 | } |
| 458 | if !self_found { |
| 459 | // This message isn't intended for us. Possibly the peer doesn't own the key which the |
| 460 | // message is signed with but forwarded someone's message to us. |
| 461 | warn!(context, "Step {step}: No self addr+pubkey gossip found."); |
| 462 | return Ok(HandshakeMessage::Ignore); |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | match step { |
| 467 | SecureJoinStep::Request { ref invitenumber } => { |
| 468 | /*======================================================= |
| 469 | ==== Alice - the inviter side ==== |
| 470 | ==== Step 3 in "Setup verified contact" protocol ==== |
| 471 | =======================================================*/ |
| 472 | |
| 473 | // this message may be unencrypted (Bob, the joiner and the sender, might not have Alice's key yet) |
| 474 | // it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it, |
| 475 | // send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here. |