Add `set_cert_verify_callback` (`SSL_CTX_set_cert_verify`)
Add a wrapper for `SSL_CTX_set_cert_verify`, which allows consumers to override the default certificate verification behavior. The binding resembles `SSL_CTX_set_verify`'s. See https://docs.openssl.org/master/man3/SSL_CTX_set_cert_verify_callback/ for more details.
This commit is contained in:
parent
ec3b412cc8
commit
bb373e5550
|
|
@ -64,6 +64,34 @@ where
|
||||||
unsafe { raw_custom_verify_callback(ssl, out_alert, callback) }
|
unsafe { raw_custom_verify_callback(ssl, out_alert, callback) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) unsafe extern "C" fn raw_cert_verify<F>(
|
||||||
|
x509_ctx: *mut ffi::X509_STORE_CTX,
|
||||||
|
_arg: *mut c_void,
|
||||||
|
) -> c_int
|
||||||
|
where
|
||||||
|
F: Fn(&mut X509StoreContextRef) -> bool + 'static + Sync + Send,
|
||||||
|
{
|
||||||
|
// SAFETY: boring provides valid inputs.
|
||||||
|
let ctx = unsafe { X509StoreContextRef::from_ptr_mut(x509_ctx) };
|
||||||
|
|
||||||
|
let ssl_idx = X509StoreContext::ssl_idx().expect("BUG: store context ssl index missing");
|
||||||
|
let verify_idx = SslContext::cached_ex_index::<F>();
|
||||||
|
|
||||||
|
let verify = ctx
|
||||||
|
.ex_data(ssl_idx)
|
||||||
|
.expect("BUG: store context missing ssl")
|
||||||
|
.ssl_context()
|
||||||
|
.ex_data(verify_idx)
|
||||||
|
.expect("BUG: verify callback missing");
|
||||||
|
|
||||||
|
// SAFETY: The callback won't outlive the context it's associated with
|
||||||
|
// because there is no way to get a mutable reference to the `SslContext`,
|
||||||
|
// so the callback can't replace itself.
|
||||||
|
let verify = unsafe { &*(verify as *const F) };
|
||||||
|
|
||||||
|
verify(ctx) as c_int
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) unsafe extern "C" fn ssl_raw_custom_verify<F>(
|
pub(super) unsafe extern "C" fn ssl_raw_custom_verify<F>(
|
||||||
ssl: *mut ffi::SSL,
|
ssl: *mut ffi::SSL,
|
||||||
out_alert: *mut u8,
|
out_alert: *mut u8,
|
||||||
|
|
|
||||||
|
|
@ -1000,6 +1000,49 @@ impl SslContextBuilder {
|
||||||
self.ctx.as_ptr()
|
self.ctx.as_ptr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers a certificate verification callback that replaces the default verification
|
||||||
|
/// process.
|
||||||
|
///
|
||||||
|
/// The callback returns true if the certificate chain is valid, and false if not.
|
||||||
|
/// A viable verification result value (either `Ok(())` or an `Err(X509VerifyError)`) must be
|
||||||
|
/// reflected in the error member of `X509StoreContextRef`, which can be done by calling
|
||||||
|
/// `X509StoreContextRef::set_error`. However, the callback's return value determines
|
||||||
|
/// whether the chain is accepted or not.
|
||||||
|
///
|
||||||
|
/// *Warning*: Providing a complete verification procedure is a complex task. See
|
||||||
|
/// https://docs.openssl.org/master/man3/SSL_CTX_set_cert_verify_callback/#notes for more
|
||||||
|
/// information.
|
||||||
|
///
|
||||||
|
/// TODO: Add the ability to unset the callback by either adding a new function or wrapping the
|
||||||
|
/// callback in an `Option`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This method panics if this `SslContext` is associated with a RPK context.
|
||||||
|
#[corresponds(SSL_CTX_set_cert_verify_callback)]
|
||||||
|
pub fn set_cert_verify_callback<F>(&mut self, callback: F)
|
||||||
|
where
|
||||||
|
F: Fn(&mut X509StoreContextRef) -> bool + 'static + Sync + Send,
|
||||||
|
{
|
||||||
|
#[cfg(feature = "rpk")]
|
||||||
|
assert!(!self.is_rpk, "This API is not supported for RPK");
|
||||||
|
|
||||||
|
// NOTE(jlarisch): Q: Why don't we wrap the callback in an Arc, since
|
||||||
|
// `set_verify_callback` does?
|
||||||
|
// A: I don't think that Arc is necessary, and I don't think one is necessary here.
|
||||||
|
// There's no way to get a mutable reference to the `Ssl` or `SslContext`, which
|
||||||
|
// is what you need to register a new callback.
|
||||||
|
// See the NOTE in `ssl_raw_verify` for confirmation.
|
||||||
|
self.replace_ex_data(SslContext::cached_ex_index::<F>(), callback);
|
||||||
|
unsafe {
|
||||||
|
ffi::SSL_CTX_set_cert_verify_callback(
|
||||||
|
self.as_ptr(),
|
||||||
|
Some(raw_cert_verify::<F>),
|
||||||
|
ptr::null_mut(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Configures the certificate verification method for new connections.
|
/// Configures the certificate verification method for new connections.
|
||||||
#[corresponds(SSL_CTX_set_verify)]
|
#[corresponds(SSL_CTX_set_verify)]
|
||||||
pub fn set_verify(&mut self, mode: SslVerifyMode) {
|
pub fn set_verify(&mut self, mode: SslVerifyMode) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
use crate::hash::MessageDigest;
|
||||||
|
use crate::ssl::test::Server;
|
||||||
|
use crate::ssl::SslVerifyMode;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_when_trusted_but_callback_returns_false() {
|
||||||
|
let mut server = Server::builder();
|
||||||
|
server.should_error();
|
||||||
|
let server = server.build();
|
||||||
|
let mut client = server.client_with_root_ca();
|
||||||
|
client.ctx().set_verify(SslVerifyMode::PEER);
|
||||||
|
client.ctx().set_cert_verify_callback(|x509| {
|
||||||
|
// The cert is OK
|
||||||
|
assert!(x509.verify_cert().unwrap());
|
||||||
|
assert!(x509.current_cert().is_some());
|
||||||
|
assert!(x509.verify_result().is_ok());
|
||||||
|
// But we return false
|
||||||
|
false
|
||||||
|
});
|
||||||
|
|
||||||
|
client.connect_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_error_when_untrusted_but_callback_returns_true() {
|
||||||
|
let server = Server::builder().build();
|
||||||
|
let mut client = server.client();
|
||||||
|
client.ctx().set_verify(SslVerifyMode::PEER);
|
||||||
|
client.ctx().set_cert_verify_callback(|x509| {
|
||||||
|
// The cert is not OK
|
||||||
|
assert!(!x509.verify_cert().unwrap());
|
||||||
|
assert!(x509.current_cert().is_some());
|
||||||
|
assert!(x509.verify_result().is_err());
|
||||||
|
// But we return true
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
client.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_error_when_trusted_and_callback_returns_true() {
|
||||||
|
let server = Server::builder().build();
|
||||||
|
let mut client = server.client_with_root_ca();
|
||||||
|
client.ctx().set_verify(SslVerifyMode::PEER);
|
||||||
|
client.ctx().set_cert_verify_callback(|x509| {
|
||||||
|
// The cert is OK
|
||||||
|
assert!(x509.verify_cert().unwrap());
|
||||||
|
assert!(x509.current_cert().is_some());
|
||||||
|
assert!(x509.verify_result().is_ok());
|
||||||
|
// And we return true
|
||||||
|
true
|
||||||
|
});
|
||||||
|
client.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn callback_receives_correct_certificate() {
|
||||||
|
let server = Server::builder().build();
|
||||||
|
let mut client = server.client();
|
||||||
|
let expected = "59172d9313e84459bcff27f967e79e6e9217e584";
|
||||||
|
client.ctx().set_verify(SslVerifyMode::PEER);
|
||||||
|
client.ctx().set_cert_verify_callback(move |x509| {
|
||||||
|
assert!(!x509.verify_cert().unwrap());
|
||||||
|
assert!(x509.current_cert().is_some());
|
||||||
|
assert!(x509.verify_result().is_err());
|
||||||
|
let cert = x509.current_cert().unwrap();
|
||||||
|
let digest = cert.digest(MessageDigest::sha1()).unwrap();
|
||||||
|
assert_eq!(hex::encode(digest), expected);
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
client.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn callback_receives_correct_chain() {
|
||||||
|
let server = Server::builder().build();
|
||||||
|
let mut client = server.client_with_root_ca();
|
||||||
|
let leaf_sha1 = "59172d9313e84459bcff27f967e79e6e9217e584";
|
||||||
|
let root_sha1 = "c0cbdf7cdd03c9773e5468e1f6d2da7d5cbb1875";
|
||||||
|
client.ctx().set_verify(SslVerifyMode::PEER);
|
||||||
|
client.ctx().set_cert_verify_callback(move |x509| {
|
||||||
|
assert!(x509.verify_cert().unwrap());
|
||||||
|
assert!(x509.current_cert().is_some());
|
||||||
|
assert!(x509.verify_result().is_ok());
|
||||||
|
let chain = x509.chain().unwrap();
|
||||||
|
assert!(chain.len() == 2);
|
||||||
|
let leaf_cert = chain.get(0).unwrap();
|
||||||
|
let leaf_digest = leaf_cert.digest(MessageDigest::sha1()).unwrap();
|
||||||
|
assert_eq!(hex::encode(leaf_digest), leaf_sha1);
|
||||||
|
let root_cert = chain.get(1).unwrap();
|
||||||
|
let root_digest = root_cert.digest(MessageDigest::sha1()).unwrap();
|
||||||
|
assert_eq!(hex::encode(root_digest), root_sha1);
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
client.connect();
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ use crate::x509::{X509Name, X509};
|
||||||
#[cfg(not(feature = "fips"))]
|
#[cfg(not(feature = "fips"))]
|
||||||
use super::CompliancePolicy;
|
use super::CompliancePolicy;
|
||||||
|
|
||||||
|
mod cert_verify;
|
||||||
mod custom_verify;
|
mod custom_verify;
|
||||||
mod private_key_method;
|
mod private_key_method;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue