diff --git a/Cargo.lock b/Cargo.lock index e069f21c..1d7f2ba8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8004,6 +8004,7 @@ dependencies = [ "trc", "types", "utils", + "x509-parser", "xxhash-rust", ] diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index 031146fc..c4fc36b1 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -39,6 +39,7 @@ tokio-rustls = { version = "0.26", optional = true, default-features = false, fe rustls = { version = "0.23.5", optional = true, default-features = false, features = ["std", "aws_lc_rs", "tls12"] } rustls-pki-types = { version = "1", optional = true } aws-lc-rs = { version = "1", optional = true } +x509-parser = { version = "0.18", optional = true } bytes = { version = "1.10", optional = true } mysql_async = { version = "0.36", default-features = false, features = ["default-rustls", "minimal"], optional = true } serde_json = { version = "1.0.64" } @@ -61,7 +62,7 @@ tokio = { version = "1.47", features = ["full"] } # Data Stores rocks = ["rocksdb", "rayon", "num_cpus"] sqlite = ["rusqlite", "rayon", "r2d2", "num_cpus", "lru-cache"] -postgres = ["tokio-postgres", "deadpool", "deadpool-postgres", "tokio-rustls", "rustls", "aws-lc-rs", "rustls-pki-types", "futures", "bytes"] +postgres = ["tokio-postgres", "deadpool", "deadpool-postgres", "tokio-rustls", "rustls", "aws-lc-rs", "rustls-pki-types", "x509-parser", "futures", "bytes"] mysql = ["mysql_async", "futures"] foundation = ["foundationdb", "futures"] fdb-chunked-bm = [] diff --git a/crates/store/src/backend/postgres/tls.rs b/crates/store/src/backend/postgres/tls.rs index b843074a..4ae056e1 100644 --- a/crates/store/src/backend/postgres/tls.rs +++ b/crates/store/src/backend/postgres/tls.rs @@ -22,6 +22,20 @@ use rustls_pki_types::ServerName; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio_postgres::tls::{ChannelBinding, MakeTlsConnect, TlsConnect}; use tokio_rustls::{TlsConnector, client::TlsStream}; +use x509_parser::{ + asn1_rs::oid, + oid_registry::{ + OID_HASH_SHA1, OID_MD5_WITH_RSA, OID_NIST_HASH_SHA256, OID_NIST_HASH_SHA384, + OID_NIST_HASH_SHA512, OID_PKCS1_MD5WITHRSAENC, OID_PKCS1_RSASSAPSS, OID_PKCS1_SHA1WITHRSA, + OID_PKCS1_SHA224WITHRSA, OID_PKCS1_SHA256WITHRSA, OID_PKCS1_SHA384WITHRSA, + OID_PKCS1_SHA512WITHRSA, OID_SHA1_WITH_RSA, OID_SIG_DSA_WITH_SHA1, + OID_SIG_ECDSA_WITH_SHA224, OID_SIG_ECDSA_WITH_SHA256, OID_SIG_ECDSA_WITH_SHA384, + OID_SIG_ECDSA_WITH_SHA512, + }, + parse_x509_certificate, + prelude::X509Certificate, + signature_algorithm::RsaSsaPssParams, +}; #[derive(Clone)] pub struct MakeRustlsConnect { @@ -85,6 +99,47 @@ where pub struct RustlsStream(Pin>>); +fn cb_digest_for_cert(cert: &X509Certificate<'_>) -> Option<&'static digest::Algorithm> { + let sig_alg = cert.signature_algorithm.oid(); + // Signature algorithms that use a digest should use the same digest for channel binding: + if sig_alg == &OID_PKCS1_SHA512WITHRSA || sig_alg == &OID_SIG_ECDSA_WITH_SHA512 { + Some(&digest::SHA512) + } else if sig_alg == &OID_PKCS1_SHA384WITHRSA || sig_alg == &OID_SIG_ECDSA_WITH_SHA384 { + Some(&digest::SHA384) + } else if sig_alg == &OID_PKCS1_MD5WITHRSAENC + || sig_alg == &OID_MD5_WITH_RSA + || sig_alg == &OID_PKCS1_SHA1WITHRSA + || sig_alg == &OID_SHA1_WITH_RSA + || sig_alg == &OID_SIG_DSA_WITH_SHA1 + || sig_alg == &OID_PKCS1_SHA256WITHRSA + || sig_alg == &OID_SIG_ECDSA_WITH_SHA256 + { + // ...apart from MD5 or SHA1, which use SHA256 for channel binding, as per RFC 5929 section 4.1: + Some(&digest::SHA256) + } else if sig_alg == &OID_PKCS1_SHA224WITHRSA || sig_alg == &OID_SIG_ECDSA_WITH_SHA224 { + Some(&digest::SHA224) + } else if sig_alg == &OID_PKCS1_RSASSAPSS { + // For RSASSA-PSS, the hash algorithm is specified in the parameters of the signature algorithm: + let params_any = cert.signature_algorithm.parameters()?; + let pss = RsaSsaPssParams::try_from(params_any).ok()?; + let alg = pss.hash_algorithm_oid(); + if alg == &OID_NIST_HASH_SHA512 { + Some(&digest::SHA512) + } else if alg == &OID_NIST_HASH_SHA384 { + Some(&digest::SHA384) + } else if alg == &OID_NIST_HASH_SHA256 || alg == &OID_HASH_SHA1 { + Some(&digest::SHA256) + } else if alg == &oid!(2.16.840.1.101.3.4.2.4) { + // id-sha224 from RFC 4055 ^ + Some(&digest::SHA224) + } else { + None + } + } else { + None + } +} + impl tokio_postgres::tls::TlsStream for RustlsStream where S: AsyncRead + AsyncWrite + Unpin, @@ -92,10 +147,17 @@ where fn channel_binding(&self) -> ChannelBinding { let (_, session) = self.0.get_ref(); match session.peer_certificates() { - Some(certs) if !certs.is_empty() => { - let sha256 = digest::digest(&digest::SHA256, certs[0].as_ref()); - ChannelBinding::tls_server_end_point(sha256.as_ref().into()) - } + Some(certs) if !certs.is_empty() => match parse_x509_certificate(certs[0].as_ref()) { + Ok((_, cert)) => { + if let Some(digest_alg) = cb_digest_for_cert(&cert) { + let dgst = digest::digest(digest_alg, certs[0].as_ref()); + ChannelBinding::tls_server_end_point(dgst.as_ref().into()) + } else { + ChannelBinding::none() + } + } + Err(_) => ChannelBinding::none(), + }, _ => ChannelBinding::none(), } }