Add `X509_STORE_CTX_get0_cert` interface

This method reliably retrieves the certificate the `X509_STORE_CTX` is
verifying, unlike `X509_STORE_CTX_get_current_cert`, which may return
the "problematic" cert when verification fails.
This commit is contained in:
James Larisch 2025-05-19 11:02:56 -04:00 committed by Kornel
parent 6e35abb2cd
commit eefc7b7265
3 changed files with 49 additions and 5 deletions

View File

@ -56,17 +56,29 @@ fn no_error_when_trusted_and_callback_returns_true() {
#[test] #[test]
fn callback_receives_correct_certificate() { fn callback_receives_correct_certificate() {
let server = Server::builder().build(); // Server sends the full chain (leaf + root)...
let server = Server::builder_full_chain().build();
// but client doesn't load the root as trusted.
// So we expect an error.
let mut client = server.client(); let mut client = server.client();
let expected = "59172d9313e84459bcff27f967e79e6e9217e584"; let leaf_sha1 = "59172d9313e84459bcff27f967e79e6e9217e584";
let root_sha1 = "c0cbdf7cdd03c9773e5468e1f6d2da7d5cbb1875";
client.ctx().set_verify(SslVerifyMode::PEER); client.ctx().set_verify(SslVerifyMode::PEER);
client.ctx().set_cert_verify_callback(move |x509| { client.ctx().set_cert_verify_callback(move |x509| {
assert!(!x509.verify_cert().unwrap()); assert!(!x509.verify_cert().unwrap());
// This is set to the root, since that's the problematic cert.
assert!(x509.current_cert().is_some()); assert!(x509.current_cert().is_some());
// This is set to the leaf, since that's the cert we're verifying.
assert!(x509.cert().is_some());
assert!(x509.verify_result().is_err()); assert!(x509.verify_result().is_err());
let cert = x509.current_cert().unwrap();
let digest = cert.digest(MessageDigest::sha1()).unwrap(); let root = x509.current_cert().unwrap();
assert_eq!(hex::encode(digest), expected); let digest = root.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(digest), root_sha1);
let leaf = x509.cert().unwrap();
let digest = leaf.digest(MessageDigest::sha1()).unwrap();
assert_eq!(hex::encode(digest), leaf_sha1);
true true
}); });

View File

@ -36,6 +36,24 @@ impl Server {
} }
} }
/// Serves the leaf and the root together.
pub fn builder_full_chain() -> Builder {
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
// Uses certs.pem instead of cert.pem.
ctx.set_certificate_chain_file("test/certs.pem").unwrap();
ctx.set_private_key_file("test/key.pem", SslFiletype::PEM)
.unwrap();
Builder {
ctx,
ssl_cb: Box::new(|_| {}),
io_cb: Box::new(|_| {}),
err_cb: Box::new(|_| {}),
should_error: false,
expected_connections_count: 1,
}
}
pub fn client(&self) -> ClientBuilder { pub fn client(&self) -> ClientBuilder {
ClientBuilder { ClientBuilder {
ctx: SslContext::builder(SslMethod::tls()).unwrap(), ctx: SslContext::builder(SslMethod::tls()).unwrap(),

View File

@ -209,6 +209,20 @@ impl X509StoreContextRef {
} }
} }
} }
/// Returns a reference to the certificate being verified.
/// May return None if a raw public key is being verified.
#[corresponds(X509_STORE_CTX_get0_cert)]
pub fn cert(&self) -> Option<&X509Ref> {
unsafe {
let ptr = ffi::X509_STORE_CTX_get0_cert(self.as_ptr());
if ptr.is_null() {
None
} else {
Some(X509Ref::from_ptr(ptr))
}
}
}
} }
/// A builder used to construct an `X509`. /// A builder used to construct an `X509`.