diff --git a/client-demo/src/main.rs b/client-demo/src/main.rs index 79dabf4c7..5382a4391 100644 --- a/client-demo/src/main.rs +++ b/client-demo/src/main.rs @@ -1,3 +1,14 @@ +//! A minimal client for connecting to the tor network +//! +//! Right now, all the client does is load a directory from disk, and +//! launch an authenticated handshake. +//! +//! It expects to find a local chutney network running in +//! `${HOME}/src/chutney/net/nodes/`. This is hardwired for now, so that +//! I don't accidentally turn it loose on the tor network. + +#![warn(missing_docs)] + mod err; use log::{info, LevelFilter}; @@ -12,12 +23,15 @@ use err::{Error, Result}; use rand::thread_rng; +/// Launch an authenticated channel to a relay. async fn connect(target: &C) -> Result>> { let addr = target .get_addrs() - .get(0) + .get(0) // Instead we might want to try multiple addresses in parallel .ok_or(Error::Misc("No addresses for chosen relay‽"))?; + // These function names are scary, but they just mean that we're skipping + // web pki, and using our own PKI functions. let connector = TlsConnector::new() .danger_accept_invalid_certs(true) .danger_accept_invalid_hostnames(true); @@ -28,21 +42,26 @@ async fn connect(target: &C) -> Result Result { let mut pb: PathBuf = std::env::var_os("HOME").unwrap().into(); pb.push("src"); diff --git a/tor-proto/src/channel.rs b/tor-proto/src/channel.rs index 49f500380..18d60f47f 100644 --- a/tor-proto/src/channel.rs +++ b/tor-proto/src/channel.rs @@ -1,6 +1,9 @@ //! Talking directly (over a TLS connection) to a Tor node - -#![allow(missing_docs)] +//! +//! Right now, we only support connecting to a Tor relay as a client. +//! +//! To do so, launch a TLS connection, then call +//! `OutboundClientHandshake::new()` use arrayref::array_ref; use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; @@ -17,15 +20,20 @@ use tor_llcrypto as ll; use digest::Digest; +/// A list of the link protocols that we support. // We only support version 4 for now, since we don't do padding right static LINK_PROTOCOLS: &[u16] = &[4]; type CellFrame = futures_codec::Framed; +/// A raw client channel on which nothing has been done. pub struct OutboundClientHandshake { tls: T, } +/// A client channel on which versions have been negotiated and the +/// server's handshake has been read, but where the certs have not +/// been checked. pub struct UnverifiedChannel { link_protocol: u16, tls: CellFrame, @@ -33,21 +41,28 @@ pub struct UnverifiedChannel { netinfo_cell: msg::Netinfo, } +/// A client channel on which versions have been negotiated, +/// server's handshake has been read, but the client has not yet +/// finished the handshake. pub struct VerifiedChannel { link_protocol: u16, tls: CellFrame, } +/// An open client channel, ready to send and receive tor cells. pub struct Channel { link_protocol: u16, tls: CellFrame, } impl OutboundClientHandshake { + /// Construct a new OutboundClientHandshake. pub fn new(tls: T) -> Self { Self { tls } } + /// Negotiate a link protocol version with the relay, and read + /// the relay's handshake information. pub async fn connect(mut self) -> Result> { // Send versions cell { @@ -58,6 +73,7 @@ impl OutboundClientHandshake { // Get versions cell. let their_versions: msg::Versions = { + // TODO: this could be turned into another function, I suppose. let mut hdr = [0u8; 5]; self.tls.read(&mut hdr).await?; if hdr[0..3] != [0, 0, ChanCmd::VERSIONS.into()] { @@ -70,14 +86,17 @@ impl OutboundClientHandshake { reader.extract()? }; - // Determine shared versions. + // Determine which link protocol we negotiated. let link_protocol = their_versions .best_shared_link_protocol(LINK_PROTOCOLS) .ok_or_else(|| Error::ChanProto("No shared link protocols".into()))?; - // Now we can switch to using a "Framed". + // Now we can switch to using a "Framed". We can ignore the + // AsyncRead/AsyncWrite aspects of the tls, and just treat it + // as a stream and a sink for cells. let mut tls = futures_codec::Framed::new(self.tls, codec::ChannelCodec::new(link_protocol)); + // Read until we have the netinfo cells. let mut certs: Option = None; let mut netinfo: Option = None; let mut seen_authchallenge = false; @@ -121,6 +140,7 @@ impl OutboundClientHandshake { } } + // If we have certs and netinfo, we can finish authenticating. match (certs, netinfo) { (Some(_), None) => Err(Error::ChanProto("Missing netinfo or closed stream".into())), (None, _) => Err(Error::ChanProto("Missing certs cell".into())), @@ -135,13 +155,36 @@ impl OutboundClientHandshake { } impl UnverifiedChannel { + /// Validate the certificates and keys in the relay's handshake. + /// + /// 'peer' is the peer that we want to make sure we're connecting to. + /// + /// 'peer_cert' is the x.509 certificate that the peer presented during + /// its handshake. + /// + /// This is a separate function because it's likely to be somewhat + /// CPU-intensive. pub fn check(self, peer: &U, peer_cert: &[u8]) -> Result> { use tor_cert::CertType; use tor_checkable::*; + // We need to check the following lines of authentication: + // + // First, to bind the ed identity to the channel. + // peer.get_ed_identity() matches the key in... + // IDENTITY_V_SIGNING cert, which signs... + // SIGNING_V_TLS_CERT cert, which signs peer_cert. + // + // Second, to bind the rsa identity to the ed identity: + // peer.get_rsa_identity() matches the key in... + // the x.509 RSA identity certificate (type 2), which signs... + // the RSA->Ed25519 crosscert (type 7), which signs... + // peer.get_ed_identity(). + let c = &self.certs_cell; let id_sk = c.parse_ed_cert(CertType::IDENTITY_V_SIGNING)?; let sk_tls = c.parse_ed_cert(CertType::SIGNING_V_TLS_CERT)?; + // Part 1: validate ed25519 stuff. let id_sk = id_sk .check_key(&None)? .check_signature() @@ -172,6 +215,7 @@ impl UnverifiedChannel { )); } + // Part 2: validate rsa stuff. let pkrsa = c .get_cert_body(2.into()) // XXX use a constant. .map(ll::util::x509_extract_rsa_subject_kludge) @@ -193,6 +237,10 @@ impl UnverifiedChannel { )); } + // Now that we've done all the verification steps, we can make sure + // that this is the peer we actually wanted. We do this _last_, since + // "this is the wrong peer" is usually a different situation than + // "this peer couldn't even identify itself right." if identity_key != peer.get_ed_identity() { return Err(Error::ChanProto("Peer ed25519 id not as expected".into())); } @@ -209,6 +257,8 @@ impl UnverifiedChannel { } impl VerifiedChannel { + /// Send a 'Netinfo' message to the relay to finish the handshake, + /// and create an open channel. pub async fn finish(mut self, peer_addr: &net::IpAddr) -> Result> { let netinfo = msg::Netinfo::for_client(*peer_addr); self.tls.send(netinfo.into()).await?;