diff --git a/Cargo.toml b/Cargo.toml index 7a323480..6cb76465 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "edgedb-errors", "edgedb-derive", diff --git a/edgedb-protocol/Cargo.toml b/edgedb-protocol/Cargo.toml index cd73c1e2..d41ac518 100644 --- a/edgedb-protocol/Cargo.toml +++ b/edgedb-protocol/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" rust-version = "1.65" [dependencies] -bytes = "1.0.1" +bytes = "1.5.0" snafu = {version="0.7.0"} uuid = "1.1.2" num-bigint = {version="0.4.3", optional=true} diff --git a/edgedb-tokio/Cargo.toml b/edgedb-tokio/Cargo.toml index 87b33917..b25070df 100644 --- a/edgedb-tokio/Cargo.toml +++ b/edgedb-tokio/Cargo.toml @@ -14,8 +14,8 @@ edgedb-protocol = {path = "../edgedb-protocol", version = "0.6.0" } edgedb-errors = {path = "../edgedb-errors", version = "0.4.1" } edgedb-derive = {path = "../edgedb-derive", version = "0.5.1", optional=true} tokio = { version="1.15", features=["net", "time", "sync", "macros"] } -bytes = "1.0.1" -scram = "0.6.0" +bytes = "1.5.0" +scram = { git="https://github.com/elprans/scram" } serde = { version="1.0", features=["derive"] } serde_json = { version="1.0", optional=true } sha1 = {version="0.10.1", features=["std"]} @@ -23,23 +23,21 @@ base16ct = {version="0.2.0", features=["alloc"]} log = "0.4.8" rand = "0.8" url = "2.1.1" -tls-api = {version="0.9.0", default-features=false, features=["runtime-tokio"]} -tls-api-not-tls = {version="0.9.0", default-features=false, features=["runtime-tokio"]} -tls-api-rustls = {version="0.9.0", default-features=false, features=["runtime-tokio"]} -rustls = {version="0.20.2", features=[ - "dangerous_configuration", # this allows insecure mode -]} -rustls-native-certs = "0.6.1" -rustls-pemfile = "1.0.2" -webpki = "0.22.0" -webpki-roots = "0.22.2" +tls-api = { git = "https://github.com/elprans/rust-tls-api.git", branch = "rustls-22", default-features=false, features=["runtime-tokio"]} +tls-api-not-tls = { git = "https://github.com/elprans/rust-tls-api.git", branch = "rustls-22", default-features=false, features=["runtime-tokio"]} +tls-api-rustls = { git = "https://github.com/elprans/rust-tls-api.git", branch = "rustls-22", default-features=false, features=["runtime-tokio"]} +rustls = "0.22.2" +rustls-native-certs = "0.7.0" +rustls-pemfile = "2.0.0" +webpki = { package = "rustls-webpki", version = "0.102.2", features = ["std"], default-features = false } +webpki-roots = "0.26.1" async-trait = "0.1.52" anyhow = "1.0.53" # needed for tls-api dirs = { version="5.0.0", optional=true } arc-swap = "1.5.1" once_cell = "1.9.0" tokio-stream = {version="0.1.11", optional=true} -base64 = "0.21" +base64 = "0.21.7" crc16 = "0.4.0" [target.'cfg(target_family="unix")'.dev-dependencies] diff --git a/edgedb-tokio/src/builder.rs b/edgedb-tokio/src/builder.rs index b5489657..4a1e0260 100644 --- a/edgedb-tokio/src/builder.rs +++ b/edgedb-tokio/src/builder.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use std::time::Duration; use base64::Engine; -use rustls::client::ServerCertVerifier; +use rustls::client::danger::ServerCertVerifier; use serde_json::from_slice; use sha1::Digest; use tokio::fs; @@ -1539,9 +1539,9 @@ fn set_credentials(cfg: &mut ConfigInner, creds: &Credentials) } fn validate_certs(data: &str) -> Result<(), Error> { - let anchors = tls::OwnedTrustAnchor::read_all(data) + let root_store = tls::read_root_cert_pem(data) .map_err(|e| ClientError::with_source_ref(e))?; - if anchors.is_empty() { + if root_store.is_empty() { return Err(ClientError::with_message( "PEM data contains no certificate")); } @@ -1786,59 +1786,51 @@ impl ConfigInner { (_, ts) => Ok(ts), } } - fn trust_anchors(&self) -> Vec { - tls::OwnedTrustAnchor::read_all( - self.pem_certificates.as_deref().unwrap_or("") - ).expect("all certificates are verified before") - } fn root_cert_store(&self) -> rustls::RootCertStore { - use CloudCerts::*; - - let mut roots = rustls::RootCertStore::empty(); if self.pem_certificates.is_some() { - roots.add_server_trust_anchors( - self.trust_anchors().into_iter().map(Into::into) - ); + tls::read_root_cert_pem( + self.pem_certificates.as_deref().unwrap_or("") + ).expect("all certificates have been verified previously") } else { - roots.add_server_trust_anchors( - webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { - rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) - }) - ); + let mut root_store = rustls::RootCertStore { + roots: webpki_roots::TLS_SERVER_ROOTS.into() + }; if let Some(certs) = self.cloud_certs { let data = match certs { // Staging certs retrieved from // https://letsencrypt.org/docs/staging-environment/#root-certificates - Staging => include_str!("letsencrypt_staging.pem"), + CloudCerts::Staging => include_str!("letsencrypt_staging.pem"), // Local nebula development root cert found in // nebula/infra/terraform/local/ca/root.certificate.pem - Local => include_str!("nebula_development.pem"), + CloudCerts::Local => include_str!("nebula_development.pem"), }; - let pem = tls::OwnedTrustAnchor::read_all(data) - .expect("embedded certs are correct"); - roots.add_server_trust_anchors( - pem.into_iter().map(Into::into) + root_store.extend( + tls::read_root_cert_pem(data).expect("embedded certs are correct").roots ); } + + root_store } - return roots; } fn make_verifier(&self, tls_security: TlsSecurity) -> Verifier { use TlsSecurity::*; + let root_store = Arc::new(self.root_cert_store()); + match tls_security { - Insecure => Arc::new(tls::NullVerifier) as Verifier, - NoHostVerification => Arc::new(tls::NoHostnameVerifier::new( - self.trust_anchors() - )) as Verifier, - Strict => Arc::new(rustls::client::WebPkiVerifier::new( - self.root_cert_store(), - None, - )) as Verifier, + Insecure => { + Arc::new(tls::NullVerifier) as Verifier + }, + NoHostVerification => { + Arc::new(tls::NoHostnameVerifier::new(root_store)) as Verifier + }, + Strict => { + rustls::client::WebPkiServerVerifier + ::builder(root_store) + .build() + .expect("WebPkiServerVerifier to build correctly") + as Verifier + }, Default => unreachable!(), } } diff --git a/edgedb-tokio/src/raw/connection.rs b/edgedb-tokio/src/raw/connection.rs index cd8f825a..122f1c79 100644 --- a/edgedb-tokio/src/raw/connection.rs +++ b/edgedb-tokio/src/raw/connection.rs @@ -18,7 +18,7 @@ use tokio::io::{AsyncWrite, AsyncWriteExt}; use tokio::io::ReadBuf; use tokio::net::TcpStream; use tokio::time::{Instant, sleep, timeout_at}; -use webpki::DnsNameRef; +use rustls::pki_types::DnsName; use edgedb_protocol::client_message::{ClientMessage, ClientHandshake}; use edgedb_protocol::encoding::{Input, Output}; @@ -335,7 +335,7 @@ async fn connect3(cfg: &Config, tls: &TlsConnectorBox) Address::Tcp(addr@(host,_)) => { let conn = TcpStream::connect(addr).await .map_err(ClientConnectionError::with_source)?; - let is_valid_dns = DnsNameRef::try_from_ascii_str(host).is_ok(); + let is_valid_dns = DnsName::try_from(host.clone()).is_ok(); let host = if !is_valid_dns { // FIXME: https://github.com/rustls/rustls/issues/184 // If self.host is neither an IP address nor a valid DNS @@ -778,7 +778,7 @@ fn is_temporary(e: &Error) -> bool { fn tls_fail(e: anyhow::Error) -> Error { if let Some(e) = e.downcast_ref::() { - if matches!(e, rustls::Error::CorruptMessage) { + if matches!(e, rustls::Error::InvalidMessage(_)) { return ProtocolTlsError::with_message( "corrupt message, possibly server \ does not support TLS connection." diff --git a/edgedb-tokio/src/tls.rs b/edgedb-tokio/src/tls.rs index 4ca14011..1fda22fb 100644 --- a/edgedb-tokio/src/tls.rs +++ b/edgedb-tokio/src/tls.rs @@ -1,92 +1,118 @@ use std::io; use std::sync::Arc; -use std::time::SystemTime; use anyhow::Context; -use rustls::client::{ServerCertVerifier, ServerCertVerified}; -use rustls::{Certificate, ServerName}; +use rustls::{SignatureScheme, DigitallySignedStruct}; +use rustls::client::danger::{ServerCertVerifier, ServerCertVerified}; +use rustls::client::danger::HandshakeSignatureValid; +use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; +use rustls::crypto::ring; +use rustls::crypto::{verify_tls12_signature, verify_tls13_signature}; +use rustls::crypto::WebPkiSupportedAlgorithms; use tls_api::{TlsConnector as _, TlsConnectorBuilder as _}; -use tls_api::{TlsConnectorBox}; -use tls_api_rustls::{TlsConnector}; -use webpki::{SignatureAlgorithm}; - - -static SIG_ALGS: &[&SignatureAlgorithm] = &[ - &webpki::ECDSA_P256_SHA256, - &webpki::ECDSA_P256_SHA384, - &webpki::ECDSA_P384_SHA256, - &webpki::ECDSA_P384_SHA384, - &webpki::ED25519, - &webpki::RSA_PKCS1_2048_8192_SHA256, - &webpki::RSA_PKCS1_2048_8192_SHA384, - &webpki::RSA_PKCS1_2048_8192_SHA512, - &webpki::RSA_PKCS1_3072_8192_SHA384, -]; +use tls_api::TlsConnectorBox; +use tls_api_rustls::TlsConnector; -pub struct NullVerifier; -pub struct NoHostnameVerifier { - trust_anchors: Vec, -} +#[derive(Debug)] +pub struct NullVerifier; #[derive(Debug)] -pub struct OwnedTrustAnchor { - subject: Vec, - spki: Vec, - name_constraints: Option>, +pub struct NoHostnameVerifier { + roots: Arc, + supported: WebPkiSupportedAlgorithms, } impl NoHostnameVerifier { - pub fn new(trust_anchors: Vec) -> Self { + pub fn new(roots: Arc) -> Self { NoHostnameVerifier { - trust_anchors + roots: roots, + supported: ring::default_provider().signature_verification_algorithms, } } } impl ServerCertVerifier for NoHostnameVerifier { fn verify_server_cert(&self, - end_entity: &Certificate, - intermediates: &[Certificate], + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], _server_name: &ServerName, - _scts: &mut dyn Iterator, _ocsp_response: &[u8], - now: SystemTime + now: UnixTime, ) -> Result { - let webpki_now = webpki::Time::try_from(now) - .map_err(|_| rustls::Error::FailedToGetCurrentTime)?; - let end_entity: webpki::EndEntityCert = end_entity.0[..].try_into() - .map_err(|e| { - log::warn!("Could not parse TLS certificate {:#}", e); - pki_error(e) - })?; - let trust_roots = self.trust_anchors.iter() - .map(Into::into) - .collect::>(); - let chain = intermediates.iter() - .map(|c| c.as_ref()) - .collect::>(); - end_entity.verify_is_valid_tls_server_cert( - &SIG_ALGS, - &webpki::TlsServerTrustAnchors(&trust_roots), - &chain, - webpki_now, - ).map_err(pki_error)?; - Ok(ServerCertVerified::assertion()) + let cert = webpki::EndEntityCert::try_from(end_entity).map_err(pki_error)?; + + let result = cert.verify_for_usage( + self.supported.all, + &self.roots.roots, + intermediates, + now, + webpki::KeyUsage::server_auth(), + None, + None, + ); + + match result { + Ok(_) => Ok(ServerCertVerified::assertion()), + Err(e) => Err(pki_error(e)), + } + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls12_signature(message, cert, dss, &self.supported) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13_signature(message, cert, dss, &self.supported) + } + + fn supported_verify_schemes(&self) -> Vec { + ring::default_provider().signature_verification_algorithms.supported_schemes() } } impl ServerCertVerifier for NullVerifier { fn verify_server_cert(&self, - _end_entity: &Certificate, - _intermediates: &[Certificate], + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], _server_name: &ServerName, - _scts: &mut dyn Iterator, _ocsp_response: &[u8], - _now: SystemTime + _now: UnixTime, ) -> Result { Ok(ServerCertVerified::assertion()) } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + ring::default_provider().signature_verification_algorithms.supported_schemes() + } } pub fn connector( @@ -100,78 +126,50 @@ pub fn connector( Ok(connector) } -impl From> for OwnedTrustAnchor { - fn from(src: webpki::TrustAnchor) -> OwnedTrustAnchor { - OwnedTrustAnchor { - subject: src.subject.into(), - spki: src.spki.into(), - name_constraints: src.name_constraints.map(|b| b.into()), - } - } -} - -impl<'a> Into> for &'a OwnedTrustAnchor { - fn into(self) -> webpki::TrustAnchor<'a> { - webpki::TrustAnchor { - subject: &self.subject, - spki: &self.spki, - name_constraints: self.name_constraints.as_deref(), - } - } -} - -impl Into for OwnedTrustAnchor { - fn into(self) -> rustls::OwnedTrustAnchor { - rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( - self.subject, - self.spki, - self.name_constraints, - ) - } -} - -impl OwnedTrustAnchor { - pub fn read_all(data: &str) -> anyhow::Result> { - let mut result = Vec::new(); - let open_data = rustls_pemfile::read_all(&mut io::Cursor::new(data)) - .context("error reading PEM data")?; - for item in open_data { - match item { - rustls_pemfile::Item::X509Certificate(data) => { - result.push( - webpki::TrustAnchor::try_from_cert_der(&data) - .context("certificate data found, \ - but trust anchor is invalid")? - .into() - ); - } - | rustls_pemfile::Item::RSAKey(_) - | rustls_pemfile::Item::PKCS8Key(_) - | rustls_pemfile::Item::ECKey(_) - => { - log::debug!("Skipping private key in cert data"); - } - _ => { - log::debug!("Skipping unknown item cert data"); - } +pub fn read_root_cert_pem(data: &str) -> anyhow::Result { + let mut cursor = io::Cursor::new(data); + let open_data = rustls_pemfile::read_all(&mut cursor); + let mut cert_store = rustls::RootCertStore::empty(); + for item in open_data { + match item { + Ok(rustls_pemfile::Item::X509Certificate(data)) => { + cert_store.add(data) + .context("certificate data found, but is not a valid root certificate")?; + }, + Ok(rustls_pemfile::Item::Pkcs1Key(_)) + | Ok(rustls_pemfile::Item::Pkcs8Key(_)) + | Ok(rustls_pemfile::Item::Sec1Key(_)) + => { + log::debug!("Skipping private key in cert data"); + }, + Ok(rustls_pemfile::Item::Crl(_)) + => { + log::debug!("Skipping CRL in cert data"); + }, + Ok(_) => { + log::debug!("Skipping unknown item cert data"); + }, + Err(e) => { + log::error!("could not parse item in PEM file: {:?}", e); } } - Ok(result) } + Ok(cert_store) } fn pki_error(error: webpki::Error) -> rustls::Error { use webpki::Error::*; match error { - BadDer | BadDerTime => rustls::Error::InvalidCertificateEncoding, + BadDer | BadDerTime + => rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding), InvalidSignatureForPublicKey - => rustls::Error::InvalidCertificateSignature, - UnsupportedSignatureAlgorithm - | UnsupportedSignatureAlgorithmForPublicKey - => rustls::Error::InvalidCertificateSignatureType, + => rustls::Error::InvalidCertificate(rustls::CertificateError::BadSignature), e => { - rustls::Error::InvalidCertificateData( - format!("invalid peer certificate: {}", e)) + rustls::Error::InvalidCertificate( + rustls::CertificateError::Other( + rustls::OtherError(Arc::new(e)), + ), + ) } } }