diff --git a/src/lib.rs b/src/lib.rs index 4e8b75c..d574099 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ use rustls::pki_types::{CertificateDer, ServerName}; use rustls::server::{Accepted, Acceptor, ProducesTickets}; use rustls::{ CipherSuite, ClientConfig, ClientConnection, Connection, HandshakeKind, ProtocolVersion, - RootCertStore, ServerConfig, SignatureScheme, SupportedProtocolVersion, + ServerConfig, SignatureScheme, SupportedProtocolVersion, }; use not_thread_safe::NotThreadSafe; @@ -416,7 +416,6 @@ pub struct SslContext { raw_options: u64, verify_mode: VerifyMode, verify_depth: c_int, - verify_roots: RootCertStore, verify_x509_store: x509::OwnedX509Store, alpn: Vec>, default_cert_file: Option, @@ -447,7 +446,6 @@ impl SslContext { raw_options: 0, verify_mode: VerifyMode::default(), verify_depth: -1, - verify_roots: RootCertStore::empty(), verify_x509_store: OwnedX509Store::default(), alpn: vec![], default_cert_file: None, @@ -624,12 +622,7 @@ impl SslContext { &mut self, certs: Vec>, ) -> Result<(), error::Error> { - for c in certs { - self.verify_roots - .add(c) - .map_err(error::Error::from_rustls)?; - } - Ok(()) + self.verify_x509_store.add(certs) } fn get_x509_store(&self) -> *mut X509_STORE { @@ -735,8 +728,8 @@ struct Ssl { mode: ConnMode, verify_mode: VerifyMode, verify_depth: c_int, - verify_roots: RootCertStore, verify_server_name: Option>, + verify_x509_store: x509::OwnedX509Store, alpn: Vec>, alpn_callback: callbacks::AlpnCallbackConfig, cert_callback: callbacks::CertCallbackConfig, @@ -776,8 +769,8 @@ impl Ssl { mode: inner.method.mode(), verify_mode: inner.verify_mode, verify_depth: inner.verify_depth, - verify_roots: Self::load_verify_certs(inner)?, verify_server_name: None, + verify_x509_store: Self::load_verify_certs(inner)?, alpn: inner.alpn.clone(), alpn_callback: inner.alpn_callback.clone(), cert_callback: inner.cert_callback.clone(), @@ -1027,7 +1020,7 @@ impl Ssl { let provider = Arc::new(provider::default_provider()); let verifier = Arc::new(verifier::ServerVerifier::new( - self.verify_roots.clone().into(), + self.verify_x509_store.clone(), provider.clone(), self.verify_mode, &self.verify_server_name, @@ -1112,7 +1105,7 @@ impl Ssl { let provider = Arc::new(provider::default_provider()); let verifier = Arc::new( verifier::ClientVerifier::new( - self.verify_roots.clone().into(), + &self.verify_x509_store, provider.clone(), self.verify_mode, ) @@ -1435,20 +1428,18 @@ impl Ssl { } } - fn load_verify_certs(ctx: &SslContext) -> Result { - let mut verify_roots = ctx.verify_roots.clone(); - + fn load_verify_certs(ctx: &SslContext) -> Result { // If verify_roots isn't empty then it was configured with `SSL_CTX_load_verify_file` // or `SSL_CTX_load_verify_dir` and we should use it as-is. - if !ctx.verify_roots.is_empty() { - return Ok(verify_roots); + if !ctx.verify_x509_store.is_empty() { + return Ok(ctx.verify_x509_store.clone()); } // Otherwise, try to load the default cert file or cert dir. + let mut verify_roots = x509::OwnedX509Store::default(); + if let Some(default_cert_file) = &ctx.default_cert_file { - verify_roots.add_parsable_certificates(x509::load_certs( - vec![default_cert_file.to_path_buf()].into_iter(), - )?); + verify_roots.add_from_files([default_cert_file.to_path_buf()])?; } else if let Some(default_cert_dir) = &ctx.default_cert_dir { let entries = match fs::read_dir(default_cert_dir) { Ok(iter) => iter, @@ -1457,7 +1448,7 @@ impl Ssl { .filter_map(|entry| entry.ok()) .map(|dir_entry| dir_entry.path()); - verify_roots.add_parsable_certificates(x509::load_certs(entries)?); + verify_roots.add_from_files(entries)?; } Ok(verify_roots) diff --git a/src/miri.rs b/src/miri.rs index b6dcece..c827c9d 100644 --- a/src/miri.rs +++ b/src/miri.rs @@ -9,6 +9,16 @@ pub extern "C" fn X509_STORE_new() -> *mut X509_STORE { Box::into_raw(Box::new(X509_STORE(()))) } +#[no_mangle] +pub extern "C" fn X509_STORE_get0_objects(s: *mut X509_STORE) -> *mut c_void { + ptr::null_mut() +} + +#[no_mangle] +pub extern "C" fn OPENSSL_sk_num(sk: *mut c_void) -> c_int { + 0 +} + #[no_mangle] pub extern "C" fn X509_STORE_free(ptr: *mut X509_STORE) { if ptr.is_null() { diff --git a/src/verifier.rs b/src/verifier.rs index 336078e..bef1038 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -16,10 +16,10 @@ use rustls::{ pki_types::{CertificateDer, ServerName, UnixTime}, server::danger::{ClientCertVerified, ClientCertVerifier}, server::{ParsedCertificate, WebPkiClientVerifier}, - CertificateError, DigitallySignedStruct, DistinguishedName, Error, RootCertStore, - SignatureScheme, + CertificateError, DigitallySignedStruct, DistinguishedName, Error, SignatureScheme, }; +use crate::x509::OwnedX509Store; use crate::VerifyMode; /// This is a verifier that implements the selection of bad ideas from OpenSSL: @@ -29,7 +29,7 @@ use crate::VerifyMode; /// - that the behaviour defaults to verifying nothing #[derive(Debug)] pub struct ServerVerifier { - root_store: Arc, + x509_store: OwnedX509Store, provider: Arc, @@ -47,13 +47,13 @@ pub struct ServerVerifier { impl ServerVerifier { pub fn new( - root_store: Arc, + x509_store: OwnedX509Store, provider: Arc, mode: VerifyMode, hostname: &Option>, ) -> Self { Self { - root_store, + x509_store, provider, verify_hostname: hostname.clone(), mode, @@ -81,10 +81,14 @@ impl ServerVerifier { now: UnixTime, ) -> Result<(), Error> { let end_entity = ParsedCertificate::try_from(end_entity)?; + let root_store = self.x509_store.to_root_store()?; + if root_store.is_empty() { + log::warn!("X509_STORE has no cached certificates"); + } verify_server_cert_signed_by_trust_anchor( &end_entity, - &self.root_store, + &root_store, intermediates, now, self.provider.signature_verification_algorithms.all, @@ -173,14 +177,16 @@ pub struct ClientVerifier { impl ClientVerifier { pub fn new( - root_store: Arc, + x509_store: &OwnedX509Store, provider: Arc, mode: VerifyMode, ) -> Result { + let root_store = x509_store.to_root_store()?; + let (parent, initial_result) = if !mode.server_must_attempt_client_auth() { (Ok(WebPkiClientVerifier::no_client_auth()), X509_V_OK) } else { - let builder = WebPkiClientVerifier::builder_with_provider(root_store, provider); + let builder = WebPkiClientVerifier::builder_with_provider(root_store.into(), provider); if mode.server_must_verify_client() { (builder.build(), X509_V_ERR_UNSPECIFIED) diff --git a/src/x509.rs b/src/x509.rs index 49af984..bb8b75f 100644 --- a/src/x509.rs +++ b/src/x509.rs @@ -5,11 +5,13 @@ use std::{fs, io}; use openssl_sys::{ d2i_X509, i2d_X509, stack_st_X509, OPENSSL_free, OPENSSL_sk_new_null, OPENSSL_sk_num, - OPENSSL_sk_push, OPENSSL_sk_value, X509_STORE_free, X509_STORE_new, X509_free, OPENSSL_STACK, + OPENSSL_sk_push, OPENSSL_sk_value, X509_STORE_add_cert, X509_STORE_free, + X509_STORE_get0_objects, X509_STORE_get1_all_certs, X509_STORE_new, X509_free, OPENSSL_STACK, X509, X509_STORE, }; use rustls::pki_types::pem::PemObject; use rustls::pki_types::CertificateDer; +use rustls::RootCertStore; use crate::error::Error; @@ -233,6 +235,7 @@ impl Drop for OwnedX509 { } } +#[derive(Debug)] pub struct OwnedX509Store { raw: *mut X509_STORE, } @@ -243,9 +246,64 @@ impl OwnedX509Store { Self { raw: store } } + pub fn add( + &mut self, + cert_ders: impl IntoIterator>, + ) -> Result<(), Error> { + for cert_der in cert_ders { + let item = OwnedX509::parse_der(cert_der.as_ref()) + .ok_or_else(|| Error::bad_data("cannot parse certificate"))?; + match unsafe { X509_STORE_add_cert(self.raw, item.borrow_ref()) } { + 1 => {} + _ => { + return Err(Error::bad_data("cannot X509_STORE_add_cert")); + } + } + } + + Ok(()) + } + + pub fn add_from_files( + &mut self, + file_names: impl IntoIterator, + ) -> Result<(), Error> { + self.add(load_certs(file_names.into_iter())?) + } + + pub fn to_root_store(&self) -> Result { + let ptr = unsafe { X509_STORE_get1_all_certs(self.raw) }; + + if ptr.is_null() { + return Err(rustls::Error::General( + "cannot retrieve trusted certs".to_string(), + )); + } + + let certs = OwnedX509Stack::new(ptr); + let mut ret = RootCertStore::empty(); + + for i in 0..certs.len() { + ret.add(certs.item(i).der_bytes().into())?; + } + + Ok(ret) + } + pub fn pointer(&self) -> *mut X509_STORE { self.raw } + + pub fn len(&self) -> usize { + match unsafe { OPENSSL_sk_num(X509_STORE_get0_objects(self.raw) as *const OPENSSL_STACK) } { + -1 | 0 => 0, + i => i as usize, + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } } impl Default for OwnedX509Store { @@ -256,6 +314,15 @@ impl Default for OwnedX509Store { } } +impl Clone for OwnedX509Store { + fn clone(&self) -> Self { + unsafe { + X509_STORE_up_ref(self.raw); + } + Self { raw: self.raw } + } +} + impl Drop for OwnedX509Store { fn drop(&mut self) { unsafe { @@ -264,6 +331,9 @@ impl Drop for OwnedX509Store { } } +unsafe impl Send for OwnedX509Store {} +unsafe impl Sync for OwnedX509Store {} + pub(crate) fn load_certs<'a>( file_names: impl Iterator, ) -> Result>, Error> { @@ -295,4 +365,5 @@ extern "C" { ); fn OPENSSL_sk_dup(st: *const OPENSSL_STACK) -> *mut OPENSSL_STACK; fn X509_up_ref(x: *mut X509) -> c_int; + fn X509_STORE_up_ref(xs: *mut X509_STORE) -> c_int; } diff --git a/tests/client.c b/tests/client.c index 3daf8b9..695a90e 100644 --- a/tests/client.c +++ b/tests/client.c @@ -56,8 +56,12 @@ int main(int argc, char **argv) { dump_openssl_error_stack(); assert(SSL_CTX_get_verify_mode(ctx) == SSL_VERIFY_PEER); assert(SSL_CTX_get_verify_callback(ctx) == NULL); + TRACE(sk_X509_OBJECT_num( + X509_STORE_get0_objects(SSL_CTX_get_cert_store(ctx)))); TRACE(SSL_CTX_load_verify_file(ctx, cacert)); dump_openssl_error_stack(); + TRACE(sk_X509_OBJECT_num( + X509_STORE_get0_objects(SSL_CTX_get_cert_store(ctx)))); } printf("SSL_CTX_get_verify_depth default %d\n", SSL_CTX_get_verify_depth(ctx)); diff --git a/tests/server.c b/tests/server.c index c2ff776..88bdb75 100644 --- a/tests/server.c +++ b/tests/server.c @@ -130,8 +130,12 @@ int main(int argc, char **argv) { if (strcmp(cacert, "unauth") != 0) { SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); dump_openssl_error_stack(); + TRACE(sk_X509_OBJECT_num( + X509_STORE_get0_objects(SSL_CTX_get_cert_store(ctx)))); TRACE(SSL_CTX_load_verify_file(ctx, cacert)); dump_openssl_error_stack(); + TRACE(sk_X509_OBJECT_num( + X509_STORE_get0_objects(SSL_CTX_get_cert_store(ctx)))); } else { printf("client auth disabled\n"); }