Make X509Store shareable between contexts

#362
This commit is contained in:
Kornel 2025-06-04 20:20:53 +01:00 committed by Kornel
parent 4d178a7f9f
commit 5d57b3a057
3 changed files with 110 additions and 3 deletions

View File

@ -88,7 +88,7 @@ use crate::ssl::bio::BioMethod;
use crate::ssl::callbacks::*; use crate::ssl::callbacks::*;
use crate::ssl::error::InnerError; use crate::ssl::error::InnerError;
use crate::stack::{Stack, StackRef, Stackable}; use crate::stack::{Stack, StackRef, Stackable};
use crate::x509::store::{X509Store, X509StoreBuilderRef, X509StoreRef}; use crate::x509::store::{X509Store, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef};
use crate::x509::verify::X509VerifyParamRef; use crate::x509::verify::X509VerifyParamRef;
use crate::x509::{ use crate::x509::{
X509Name, X509Ref, X509StoreContextRef, X509VerifyError, X509VerifyResult, X509, X509Name, X509Ref, X509StoreContextRef, X509VerifyError, X509VerifyResult, X509,
@ -933,6 +933,8 @@ extern "C" fn rpk_verify_failure_callback(
/// A builder for `SslContext`s. /// A builder for `SslContext`s.
pub struct SslContextBuilder { pub struct SslContextBuilder {
ctx: SslContext, ctx: SslContext,
/// If it's not shared, it can be exposed as mutable
has_shared_cert_store: bool,
#[cfg(feature = "rpk")] #[cfg(feature = "rpk")]
is_rpk: bool, is_rpk: bool,
} }
@ -1006,7 +1008,11 @@ impl SslContextBuilder {
#[cfg(feature = "rpk")] #[cfg(feature = "rpk")]
pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX, is_rpk: bool) -> SslContextBuilder { pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX, is_rpk: bool) -> SslContextBuilder {
let ctx = SslContext::from_ptr(ctx); let ctx = SslContext::from_ptr(ctx);
let mut builder = SslContextBuilder { ctx, is_rpk }; let mut builder = SslContextBuilder {
ctx,
is_rpk,
has_shared_cert_store: false,
};
builder.set_ex_data(*RPK_FLAG_INDEX, is_rpk); builder.set_ex_data(*RPK_FLAG_INDEX, is_rpk);
@ -1022,6 +1028,7 @@ impl SslContextBuilder {
pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder { pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder {
SslContextBuilder { SslContextBuilder {
ctx: SslContext::from_ptr(ctx), ctx: SslContext::from_ptr(ctx),
has_shared_cert_store: false,
} }
} }
@ -1206,17 +1213,48 @@ impl SslContextBuilder {
} }
} }
/// Use [`set_cert_store_builder`] or [`set_cert_store_ref`] instead.
///
/// Replaces the context's certificate store. /// Replaces the context's certificate store.
#[corresponds(SSL_CTX_set_cert_store)] #[corresponds(SSL_CTX_set_cert_store)]
#[deprecated(note = "Use set_cert_store_builder or set_cert_store_ref instead")]
pub fn set_cert_store(&mut self, cert_store: X509Store) { pub fn set_cert_store(&mut self, cert_store: X509Store) {
#[cfg(feature = "rpk")] #[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK"); assert!(!self.is_rpk, "This API is not supported for RPK");
self.has_shared_cert_store = false;
unsafe { unsafe {
ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr()); ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr());
} }
} }
/// Replaces the context's certificate store, and allows mutating the store afterwards.
#[corresponds(SSL_CTX_set_cert_store)]
pub fn set_cert_store_builder(&mut self, cert_store: X509StoreBuilder) {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
self.has_shared_cert_store = false;
unsafe {
ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.into_ptr());
}
}
/// Replaces the context's certificate store, and keeps it immutable.
///
/// This method allows sharing the `X509Store`, but calls to `cert_store_mut` will panic.
#[corresponds(SSL_CTX_set_cert_store)]
pub fn set_cert_store_ref(&mut self, cert_store: &X509Store) {
#[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK");
self.has_shared_cert_store = true;
unsafe {
ffi::X509_STORE_up_ref(cert_store.as_ptr());
ffi::SSL_CTX_set_cert_store(self.as_ptr(), cert_store.as_ptr());
}
}
/// Controls read ahead behavior. /// Controls read ahead behavior.
/// ///
/// If enabled, OpenSSL will read as much data as is available from the underlying stream, /// If enabled, OpenSSL will read as much data as is available from the underlying stream,
@ -1701,11 +1739,25 @@ impl SslContextBuilder {
} }
/// Returns a mutable reference to the context's certificate store. /// Returns a mutable reference to the context's certificate store.
///
/// Newly-created `SslContextBuilder` will have its own default mutable store.
///
/// ## Panics
///
/// * If a shared store has been set via [`set_cert_store_ref`]
/// * If context has been created for Raw Public Key verification (requires `rpk` Cargo feature)
///
#[corresponds(SSL_CTX_get_cert_store)] #[corresponds(SSL_CTX_get_cert_store)]
pub fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef { pub fn cert_store_mut(&mut self) -> &mut X509StoreBuilderRef {
#[cfg(feature = "rpk")] #[cfg(feature = "rpk")]
assert!(!self.is_rpk, "This API is not supported for RPK"); assert!(!self.is_rpk, "This API is not supported for RPK");
assert!(
!self.has_shared_cert_store,
"Shared X509Store can't be mutated. Make a new store"
);
// OTOH, it's not safe to return a shared &X509Store when the builder owns it exclusively
unsafe { X509StoreBuilderRef::from_ptr_mut(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } unsafe { X509StoreBuilderRef::from_ptr_mut(ffi::SSL_CTX_get_cert_store(self.as_ptr())) }
} }

View File

@ -1,4 +1,4 @@
use hex; use foreign_types::{ForeignType, ForeignTypeRef};
use std::io; use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use std::mem; use std::mem;
@ -18,6 +18,7 @@ use crate::ssl::{
ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder, ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder,
SslConnector, SslContext, SslFiletype, SslMethod, SslOptions, SslStream, SslVerifyMode, SslConnector, SslContext, SslFiletype, SslMethod, SslOptions, SslStream, SslVerifyMode,
}; };
use crate::x509::store::X509StoreBuilder;
use crate::x509::verify::X509CheckFlags; use crate::x509::verify::X509CheckFlags;
use crate::x509::{X509Name, X509}; use crate::x509::{X509Name, X509};
@ -308,6 +309,52 @@ fn test_select_cert_ok() {
client.connect(); client.connect();
} }
#[test]
fn test_mutable_store() {
#![allow(deprecated)]
let cert = include_bytes!("../../../test/cert.pem");
let cert = X509::from_pem(cert).unwrap();
let cert2 = include_bytes!("../../../test/root-ca.pem");
let cert2 = X509::from_pem(cert2).unwrap();
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
ctx.cert_store_mut().add_cert(cert.clone()).unwrap();
assert_eq!(1, ctx.cert_store().objects_len());
ctx.set_cert_store_builder(X509StoreBuilder::new().unwrap());
assert_eq!(0, ctx.cert_store().objects_len());
ctx.cert_store_mut().add_cert(cert.clone()).unwrap();
assert_eq!(1, ctx.cert_store().objects_len());
let mut new_store = X509StoreBuilder::new().unwrap();
new_store.add_cert(cert).unwrap();
new_store.add_cert(cert2).unwrap();
let new_store = new_store.build();
assert_eq!(2, new_store.objects_len());
ctx.set_cert_store_ref(&new_store);
assert_eq!(2, ctx.cert_store().objects_len());
assert!(std::ptr::eq(new_store.as_ptr(), ctx.cert_store().as_ptr()));
let ctx = ctx.build();
assert!(std::ptr::eq(new_store.as_ptr(), ctx.cert_store().as_ptr()));
drop(new_store);
assert_eq!(2, ctx.cert_store().objects_len());
}
#[test]
#[should_panic(expected = "mutated")]
fn shared_store_must_not_be_mutated() {
let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
let shared = X509StoreBuilder::new().unwrap().build();
ctx.set_cert_store_ref(&shared);
ctx.cert_store_mut();
}
#[test] #[test]
fn test_select_cert_error() { fn test_select_cert_error() {
let mut server = Server::builder(); let mut server = Server::builder();

View File

@ -115,6 +115,14 @@ impl X509StoreBuilderRef {
pub fn set_param(&mut self, param: &X509VerifyParamRef) -> Result<(), ErrorStack> { pub fn set_param(&mut self, param: &X509VerifyParamRef) -> Result<(), ErrorStack> {
unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())).map(|_| ()) } unsafe { cvt(ffi::X509_STORE_set1_param(self.as_ptr(), param.as_ptr())).map(|_| ()) }
} }
/// For testing only
#[cfg(test)]
pub fn objects_len(&self) -> usize {
unsafe {
StackRef::<X509Object>::from_ptr(ffi::X509_STORE_get0_objects(self.as_ptr())).len()
}
}
} }
foreign_type_and_impl_send_sync! { foreign_type_and_impl_send_sync! {