From 255d74ac3cc263ddc1b7d6227cad818042927604 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 12 Jun 2023 09:14:03 -0400 Subject: [PATCH] hsclient: fill in a lot more introduce/rendezvous logic. This code tries to fill in some TODO HS code, replacing it with a lot more code with a bunch more TODO HS comments. Hopefully the expansions of the new TODO HS comments should be simpler. --- crates/tor-hsclient/src/connect.rs | 150 ++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 4 deletions(-) diff --git a/crates/tor-hsclient/src/connect.rs b/crates/tor-hsclient/src/connect.rs index bb8508577..be87bde8f 100644 --- a/crates/tor-hsclient/src/connect.rs +++ b/crates/tor-hsclient/src/connect.rs @@ -14,13 +14,18 @@ use futures::channel::oneshot; use futures::{AsyncRead, AsyncWrite}; use itertools::Itertools; use rand::Rng; -use tor_cell::relaycell::msg::{AnyRelayMsg, Rendezvous2}; +use tor_bytes::Writeable; +use tor_cell::relaycell::hs::intro_payload::{self, IntroduceHandshakePayload}; +use tor_cell::relaycell::msg::{AnyRelayMsg, Introduce1, Rendezvous2}; use tor_hscrypto::Subcredential; +use tor_proto::circuit::handshake::{self, hs_ntor}; use tracing::{debug, trace}; use retry_error::RetryError; use safelog::Redacted; -use tor_cell::relaycell::hs::{EstablishRendezvous, RendezvousEstablished}; +use tor_cell::relaycell::hs::{ + AuthKeyType, EstablishRendezvous, IntroduceHeader, RendezvousEstablished, +}; use tor_cell::relaycell::{AnyRelayCell, RelayMsg, UnparsedRelayCell}; use tor_checkable::{timed::TimerangeBound, Timebound}; use tor_circmgr::hspool::{HsCircKind, HsCircPool}; @@ -33,7 +38,7 @@ use tor_linkspec::{CircTarget, OwnedCircTarget, RelayId}; use tor_llcrypto::pk::ed25519::Ed25519Identity; use tor_netdir::{HsDirOp, NetDir, Relay}; use tor_netdoc::doc::hsdesc::{HsDesc, IntroPointDesc}; -use tor_proto::circuit::{ClientCirc, MetaCellDisposition, MsgHandler}; +use tor_proto::circuit::{CircParameters, ClientCirc, MetaCellDisposition, MsgHandler}; use tor_rtcompat::{Runtime, SleepProviderExt as _, TimeoutError}; use crate::relay_info::ipt_to_circtarget; @@ -812,6 +817,72 @@ impl<'c, R: Runtime, M: MocksForConnect> Context<'c, R, M> { intro_index, ); + // Now we construct an introduce1 message and perform the first part of the + // rendezvous handshake. + // + // This process is tricky because the header of the INTRODUCE1 message + // -- which depends on the IntroPt configuration -- is authenticated as + // part of the HsDesc handshake. + + // Construct the header, since we need it as input to our encryption. + let intro_header = { + let ipt_sid_key = ipt.intro_desc.ipt_sid_key(); + let intro1 = Introduce1::new( + AuthKeyType::ED25519_SHA3_256, + ipt_sid_key.as_bytes().to_vec(), + vec![], + ); + let mut header = vec![]; + intro1 + .encode_onto(&mut header) + .map_err(into_internal!("couldn't encode intro1 header"))?; + header + }; + + // Construct the introduce payload, which tells the onion service how to find + // our rendezvous point. (We could do this earlier if we wanted.) + let intro_payload = { + let onion_key = + intro_payload::OnionKey::NtorOnionKey(*rendezvous.rend_relay.ntor_onion_key()); + let linkspecs = rendezvous + .rend_relay + .linkspecs() + .map_err(into_internal!("Couldn't encode link specifiers"))?; + let payload = + IntroduceHandshakePayload::new(rendezvous.rend_cookie, onion_key, linkspecs); + let mut encoded = vec![]; + payload + .write_onto(&mut encoded) + .map_err(into_internal!("Couldn't encode introduce1 payload"))?; + encoded + }; + + // Perform the cryptographic handshake with the onion service. + let service_info = hs_ntor::HsNtorServiceInfo::new( + ipt.intro_desc.svc_ntor_key().clone(), + ipt.intro_desc.ipt_sid_key().clone(), + self.subcredential, + ); + let handshake_state = + hs_ntor::HsNtorClientState::new(&mut self.mocks.thread_rng(), service_info); + let encrypted_body = handshake_state + .client_send_intro(&intro_header, &intro_payload) + .map_err(into_internal!("can't begin hs-ntor handshake"))?; + + // Build our actual INTRODUCE1 message. + let intro1_real = Introduce1::new( + AuthKeyType::ED25519_SHA3_256, + ipt.intro_desc.ipt_sid_key().as_bytes().to_vec(), + encrypted_body, + ); + + // TODO HS: Send intro1_real on the introduce circuit and wait for + // either an error or an INTRO_ACK. + // intro_circ.send_control_message(intro1_real)... + + // TODO HS: We need to remember handshake_state so we can later handle a + // RENDEZVOUS2 message! + Err(internal!("sending INTRODUCE1 is not yet implemented!").into()) // TODO HS } @@ -830,6 +901,48 @@ impl<'c, R: Runtime, M: MocksForConnect> Context<'c, R, M> { ipt: &UsableIntroPt<'_>, rendezvous: Rendezvous<'c, R, M>, ) -> Result, FAE> { + #![allow(unreachable_code, clippy::diverging_sub_expression)] // TODO HS remove. + use tor_proto::circuit::handshake; + + // TODO HS: Wait for our Rendezvous2 message on rend_tx + let rend2_msg: Rendezvous2 = todo!(); // TODO HS + + // TODO HS: get handshake_state form wherever we stored it above. + // + // TODO: It would be great if we could have multiple of these existing + // in parallel with similar x,X values but different ipts. I believe C + // tor manages it somehow. + let handshake_state: &hs_ntor::HsNtorClientState = todo!(); // TODO HS + + // Try to complete the cryptographic handshake. + let keygen = handshake_state + .client_receive_rend(rend2_msg.handshake_info()) + .map_err(into_internal!( + "ACTUALLY this is a protocol violation, make a better error" // TODO HS + ))?; + // TODO HS: make sure that we do the correct error recovery from the + // above error. Either the onion service has failed, or the rendezvous + // point has misbehaved, or we have used the wrong handshake_state. + + // TODO HS: Generate this more sensibly! + let params = CircParameters::default(); + + rendezvous + .rend_circ + .extend_virtual( + handshake::RelayProtocol::HsV3, + handshake::HandshakeRole::Initiator, + keygen, + params, + ) + .await + .map_err(into_internal!( + "actually this is probably a 'circuit closed' error" // TODO HS + ))?; + + // TODO HS: Now we can return the rend_circ circuit to the calling code, + // which can start using it! Isn't that great? we're done! + todo!() // HS implement } } @@ -890,7 +1003,7 @@ trait MocksForConnect: Clone { type HsCircPool: MockableCircPool; /// A random number generator - type Rng: rand::Rng; + type Rng: rand::Rng + rand::CryptoRng; /// Tell tests we got this descriptor text fn test_got_desc(&self, desc: &HsDesc) { @@ -935,6 +1048,15 @@ trait MockableClientCirc: Debug { msg: AnyRelayMsg, reply_handler: impl MsgHandler + Send + 'static, ) -> tor_proto::Result<()>; + + /// Add a virtual hop to the circuit. + async fn extend_virtual( + &self, + protocol: tor_proto::circuit::handshake::RelayProtocol, + protocol: tor_proto::circuit::handshake::HandshakeRole, + handshake: impl tor_proto::circuit::handshake::KeyGenerator + Send, + params: CircParameters, + ) -> tor_proto::Result<()>; } impl MocksForConnect for () { @@ -977,6 +1099,16 @@ impl MockableClientCirc for ClientCirc { ) -> tor_proto::Result<()> { ClientCirc::send_control_message(self, msg, reply_handler).await } + + async fn extend_virtual( + &self, + protocol: tor_proto::circuit::handshake::RelayProtocol, + role: tor_proto::circuit::handshake::HandshakeRole, + handshake: impl tor_proto::circuit::handshake::KeyGenerator + Send, + params: CircParameters, + ) -> tor_proto::Result<()> { + ClientCirc::extend_virtual(self, protocol, role, handshake, params).await + } } #[async_trait] @@ -1107,6 +1239,16 @@ mod test { ) -> tor_proto::Result<()> { todo!() } + + async fn extend_virtual( + &self, + protocol: tor_proto::circuit::handshake::RelayProtocol, + role: tor_proto::circuit::handshake::HandshakeRole, + handshake: impl tor_proto::circuit::handshake::KeyGenerator + Send, + params: CircParameters, + ) -> tor_proto::Result<()> { + todo!() + } } #[traced_test]