blob: 9da78571722521b01e04cb6a481c26b90540cf79 [file] [log] [blame]
// Copyright 2015-2016 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::io;
use std::net::SocketAddr;
use std::pin::Pin;
use std::{future::Future, marker::PhantomData};
use futures_util::{future, TryFutureExt};
use openssl::pkcs12::ParsedPkcs12;
use openssl::pkey::{PKeyRef, Private};
use openssl::ssl::{ConnectConfiguration, SslConnector, SslContextBuilder, SslMethod, SslOptions};
use openssl::stack::Stack;
use openssl::x509::store::X509StoreBuilder;
use openssl::x509::{X509Ref, X509};
use tokio_openssl::{self, SslStream as TokioTlsStream};
use crate::iocompat::{AsyncIoStdAsTokio, AsyncIoTokioAsStd};
use crate::tcp::Connect;
use crate::tcp::TcpStream;
use crate::xfer::BufDnsStreamHandle;
pub(crate) trait TlsIdentityExt {
fn identity(&mut self, pkcs12: &ParsedPkcs12) -> io::Result<()> {
self.identity_parts(&pkcs12.cert, &pkcs12.pkey, pkcs12.chain.as_ref())
}
fn identity_parts(
&mut self,
cert: &X509Ref,
pkey: &PKeyRef<Private>,
chain: Option<&Stack<X509>>,
) -> io::Result<()>;
}
impl TlsIdentityExt for SslContextBuilder {
fn identity_parts(
&mut self,
cert: &X509Ref,
pkey: &PKeyRef<Private>,
chain: Option<&Stack<X509>>,
) -> io::Result<()> {
self.set_certificate(cert)?;
self.set_private_key(pkey)?;
self.check_private_key()?;
if let Some(chain) = chain {
for cert in chain {
self.add_extra_chain_cert(cert.to_owned())?;
}
}
Ok(())
}
}
/// A TlsStream counterpart to the TcpStream which embeds a secure TlsStream
pub type TlsStream<S> = TcpStream<AsyncIoTokioAsStd<TokioTlsStream<S>>>;
pub(crate) type CompatTlsStream<S> = TlsStream<AsyncIoStdAsTokio<S>>;
fn new(certs: Vec<X509>, pkcs12: Option<ParsedPkcs12>) -> io::Result<SslConnector> {
let mut tls = SslConnector::builder(SslMethod::tls()).map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
})?;
// mutable reference block
{
let openssl_ctx_builder = &mut tls;
// only want to support current TLS versions, 1.2 or future
openssl_ctx_builder.set_options(
SslOptions::NO_SSLV2
| SslOptions::NO_SSLV3
| SslOptions::NO_TLSV1
| SslOptions::NO_TLSV1_1,
);
let mut store = X509StoreBuilder::new().map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
})?;
for cert in certs {
store.add_cert(cert).map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
})?;
}
openssl_ctx_builder
.set_verify_cert_store(store.build())
.map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
})?;
// if there was a pkcs12 associated, we'll add it to the identity
if let Some(pkcs12) = pkcs12 {
openssl_ctx_builder.identity(&pkcs12)?;
}
}
Ok(tls.build())
}
/// Initializes a TlsStream with an existing tokio_tls::TlsStream.
///
/// This is intended for use with a TlsListener and Incoming connections
pub fn tls_stream_from_existing_tls_stream<S: Connect>(
stream: AsyncIoTokioAsStd<TokioTlsStream<AsyncIoStdAsTokio<S>>>,
peer_addr: SocketAddr,
) -> (CompatTlsStream<S>, BufDnsStreamHandle) {
let (message_sender, outbound_messages) = BufDnsStreamHandle::new(peer_addr);
let stream = TcpStream::from_stream_with_receiver(stream, peer_addr, outbound_messages);
(stream, message_sender)
}
async fn connect_tls<S: Connect>(
tls_config: ConnectConfiguration,
dns_name: String,
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
) -> Result<TokioTlsStream<AsyncIoStdAsTokio<S>>, io::Error> {
let tcp = S::connect_with_bind(name_server, bind_addr)
.await
.map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
})?;
let mut stream = tls_config
.into_ssl(&dns_name)
.and_then(|ssl| TokioTlsStream::new(ssl, AsyncIoStdAsTokio(tcp)))
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("tls error: {}", e)))?;
Pin::new(&mut stream).connect().await.map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
})?;
Ok(stream)
}
/// A builder for the TlsStream
#[derive(Default)]
pub struct TlsStreamBuilder<S> {
ca_chain: Vec<X509>,
identity: Option<ParsedPkcs12>,
bind_addr: Option<SocketAddr>,
marker: PhantomData<S>,
}
impl<S: Connect> TlsStreamBuilder<S> {
/// A builder for associating trust information to the `TlsStream`.
pub fn new() -> Self {
Self {
ca_chain: vec![],
identity: None,
bind_addr: None,
marker: PhantomData,
}
}
/// Add a custom trusted peer certificate or certificate authority.
///
/// If this is the 'client' then the 'server' must have it associated as it's `identity`, or have had the `identity` signed by this
pub fn add_ca(&mut self, ca: X509) {
self.ca_chain.push(ca);
}
/// Client side identity for client auth in TLS (aka mutual TLS auth)
#[cfg(feature = "mtls")]
pub fn identity(&mut self, pkcs12: ParsedPkcs12) {
self.identity = Some(pkcs12);
}
/// Sets the address to connect from.
pub fn bind_addr(&mut self, bind_addr: SocketAddr) {
self.bind_addr = Some(bind_addr);
}
/// Creates a new TlsStream to the specified name_server
///
/// [RFC 7858](https://tools.ietf.org/html/rfc7858), DNS over TLS, May 2016
///
/// ```text
/// 3.2. TLS Handshake and Authentication
///
/// Once the DNS client succeeds in connecting via TCP on the well-known
/// port for DNS over TLS, it proceeds with the TLS handshake [RFC5246],
/// following the best practices specified in [BCP195].
///
/// The client will then authenticate the server, if required. This
/// document does not propose new ideas for authentication. Depending on
/// the privacy profile in use (Section 4), the DNS client may choose not
/// to require authentication of the server, or it may make use of a
/// trusted Subject Public Key Info (SPKI) Fingerprint pin set.
///
/// After TLS negotiation completes, the connection will be encrypted and
/// is now protected from eavesdropping.
/// ```
///
/// # Arguments
///
/// * `name_server` - IP and Port for the remote DNS resolver
/// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate
#[allow(clippy::type_complexity)]
pub fn build(
self,
name_server: SocketAddr,
dns_name: String,
) -> (
Pin<Box<dyn Future<Output = Result<CompatTlsStream<S>, io::Error>> + Send>>,
BufDnsStreamHandle,
) {
let (message_sender, outbound_messages) = BufDnsStreamHandle::new(name_server);
let tls_config = match new(self.ca_chain, self.identity) {
Ok(c) => c,
Err(e) => {
return (
Box::pin(future::err(e).map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
})),
message_sender,
)
}
};
let tls_config = match tls_config.configure() {
Ok(c) => c,
Err(e) => {
return (
Box::pin(future::err(e).map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls config error: {}", e),
)
})),
message_sender,
)
}
};
// This set of futures collapses the next tcp socket into a stream which can be used for
// sending and receiving tcp packets.
let stream = Box::pin(
connect_tls(tls_config, dns_name, name_server, self.bind_addr).map_ok(move |s| {
TcpStream::from_stream_with_receiver(
AsyncIoTokioAsStd(s),
name_server,
outbound_messages,
)
}),
);
(stream, message_sender)
}
}