Add some missing documentation

This commit is contained in:
Nick Mathewson 2020-09-09 18:58:33 -04:00
parent e02255ec00
commit 34843f1fde
2 changed files with 76 additions and 7 deletions

View File

@ -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<C: ChanTarget>(target: &C) -> Result<Channel<TlsStream<net::TcpStream>>> {
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<C: ChanTarget>(target: &C) -> Result<Channel<TlsStream<net::Tcp
info!("Negotiating TLS with {}", addr);
let tlscon = connector.connect("ignored", stream).await?;
info!("TLS negotiated.");
// Extract the peer certificate now before we wrap the tlscon.
let peer_cert = tlscon
.peer_certificate()?
.ok_or(Error::Misc("Somehow a TLS server didn't show a cert?"))?
.to_der()?;
let chan = OutboundClientHandshake::new(tlscon).connect().await?;
info!("version negotiated and cells read.");
info!("Version negotiated and cells read.");
let chan = chan.check(target, &peer_cert)?;
info!("Certs validated");
info!("Certificates validated; peer authenticated.");
let chan = chan.finish(&addr.ip()).await?;
info!("Channel complete.");
Ok(chan)
}
/// Load a network directory from `~/src/chutney/net/nodes/000a/`
fn get_netdir() -> Result<tor_netdir::NetDir> {
let mut pb: PathBuf = std::env::var_os("HOME").unwrap().into();
pb.push("src");

View File

@ -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<T> = futures_codec::Framed<T, codec::ChannelCodec>;
/// A raw client channel on which nothing has been done.
pub struct OutboundClientHandshake<T: AsyncRead + AsyncWrite + Unpin> {
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<T: AsyncRead + AsyncWrite + Unpin> {
link_protocol: u16,
tls: CellFrame<T>,
@ -33,21 +41,28 @@ pub struct UnverifiedChannel<T: AsyncRead + AsyncWrite + Unpin> {
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<T: AsyncRead + AsyncWrite + Unpin> {
link_protocol: u16,
tls: CellFrame<T>,
}
/// An open client channel, ready to send and receive tor cells.
pub struct Channel<T: AsyncRead + AsyncWrite + Unpin> {
link_protocol: u16,
tls: CellFrame<T>,
}
impl<T: AsyncRead + AsyncWrite + Unpin> OutboundClientHandshake<T> {
/// 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<UnverifiedChannel<T>> {
// Send versions cell
{
@ -58,6 +73,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin> OutboundClientHandshake<T> {
// 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<T: AsyncRead + AsyncWrite + Unpin> OutboundClientHandshake<T> {
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<msg::Certs> = None;
let mut netinfo: Option<msg::Netinfo> = None;
let mut seen_authchallenge = false;
@ -121,6 +140,7 @@ impl<T: AsyncRead + AsyncWrite + Unpin> OutboundClientHandshake<T> {
}
}
// 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<T: AsyncRead + AsyncWrite + Unpin> OutboundClientHandshake<T> {
}
impl<T: AsyncRead + AsyncWrite + Unpin> UnverifiedChannel<T> {
/// 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<U: ChanTarget>(self, peer: &U, peer_cert: &[u8]) -> Result<VerifiedChannel<T>> {
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<T: AsyncRead + AsyncWrite + Unpin> UnverifiedChannel<T> {
));
}
// 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<T: AsyncRead + AsyncWrite + Unpin> UnverifiedChannel<T> {
));
}
// 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<T: AsyncRead + AsyncWrite + Unpin> UnverifiedChannel<T> {
}
impl<T: AsyncRead + AsyncWrite + Unpin> VerifiedChannel<T> {
/// 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<Channel<T>> {
let netinfo = msg::Netinfo::for_client(*peer_addr);
self.tls.send(netinfo.into()).await?;