Add some missing documentation
This commit is contained in:
parent
e02255ec00
commit
34843f1fde
|
@ -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");
|
||||
|
|
|
@ -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?;
|
||||
|
|
Loading…
Reference in New Issue