Commit 5610cec0 authored by Ian Jackson's avatar Ian Jackson
Browse files

Merge branch 'main' into hyper-docs

parents 9bf69f55 7d826e6d
Loading
Loading
Loading
Loading
+200 −7
Original line number Diff line number Diff line
@@ -41,6 +41,15 @@ dependencies = [
 "version_check",
]

[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
 "memchr",
]

[[package]]
name = "ansi_term"
version = "0.12.1"
@@ -166,6 +175,8 @@ dependencies = [
 "hyper",
 "pin-project",
 "thiserror",
 "tls-api",
 "tls-api-native-tls",
 "tokio",
 "tor-error",
 "tor-rtcompat",
@@ -323,7 +334,7 @@ checksum = "9c86f33abd5a4f3e2d6d9251a9e0c6a7e52eb1113caf893dae8429bf4a53f378"
dependencies = [
 "futures-lite",
 "rustls",
 "webpki",
 "webpki 0.21.4",
]

[[package]]
@@ -971,6 +982,19 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"

[[package]]
name = "env_logger"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38"
dependencies = [
 "atty",
 "humantime 1.3.0",
 "log",
 "regex",
 "termcolor",
]

[[package]]
name = "event-listener"
version = "2.5.2"
@@ -1103,6 +1127,12 @@ dependencies = [
 "winapi 0.3.9",
]

[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"

[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
@@ -1387,6 +1417,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"

[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
 "quick-error",
]

[[package]]
name = "humantime"
version = "2.1.0"
@@ -1399,7 +1438,7 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
dependencies = [
 "humantime",
 "humantime 2.1.0",
 "serde",
]

@@ -1997,6 +2036,17 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"

[[package]]
name = "pem"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb"
dependencies = [
 "base64",
 "once_cell",
 "regex",
]

[[package]]
name = "pem-rfc7468"
version = "0.2.4"
@@ -2166,6 +2216,12 @@ dependencies = [
 "unicode-xid",
]

[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"

[[package]]
name = "quickcheck"
version = "1.0.3"
@@ -2184,6 +2240,19 @@ dependencies = [
 "proc-macro2",
]

[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
 "fuchsia-cprng",
 "libc",
 "rand_core 0.3.1",
 "rdrand",
 "winapi 0.3.9",
]

[[package]]
name = "rand"
version = "0.7.3"
@@ -2228,6 +2297,21 @@ dependencies = [
 "rand_core 0.6.3",
]

[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
 "rand_core 0.4.2",
]

[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"

[[package]]
name = "rand_core"
version = "0.5.1"
@@ -2255,6 +2339,15 @@ dependencies = [
 "rand_core 0.5.1",
]

[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
 "rand_core 0.3.1",
]

[[package]]
name = "redox_syscall"
version = "0.2.10"
@@ -2280,6 +2373,8 @@ version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-syntax",
]

@@ -2330,7 +2425,7 @@ dependencies = [
 "libc",
 "once_cell",
 "spin",
 "untrusted",
 "untrusted 0.7.1",
 "web-sys",
 "winapi 0.3.9",
]
@@ -2405,7 +2500,7 @@ dependencies = [
 "log",
 "ring",
 "sct",
 "webpki",
 "webpki 0.21.4",
]

[[package]]
@@ -2456,7 +2551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [
 "ring",
 "untrusted",
 "untrusted 0.7.1",
]

[[package]]
@@ -2718,6 +2813,16 @@ dependencies = [
 "unicode-xid",
]

[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
 "rand 0.4.6",
 "remove_dir_all",
]

[[package]]
name = "tempfile"
version = "3.3.0"
@@ -2732,6 +2837,25 @@ dependencies = [
 "winapi 0.3.9",
]

[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
 "winapi-util",
]

[[package]]
name = "test-cert-gen"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3208d0ae2e3736d4ac2f6ba2229c4d9bbd54080e228e662a7684eabcf13ff419"
dependencies = [
 "pem",
 "tempdir",
]

[[package]]
name = "textwrap"
version = "0.11.0"
@@ -2804,6 +2928,53 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"

[[package]]
name = "tls-api"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7dded74ddc6d4a98f9f94f17f1c4d796e4af3cb5fba9e7655f157a036ee7de0"
dependencies = [
 "anyhow",
 "log",
 "pem",
 "tempdir",
 "thiserror",
 "tokio",
 "void",
 "webpki 0.22.0",
]

[[package]]
name = "tls-api-native-tls"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c547db405b51a4e549f803c980572f3cb3957dff153b04e3e7aebb1fc5f249b4"
dependencies = [
 "anyhow",
 "native-tls",
 "thiserror",
 "tls-api",
 "tls-api-test",
 "tokio",
]

[[package]]
name = "tls-api-test"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "344ab291be7ed9ab296fc28153fe3ac1e430f44c4dfb3f1324a3c09bbbb5f104"
dependencies = [
 "anyhow",
 "env_logger",
 "log",
 "pem",
 "test-cert-gen",
 "tls-api",
 "tokio",
 "untrusted 0.6.2",
 "webpki 0.22.0",
]

[[package]]
name = "tokio"
version = "1.17.0"
@@ -3480,6 +3651,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"

[[package]]
name = "untrusted"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"

[[package]]
name = "untrusted"
version = "0.7.1"
@@ -3532,6 +3709,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"

[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

[[package]]
name = "waker-fn"
version = "1.1.0"
@@ -3666,7 +3849,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [
 "ring",
 "untrusted",
 "untrusted 0.7.1",
]

[[package]]
name = "webpki"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
 "ring",
 "untrusted 0.7.1",
]

[[package]]
@@ -3792,7 +3985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb2bc2a902d992cd5f471ee3ab0ffd6603047a4207384562755b9d6de977518"
dependencies = [
 "ring",
 "untrusted",
 "untrusted 0.7.1",
]

[[package]]
+3 −1
Original line number Diff line number Diff line
@@ -23,14 +23,16 @@ static = [ "arti-client/static" ]
experimental-api = []

[dependencies]
anyhow = "1.0.23"
arti-client = { path="../arti-client", version = "0.0.4"}
hyper = { version = "0.14", features = ["http1", "client", "runtime"] }
pin-project = "1"
tokio = { package = "tokio", version = "1.7", features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros" ] }
thiserror = "1"
tls-api = { version = "0.7" }
tls-api-native-tls = { version = "0.7.0" }
tor-error = { path="../tor-error", version = "0.0.1" }
tor-rtcompat = { path="../tor-rtcompat", version = "0.0.4", features=["tokio"] }

[dev-dependencies]
anyhow = "1.0.23"
tracing-subscriber = "0.3.0"
+2 −4
Original line number Diff line number Diff line
@@ -8,9 +8,7 @@ High-level layer for making http(s) requests the Tor network as a client.
Note that these APIs are NOT covered by semantic versioning guarantees:
we might break them or remove them between patch versions.

`error_detail` -- Make the `TorError` type transparent, and expose the `Error` within.
Note that the resulting APIs are not stable.

`native-tls` (default), `rustls` -- Select TLS libraries to support. 
`native-tls` (default), `rustls` -- Select TLS libraries to use for Tor's purposes.
(The end-to-end TLS to the origin server is separate, and handled via `tls-api`.)

License: MIT OR Apache-2.0
+4 −2
Original line number Diff line number Diff line
/// TODO this ought to support https!
use arti_hyper::*;

use anyhow::Result;
use arti_client::{TorClient, TorClientConfig};
use hyper::Body;
use std::convert::TryInto;
use tls_api::{TlsConnector, TlsConnectorBuilder};
use tor_rtcompat::tokio::TokioNativeTlsRuntime;

#[tokio::main]
@@ -34,8 +34,10 @@ async fn main() -> Result<()> {
    // (This takes a while to gather the necessary consensus state, etc.)
    let tor_client = TorClient::create_bootstrapped(rt, config).await?;

    let tls_connector = tls_api_native_tls::TlsConnector::builder()?.build()?;

    // The `ArtiHttpConnector` lets us make HTTP requests via the Tor network.
    let tor_connector = ArtiHttpConnector::new(tor_client);
    let tor_connector = ArtiHttpConnector::new(tor_client, tls_connector);
    let http = hyper::Client::builder().build::<_, Body>(tor_connector);

    // The rest is just standard usage of Hyper.
+107 −31
Original line number Diff line number Diff line
//! High-level layer for making http(s) requests the Tor network as a client.
//!
//! Work-in-progress.
//! This is **not suitable for use** right now because it does not support HTTPs.

#![deny(missing_docs)]
#![warn(noop_method_call)]
@@ -11,7 +8,6 @@
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::clone_on_ref_ptr)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
@@ -37,6 +33,7 @@
use std::future::Future;
use std::io::Error;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};

use arti_client::{DataStream, IntoTorAddr, TorClient};
@@ -46,6 +43,7 @@ use hyper::http::Uri;
use hyper::service::Service;
use pin_project::pin_project;
use thiserror::Error;
use tls_api::TlsConnector as TlsConn; // This is different from tor_rtompat::TlsConnector
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tor_rtcompat::Runtime;

@@ -62,7 +60,7 @@ pub enum ConnectionError {
        uri: Uri,
    },

    /// Unsupported URI scheme
    /// Missing hostname
    #[error("Missing hostname in {uri:?}")]
    MissingHostname {
        /// URI
@@ -72,6 +70,10 @@ pub enum ConnectionError {
    /// Tor connection failed
    #[error("Tor connection failed")]
    Arti(#[from] arti_client::Error),

    /// TLS connection failed
    #[error("TLS connection failed")]
    TLS(#[source] Arc<anyhow::Error>),
}

/// We implement this for form's sake
@@ -84,6 +86,7 @@ impl tor_error::HasKind for ConnectionError {
            CE::UnsupportedUriScheme{..} => EK::NotImplemented,
            CE::MissingHostname{..}      => EK::BadApiUsage,
            CE::Arti(e)                  => e.kind(),
            CE::TLS(_)                   => EK::RemoteProtocolFailed,
        }
    }
}
@@ -91,29 +94,56 @@ impl tor_error::HasKind for ConnectionError {
/// A `hyper` connector to proxy HTTP connections via the Tor network, using Arti.
///
/// Only supports plaintext HTTP for now.
#[derive(Clone)]
pub struct ArtiHttpConnector<R: Runtime> {
///
/// TC is the TLS to used *across* Tor to connect to the origin server.
/// This is a different Rust type to the TLS used *by* Tor to connect to relays etc.
/// It might even be a different underlying TLS implementation
/// (although that is usually not a particularly good idea).
pub struct ArtiHttpConnector<R: Runtime, TC: TlsConn> {
    /// The client
    client: TorClient<R>,

    /// TLS for using across Tor.
    tls_conn: Arc<TC>,
}

// #[derive(Clone)] infers a TC: Clone bound
impl<R: Runtime, TC: TlsConn> Clone for ArtiHttpConnector<R, TC> {
    fn clone(&self) -> Self {
        let client = self.client.clone();
        let tls_conn = self.tls_conn.clone();
        Self { client, tls_conn }
    }
}

impl<R: Runtime> ArtiHttpConnector<R> {
impl<R: Runtime, TC: TlsConn> ArtiHttpConnector<R, TC> {
    /// Make a new `ArtiHttpConnector` using an Arti `TorClient` object.
    pub fn new(client: TorClient<R>) -> Self {
        Self { client }
    pub fn new(client: TorClient<R>, tls_conn: TC) -> Self {
        let tls_conn = tls_conn.into();
        Self { client, tls_conn }
    }
}

/// Wrapper type that makes an Arti `DataStream` implement necessary traits to be used as
/// a `hyper` connection object (mainly `Connection`).
#[pin_project]
pub struct ArtiHttpConnection {
pub struct ArtiHttpConnection<TC: TlsConn> {
    /// The stream
    #[pin]
    inner: DataStream,
    inner: MaybeHttpsStream<TC>,
}

/// The actual actual stream; might be TLS, might not
#[pin_project(project = MaybeHttpsStreamProj)]
enum MaybeHttpsStream<TC: TlsConn> {
    /// http
    Http(Pin<Box<DataStream>>), // Tc:TlsStream is generally boxed; box this one too

    /// https
    Https(#[pin] TC::TlsStream),
}

impl Connection for ArtiHttpConnection {
impl<TC: TlsConn> Connection for ArtiHttpConnection<TC> {
    fn connected(&self) -> Connected {
        Connected::new()
    }
@@ -121,50 +151,83 @@ impl Connection for ArtiHttpConnection {

// These trait implementations just defer to the inner `DataStream`; the wrapper type is just
// there to implement the `Connection` trait.
impl AsyncRead for ArtiHttpConnection {
impl<TC: TlsConn> AsyncRead for ArtiHttpConnection<TC> {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut ReadBuf<'_>,
    ) -> Poll<Result<(), std::io::Error>> {
        self.project().inner.poll_read(cx, buf)
        match self.project().inner.project() {
            MaybeHttpsStreamProj::Http(ds) => ds.as_mut().poll_read(cx, buf),
            MaybeHttpsStreamProj::Https(t) => t.poll_read(cx, buf),
        }
    }
}

impl AsyncWrite for ArtiHttpConnection {
impl<TC: TlsConn> AsyncWrite for ArtiHttpConnection<TC> {
    fn poll_write(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &[u8],
    ) -> Poll<Result<usize, Error>> {
        self.project().inner.poll_write(cx, buf)
        match self.project().inner.project() {
            MaybeHttpsStreamProj::Http(ds) => ds.as_mut().poll_write(cx, buf),
            MaybeHttpsStreamProj::Https(t) => t.poll_write(cx, buf),
        }
    }

    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        self.project().inner.poll_flush(cx)
        match self.project().inner.project() {
            MaybeHttpsStreamProj::Http(ds) => ds.as_mut().poll_flush(cx),
            MaybeHttpsStreamProj::Https(t) => t.poll_flush(cx),
        }
    }

    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        self.project().inner.poll_shutdown(cx)
        match self.project().inner.project() {
            MaybeHttpsStreamProj::Http(ds) => ds.as_mut().poll_shutdown(cx),
            MaybeHttpsStreamProj::Https(t) => t.poll_shutdown(cx),
        }
    }
}

/// Convert uri to host and port
fn uri_to_host_port(uri: Uri) -> Result<(String, u16), ConnectionError> {
    if uri.scheme() != Some(&Scheme::HTTP) {
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
/// Are we doing TLS?
enum UseTls {
    /// No
    Bare,

    /// Yes
    Tls,
}

/// Convert uri to http[s] host and port, and whether to do tls
fn uri_to_host_port_tls(uri: Uri) -> Result<(String, u16, UseTls), ConnectionError> {
    let use_tls = {
        // Scheme doesn't derive PartialEq so can't be matched on
        let scheme = uri.scheme();
        if scheme == Some(&Scheme::HTTP) {
            UseTls::Bare
        } else if scheme == Some(&Scheme::HTTPS) {
            UseTls::Tls
        } else {
            return Err(ConnectionError::UnsupportedUriScheme { uri });
        }
    };
    let host = match uri.host() {
        Some(h) => h,
        _ => return Err(ConnectionError::MissingHostname { uri }),
    };
    let port = uri.port().map(|x| x.as_u16()).unwrap_or(80);
    let port = uri.port().map(|x| x.as_u16()).unwrap_or(match use_tls {
        UseTls::Tls => 443,
        UseTls::Bare => 80,
    });

    Ok((host.to_owned(), port))
    Ok((host.to_owned(), port, use_tls))
}

impl<R: Runtime> Service<Uri> for ArtiHttpConnector<R> {
    type Response = ArtiHttpConnection;
impl<R: Runtime, TC: TlsConn> Service<Uri> for ArtiHttpConnector<R, TC> {
    type Response = ArtiHttpConnection<TC>;
    type Error = ConnectionError;
    #[allow(clippy::type_complexity)]
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
@@ -178,15 +241,28 @@ impl<R: Runtime> Service<Uri> for ArtiHttpConnector<R> {
        // underlying handles required to make Tor connections internally).
        // We use this to avoid the returned future having to borrow `self`.
        let client = self.client.clone();
        let tls_conn = self.tls_conn.clone();
        Box::pin(async move {
            // Extract the host and port to connect to from the URI.
            let (host, port) = uri_to_host_port(req)?;
            let (host, port, use_tls) = uri_to_host_port_tls(req)?;
            // Initiate a new Tor connection, producing a `DataStream` if successful.
            let addr = (&host as &str, port)
                .into_tor_addr()
                .map_err(arti_client::Error::from)?;
            let ds = client.connect(addr).await?;
            Ok(ArtiHttpConnection { inner: ds })

            let inner = match use_tls {
                UseTls::Tls => {
                    let conn = tls_conn
                        .connect_impl_tls_stream(&host, ds)
                        .await
                        .map_err(|e| ConnectionError::TLS(e.into()))?;
                    MaybeHttpsStream::Https(conn)
                }
                UseTls::Bare => MaybeHttpsStream::Http(Box::new(ds).into()),
            };

            Ok(ArtiHttpConnection { inner })
        })
    }
}
Loading