diff --git a/bindings/rust/s2n-tls/src/cert_chain.rs b/bindings/rust/s2n-tls/src/cert_chain.rs index 007657d15f5..5638c373288 100644 --- a/bindings/rust/s2n-tls/src/cert_chain.rs +++ b/bindings/rust/s2n-tls/src/cert_chain.rs @@ -4,8 +4,10 @@ use crate::error::{Error, Fallible}; use s2n_tls_sys::*; use std::{ + ffi::{c_void, CString}, marker::PhantomData, ptr::{self, NonNull}, + sync::atomic::{AtomicUsize, Ordering}, }; /// A CertificateChain represents a chain of X.509 certificates. @@ -18,16 +20,26 @@ pub struct CertificateChain<'a> { impl CertificateChain<'_> { /// This allocates a new certificate chain from s2n. pub(crate) fn new() -> Result, Error> { + crate::init::init(); + let ptr = unsafe { s2n_cert_chain_and_key_new().into_result()? }; + + let context = Box::::default(); + let context = Box::into_raw(context) as *mut c_void; + unsafe { - let ptr = s2n_cert_chain_and_key_new().into_result()?; - Ok(CertificateChain { - ptr, - is_owned: true, - _lifetime: PhantomData, - }) + s2n_cert_chain_and_key_set_ctx(ptr.as_ptr(), context) + .into_result() + .unwrap(); } + Ok(CertificateChain { + ptr, + is_owned: true, + _lifetime: PhantomData, + }) } + /// This CertificateChain is not owned and will not increment the reference count. + /// When the rust instance is dropped it will not drop the pointer. pub(crate) unsafe fn from_ptr_reference<'a>( ptr: NonNull, ) -> CertificateChain<'a> { @@ -38,6 +50,26 @@ impl CertificateChain<'_> { } } + /// # Safety + /// + /// This CertificateChain _MUST_ have been initialized with the constructor. + /// Additionally, this does NOT increment the reference count, + /// so consider cloning the result if the source pointer is still valid and usable afterwards. + pub(crate) unsafe fn from_owned_ptr_reference<'a>( + ptr: NonNull, + ) -> CertificateChain<'a> { + let cert_chain = CertificateChain { + ptr, + is_owned: true, + _lifetime: PhantomData, + }; + + // Check if the context can be retrieved. + // If it can't, this is not an owned CertificateChain created through constructor. + cert_chain.context(); + cert_chain + } + pub fn iter(&self) -> CertificateChainIter<'_> { CertificateChainIter { idx: 0, @@ -75,6 +107,68 @@ impl CertificateChain<'_> { pub(crate) fn as_mut_ptr(&mut self) -> NonNull { self.ptr } + + /// Retrieve a reference to the [`CertificateChainContext`] stored on the CertificateChain. + pub(crate) fn context(&self) -> &CertificateChainContext { + unsafe { + let ctx = s2n_cert_chain_and_key_get_ctx(self.ptr.as_ptr()) + .into_result() + .unwrap(); + &*(ctx.as_ptr() as *const CertificateChainContext) + } + } + + /// Retrieve a mutable reference to the [`CertificateChainContext`] stored on the CertificateChain. + pub(crate) fn context_mut(&mut self) -> &mut CertificateChainContext { + unsafe { + let ctx = s2n_cert_chain_and_key_get_ctx(self.ptr.as_ptr()) + .into_result() + .unwrap(); + &mut *(ctx.as_ptr() as *mut CertificateChainContext) + } + } + + pub fn load_pem(&mut self, certificate: &[u8], private_key: &[u8]) -> Result<&mut Self, Error> { + let certificate = CString::new(certificate).map_err(|_| Error::INVALID_INPUT)?; + let private_key = CString::new(private_key).map_err(|_| Error::INVALID_INPUT)?; + unsafe { + s2n_cert_chain_and_key_load_pem( + self.ptr.as_ptr(), + certificate.as_ptr(), + private_key.as_ptr(), + ) + .into_result() + }?; + Ok(self) + } + + pub fn set_ocsp_data(&mut self, data: &[u8]) -> Result<&mut Self, Error> { + let size: u32 = data.len().try_into().map_err(|_| Error::INVALID_INPUT)?; + unsafe { + s2n_cert_chain_and_key_set_ocsp_data(self.ptr.as_ptr(), data.as_ptr(), size) + .into_result() + }?; + Ok(self) + } +} + +impl Clone for CertificateChain<'_> { + fn clone(&self) -> Self { + let context = self.context(); + + // Safety + // + // Using a relaxed ordering is alright here, as knowledge of the + // original reference prevents other threads from erroneously deleting + // the object. + // https://github.com/rust-lang/rust/blob/e012a191d768adeda1ee36a99ef8b92d51920154/library/alloc/src/sync.rs#L1329 + let _count = context.refcount.fetch_add(1, Ordering::Relaxed); + Self { + ptr: self.ptr, + is_owned: true, // clone only makes sense for owned + _lifetime: PhantomData, + } + } } // # Safety @@ -82,13 +176,46 @@ impl CertificateChain<'_> { // s2n_cert_chain_and_key objects can be sent across threads. unsafe impl Send for CertificateChain<'_> {} +/// # Safety +/// +/// Safety: All C methods that mutate the s2n_cert_chain are wrapped +/// in Rust methods that require a mutable reference. +unsafe impl Sync for CertificateChain<'_> {} + impl Drop for CertificateChain<'_> { fn drop(&mut self) { - if self.is_owned { - // ignore failures since there's not much we can do about it - unsafe { - let _ = s2n_cert_chain_and_key_free(self.ptr.as_ptr()).into_result(); - } + if !self.is_owned { + // not ours to cleanup + return; + } + let context = self.context_mut(); + let count = context.refcount.fetch_sub(1, Ordering::Release); + debug_assert!(count > 0, "refcount should not drop below 1 instance"); + + // only free the cert if this is the last instance + if count != 1 { + return; + } + + // Safety + // + // The use of Ordering and fence mirrors the `Arc` implementation in + // the standard library. + // + // This fence is needed to prevent reordering of use of the data and + // deletion of the data. Because it is marked `Release`, the decreasing + // of the reference count synchronizes with this `Acquire` fence. This + // means that use of the data happens before decreasing the reference + // count, which happens before this fence, which happens before the + // deletion of the data. + // https://github.com/rust-lang/rust/blob/e012a191d768adeda1ee36a99ef8b92d51920154/library/alloc/src/sync.rs#L1637 + std::sync::atomic::fence(Ordering::Acquire); + + unsafe { + // This is the last instance so free the context. + let context = Box::from_raw(context); + drop(context); + let _ = s2n_cert_chain_and_key_free(self.ptr.as_ptr()).into_result(); } } } @@ -152,3 +279,40 @@ impl<'a> Certificate<'a> { // // Certificates just reference data in the chain, so share the Send-ness of the chain. unsafe impl Send for Certificate<'_> {} + +pub(crate) struct CertificateChainContext { + refcount: AtomicUsize, +} + +impl Default for CertificateChainContext { + fn default() -> Self { + // The AtomicUsize is used to manually track the reference count of the CertificateChain. + // This mechanism is used to track when the CertificateChain object should be freed. + Self { + refcount: AtomicUsize::new(1), + } + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn clone_and_drop_update_ref_count() { + let original_cert = CertificateChain::new().unwrap(); + assert_eq!(original_cert.context().refcount.load(Ordering::Relaxed), 1); + + let second_cert = original_cert.clone(); + assert_eq!(original_cert.context().refcount.load(Ordering::Relaxed), 2); + + drop(second_cert); + assert_eq!(original_cert.context().refcount.load(Ordering::Relaxed), 1); + } + + // ensure the CertificateChainContext is send and sync + #[test] + fn context_send_sync_test() { + fn assert_send_sync() {} + assert_send_sync::(); + } +} diff --git a/bindings/rust/s2n-tls/src/config.rs b/bindings/rust/s2n-tls/src/config.rs index c7e6353347e..84f27ea356a 100644 --- a/bindings/rust/s2n-tls/src/config.rs +++ b/bindings/rust/s2n-tls/src/config.rs @@ -5,10 +5,12 @@ use crate::renegotiate::RenegotiateCallback; use crate::{ callbacks::*, + cert_chain::CertificateChain, enums::*, error::{Error, Fallible}, security, }; +use core::mem::{self}; use core::{convert::TryInto, ptr::NonNull}; use s2n_tls_sys::*; use std::{ @@ -156,6 +158,26 @@ impl Drop for Config { let context = Box::from_raw(context); drop(context); + // Clean up the certificate chains + let mut cert_chains: *mut *mut s2n_cert_chain_and_key = std::ptr::null_mut(); + let mut chain_count: u32 = 0; + if s2n_config_get_cert_chains(self.0.as_ptr(), &mut cert_chains, &mut chain_count) + .into_result() + .is_ok() + { + if !cert_chains.is_null() && chain_count > 0 { + let cert_slice = std::slice::from_raw_parts(cert_chains, chain_count as usize); + for &cert_ptr in cert_slice { + if let Some(non_null_ptr) = NonNull::new(cert_ptr) { + drop(CertificateChain::from_owned_ptr_reference(non_null_ptr)); + } + } + + let _ = + s2n_free_config_cert_chains(&mut cert_chains, chain_count).into_result(); + } + } + let _ = s2n_config_free(self.0.as_ptr()).into_result(); } } @@ -277,6 +299,26 @@ impl Builder { Ok(self) } + /// Adds the CertificateChain to the Config. + /// Prefer to use this function over load_pem. + /// This function is not compatible with load_pem and will error if both are used + pub fn add_cert_chain(&mut self, mut cert_chain: CertificateChain) -> Result<&mut Self, Error> { + unsafe { + s2n_config_add_cert_chain_and_key_to_store( + self.as_mut_ptr(), + cert_chain.as_mut_ptr().as_ptr(), + ) + .into_result()?; + } + // Setting the cert chain on the config creates one additional reference + // so do not drop so prevent Rust from calling `drop()` at the end of this function. + mem::forget(cert_chain); + Ok(self) + } + + /// Creates a certificate chain and binds it to the config. + /// Prefer to use the newer add_cert_chain which enables multiple certs over this function. + /// This function is not compatible with add_cert_chain and will error if both are used pub fn load_pem(&mut self, certificate: &[u8], private_key: &[u8]) -> Result<&mut Self, Error> { let certificate = CString::new(certificate).map_err(|_| Error::INVALID_INPUT)?; let private_key = CString::new(private_key).map_err(|_| Error::INVALID_INPUT)?; @@ -395,6 +437,8 @@ impl Builder { /// Sets the OCSP data for the default certificate chain associated with the Config. /// /// Servers will send the data in response to OCSP stapling requests from clients. + /// + /// Prefer to use add_cert_chain with a CertificateChain that has OCSP data set over this function. // // NOTE: this modifies a certificate chain, NOT the Config itself. This is currently safe // because the certificate chain is set with s2n_config_add_cert_chain_and_key, which diff --git a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs index 1d5d920c384..380572fd668 100644 --- a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs +++ b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs @@ -5,6 +5,7 @@ mod tests { use crate::{ callbacks::{ClientHelloCallback, ConnectionFuture, ConnectionFutureResult}, + cert_chain::CertificateChain, enums::ClientAuthType, error::ErrorType, testing::{self, client_hello::*, Error, Result, *}, @@ -114,6 +115,89 @@ mod tests { Ok(()) } + #[test] + fn config_builder_fails_when_mixing_ownership() { + let first_prefix = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../tests/pems/rsa_4096_sha512_client_" + ); + let first_keypair = CertKeyPair::from(first_prefix, "cert", "key", "cert"); + let second_prefix = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../tests/pems/rsa_4096_sha256_client_" + ); + let second_keypair = CertKeyPair::from(second_prefix, "cert", "key", "cert"); + + let mut cert_chain = CertificateChain::new().unwrap(); + cert_chain + .load_pem(first_keypair.cert(), first_keypair.key()) + .expect("Unable to load pem into cert chain"); + + let mut builder = Builder::new(); + builder + .add_cert_chain(cert_chain) + .expect("Unable to add cert chain to builder"); + + // load_pem and add_cert_chain cannot mix on the builder due to ownership so we should get an error + let result = builder.load_pem(second_keypair.cert(), second_keypair.key()); + assert!(result.is_err()); + } + + #[test] + fn config_builder_accepts_multiple_cert_chains() { + let first_prefix = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../tests/pems/rsa_4096_sha512_client_" + ); + let first_keypair = CertKeyPair::from(first_prefix, "cert", "key", "cert"); + let second_prefix = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../tests/pems/rsa_4096_sha256_client_" + ); + let second_keypair = CertKeyPair::from(second_prefix, "cert", "key", "cert"); + + let mut first_cert_chain = CertificateChain::new().unwrap(); + first_cert_chain + .load_pem(first_keypair.cert(), first_keypair.key()) + .expect("Unable to load pem into cert chain"); + let mut second_cert_chain = CertificateChain::new().unwrap(); + second_cert_chain + .load_pem(second_keypair.cert(), second_keypair.key()) + .expect("Unable to load pem into cert chain"); + + let mut builder = Builder::new(); + + assert!(builder.add_cert_chain(first_cert_chain).is_ok()); + assert!(builder.add_cert_chain(second_cert_chain).is_ok()); + let _ = builder + .build() + .expect("Failed to build config with multiple cert chains"); + } + + #[test] + fn cert_chain_sharable_by_configs() -> Result<(), Error> { + let keypair = CertKeyPair::default(); + let mut cert_chain = CertificateChain::new().unwrap(); + cert_chain + .load_pem(keypair.cert(), keypair.key()) + .expect("Unable to load pem into cert chain"); + let cloned_cert_chain = cert_chain.clone(); + + let mut first_builder = Builder::new(); + first_builder + .add_cert_chain(cert_chain) + .expect("Failed to add cert chain to first config"); + let mut second_builder = Builder::new(); + second_builder + .add_cert_chain(cloned_cert_chain) + .expect("Failed to add cert chain to second config"); + + first_builder.build().expect("Unable to build first config"); + second_builder.build().expect("Unable to build second config"); + + Ok(()) + } + #[test] fn connnection_waker() { let config = build_config(&security::DEFAULT_TLS13).unwrap(); diff --git a/tests/unit/s2n_config_test.c b/tests/unit/s2n_config_test.c index e43930bb188..6ad3d8166bd 100644 --- a/tests/unit/s2n_config_test.c +++ b/tests/unit/s2n_config_test.c @@ -763,6 +763,101 @@ int main(int argc, char **argv) EXPECT_SUCCESS(s2n_connection_set_config(conn, config)); }; +/* Test s2n_config_get_cert_chains */ +{ + /* Test with no certificates */ + { + struct s2n_config *config = s2n_config_new(); + EXPECT_NOT_NULL(config); + + struct s2n_cert_chain_and_key **cert_chains = NULL; + uint32_t chain_count = 0; + + EXPECT_SUCCESS(s2n_config_get_cert_chains(config, &cert_chains, &chain_count)); + EXPECT_NULL(cert_chains); + EXPECT_EQUAL(chain_count, 0); + EXPECT_SUCCESS(s2n_free_config_cert_chains(&cert_chains, chain_count)); + + EXPECT_SUCCESS(s2n_config_free(config)); + }; + + /* Test with a single certificate */ + { + struct s2n_config *config = s2n_config_new(); + EXPECT_NOT_NULL(config); + + struct s2n_cert_chain_and_key *chain_and_key = NULL; + EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&chain_and_key, S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY)); + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); + + struct s2n_cert_chain_and_key **cert_chains = NULL; + uint32_t chain_count = 0; + + EXPECT_SUCCESS(s2n_config_get_cert_chains(config, &cert_chains, &chain_count)); + EXPECT_NOT_NULL(cert_chains); + EXPECT_EQUAL(chain_count, 1); + EXPECT_EQUAL(cert_chains[0], chain_and_key); + + EXPECT_SUCCESS(s2n_free_config_cert_chains(&cert_chains, chain_count)); + EXPECT_SUCCESS(s2n_config_free(config)); + EXPECT_SUCCESS(s2n_cert_chain_and_key_free(chain_and_key)); + }; + + /* Test with multiple certificates */ + { + struct s2n_config *config = s2n_config_new(); + EXPECT_NOT_NULL(config); + + struct s2n_cert_chain_and_key *chain1 = NULL; + struct s2n_cert_chain_and_key *chain2 = NULL; + EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&chain1, S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY)); + EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&chain2, S2N_DEFAULT_ECDSA_TEST_CERT_CHAIN, S2N_DEFAULT_ECDSA_TEST_PRIVATE_KEY)); + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain1)); + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain2)); + + struct s2n_cert_chain_and_key **cert_chains = NULL; + uint32_t chain_count = 0; + + EXPECT_SUCCESS(s2n_config_get_cert_chains(config, &cert_chains, &chain_count)); + EXPECT_NOT_NULL(cert_chains); + EXPECT_EQUAL(chain_count, 2); + EXPECT_TRUE((cert_chains[0] == chain1 && cert_chains[1] == chain2) || + (cert_chains[0] == chain2 && cert_chains[1] == chain1)); + + EXPECT_SUCCESS(s2n_free_config_cert_chains(&cert_chains, chain_count)); + EXPECT_SUCCESS(s2n_config_free(config)); + EXPECT_SUCCESS(s2n_cert_chain_and_key_free(chain1)); + EXPECT_SUCCESS(s2n_cert_chain_and_key_free(chain2)); + }; + + /* Test with domain name to cert map */ + { + struct s2n_config *config = s2n_config_new(); + EXPECT_NOT_NULL(config); + + struct s2n_cert_chain_and_key *chain1 = NULL; + struct s2n_cert_chain_and_key *chain2 = NULL; + EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&chain1, S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY)); + EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&chain2, S2N_DEFAULT_ECDSA_TEST_CERT_CHAIN, S2N_DEFAULT_ECDSA_TEST_PRIVATE_KEY)); + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain1)); + EXPECT_SUCCESS(s2n_config_build_domain_name_to_cert_map(config, chain2)); + + struct s2n_cert_chain_and_key **cert_chains = NULL; + uint32_t chain_count = 0; + + EXPECT_SUCCESS(s2n_config_get_cert_chains(config, &cert_chains, &chain_count)); + EXPECT_NOT_NULL(cert_chains); + EXPECT_EQUAL(chain_count, 2); + EXPECT_TRUE((cert_chains[0] == chain1 && cert_chains[1] == chain2) || + (cert_chains[0] == chain2 && cert_chains[1] == chain1)); + + EXPECT_SUCCESS(s2n_free_config_cert_chains(&cert_chains, chain_count)); + EXPECT_SUCCESS(s2n_config_free(config)); + EXPECT_SUCCESS(s2n_cert_chain_and_key_free(chain1)); + EXPECT_SUCCESS(s2n_cert_chain_and_key_free(chain2)); + }; +} + /* Test loading system certs */ { /* s2n_config_load_system_certs safety */ diff --git a/tls/s2n_config.c b/tls/s2n_config.c index ccc1940c0ac..80706727330 100644 --- a/tls/s2n_config.c +++ b/tls/s2n_config.c @@ -729,6 +729,99 @@ int s2n_config_set_cert_chain_and_key_defaults(struct s2n_config *config, return 0; } +/* Only used in the Rust bindings for cleanup */ +int s2n_config_get_cert_chains(struct s2n_config *config, + struct s2n_cert_chain_and_key ***cert_chains, + uint32_t *chain_count) +{ + POSIX_ENSURE_REF(config); + POSIX_ENSURE_REF(cert_chains); + POSIX_ENSURE_REF(chain_count); + *chain_count = 0; + *cert_chains = NULL; + uint32_t total_possible_chains = 0; + + /* Count all the certs to know how much max memory to allocate */ + for (int i = 0; i < S2N_CERT_TYPE_COUNT; i++) { + if (config->default_certs_by_type.certs[i] != NULL) { + total_possible_chains++; + } + } + if (config->domain_name_to_cert_map != NULL) { + uint32_t domain_count = 0; + POSIX_GUARD_RESULT(s2n_map_size(config->domain_name_to_cert_map, &domain_count)); + total_possible_chains += (domain_count * S2N_CERT_TYPE_COUNT); + } + + if (total_possible_chains == 0) { + return S2N_SUCCESS; + } + + /* Use a union to ensure proper alignment (casting gives error saying increases required alignment from 1 to 8 ) */ + union { + struct s2n_cert_chain_and_key **chains; + uint8_t *data; + } aligned_chains; + + /* Allocate memory for the array of pointers */ + DEFER_CLEANUP(struct s2n_blob allocator = {0}, s2n_free); + POSIX_GUARD(s2n_alloc(&allocator, sizeof(struct s2n_cert_chain_and_key*) * total_possible_chains)); + aligned_chains.data = allocator.data; + + uint32_t cert_index = 0; + + for (int i = 0; i < S2N_CERT_TYPE_COUNT; i++) { + if (config->default_certs_by_type.certs[i] != NULL) { + aligned_chains.chains[cert_index++] = config->default_certs_by_type.certs[i]; + } + } + if (config->domain_name_to_cert_map != NULL) { + struct s2n_map_iterator iter = {0}; + POSIX_GUARD_RESULT(s2n_map_iterator_init(&iter, config->domain_name_to_cert_map)); + + while (s2n_map_iterator_has_next(&iter)) { + struct s2n_blob value = {0}; + POSIX_GUARD_RESULT(s2n_map_iterator_next(&iter, &value)); + + struct certs_by_type *domain_certs = (void *)value.data; + for (int i = 0; i < S2N_CERT_TYPE_COUNT; i++) { + if (domain_certs->certs[i] != NULL) { + bool duplicate = false; + for (uint32_t j = 0; j < cert_index; j++) { + if (aligned_chains.chains[j] == domain_certs->certs[i]) { + duplicate = true; + break; + } + } + + if (!duplicate) { + aligned_chains.chains[cert_index++] = domain_certs->certs[i]; + } + } + } + } + } + + *cert_chains = aligned_chains.chains; + *chain_count = cert_index; + + /* Prevent double free of the memory */ + allocator.data = NULL; + + return S2N_SUCCESS; +} + +int s2n_free_config_cert_chains(struct s2n_cert_chain_and_key ***cert_chains, uint32_t chain_count) +{ + POSIX_ENSURE_REF(cert_chains); + if (*cert_chains != NULL) { + POSIX_GUARD(s2n_free_object((uint8_t **)cert_chains, sizeof(struct s2n_cert_chain_and_key *) * chain_count)); + *cert_chains = NULL; + } + + return S2N_SUCCESS; +} + int s2n_config_add_dhparams(struct s2n_config *config, const char *dhparams_pem) { DEFER_CLEANUP(struct s2n_stuffer dhparams_in_stuffer = { 0 }, s2n_stuffer_free); diff --git a/tls/s2n_internal.h b/tls/s2n_internal.h index 7c51f656a24..5c592acd614 100644 --- a/tls/s2n_internal.h +++ b/tls/s2n_internal.h @@ -59,3 +59,19 @@ S2N_PRIVATE_API int s2n_config_add_cert_chain(struct s2n_config *config, * is still waiting for encryption. */ S2N_PRIVATE_API int s2n_flush(struct s2n_connection *conn, s2n_blocked_status *blocked); + +/* + * Gets all the s2n_cert_chain_and_key set on the config + * + * This method is only useful for the Rust bindings that need to iterate over + * the cert chains for cleanup + */ +S2N_PRIVATE_API int s2n_config_get_cert_chains(struct s2n_config *config, + struct s2n_cert_chain_and_key ***cert_chains, uint32_t *chain_count); + +/* + * Cleanup for memory needed for s2n_config_get_cert_chains + * + * This method is only used by the Rust bindings + */ +S2N_PRIVATE_API int s2n_free_config_cert_chains(struct s2n_cert_chain_and_key ***cert_chains, uint32_t chain_count);