diff options
author | Alex Auvolat <alex@adnab.me> | 2020-04-12 19:00:30 +0200 |
---|---|---|
committer | Alex Auvolat <alex@adnab.me> | 2020-04-12 19:00:30 +0200 |
commit | d2814b5c3374f8b99a81dbb9fa3614c875cfc5e6 (patch) | |
tree | 08309e6d85dea5c28f4c12df151ed1b3bdb6bec9 /src | |
parent | d1e8f78b2cd28f4514ad6f7d54aae6aaa4ef3f15 (diff) | |
download | garage-d2814b5c3374f8b99a81dbb9fa3614c875cfc5e6.tar.gz garage-d2814b5c3374f8b99a81dbb9fa3614c875cfc5e6.zip |
TLS works \o/
So, the issues were:
- webpki does not support IP addresses as DNS names in URLs,
so I hacked the HttpsConnector to always provide a fixed string
as the DNS name for server certificate validation
- the certificate requied a SAN section which was complicated to build
but eventually the solution is there in genkeys.sh
Diffstat (limited to 'src')
-rw-r--r-- | src/rpc_client.rs | 11 | ||||
-rw-r--r-- | src/rpc_server.rs | 2 | ||||
-rw-r--r-- | src/tls_util.rs | 152 |
3 files changed, 158 insertions, 7 deletions
diff --git a/src/rpc_client.rs b/src/rpc_client.rs index 247f114e..255eb958 100644 --- a/src/rpc_client.rs +++ b/src/rpc_client.rs @@ -8,7 +8,6 @@ use futures::stream::StreamExt; use futures_util::future::FutureExt; use hyper::client::{Client, HttpConnector}; use hyper::{Body, Method, Request, StatusCode}; -use hyper_rustls::HttpsConnector; use crate::data::*; use crate::error::Error; @@ -93,7 +92,7 @@ pub async fn rpc_call( pub enum RpcClient { HTTP(Client<HttpConnector, hyper::Body>), - HTTPS(Client<HttpsConnector<HttpConnector>, hyper::Body>), + HTTPS(Client<tls_util::HttpsConnectorFixedDnsname<HttpConnector>, hyper::Body>), } impl RpcClient { @@ -109,12 +108,11 @@ impl RpcClient { config.root_store.add(crt)?; } - config.set_single_client_cert([&ca_certs[..], &node_certs[..]].concat(), node_key)?; + config.set_single_client_cert([&node_certs[..], &ca_certs[..]].concat(), node_key)?; + // config.dangerous().set_certificate_verifier(Arc::new(tls_util::NoHostnameCertVerifier)); - let mut http_connector = HttpConnector::new(); - http_connector.enforce_http(false); let connector = - HttpsConnector::<HttpConnector>::from((http_connector, Arc::new(config))); + tls_util::HttpsConnectorFixedDnsname::<HttpConnector>::new(config, "garage"); Ok(RpcClient::HTTPS(Client::builder().build(connector))) } else { @@ -161,3 +159,4 @@ impl RpcClient { } } } + diff --git a/src/rpc_server.rs b/src/rpc_server.rs index f42d54ac..17da6f86 100644 --- a/src/rpc_server.rs +++ b/src/rpc_server.rs @@ -120,7 +120,7 @@ pub async fn run_rpc_server( let mut config = rustls::ServerConfig::new(rustls::AllowAnyAuthenticatedClient::new(ca_store)); - config.set_single_cert([&ca_certs[..], &node_certs[..]].concat(), node_key)?; + config.set_single_cert([&node_certs[..], &ca_certs[..]].concat(), node_key)?; let tls_acceptor = Arc::new(TlsAcceptor::from(Arc::new(config))); let mut listener = TcpListener::bind(&bind_addr).await?; diff --git a/src/tls_util.rs b/src/tls_util.rs index a9e16c53..5a17d380 100644 --- a/src/tls_util.rs +++ b/src/tls_util.rs @@ -1,6 +1,20 @@ use std::{fs, io}; +use core::task::{Poll, Context}; +use std::pin::Pin; +use std::sync::Arc; +use core::future::Future; +use futures_util::future::*; +use tokio::io::{AsyncRead, AsyncWrite}; use rustls::internal::pemfile; +use rustls::*; +use hyper::client::HttpConnector; +use hyper::client::connect::Connection; +use hyper::service::Service; +use hyper::Uri; +use hyper_rustls::MaybeHttpsStream; +use tokio_rustls::TlsConnector; +use webpki::DNSNameRef; use crate::error::Error; @@ -44,3 +58,141 @@ pub fn load_private_key(filename: &str) -> Result<rustls::PrivateKey, Error> { } Ok(keys[0].clone()) } + + +// ---- AWFUL COPYPASTA FROM rustls/verifier.rs +// ---- USED TO ALLOW TO VERIFY SERVER CERTIFICATE VALIDITY IN CHAIN +// ---- BUT DISREGARD HOSTNAME PARAMETER + +pub struct NoHostnameCertVerifier; + +type SignatureAlgorithms = &'static [&'static webpki::SignatureAlgorithm]; +static SUPPORTED_SIG_ALGS: SignatureAlgorithms = &[ + &webpki::ECDSA_P256_SHA256, + &webpki::ECDSA_P256_SHA384, + &webpki::ECDSA_P384_SHA256, + &webpki::ECDSA_P384_SHA384, + &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, + &webpki::RSA_PKCS1_2048_8192_SHA256, + &webpki::RSA_PKCS1_2048_8192_SHA384, + &webpki::RSA_PKCS1_2048_8192_SHA512, + &webpki::RSA_PKCS1_3072_8192_SHA384 +]; + +impl rustls::ServerCertVerifier for NoHostnameCertVerifier { + fn verify_server_cert(&self, + roots: &RootCertStore, + presented_certs: &[Certificate], + _dns_name: webpki::DNSNameRef, + _ocsp_response: &[u8]) -> Result<rustls::ServerCertVerified, TLSError> { + + if presented_certs.is_empty() { + return Err(TLSError::NoCertificatesPresented); + } + + let cert = webpki::EndEntityCert::from(&presented_certs[0].0) + .map_err(TLSError::WebPKIError)?; + + let chain = presented_certs.iter() + .skip(1) + .map(|cert| cert.0.as_ref()) + .collect::<Vec<_>>(); + + let trustroots: Vec<webpki::TrustAnchor> = roots.roots + .iter() + .map(|x| x.to_trust_anchor()) + .collect(); + + let now = webpki::Time::try_from(std::time::SystemTime::now()) + .map_err( |_ | TLSError::FailedToGetCurrentTime)?; + + cert.verify_is_valid_tls_server_cert(SUPPORTED_SIG_ALGS, + &webpki::TLSServerTrustAnchors(&trustroots), &chain, now) + .map_err(TLSError::WebPKIError)?; + + Ok(rustls::ServerCertVerified::assertion()) + } +} + + +// ---- AWFUL COPYPASTA FROM HYPER-RUSTLS connector.rs +// ---- ALWAYS USE `garage` AS HOSTNAME FOR TLS VERIFICATION + +#[derive(Clone)] +pub struct HttpsConnectorFixedDnsname<T> { + http: T, + tls_config: Arc<rustls::ClientConfig>, + fixed_dnsname: &'static str, +} + +type BoxError = Box<dyn std::error::Error + Send + Sync>; + +impl HttpsConnectorFixedDnsname<HttpConnector> { + pub fn new(mut tls_config: rustls::ClientConfig, fixed_dnsname: &'static str) -> Self { + let mut http = HttpConnector::new(); + http.enforce_http(false); + tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + Self { + http, + tls_config: Arc::new(tls_config), + fixed_dnsname, + } + } +} + +impl<T> Service<Uri> for HttpsConnectorFixedDnsname<T> + where + T: Service<Uri>, + T::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static, + T::Future: Send + 'static, + T::Error: Into<BoxError>, +{ + type Response = MaybeHttpsStream<T::Response>; + type Error = BoxError; + + #[allow(clippy::type_complexity)] + type Future = + Pin<Box<dyn Future<Output = Result<MaybeHttpsStream<T::Response>, BoxError>> + Send>>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { + match self.http.poll_ready(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), + Poll::Pending => Poll::Pending, + } + } + + fn call(&mut self, dst: Uri) -> Self::Future { + let is_https = dst.scheme_str() == Some("https"); + + if !is_https { + let connecting_future = self.http.call(dst); + + let f = async move { + let tcp = connecting_future.await.map_err(Into::into)?; + + Ok(MaybeHttpsStream::Http(tcp)) + }; + f.boxed() + } else { + let cfg = self.tls_config.clone(); + let connecting_future = self.http.call(dst); + + let dnsname = DNSNameRef::try_from_ascii_str(self.fixed_dnsname) + .expect("Invalid fixed dnsname"); + + let f = async move { + let tcp = connecting_future.await.map_err(Into::into)?; + let connector = TlsConnector::from(cfg); + let tls = connector + .connect(dnsname, tcp) + .await + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + Ok(MaybeHttpsStream::Https(tls)) + }; + f.boxed() + } + } +} |