Skip to content
Snippets Groups Projects
Commit e145f2b6 authored by Nick Mathewson's avatar Nick Mathewson :game_die:
Browse files

Merge branch 'rustls_v2' into 'main'

tor-rtcompat: Add support for a rustls backend

Closes #86

See merge request tpo/core/arti!260
parents b893f88c e4e691a7
No related branches found
No related tags found
1 merge request!260tor-rtcompat: Add support for a rustls backend
......@@ -292,6 +292,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "async-rustls"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c86f33abd5a4f3e2d6d9251a9e0c6a7e52eb1113caf893dae8429bf4a53f378"
dependencies = [
"futures-lite",
"rustls",
"webpki",
]
[[package]]
name = "async-std"
version = "1.10.0"
......@@ -2092,6 +2103,21 @@ dependencies = [
"anyhow",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rlimit"
version = "0.6.2"
......@@ -2146,6 +2172,19 @@ dependencies = [
"semver",
]
[[package]]
name = "rustls"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
dependencies = [
"base64",
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "ryu"
version = "1.0.9"
......@@ -2178,6 +2217,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sct"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "security-framework"
version = "2.4.2"
......@@ -2552,16 +2601,6 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-socks"
version = "0.5.1"
......@@ -2984,15 +3023,17 @@ version = "0.0.3"
dependencies = [
"async-io",
"async-native-tls",
"async-rustls",
"async-std",
"async-trait",
"async_executors",
"futures",
"native-tls",
"pin-project",
"rustls",
"tokio",
"tokio-native-tls",
"tokio-util",
"x509-signature",
]
[[package]]
......@@ -3179,6 +3220,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.2.2"
......@@ -3329,6 +3376,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
......@@ -3371,6 +3428,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "x509-signature"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb2bc2a902d992cd5f471ee3ab0ffd6603047a4207384562755b9d6de977518"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "xz2"
version = "0.1.6"
......
......@@ -13,9 +13,10 @@ repository="https://gitlab.torproject.org/tpo/core/arti.git/"
[features]
default = [ ]
async-std = [ "async-std-crate", "async-io", "async-native-tls" , "async_executors/async_std" ]
tokio = [ "tokio-crate", "tokio-util", "tokio-native-tls", "async_executors/tokio_tp" ]
async-std = [ "async-std-crate", "async-io", "async_executors/async_std" ]
tokio = [ "tokio-crate", "tokio-util", "async_executors/tokio_tp" ]
static = [ "native-tls/vendored" ]
rustls = [ "rustls-crate", "async-rustls", "x509-signature" ]
[dependencies]
......@@ -24,12 +25,14 @@ async-trait = "0.1.2"
futures = "0.3"
pin-project = "1"
native-tls = "0.2"
rustls-crate = { package = "rustls", version = "0.19", optional = true, features = [ "dangerous_configuration" ] }
async-std-crate = { package = "async-std", version = "1.7.0", optional = true }
async-io = { version = "1.4.1", optional = true }
async-native-tls = { version = "0.4.0", optional = true }
async-native-tls = { version = "0.4.0" }
tokio-crate = { package = "tokio", version = "1.4", optional = true, features = ["rt", "rt-multi-thread", "io-util", "net", "time" ] }
tokio-util = { version = "0.6", features = ["compat"], optional = true }
tokio-native-tls = { version = "0.3.0", optional = true }
async-rustls = { version = "0.2.0", optional = true }
x509-signature = { version = "0.5.0", optional = true }
......@@ -2,7 +2,11 @@
pub use crate::impls::async_std::create_runtime as create_runtime_impl;
use crate::{compound::CompoundRuntime, SpawnBlocking};
use crate::impls::async_std::NativeTlsAsyncStd;
use crate::impls::native_tls::NativeTlsProvider;
#[cfg(feature = "rustls")]
use crate::impls::rustls::RustlsProvider;
use async_std_crate::net::TcpStream;
use async_executors::AsyncStd;
......@@ -10,14 +14,31 @@ use async_executors::AsyncStd;
#[derive(Clone)]
pub struct AsyncStdRuntime {
/// The actual runtime object.
inner: Inner,
inner: NativeTlsInner,
}
/// Implementation type for AsyncStdRuntime.
type Inner = CompoundRuntime<AsyncStd, AsyncStd, AsyncStd, NativeTlsAsyncStd>;
type NativeTlsInner = CompoundRuntime<AsyncStd, AsyncStd, AsyncStd, NativeTlsProvider<TcpStream>>;
crate::opaque::implement_opaque_runtime! {
AsyncStdRuntime { inner : NativeTlsInner }
}
#[cfg(feature = "rustls")]
/// A [`Runtime`](crate::Runtime) powered by `async_std` and `rustls`.
#[derive(Clone)]
pub struct AsyncStdRustlsRuntime {
/// The actual runtime object.
inner: RustlsInner,
}
/// Implementation type for AsyncStdRustlsRuntime.
#[cfg(feature = "rustls")]
type RustlsInner = CompoundRuntime<AsyncStd, AsyncStd, AsyncStd, RustlsProvider<TcpStream>>;
#[cfg(feature = "rustls")]
crate::opaque::implement_opaque_runtime! {
AsyncStdRuntime { inner : Inner }
AsyncStdRustlsRuntime { inner: RustlsInner }
}
/// Return a new async-std-based [`Runtime`](crate::Runtime).
......@@ -25,11 +46,19 @@ crate::opaque::implement_opaque_runtime! {
/// Generally you should call this function only once, and then use
/// [`Clone::clone()`] to create additional references to that
/// runtime.
pub fn create_runtime() -> std::io::Result<AsyncStdRuntime> {
let rt = create_runtime_impl();
Ok(AsyncStdRuntime {
inner: CompoundRuntime::new(rt, rt, rt, NativeTlsAsyncStd::default()),
inner: CompoundRuntime::new(rt, rt, rt, NativeTlsProvider::default()),
})
}
/// Return a new [`Runtime`](crate::Runtime) based on `async_std` and `rustls`.
#[cfg(feature = "rustls")]
pub fn create_rustls_runtime() -> std::io::Result<AsyncStdRustlsRuntime> {
let rt = create_runtime_impl();
Ok(AsyncStdRustlsRuntime {
inner: CompoundRuntime::new(rt, rt, rt, RustlsProvider::default()),
})
}
......
......@@ -7,3 +7,8 @@ pub(crate) mod async_std;
#[cfg(all(feature = "tokio"))]
pub(crate) mod tokio;
#[cfg(all(feature = "rustls"))]
pub(crate) mod rustls;
pub(crate) mod native_tls;
......@@ -5,8 +5,6 @@
//!
//! We'll probably want to support tokio as well in the future.
use std::convert::TryInto;
/// Types used for networking (async_std implementation)
mod net {
use crate::traits;
......@@ -113,74 +111,8 @@ mod net {
}
}
/// Implement TLS using async_std and async_native_tls.
mod tls {
use async_std_crate::net::TcpStream;
use async_trait::async_trait;
use futures::io::{AsyncRead, AsyncWrite};
use std::convert::TryFrom;
use std::io::{Error as IoError, Result as IoResult};
/// The TLS-over-TCP type returned by this module.
#[allow(unreachable_pub)] // not actually unreachable; depends on features
pub type TlsStream = async_native_tls::TlsStream<TcpStream>;
/// A connection factory for use with async_std.
pub struct TlsConnector {
/// The internal connector that we're wrapping with a new API
connector: async_native_tls::TlsConnector,
}
impl TryFrom<native_tls::TlsConnectorBuilder> for TlsConnector {
type Error = std::convert::Infallible;
fn try_from(builder: native_tls::TlsConnectorBuilder) -> Result<TlsConnector, Self::Error> {
let connector = builder.into();
Ok(TlsConnector { connector })
}
}
#[async_trait]
impl crate::traits::TlsConnector<TcpStream> for TlsConnector {
type Conn = TlsStream;
async fn negotiate_unvalidated(
&self,
stream: TcpStream,
hostname: &str,
) -> IoResult<Self::Conn> {
let conn = self
.connector
.connect(hostname, stream)
.await
.map_err(|e| IoError::new(std::io::ErrorKind::Other, e))?;
Ok(conn)
}
}
impl<S> crate::traits::CertifiedConn for async_native_tls::TlsStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
fn peer_certificate(&self) -> IoResult<Option<Vec<u8>>> {
let cert = self.peer_certificate();
match cert {
Ok(Some(c)) => {
let der = c
.to_der()
.map_err(|e| IoError::new(std::io::ErrorKind::Other, e))?;
Ok(Some(der))
}
Ok(None) => Ok(None),
Err(e) => Err(IoError::new(std::io::ErrorKind::Other, e)),
}
}
}
}
// ==============================
use async_std_crate::net::TcpStream;
use futures::{Future, FutureExt};
use std::pin::Pin;
use std::time::Duration;
......@@ -204,26 +136,3 @@ impl SpawnBlocking for async_executors::AsyncStd {
async_executors::AsyncStd::block_on(f)
}
}
/// A TlsProvider that uses native_tls and works with the AsyncStd executor.
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct NativeTlsAsyncStd {}
impl TlsProvider<TcpStream> for NativeTlsAsyncStd {
type TlsStream = tls::TlsStream;
type Connector = tls::TlsConnector;
fn tls_connector(&self) -> tls::TlsConnector {
let mut builder = native_tls::TlsConnector::builder();
// These function names are scary, but they just mean that we
// aren't checking whether the signer of this cert
// participates in the web PKI, and we aren't checking the
// hostname in the cert.
builder
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true);
builder.try_into().expect("Couldn't build a TLS connector!")
}
}
//! Implementation for using `native_tls`
use crate::traits::{CertifiedConn, TlsConnector, TlsProvider};
use async_trait::async_trait;
use futures::{AsyncRead, AsyncWrite};
use std::{
convert::TryInto,
io::{Error as IoError, Result as IoResult},
};
/// A [`TlsProvider`] that uses `native_tls`.
///
/// It supports wrapping any reasonable stream type that implements `AsyncRead` + `AsyncWrite`.
#[non_exhaustive]
pub struct NativeTlsProvider<S> {
/// Phantom data to ensure proper variance.
_phantom: std::marker::PhantomData<fn(S) -> S>,
}
impl<S> CertifiedConn for async_native_tls::TlsStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
fn peer_certificate(&self) -> IoResult<Option<Vec<u8>>> {
let cert = self.peer_certificate();
match cert {
Ok(Some(c)) => {
let der = c
.to_der()
.map_err(|e| IoError::new(std::io::ErrorKind::Other, e))?;
Ok(Some(der))
}
Ok(None) => Ok(None),
Err(e) => Err(IoError::new(std::io::ErrorKind::Other, e)),
}
}
}
/// An implementation of [`TlsConnector`] built with `native_tls`.
pub struct NativeTlsConnector<S> {
/// The inner connector object.
connector: async_native_tls::TlsConnector,
/// Phantom data to ensure proper variance.
_phantom: std::marker::PhantomData<fn(S) -> S>,
}
#[async_trait]
impl<S> TlsConnector<S> for NativeTlsConnector<S>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Conn = async_native_tls::TlsStream<S>;
async fn negotiate_unvalidated(&self, stream: S, sni_hostname: &str) -> IoResult<Self::Conn> {
let conn = self
.connector
.connect(sni_hostname, stream)
.await
.map_err(|e| IoError::new(std::io::ErrorKind::Other, e))?;
Ok(conn)
}
}
impl<S> TlsProvider<S> for NativeTlsProvider<S>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Connector = NativeTlsConnector<S>;
type TlsStream = async_native_tls::TlsStream<S>;
fn tls_connector(&self) -> Self::Connector {
let mut builder = native_tls::TlsConnector::builder();
// These function names are scary, but they just mean that we
// aren't checking whether the signer of this cert
// participates in the web PKI, and we aren't checking the
// hostname in the cert.
builder
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true);
let connector = builder.try_into().expect("Couldn't build a TLS connector!");
NativeTlsConnector {
connector,
_phantom: std::marker::PhantomData,
}
}
}
impl<S> NativeTlsProvider<S> {
/// Construct a new [`NativeTlsProvider`.]
pub(crate) fn new() -> Self {
NativeTlsProvider {
_phantom: std::marker::PhantomData,
}
}
}
impl<S> Default for NativeTlsProvider<S> {
fn default() -> Self {
Self::new()
}
}
// We have to provide this ourselves, since derive(Clone) wrongly infers a
// `where S: Clone` bound (from the generic argument).
impl<S> Clone for NativeTlsProvider<S> {
fn clone(&self) -> Self {
Self {
_phantom: std::marker::PhantomData,
}
}
}
//! Implementation for using Rustls with a runtime.
use crate::traits::{CertifiedConn, TlsConnector, TlsProvider};
use async_rustls::webpki::{DNSNameRef, Error as WebpkiError};
use async_trait::async_trait;
use futures::{AsyncRead, AsyncWrite};
use rustls::{Session, TLSError};
use rustls_crate as rustls;
use std::{
io::{self, Error as IoError, Result as IoResult},
sync::Arc,
};
/// A [`TlsProvider`] that uses `rustls`.
///
/// It supports wrapping any reasonable stream type that implements `AsyncRead` + `AsyncWrite`.
#[non_exhaustive]
pub struct RustlsProvider<S> {
/// Inner `ClientConfig` logic used to create connectors.
config: Arc<async_rustls::rustls::ClientConfig>,
/// Phantom data to ensure proper variance.
_phantom: std::marker::PhantomData<fn(S) -> S>,
}
impl<S> CertifiedConn for async_rustls::client::TlsStream<S> {
fn peer_certificate(&self) -> IoResult<Option<Vec<u8>>> {
let (_, session) = self.get_ref();
Ok(session
.get_peer_certificates()
.and_then(|certs| certs.get(0).map(|c| Vec::from(c.as_ref()))))
}
}
/// An implementation of [`TlsConnector`] built with `rustls`.
pub struct RustlsConnector<S> {
/// The inner connector object.
connector: async_rustls::TlsConnector,
/// Phantom data to ensure proper variance.
_phantom: std::marker::PhantomData<fn(S) -> S>,
}
#[async_trait]
impl<S> TlsConnector<S> for RustlsConnector<S>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Conn = async_rustls::client::TlsStream<S>;
async fn negotiate_unvalidated(&self, stream: S, sni_hostname: &str) -> IoResult<Self::Conn> {
let name = get_dns_name(sni_hostname)?;
self.connector.connect(name, stream).await
}
}
impl<S> TlsProvider<S> for RustlsProvider<S>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Connector = RustlsConnector<S>;
type TlsStream = async_rustls::client::TlsStream<S>;
fn tls_connector(&self) -> Self::Connector {
let connector = async_rustls::TlsConnector::from(Arc::clone(&self.config));
RustlsConnector {
connector,
_phantom: std::marker::PhantomData,
}
}
}
impl<S> RustlsProvider<S> {
/// Construct a new [`RustlsProvider`.]
pub(crate) fn new() -> Self {
let mut config = async_rustls::rustls::ClientConfig::new();
// Be afraid: we are overriding the default certificate verification and
// TLS signature checking code! See notes on `Verifier` below for
// details.
//
// Note that the `set_certificate_verifier` function is somewhat
// misnamed: it overrides not only how certificates are verified, but
// also how certificates are used to check the signatures in a TLS
// handshake.
config
.dangerous()
.set_certificate_verifier(std::sync::Arc::new(Verifier {}));
RustlsProvider {
config: Arc::new(config),
_phantom: std::marker::PhantomData,
}
}
}
impl<S> Default for RustlsProvider<S> {
fn default() -> Self {
Self::new()
}
}
// We have to provide this ourselves, since derive(Clone) wrongly infers a
// `where S: Clone` bound (from the generic argument).
impl<S> Clone for RustlsProvider<S> {
fn clone(&self) -> Self {
Self {
config: Arc::clone(&self.config),
_phantom: std::marker::PhantomData,
}
}
}
// We have to provide this ourselves, since derive(Clone) wrongly infers a
// `where S: Clone` bound (from the generic argument).
impl<S> Clone for RustlsConnector<S> {
fn clone(&self) -> Self {
Self {
connector: self.connector.clone(),
_phantom: std::marker::PhantomData,
}
}
}
/// A [`rustls::ServerCertVerifier`] based on the [`x509_signature`] crate.
///
/// This verifier is necessary since Tor relays doesn't participate in the web
/// browser PKI, and as such their certificates won't check out as valid ones.
///
/// What's more, the `webpki` crate rejects most of Tor's certificates as
/// unparsable because they do not contain any extensions: That means we need to
/// replace the TLS-handshake signature checking functions too, since otherwise
/// `rustls` would think all the certificates were invalid.
///
/// Fortunately, the p2p people have provided `x509_signature` for this
/// purpose.
#[derive(Clone, Debug)]
struct Verifier {}
impl rustls::ServerCertVerifier for Verifier {
fn verify_server_cert(
&self,
_roots: &rustls::RootCertStore,
presented_certs: &[rustls::Certificate],
_dns_name: async_rustls::webpki::DNSNameRef,
_ocsp_response: &[u8],
) -> Result<rustls::ServerCertVerified, TLSError> {
// We don't check anything about the certificate at this point other
// than making sure it is well-formed.
//
// When we make a channel, we'll check that it's authenticated by the
// other party's real identity key, inside the Tor handshake.
//
// In theory, we shouldn't have to do even this much: rustls should not
// allow a handshake without a certificate, and the certificate's
// well-formedness should get checked below in one of the
// verify_*_signature functions. But this check is cheap, so let's
// leave it in.
let cert0 = presented_certs
.get(0)
.ok_or(TLSError::NoCertificatesPresented)?;
let _cert = get_cert(cert0)?;
// Note that we don't even check timeliness: Tor uses the presented
// relay certificate just as a container for the relay's public link
// key. Actual timeliness checks will happen later, on the certificates
// that authenticate this one, when we process the relay's CERTS cell in
// `tor_proto::channel::handshake`.
Ok(rustls::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::Certificate,
dss: &rustls::internal::msgs::handshake::DigitallySignedStruct,
) -> Result<rustls::HandshakeSignatureValid, rustls::TLSError> {
let cert = get_cert(cert)?;
let scheme = convert_scheme(dss.scheme)?;
let signature = dss.sig.0.as_ref();
// NOTE:
//
// We call `check_signature` here rather than `check_tls12_signature`.
// That means that we're allowing the other side to use signature
// algorithms that aren't actually supported by TLS 1.2.
//
// It turns out, apparently, unless my experiments are wrong, that
// OpenSSL will happily use PSS with TLS 1.2. At least, it seems to do
// so when invoked via native_tls in the test code for this crate.
cert.check_signature(scheme, message, signature)
.map(|_| rustls::HandshakeSignatureValid::assertion())
.map_err(|_| TLSError::WebPKIError(WebpkiError::InvalidSignatureForPublicKey))
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::Certificate,
dss: &rustls::internal::msgs::handshake::DigitallySignedStruct,
) -> Result<rustls::HandshakeSignatureValid, rustls::TLSError> {
let cert = get_cert(cert)?;
let scheme = convert_scheme(dss.scheme)?;
let signature = dss.sig.0.as_ref();
cert.check_tls13_signature(scheme, message, signature)
.map(|_| rustls::HandshakeSignatureValid::assertion())
.map_err(|_| TLSError::WebPKIError(WebpkiError::InvalidSignatureForPublicKey))
}
}
/// Convert a string into a `DnsNameRef`, if possible.
fn get_dns_name(s: &str) -> IoResult<DNSNameRef> {
DNSNameRef::try_from_ascii_str(s).map_err(|e| IoError::new(io::ErrorKind::InvalidInput, e))
}
/// Parse a `rustls::Certificate` as an `x509_signature::X509Certificate`, if possible.
fn get_cert(c: &rustls::Certificate) -> Result<x509_signature::X509Certificate, TLSError> {
x509_signature::parse_certificate(c.as_ref())
.map_err(|_| TLSError::WebPKIError(async_rustls::webpki::Error::BadDER))
}
/// Convert from the signature scheme type used in `rustls` to the one used in
/// `x509_signature`.
///
/// (We can't just use the x509_signature crate's "rustls" feature to have it
/// use the same enum from `rustls`, because it seems to be on a different
/// version from the rustls we want.)
fn convert_scheme(
scheme: rustls::internal::msgs::enums::SignatureScheme,
) -> Result<x509_signature::SignatureScheme, TLSError> {
use rustls::internal::msgs::enums::SignatureScheme as R;
use x509_signature::SignatureScheme as X;
// Yes, we do allow PKCS1 here. That's fine in practice when PKCS1 is only
// used (as in TLS 1.2) for signatures; the attacks against correctly
// implemented PKCS1 make sense only when it's used for encryption.
Ok(match scheme {
R::RSA_PKCS1_SHA256 => X::RSA_PKCS1_SHA256,
R::ECDSA_NISTP256_SHA256 => X::ECDSA_NISTP256_SHA256,
R::RSA_PKCS1_SHA384 => X::RSA_PKCS1_SHA384,
R::ECDSA_NISTP384_SHA384 => X::ECDSA_NISTP384_SHA384,
R::RSA_PKCS1_SHA512 => X::RSA_PKCS1_SHA512,
R::RSA_PSS_SHA256 => X::RSA_PSS_SHA256,
R::RSA_PSS_SHA384 => X::RSA_PSS_SHA384,
R::RSA_PSS_SHA512 => X::RSA_PSS_SHA512,
R::ED25519 => X::ED25519,
R::ED448 => X::ED448,
R::RSA_PKCS1_SHA1 | R::ECDSA_SHA1_Legacy | R::ECDSA_NISTP521_SHA512 => {
// The `x509-signature` crate doesn't support these, nor should it really.
return Err(TLSError::PeerIncompatibleError(format!(
"Unsupported signature scheme {:?}",
scheme
)));
}
R::Unknown(_) => {
return Err(TLSError::PeerIncompatibleError(format!(
"Unrecognized signature scheme {:?}",
scheme
)))
}
})
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_cvt_scheme() {
use rustls::internal::msgs::enums::SignatureScheme as R;
use x509_signature::SignatureScheme as X;
macro_rules! check_cvt {
{ $id:ident } =>
{ assert_eq!(convert_scheme(R::$id).unwrap(), X::$id); }
}
check_cvt!(RSA_PKCS1_SHA256);
check_cvt!(RSA_PKCS1_SHA384);
check_cvt!(RSA_PKCS1_SHA512);
check_cvt!(ECDSA_NISTP256_SHA256);
check_cvt!(ECDSA_NISTP384_SHA384);
check_cvt!(RSA_PSS_SHA256);
check_cvt!(RSA_PSS_SHA384);
check_cvt!(ED25519);
check_cvt!(ED448);
assert!(convert_scheme(R::RSA_PKCS1_SHA1).is_err());
assert!(convert_scheme(R::Unknown(0x1337)).is_err());
}
}
......@@ -3,10 +3,8 @@
//! This crate helps define a slim API around our async runtime so that we
//! can easily swap it out.
use std::convert::TryInto;
/// Types used for networking (tokio implementation)
mod net {
pub(crate) mod net {
use crate::traits;
use async_trait::async_trait;
......@@ -118,130 +116,8 @@ mod net {
}
}
/// Implement a set of TLS wrappers for use with tokio.
///
/// Right now only tokio_native_tls is supported.
mod tls {
use async_trait::async_trait;
use tokio_util::compat::{Compat, TokioAsyncReadCompatExt as _};
use futures::io::{AsyncRead, AsyncWrite};
use crate::impls::tokio::net::TcpStream;
use std::convert::TryFrom;
use std::io::{Error as IoError, Result as IoResult};
use std::pin::Pin;
use std::task::{Context, Poll};
/// Connection factory for building tls connections with tokio and
/// native_tls.
pub struct TlsConnector {
/// The inner connector object
connector: tokio_native_tls::TlsConnector,
}
impl TryFrom<native_tls::TlsConnectorBuilder> for TlsConnector {
type Error = native_tls::Error;
fn try_from(builder: native_tls::TlsConnectorBuilder) -> native_tls::Result<TlsConnector> {
let connector = builder.build()?.into();
Ok(TlsConnector { connector })
}
}
/// A TLS-over-TCP stream, using Tokio.
pub struct TlsStream {
/// The inner stream object.
s: Compat<tokio_native_tls::TlsStream<tokio_crate::net::TcpStream>>,
}
#[async_trait]
impl crate::traits::TlsConnector<TcpStream> for TlsConnector {
type Conn = TlsStream;
async fn negotiate_unvalidated(
&self,
stream: TcpStream,
hostname: &str,
) -> IoResult<Self::Conn> {
let stream = stream.into_inner();
let conn = self
.connector
.connect(hostname, stream)
.await
.map_err(|e| IoError::new(std::io::ErrorKind::Other, e))?;
let conn = conn.compat();
Ok(TlsStream { s: conn })
}
}
impl AsyncRead for TlsStream {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<IoResult<usize>> {
Pin::new(&mut self.s).poll_read(cx, buf)
}
}
impl AsyncWrite for TlsStream {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<IoResult<usize>> {
Pin::new(&mut self.s).poll_write(cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
Pin::new(&mut self.s).poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<IoResult<()>> {
Pin::new(&mut self.s).poll_close(cx)
}
}
impl crate::traits::CertifiedConn for TlsStream {
fn peer_certificate(&self) -> IoResult<Option<Vec<u8>>> {
let cert = self.s.get_ref().get_ref().peer_certificate();
match cert {
Ok(Some(c)) => {
let der = c
.to_der()
.map_err(|e| IoError::new(std::io::ErrorKind::Other, e))?;
Ok(Some(der))
}
Ok(None) => Ok(None),
Err(e) => Err(IoError::new(std::io::ErrorKind::Other, e)),
}
}
}
}
// ==============================
/// A TlsProvider that uses native_tls and works with the Tokio executor.
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct NativeTlsTokio {}
impl TlsProvider<net::TcpStream> for NativeTlsTokio {
type TlsStream = tls::TlsStream;
type Connector = tls::TlsConnector;
fn tls_connector(&self) -> tls::TlsConnector {
let mut builder = native_tls::TlsConnector::builder();
// These function names are scary, but they just mean that we
// aren't checking whether the signer of this cert
// participates in the web PKI, and we aren't checking the
// hostname in the cert.
builder
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true);
builder.try_into().expect("Couldn't build a TLS connector!")
}
}
use crate::traits::*;
use async_trait::async_trait;
use futures::Future;
......
No preview for this file type
......@@ -161,12 +161,12 @@ fn simple_tls<R: Runtime>(runtime: &R) -> IoResult<()> {
/*
A simple expired self-signed rsa-2048 certificate.
Generated using OpenSSL 1.1.1k with:
Generated by running the make-cert.c program in tor-rtcompat/test-data-helper,
and then making a PFX file using
openssl genpkey -algorithm RSA > test.key
openssl req -new -out - -key test.key > test.csr
openssl x509 -in test.csr -out test.crt -req -signkey test.key -days 0
openssl pkcs12 -export -certpbe PBE-SHA1-3DES -out test.pfx -inkey test.key -in test.crt
The password is "abc".
*/
static PFX_ID: &[u8] = include_bytes!("test.pfx");
// Note that we need to set a password on the pkcs12 file, since apparently
......@@ -242,6 +242,53 @@ macro_rules! runtime_tests {
}
}
macro_rules! tls_runtime_tests {
{ $($id:ident),* $(,)? } => {
#[cfg(feature="tokio")]
mod tokio_native_tls_tests {
use std::io::Result as IoResult;
$(
#[test]
fn $id() -> IoResult<()> {
super::$id(&crate::tokio::create_runtime()?)
}
)*
}
#[cfg(feature="async-std")]
mod async_std_native_tls_tests {
use std::io::Result as IoResult;
$(
#[test]
fn $id() -> IoResult<()> {
super::$id(&crate::async_std::create_runtime()?)
}
)*
}
#[cfg(all(feature="tokio", feature="rustls"))]
mod tokio_rusttls_tests {
use std::io::Result as IoResult;
$(
#[test]
fn $id() -> IoResult<()> {
super::$id(&crate::tokio::create_rustls_runtime()?)
}
)*
}
#[cfg(all(feature="async-std", feature="rustls"))]
mod async_std_rusttls_tests {
use std::io::Result as IoResult;
$(
#[test]
fn $id() -> IoResult<()> {
super::$id(&crate::async_std::create_rustls_runtime()?)
}
)*
}
}
}
runtime_tests! {
small_delay,
small_timeout_ok,
......@@ -249,5 +296,8 @@ runtime_tests! {
tiny_wallclock,
self_connect,
listener_stream,
}
tls_runtime_tests! {
simple_tls,
}
//! Entry points for use with Tokio runtimes.
use crate::impls::tokio::{NativeTlsTokio, TokioRuntimeHandle as Handle};
use crate::impls::native_tls::NativeTlsProvider;
use crate::impls::tokio::TokioRuntimeHandle as Handle;
use async_executors::TokioTp;
use crate::{CompoundRuntime, Runtime, SpawnBlocking};
use std::io::{Error as IoError, ErrorKind, Result as IoResult};
#[cfg(feature = "rustls")]
use crate::impls::rustls::RustlsProvider;
use crate::impls::tokio::net::TcpStream;
/// A [`Runtime`] built around a Handle to a tokio runtime, and `native_tls`.
///
/// # Limitations
......@@ -19,7 +24,19 @@ pub struct TokioRuntimeHandle {
}
/// Implementation type for a TokioRuntimeHandle.
type HandleInner = CompoundRuntime<Handle, Handle, Handle, NativeTlsTokio>;
type HandleInner = CompoundRuntime<Handle, Handle, Handle, NativeTlsProvider<TcpStream>>;
/// A [`Runtime`] built around a Handle to a tokio runtime, and `rustls`.
#[derive(Clone)]
#[cfg(feature = "rustls")]
pub struct TokioRustlsRuntimeHandle {
/// The actual [`CompoundRuntime`] that implements this.
inner: RustlsHandleInner,
}
/// Implementation for a TokioRuntimeRustlsHandle
#[cfg(feature = "rustls")]
type RustlsHandleInner = CompoundRuntime<Handle, Handle, Handle, RustlsProvider<TcpStream>>;
/// A [`Runtime`] built around an owned `TokioTp` executor, and `native_tls`.
#[derive(Clone)]
......@@ -28,8 +45,21 @@ pub struct TokioRuntime {
inner: TokioRuntimeInner,
}
/// A [`Runtime`] built around an owned `TokioTp` executor, and `rustls`.
#[derive(Clone)]
#[cfg(feature = "rustls")]
pub struct TokioRustlsRuntime {
/// The actual [`CompoundRuntime`] that implements this.
inner: TokioRustlsRuntimeInner,
}
/// Implementation type for TokioRuntime.
type TokioRuntimeInner = CompoundRuntime<TokioTp, TokioTp, TokioTp, NativeTlsTokio>;
type TokioRuntimeInner = CompoundRuntime<TokioTp, TokioTp, TokioTp, NativeTlsProvider<TcpStream>>;
/// Implementation type for TokioRustlsRuntime.
#[cfg(feature = "rustls")]
type TokioRustlsRuntimeInner =
CompoundRuntime<TokioTp, TokioTp, TokioTp, RustlsProvider<TcpStream>>;
crate::opaque::implement_opaque_runtime! {
TokioRuntimeHandle { inner : HandleInner }
......@@ -39,11 +69,21 @@ crate::opaque::implement_opaque_runtime! {
TokioRuntime { inner : TokioRuntimeInner }
}
#[cfg(feature = "rustls")]
crate::opaque::implement_opaque_runtime! {
TokioRustlsRuntimeHandle { inner : RustlsHandleInner }
}
#[cfg(feature = "rustls")]
crate::opaque::implement_opaque_runtime! {
TokioRustlsRuntime { inner : TokioRustlsRuntimeInner }
}
impl From<tokio_crate::runtime::Handle> for TokioRuntimeHandle {
fn from(h: tokio_crate::runtime::Handle) -> Self {
let h = Handle::new(h);
TokioRuntimeHandle {
inner: CompoundRuntime::new(h.clone(), h.clone(), h, NativeTlsTokio::default()),
inner: CompoundRuntime::new(h.clone(), h.clone(), h, NativeTlsProvider::default()),
}
}
}
......@@ -51,7 +91,15 @@ impl From<tokio_crate::runtime::Handle> for TokioRuntimeHandle {
/// Create and return a new Tokio multithreaded runtime.
fn create_tokio_runtime() -> IoResult<TokioRuntime> {
crate::impls::tokio::create_runtime().map(|r| TokioRuntime {
inner: CompoundRuntime::new(r.clone(), r.clone(), r, NativeTlsTokio::default()),
inner: CompoundRuntime::new(r.clone(), r.clone(), r, NativeTlsProvider::default()),
})
}
/// Create and return a new Tokio multithreaded runtime configured to use `rustls`.
#[cfg(feature = "rustls")]
fn create_tokio_rustls_runtime() -> IoResult<TokioRustlsRuntime> {
crate::impls::tokio::create_runtime().map(|r| TokioRustlsRuntime {
inner: CompoundRuntime::new(r.clone(), r.clone(), r, RustlsProvider::default()),
})
}
......@@ -69,6 +117,21 @@ pub fn create_runtime() -> std::io::Result<impl Runtime> {
create_tokio_runtime()
}
/// Create a new Tokio-based [`Runtime`] with `rustls`.
///
/// Generally you should call this function only once, and then use
/// [`Clone::clone()`] to create additional references to that
/// runtime.
///
/// Tokio users may want to avoid this function and instead make a
/// runtime using [`current_runtime()`]: this function always _builds_ a
/// runtime, and if you already have a runtime, that isn't what you
/// want with Tokio.
#[cfg(feature = "rustls")]
pub fn create_rustls_runtime() -> std::io::Result<impl Runtime> {
create_tokio_rustls_runtime()
}
/// Try to return an instance of the currently running tokio [`Runtime`].
///
/// # Usage note
......@@ -86,7 +149,19 @@ pub fn current_runtime() -> std::io::Result<TokioRuntimeHandle> {
.map_err(|e| IoError::new(ErrorKind::Other, e))?;
let h = Handle::new(handle);
Ok(TokioRuntimeHandle {
inner: CompoundRuntime::new(h.clone(), h.clone(), h, NativeTlsTokio::default()),
inner: CompoundRuntime::new(h.clone(), h.clone(), h, NativeTlsProvider::default()),
})
}
/// Return an instance of the currently running tokio [`Runtime`], wrapped to
/// use `rustls`.
#[cfg(feature = "rustls")]
pub fn current_runtime_rustls() -> std::io::Result<TokioRustlsRuntimeHandle> {
let handle = tokio_crate::runtime::Handle::try_current()
.map_err(|e| IoError::new(ErrorKind::Other, e))?;
let h = Handle::new(handle);
Ok(TokioRustlsRuntimeHandle {
inner: CompoundRuntime::new(h.clone(), h.clone(), h, RustlsProvider::default()),
})
}
......
......@@ -172,10 +172,29 @@ pub trait CertifiedConn {
/// An object that knows how to wrap a TCP connection (where the type of said TCP
/// connection is `S`) with TLS.
///
/// (Note that because of Tor's peculiarities, this is not a
/// # Usage notes
///
/// Note that because of Tor's peculiarities, this is not a
/// general-purpose TLS type. Unlike typical users, Tor does not want
/// its TLS library to check whether the certificates are signed
/// within the web PKI hierarchy, or what their hostnames are.
/// its TLS library to check whether the certificates used in TLS are signed
/// within the web PKI hierarchy, or what their hostnames are, or even whether
/// they are valid. It *does*, however, check that the subject public key in the
/// certificate is indeed correctly used to authenticate the TLS handshake.
///
/// If you are implementing something other than Tor, this is **not** the
/// functionality you want.
///
/// How can this behavior be remotely safe, even in Tor? It only works for Tor
/// because the certificate that a Tor relay uses in TLS is not actually being
/// used to certify that relay's public key. Instead, the certificate only used
/// as a container for the relay's public key. The real certification happens
/// later, inside the TLS session, when the relay presents a CERTS cell.
///
/// Such sneakiness was especially necessary before TLS 1.3, which encrypts more
/// of the handshake, and before pluggable transports, which make
/// "innocuous-looking TLS handshakes" less important than they once were. Once
/// TLS 1.3 is completely ubiquitous, we might be able to specify a simpler link
/// handshake than Tor uses now.
#[async_trait]
pub trait TlsConnector<S> {
/// The type of connection returned by this connector
......@@ -183,9 +202,11 @@ pub trait TlsConnector<S> {
/// Start a TLS session over the provided TCP stream `stream`.
///
/// Declare `sni_hostname` as the desired hostname, but don't
/// actually check whether the hostname in the certificate matches
/// it.
/// Declare `sni_hostname` as the desired hostname, but don't actually check
/// whether the hostname in the certificate matches it. The connector may
/// send `sni_hostname` as part of its handshake, if it supports
/// [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) or one of
/// the TLS 1.3 equivalents.
async fn negotiate_unvalidated(&self, stream: S, sni_hostname: &str) -> IoResult<Self::Conn>;
}
......@@ -195,6 +216,10 @@ pub trait TlsConnector<S> {
/// This is separate from [`TlsConnector`] because eventually we may
/// eventually want to support multiple `TlsConnector` implementations
/// that use a single [`Runtime`].
///
/// See the [`TlsConnector`] documentation for a discussion of the Tor-specific
/// limitations of this trait: If you are implementing something other than Tor,
/// this is **not** the functionality you want.
pub trait TlsProvider<S> {
/// The Connector object that this provider can return.
type Connector: TlsConnector<S, Conn = Self::TlsStream> + Send + Sync + Unpin;
......
// Helper program based on the tor source code; makes a certificate
// to use in testing our TLS implementation.
//
// This has to be done using OpenSSL's C API since there's no way to emulate
// Tor's particular flavor of weirdness (version 3 certs with no extensions)
// from the OpenSSL CLI.
//
// This is not meant to be used for anything but testing Arti. If you use
// it for something else, you might regret it deeply.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <openssl/opensslv.h>
#include <openssl/err.h>
#include <openssl/asn1.h>
#include <openssl/bio.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/objects.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
X509_NAME *
tor_x509_name_new(const char *cname)
{
int nid;
X509_NAME *name;
if (!(name = X509_NAME_new()))
return NULL;
if ((nid = OBJ_txt2nid("commonName")) == NID_undef) goto error;
if (!(X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC,
(unsigned char*)cname, -1, -1, 0)))
goto error;
return name;
error:
X509_NAME_free(name);
return NULL;
}
X509 *
tor_tls_create_certificate(EVP_PKEY *pkey,
EVP_PKEY *sign_pkey,
const char *cname,
const char *cname_sign,
unsigned int cert_lifetime)
{
/* OpenSSL generates self-signed certificates with random 64-bit serial
* numbers, so let's do that too. */
#define SERIAL_NUMBER_SIZE 8
BIGNUM *serial_number = NULL;
unsigned char serial_tmp[SERIAL_NUMBER_SIZE];
X509 *x509 = NULL;
X509_NAME *name = NULL, *name_issuer=NULL;
time_t start_time = time(NULL);
time_t end_time = start_time + cert_lifetime;
if (!(x509 = X509_new()))
goto error;
if (!(X509_set_version(x509, 2)))
goto error;
{ /* our serial number is 8 random bytes. */
RAND_bytes(serial_tmp, sizeof(serial_tmp));
if (!(serial_number = BN_bin2bn(serial_tmp, sizeof(serial_tmp), NULL)))
goto error;
if (!(BN_to_ASN1_INTEGER(serial_number, X509_get_serialNumber(x509))))
goto error;
}
if (!(name = tor_x509_name_new(cname)))
goto error;
if (!(X509_set_subject_name(x509, name)))
goto error;
if (!(name_issuer = tor_x509_name_new(cname_sign)))
goto error;
if (!(X509_set_issuer_name(x509, name_issuer)))
goto error;
if (!X509_time_adj(X509_get_notBefore(x509),0,&start_time))
goto error;
if (!X509_time_adj(X509_get_notAfter(x509),0,&end_time))
goto error;
if (!X509_set_pubkey(x509, pkey))
goto error;
if (!X509_sign(x509, sign_pkey, EVP_sha256()))
goto error;
goto done;
error:
fprintf(stderr, "Error making certificate\n");
if (x509) {
X509_free(x509);
x509 = NULL;
}
done:
if (serial_number)
BN_clear_free(serial_number);
if (name)
X509_NAME_free(name);
if (name_issuer)
X509_NAME_free(name_issuer);
return x509;
#undef SERIAL_NUMBER_SIZE
}
int
main(int argc, char **argv)
{
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
EVP_PKEY *link = NULL, *sign = NULL;
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
assert(ctx);
if (EVP_PKEY_keygen_init(ctx) <= 0) {
puts("BLAH");
return 1;
}
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048);
int r1 = EVP_PKEY_keygen(ctx, &link);
int r2 = EVP_PKEY_keygen(ctx, &sign);
assert(r1 == 1 && r2 == 1);
X509* x509 = tor_tls_create_certificate(link,
sign,
"Hello",
"World",
86400);
if (!x509) {
return 1;
}
FILE *key = fopen("test.key", "w");
int r3 = PEM_write_PrivateKey(key, link, NULL, NULL, 0 , NULL, NULL);
assert(r3 == 1);
fclose(key);
FILE *cert = fopen("test.crt", "w");
int r4 = PEM_write_X509(cert, x509);
assert(r4 == 1);
fclose(cert);
puts("OK.");
return 0;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment