Fix Store PostgreSQL channel binding (#3212)

The former behaviour to always use a SHA-256 hash of the server
certificate for the tls-server-end-point channel binding was
incorrect with regard to RFC5929, which specifies if the signature
algorithm uses a single hash function, that hash function should be
used, unless the signature algorithm uses MD5 or SHA-1, in which
case the channel binding should use an SHA-256 hash.

This change attempts to align with the behaviour of OpenSSL, used
by PostgreSQL, in using the signature algorithm's hash function.
This commit is contained in:
Hugh Cole-Baker
2026-07-08 22:00:16 +01:00
committed by GitHub
parent 1cadee382f
commit 448f735d55
3 changed files with 69 additions and 5 deletions

1
Cargo.lock generated
View File

@@ -8004,6 +8004,7 @@ dependencies = [
"trc",
"types",
"utils",
"x509-parser",
"xxhash-rust",
]

View File

@@ -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 = []

View File

@@ -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<S>(Pin<Box<TlsStream<S>>>);
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<S> tokio_postgres::tls::TlsStream for RustlsStream<S>
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(),
}
}