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.
This commit is contained in:
Nick Mathewson 2023-06-12 09:14:03 -04:00
parent 2c8e3b22b5
commit 255d74ac3c
1 changed files with 146 additions and 4 deletions

View File

@ -14,13 +14,18 @@ use futures::channel::oneshot;
use futures::{AsyncRead, AsyncWrite}; use futures::{AsyncRead, AsyncWrite};
use itertools::Itertools; use itertools::Itertools;
use rand::Rng; 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_hscrypto::Subcredential;
use tor_proto::circuit::handshake::{self, hs_ntor};
use tracing::{debug, trace}; use tracing::{debug, trace};
use retry_error::RetryError; use retry_error::RetryError;
use safelog::Redacted; 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_cell::relaycell::{AnyRelayCell, RelayMsg, UnparsedRelayCell};
use tor_checkable::{timed::TimerangeBound, Timebound}; use tor_checkable::{timed::TimerangeBound, Timebound};
use tor_circmgr::hspool::{HsCircKind, HsCircPool}; use tor_circmgr::hspool::{HsCircKind, HsCircPool};
@ -33,7 +38,7 @@ use tor_linkspec::{CircTarget, OwnedCircTarget, RelayId};
use tor_llcrypto::pk::ed25519::Ed25519Identity; use tor_llcrypto::pk::ed25519::Ed25519Identity;
use tor_netdir::{HsDirOp, NetDir, Relay}; use tor_netdir::{HsDirOp, NetDir, Relay};
use tor_netdoc::doc::hsdesc::{HsDesc, IntroPointDesc}; 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 tor_rtcompat::{Runtime, SleepProviderExt as _, TimeoutError};
use crate::relay_info::ipt_to_circtarget; use crate::relay_info::ipt_to_circtarget;
@ -812,6 +817,72 @@ impl<'c, R: Runtime, M: MocksForConnect<R>> Context<'c, R, M> {
intro_index, 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 Err(internal!("sending INTRODUCE1 is not yet implemented!").into()) // TODO HS
} }
@ -830,6 +901,48 @@ impl<'c, R: Runtime, M: MocksForConnect<R>> Context<'c, R, M> {
ipt: &UsableIntroPt<'_>, ipt: &UsableIntroPt<'_>,
rendezvous: Rendezvous<'c, R, M>, rendezvous: Rendezvous<'c, R, M>,
) -> Result<Arc<ClientCirc!(R, M)>, FAE> { ) -> Result<Arc<ClientCirc!(R, M)>, 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 todo!() // HS implement
} }
} }
@ -890,7 +1003,7 @@ trait MocksForConnect<R>: Clone {
type HsCircPool: MockableCircPool<R>; type HsCircPool: MockableCircPool<R>;
/// A random number generator /// A random number generator
type Rng: rand::Rng; type Rng: rand::Rng + rand::CryptoRng;
/// Tell tests we got this descriptor text /// Tell tests we got this descriptor text
fn test_got_desc(&self, desc: &HsDesc) { fn test_got_desc(&self, desc: &HsDesc) {
@ -935,6 +1048,15 @@ trait MockableClientCirc: Debug {
msg: AnyRelayMsg, msg: AnyRelayMsg,
reply_handler: impl MsgHandler + Send + 'static, reply_handler: impl MsgHandler + Send + 'static,
) -> tor_proto::Result<()>; ) -> 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<R: Runtime> MocksForConnect<R> for () { impl<R: Runtime> MocksForConnect<R> for () {
@ -977,6 +1099,16 @@ impl MockableClientCirc for ClientCirc {
) -> tor_proto::Result<()> { ) -> tor_proto::Result<()> {
ClientCirc::send_control_message(self, msg, reply_handler).await 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] #[async_trait]
@ -1107,6 +1239,16 @@ mod test {
) -> tor_proto::Result<()> { ) -> tor_proto::Result<()> {
todo!() 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] #[traced_test]