From bb3a874683c173ad1a53164c3c4174ede81a2856 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 12:42:24 -0400 Subject: [PATCH 01/25] ci: pin cbindgen to 0.27.0 Prev. we pinned cbindgen to 0.24.5. I've been using 0.27.0 (the latest available release) locally without spurious diffs. Let's update CI to match. Minor reformatting of YAML comes along for the ride. --- .github/workflows/test.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5ddba23f..92d3f772 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,7 +17,7 @@ jobs: strategy: matrix: # test a bunch of toolchains on ubuntu - cc: [clang, gcc] + cc: [ clang, gcc ] rust: - stable - beta @@ -25,7 +25,7 @@ jobs: # MSRV - keep in sync with what rustls and rustls-platform-verifier # consider MSRV - 1.64.0 - os: [ubuntu-latest] + os: [ ubuntu-latest ] # but only stable on macos/windows (slower platforms) include: - os: macos-latest @@ -123,7 +123,7 @@ jobs: # reliably matched to CI. There can be non-semantic differences in # output between point releases of cbindgen that will fail this check # otherwise. - run: cargo install cbindgen --force --version 0.24.5 + run: cargo install cbindgen --force --version 0.27.0 - run: touch src/lib.rs - run: cbindgen --version - run: make src/rustls.h From 0b7e8c61d2ef51d78ab9e9a6a6c925a66a3257a5 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 11:15:08 -0400 Subject: [PATCH 02/25] connection: use preferred rustls style for imports "Within the import blocks we prefer to separate imports that don't share a parent module."[0] [0]: https://github.com/rustls/rustls/blob/main/CONTRIBUTING.md#imports --- src/connection.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/connection.rs b/src/connection.rs index d4871c98..e84803cf 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -7,18 +7,16 @@ use pki_types::CertificateDer; use rustls::crypto::ring::ALL_CIPHER_SUITES; use rustls::{ClientConnection, ServerConnection, SupportedCipherSuite}; +use crate::cipher::{rustls_certificate, rustls_supported_ciphersuite}; +use crate::error::{map_error, rustls_io_result, rustls_result}; use crate::io::{ - rustls_write_vectored_callback, CallbackReader, CallbackWriter, VectoredCallbackWriter, + rustls_read_callback, rustls_write_callback, rustls_write_vectored_callback, CallbackReader, + CallbackWriter, VectoredCallbackWriter, }; use crate::log::{ensure_log_registered, rustls_log_callback}; - use crate::{ - box_castable, - cipher::{rustls_certificate, rustls_supported_ciphersuite}, - error::{map_error, rustls_io_result, rustls_result}, - ffi_panic_boundary, free_box, - io::{rustls_read_callback, rustls_write_callback}, - try_callback, try_mut_from_ptr, try_ref_from_ptr, try_slice, userdata_push, + box_castable, ffi_panic_boundary, free_box, try_callback, try_mut_from_ptr, try_ref_from_ptr, + try_slice, userdata_push, }; use rustls_result::NullParameter; From cf5d6e5e3b32d9d952ffe729fc12661cfc9fe7b3 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 11:25:46 -0400 Subject: [PATCH 03/25] rslice: rustls_str -> str Offer an `unsafe` route for Rust code to translate a `rustls_str` to a `&str`. --- src/rslice.rs | 11 +++++++++++ tests/rustls_version.rs | 8 +------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/rslice.rs b/src/rslice.rs index c14c47ea..d653cef6 100644 --- a/src/rslice.rs +++ b/src/rslice.rs @@ -212,6 +212,15 @@ impl<'a> rustls_str<'a> { pub unsafe fn into_static(self) -> rustls_str<'static> { std::mem::transmute(self) } + + /// Change a rustls_str back to a &str. + /// + /// # Safety + /// + /// The caller must ensure the rustls_str data is valid utf8 + pub unsafe fn to_str(&self) -> &str { + str::from_utf8_unchecked(slice::from_raw_parts(self.data as *const u8, self.len)) + } } // If the assertion about Rust code being the only creator of rustls_str objects @@ -247,6 +256,8 @@ fn test_rustls_str() { assert_eq!(*rs.data, 'a' as c_char); assert_eq!(*rs.data.offset(3), 'd' as c_char); } + let rs = unsafe { rs.to_str() }; + assert_eq!(rs, s); } #[cfg(test)] diff --git a/tests/rustls_version.rs b/tests/rustls_version.rs index 446cd58b..4830d3c3 100644 --- a/tests/rustls_version.rs +++ b/tests/rustls_version.rs @@ -1,7 +1,6 @@ use std::fs::File; use std::io::Read; use std::path::PathBuf; -use std::{slice, str}; use toml::Table; @@ -51,12 +50,7 @@ fn rustls_version_match() { // E.g.: // rustls-ffi/0.13.0/rustls/0.23.4 let rustls_ffi_version = rustls_version(); - let rustls_ffi_version = unsafe { - str::from_utf8_unchecked(slice::from_raw_parts( - rustls_ffi_version.data as *const u8, - rustls_ffi_version.len, - )) - }; + let rustls_ffi_version = unsafe { rustls_ffi_version.to_str() }; let rustls_ffi_version_parts = rustls_ffi_version.split('/').collect::>(); assert_eq!( rustls_ffi_version_parts, From 713ccbc95078e0f59ad65fc00d7146adf8c8c5e1 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 11:39:18 -0400 Subject: [PATCH 04/25] connection: avoid `rustls_supported_ciphersuite` ptr Previously the `rustls_connection_get_negotiated_ciphersuite` function returned a pointer to a `rustls_supported_ciphersuite`. This commit changes that function to only return the identifier int. A new `rustls_connection_get_negotiated_ciphersuite_name` function is added for getting the negotiated ciphersuite name as a `rustls_str`. We want to avoid returning a `rustls_supported_ciphersuite` here because this type is both the _implementation_ of a ciphersuite, and metadata such as the name/ID. Getting a handle to the implementation for a given connection requires iterating the `ALL_CIPHER_SUITES` array to find the matching ciphersuite impl, and this is only workable when the available ciphersuites is a fixed quantity. Soon we will support customizing the cryptography provider, complicating this design greatly. Functionally, the only thing a caller wants to do in this circumstance is find the negotiated ciphersuite ID or name. To avoid the complication discussed above we can simply return this information directly and avoid the need to find the full fledged ciphersuite implementation at connection time. --- src/client.rs | 4 +++- src/connection.rs | 56 ++++++++++++++++++++++++++++++----------------- src/rustls.h | 20 +++++++++++++---- src/server.rs | 4 +++- tests/client.c | 9 ++++++++ tests/server.c | 10 +++++++++ 6 files changed, 77 insertions(+), 26 deletions(-) diff --git a/src/client.rs b/src/client.rs index a6f4b386..0ac3279b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -679,8 +679,10 @@ mod tests { assert_eq!( rustls_connection::rustls_connection_get_negotiated_ciphersuite(conn), - null() + 0 ); + let cs_name = rustls_connection::rustls_connection_get_negotiated_ciphersuite_name(conn); + assert_eq!(unsafe { cs_name.to_str() }, ""); assert_eq!( rustls_connection::rustls_connection_get_peer_certificate(conn, 0), null() diff --git a/src/connection.rs b/src/connection.rs index e84803cf..602b1695 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -4,16 +4,17 @@ use std::{ptr::null_mut, slice}; use libc::{size_t, EINVAL, EIO}; use pki_types::CertificateDer; -use rustls::crypto::ring::ALL_CIPHER_SUITES; -use rustls::{ClientConnection, ServerConnection, SupportedCipherSuite}; +use rustls::CipherSuite::TLS_NULL_WITH_NULL_NULL; +use rustls::{ClientConnection, ServerConnection}; -use crate::cipher::{rustls_certificate, rustls_supported_ciphersuite}; +use crate::cipher::rustls_certificate; use crate::error::{map_error, rustls_io_result, rustls_result}; use crate::io::{ rustls_read_callback, rustls_write_callback, rustls_write_vectored_callback, CallbackReader, CallbackWriter, VectoredCallbackWriter, }; use crate::log::{ensure_log_registered, rustls_log_callback}; +use crate::rslice::rustls_str; use crate::{ box_castable, ffi_panic_boundary, free_box, try_callback, try_mut_from_ptr, try_ref_from_ptr, try_slice, userdata_push, @@ -389,29 +390,44 @@ impl rustls_connection { } } - /// Retrieves the cipher suite agreed with the peer. - /// This returns NULL until the ciphersuite is agreed. - /// The returned pointer lives as long as the program. - /// + /// Retrieves the [IANA registered cipher suite identifier][IANA] agreed with the peer. + /// + /// This returns `TLS_NULL_WITH_NULL_NULL` (0x0000) until the ciphersuite is agreed. + /// + /// [IANA]: #[no_mangle] pub extern "C" fn rustls_connection_get_negotiated_ciphersuite( conn: *const rustls_connection, - ) -> *const rustls_supported_ciphersuite { + ) -> u16 { ffi_panic_boundary! { - let conn = try_ref_from_ptr!(conn); - let negotiated = match conn.negotiated_cipher_suite() { + match try_ref_from_ptr!(conn).negotiated_cipher_suite() { + Some(cs) => u16::from(cs.suite()), + None => u16::from(TLS_NULL_WITH_NULL_NULL), + } + } + } + + /// Retrieves the cipher suite name agreed with the peer. + /// + /// This returns "" until the ciphersuite is agreed. + /// + /// The lifetime of the `rustls_str` is the lifetime of the program, it does not + /// need to be freed. + /// + /// + #[no_mangle] + pub extern "C" fn rustls_connection_get_negotiated_ciphersuite_name( + conn: *const rustls_connection, + ) -> rustls_str<'static> { + ffi_panic_boundary! { + let cs_name = try_ref_from_ptr!(conn) + .negotiated_cipher_suite() + .and_then(|cs| cs.suite().as_str()) + .and_then(|name| rustls_str::try_from(name).ok()); + match cs_name { Some(cs) => cs, - None => return null(), - }; - for cs in ALL_CIPHER_SUITES { - // This type annotation is here to enforce the lifetime stated - // in the doccomment - that the returned pointer lives as long - // as the program. - if negotiated == *cs { - return cs as *const SupportedCipherSuite as *const _; - } + None => rustls_str::from_str_unchecked(""), } - null() } } diff --git a/src/rustls.h b/src/rustls.h index 60aade0f..8cc6cc32 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -1706,12 +1706,24 @@ void rustls_connection_get_alpn_protocol(const struct rustls_connection *conn, uint16_t rustls_connection_get_protocol_version(const struct rustls_connection *conn); /** - * Retrieves the cipher suite agreed with the peer. - * This returns NULL until the ciphersuite is agreed. - * The returned pointer lives as long as the program. + * Retrieves the [IANA registered cipher suite identifier][IANA] agreed with the peer. + * This returns `TLS_NULL_WITH_NULL_NULL` (0x0000) until the ciphersuite is agreed. + * + * [IANA]: + */ +uint16_t rustls_connection_get_negotiated_ciphersuite(const struct rustls_connection *conn); + +/** + * Retrieves the cipher suite name agreed with the peer. + * + * This returns "" until the ciphersuite is agreed. + * + * The lifetime of the `rustls_str` is the lifetime of the program, it does not + * need to be freed. + * * */ -const struct rustls_supported_ciphersuite *rustls_connection_get_negotiated_ciphersuite(const struct rustls_connection *conn); +struct rustls_str rustls_connection_get_negotiated_ciphersuite_name(const struct rustls_connection *conn); /** * Write up to `count` plaintext bytes from `buf` into the `rustls_connection`. diff --git a/src/server.rs b/src/server.rs index 9ef3fda2..64090ced 100644 --- a/src/server.rs +++ b/src/server.rs @@ -777,8 +777,10 @@ mod tests { assert_eq!( rustls_connection::rustls_connection_get_negotiated_ciphersuite(conn), - null() + 0 ); + let cs_name = rustls_connection::rustls_connection_get_negotiated_ciphersuite_name(conn); + assert_eq!(unsafe { cs_name.to_str() }, ""); assert_eq!( rustls_connection::rustls_connection_get_peer_certificate(conn, 0), null() diff --git a/tests/client.c b/tests/client.c index fe01f1e1..39233bbf 100644 --- a/tests/client.c +++ b/tests/client.c @@ -170,6 +170,8 @@ send_request_and_read_response(struct conndata *conn, unsigned long content_length = 0; size_t headers_len = 0; struct rustls_str version; + int ciphersuite_id; + struct rustls_str ciphersuite_name; version = rustls_version(); memset(buf, '\0', sizeof(buf)); @@ -197,6 +199,13 @@ send_request_and_read_response(struct conndata *conn, goto cleanup; } + ciphersuite_id = rustls_connection_get_negotiated_ciphersuite(rconn); + ciphersuite_name = rustls_connection_get_negotiated_ciphersuite_name(rconn); + LOG("negotiated ciphersuite: %.*s (%#x)", + (int)ciphersuite_name.len, + ciphersuite_name.data, + ciphersuite_id); + for(;;) { FD_ZERO(&read_fds); /* These two calls just inspect the state of the connection - if it's time diff --git a/tests/server.c b/tests/server.c index c0a3a4f0..b1a96c9c 100644 --- a/tests/server.c +++ b/tests/server.c @@ -127,6 +127,8 @@ handle_conn(struct conndata *conn) int sockfd = conn->fd; struct timeval tv; enum exchange_state state = READING_REQUEST; + int ciphersuite_id; + struct rustls_str ciphersuite_name; LOG("acccepted conn on fd %d", conn->fd); @@ -189,6 +191,14 @@ handle_conn(struct conndata *conn) if(state == READING_REQUEST && body_beginning(&conn->data) != NULL) { state = SENT_RESPONSE; LOG_SIMPLE("writing response"); + ciphersuite_id = rustls_connection_get_negotiated_ciphersuite(rconn); + ciphersuite_name = + rustls_connection_get_negotiated_ciphersuite_name(rconn); + LOG("negotiated ciphersuite: %.*s (%#x)", + (int)ciphersuite_name.len, + ciphersuite_name.data, + ciphersuite_id); + rustls_connection_get_alpn_protocol( rconn, &negotiated_alpn, &negotiated_alpn_len); if(negotiated_alpn != NULL) { From dd15e56883d47ddf78e93104c2f1db186fd00d84 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Wed, 7 Aug 2024 13:48:17 -0400 Subject: [PATCH 05/25] client: make config builder building fallible Previously the `rustls_client_config_builder_build` function was infallible and returned a `rustls_client_config` instance without any chance to communicate errors. This commit updates this function to instead return `rustls_result` and use an out parameter for the `rustls_client_config`. Having this function unable to return a detailed error has a number of knock-on effects we want to address: * If no server certificate verifier has been configured the previous implementation isn't able to communicate that and instead configures a `NoneVerifier` - this ends up pushing failures to the time of certificate verification, making for a subpar user experience. If the user intended to disable certificate validation they need to do so with a verifier that won't error on use. If the user intended to configure certificate validation but something went wrong, we've made debugging harder. * Shortly we will allow customizing the crypto provider used for the configuration and may need to error if no suitable provider has been configured. The `client.c` example and `client.rs` unit tests are both updated to use the new out-parameter based API. --- src/acceptor.rs | 5 ++++- src/client.rs | 33 +++++++++++++++++++++++++-------- src/rustls.h | 3 ++- tests/client.c | 6 +++++- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/acceptor.rs b/src/acceptor.rs index edac4904..9d36c599 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -641,7 +641,10 @@ mod tests { protocols_slices.len(), ); - let config = ccb::rustls_client_config_builder_build(builder); + let mut config = null(); + let result = ccb::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); let mut client_conn = null_mut(); let result = rustls_client_config::rustls_client_connection_new( config, diff --git a/src/client.rs b/src/client.rs index 0ac3279b..71b7d0d5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -22,9 +22,9 @@ use crate::error::{self, rustls_result}; use crate::rslice::NulByte; use crate::rslice::{rustls_slice_bytes, rustls_slice_slice_bytes, rustls_str}; use crate::{ - arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, set_boxed_mut_ptr, - to_arc_const_ptr, to_boxed_mut_ptr, try_box_from_ptr, try_clone_arc, try_mut_from_ptr, - try_mut_from_ptr_ptr, try_ref_from_ptr, try_slice, userdata_get, + arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, set_arc_mut_ptr, + set_boxed_mut_ptr, to_boxed_mut_ptr, try_box_from_ptr, try_clone_arc, try_mut_from_ptr, + try_mut_from_ptr_ptr, try_ref_from_ptr, try_ref_from_ptr_ptr, try_slice, userdata_get, }; box_castable! { @@ -520,9 +520,12 @@ impl rustls_client_config_builder { #[no_mangle] pub extern "C" fn rustls_client_config_builder_build( builder: *mut rustls_client_config_builder, - ) -> *const rustls_client_config { + config_out: *mut *const rustls_client_config, + ) -> rustls_result { ffi_panic_boundary! { let builder = try_box_from_ptr!(builder); + let config_out = try_ref_from_ptr_ptr!(config_out); + let config = builder .base .dangerous() @@ -533,7 +536,9 @@ impl rustls_client_config_builder { }; config.alpn_protocols = builder.alpn_protocols; config.enable_sni = builder.enable_sni; - to_arc_const_ptr(config) + + set_arc_mut_ptr(config_out, config); + rustls_result::Ok } } @@ -638,7 +643,11 @@ mod tests { alpn.len(), ); rustls_client_config_builder::rustls_client_config_builder_set_enable_sni(builder, false); - let config = rustls_client_config_builder::rustls_client_config_builder_build(builder); + let mut config = null(); + let result = + rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); { let config2 = try_ref_from_ptr!(config); assert!(!config2.enable_sni); @@ -652,7 +661,11 @@ mod tests { #[cfg_attr(miri, ignore)] fn test_client_connection_new() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); - let config = rustls_client_config_builder::rustls_client_config_builder_build(builder); + let mut config = null(); + let result = + rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); let mut conn = null_mut(); let result = rustls_client_config::rustls_client_connection_new( config, @@ -699,7 +712,11 @@ mod tests { #[cfg_attr(miri, ignore)] fn test_client_connection_new_ipaddress() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); - let config = rustls_client_config_builder::rustls_client_config_builder_build(builder); + let mut config = null(); + let result = + rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); let mut conn = null_mut(); let result = rustls_client_config::rustls_client_connection_new( config, diff --git a/src/rustls.h b/src/rustls.h index 8cc6cc32..887b0376 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -1510,7 +1510,8 @@ rustls_result rustls_client_config_builder_set_certified_key(struct rustls_clien * Turn a *rustls_client_config_builder (mutable) into a const *rustls_client_config * (read-only). */ -const struct rustls_client_config *rustls_client_config_builder_build(struct rustls_client_config_builder *builder); +rustls_result rustls_client_config_builder_build(struct rustls_client_config_builder *builder, + const struct rustls_client_config **config_out); /** * "Free" a client_config_builder without building it into a rustls_client_config. diff --git a/tests/client.c b/tests/client.c index 39233bbf..34b9ca39 100644 --- a/tests/client.c +++ b/tests/client.c @@ -503,7 +503,11 @@ main(int argc, const char **argv) rustls_client_config_builder_set_alpn_protocols( config_builder, &alpn_http11, 1); - client_config = rustls_client_config_builder_build(config_builder); + result = rustls_client_config_builder_build(config_builder, &client_config); + if(result != RUSTLS_RESULT_OK) { + print_error("building client config", result); + goto cleanup; + } int i; for(i = 0; i < 3; i++) { From 8253f497ce5200acd6190707dbb011a131acd86f Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Wed, 7 Aug 2024 14:07:14 -0400 Subject: [PATCH 06/25] client: remove NoneVerifier Now that the `rustls_client_config_builder_build()` fn is fallible it makes more sense to return an error (`RUSTLS_RESULT_NO_SERVER_CERT_VERIFIER`) when the required server certificate verifier hasn't been set instead of using `NoneVerifier` and failing all certificate validations. This commit removes the `NoneVerifier` and updates the tests that were building a client config without specifying a verifier to use the platform verifier instead. A new unit test is added that ensures the correct error is returned when a config is built without a verifier. --- src/acceptor.rs | 9 ++++- src/client.rs | 87 +++++++++++++++++++++---------------------------- src/error.rs | 7 ++++ src/rustls.h | 1 + 4 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/acceptor.rs b/src/acceptor.rs index 9d36c599..f7799424 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -520,7 +520,7 @@ mod tests { use rustls::internal::msgs::enums::AlertLevel; use rustls::{AlertDescription, ContentType, ProtocolVersion}; - use crate::cipher::rustls_certified_key; + use crate::cipher::{rustls_certified_key, rustls_server_cert_verifier}; use crate::client::{rustls_client_config, rustls_client_config_builder}; use crate::server::rustls_server_config_builder; @@ -641,6 +641,12 @@ mod tests { protocols_slices.len(), ); + let verifier = rustls_server_cert_verifier::rustls_platform_server_cert_verifier(); + assert!(!verifier.is_null()); + rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( + builder, verifier, + ); + let mut config = null(); let result = ccb::rustls_client_config_builder_build(builder, &mut config); assert_eq!(result, rustls_result::Ok); @@ -665,6 +671,7 @@ mod tests { rustls_connection::rustls_connection_free(client_conn); rustls_client_config::rustls_client_config_free(config); + rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); buf } diff --git a/src/client.rs b/src/client.rs index 71b7d0d5..48cbdd28 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,8 +9,8 @@ use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, Server use rustls::client::ResolvesClientCert; use rustls::crypto::ring::ALL_CIPHER_SUITES; use rustls::{ - sign::CertifiedKey, CertificateError, ClientConfig, ClientConnection, DigitallySignedStruct, - Error, ProtocolVersion, SignatureScheme, WantsVerifier, + sign::CertifiedKey, ClientConfig, ClientConnection, DigitallySignedStruct, Error, + ProtocolVersion, SignatureScheme, WantsVerifier, }; use crate::cipher::{ @@ -45,7 +45,7 @@ box_castable! { pub(crate) struct ClientConfigBuilder { base: rustls::ConfigBuilder, - verifier: Arc, + verifier: Option>, alpn_protocols: Vec>, enable_sni: bool, cert_resolver: Option>, @@ -59,46 +59,6 @@ arc_castable! { pub struct rustls_client_config(ClientConfig); } -#[derive(Debug)] -struct NoneVerifier; - -impl ServerCertVerifier for NoneVerifier { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer, - _intermediates: &[CertificateDer], - _server_name: &pki_types::ServerName<'_>, - _ocsp_response: &[u8], - _now: UnixTime, - ) -> Result { - Err(Error::InvalidCertificate(CertificateError::UnknownIssuer)) - } - - fn verify_tls12_signature( - &self, - _message: &[u8], - _cert: &CertificateDer, - _dss: &DigitallySignedStruct, - ) -> Result { - Err(Error::InvalidCertificate(CertificateError::BadSignature)) - } - - fn verify_tls13_signature( - &self, - _message: &[u8], - _cert: &CertificateDer, - _dss: &DigitallySignedStruct, - ) -> Result { - Err(Error::InvalidCertificate(CertificateError::BadSignature)) - } - - fn supported_verify_schemes(&self) -> Vec { - rustls::crypto::ring::default_provider() - .signature_verification_algorithms - .supported_schemes() - } -} - impl rustls_client_config_builder { /// Create a rustls_client_config_builder. /// @@ -126,7 +86,7 @@ impl rustls_client_config_builder { .unwrap(); let builder = ClientConfigBuilder { base, - verifier: Arc::new(NoneVerifier), + verifier: None, cert_resolver: None, alpn_protocols: vec![], enable_sni: true, @@ -205,7 +165,7 @@ impl rustls_client_config_builder { }; let config_builder = ClientConfigBuilder { base, - verifier: Arc::new(NoneVerifier), + verifier: None, cert_resolver: None, alpn_protocols: vec![], enable_sni: true, @@ -386,8 +346,7 @@ impl rustls_client_config_builder { None => return InvalidParameter, }; - let verifier = Verifier { callback }; - config_builder.verifier = Arc::new(verifier); + config_builder.verifier = Some(Arc::new(Verifier { callback })); rustls_result::Ok } } @@ -402,8 +361,7 @@ impl rustls_client_config_builder { ) { ffi_panic_boundary! { let builder = try_mut_from_ptr!(builder); - let verifier = try_ref_from_ptr!(verifier); - builder.verifier = verifier.clone(); + builder.verifier = Some(try_ref_from_ptr!(verifier).clone()); } } @@ -526,10 +484,15 @@ impl rustls_client_config_builder { let builder = try_box_from_ptr!(builder); let config_out = try_ref_from_ptr_ptr!(config_out); + let verifier = match builder.verifier { + Some(v) => v, + None => return rustls_result::NoServerCertVerifier, + }; + let config = builder .base .dangerous() - .with_custom_certificate_verifier(builder.verifier); + .with_custom_certificate_verifier(verifier); let mut config = match builder.cert_resolver { Some(r) => config.with_client_cert_resolver(r), None => config.with_no_client_auth(), @@ -634,6 +597,11 @@ mod tests { #[test] fn test_config_builder() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); + let verifier = rustls_server_cert_verifier::rustls_platform_server_cert_verifier(); + assert!(!verifier.is_null()); + rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( + builder, verifier, + ); let h1 = "http/1.1".as_bytes(); let h2 = "h2".as_bytes(); let alpn = [h1.into(), h2.into()]; @@ -661,6 +629,11 @@ mod tests { #[cfg_attr(miri, ignore)] fn test_client_connection_new() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); + let verifier = rustls_server_cert_verifier::rustls_platform_server_cert_verifier(); + assert!(!verifier.is_null()); + rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( + builder, verifier, + ); let mut config = null(); let result = rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); @@ -712,6 +685,11 @@ mod tests { #[cfg_attr(miri, ignore)] fn test_client_connection_new_ipaddress() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); + let verifier = rustls_server_cert_verifier::rustls_platform_server_cert_verifier(); + assert!(!verifier.is_null()); + rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( + builder, verifier, + ); let mut config = null(); let result = rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); @@ -727,4 +705,13 @@ mod tests { panic!("expected RUSTLS_RESULT_OK, got {:?}", result); } } + + #[test] + fn test_client_builder_no_verifier_err() { + let builder = rustls_client_config_builder::rustls_client_config_builder_new(); + let mut config = null(); + let result = + rustls_client_config_builder::rustls_client_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::NoServerCertVerifier); + } } diff --git a/src/error.rs b/src/error.rs index 85ce803d..95ba3e81 100644 --- a/src/error.rs +++ b/src/error.rs @@ -60,6 +60,7 @@ u32_enum_builder! { AcceptorNotReady => 7012, AlreadyUsed => 7013, CertificateRevocationListParseError => 7014, + NoServerCertVerifier => 7015, // From https://docs.rs/rustls/latest/rustls/enum.Error.html NoCertificatesPresented => 7101, @@ -481,6 +482,12 @@ impl Display for rustls_result { CertificateRevocationListParseError => { write!(f, "error parsing certificate revocation list (CRL)",) } + NoServerCertVerifier => { + write!( + f, + "no server certificate verifier was configured on the client config builder" + ) + } CertEncodingBad => Error::InvalidCertificate(CertificateError::BadEncoding).fmt(f), CertExpired => Error::InvalidCertificate(CertificateError::Expired).fmt(f), diff --git a/src/rustls.h b/src/rustls.h index 887b0376..8bf76129 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -23,6 +23,7 @@ enum rustls_result { RUSTLS_RESULT_ACCEPTOR_NOT_READY = 7012, RUSTLS_RESULT_ALREADY_USED = 7013, RUSTLS_RESULT_CERTIFICATE_REVOCATION_LIST_PARSE_ERROR = 7014, + RUSTLS_RESULT_NO_SERVER_CERT_VERIFIER = 7015, RUSTLS_RESULT_NO_CERTIFICATES_PRESENTED = 7101, RUSTLS_RESULT_DECRYPT_ERROR = 7102, RUSTLS_RESULT_FAILED_TO_GET_CURRENT_TIME = 7103, From c3c021f71ac633483f355ddbc23077ceb61778de Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 11:54:22 -0400 Subject: [PATCH 07/25] crypto_provider: start wiring up crypto provider * Adds a `rustls_crypto_provider` type for representing a `rustls::crypto::CryptoProvider`. * The `*ring*` specific provider can be retrieved with `rustls_ring_crypto_provider()`. * The process-wide default crypto provider (if any) can be retrieved with `rustls_crypto_provider_default()`. * `rustls_crypto_provider_ciphersuites_len()` and `rustls_crypto_provider_ciphersuites_get()` can be used to fetch `rustls_supported_ciphersuite` instances the provider supports. * `rustls_default_crypto_provider_ciphersuites_len()` and `rustls_default_crypto_provider_ciphersuites_get()` can be used to fetch `rustls_supported_ciphersuite` instances the _default_ provider supports. * Adds a `rustls_crypto_provider_builder` that can be constructed based on the process default (`rustls_crypto_provider_builder_new()`) or a specific `rustls_crypto_provider` (`rustls_crypto_provider_builder_new_with_base()`). * The builder's supported ciphersuites can be customized with `rustls_crypto_provider_builder_set_cipher_suites()` * The builder can be turned into a `rustls_crypto_provider` with `rustls_crypto_provider_builder_build()`, or it can be built and installed as the process-wide default using `rustls_crypto_provider_builder_build_as_default()`. For the functions that assume a default (e.g. `rustls_default_supported_ciphersuites_len/get()`, and `rustls_crypto_provider_builder_new()`) we make an attempt to install a default based on unambiguous feature state if none has been explicitly set at the time of use. This matches the upstream Rustls behaviour using a function like `ClientConfig::builder()` and makes life easier for existing applications. The existing rustls-ffi code is not yet updated to use these abstractions. Similarly, the `*ring*` backend is unconditionally offered, but will become optional in subsequent commits. --- src/crypto_provider.rs | 359 +++++++++++++++++++++++++++++++++++++++++ src/error.rs | 7 + src/lib.rs | 1 + src/rustls.h | 216 ++++++++++++++++++++++++- 4 files changed, 582 insertions(+), 1 deletion(-) create mode 100644 src/crypto_provider.rs diff --git a/src/crypto_provider.rs b/src/crypto_provider.rs new file mode 100644 index 00000000..6d68e447 --- /dev/null +++ b/src/crypto_provider.rs @@ -0,0 +1,359 @@ +use libc::size_t; +use std::slice; +use std::sync::Arc; + +use rustls::crypto::ring; +use rustls::crypto::CryptoProvider; +use rustls::SupportedCipherSuite; + +use crate::cipher::rustls_supported_ciphersuite; +use crate::{ + arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, rustls_result, + set_arc_mut_ptr, set_boxed_mut_ptr, to_boxed_mut_ptr, try_clone_arc, try_mut_from_ptr, + try_mut_from_ptr_ptr, try_ref_from_ptr, try_ref_from_ptr_ptr, try_slice, try_take, +}; + +box_castable! { + /// A `rustls_crypto_provider` builder. + pub struct rustls_crypto_provider_builder(Option); +} + +/// A builder for customizing a `CryptoProvider`. Can be used to install a process-wide default. +#[derive(Debug)] +pub struct CryptoProviderBuilder { + base: Arc, + cipher_suites: Vec, +} + +impl CryptoProviderBuilder { + fn build_provider(self) -> CryptoProvider { + let cipher_suites = match self.cipher_suites.is_empty() { + true => self.base.cipher_suites.clone(), + false => self.cipher_suites, + }; + + // Unfortunately we can't use the `..` syntax to fill in the rest of the provider + // fields, because we're working with `Arc` as the base, + // not `CryptoProvider`. + // TODO(#450): once MSRV is 1.76+, use `Arc::unwrap_or_clone`. + CryptoProvider { + cipher_suites, + kx_groups: self.base.kx_groups.clone(), + signature_verification_algorithms: self.base.signature_verification_algorithms, + secure_random: self.base.secure_random, + key_provider: self.base.key_provider, + } + } +} + +/// Constructs a new `rustls_crypto_provider_builder` using the process-wide default crypto +/// provider as the base crypto provider to be customized. +/// +/// When this function returns `rustls_result::Ok` a pointer to the `rustls_crypto_provider_builder` +/// is written to `builder_out`. It returns `rustls_result::NoDefaultCryptoProvider` if no default +/// provider has been registered. +/// +/// The caller owns the returned `rustls_crypto_provider_builder` and must free it using +/// `rustls_crypto_provider_builder_free`. +/// +/// This function is typically used for customizing the default crypto provider for specific +/// connections. For example, a typical workflow might be to: +/// +/// * Either: +/// * Use the default `aws-lc-rs` or `*ring*` provider that rustls-ffi is built with based on +/// the `CRYPTO_PROVIDER` build variable. +/// * Call `rustls_crypto_provider_builder_new_with_base` with the desired provider, and +/// then install it as the process default with +/// `rustls_crypto_provider_builder_build_as_default`. +/// * Afterward, as required for customization: +/// * Use `rustls_crypto_provider_builder_new_from_default` to get a builder backed by the +/// default crypto provider. +/// * Use `rustls_crypto_provider_builder_set_cipher_suites` to customize the supported +/// ciphersuites. +/// * Use `rustls_crypto_provider_builder_build` to build a customized provider. +/// * Provide that customized provider to client or server configuration builders. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_new_from_default( + builder_out: *mut *mut rustls_crypto_provider_builder, +) -> rustls_result { + ffi_panic_boundary! { + let provider_out = try_mut_from_ptr_ptr!(builder_out); + + let base = match get_default_or_install_from_crate_features() { + Some(provider) => provider, + None => return rustls_result::NoDefaultCryptoProvider, + }; + + set_boxed_mut_ptr( + provider_out, + Some(CryptoProviderBuilder { + base, + cipher_suites: Vec::default(), + }), + ); + + rustls_result::Ok + } +} + +/// Constructs a new `rustls_crypto_provider_builder` using the given `rustls_crypto_provider` +/// as the base crypto provider to be customized. +/// +/// The caller owns the returned `rustls_crypto_provider_builder` and must free it using +/// `rustls_crypto_provider_builder_free`. +/// +/// This function can be used for setting the default process wide crypto provider, +/// or for constructing a custom crypto provider for a specific connection. A typical +/// workflow could be to: +/// +/// * Call `rustls_crypto_provider_builder_new_with_base` with a custom provider +/// * Install the custom provider as the process-wide default with +/// `rustls_crypto_provider_builder_build_as_default`. +/// +/// Or, for per-connection customization: +/// +/// * Call `rustls_crypto_provider_builder_new_with_base` with a custom provider +/// * Use `rustls_crypto_provider_builder_set_cipher_suites` to customize the supported +/// ciphersuites. +/// * Use `rustls_crypto_provider_builder_build` to build a customized provider. +/// * Provide that customized provider to client or server configuration builders. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_new_with_base( + base: *const rustls_crypto_provider, +) -> *mut rustls_crypto_provider_builder { + ffi_panic_boundary! { + to_boxed_mut_ptr(Some(CryptoProviderBuilder { + base: try_clone_arc!(base), + cipher_suites: Vec::default(), + })) + } +} + +/// Customize the supported ciphersuites of the `rustls_crypto_provider_builder`. +/// +/// Returns an error if the builder has already been built. Overwrites any previously +/// set ciphersuites. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_set_cipher_suites( + builder: *mut rustls_crypto_provider_builder, + cipher_suites: *const *const rustls_supported_ciphersuite, + cipher_suites_len: size_t, +) -> rustls_result { + ffi_panic_boundary! { + let builder = try_mut_from_ptr!(builder); + let builder = match builder { + Some(builder) => builder, + None => return rustls_result::AlreadyUsed, + }; + + let cipher_suites = try_slice!(cipher_suites, cipher_suites_len); + let mut supported_cipher_suites = Vec::new(); + for cs in cipher_suites { + let cs = *cs; + let cs = try_ref_from_ptr!(cs); + supported_cipher_suites.push(*cs); + } + + builder.cipher_suites = supported_cipher_suites; + rustls_result::Ok + } +} + +/// Builds a `rustls_crypto_provider` from the builder and returns it. Returns an error if the +/// builder has already been built. +/// +/// The `rustls_crypto_provider_builder` builder is consumed and should not be used +/// for further calls, except to `rustls_crypto_provider_builder_free`. The caller must +/// still free the builder after a successful build. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_build( + builder: *mut rustls_crypto_provider_builder, + provider_out: *mut *const rustls_crypto_provider, +) -> rustls_result { + ffi_panic_boundary! { + let builder = try_mut_from_ptr!(builder); + set_arc_mut_ptr( + try_ref_from_ptr_ptr!(provider_out), + try_take!(builder).build_provider(), + ); + rustls_result::Ok + } +} + +/// Builds a `rustls_crypto_provider` from the builder and sets it as the +/// process-wide default crypto provider. +/// +/// Afterward, the default provider can be retrieved using `rustls_crypto_provider_default`. +/// +/// This can only be done once per process, and will return an error if a +/// default provider has already been set, or if the builder has already been built. +/// +/// The `rustls_crypto_provider_builder` builder is consumed and should not be used +/// for further calls, except to `rustls_crypto_provider_builder_free`. The caller must +/// still free the builder after a successful build. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_build_as_default( + builder: *mut rustls_crypto_provider_builder, +) -> rustls_result { + let builder = try_mut_from_ptr!(builder); + match try_take!(builder).build_provider().install_default() { + Ok(_) => rustls_result::Ok, + Err(_) => rustls_result::AlreadyUsed, + } +} + +/// Free the `rustls_crypto_provider_builder`. +/// +/// Calling with `NULL` is fine. +/// Must not be called twice with the same value. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_builder_free( + builder: *mut rustls_crypto_provider_builder, +) { + ffi_panic_boundary! { + free_box(builder); + } +} + +/// Return the `rustls_crypto_provider` backed by the `*ring*` cryptography library. +/// +/// The caller owns the returned `rustls_crypto_provider` and must free it using +/// `rustls_crypto_provider_free`. +// TODO(@cpu): Add a feature gate when we add support for other crypto providers. +#[no_mangle] +pub extern "C" fn rustls_ring_crypto_provider() -> *const rustls_crypto_provider { + ffi_panic_boundary! { + Arc::into_raw(Arc::new(ring::default_provider())) as *const rustls_crypto_provider + } +} + +/// Retrieve a pointer to the process default `rustls_crypto_provider`. +/// +/// This may return `NULL` if no process default provider has been set using +/// `rustls_crypto_provider_builder_build_default`. +/// +/// Caller owns the returned `rustls_crypto_provider` and must free it w/ `rustls_crypto_provider_free`. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_default() -> *const rustls_crypto_provider { + ffi_panic_boundary! { + match CryptoProvider::get_default() { + Some(provider) => Arc::into_raw(provider.clone()) as *const rustls_crypto_provider, + None => core::ptr::null(), + } + } +} + +arc_castable! { + /// A C representation of a Rustls [`CryptoProvider`]. + pub struct rustls_crypto_provider(CryptoProvider); +} + +/// Returns the number of ciphersuites the `rustls_crypto_provider` supports. +/// +/// You can use this to know the maximum allowed index for use with +/// `rustls_crypto_provider_ciphersuites_get`. +/// +/// This function will return 0 if the `provider` is NULL. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_ciphersuites_len( + provider: *const rustls_crypto_provider, +) -> usize { + ffi_panic_boundary! { + try_clone_arc!(provider).cipher_suites.len() + } +} + +/// Retrieve a pointer to a supported ciphersuite of the `rustls_crypto_provider`. +/// +/// This function will return NULL if the `provider` is NULL, or if the index is out of bounds +/// with respect to `rustls_crypto_provider_ciphersuites_len`. +/// +/// The lifetime of the returned `rustls_supported_ciphersuite` is equal to the lifetime of the +/// `provider` and should not be used after the `provider` is freed. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_ciphersuites_get( + provider: *const rustls_crypto_provider, + index: usize, +) -> *const rustls_supported_ciphersuite { + ffi_panic_boundary! { + match try_clone_arc!(provider).cipher_suites.get(index) { + Some(ciphersuite) => ciphersuite as *const SupportedCipherSuite as *const _, + None => core::ptr::null(), + } + } +} + +/// Frees the `rustls_crypto_provider`. +/// +/// Calling with `NULL` is fine. +/// Must not be called twice with the same value. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_free(provider: *const rustls_crypto_provider) { + ffi_panic_boundary! { + free_arc(provider); + } +} + +/// Returns the number of ciphersuites the default process-wide crypto provider supports. +/// +/// You can use this to know the maximum allowed index for use with +/// `rustls_default_crypto_provider_ciphersuites_get`. +/// +/// This function will return 0 if no process-wide default `rustls_crypto_provider` is available. +#[no_mangle] +pub extern "C" fn rustls_default_crypto_provider_ciphersuites_len() -> usize { + ffi_panic_boundary! { + match get_default_or_install_from_crate_features() { + Some(provider) => provider.cipher_suites.len(), + None => return 0, + } + } +} + +/// Retrieve a pointer to a supported ciphersuite of the default process-wide crypto provider. +/// +/// This function will return NULL if the `provider` is NULL, or if the index is out of bounds +/// with respect to `rustls_default_crypto_provider_ciphersuites_len`. +/// +/// The lifetime of the returned `rustls_supported_ciphersuite` is static, as the process-wide +/// default provider lives for as long as the process. +#[no_mangle] +pub extern "C" fn rustls_default_crypto_provider_ciphersuites_get( + index: usize, +) -> *const rustls_supported_ciphersuite { + ffi_panic_boundary! { + let default_provider = match get_default_or_install_from_crate_features() { + Some(provider) => provider, + None => return core::ptr::null(), + }; + match default_provider.cipher_suites.get(index) { + Some(ciphersuite) => ciphersuite as *const SupportedCipherSuite as *const _, + None => core::ptr::null(), + } + } +} + +fn get_default_or_install_from_crate_features() -> Option> { + // If a process-wide default has already been installed, return it. + if let Some(provider) = CryptoProvider::get_default() { + return Some(provider.clone()); + } + + let provider = match provider_from_crate_features() { + Some(provider) => provider, + None => return None, + }; + + // Ignore the error resulting from us losing a race to install the default, + // and accept the outcome. + let _ = provider.install_default(); + + // Safety: we can unwrap safely here knowing we've just set the default, or + // lost a race to something else setting the default. + Some(CryptoProvider::get_default().unwrap().clone()) +} + +fn provider_from_crate_features() -> Option { + // TODO(XXX): Switch based on crate feature once ring is optional. + Some(ring::default_provider()) +} diff --git a/src/error.rs b/src/error.rs index 95ba3e81..0dbcb922 100644 --- a/src/error.rs +++ b/src/error.rs @@ -61,6 +61,7 @@ u32_enum_builder! { AlreadyUsed => 7013, CertificateRevocationListParseError => 7014, NoServerCertVerifier => 7015, + NoDefaultCryptoProvider => 7016, // From https://docs.rs/rustls/latest/rustls/enum.Error.html NoCertificatesPresented => 7101, @@ -488,6 +489,12 @@ impl Display for rustls_result { "no server certificate verifier was configured on the client config builder" ) } + NoDefaultCryptoProvider => { + write!( + f, + "no default process-wide crypto provider has been installed" + ) + } CertEncodingBad => Error::InvalidCertificate(CertificateError::BadEncoding).fmt(f), CertExpired => Error::InvalidCertificate(CertificateError::Expired).fmt(f), diff --git a/src/lib.rs b/src/lib.rs index 80787b8a..2bcf9464 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub mod acceptor; pub mod cipher; pub mod client; pub mod connection; +pub mod crypto_provider; pub mod enums; mod error; pub mod io; diff --git a/src/rustls.h b/src/rustls.h index 8bf76129..e3563e73 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -24,6 +24,7 @@ enum rustls_result { RUSTLS_RESULT_ALREADY_USED = 7013, RUSTLS_RESULT_CERTIFICATE_REVOCATION_LIST_PARSE_ERROR = 7014, RUSTLS_RESULT_NO_SERVER_CERT_VERIFIER = 7015, + RUSTLS_RESULT_NO_DEFAULT_CRYPTO_PROVIDER = 7016, RUSTLS_RESULT_NO_CERTIFICATES_PRESENTED = 7101, RUSTLS_RESULT_DECRYPT_ERROR = 7102, RUSTLS_RESULT_FAILED_TO_GET_CURRENT_TIME = 7103, @@ -226,6 +227,16 @@ typedef struct rustls_client_config_builder rustls_client_config_builder; typedef struct rustls_connection rustls_connection; +/** + * A C representation of a Rustls [`CryptoProvider`]. + */ +typedef struct rustls_crypto_provider rustls_crypto_provider; + +/** + * A `rustls_crypto_provider` builder. + */ +typedef struct rustls_crypto_provider_builder rustls_crypto_provider_builder; + /** * An alias for `struct iovec` from uio.h (on Unix) or `WSABUF` on Windows. * @@ -658,20 +669,54 @@ typedef uint32_t (*rustls_session_store_put_callback)(rustls_session_store_userd const struct rustls_slice_bytes *key, const struct rustls_slice_bytes *val); +/** + * Rustls' list of supported cipher suites. + * + * This is an array of pointers, and its length is given by + * `RUSTLS_ALL_CIPHER_SUITES_LEN`. The pointers will always be valid. + * The contents and order of this array may change between releases. + */ extern const struct rustls_supported_ciphersuite *RUSTLS_ALL_CIPHER_SUITES[9]; +/** + * The length of the array `RUSTLS_ALL_CIPHER_SUITES`. + */ extern const size_t RUSTLS_ALL_CIPHER_SUITES_LEN; +/** + * Rustls' list of default cipher suites. + * + * This is an array of pointers, and its length is given by + * `RUSTLS_DEFAULT_CIPHER_SUITES_LEN`. The pointers will always be valid. + * The contents and order of this array may change between releases. + */ extern const struct rustls_supported_ciphersuite *RUSTLS_DEFAULT_CIPHER_SUITES[9]; +/** + * The length of the array `RUSTLS_DEFAULT_CIPHER_SUITES`. + */ extern const size_t RUSTLS_DEFAULT_CIPHER_SUITES_LEN; +/** + * Rustls' list of supported protocol versions. The length of the array is + * given by `RUSTLS_ALL_VERSIONS_LEN`. + */ extern const uint16_t RUSTLS_ALL_VERSIONS[2]; +/** + * The length of the array `RUSTLS_ALL_VERSIONS`. + */ extern const size_t RUSTLS_ALL_VERSIONS_LEN; +/** + * Rustls' default list of protocol versions. The length of the array is + * given by `RUSTLS_DEFAULT_VERSIONS_LEN`. + */ extern const uint16_t RUSTLS_DEFAULT_VERSIONS[2]; +/** + * The length of the array `RUSTLS_DEFAULT_VERSIONS`. + */ extern const size_t RUSTLS_DEFAULT_VERSIONS_LEN; /** @@ -1709,6 +1754,7 @@ uint16_t rustls_connection_get_protocol_version(const struct rustls_connection * /** * Retrieves the [IANA registered cipher suite identifier][IANA] agreed with the peer. + * * This returns `TLS_NULL_WITH_NULL_NULL` (0x0000) until the ciphersuite is agreed. * * [IANA]: @@ -1787,6 +1833,174 @@ rustls_result rustls_connection_read_2(struct rustls_connection *conn, */ void rustls_connection_free(struct rustls_connection *conn); +/** + * Constructs a new `rustls_crypto_provider_builder` using the process-wide default crypto + * provider as the base crypto provider to be customized. + * + * When this function returns `rustls_result::Ok` a pointer to the `rustls_crypto_provider_builder` + * is written to `builder_out`. It returns `rustls_result::NoDefaultCryptoProvider` if no default + * provider has been registered. + * + * The caller owns the returned `rustls_crypto_provider_builder` and must free it using + * `rustls_crypto_provider_builder_free`. + * + * This function is typically used for customizing the default crypto provider for specific + * connections. For example, a typical workflow might be to: + * + * * Either: + * * Use the default `aws-lc-rs` or `*ring*` provider that rustls-ffi is built with based on + * the `CRYPTO_PROVIDER` build variable. + * * Call `rustls_crypto_provider_builder_new_with_base` with the desired provider, and + * then install it as the process default with + * `rustls_crypto_provider_builder_build_as_default`. + * * Afterward, as required for customization: + * * Use `rustls_crypto_provider_builder_new_from_default` to get a builder backed by the + * default crypto provider. + * * Use `rustls_crypto_provider_builder_set_cipher_suites` to customize the supported + * ciphersuites. + * * Use `rustls_crypto_provider_builder_build` to build a customized provider. + * * Provide that customized provider to client or server configuration builders. + */ +rustls_result rustls_crypto_provider_builder_new_from_default(struct rustls_crypto_provider_builder **builder_out); + +/** + * Constructs a new `rustls_crypto_provider_builder` using the given `rustls_crypto_provider` + * as the base crypto provider to be customized. + * + * The caller owns the returned `rustls_crypto_provider_builder` and must free it using + * `rustls_crypto_provider_builder_free`. + * + * This function can be used for setting the default process wide crypto provider, + * or for constructing a custom crypto provider for a specific connection. A typical + * workflow could be to: + * + * * Call `rustls_crypto_provider_builder_new_with_base` with a custom provider + * * Install the custom provider as the process-wide default with + * `rustls_crypto_provider_builder_build_as_default`. + * + * Or, for per-connection customization: + * + * * Call `rustls_crypto_provider_builder_new_with_base` with a custom provider + * * Use `rustls_crypto_provider_builder_set_cipher_suites` to customize the supported + * ciphersuites. + * * Use `rustls_crypto_provider_builder_build` to build a customized provider. + * * Provide that customized provider to client or server configuration builders. + */ +struct rustls_crypto_provider_builder *rustls_crypto_provider_builder_new_with_base(const struct rustls_crypto_provider *base); + +/** + * Customize the supported ciphersuites of the `rustls_crypto_provider_builder`. + * + * Returns an error if the builder has already been built. Overwrites any previously + * set ciphersuites. + */ +rustls_result rustls_crypto_provider_builder_set_cipher_suites(struct rustls_crypto_provider_builder *builder, + const struct rustls_supported_ciphersuite *const *cipher_suites, + size_t cipher_suites_len); + +/** + * Builds a `rustls_crypto_provider` from the builder and returns it. Returns an error if the + * builder has already been built. + * + * The `rustls_crypto_provider_builder` builder is consumed and should not be used + * for further calls, except to `rustls_crypto_provider_builder_free`. The caller must + * still free the builder after a successful build. + */ +rustls_result rustls_crypto_provider_builder_build(struct rustls_crypto_provider_builder *builder, + const struct rustls_crypto_provider **provider_out); + +/** + * Builds a `rustls_crypto_provider` from the builder and sets it as the + * process-wide default crypto provider. + * + * Afterward, the default provider can be retrieved using `rustls_crypto_provider_default`. + * + * This can only be done once per process, and will return an error if a + * default provider has already been set, or if the builder has already been built. + * + * The `rustls_crypto_provider_builder` builder is consumed and should not be used + * for further calls, except to `rustls_crypto_provider_builder_free`. The caller must + * still free the builder after a successful build. + */ +rustls_result rustls_crypto_provider_builder_build_as_default(struct rustls_crypto_provider_builder *builder); + +/** + * Free the `rustls_crypto_provider_builder`. + * + * Calling with `NULL` is fine. + * Must not be called twice with the same value. + */ +void rustls_crypto_provider_builder_free(struct rustls_crypto_provider_builder *builder); + +/** + * Return the `rustls_crypto_provider` backed by the `*ring*` cryptography library. + * + * The caller owns the returned `rustls_crypto_provider` and must free it using + * `rustls_crypto_provider_free`. + */ +const struct rustls_crypto_provider *rustls_ring_crypto_provider(void); + +/** + * Retrieve a pointer to the process default `rustls_crypto_provider`. + * + * This may return `NULL` if no process default provider has been set using + * `rustls_crypto_provider_builder_build_default`. + * + * Caller owns the returned `rustls_crypto_provider` and must free it w/ `rustls_crypto_provider_free`. + */ +const struct rustls_crypto_provider *rustls_crypto_provider_default(void); + +/** + * Returns the number of ciphersuites the `rustls_crypto_provider` supports. + * + * You can use this to know the maximum allowed index for use with + * `rustls_crypto_provider_ciphersuites_get`. + * + * This function will return 0 if the `provider` is NULL. + */ +size_t rustls_crypto_provider_ciphersuites_len(const struct rustls_crypto_provider *provider); + +/** + * Retrieve a pointer to a supported ciphersuite of the `rustls_crypto_provider`. + * + * This function will return NULL if the `provider` is NULL, or if the index is out of bounds + * with respect to `rustls_crypto_provider_ciphersuites_len`. + * + * The lifetime of the returned `rustls_supported_ciphersuite` is equal to the lifetime of the + * `provider` and should not be used after the `provider` is freed. + */ +const struct rustls_supported_ciphersuite *rustls_crypto_provider_ciphersuites_get(const struct rustls_crypto_provider *provider, + size_t index); + +/** + * Frees the `rustls_crypto_provider`. + * + * Calling with `NULL` is fine. + * Must not be called twice with the same value. + */ +void rustls_crypto_provider_free(const struct rustls_crypto_provider *provider); + +/** + * Returns the number of ciphersuites the default process-wide crypto provider supports. + * + * You can use this to know the maximum allowed index for use with + * `rustls_default_crypto_provider_ciphersuites_get`. + * + * This function will return 0 if no process-wide default `rustls_crypto_provider` is available. + */ +size_t rustls_default_crypto_provider_ciphersuites_len(void); + +/** + * Retrieve a pointer to a supported ciphersuite of the default process-wide crypto provider. + * + * This function will return NULL if the `provider` is NULL, or if the index is out of bounds + * with respect to `rustls_default_crypto_provider_ciphersuites_len`. + * + * The lifetime of the returned `rustls_supported_ciphersuite` is static, as the process-wide + * default provider lives for as long as the process. + */ +const struct rustls_supported_ciphersuite *rustls_default_crypto_provider_ciphersuites_get(size_t index); + /** * After a rustls function returns an error, you may call * this to get a pointer to a buffer containing a detailed error @@ -2049,4 +2263,4 @@ rustls_result rustls_server_config_builder_set_persistence(struct rustls_server_ rustls_session_store_get_callback get_cb, rustls_session_store_put_callback put_cb); -#endif /* RUSTLS_H */ +#endif /* RUSTLS_H */ From fc4f692f7f7456eec3af7a8649f839dd9eddb40f Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 13:06:32 -0400 Subject: [PATCH 08/25] server: convert server config/builder to provider * `rustls_server_config_builder_new()` now uses the process default crypto provider instead of being hardcoded to `*ring*`. We defer constructing the `ServerConfig` to avoid a panic in the event the process default isn't constructed. This will be surfaced as an error at build time instead. Like the upstream `ServerConfig::builder()` we make an attempt to install a process default provider from `rustls_server_config_builder_new()` if one has not been set and a clear choice is available based on crate features. * `rustls_server_config_builder_new_custom()` now takes a `rustls_crypto_provider` as an argument in place of the list of custom ciphersuites. The ciphersuites can be customized when the provider is constructed. * `rustls_server_config_builder_build()` now uses an out param for the `ServerConfig` so we can return a suitable error if there is no crypto provider (e.g. because `rustls_server_config_builder_new()` was used but the process default wasn't set and couldn't be guessed by crate features). * The `server.c` test code is updated to account for the breaking change in the builder out param. --- src/acceptor.rs | 7 +- src/crypto_provider.rs | 2 +- src/rustls.h | 28 ++++---- src/server.rs | 160 +++++++++++++++++++++++------------------ tests/server.c | 7 +- 5 files changed, 120 insertions(+), 84 deletions(-) diff --git a/src/acceptor.rs b/src/acceptor.rs index f7799424..a57feed6 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -696,8 +696,11 @@ mod tests { assert_eq!(result, rustls_result::Ok); rustls_certified_key::rustls_certified_key_free(certified_key); - let config = rustls_server_config_builder::rustls_server_config_builder_build(builder); - assert_ne!(config, null()); + let mut config = null(); + let res = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(res, rustls_result::Ok); + assert!(!config.is_null()); config } diff --git a/src/crypto_provider.rs b/src/crypto_provider.rs index 6d68e447..d12a0502 100644 --- a/src/crypto_provider.rs +++ b/src/crypto_provider.rs @@ -333,7 +333,7 @@ pub extern "C" fn rustls_default_crypto_provider_ciphersuites_get( } } -fn get_default_or_install_from_crate_features() -> Option> { +pub(crate) fn get_default_or_install_from_crate_features() -> Option> { // If a process-wide default has already been installed, return it. if let Some(provider) = CryptoProvider::get_default() { return Some(provider.clone()); diff --git a/src/rustls.h b/src/rustls.h index e3563e73..29d4420f 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -2049,7 +2049,7 @@ size_t rustls_slice_str_len(const struct rustls_slice_str *input); struct rustls_str rustls_slice_str_get(const struct rustls_slice_str *input, size_t n); /** - * Create a rustls_server_config_builder. + * Create a rustls_server_config_builder using the process default crypto provider. * * Caller owns the memory and must eventually call rustls_server_config_builder_build, * then free the resulting rustls_server_config. @@ -2057,13 +2057,13 @@ struct rustls_str rustls_slice_str_get(const struct rustls_slice_str *input, siz * Alternatively, if an error occurs or, you don't wish to build a config, call * `rustls_server_config_builder_free` to free the builder directly. * - * This uses rustls safe default values for the cipher suites, key exchange groups - * and protocol versions. + * This uses the process default provider's values for the cipher suites and key exchange + * groups, as well as safe defaults for protocol versions. */ struct rustls_server_config_builder *rustls_server_config_builder_new(void); /** - * Create a rustls_server_config_builder. + * Create a rustls_server_config_builder using the specified crypto provider. * * Caller owns the memory and must eventually call rustls_server_config_builder_build, * then free the resulting rustls_server_config. @@ -2071,10 +2071,6 @@ struct rustls_server_config_builder *rustls_server_config_builder_new(void); * Alternatively, if an error occurs or, you don't wish to build a config, call * `rustls_server_config_builder_free` to free the builder directly. * - * Specify cipher suites in preference order; the `cipher_suites` parameter must - * point to an array containing `len` pointers to `rustls_supported_ciphersuite` - * previously obtained from `rustls_all_ciphersuites_get_entry()`. - * * `tls_versions` set the TLS protocol versions to use when negotiating a TLS session. * * `tls_versions` is the version of the protocol, as defined in rfc8446, @@ -2084,9 +2080,11 @@ struct rustls_server_config_builder *rustls_server_config_builder_new(void); * `tls_versions` will only be used during the call and the application retains * ownership. `tls_versions_len` is the number of consecutive `uint16_t` pointed * to by `tls_versions`. + * + * Ciphersuites are configured separately via the crypto provider. See + * `rustls_crypto_provider_builder_set_cipher_suites` for more information. */ -rustls_result rustls_server_config_builder_new_custom(const struct rustls_supported_ciphersuite *const *cipher_suites, - size_t cipher_suites_len, +rustls_result rustls_server_config_builder_new_custom(const struct rustls_crypto_provider *provider, const uint16_t *tls_versions, size_t tls_versions_len, struct rustls_server_config_builder **builder_out); @@ -2159,9 +2157,15 @@ rustls_result rustls_server_config_builder_set_certified_keys(struct rustls_serv /** * Turn a *rustls_server_config_builder (mutable) into a const *rustls_server_config - * (read-only). + * (read-only). The constructed `rustls_server_config` will be written to the `config_out` + * pointer when this function returns `rustls_result::Ok`. + * + * This function may return an error if no process default crypto provider has been set + * and the builder was constructed using `rustls_server_config_builder_new`, or if no + * certificate resolver was set. */ -const struct rustls_server_config *rustls_server_config_builder_build(struct rustls_server_config_builder *builder); +rustls_result rustls_server_config_builder_build(struct rustls_server_config_builder *builder, + const struct rustls_server_config **config_out); /** * "Free" a rustls_server_config previously returned from diff --git a/src/server.rs b/src/server.rs index 64090ced..0e94ac6b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,33 +1,32 @@ use std::ffi::c_void; use std::fmt::{Debug, Formatter}; -use std::ptr::null; use std::slice; use std::sync::Arc; use libc::size_t; -use rustls::crypto::ring::ALL_CIPHER_SUITES; +use rustls::crypto::CryptoProvider; use rustls::server::danger::ClientCertVerifier; use rustls::server::{ ClientHello, ResolvesServerCert, ServerConfig, ServerConnection, StoresServerSessions, WebPkiClientVerifier, }; use rustls::sign::CertifiedKey; -use rustls::{ProtocolVersion, SignatureScheme, WantsVerifier}; +use rustls::{ProtocolVersion, SignatureScheme, SupportedProtocolVersion}; -use crate::cipher::{ - rustls_certified_key, rustls_client_cert_verifier, rustls_supported_ciphersuite, -}; +use crate::cipher::{rustls_certified_key, rustls_client_cert_verifier}; use crate::connection::{rustls_connection, Connection}; -use crate::error::rustls_result::{InvalidParameter, NullParameter}; +use crate::crypto_provider::rustls_crypto_provider; +use crate::error::rustls_result::NullParameter; use crate::error::{map_error, rustls_result}; use crate::rslice::{rustls_slice_bytes, rustls_slice_slice_bytes, rustls_slice_u16, rustls_str}; use crate::session::{ rustls_session_store_get_callback, rustls_session_store_put_callback, SessionStoreBroker, }; use crate::{ - arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, set_boxed_mut_ptr, - to_arc_const_ptr, to_boxed_mut_ptr, try_box_from_ptr, try_clone_arc, try_mut_from_ptr, - try_mut_from_ptr_ptr, try_ref_from_ptr, try_slice, userdata_get, Castable, OwnershipRef, + arc_castable, box_castable, crypto_provider, ffi_panic_boundary, free_arc, free_box, + set_arc_mut_ptr, set_boxed_mut_ptr, to_boxed_mut_ptr, try_box_from_ptr, try_clone_arc, + try_mut_from_ptr, try_mut_from_ptr_ptr, try_ref_from_ptr, try_ref_from_ptr_ptr, try_slice, + userdata_get, Castable, OwnershipRef, }; box_castable! { @@ -47,7 +46,8 @@ box_castable! { } pub(crate) struct ServerConfigBuilder { - base: rustls::ConfigBuilder, + provider: Option>, + versions: Vec<&'static SupportedProtocolVersion>, verifier: Arc, cert_resolver: Option>, session_storage: Option>, @@ -64,7 +64,7 @@ arc_castable! { } impl rustls_server_config_builder { - /// Create a rustls_server_config_builder. + /// Create a rustls_server_config_builder using the process default crypto provider. /// /// Caller owns the memory and must eventually call rustls_server_config_builder_build, /// then free the resulting rustls_server_config. @@ -72,20 +72,14 @@ impl rustls_server_config_builder { /// Alternatively, if an error occurs or, you don't wish to build a config, call /// `rustls_server_config_builder_free` to free the builder directly. /// - /// This uses rustls safe default values for the cipher suites, key exchange groups - /// and protocol versions. + /// This uses the process default provider's values for the cipher suites and key exchange + /// groups, as well as safe defaults for protocol versions. #[no_mangle] pub extern "C" fn rustls_server_config_builder_new() -> *mut rustls_server_config_builder { ffi_panic_boundary! { - // Unwrap safety: *ring* default provider always has ciphersuites compatible with the - // default protocol versions. - let base = ServerConfig::builder_with_provider( - rustls::crypto::ring::default_provider().into(), - ) - .with_safe_default_protocol_versions() - .unwrap(); let builder = ServerConfigBuilder { - base, + provider: crypto_provider::get_default_or_install_from_crate_features(), + versions: rustls::DEFAULT_VERSIONS.to_vec(), verifier: WebPkiClientVerifier::no_client_auth(), cert_resolver: None, session_storage: None, @@ -96,7 +90,7 @@ impl rustls_server_config_builder { } } - /// Create a rustls_server_config_builder. + /// Create a rustls_server_config_builder using the specified crypto provider. /// /// Caller owns the memory and must eventually call rustls_server_config_builder_build, /// then free the resulting rustls_server_config. @@ -104,10 +98,6 @@ impl rustls_server_config_builder { /// Alternatively, if an error occurs or, you don't wish to build a config, call /// `rustls_server_config_builder_free` to free the builder directly. /// - /// Specify cipher suites in preference order; the `cipher_suites` parameter must - /// point to an array containing `len` pointers to `rustls_supported_ciphersuite` - /// previously obtained from `rustls_all_ciphersuites_get_entry()`. - /// /// `tls_versions` set the TLS protocol versions to use when negotiating a TLS session. /// /// `tls_versions` is the version of the protocol, as defined in rfc8446, @@ -117,28 +107,18 @@ impl rustls_server_config_builder { /// `tls_versions` will only be used during the call and the application retains /// ownership. `tls_versions_len` is the number of consecutive `uint16_t` pointed /// to by `tls_versions`. + /// + /// Ciphersuites are configured separately via the crypto provider. See + /// `rustls_crypto_provider_builder_set_cipher_suites` for more information. #[no_mangle] pub extern "C" fn rustls_server_config_builder_new_custom( - cipher_suites: *const *const rustls_supported_ciphersuite, - cipher_suites_len: size_t, + provider: *const rustls_crypto_provider, tls_versions: *const u16, tls_versions_len: size_t, builder_out: *mut *mut rustls_server_config_builder, ) -> rustls_result { ffi_panic_boundary! { - if builder_out.is_null() { - return NullParameter; - } - let cipher_suites = try_slice!(cipher_suites, cipher_suites_len); - let mut cs_vec = Vec::new(); - for &cs in cipher_suites.iter() { - let cs = try_ref_from_ptr!(cs); - match ALL_CIPHER_SUITES.iter().find(|&acs| cs.eq(acs)) { - Some(scs) => cs_vec.push(*scs), - None => return InvalidParameter, - } - } - + let provider = try_clone_arc!(provider); let tls_versions = try_slice!(tls_versions, tls_versions_len); let mut versions = vec![]; for version_number in tls_versions { @@ -149,22 +129,11 @@ impl rustls_server_config_builder { versions.push(&rustls::version::TLS13); } } - let builder_out = try_mut_from_ptr_ptr!(builder_out); - let provider = rustls::crypto::CryptoProvider { - cipher_suites: cs_vec, - ..rustls::crypto::ring::default_provider() - }; - let result = rustls::ServerConfig::builder_with_provider(provider.into()) - .with_protocol_versions(&versions); - let base = match result { - Ok(new) => new, - Err(_) => return rustls_result::InvalidParameter, - }; - let builder = ServerConfigBuilder { - base, + provider: Some(provider), + versions, verifier: WebPkiClientVerifier::no_client_auth(), cert_resolver: None, session_storage: None, @@ -287,18 +256,38 @@ impl rustls_server_config_builder { } /// Turn a *rustls_server_config_builder (mutable) into a const *rustls_server_config - /// (read-only). + /// (read-only). The constructed `rustls_server_config` will be written to the `config_out` + /// pointer when this function returns `rustls_result::Ok`. + /// + /// This function may return an error if no process default crypto provider has been set + /// and the builder was constructed using `rustls_server_config_builder_new`, or if no + /// certificate resolver was set. #[no_mangle] pub extern "C" fn rustls_server_config_builder_build( builder: *mut rustls_server_config_builder, - ) -> *const rustls_server_config { + config_out: *mut *const rustls_server_config, + ) -> rustls_result { ffi_panic_boundary! { let builder = try_box_from_ptr!(builder); - let base = builder.base.with_client_cert_verifier(builder.verifier); + let config_out = try_ref_from_ptr_ptr!(config_out); + + let provider = match builder.provider { + Some(provider) => provider, + None => return rustls_result::NoDefaultCryptoProvider, + }; + + let base = match ServerConfig::builder_with_provider(provider) + .with_protocol_versions(&builder.versions) + { + Ok(base) => base, + Err(err) => return map_error(err), + } + .with_client_cert_verifier(builder.verifier); + let mut config = if let Some(r) = builder.cert_resolver { base.with_cert_resolver(r) } else { - return null(); + return rustls_result::General; }; if let Some(ss) = builder.session_storage { config.session_storage = ss; @@ -307,7 +296,9 @@ impl rustls_server_config_builder { if let Some(ignore_client_order) = builder.ignore_client_order { config.ignore_client_order = ignore_client_order; } - to_arc_const_ptr(config) + + set_arc_mut_ptr(config_out, config); + rustls_result::Ok } } } @@ -692,11 +683,13 @@ impl rustls_server_config_builder { #[cfg(test)] mod tests { + use std::ptr::null; use std::ptr::null_mut; use super::*; #[test] + #[cfg_attr(miri, ignore)] fn test_config_builder() { let builder = rustls_server_config_builder::rustls_server_config_builder_new(); let h1 = "http/1.1".as_bytes(); @@ -707,7 +700,34 @@ mod tests { alpn.as_ptr(), alpn.len(), ); - let config = rustls_server_config_builder::rustls_server_config_builder_build(builder); + + let cert_pem = include_str!("../testdata/localhost/cert.pem").as_bytes(); + let key_pem = include_str!("../testdata/localhost/key.pem").as_bytes(); + let mut certified_key = null(); + let result = rustls_certified_key::rustls_certified_key_build( + cert_pem.as_ptr(), + cert_pem.len(), + key_pem.as_ptr(), + key_pem.len(), + &mut certified_key, + ); + if !matches!(result, rustls_result::Ok) { + panic!( + "expected RUSTLS_RESULT_OK from rustls_certified_key_build, got {:?}", + result + ); + } + rustls_server_config_builder::rustls_server_config_builder_set_certified_keys( + builder, + &certified_key, + 1, + ); + + let mut config = null(); + let result = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); { let config2 = try_ref_from_ptr!(config); assert_eq!(config2.alpn_protocols, vec![h1, h2]); @@ -719,11 +739,12 @@ mod tests { #[test] fn test_server_config_builder_new_empty() { let builder = rustls_server_config_builder::rustls_server_config_builder_new(); - // Building a config with no certificate and key configured results in null. - assert_eq!( - rustls_server_config_builder::rustls_server_config_builder_build(builder), - null() - ); + // Building a config with no certificate and key configured results in an error. + let mut config = null(); + let result = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::General); + assert!(config.is_null()); } #[test] @@ -752,8 +773,11 @@ mod tests { 1, ); - let config = rustls_server_config_builder::rustls_server_config_builder_build(builder); - assert_ne!(config, null()); + let mut config = null(); + let result = + rustls_server_config_builder::rustls_server_config_builder_build(builder, &mut config); + assert_eq!(result, rustls_result::Ok); + assert!(!config.is_null()); let mut conn = null_mut(); let result = rustls_server_config::rustls_server_connection_new(config, &mut conn); diff --git a/tests/server.c b/tests/server.c index b1a96c9c..723f25f7 100644 --- a/tests/server.c +++ b/tests/server.c @@ -336,7 +336,12 @@ main(int argc, const char **argv) client_cert_verifier); } - server_config = rustls_server_config_builder_build(config_builder); + rustls_result result = + rustls_server_config_builder_build(config_builder, &server_config); + if(result != RUSTLS_RESULT_OK) { + print_error("building server config", result); + goto cleanup; + } #ifdef _WIN32 WSADATA wsa; From 98f1d51230e26c6c4e8aca43b2d524afafe7d089 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 15:35:20 -0400 Subject: [PATCH 09/25] client: convert client config/builder to provider * `rustls_client_config_builder_new()` now uses the process default crypto provider instead of being hardcoded to `*ring*`. We defer constructing the `ClientConfig` to avoid a panic in the event the process default isn't constructed. This will be surfaced as an error at build time instead. Like the upstream `ClientConfig::builder()` if no process default provider has been set when `rustls_client_config_builder_new()` is called we try to set one based on an unambiguous default implied by crate features. * `rustls_client_config_builder_new_custom()` now takes a `rustls_crypto_provider` as an argument in place of the list of custom ciphersuites. The ciphersuites can be customized when the provider is constructed. * `rustls_client_config_builder_build()` now uses an out param for the `ClientConfig` so we can return a suitable error if there is no crypto provider (e.g. because `rustls_client_config_builder_new()` was used but the process default wasn't set and couldn't be inferred from crate features). * The `client.c` test binary is updated to account for the breaking change in the client config builder out-param. --- src/client.rs | 99 ++++++++++++++++++++------------------------------- src/rustls.h | 25 ++++++------- 2 files changed, 49 insertions(+), 75 deletions(-) diff --git a/src/client.rs b/src/client.rs index 48cbdd28..7c873786 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,24 +7,24 @@ use libc::{c_char, size_t}; use pki_types::{CertificateDer, UnixTime}; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; use rustls::client::ResolvesClientCert; -use rustls::crypto::ring::ALL_CIPHER_SUITES; +use rustls::crypto::CryptoProvider; use rustls::{ sign::CertifiedKey, ClientConfig, ClientConnection, DigitallySignedStruct, Error, - ProtocolVersion, SignatureScheme, WantsVerifier, + ProtocolVersion, SignatureScheme, SupportedProtocolVersion, }; -use crate::cipher::{ - rustls_certified_key, rustls_server_cert_verifier, rustls_supported_ciphersuite, -}; +use crate::cipher::{rustls_certified_key, rustls_server_cert_verifier}; use crate::connection::{rustls_connection, Connection}; +use crate::crypto_provider::rustls_crypto_provider; use crate::error::rustls_result::{InvalidParameter, NullParameter}; -use crate::error::{self, rustls_result}; +use crate::error::{self, map_error, rustls_result}; use crate::rslice::NulByte; use crate::rslice::{rustls_slice_bytes, rustls_slice_slice_bytes, rustls_str}; use crate::{ - arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, set_arc_mut_ptr, - set_boxed_mut_ptr, to_boxed_mut_ptr, try_box_from_ptr, try_clone_arc, try_mut_from_ptr, - try_mut_from_ptr_ptr, try_ref_from_ptr, try_ref_from_ptr_ptr, try_slice, userdata_get, + arc_castable, box_castable, crypto_provider, ffi_panic_boundary, free_arc, free_box, + set_arc_mut_ptr, set_boxed_mut_ptr, to_boxed_mut_ptr, try_box_from_ptr, try_clone_arc, + try_mut_from_ptr, try_mut_from_ptr_ptr, try_ref_from_ptr, try_ref_from_ptr_ptr, try_slice, + userdata_get, }; box_castable! { @@ -44,7 +44,8 @@ box_castable! { } pub(crate) struct ClientConfigBuilder { - base: rustls::ConfigBuilder, + provider: Option>, + versions: Vec<&'static SupportedProtocolVersion>, verifier: Option>, alpn_protocols: Vec>, enable_sni: bool, @@ -60,7 +61,7 @@ arc_castable! { } impl rustls_client_config_builder { - /// Create a rustls_client_config_builder. + /// Create a rustls_client_config_builder using the process default crypto provider. /// /// Caller owns the memory and must eventually call `rustls_client_config_builder_build`, /// then free the resulting `rustls_client_config`. @@ -68,24 +69,17 @@ impl rustls_client_config_builder { /// Alternatively, if an error occurs or, you don't wish to build a config, /// call `rustls_client_config_builder_free` to free the builder directly. /// - /// This uses rustls safe default values for the cipher suites, key exchange - /// groups and protocol versions. + /// This uses the process default provider's values for the cipher suites and key + /// exchange groups, as well as safe defaults for protocol versions. /// /// This starts out with no trusted roots. Caller must add roots with - /// rustls_client_config_builder_load_roots_from_file or provide a custom - /// verifier. + /// rustls_client_config_builder_load_roots_from_file or provide a custom verifier. #[no_mangle] pub extern "C" fn rustls_client_config_builder_new() -> *mut rustls_client_config_builder { ffi_panic_boundary! { - // Unwrap safety: *ring* default provider always has ciphersuites compatible with the - // default protocol versions. - let base = ClientConfig::builder_with_provider( - rustls::crypto::ring::default_provider().into(), - ) - .with_safe_default_protocol_versions() - .unwrap(); let builder = ClientConfigBuilder { - base, + provider: crypto_provider::get_default_or_install_from_crate_features(), + versions: rustls::DEFAULT_VERSIONS.to_vec(), verifier: None, cert_resolver: None, alpn_protocols: vec![], @@ -95,7 +89,7 @@ impl rustls_client_config_builder { } } - /// Create a rustls_client_config_builder. + /// Create a rustls_client_config_builder using the specified crypto provider. /// /// Caller owns the memory and must eventually call `rustls_client_config_builder_build`, /// then free the resulting `rustls_client_config`. @@ -103,13 +97,7 @@ impl rustls_client_config_builder { /// Alternatively, if an error occurs or, you don't wish to build a config, /// call `rustls_client_config_builder_free` to free the builder directly. /// - /// Specify cipher suites in preference order; the `cipher_suites` parameter - /// must point to an array containing `cipher_suites_len` pointers to - /// `rustls_supported_ciphersuite` previously obtained from - /// `rustls_all_ciphersuites_get_entry()`, or to a provided array, - /// RUSTLS_DEFAULT_CIPHER_SUITES or RUSTLS_ALL_CIPHER_SUITES. - /// - /// Set the TLS protocol versions to use when negotiating a TLS session. + /// `tls_version` sets the TLS protocol versions to use when negotiating a TLS session. /// `tls_version` is the version of the protocol, as defined in rfc8446, /// ch. 4.2.1 and end of ch. 5.1. Some values are defined in /// `rustls_tls_version` for convenience, and the arrays @@ -118,28 +106,18 @@ impl rustls_client_config_builder { /// `tls_versions` will only be used during the call and the application retains /// ownership. `tls_versions_len` is the number of consecutive `uint16_t` /// pointed to by `tls_versions`. + /// + /// Ciphersuites are configured separately via the crypto provider. See + /// `rustls_crypto_provider_builder_set_cipher_suites` for more information. #[no_mangle] pub extern "C" fn rustls_client_config_builder_new_custom( - cipher_suites: *const *const rustls_supported_ciphersuite, - cipher_suites_len: size_t, + provider: *const rustls_crypto_provider, tls_versions: *const u16, tls_versions_len: size_t, builder_out: *mut *mut rustls_client_config_builder, ) -> rustls_result { ffi_panic_boundary! { - if builder_out.is_null() { - return NullParameter; - } - let cipher_suites = try_slice!(cipher_suites, cipher_suites_len); - let mut cs_vec = Vec::new(); - for &cs in cipher_suites.iter() { - let cs = try_ref_from_ptr!(cs); - match ALL_CIPHER_SUITES.iter().find(|&acs| cs.eq(acs)) { - Some(scs) => cs_vec.push(*scs), - None => return InvalidParameter, - } - } - + let provider = try_clone_arc!(provider); let tls_versions = try_slice!(tls_versions, tls_versions_len); let mut versions = vec![]; for version_number in tls_versions { @@ -150,21 +128,11 @@ impl rustls_client_config_builder { versions.push(&rustls::version::TLS13); } } - let builder_out = try_mut_from_ptr_ptr!(builder_out); - let provider = rustls::crypto::CryptoProvider { - cipher_suites: cs_vec, - ..rustls::crypto::ring::default_provider() - }; - let result = ClientConfig::builder_with_provider(provider.into()) - .with_protocol_versions(&versions); - let base = match result { - Ok(new) => new, - Err(_) => return InvalidParameter, - }; let config_builder = ClientConfigBuilder { - base, + provider: Some(provider), + versions, verifier: None, cert_resolver: None, alpn_protocols: vec![], @@ -484,13 +452,24 @@ impl rustls_client_config_builder { let builder = try_box_from_ptr!(builder); let config_out = try_ref_from_ptr_ptr!(config_out); + let provider = match builder.provider { + Some(provider) => provider, + None => return rustls_result::NoDefaultCryptoProvider, + }; + let verifier = match builder.verifier { Some(v) => v, None => return rustls_result::NoServerCertVerifier, }; - let config = builder - .base + let config = match ClientConfig::builder_with_provider(provider) + .with_protocol_versions(&builder.versions) + { + Ok(c) => c, + Err(err) => return map_error(err), + }; + + let config = config .dangerous() .with_custom_certificate_verifier(verifier); let mut config = match builder.cert_resolver { diff --git a/src/rustls.h b/src/rustls.h index 29d4420f..aee84949 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -1419,7 +1419,7 @@ struct rustls_server_cert_verifier *rustls_platform_server_cert_verifier(void); void rustls_server_cert_verifier_free(struct rustls_server_cert_verifier *verifier); /** - * Create a rustls_client_config_builder. + * Create a rustls_client_config_builder using the process default crypto provider. * * Caller owns the memory and must eventually call `rustls_client_config_builder_build`, * then free the resulting `rustls_client_config`. @@ -1427,17 +1427,16 @@ void rustls_server_cert_verifier_free(struct rustls_server_cert_verifier *verifi * Alternatively, if an error occurs or, you don't wish to build a config, * call `rustls_client_config_builder_free` to free the builder directly. * - * This uses rustls safe default values for the cipher suites, key exchange - * groups and protocol versions. + * This uses the process default provider's values for the cipher suites and key + * exchange groups, as well as safe defaults for protocol versions. * * This starts out with no trusted roots. Caller must add roots with - * rustls_client_config_builder_load_roots_from_file or provide a custom - * verifier. + * rustls_client_config_builder_load_roots_from_file or provide a custom verifier. */ struct rustls_client_config_builder *rustls_client_config_builder_new(void); /** - * Create a rustls_client_config_builder. + * Create a rustls_client_config_builder using the specified crypto provider. * * Caller owns the memory and must eventually call `rustls_client_config_builder_build`, * then free the resulting `rustls_client_config`. @@ -1445,13 +1444,7 @@ struct rustls_client_config_builder *rustls_client_config_builder_new(void); * Alternatively, if an error occurs or, you don't wish to build a config, * call `rustls_client_config_builder_free` to free the builder directly. * - * Specify cipher suites in preference order; the `cipher_suites` parameter - * must point to an array containing `cipher_suites_len` pointers to - * `rustls_supported_ciphersuite` previously obtained from - * `rustls_all_ciphersuites_get_entry()`, or to a provided array, - * RUSTLS_DEFAULT_CIPHER_SUITES or RUSTLS_ALL_CIPHER_SUITES. - * - * Set the TLS protocol versions to use when negotiating a TLS session. + * `tls_version` sets the TLS protocol versions to use when negotiating a TLS session. * `tls_version` is the version of the protocol, as defined in rfc8446, * ch. 4.2.1 and end of ch. 5.1. Some values are defined in * `rustls_tls_version` for convenience, and the arrays @@ -1460,9 +1453,11 @@ struct rustls_client_config_builder *rustls_client_config_builder_new(void); * `tls_versions` will only be used during the call and the application retains * ownership. `tls_versions_len` is the number of consecutive `uint16_t` * pointed to by `tls_versions`. + * + * Ciphersuites are configured separately via the crypto provider. See + * `rustls_crypto_provider_builder_set_cipher_suites` for more information. */ -rustls_result rustls_client_config_builder_new_custom(const struct rustls_supported_ciphersuite *const *cipher_suites, - size_t cipher_suites_len, +rustls_result rustls_client_config_builder_new_custom(const struct rustls_crypto_provider *provider, const uint16_t *tls_versions, size_t tls_versions_len, struct rustls_client_config_builder **builder_out); From aec3d3a43318cd45624ec122eed58512b4797880 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 16:15:46 -0400 Subject: [PATCH 10/25] cipher: remove hardcoded ring ciphersuites The provider model replaces these. --- src/cipher.rs | 159 ++++---------------------------------------------- src/rustls.h | 58 ------------------ 2 files changed, 12 insertions(+), 205 deletions(-) diff --git a/src/cipher.rs b/src/cipher.rs index 725f0c1d..878d7a29 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -10,7 +10,6 @@ use std::sync::Arc; use pki_types::{CertificateDer, CertificateRevocationListDer}; use rustls::client::danger::ServerCertVerifier; use rustls::client::WebPkiServerVerifier; -use rustls::crypto::ring::{ALL_CIPHER_SUITES, DEFAULT_CIPHER_SUITES}; use rustls::server::danger::ClientCertVerifier; use rustls::server::WebPkiClientVerifier; use rustls::sign::CertifiedKey; @@ -99,112 +98,6 @@ pub extern "C" fn rustls_supported_ciphersuite_get_name( } } -/// Return the length of rustls' list of supported cipher suites. -#[no_mangle] -pub extern "C" fn rustls_all_ciphersuites_len() -> usize { - ALL_CIPHER_SUITES.len() -} - -/// Get a pointer to a member of rustls' list of supported cipher suites. -/// -/// This will return non-NULL for i < rustls_all_ciphersuites_len(). -/// -/// The returned pointer is valid for the lifetime of the program and may -/// be used directly when building a ClientConfig or ServerConfig. -#[no_mangle] -pub extern "C" fn rustls_all_ciphersuites_get_entry( - i: size_t, -) -> *const rustls_supported_ciphersuite { - match ALL_CIPHER_SUITES.get(i) { - Some(cs) => cs as *const SupportedCipherSuite as *const _, - None => null(), - } -} - -/// Return the length of rustls' list of default cipher suites. -#[no_mangle] -pub extern "C" fn rustls_default_ciphersuites_len() -> usize { - DEFAULT_CIPHER_SUITES.len() -} - -/// Get a pointer to a member of rustls' list of supported cipher suites. -/// -/// This will return non-NULL for i < rustls_default_ciphersuites_len(). -/// -/// The returned pointer is valid for the lifetime of the program and may -/// be used directly when building a ClientConfig or ServerConfig. -#[no_mangle] -pub extern "C" fn rustls_default_ciphersuites_get_entry( - i: size_t, -) -> *const rustls_supported_ciphersuite { - match DEFAULT_CIPHER_SUITES.get(i) { - Some(cs) => cs as *const SupportedCipherSuite as *const _, - None => null(), - } -} - -/// Rustls' list of supported cipher suites. -/// -/// This is an array of pointers, and its length is given by -/// `RUSTLS_ALL_CIPHER_SUITES_LEN`. The pointers will always be valid. -/// The contents and order of this array may change between releases. -#[no_mangle] -pub static mut RUSTLS_ALL_CIPHER_SUITES: [*const rustls_supported_ciphersuite; 9] = [ - &rustls::crypto::ring::cipher_suite::TLS13_AES_256_GCM_SHA384 as *const SupportedCipherSuite - as *const _, - &rustls::crypto::ring::cipher_suite::TLS13_AES_128_GCM_SHA256 as *const SupportedCipherSuite - as *const _, - &rustls::crypto::ring::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, -]; - -/// The length of the array `RUSTLS_ALL_CIPHER_SUITES`. -#[no_mangle] -pub static RUSTLS_ALL_CIPHER_SUITES_LEN: usize = unsafe { RUSTLS_ALL_CIPHER_SUITES.len() }; - -/// Rustls' list of default cipher suites. -/// -/// This is an array of pointers, and its length is given by -/// `RUSTLS_DEFAULT_CIPHER_SUITES_LEN`. The pointers will always be valid. -/// The contents and order of this array may change between releases. -#[no_mangle] -pub static mut RUSTLS_DEFAULT_CIPHER_SUITES: [*const rustls_supported_ciphersuite; 9] = [ - &rustls::crypto::ring::cipher_suite::TLS13_AES_256_GCM_SHA384 as *const SupportedCipherSuite - as *const _, - &rustls::crypto::ring::cipher_suite::TLS13_AES_128_GCM_SHA256 as *const SupportedCipherSuite - as *const _, - &rustls::crypto::ring::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - as *const SupportedCipherSuite as *const _, - &rustls::crypto::ring::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - as *const SupportedCipherSuite as *const _, -]; - -/// The length of the array `RUSTLS_DEFAULT_CIPHER_SUITES`. -#[no_mangle] -pub static RUSTLS_DEFAULT_CIPHER_SUITES_LEN: usize = unsafe { RUSTLS_DEFAULT_CIPHER_SUITES.len() }; - arc_castable! { /// The complete chain of certificates to send during a TLS handshake, /// plus a private key that matches the end-entity (leaf) certificate. @@ -1116,49 +1009,21 @@ impl rustls_server_cert_verifier { #[cfg(test)] mod tests { use super::*; - use std::str; - - #[test] - fn all_cipher_suites_arrays() { - assert_eq!(RUSTLS_ALL_CIPHER_SUITES_LEN, ALL_CIPHER_SUITES.len()); - for (original, ffi) in ALL_CIPHER_SUITES - .iter() - .zip(unsafe { RUSTLS_ALL_CIPHER_SUITES }.iter().copied()) - { - let ffi_cipher_suite = try_ref_from_ptr!(ffi); - assert_eq!(original, ffi_cipher_suite); - } - } - #[test] - fn default_cipher_suites_arrays() { - assert_eq!( - RUSTLS_DEFAULT_CIPHER_SUITES_LEN, - DEFAULT_CIPHER_SUITES.len() - ); - for (original, ffi) in DEFAULT_CIPHER_SUITES - .iter() - .zip(unsafe { RUSTLS_DEFAULT_CIPHER_SUITES }.iter().copied()) - { - let ffi_cipher_suite = try_ref_from_ptr!(ffi); - assert_eq!(original, ffi_cipher_suite); - } - } + use crate::crypto_provider::{ + rustls_default_crypto_provider_ciphersuites_get, + rustls_default_crypto_provider_ciphersuites_len, + }; #[test] - fn ciphersuite_get_name() { - let suite = rustls_all_ciphersuites_get_entry(0); - let s = rustls_supported_ciphersuite_get_name(suite); - let want = "TLS13_AES_256_GCM_SHA384"; - unsafe { - let got = str::from_utf8(slice::from_raw_parts(s.data as *const u8, s.len)).unwrap(); - assert_eq!(want, got) + fn default_cipher_suites() { + let num_suites = rustls_default_crypto_provider_ciphersuites_len(); + assert!(num_suites > 2); + for i in 0..num_suites { + let suite = rustls_default_crypto_provider_ciphersuites_get(i); + let name = rustls_supported_ciphersuite_get_name(suite); + let name = unsafe { name.to_str() }; + println!("{}: {}", i, name); } } - - #[test] - fn test_all_ciphersuites_len() { - let len = rustls_all_ciphersuites_len(); - assert!(len > 2); - } } diff --git a/src/rustls.h b/src/rustls.h index aee84949..bb15ecc7 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -669,34 +669,6 @@ typedef uint32_t (*rustls_session_store_put_callback)(rustls_session_store_userd const struct rustls_slice_bytes *key, const struct rustls_slice_bytes *val); -/** - * Rustls' list of supported cipher suites. - * - * This is an array of pointers, and its length is given by - * `RUSTLS_ALL_CIPHER_SUITES_LEN`. The pointers will always be valid. - * The contents and order of this array may change between releases. - */ -extern const struct rustls_supported_ciphersuite *RUSTLS_ALL_CIPHER_SUITES[9]; - -/** - * The length of the array `RUSTLS_ALL_CIPHER_SUITES`. - */ -extern const size_t RUSTLS_ALL_CIPHER_SUITES_LEN; - -/** - * Rustls' list of default cipher suites. - * - * This is an array of pointers, and its length is given by - * `RUSTLS_DEFAULT_CIPHER_SUITES_LEN`. The pointers will always be valid. - * The contents and order of this array may change between releases. - */ -extern const struct rustls_supported_ciphersuite *RUSTLS_DEFAULT_CIPHER_SUITES[9]; - -/** - * The length of the array `RUSTLS_DEFAULT_CIPHER_SUITES`. - */ -extern const size_t RUSTLS_DEFAULT_CIPHER_SUITES_LEN; - /** * Rustls' list of supported protocol versions. The length of the array is * given by `RUSTLS_ALL_VERSIONS_LEN`. @@ -1040,36 +1012,6 @@ uint16_t rustls_supported_ciphersuite_get_suite(const struct rustls_supported_ci */ struct rustls_str rustls_supported_ciphersuite_get_name(const struct rustls_supported_ciphersuite *supported_ciphersuite); -/** - * Return the length of rustls' list of supported cipher suites. - */ -size_t rustls_all_ciphersuites_len(void); - -/** - * Get a pointer to a member of rustls' list of supported cipher suites. - * - * This will return non-NULL for i < rustls_all_ciphersuites_len(). - * - * The returned pointer is valid for the lifetime of the program and may - * be used directly when building a ClientConfig or ServerConfig. - */ -const struct rustls_supported_ciphersuite *rustls_all_ciphersuites_get_entry(size_t i); - -/** - * Return the length of rustls' list of default cipher suites. - */ -size_t rustls_default_ciphersuites_len(void); - -/** - * Get a pointer to a member of rustls' list of supported cipher suites. - * - * This will return non-NULL for i < rustls_default_ciphersuites_len(). - * - * The returned pointer is valid for the lifetime of the program and may - * be used directly when building a ClientConfig or ServerConfig. - */ -const struct rustls_supported_ciphersuite *rustls_default_ciphersuites_get_entry(size_t i); - /** * Build a `rustls_certified_key` from a certificate chain and a private key. * From 3e2fbec2ccd1391e25ce2f37ef2c8123ece52e2e Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 15:44:08 -0400 Subject: [PATCH 11/25] client: convert Verifier to provider The `Verifier` type previously had an unconditional dependency on the `*ring*` crypto provider. This commit converts it to use the crypto provider set up by the client config builder as appropriate. --- src/client.rs | 25 +++++++++++++++++-------- src/rustls.h | 5 ++++- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/client.rs b/src/client.rs index 7c873786..9ed00aca 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,7 +7,7 @@ use libc::{c_char, size_t}; use pki_types::{CertificateDer, UnixTime}; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; use rustls::client::ResolvesClientCert; -use rustls::crypto::CryptoProvider; +use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; use rustls::{ sign::CertifiedKey, ClientConfig, ClientConnection, DigitallySignedStruct, Error, ProtocolVersion, SignatureScheme, SupportedProtocolVersion, @@ -187,6 +187,7 @@ type VerifyCallback = unsafe extern "C" fn( // An implementation of rustls::ServerCertVerifier based on a C callback. struct Verifier { + provider: Arc, callback: VerifyCallback, } @@ -242,11 +243,11 @@ impl ServerCertVerifier for Verifier { cert: &CertificateDer, dss: &DigitallySignedStruct, ) -> Result { - rustls::crypto::verify_tls12_signature( + verify_tls12_signature( message, cert, dss, - &rustls::crypto::ring::default_provider().signature_verification_algorithms, + &self.provider.signature_verification_algorithms, ) } @@ -256,16 +257,16 @@ impl ServerCertVerifier for Verifier { cert: &CertificateDer, dss: &DigitallySignedStruct, ) -> Result { - rustls::crypto::verify_tls13_signature( + verify_tls13_signature( message, cert, dss, - &rustls::crypto::ring::default_provider().signature_verification_algorithms, + &self.provider.signature_verification_algorithms, ) } fn supported_verify_schemes(&self) -> Vec { - rustls::crypto::ring::default_provider() + self.provider .signature_verification_algorithms .supported_schemes() } @@ -278,7 +279,10 @@ impl Debug for Verifier { } impl rustls_client_config_builder { - /// Set a custom server certificate verifier. + /// Set a custom server certificate verifier using the builder crypto provider. + /// Returns rustls_result::NoDefaultCryptoProvider if no process default crypto + /// provider has been set, and the builder was not constructed with an explicit + /// provider choice. /// /// The callback must not capture any of the pointers in its /// rustls_verify_server_cert_params. @@ -314,7 +318,12 @@ impl rustls_client_config_builder { None => return InvalidParameter, }; - config_builder.verifier = Some(Arc::new(Verifier { callback })); + let provider = match &config_builder.provider { + Some(provider) => provider.clone(), + None => return rustls_result::NoDefaultCryptoProvider, + }; + + config_builder.verifier = Some(Arc::new(Verifier { provider, callback })); rustls_result::Ok } } diff --git a/src/rustls.h b/src/rustls.h index bb15ecc7..a201b369 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -1405,7 +1405,10 @@ rustls_result rustls_client_config_builder_new_custom(const struct rustls_crypto struct rustls_client_config_builder **builder_out); /** - * Set a custom server certificate verifier. + * Set a custom server certificate verifier using the builder crypto provider. + * Returns rustls_result::NoDefaultCryptoProvider if no process default crypto + * provider has been set, and the builder was not constructed with an explicit + * provider choice. * * The callback must not capture any of the pointers in its * rustls_verify_server_cert_params. From 4c9b4317738adff76dde65f751281a8bacd5a7ef Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 16:07:16 -0400 Subject: [PATCH 12/25] crypto_provider: add signing key loading support This commit adds a new type, `rustls_signing_key`, that represents a `&dyn SigningKey` loaded by a `rustls_crypto_provider`. A new `rustls_crypto_provider_load_key` fn is added to create a `rustls_signing_key` from a pointer to a `rustls_crypto_provider`, and PEM content in-memory. Wiring this up will be done in a subsequent commit. --- src/crypto_provider.rs | 59 ++++++++++++++++++++++++++++++++++++++++++ src/rustls.h | 28 ++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/crypto_provider.rs b/src/crypto_provider.rs index d12a0502..3aa00fe3 100644 --- a/src/crypto_provider.rs +++ b/src/crypto_provider.rs @@ -1,12 +1,15 @@ use libc::size_t; +use std::io::Cursor; use std::slice; use std::sync::Arc; use rustls::crypto::ring; use rustls::crypto::CryptoProvider; +use rustls::sign::SigningKey; use rustls::SupportedCipherSuite; use crate::cipher::rustls_supported_ciphersuite; +use crate::error::map_error; use crate::{ arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, rustls_result, set_arc_mut_ptr, set_boxed_mut_ptr, to_boxed_mut_ptr, try_clone_arc, try_mut_from_ptr, @@ -283,6 +286,43 @@ pub extern "C" fn rustls_crypto_provider_ciphersuites_get( } } +/// Load a private key from the provided PEM content using the crypto provider. +/// +/// `private_key` must point to a buffer of `private_key_len` bytes, containing +/// a PEM-encoded private key. The exact formats supported will differ based on +/// the crypto provider in use. The default providers support PKCS#1, PKCS#8 or +/// SEC1 formats. +/// +/// When this function returns `rustls_result::Ok` a pointer to a `rustls_signing_key` +/// is written to `signing_key_out`. The caller owns the returned `rustls_signing_key` +/// and must free it with `rustls_signing_key_free`. +#[no_mangle] +pub extern "C" fn rustls_crypto_provider_load_key( + provider: *const rustls_crypto_provider, + private_key: *const u8, + private_key_len: size_t, + signing_key_out: *mut *mut rustls_signing_key, +) -> rustls_result { + ffi_panic_boundary! { + let provider = try_clone_arc!(provider); + let private_key_pem = try_slice!(private_key, private_key_len); + let signing_key_out = try_mut_from_ptr_ptr!(signing_key_out); + + let private_key_der = match rustls_pemfile::private_key(&mut Cursor::new(private_key_pem)) { + Ok(Some(p)) => p, + _ => return rustls_result::PrivateKeyParseError, + }; + + let private_key = match provider.key_provider.load_private_key(private_key_der) { + Ok(key) => key, + Err(e) => return map_error(e), + }; + + set_boxed_mut_ptr(signing_key_out, private_key); + rustls_result::Ok + } +} + /// Frees the `rustls_crypto_provider`. /// /// Calling with `NULL` is fine. @@ -333,6 +373,25 @@ pub extern "C" fn rustls_default_crypto_provider_ciphersuites_get( } } +box_castable! { + /// A signing key that can be used to construct a certified key. + // NOTE: we box cast an arc over the dyn trait per the pattern described + // in our docs[0] for dynamically sized types. + // [0]: + pub struct rustls_signing_key(Arc); +} + +impl rustls_signing_key { + /// Frees the `rustls_signing_key`. This is safe to call with a `NULL` argument, but + /// must not be called twice with the same value. + #[no_mangle] + pub extern "C" fn rustls_signing_key_free(signing_key: *mut rustls_signing_key) { + ffi_panic_boundary! { + free_box(signing_key); + } + } +} + pub(crate) fn get_default_or_install_from_crate_features() -> Option> { // If a process-wide default has already been installed, return it. if let Some(provider) = CryptoProvider::get_default() { diff --git a/src/rustls.h b/src/rustls.h index a201b369..9b21648b 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -292,6 +292,11 @@ typedef struct rustls_server_config rustls_server_config; */ typedef struct rustls_server_config_builder rustls_server_config_builder; +/** + * A signing key that can be used to construct a certified key. + */ +typedef struct rustls_signing_key rustls_signing_key; + /** * A read-only view of a slice of Rust byte slices. * @@ -1912,6 +1917,23 @@ size_t rustls_crypto_provider_ciphersuites_len(const struct rustls_crypto_provid const struct rustls_supported_ciphersuite *rustls_crypto_provider_ciphersuites_get(const struct rustls_crypto_provider *provider, size_t index); +/** + * Load a private key from the provided PEM content using the crypto provider. + * + * `private_key` must point to a buffer of `private_key_len` bytes, containing + * a PEM-encoded private key. The exact formats supported will differ based on + * the crypto provider in use. The default providers support PKCS#1, PKCS#8 or + * SEC1 formats. + * + * When this function returns `rustls_result::Ok` a pointer to a `rustls_signing_key` + * is written to `signing_key_out`. The caller owns the returned `rustls_signing_key` + * and must free it with `rustls_signing_key_free`. + */ +rustls_result rustls_crypto_provider_load_key(const struct rustls_crypto_provider *provider, + const uint8_t *private_key, + size_t private_key_len, + struct rustls_signing_key **signing_key_out); + /** * Frees the `rustls_crypto_provider`. * @@ -1941,6 +1963,12 @@ size_t rustls_default_crypto_provider_ciphersuites_len(void); */ const struct rustls_supported_ciphersuite *rustls_default_crypto_provider_ciphersuites_get(size_t index); +/** + * Frees the `rustls_signing_key`. This is safe to call with a `NULL` argument, but + * must not be called twice with the same value. + */ +void rustls_signing_key_free(struct rustls_signing_key *signing_key); + /** * After a rustls function returns an error, you may call * this to get a pointer to a buffer containing a detailed error From df61ee392ab82cd6a9457395a71efd7da288eea9 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Wed, 7 Aug 2024 15:30:56 -0400 Subject: [PATCH 13/25] cipher: use provider to load signing keys This breaks an unconditional dependency on `*ring*` for loading certified key private keys. The existing `rustls_certified_key_build()` fn is converted to use the process-default crypto provider for this purpose. Like other functions that use the implied default if we find no default has been set yet and a clear default is available based on crate features this function will install & use it. For more control over which crypto provider is used to load a private key a new `rustls_certified_key_build_with_signing_key()` fn is added that allows specifying a `rustls_crypto_provider` to use. --- src/cipher.rs | 151 +++++++++++++++++++++++++++++--------------------- src/rustls.h | 42 +++++++++++++- 2 files changed, 127 insertions(+), 66 deletions(-) diff --git a/src/cipher.rs b/src/cipher.rs index 878d7a29..5728fff9 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -14,16 +14,17 @@ use rustls::server::danger::ClientCertVerifier; use rustls::server::WebPkiClientVerifier; use rustls::sign::CertifiedKey; use rustls::{DistinguishedName, RootCertStore, SupportedCipherSuite}; -use rustls_pemfile::{certs, crls, pkcs8_private_keys, rsa_private_keys}; +use rustls_pemfile::{certs, crls}; use webpki::{RevocationCheckDepth, UnknownStatusPolicy}; -use crate::error::{self, rustls_result}; +use crate::crypto_provider::rustls_signing_key; +use crate::error::{self, map_error, rustls_result}; use crate::rslice::{rustls_slice_bytes, rustls_str}; use crate::{ - arc_castable, box_castable, ffi_panic_boundary, free_arc, free_box, ref_castable, - set_arc_mut_ptr, set_boxed_mut_ptr, to_arc_const_ptr, to_boxed_mut_ptr, try_clone_arc, - try_mut_from_ptr, try_mut_from_ptr_ptr, try_ref_from_ptr, try_ref_from_ptr_ptr, try_slice, - try_take, + arc_castable, box_castable, crypto_provider, ffi_panic_boundary, free_arc, free_box, + ref_castable, set_arc_mut_ptr, set_boxed_mut_ptr, to_arc_const_ptr, to_boxed_mut_ptr, + try_box_from_ptr, try_clone_arc, try_mut_from_ptr, try_mut_from_ptr_ptr, try_ref_from_ptr, + try_ref_from_ptr_ptr, try_slice, try_take, }; use rustls_result::{AlreadyUsed, NullParameter}; @@ -108,14 +109,17 @@ arc_castable! { } impl rustls_certified_key { - /// Build a `rustls_certified_key` from a certificate chain and a private key. + /// Build a `rustls_certified_key` from a certificate chain and a private key + /// and the default process-wide crypto provider. /// /// `cert_chain` must point to a buffer of `cert_chain_len` bytes, containing /// a series of PEM-encoded certificates, with the end-entity (leaf) /// certificate first. /// /// `private_key` must point to a buffer of `private_key_len` bytes, containing - /// a PEM-encoded private key in either PKCS#1 or PKCS#8 format. + /// a PEM-encoded private key in either PKCS#1, PKCS#8 or SEC#1 format when + /// using `aws-lc-rs` as the crypto provider. Supported formats may vary by + /// provider. /// /// On success, this writes a pointer to the newly created /// `rustls_certified_key` in `certified_key_out`. That pointer must later @@ -144,23 +148,85 @@ impl rustls_certified_key { certified_key_out: *mut *const rustls_certified_key, ) -> rustls_result { ffi_panic_boundary! { - let certified_key_out = unsafe { - match certified_key_out.as_mut() { - Some(c) => c, - None => return NullParameter, - } + let default_provider = + match crypto_provider::get_default_or_install_from_crate_features() { + Some(default_provider) => default_provider, + None => return rustls_result::NoDefaultCryptoProvider, + }; + let private_key_pem = try_slice!(private_key, private_key_len); + + let private_key_der = + match rustls_pemfile::private_key(&mut Cursor::new(private_key_pem)) { + Ok(Some(p)) => p, + _ => return rustls_result::PrivateKeyParseError, + }; + + let private_key = match default_provider + .key_provider + .load_private_key(private_key_der) + { + Ok(key) => key, + Err(e) => return map_error(e), }; - let certified_key = match rustls_certified_key::certified_key_build( + + Self::rustls_certified_key_build_with_signing_key( cert_chain, cert_chain_len, - private_key, - private_key_len, - ) { - Ok(key) => Box::new(key), - Err(rr) => return rr, + to_boxed_mut_ptr(private_key), + certified_key_out, + ) + } + } + + /// Build a `rustls_certified_key` from a certificate chain and a + /// `rustls_signing_key`. + /// + /// `cert_chain` must point to a buffer of `cert_chain_len` bytes, containing + /// a series of PEM-encoded certificates, with the end-entity (leaf) + /// certificate first. + /// + /// `signing_key` must point to a `rustls_signing_key` loaded using a + /// `rustls_crypto_provider` and `rustls_crypto_provider_load_key()`. + /// + /// On success, this writes a pointer to the newly created + /// `rustls_certified_key` in `certified_key_out`. That pointer must later + /// be freed with `rustls_certified_key_free` to avoid memory leaks. Note that + /// internally, this is an atomically reference-counted pointer, so even after + /// the original caller has called `rustls_certified_key_free`, other objects + /// may retain a pointer to the object. The memory will be freed when all + /// references are gone. + /// + /// This function does not take ownership of any of its input pointers. It + /// parses the pointed-to data and makes a copy of the result. You may + /// free the cert_chain and private_key pointers after calling it. + /// + /// Typically, you will build a `rustls_certified_key`, use it to create a + /// `rustls_server_config` (which increments the reference count), and then + /// immediately call `rustls_certified_key_free`. That leaves the + /// `rustls_server_config` in possession of the sole reference, so the + /// `rustls_certified_key`'s memory will automatically be released when + /// the `rustls_server_config` is freed. + #[no_mangle] + pub extern "C" fn rustls_certified_key_build_with_signing_key( + cert_chain: *const u8, + cert_chain_len: size_t, + signing_key: *mut rustls_signing_key, + certified_key_out: *mut *const rustls_certified_key, + ) -> rustls_result { + ffi_panic_boundary! { + let mut cert_chain = try_slice!(cert_chain, cert_chain_len); + let signing_key = try_box_from_ptr!(signing_key); + let certified_key_out = try_ref_from_ptr_ptr!(certified_key_out); + + let parsed_chain = match certs(&mut cert_chain).collect::, _>>() { + Ok(v) => v, + Err(_) => return rustls_result::CertificateParseError, }; - let certified_key = Arc::into_raw(Arc::new(*certified_key)) as *const _; - *certified_key_out = certified_key; + + set_arc_mut_ptr( + certified_key_out, + CertifiedKey::new(parsed_chain, *signing_key), + ); rustls_result::Ok } } @@ -234,49 +300,6 @@ impl rustls_certified_key { free_arc(key); } } - - fn certified_key_build( - cert_chain: *const u8, - cert_chain_len: size_t, - private_key: *const u8, - private_key_len: size_t, - ) -> Result { - let mut cert_chain = unsafe { - if cert_chain.is_null() { - return Err(NullParameter); - } - slice::from_raw_parts(cert_chain, cert_chain_len) - }; - let private_key_der = unsafe { - if private_key.is_null() { - return Err(NullParameter); - } - slice::from_raw_parts(private_key, private_key_len) - }; - let private_key = match pkcs8_private_keys(&mut Cursor::new(private_key_der)).next() { - Some(Ok(p)) => p.into(), - Some(Err(_)) => return Err(rustls_result::PrivateKeyParseError), - None => { - let rsa_private_key = - match rsa_private_keys(&mut Cursor::new(private_key_der)).next() { - Some(Ok(p)) => p.into(), - _ => return Err(rustls_result::PrivateKeyParseError), - }; - rsa_private_key - } - }; - let signing_key = match rustls::crypto::ring::sign::any_supported_type(&private_key) { - Ok(key) => key, - Err(_) => return Err(rustls_result::PrivateKeyParseError), - }; - let parsed_chain: Result, _> = certs(&mut cert_chain).collect(); - let parsed_chain = match parsed_chain { - Ok(v) => v, - Err(_) => return Err(rustls_result::CertificateParseError), - }; - - Ok(CertifiedKey::new(parsed_chain, signing_key)) - } } box_castable! { diff --git a/src/rustls.h b/src/rustls.h index 9b21648b..6d8f7041 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -1018,14 +1018,17 @@ uint16_t rustls_supported_ciphersuite_get_suite(const struct rustls_supported_ci struct rustls_str rustls_supported_ciphersuite_get_name(const struct rustls_supported_ciphersuite *supported_ciphersuite); /** - * Build a `rustls_certified_key` from a certificate chain and a private key. + * Build a `rustls_certified_key` from a certificate chain and a private key + * and the default process-wide crypto provider. * * `cert_chain` must point to a buffer of `cert_chain_len` bytes, containing * a series of PEM-encoded certificates, with the end-entity (leaf) * certificate first. * * `private_key` must point to a buffer of `private_key_len` bytes, containing - * a PEM-encoded private key in either PKCS#1 or PKCS#8 format. + * a PEM-encoded private key in either PKCS#1, PKCS#8 or SEC#1 format when + * using `aws-lc-rs` as the crypto provider. Supported formats may vary by + * provider. * * On success, this writes a pointer to the newly created * `rustls_certified_key` in `certified_key_out`. That pointer must later @@ -1052,6 +1055,41 @@ rustls_result rustls_certified_key_build(const uint8_t *cert_chain, size_t private_key_len, const struct rustls_certified_key **certified_key_out); +/** + * Build a `rustls_certified_key` from a certificate chain and a + * `rustls_signing_key`. + * + * `cert_chain` must point to a buffer of `cert_chain_len` bytes, containing + * a series of PEM-encoded certificates, with the end-entity (leaf) + * certificate first. + * + * `signing_key` must point to a `rustls_signing_key` loaded using a + * `rustls_crypto_provider` and `rustls_crypto_provider_load_key()`. + * + * On success, this writes a pointer to the newly created + * `rustls_certified_key` in `certified_key_out`. That pointer must later + * be freed with `rustls_certified_key_free` to avoid memory leaks. Note that + * internally, this is an atomically reference-counted pointer, so even after + * the original caller has called `rustls_certified_key_free`, other objects + * may retain a pointer to the object. The memory will be freed when all + * references are gone. + * + * This function does not take ownership of any of its input pointers. It + * parses the pointed-to data and makes a copy of the result. You may + * free the cert_chain and private_key pointers after calling it. + * + * Typically, you will build a `rustls_certified_key`, use it to create a + * `rustls_server_config` (which increments the reference count), and then + * immediately call `rustls_certified_key_free`. That leaves the + * `rustls_server_config` in possession of the sole reference, so the + * `rustls_certified_key`'s memory will automatically be released when + * the `rustls_server_config` is freed. + */ +rustls_result rustls_certified_key_build_with_signing_key(const uint8_t *cert_chain, + size_t cert_chain_len, + struct rustls_signing_key *signing_key, + const struct rustls_certified_key **certified_key_out); + /** * Return the i-th rustls_certificate in the rustls_certified_key. * From 3a9e95f3ddcb0c34bd8efc32dddb22548f0a2350 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 16:39:27 -0400 Subject: [PATCH 14/25] cipher: convert server/client webpki verifiers to provider This breaks an unconditional dep on `*ring*` for both verifiers. The client/server test binaries do not require any update in this case since they are using the APIs that assume a process-wide default crypto provider has been set. --- src/cipher.rs | 140 +++++++++++++++++++++++++++++++++++++++++--------- src/rustls.h | 61 +++++++++++++++++++++- 2 files changed, 174 insertions(+), 27 deletions(-) diff --git a/src/cipher.rs b/src/cipher.rs index 5728fff9..d68f4163 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -10,6 +10,7 @@ use std::sync::Arc; use pki_types::{CertificateDer, CertificateRevocationListDer}; use rustls::client::danger::ServerCertVerifier; use rustls::client::WebPkiServerVerifier; +use rustls::crypto::CryptoProvider; use rustls::server::danger::ClientCertVerifier; use rustls::server::WebPkiClientVerifier; use rustls::sign::CertifiedKey; @@ -17,7 +18,7 @@ use rustls::{DistinguishedName, RootCertStore, SupportedCipherSuite}; use rustls_pemfile::{certs, crls}; use webpki::{RevocationCheckDepth, UnknownStatusPolicy}; -use crate::crypto_provider::rustls_signing_key; +use crate::crypto_provider::{rustls_crypto_provider, rustls_signing_key}; use crate::error::{self, map_error, rustls_result}; use crate::rslice::{rustls_slice_bytes, rustls_str}; use crate::{ @@ -516,6 +517,7 @@ impl rustls_client_cert_verifier { } pub(crate) struct ClientCertVerifierBuilder { + provider: Option>, roots: Arc, root_hint_subjects: Vec, crls: Vec>, @@ -540,7 +542,8 @@ box_castable! { } impl rustls_web_pki_client_cert_verifier_builder { - /// Create a `rustls_web_pki_client_cert_verifier_builder`. + /// Create a `rustls_web_pki_client_cert_verifier_builder` using the process-wide default + /// cryptography provider. /// /// Caller owns the memory and may eventually call `rustls_web_pki_client_cert_verifier_builder_free` /// to free it, whether or not `rustls_web_pki_client_cert_verifier_builder_build` was called. @@ -569,15 +572,60 @@ impl rustls_web_pki_client_cert_verifier_builder { ) -> *mut rustls_web_pki_client_cert_verifier_builder { ffi_panic_boundary! { let store = try_clone_arc!(store); - let builder = ClientCertVerifierBuilder { + to_boxed_mut_ptr(Some(ClientCertVerifierBuilder { + provider: crypto_provider::get_default_or_install_from_crate_features(), root_hint_subjects: store.subjects(), roots: store, crls: Vec::default(), revocation_depth: RevocationCheckDepth::Chain, revocation_policy: UnknownStatusPolicy::Deny, allow_unauthenticated: false, - }; - to_boxed_mut_ptr(Some(builder)) + })) + } + } + + /// Create a `rustls_web_pki_client_cert_verifier_builder` using the specified + /// cryptography provider. + /// + /// Caller owns the memory and may eventually call + /// `rustls_web_pki_client_cert_verifier_builder_free` to free it, whether or + /// not `rustls_web_pki_client_cert_verifier_builder_build` was called. + /// + /// Without further modification the builder will produce a client certificate verifier that + /// will require a client present a client certificate that chains to one of the trust anchors + /// in the provided `rustls_root_cert_store`. The root cert store must not be empty. + /// + /// Revocation checking will not be performed unless + /// `rustls_web_pki_client_cert_verifier_builder_add_crl` is used to add certificate revocation + /// lists (CRLs) to the builder. If CRLs are added, revocation checking will be performed + /// for the entire certificate chain unless + /// `rustls_web_pki_client_cert_verifier_only_check_end_entity_revocation` is used. Unknown + /// revocation status for certificates considered for revocation status will be treated as + /// an error unless `rustls_web_pki_client_cert_verifier_allow_unknown_revocation_status` is + /// used. + /// + /// Unauthenticated clients will not be permitted unless + /// `rustls_web_pki_client_cert_verifier_builder_allow_unauthenticated` is used. + /// + /// This copies the contents of the `rustls_root_cert_store`. It does not take + /// ownership of the pointed-to data. + #[no_mangle] + pub extern "C" fn rustls_web_pki_client_cert_verifier_builder_new_with_provider( + provider: *const rustls_crypto_provider, + store: *const rustls_root_cert_store, + ) -> *mut rustls_web_pki_client_cert_verifier_builder { + ffi_panic_boundary! { + let provider = try_clone_arc!(provider); + let store = try_clone_arc!(store); + to_boxed_mut_ptr(Some(ClientCertVerifierBuilder { + provider: Some(provider), + root_hint_subjects: store.subjects(), + roots: store, + crls: Vec::default(), + revocation_depth: RevocationCheckDepth::Chain, + revocation_policy: UnknownStatusPolicy::Deny, + allow_unauthenticated: false, + })) } } @@ -734,18 +782,19 @@ impl rustls_web_pki_client_cert_verifier_builder { verifier_out: *mut *mut rustls_client_cert_verifier, ) -> rustls_result { ffi_panic_boundary! { - if verifier_out.is_null() { - return NullParameter; - } let client_verifier_builder = try_mut_from_ptr!(builder); let client_verifier_builder = try_take!(client_verifier_builder); let verifier_out = try_mut_from_ptr_ptr!(verifier_out); - let mut builder = WebPkiClientVerifier::builder_with_provider( - client_verifier_builder.roots, - rustls::crypto::ring::default_provider().into(), - ) - .with_crls(client_verifier_builder.crls); + let builder = match client_verifier_builder.provider { + Some(provider) => WebPkiClientVerifier::builder_with_provider( + client_verifier_builder.roots, + provider, + ), + None => WebPkiClientVerifier::builder(client_verifier_builder.roots), + }; + + let mut builder = builder.with_crls(client_verifier_builder.crls); match client_verifier_builder.revocation_depth { RevocationCheckDepth::EndEntity => { builder = builder.only_check_end_entity_revocation() @@ -804,6 +853,7 @@ box_castable! { } pub(crate) struct ServerCertVerifierBuilder { + provider: Option>, roots: Arc, crls: Vec>, revocation_depth: RevocationCheckDepth, @@ -811,7 +861,8 @@ pub(crate) struct ServerCertVerifierBuilder { } impl ServerCertVerifierBuilder { - /// Create a `rustls_web_pki_server_cert_verifier_builder`. + /// Create a `rustls_web_pki_server_cert_verifier_builder` using the process-wide default + /// crypto provider. Caller owns the memory and may free it with /// /// Caller owns the memory and may free it with `rustls_web_pki_server_cert_verifier_builder_free`, /// regardless of whether `rustls_web_pki_server_cert_verifier_builder_build` was called. @@ -837,13 +888,51 @@ impl ServerCertVerifierBuilder { ) -> *mut rustls_web_pki_server_cert_verifier_builder { ffi_panic_boundary! { let store = try_clone_arc!(store); - let builder = ServerCertVerifierBuilder { + to_boxed_mut_ptr(Some(ServerCertVerifierBuilder { + provider: crypto_provider::get_default_or_install_from_crate_features(), roots: store, crls: Vec::default(), revocation_depth: RevocationCheckDepth::Chain, revocation_policy: UnknownStatusPolicy::Deny, - }; - to_boxed_mut_ptr(Some(builder)) + })) + } + } + + /// Create a `rustls_web_pki_server_cert_verifier_builder` using the specified + /// crypto provider. Caller owns the memory and may free it with + /// `rustls_web_pki_server_cert_verifier_builder_free`, regardless of whether + /// `rustls_web_pki_server_cert_verifier_builder_build` was called. + /// + /// Without further modification the builder will produce a server certificate verifier that + /// will require a server present a certificate that chains to one of the trust anchors + /// in the provided `rustls_root_cert_store`. The root cert store must not be empty. + /// + /// Revocation checking will not be performed unless + /// `rustls_web_pki_server_cert_verifier_builder_add_crl` is used to add certificate revocation + /// lists (CRLs) to the builder. If CRLs are added, revocation checking will be performed + /// for the entire certificate chain unless + /// `rustls_web_pki_server_cert_verifier_only_check_end_entity_revocation` is used. Unknown + /// revocation status for certificates considered for revocation status will be treated as + /// an error unless `rustls_web_pki_server_cert_verifier_allow_unknown_revocation_status` is + /// used. + /// + /// This copies the contents of the `rustls_root_cert_store`. It does not take + /// ownership of the pointed-to data. + #[no_mangle] + pub extern "C" fn rustls_web_pki_server_cert_verifier_builder_new_with_provider( + provider: *const rustls_crypto_provider, + store: *const rustls_root_cert_store, + ) -> *mut rustls_web_pki_server_cert_verifier_builder { + ffi_panic_boundary! { + let provider = try_clone_arc!(provider); + let store = try_clone_arc!(store); + to_boxed_mut_ptr(Some(ServerCertVerifierBuilder { + provider: Some(provider), + roots: store, + crls: Vec::default(), + revocation_depth: RevocationCheckDepth::Chain, + revocation_policy: UnknownStatusPolicy::Deny, + })) } } @@ -938,18 +1027,19 @@ impl ServerCertVerifierBuilder { verifier_out: *mut *mut rustls_server_cert_verifier, ) -> rustls_result { ffi_panic_boundary! { - if verifier_out.is_null() { - return NullParameter; - } let server_verifier_builder = try_mut_from_ptr!(builder); let server_verifier_builder = try_take!(server_verifier_builder); let verifier_out = try_mut_from_ptr_ptr!(verifier_out); - let mut builder = WebPkiServerVerifier::builder_with_provider( - server_verifier_builder.roots, - rustls::crypto::ring::default_provider().into(), - ) - .with_crls(server_verifier_builder.crls); + let builder = match server_verifier_builder.provider { + Some(provider) => WebPkiServerVerifier::builder_with_provider( + server_verifier_builder.roots, + provider, + ), + None => WebPkiServerVerifier::builder(server_verifier_builder.roots), + }; + + let mut builder = builder.with_crls(server_verifier_builder.crls); match server_verifier_builder.revocation_depth { RevocationCheckDepth::EndEntity => { builder = builder.only_check_end_entity_revocation() diff --git a/src/rustls.h b/src/rustls.h index 6d8f7041..5021877f 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -1205,7 +1205,8 @@ void rustls_root_cert_store_free(const struct rustls_root_cert_store *store); void rustls_client_cert_verifier_free(struct rustls_client_cert_verifier *verifier); /** - * Create a `rustls_web_pki_client_cert_verifier_builder`. + * Create a `rustls_web_pki_client_cert_verifier_builder` using the process-wide default + * cryptography provider. * * Caller owns the memory and may eventually call `rustls_web_pki_client_cert_verifier_builder_free` * to free it, whether or not `rustls_web_pki_client_cert_verifier_builder_build` was called. @@ -1231,6 +1232,36 @@ void rustls_client_cert_verifier_free(struct rustls_client_cert_verifier *verifi */ struct rustls_web_pki_client_cert_verifier_builder *rustls_web_pki_client_cert_verifier_builder_new(const struct rustls_root_cert_store *store); +/** + * Create a `rustls_web_pki_client_cert_verifier_builder` using the specified + * cryptography provider. + * + * Caller owns the memory and may eventually call + * `rustls_web_pki_client_cert_verifier_builder_free` to free it, whether or + * not `rustls_web_pki_client_cert_verifier_builder_build` was called. + * + * Without further modification the builder will produce a client certificate verifier that + * will require a client present a client certificate that chains to one of the trust anchors + * in the provided `rustls_root_cert_store`. The root cert store must not be empty. + * + * Revocation checking will not be performed unless + * `rustls_web_pki_client_cert_verifier_builder_add_crl` is used to add certificate revocation + * lists (CRLs) to the builder. If CRLs are added, revocation checking will be performed + * for the entire certificate chain unless + * `rustls_web_pki_client_cert_verifier_only_check_end_entity_revocation` is used. Unknown + * revocation status for certificates considered for revocation status will be treated as + * an error unless `rustls_web_pki_client_cert_verifier_allow_unknown_revocation_status` is + * used. + * + * Unauthenticated clients will not be permitted unless + * `rustls_web_pki_client_cert_verifier_builder_allow_unauthenticated` is used. + * + * This copies the contents of the `rustls_root_cert_store`. It does not take + * ownership of the pointed-to data. + */ +struct rustls_web_pki_client_cert_verifier_builder *rustls_web_pki_client_cert_verifier_builder_new_with_provider(const struct rustls_crypto_provider *provider, + const struct rustls_root_cert_store *store); + /** * Add one or more certificate revocation lists (CRLs) to the client certificate verifier * builder by reading the CRL content from the provided buffer of PEM encoded content. @@ -1310,7 +1341,8 @@ rustls_result rustls_web_pki_client_cert_verifier_builder_build(struct rustls_we void rustls_web_pki_client_cert_verifier_builder_free(struct rustls_web_pki_client_cert_verifier_builder *builder); /** - * Create a `rustls_web_pki_server_cert_verifier_builder`. + * Create a `rustls_web_pki_server_cert_verifier_builder` using the process-wide default + * crypto provider. Caller owns the memory and may free it with * * Caller owns the memory and may free it with `rustls_web_pki_server_cert_verifier_builder_free`, * regardless of whether `rustls_web_pki_server_cert_verifier_builder_build` was called. @@ -1333,6 +1365,31 @@ void rustls_web_pki_client_cert_verifier_builder_free(struct rustls_web_pki_clie */ struct rustls_web_pki_server_cert_verifier_builder *rustls_web_pki_server_cert_verifier_builder_new(const struct rustls_root_cert_store *store); +/** + * Create a `rustls_web_pki_server_cert_verifier_builder` using the specified + * crypto provider. Caller owns the memory and may free it with + * `rustls_web_pki_server_cert_verifier_builder_free`, regardless of whether + * `rustls_web_pki_server_cert_verifier_builder_build` was called. + * + * Without further modification the builder will produce a server certificate verifier that + * will require a server present a certificate that chains to one of the trust anchors + * in the provided `rustls_root_cert_store`. The root cert store must not be empty. + * + * Revocation checking will not be performed unless + * `rustls_web_pki_server_cert_verifier_builder_add_crl` is used to add certificate revocation + * lists (CRLs) to the builder. If CRLs are added, revocation checking will be performed + * for the entire certificate chain unless + * `rustls_web_pki_server_cert_verifier_only_check_end_entity_revocation` is used. Unknown + * revocation status for certificates considered for revocation status will be treated as + * an error unless `rustls_web_pki_server_cert_verifier_allow_unknown_revocation_status` is + * used. + * + * This copies the contents of the `rustls_root_cert_store`. It does not take + * ownership of the pointed-to data. + */ +struct rustls_web_pki_server_cert_verifier_builder *rustls_web_pki_server_cert_verifier_builder_new_with_provider(const struct rustls_crypto_provider *provider, + const struct rustls_root_cert_store *store); + /** * Add one or more certificate revocation lists (CRLs) to the server certificate verifier * builder by reading the CRL content from the provided buffer of PEM encoded content. From dd9ffab14355ca8600068db136d85240160ff545 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 16:41:04 -0400 Subject: [PATCH 15/25] cipher: convert platform verifier to provider This breaks an unconditional dep on `*ring*` for the `rustls_platform_verifier` verifier. The `client.c` test binary is updated to use the fallible form of the verifier constructor that uses the default crypto provider. --- src/acceptor.rs | 5 ++++- src/cipher.rs | 36 +++++++++++++++++++++++++++++++----- src/client.rs | 20 ++++++++++++++++---- src/rustls.h | 14 +++++++++++++- tests/client.c | 5 +++-- 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/acceptor.rs b/src/acceptor.rs index a57feed6..327bb6c7 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -641,7 +641,10 @@ mod tests { protocols_slices.len(), ); - let verifier = rustls_server_cert_verifier::rustls_platform_server_cert_verifier(); + let mut verifier = null_mut(); + let result = + rustls_server_cert_verifier::rustls_platform_server_cert_verifier(&mut verifier); + assert_eq!(result, rustls_result::Ok); assert!(!verifier.is_null()); rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( builder, verifier, diff --git a/src/cipher.rs b/src/cipher.rs index d68f4163..09626457 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -1097,12 +1097,38 @@ impl rustls_server_cert_verifier { /// /// [`rustls-platform-verifier`]: https://github.com/rustls/rustls-platform-verifier #[no_mangle] - pub extern "C" fn rustls_platform_server_cert_verifier() -> *mut rustls_server_cert_verifier { + pub extern "C" fn rustls_platform_server_cert_verifier( + verifier_out: *mut *mut rustls_server_cert_verifier, + ) -> rustls_result { ffi_panic_boundary! { - let verifier: Arc = Arc::new( - rustls_platform_verifier::Verifier::new() - .with_provider(rustls::crypto::ring::default_provider().into()), - ); + let verifier_out = try_mut_from_ptr_ptr!(verifier_out); + let provider = match crypto_provider::get_default_or_install_from_crate_features() { + Some(provider) => provider, + None => return rustls_result::NoDefaultCryptoProvider, + }; + let verifier: Arc = + Arc::new(rustls_platform_verifier::Verifier::new().with_provider(provider)); + set_boxed_mut_ptr(verifier_out, verifier); + rustls_result::Ok + } + } + + /// Create a verifier that uses the default behavior for the current platform. + /// + /// This uses [`rustls-platform-verifier`][] and the specified crypto provider. + /// + /// The verifier can be used in several `rustls_client_config` instances and must be freed by + /// the application using `rustls_server_cert_verifier_free` when no longer needed. + /// + /// [`rustls-platform-verifier`]: https://github.com/rustls/rustls-platform-verifier + #[no_mangle] + pub extern "C" fn rustls_platform_server_cert_verifier_with_provider( + provider: *const rustls_crypto_provider, + ) -> *mut rustls_server_cert_verifier { + ffi_panic_boundary! { + let provider = try_clone_arc!(provider); + let verifier: Arc = + Arc::new(rustls_platform_verifier::Verifier::new().with_provider(provider)); to_boxed_mut_ptr(verifier) } } diff --git a/src/client.rs b/src/client.rs index 9ed00aca..a1679050 100644 --- a/src/client.rs +++ b/src/client.rs @@ -585,7 +585,10 @@ mod tests { #[test] fn test_config_builder() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); - let verifier = rustls_server_cert_verifier::rustls_platform_server_cert_verifier(); + let mut verifier = null_mut(); + let result = + rustls_server_cert_verifier::rustls_platform_server_cert_verifier(&mut verifier); + assert_eq!(result, rustls_result::Ok); assert!(!verifier.is_null()); rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( builder, verifier, @@ -609,7 +612,8 @@ mod tests { assert!(!config2.enable_sni); assert_eq!(config2.alpn_protocols, vec![h1, h2]); } - rustls_client_config::rustls_client_config_free(config) + rustls_client_config::rustls_client_config_free(config); + rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); } // Build a client connection and test the getters and initial values. @@ -617,7 +621,10 @@ mod tests { #[cfg_attr(miri, ignore)] fn test_client_connection_new() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); - let verifier = rustls_server_cert_verifier::rustls_platform_server_cert_verifier(); + let mut verifier = null_mut(); + let result = + rustls_server_cert_verifier::rustls_platform_server_cert_verifier(&mut verifier); + assert_eq!(result, rustls_result::Ok); assert!(!verifier.is_null()); rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( builder, verifier, @@ -667,13 +674,17 @@ mod tests { 0 ); rustls_connection::rustls_connection_free(conn); + rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); } #[test] #[cfg_attr(miri, ignore)] fn test_client_connection_new_ipaddress() { let builder = rustls_client_config_builder::rustls_client_config_builder_new(); - let verifier = rustls_server_cert_verifier::rustls_platform_server_cert_verifier(); + let mut verifier = null_mut(); + let result = + rustls_server_cert_verifier::rustls_platform_server_cert_verifier(&mut verifier); + assert_eq!(result, rustls_result::Ok); assert!(!verifier.is_null()); rustls_client_config_builder::rustls_client_config_builder_set_server_verifier( builder, verifier, @@ -692,6 +703,7 @@ mod tests { if !matches!(result, rustls_result::Ok) { panic!("expected RUSTLS_RESULT_OK, got {:?}", result); } + rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); } #[test] diff --git a/src/rustls.h b/src/rustls.h index 5021877f..67973642 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -1450,7 +1450,19 @@ void rustls_web_pki_server_cert_verifier_builder_free(struct rustls_web_pki_serv * * [`rustls-platform-verifier`]: https://github.com/rustls/rustls-platform-verifier */ -struct rustls_server_cert_verifier *rustls_platform_server_cert_verifier(void); +rustls_result rustls_platform_server_cert_verifier(struct rustls_server_cert_verifier **verifier_out); + +/** + * Create a verifier that uses the default behavior for the current platform. + * + * This uses [`rustls-platform-verifier`][] and the specified crypto provider. + * + * The verifier can be used in several `rustls_client_config` instances and must be freed by + * the application using `rustls_server_cert_verifier_free` when no longer needed. + * + * [`rustls-platform-verifier`]: https://github.com/rustls/rustls-platform-verifier + */ +struct rustls_server_cert_verifier *rustls_platform_server_cert_verifier_with_provider(const struct rustls_crypto_provider *provider); /** * Free a `rustls_server_cert_verifier` previously returned from diff --git a/tests/client.c b/tests/client.c index 34b9ca39..958f9132 100644 --- a/tests/client.c +++ b/tests/client.c @@ -441,8 +441,9 @@ main(int argc, const char **argv) #endif if(getenv("RUSTLS_PLATFORM_VERIFIER")) { - server_cert_verifier = rustls_platform_server_cert_verifier(); - if(server_cert_verifier == NULL) { + result = rustls_platform_server_cert_verifier(&server_cert_verifier); + if(result != RUSTLS_RESULT_OK) { + fprintf(stderr, "client: failed to construct platform verifier\n"); goto cleanup; } rustls_client_config_builder_set_server_verifier(config_builder, From f0c88c4ca3815215f24556c392d797c47577bd4f Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 17:00:57 -0400 Subject: [PATCH 16/25] cmake: reformat CMakeLists files My IDE (clion) wants to do this automatically and I agree with its choices w.r.t removing hard tabs and adding some more consistent whitespace. --- CMakeLists.txt | 28 +++++++++++----------- tests/CMakeLists.txt | 56 ++++++++++++++++++++++---------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c230978..5aa7749b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,18 +7,18 @@ include(ExternalProject) set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust) ExternalProject_Add( - rustls-ffi - DOWNLOAD_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - COMMAND cargo build --locked "$,--release,-->" - # Rely on cargo checking timestamps, rather than tell CMake where every - # output is. - BUILD_ALWAYS true - INSTALL_COMMAND "" - # Run cargo test with --quiet because msbuild will treat the presence - # of "error" in stdout as an error, and we have some test functions that - # end in "_error". Quiet mode suppresses test names, so this is a - # sufficient workaround. - TEST_COMMAND cargo test --locked "$,--release,-->" --quiet + rustls-ffi + DOWNLOAD_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + COMMAND cargo build --locked "$,--release,-->" + # Rely on cargo checking timestamps, rather than tell CMake where every + # output is. + BUILD_ALWAYS true + INSTALL_COMMAND "" + # Run cargo test with --quiet because msbuild will treat the presence + # of "error" in stdout as an error, and we have some test functions that + # end in "_error". Quiet mode suppresses test names, so this is a + # sufficient workaround. + TEST_COMMAND cargo test --locked "$,--release,-->" --quiet ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cd30a772..4ba518df 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,46 +1,46 @@ -IF(WIN32) - add_compile_definitions( - _WIN32_WINNT=0x601 - _CRT_SECURE_NO_WARNINGS - _CRT_NONSTDC_NO_WARNINGS - ssize_t=int - ) -ENDIF(WIN32) +IF (WIN32) + add_compile_definitions( + _WIN32_WINNT=0x601 + _CRT_SECURE_NO_WARNINGS + _CRT_NONSTDC_NO_WARNINGS + ssize_t=int + ) +ENDIF (WIN32) add_executable(client client.c common.c) add_dependencies(client rustls-ffi) target_include_directories(client PUBLIC ${CMAKE_SOURCE_DIR}/src) -IF(WIN32) +IF (WIN32) target_link_libraries( - client - debug "${CMAKE_SOURCE_DIR}/target/debug/rustls_ffi.lib" - optimized "${CMAKE_SOURCE_DIR}/target/release/rustls_ffi.lib" - advapi32.lib bcrypt.lib crypt32.lib cryptnet.lib kernel32.lib ncrypt.lib bcrypt.lib advapi32.lib legacy_stdio_definitions.lib kernel32.lib advapi32.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib synchronization.lib kernel32.lib ws2_32.lib kernel32.lib msvcrt.lib + client + debug "${CMAKE_SOURCE_DIR}/target/debug/rustls_ffi.lib" + optimized "${CMAKE_SOURCE_DIR}/target/release/rustls_ffi.lib" + advapi32.lib bcrypt.lib crypt32.lib cryptnet.lib kernel32.lib ncrypt.lib bcrypt.lib advapi32.lib legacy_stdio_definitions.lib kernel32.lib advapi32.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib synchronization.lib kernel32.lib ws2_32.lib kernel32.lib msvcrt.lib ) set_property(TARGET client PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") -ENDIF(WIN32) -IF(UNIX) +ENDIF (WIN32) +IF (UNIX) # TODO -ENDIF(UNIX) -IF(APPLE) +ENDIF (UNIX) +IF (APPLE) # TODO -ENDIF(APPLE) +ENDIF (APPLE) add_executable(server server.c common.c) add_dependencies(server rustls-ffi) target_include_directories(server PUBLIC ${CMAKE_SOURCE_DIR}/src) -IF(WIN32) +IF (WIN32) target_link_libraries( - server - debug "${CMAKE_SOURCE_DIR}/target/debug/rustls_ffi.lib" - optimized "${CMAKE_SOURCE_DIR}/target/release/rustls_ffi.lib" - advapi32.lib bcrypt.lib crypt32.lib cryptnet.lib kernel32.lib ncrypt.lib bcrypt.lib advapi32.lib legacy_stdio_definitions.lib kernel32.lib advapi32.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib synchronization.lib kernel32.lib ws2_32.lib kernel32.lib msvcrt.lib + server + debug "${CMAKE_SOURCE_DIR}/target/debug/rustls_ffi.lib" + optimized "${CMAKE_SOURCE_DIR}/target/release/rustls_ffi.lib" + advapi32.lib bcrypt.lib crypt32.lib cryptnet.lib kernel32.lib ncrypt.lib bcrypt.lib advapi32.lib legacy_stdio_definitions.lib kernel32.lib advapi32.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib synchronization.lib kernel32.lib ws2_32.lib kernel32.lib msvcrt.lib ) set_property(TARGET server PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") -ENDIF(WIN32) -IF(UNIX) +ENDIF (WIN32) +IF (UNIX) # TODO -ENDIF(UNIX) -IF(APPLE) +ENDIF (UNIX) +IF (APPLE) # TODO -ENDIF(APPLE) +ENDIF (APPLE) From 001b0c799fc8900f8dfd9af8ea400013f4f614e1 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 5 Jul 2024 17:02:29 -0400 Subject: [PATCH 17/25] project: default to aws-lc-rs, offer ring feature This commit: * Makes the `*ring*` dep optional, behind a `ring` feature flag * Adds an optional (but default) dep on `aws-lc-rs` behind a `aws-lc-rs` feature flag. * Adds `nasm` to the Windows build runners for the `aws-lc-rs` default crypto provider. This build requirement may be relaxed in the future depending on whether the upstream project chooses to take a ring-like strategy of distributing pre-built content. * Updates the cbindgen config to respect these new features. * Updates Makefile/Makefile.pkg-config and CMake build systems to support specifying which crypto provider to use, piping through the correct Rust features and C defines to make it all work. * One acceptor unit test is updated: the list of expected supported ciphersuites differs between `ring` and `aws-lc-rs`, with the latter also offering a P-521 suite that isn't present in `*ring*`. * The client/server examples use the implied default and so require no adjustments. --- .github/workflows/test.yaml | 4 + CMakeLists.txt | 18 ++- Cargo.lock | 268 +++++++++++++++++++++++++++++++++++- Cargo.toml | 7 +- Makefile | 9 ++ Makefile.pkg-config | 9 ++ cbindgen.toml | 4 +- src/acceptor.rs | 9 +- src/crypto_provider.rs | 35 ++++- src/rustls.h | 12 ++ tests/CMakeLists.txt | 6 + 11 files changed, 368 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 92d3f772..5502309e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -81,6 +81,8 @@ jobs: persist-credentials: false - name: Install nightly rust toolchain uses: dtolnay/rust-toolchain@nightly + - name: Install NASM for aws-lc-rs + uses: ilammy/setup-nasm@v1 - name: Configure CMake run: cmake -S . -B build - name: Build, debug configuration @@ -100,6 +102,8 @@ jobs: persist-credentials: false - name: Install nightly rust toolchain uses: dtolnay/rust-toolchain@nightly + - name: Install NASM for aws-lc-rs + uses: ilammy/setup-nasm@v1 - name: Configure CMake run: cmake -S . -B build - name: Build, release configuration diff --git a/CMakeLists.txt b/CMakeLists.txt index 5aa7749b..eb884b6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,20 @@ cmake_minimum_required(VERSION 3.15) project(rustls-ffi) + +set(CRYPTO_PROVIDER "aws-lc-rs" CACHE STRING "Crypto provider to use (aws-lc-rs or ring)") + +if (NOT (CRYPTO_PROVIDER STREQUAL "aws-lc-rs" OR CRYPTO_PROVIDER STREQUAL "ring")) + message(FATAL_ERROR "Invalid crypto provider specified: ${CRYPTO_PROVIDER}. Must be 'aws-lc-rs' or 'ring'.") +endif () + +set(CARGO_FEATURES --no-default-features) +if (CRYPTO_PROVIDER STREQUAL "aws-lc-rs") + list(APPEND CARGO_FEATURES --features=aws-lc-rs) +elseif (CRYPTO_PROVIDER STREQUAL "ring") + list(APPEND CARGO_FEATURES --features=ring) +endif () + add_subdirectory(tests) include(ExternalProject) @@ -11,7 +25,7 @@ ExternalProject_Add( DOWNLOAD_COMMAND "" CONFIGURE_COMMAND "" BUILD_COMMAND "" - COMMAND cargo build --locked "$,--release,-->" + COMMAND cargo build --locked ${CARGO_FEATURES} "$,--release,-->" # Rely on cargo checking timestamps, rather than tell CMake where every # output is. BUILD_ALWAYS true @@ -20,5 +34,5 @@ ExternalProject_Add( # of "error" in stdout as an error, and we have some test functions that # end in "_error". Quiet mode suppresses test names, so this is a # sufficient workaround. - TEST_COMMAND cargo test --locked "$,--release,-->" --quiet + TEST_COMMAND cargo test --locked ${CARGO_FEATURES} "$,--release,-->" --quiet ) diff --git a/Cargo.lock b/Cargo.lock index 5bdf036e..ae96332d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,18 +17,74 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "aws-lc-rs" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a47f2fb521b70c11ce7369a6c5fa4bd6af7e5d62ec06303875bafe7c6ba245" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2927c7af777b460b7ccd95f8b67acd7b4c04ec8896bf0c8e80ba30523cffc057" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "base64" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "bytes" version = "1.6.0" @@ -41,6 +97,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -50,12 +107,41 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "combine" version = "4.6.6" @@ -82,6 +168,34 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "getrandom" version = "0.2.11" @@ -93,12 +207,27 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -109,6 +238,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "jni" version = "0.19.0" @@ -129,12 +267,49 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libloading" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +dependencies = [ + "cfg-if", + "windows-targets 0.48.5", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "log" version = "0.4.22" @@ -147,6 +322,28 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom8" version = "0.2.0" @@ -197,6 +394,22 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "prettyplease" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -258,12 +471,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c4d6d8ad9f2492485e13453acbb291dd08f64441b6609c491f1c2cd2c6b4fe1" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -350,6 +583,7 @@ version = "0.102.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -385,7 +619,7 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -432,6 +666,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "spin" version = "0.9.8" @@ -546,6 +786,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -714,3 +966,17 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 2df7c77a..11eb863a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ links = "rustls_ffi" rust-version = "1.64" [features] +default = ["aws-lc-rs"] # Enable this feature when building as Rust dependency. It inhibits the # default behavior of capturing the global logger, which only works when # built using the Makefile, which passes -C metadata=rustls-ffi to avoid @@ -20,12 +21,14 @@ rust-version = "1.64" no_log_capture = [] read_buf = ["rustls/read_buf"] capi = [] +ring = ["rustls/ring", "webpki/ring"] +aws-lc-rs = ["rustls/aws-lc-rs", "webpki/aws_lc_rs"] [dependencies] # Keep in sync with RUSTLS_CRATE_VERSION in build.rs -rustls = { version = "0.23.4", default-features = false, features = ["ring", "std", "tls12"] } +rustls = { version = "0.23.4", default-features = false, features = ["std", "tls12"] } pki-types = { package = "rustls-pki-types", version = "1", features = ["std"] } -webpki = { package = "rustls-webpki", version = "0.102.0", default-features = false, features = ["ring", "std"] } +webpki = { package = "rustls-webpki", version = "0.102.0", default-features = false, features = ["std"] } libc = "0.2" rustls-pemfile = "2" log = "0.4.22" diff --git a/Makefile b/Makefile index c76bdc86..0199d87c 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ CARGOFLAGS += --locked CFLAGS := -Werror -Wall -Wextra -Wpedantic -g -I src/ PROFILE := release +CRYPTO_PROVIDER := aws-lc-rs DESTDIR=/usr/local ifeq ($(PROFILE), debug) @@ -26,6 +27,14 @@ ifneq (,$(TARGET)) CARGOFLAGS += --target $(TARGET) endif +ifeq ($(CRYPTO_PROVIDER), aws-lc-rs) + CFLAGS += -D DEFINE_AWS_LC_RS + CARGOFLAGS += --no-default-features --features aws-lc-rs +else ifeq ($(CRYPTO_PROVIDER), ring) + CFLAGS += -D DEFINE_RING + CARGOFLAGS += --no-default-features --features ring +endif + all: target/client target/server test: all diff --git a/Makefile.pkg-config b/Makefile.pkg-config index bfdcd3bf..fe25c925 100644 --- a/Makefile.pkg-config +++ b/Makefile.pkg-config @@ -13,6 +13,7 @@ CARGOFLAGS += --locked CFLAGS := -Werror -Wall -Wextra -Wpedantic -g -I src/ PROFILE := release +CRYPTO_PROVIDER := aws-lc-rs PREFIX=/usr/local ifeq ($(PROFILE), debug) @@ -25,6 +26,14 @@ ifeq ($(PROFILE), release) CARGOFLAGS += --release endif +ifeq ($(CRYPTO_PROVIDER), aws-lc-rs) + CFLAGS += -D DEFINE_AWS_LC_RS + CARGOFLAGS += --no-default-features --features aws-lc-rs +else ifeq ($(CRYPTO_PROVIDER), ring) + CFLAGS += -D DEFINE_RING + CARGOFLAGS += --no-default-features --features ring +endif + all: target/client target/server integration: all diff --git a/cbindgen.toml b/cbindgen.toml index 335688d7..b2bf782f 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -12,7 +12,9 @@ include = ["rustls_tls_version"] [defines] "feature = read_buf" = "DEFINE_READ_BUF" +"feature = aws-lc-rs" = "DEFINE_AWS_LC_RS" +"feature = ring" = "DEFINE_RING" [parse.expand] crates = ["rustls-ffi"] -features = ["read_buf"] +features = ["read_buf", "aws-lc-rs", "ring"] diff --git a/src/acceptor.rs b/src/acceptor.rs index 327bb6c7..a01dbb2d 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -750,10 +750,11 @@ mod tests { } // Sort to ensure consistent comparison signature_schemes.sort(); - assert_eq!( - &signature_schemes, - &[1025, 1027, 1281, 1283, 1537, 2052, 2053, 2054, 2055] - ); + #[cfg(feature = "aws-lc-rs")] // aws-lc-rs includes P-521. + let expected_schemes = &[1025, 1027, 1281, 1283, 1537, 1539, 2052, 2053, 2054, 2055]; + #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))] + let expected_schemes = &[1025, 1027, 1281, 1283, 1537, 2052, 2053, 2054, 2055]; + assert_eq!(&signature_schemes, expected_schemes); let mut alpn = vec![]; for i in 0.. { diff --git a/src/crypto_provider.rs b/src/crypto_provider.rs index 3aa00fe3..e61e36d3 100644 --- a/src/crypto_provider.rs +++ b/src/crypto_provider.rs @@ -3,6 +3,9 @@ use std::io::Cursor; use std::slice; use std::sync::Arc; +#[cfg(feature = "aws-lc-rs")] +use rustls::crypto::aws_lc_rs; +#[cfg(feature = "ring")] use rustls::crypto::ring; use rustls::crypto::CryptoProvider; use rustls::sign::SigningKey; @@ -222,14 +225,26 @@ pub extern "C" fn rustls_crypto_provider_builder_free( /// /// The caller owns the returned `rustls_crypto_provider` and must free it using /// `rustls_crypto_provider_free`. -// TODO(@cpu): Add a feature gate when we add support for other crypto providers. #[no_mangle] +#[cfg(feature = "ring")] pub extern "C" fn rustls_ring_crypto_provider() -> *const rustls_crypto_provider { ffi_panic_boundary! { Arc::into_raw(Arc::new(ring::default_provider())) as *const rustls_crypto_provider } } +/// Return the `rustls_crypto_provider` backed by the `aws-lc-rs` cryptography library. +/// +/// The caller owns the returned `rustls_crypto_provider` and must free it using +/// `rustls_crypto_provider_free`. +#[no_mangle] +#[cfg(feature = "aws-lc-rs")] +pub extern "C" fn rustls_aws_lc_rs_crypto_provider() -> *const rustls_crypto_provider { + ffi_panic_boundary! { + Arc::into_raw(Arc::new(aws_lc_rs::default_provider())) as *const rustls_crypto_provider + } +} + /// Retrieve a pointer to the process default `rustls_crypto_provider`. /// /// This may return `NULL` if no process default provider has been set using @@ -413,6 +428,20 @@ pub(crate) fn get_default_or_install_from_crate_features() -> Option Option { - // TODO(XXX): Switch based on crate feature once ring is optional. - Some(ring::default_provider()) + // Provider default is unambiguously aws-lc-rs + #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] + { + return Some(aws_lc_rs::default_provider()); + } + + // Provider default is unambiguously ring + #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))] + { + return Some(ring::default_provider()); + } + + // Both features activated - no clear default provider based on + // crate features. + #[allow(unreachable_code)] + None } diff --git a/src/rustls.h b/src/rustls.h index 67973642..711cb4f7 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -1984,6 +1984,7 @@ rustls_result rustls_crypto_provider_builder_build_as_default(struct rustls_cryp */ void rustls_crypto_provider_builder_free(struct rustls_crypto_provider_builder *builder); +#if defined(DEFINE_RING) /** * Return the `rustls_crypto_provider` backed by the `*ring*` cryptography library. * @@ -1991,6 +1992,17 @@ void rustls_crypto_provider_builder_free(struct rustls_crypto_provider_builder * * `rustls_crypto_provider_free`. */ const struct rustls_crypto_provider *rustls_ring_crypto_provider(void); +#endif + +#if defined(DEFINE_AWS_LC_RS) +/** + * Return the `rustls_crypto_provider` backed by the `aws-lc-rs` cryptography library. + * + * The caller owns the returned `rustls_crypto_provider` and must free it using + * `rustls_crypto_provider_free`. + */ +const struct rustls_crypto_provider *rustls_aws_lc_rs_crypto_provider(void); +#endif /** * Retrieve a pointer to the process default `rustls_crypto_provider`. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4ba518df..a7b10a04 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,6 +7,12 @@ IF (WIN32) ) ENDIF (WIN32) +if (CRYPTO_PROVIDER STREQUAL "aws_lc_rs") + add_compile_definitions(DEFINE_AWS_LC_RS) +elseif (CRYPTO_PROVIDER STREQUAL "ring") + add_compile_definitions(DEFINE_RING) +endif () + add_executable(client client.c common.c) add_dependencies(client rustls-ffi) target_include_directories(client PUBLIC ${CMAKE_SOURCE_DIR}/src) From 29edf5aa297da5c27757b0601797d4600a91c595 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Tue, 30 Jul 2024 12:43:26 -0400 Subject: [PATCH 18/25] build: include crypto provider in rustls_version() --- build.rs | 12 ++++++++++-- tests/rustls_version.rs | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/build.rs b/build.rs index 5e4c14ee..4b78ca23 100644 --- a/build.rs +++ b/build.rs @@ -19,13 +19,21 @@ fn main() { println!("cargo:include={}", include_dir.to_str().unwrap()); + let rustls_crypto_provider = { + if cfg!(all(feature = "ring", not(feature = "aws-lc-rs"))) { + "ring" + } else { + "aws-lc-rs" + } + }; + let dest_path = out_dir.join("version.rs"); let mut f = File::create(dest_path).expect("Could not create file"); let pkg_version = env!("CARGO_PKG_VERSION"); writeln!( &mut f, - r#"const RUSTLS_FFI_VERSION: &str = "rustls-ffi/{}/rustls/{}";"#, - pkg_version, RUSTLS_CRATE_VERSION + r#"const RUSTLS_FFI_VERSION: &str = "rustls-ffi/{}/rustls/{}/{}";"#, + pkg_version, RUSTLS_CRATE_VERSION, rustls_crypto_provider ) .expect("Could not write file"); diff --git a/tests/rustls_version.rs b/tests/rustls_version.rs index 4830d3c3..bf2a5358 100644 --- a/tests/rustls_version.rs +++ b/tests/rustls_version.rs @@ -34,6 +34,14 @@ fn rustls_version_match() { .as_str() .expect("missing crate version"); + let rustls_crypto_provider = { + if cfg!(all(feature = "ring", not(feature = "aws-lc-rs"))) { + "ring" + } else { + "aws-lc-rs" + } + }; + // Find the rustls dependency version specified in Cargo.toml let deps = metadata["dependencies"].as_table().unwrap(); let rustls_dep = &deps["rustls"]; @@ -59,6 +67,7 @@ fn rustls_version_match() { crate_version, "rustls", rustls_dep_version, + rustls_crypto_provider, ] ); } From ed2303da8475fde8ffe91d0464fd8fdba4a68e21 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 29 Jul 2024 12:34:11 -0400 Subject: [PATCH 19/25] acceptor: tidy up expected sig scheme test Rather than using decimal constants, rely on the rustls `SignatureScheme` enum. --- src/acceptor.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/acceptor.rs b/src/acceptor.rs index a01dbb2d..c9049e29 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -518,7 +518,7 @@ mod tests { use libc::c_char; use rustls::internal::msgs::codec::Codec; use rustls::internal::msgs::enums::AlertLevel; - use rustls::{AlertDescription, ContentType, ProtocolVersion}; + use rustls::{AlertDescription, ContentType, ProtocolVersion, SignatureScheme}; use crate::cipher::{rustls_certified_key, rustls_server_cert_verifier}; use crate::client::{rustls_client_config, rustls_client_config_builder}; @@ -750,11 +750,28 @@ mod tests { } // Sort to ensure consistent comparison signature_schemes.sort(); - #[cfg(feature = "aws-lc-rs")] // aws-lc-rs includes P-521. - let expected_schemes = &[1025, 1027, 1281, 1283, 1537, 1539, 2052, 2053, 2054, 2055]; - #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))] - let expected_schemes = &[1025, 1027, 1281, 1283, 1537, 2052, 2053, 2054, 2055]; - assert_eq!(&signature_schemes, expected_schemes); + + #[cfg_attr(feature = "ring", allow(unused_mut))] + let mut expected_schemes = vec![ + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::ED25519, + ]; + #[cfg(feature = "aws-lc-rs")] // aws-lc-rs also includes P-521. + expected_schemes.push(SignatureScheme::ECDSA_NISTP521_SHA512); + + let mut expected_schemes = expected_schemes + .into_iter() + .map(u16::from) + .collect::>(); + expected_schemes.sort(); + assert_eq!(signature_schemes, expected_schemes); let mut alpn = vec![]; for i in 0.. { From 9c9d00f1d269b9ef05223439764b09e509102148 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 8 Jul 2024 14:10:11 -0400 Subject: [PATCH 20/25] ci: add aws-lc-rs/ring crypto provider coverage This commit updates the `test` and `pkg-config` CI workflows to take into account the variable `CRYPTO_PROVIDER` support. --- .github/workflows/pkg-config.yaml | 7 +++-- .github/workflows/test.yaml | 47 +++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/.github/workflows/pkg-config.yaml b/.github/workflows/pkg-config.yaml index a9ec44db..e99fc4b9 100644 --- a/.github/workflows/pkg-config.yaml +++ b/.github/workflows/pkg-config.yaml @@ -20,6 +20,7 @@ jobs: matrix: cc: [ clang, gcc ] os: [ ubuntu-latest, macos-latest ] + crypto: [ aws-lc-rs, ring ] steps: - name: Checkout sources uses: actions/checkout@v4 @@ -54,10 +55,10 @@ jobs: # that will complicate setting PKG_CONFIG_PATH/LD_LIBRARY_PATH. run: > CARGOFLAGS=--libdir=lib - make --file=Makefile.pkg-config PREFIX=${PREFIX} install + make --file=Makefile.pkg-config PREFIX=${PREFIX} CRYPTO_PROVIDER=${{ matrix.crypto }} install - name: Build the client/server examples - run: PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig make --file=Makefile.pkg-config PROFILE=debug + run: PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig make --file=Makefile.pkg-config PROFILE=debug CRYPTO_PROVIDER=${{ matrix.crypto }} - name: Verify client is dynamically linked (Ubuntu) if: matrix.os == 'ubuntu-latest' @@ -76,4 +77,4 @@ jobs: run: LD_LIBRARY_PATH=$PREFIX/lib otool -L target/server | grep "rustls" - name: Run the integration tests - run: LD_LIBRARY_PATH=$PREFIX/lib make --file=Makefile.pkg-config PROFILE=debug integration + run: LD_LIBRARY_PATH=$PREFIX/lib make --file=Makefile.pkg-config PROFILE=debug CRYPTO_PROVIDER=${{ matrix.crypto }} integration diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5502309e..5642c762 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,8 +16,9 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - # test a bunch of toolchains on ubuntu + # test a bunch of toolchain and crypto providers on ubuntu cc: [ clang, gcc ] + crypto: [ aws-lc-rs, ring ] rust: - stable - beta @@ -26,10 +27,11 @@ jobs: # consider MSRV - 1.64.0 os: [ ubuntu-latest ] - # but only stable on macos/windows (slower platforms) + # but only stable, clang, and aws-lc-rs on macos (slower platform) include: - os: macos-latest cc: clang + crypto: aws-lc-rs rust: stable steps: - name: Checkout sources @@ -41,11 +43,19 @@ jobs: uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - - env: + - name: Unit tests + env: CARGO_UNSTABLE_HTTP_REGISTRY: true - run: make CC=${{ matrix.cc }} PROFILE=debug test integration + run: make CC=${{ matrix.cc }} PROFILE=debug CRYPTO_PROVIDER=${{ matrix.crypto }} test - name: Platform verifier connect test - run: make PROFILE=debug connect-test + run: make PROFILE=debug CRYPTO_PROVIDER=${{ matrix.crypto }} connect-test + - name: Integration tests + env: + CARGO_UNSTABLE_HTTP_REGISTRY: true + # Note: we run this after the connect-tests because the static libs test rebuilds the crate + # squashing whatever RUSTFLAGS the Makefile has set and producing a librustls_ffi.a + # for the default build config. + run: make CC=${{ matrix.cc }} PROFILE=debug CRYPTO_PROVIDER=${{ matrix.crypto }} integration - name: Verify debug builds were using ASAN if: runner.os == 'Linux' # For 'nm' run: | @@ -54,7 +64,7 @@ jobs: - name: Build release binaries run: | make clean - make CC=${{ matrix.cc }} PROFILE=release + make CC=${{ matrix.cc }} CRYPTO_PROVIDER=${{ matrix.crypto }} PROFILE=release - name: Verify release builds were not using ASAN if: runner.os == 'Linux' # For 'nm' run: | @@ -75,6 +85,9 @@ jobs: test-windows-cmake-debug: name: Windows CMake, Debug configuration runs-on: windows-latest + strategy: + matrix: + crypto: [ aws-lc-rs, ring ] steps: - uses: actions/checkout@v4 with: @@ -84,11 +97,11 @@ jobs: - name: Install NASM for aws-lc-rs uses: ilammy/setup-nasm@v1 - name: Configure CMake - run: cmake -S . -B build + run: cmake -DCRYPTO_PROVIDER="${{ matrix.crypto }}" -S . -B build - name: Build, debug configuration run: cmake --build build --config Debug - name: Integration test, debug configuration - run: cargo test --locked --test client_server client_server_integration -- --ignored --exact + run: cargo test --no-default-features --features="${{ matrix.crypto }}" --locked --test client_server client_server_integration -- --ignored --exact env: CLIENT_BINARY: D:\a\rustls-ffi\rustls-ffi\build\tests\Debug\client.exe SERVER_BINARY: D:\a\rustls-ffi\rustls-ffi\build\tests\Debug\server.exe @@ -96,6 +109,9 @@ jobs: test-windows-cmake-release: name: Windows CMake, Release configuration runs-on: windows-latest + strategy: + matrix: + crypto: [ aws-lc-rs, ring ] steps: - uses: actions/checkout@v4 with: @@ -105,11 +121,11 @@ jobs: - name: Install NASM for aws-lc-rs uses: ilammy/setup-nasm@v1 - name: Configure CMake - run: cmake -S . -B build + run: cmake -DCRYPTO_PROVIDER="${{ matrix.crypto }}" -S . -B build - name: Build, release configuration run: cmake --build build --config Release - name: Integration test, release configuration - run: cargo test --locked --test client_server client_server_integration -- --ignored --exact + run: cargo test --no-default-features --features="${{ matrix.crypto }}" --locked --test client_server client_server_integration -- --ignored --exact env: CLIENT_BINARY: D:\a\rustls-ffi\rustls-ffi\build\tests\Release\client.exe SERVER_BINARY: D:\a\rustls-ffi\rustls-ffi\build\tests\Release\server.exe @@ -165,8 +181,11 @@ jobs: - name: Build client/server binaries run: make target/client target/server - - name: cargo test (debug; all features; -Z minimal-versions) - run: cargo -Z minimal-versions test --all-features --locked + - name: cargo test (debug; default features; -Z minimal-versions) + run: cargo -Z minimal-versions test --locked + + - name: cargo test (debug; ring; -Z minimal-versions) + run: cargo -Z minimal-versions test --no-default-features --features=ring --locked format: name: Format @@ -204,6 +223,8 @@ jobs: # If we suppress (e.g. #![allow(clippy::arc_with_non_send_sync)]), # we would get an unknown-lint error from older clippy versions. run: cargo clippy --locked --workspace --all-targets -- -D warnings -A unknown-lints + - name: Check clippy (ring) + run: cargo clippy --locked --workspace --all-targets --no-default-features --features=ring -- -D warnings -A unknown-lints clippy-nightly-optional: name: Clippy nightly (optional) @@ -219,6 +240,8 @@ jobs: components: clippy - name: Check clippy (default features) run: cargo clippy --locked --workspace --all-targets -- -D warnings + - name: Check clippy (ring) + run: cargo clippy --locked --workspace --all-targets --no-default-features --features=ring -- -D warnings - name: Check clippy (all features) # We only test --all-features on nightly, because two of the features # (read_buf, core_io_borrowed_buf) require nightly. From 370d42fc534ef7e14a9ba226df6e4598f9e4e418 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 8 Jul 2024 15:30:48 -0400 Subject: [PATCH 21/25] tests: support customizing supported ciphersuite This commit updates both `client.c` and `server.c` to respect a new `RUSTLS_CIPHERSUITE` env var. When set, the process-default cryptography provider's supported ciphersuites will be reduced to _just_ the one specified by name in the env var. The `client_server.rs` integration test is then updated to start a server that only supports one ciphersuite. Two clients are created, one with a matching ciphersuite and one without. We use each client to connect to the server and assert only the expected one with matching ciphersuite support works. --- tests/client.c | 27 ++++++++++++++++-- tests/client_server.rs | 29 +++++++++++++++++++ tests/common.c | 64 ++++++++++++++++++++++++++++++++++++++++++ tests/common.h | 6 ++++ tests/server.c | 32 ++++++++++++++++++--- 5 files changed, 152 insertions(+), 6 deletions(-) diff --git a/tests/client.c b/tests/client.c index 958f9132..65ee6218 100644 --- a/tests/client.c +++ b/tests/client.c @@ -420,8 +420,8 @@ main(int argc, const char **argv) /* Set this global variable for logging purposes. */ programname = "client"; - struct rustls_client_config_builder *config_builder = - rustls_client_config_builder_new(); + const struct rustls_crypto_provider *custom_provider = NULL; + struct rustls_client_config_builder *config_builder = NULL; struct rustls_root_cert_store_builder *server_cert_root_store_builder = NULL; const struct rustls_root_cert_store *server_cert_root_store = NULL; const struct rustls_client_config *client_config = NULL; @@ -440,6 +440,28 @@ main(int argc, const char **argv) setmode(STDOUT_FILENO, O_BINARY); #endif + const char *custom_ciphersuite_name = getenv("RUSTLS_CIPHERSUITE"); + if(custom_ciphersuite_name != NULL) { + custom_provider = + default_provider_with_custom_ciphersuite(custom_ciphersuite_name); + if(custom_provider == NULL) { + goto cleanup; + } + printf("customized to use ciphersuite: %s\n", custom_ciphersuite_name); + + result = rustls_client_config_builder_new_custom(custom_provider, + default_tls_versions, + default_tls_versions_len, + &config_builder); + if(result != RUSTLS_RESULT_OK) { + print_error("creating client config builder", result); + goto cleanup; + } + } + else { + config_builder = rustls_client_config_builder_new(); + } + if(getenv("RUSTLS_PLATFORM_VERIFIER")) { result = rustls_platform_server_cert_verifier(&server_cert_verifier); if(result != RUSTLS_RESULT_OK) { @@ -529,6 +551,7 @@ main(int argc, const char **argv) rustls_server_cert_verifier_free(server_cert_verifier); rustls_certified_key_free(certified_key); rustls_client_config_free(client_config); + rustls_crypto_provider_free(custom_provider); #ifdef _WIN32 WSACleanup(); diff --git a/tests/client_server.rs b/tests/client_server.rs index d07b05b1..d0820c2a 100644 --- a/tests/client_server.rs +++ b/tests/client_server.rs @@ -92,11 +92,40 @@ fn client_server_integration() { ], }; + let custom_ciphersuites = TestCase { + name: "client/server with limited ciphersuites", + server_opts: ServerOptions { + valgrind: valgrind.clone(), + env: vec![("RUSTLS_CIPHERSUITE", "TLS13_CHACHA20_POLY1305_SHA256")], + }, + client_tests: vec![ + ClientTest { + name: "limited ciphersuite, supported by server", + valgrind: valgrind.clone(), + env: vec![ + ("NO_CHECK_CERTIFICATE", "1"), + ("RUSTLS_CIPHERSUITE", "TLS13_CHACHA20_POLY1305_SHA256"), + ], + expect_error: false, + }, + ClientTest { + name: "limited ciphersuite, not supported by server", + valgrind: valgrind.clone(), + env: vec![ + ("NO_CHECK_CERTIFICATE", "1"), + ("RUSTLS_CIPHERSUITE", "TLS13_AES_128_GCM_SHA256"), + ], + expect_error: true, // Unsupported ciphersuite. + }, + ], + }; + TestCases(vec![ standard_server, vectored_server, mandatory_client_auth_server, mandatory_client_auth_server_with_crls, + custom_ciphersuites, ]) .run(); } diff --git a/tests/common.c b/tests/common.c index 20cd537a..55d2084a 100644 --- a/tests/common.c +++ b/tests/common.c @@ -385,3 +385,67 @@ load_cert_and_key(const char *certfile, const char *keyfile) } return certified_key; } + +const struct rustls_crypto_provider * +default_provider_with_custom_ciphersuite(const char *custom_ciphersuite_name) +{ + const struct rustls_supported_ciphersuite *custom_ciphersuite = NULL; + rustls_crypto_provider_builder *provider_builder = NULL; + const struct rustls_crypto_provider *custom_provider = NULL; + + size_t num_supported = rustls_default_crypto_provider_ciphersuites_len(); + for(size_t i = 0; i < num_supported; i++) { + const struct rustls_supported_ciphersuite *suite = + rustls_default_crypto_provider_ciphersuites_get(i); + if(suite == NULL) { + fprintf(stderr, "failed to get ciphersuite %zu\n", i); + goto cleanup; + } + + const rustls_str suite_name = rustls_supported_ciphersuite_get_name(suite); + if(strncmp(suite_name.data, custom_ciphersuite_name, suite_name.len) == + 0) { + custom_ciphersuite = suite; + break; + } + } + + if(custom_ciphersuite == NULL) { + fprintf(stderr, + "failed to select custom ciphersuite: %s\n", + custom_ciphersuite_name); + goto cleanup; + } + + rustls_result result = + rustls_crypto_provider_builder_new_from_default(&provider_builder); + if(result != RUSTLS_RESULT_OK) { + fprintf(stderr, "failed to create provider builder\n"); + goto cleanup; + } + + result = rustls_crypto_provider_builder_set_cipher_suites( + provider_builder, &custom_ciphersuite, 1); + if(result != RUSTLS_RESULT_OK) { + fprintf(stderr, "failed to set custom ciphersuite\n"); + goto cleanup; + } + + result = + rustls_crypto_provider_builder_build(provider_builder, &custom_provider); + if(result != RUSTLS_RESULT_OK) { + fprintf(stderr, "failed to build custom provider\n"); + goto cleanup; + } + +cleanup: + rustls_crypto_provider_builder_free(provider_builder); + return custom_provider; +} + +// TLS 1.2 and TLS 1.3, matching Rustls default. +const uint16_t default_tls_versions[] = { 0x0303, 0x0304 }; + +// Declare the length of the TLS versions array as a global constant +const size_t default_tls_versions_len = + sizeof(default_tls_versions) / sizeof(default_tls_versions[0]); diff --git a/tests/common.h b/tests/common.h index edd3ceab..1980c055 100644 --- a/tests/common.h +++ b/tests/common.h @@ -133,4 +133,10 @@ enum demo_result read_file(const char *filename, char *buf, size_t buflen, const struct rustls_certified_key *load_cert_and_key(const char *certfile, const char *keyfile); +const struct rustls_crypto_provider *default_provider_with_custom_ciphersuite( + const char *custom_ciphersuite_name); + +extern const uint16_t default_tls_versions[]; +extern const size_t default_tls_versions_len; + #endif /* COMMON_H */ diff --git a/tests/server.c b/tests/server.c index 723f25f7..b3532720 100644 --- a/tests/server.c +++ b/tests/server.c @@ -242,8 +242,9 @@ main(int argc, const char **argv) { int ret = 1; int sockfd = 0; - struct rustls_server_config_builder *config_builder = - rustls_server_config_builder_new(); + + const struct rustls_crypto_provider *custom_provider = NULL; + struct rustls_server_config_builder *config_builder = NULL; const struct rustls_server_config *server_config = NULL; struct rustls_connection *rconn = NULL; const struct rustls_certified_key *certified_key = NULL; @@ -253,6 +254,7 @@ main(int argc, const char **argv) struct rustls_web_pki_client_cert_verifier_builder *client_cert_verifier_builder = NULL; struct rustls_client_cert_verifier *client_cert_verifier = NULL; + rustls_result result = RUSTLS_RESULT_OK; /* Set this global variable for logging purposes. */ programname = "server"; @@ -277,6 +279,28 @@ main(int argc, const char **argv) goto cleanup; } + const char *custom_ciphersuite_name = getenv("RUSTLS_CIPHERSUITE"); + if(custom_ciphersuite_name != NULL) { + custom_provider = + default_provider_with_custom_ciphersuite(custom_ciphersuite_name); + if(custom_provider == NULL) { + goto cleanup; + } + printf("customized to use ciphersuite: %s\n", custom_ciphersuite_name); + + result = rustls_server_config_builder_new_custom(custom_provider, + default_tls_versions, + default_tls_versions_len, + &config_builder); + if(result != RUSTLS_RESULT_OK) { + print_error("creating client config builder", result); + goto cleanup; + } + } + else { + config_builder = rustls_server_config_builder_new(); + } + certified_key = load_cert_and_key(argv[1], argv[2]); if(certified_key == NULL) { goto cleanup; @@ -336,8 +360,7 @@ main(int argc, const char **argv) client_cert_verifier); } - rustls_result result = - rustls_server_config_builder_build(config_builder, &server_config); + result = rustls_server_config_builder_build(config_builder, &server_config); if(result != RUSTLS_RESULT_OK) { print_error("building server config", result); goto cleanup; @@ -427,6 +450,7 @@ main(int argc, const char **argv) rustls_client_cert_verifier_free(client_cert_verifier); rustls_server_config_free(server_config); rustls_connection_free(rconn); + rustls_crypto_provider_free(custom_provider); if(sockfd > 0) { close(sockfd); } From bab2c58faecdeaca15cc722d5052d2688c2741ce Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Thu, 1 Aug 2024 10:02:53 -0400 Subject: [PATCH 22/25] docs: update README for crypto provider support * Mentions which providers we support, and explicitly that we do not encourage/support building with both providers enabled. * Mentions how to select a provider with the supported build systems (Make, cmake, cargo-c). * Mentions the build requirements/supported platforms of the upstream providers. For e.g. on Windows aws-lc-rs presently requires nasm because at present it (sensibly) does not ship pre-generated binaries. --- README.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c81462e..c96dce3f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,63 @@ to provide the cryptographic primitives. # Build You'll need to [install the Rust toolchain](https://rustup.rs/) (version 1.64 -or above) and a C compiler (`gcc` and `clang` should both work). +or above) and a C compiler (`gcc` and `clang` should both work). + +## Cryptography provider + +Both rustls and rustls-ffi support choosing a cryptography provider for +implementing the cryptography required for TLS. By default, both will use +[`aws-lc-rs`][], but [`*ring*`][] is available as an opt-in choice. + +It is **not** presently supported to build with both cryptography providers +activated, or with neither provider activated. + +### Choosing a provider + +#### Make + +When building with the `Makefile`, or example `Makefile.pkg-config` specify +a `CRYPTO_PROVIDER` as a makefile variable. E.g.: + +* `make` to build with the default (`aws-lc-rs`). +* `make CRYPTO_PROVIDER=aws-lc-rs` to build with `aws-lc-rs` explicitly. +* `make CRYPTO_PROVIDER=ring` to build with `*ring*`. + +#### CMake + +When building with `cmake`, specify a `CRYPTO_PROVIDER` as a cmake cache entry +variable with `-DCRYPTO_PROVIDER`. E.g.: + +* `cmake -S . -B build; cmake --build build --config Release` - to build with + the default (`aws-lc-rs`). +* `cmake -DCRYPTO_PROVIDER=aws-lc-rs -S . -B build; cmake --build build --config + Release` - to build with `aws-lc-rs` explicitly. +* `cmake -DCRYPTO_PROVIDER=ring -S . -B build; cmake --build build --config + Release` - to build with `aws-lc-rs` explicitly. + +#### Cargo-c + +When building with the experimental [`cargo-c`] support, use `--features` to +specify which provider to use. E.g.: + +* `cargo cinstall` to build with the default (`aws-lc-rs`). +* `cargo cinstall --features aws-lc-rs` to build with `aws-lc-rs` explicitly. +* `cargo cinstall --no-default-features --features ring` to build with `*ring*`. + +[`cargo-c`]: https://github.com/lu-zero/cargo-c + +### Cryptography provider build requirements + +For more information on cryptography provider builder requirements and supported +platforms see the upstream documentation: + +* [`aws-lc-rs` platforms and requirements][] +* [`*ring*` supported platforms][] + +[`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs +[`aws-lc-rs` platforms and requirements]: https://aws.github.io/aws-lc-rs/requirements/index.html +[`*ring*`]: https://crates.io/crates/ring +[`*ring*` supported platforms]: https://github.com/briansmith/ring/blob/2e8363b433fa3b3962c877d9ed2e9145612f3160/include/ring-core/target.h#L18-L64 ## Static Library From 86b843529de9e3dd98e57f98b447e3c82b9da631 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 8 Jul 2024 16:11:16 -0400 Subject: [PATCH 23/25] Cargo: update rustls 0.23.4 -> 0.23.12 There are no breaking changes to account for. --- Cargo.lock | 28 ++++++++++++++-------------- Cargo.toml | 2 +- build.rs | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae96332d..4c41ea05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "aws-lc-rs" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a47f2fb521b70c11ce7369a6c5fa4bd6af7e5d62ec06303875bafe7c6ba245" +checksum = "4ae74d9bd0a7530e8afd1770739ad34b36838829d6ad61818f9230f683f5ad77" dependencies = [ "aws-lc-sys", "mirai-annotations", @@ -31,9 +31,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.19.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2927c7af777b460b7ccd95f8b67acd7b4c04ec8896bf0c8e80ba30523cffc057" +checksum = "0f0e249228c6ad2d240c2dc94b714d711629d52bad946075d8e9b2f5391f0703" dependencies = [ "bindgen", "cc", @@ -492,9 +492,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.4" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4d6d8ad9f2492485e13453acbb291dd08f64441b6609c491f1c2cd2c6b4fe1" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "aws-lc-rs", "once_cell", @@ -523,9 +523,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -573,15 +573,15 @@ dependencies = [ [[package]] name = "rustls-platform-verifier-android" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "aws-lc-rs", "ring", @@ -779,9 +779,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ "rustls-pki-types", ] diff --git a/Cargo.toml b/Cargo.toml index 11eb863a..e1077904 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ aws-lc-rs = ["rustls/aws-lc-rs", "webpki/aws_lc_rs"] [dependencies] # Keep in sync with RUSTLS_CRATE_VERSION in build.rs -rustls = { version = "0.23.4", default-features = false, features = ["std", "tls12"] } +rustls = { version = "0.23.12", default-features = false, features = ["std", "tls12"] } pki-types = { package = "rustls-pki-types", version = "1", features = ["std"] } webpki = { package = "rustls-webpki", version = "0.102.0", default-features = false, features = ["std"] } libc = "0.2" diff --git a/build.rs b/build.rs index 4b78ca23..c5370df5 100644 --- a/build.rs +++ b/build.rs @@ -8,7 +8,7 @@ use std::{env, fs, path::PathBuf}; // because doing so would require a heavy-weight deserialization lib dependency // (and it couldn't be a _dev_ dep for use in a build script) or doing brittle // by-hand parsing. -const RUSTLS_CRATE_VERSION: &str = "0.23.4"; +const RUSTLS_CRATE_VERSION: &str = "0.23.12"; fn main() { let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); From 1375c7b0cf504800826507b0e4a591f98cffb163 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Thu, 1 Aug 2024 11:25:00 -0400 Subject: [PATCH 24/25] docs: add 0.14.0 changelog --- CHANGELOG.md | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d989e4c6..d8a713fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,137 @@ # Changelog +## 0.14.0 (2024-08-01) + +This release updates to [Rustls 0.23.12][] and changes the rustls-ffi API to allow +choosing a cryptography provider to use with Rustls. + +The default provider has been changed to match the Rustls default, +[`aws-lc-rs`][]. Users that wish to continue using `*ring*` as the provider may +opt-in. See the `README` for more detail on supported platforms and build +requirements. + +[Rustls 0.23.12]: https://github.com/rustls/rustls/releases/tag/v%2F0.23.12 +[`aws-lc-rs`]: https://github.com/aws/aws-lc-rs + +### Added + +* A new `rustls_crypto_provider` type has been added to represent + `rustls::CryptoProvider` instances. + * The current process-wide default crypto provider (if any) can be retrieved + with `rustls_crypto_provider_default()`. + * If rustls-ffi was built with `aws-lc-rs`, (`DEFINE_AWS_LC_RS` is true), then + `rustls_aws_lc_rs_crypto_provider()` can be used to retrieve the `aws-lc-rs` + provider. + * If rustls-ffi was built with `ring`, (`DEFINE_RING` is true), then + `rustls_ring_crypto_provider()` can be used to retrieve the `aws-lc-rs` + provider. + * Ciphersuites supported by a specific `rustls_crypto_provider` can be retrieved with + `rustls_crypto_provider_ciphersuites_len()` and `rustls_crypto_provider_ciphersuites_get()`. + * Ciphersuites supported by the current process-wide default crypto provider (if any) can + be retrieved with `rustls_default_crypto_provider_ciphersuites_len()` and + `rustls_default_crypto_provider_ciphersuites_get()`. + +* A new `RUSTLS_RESULT_NO_DEFAULT_CRYPTO_PROVIDER` `rustls_result` was added to + indicate when an operation that requires a process-wide default crypto + provider fails because no provider has been installed as the default, or + the default was not implicit based on supported provider. + +* A new `rustls_crypto_provider_builder` type has been added to customize, or + install, a crypto provider. + * `rustls_crypto_provider_builder_new_from_default` will construct a builder + based on the current process-wide default. + * `rustls_crypto_provider_builder_new_with_base` will construct a builder + based on a specified `rustls_crypto_provider`. + * Customization of supported ciphersuites can be achieved with + `rustls_crypto_provider_builder_set_cipher_suites()`. + * The default process-wide provider can be installed from a builder using + `rustls_crypto_provider_builder_build_as_default()`, if it has not already + been done. + * Or, a new `rustls_crypto_provider` instance built with + `rustls_crypto_provider_builder_build()`. + * See the function documentation for more information on recommended + workflows. + +* A new `rustls_signing_key` type has been added to represent a private key + that has been parsed by a `rustls_crypto_provider` and is ready to use for + cryptographic operations. + * Use `rustls_crypto_provider_load_key()` to load a `signing_key` from + a buffer of PEM data using a `rustls_crypto_provider`. + * Use `rustls_certified_key_build_with_signing_key()` to build + a `rustls_certified_key` with a PEM cert chain and a `rustls_signing_key`. + +* New `rustls_web_pki_client_cert_verifier_builder_new_with_provider()` and + `rustls_web_pki_server_cert_verifier_builder_new_with_provider()` + functions have been added to construct `rustls_client_cert_verifier` or + `rustls_server_cert_verifier` instances that use a specified + `rustls_crypto_provider`. + +* Support for constructing a `rustls_server_cert_verifier` that uses the + platform operating system's native certificate verification functionality was + added. See the [`rustls-platform-verifier`] crate docs for + more information on supported platforms. + * Use `rustls_platform_server_cert_verifier()` to construct a platform verifier + that uses the default crypto provider. + * Use `rustls_platform_server_cert_verifier_with_provider()` to construct a + platform verifier that uses the specified `rustls_crypto_provider`. + * The returned `rustls_server_cert_verifier` can be used with + a `rustls_client_config_builder` with + `rustls_client_config_builder_set_server_verifier()`. + +* When using `aws-lc-rs` as the crypto provider, NIST P-521 signatures are now + supported. + +[`rustls-platform-verifier`]: https://github.com/rustls/rustls-platform-verifier + +### Changed + +* `rustls_server_config_builder_new()`, `rustls_client_config_builder_new()`, + `rustls_web_pki_client_cert_verifier_builder_new()`, and + `rustls_web_pki_server_cert_verifier_builder_new()`, and + `rustls_certified_key_build` functions now use the process + default crypto provider instead of being hardcoded to use `ring`. + +* `rustls_server_config_builder_new_custom()` and + `rustls_client_config_builder_new_custom()` no longer take custom + ciphersuites as an argument. Instead they require providing + a `rustls_crypto_provider`. + * Customizing ciphersuite support is now done at the provider level using + `rustls_crypto_provider_builder` and + `rustls_crypto_provider_builder_set_cipher_suites()`. + +* `rustls_server_config_builder_build()` and + `rustls_client_config_builder_build()` now use out-parameters for the + `rustls_server_config` or `rustls_client_config`, and return a `rustls_result`. + This allows returning an error if the build operation fails because a suitable + crypto provider was not available. + +* `rustls_client_config_builder_build()` now returns + a `RUSTLS_RESULT_NO_SERVER_CERT_VERIFIER` `rustls_result` error if a server + certificate verifier was not set instead of falling back to a verifier that + would fail all certificate validation attempts. + +* The `NoneVerifier` used if a `rustls_client_config` is constructed by + a `rustls_client_config_builder` without a verifier configured has been + changed to return an unknown issuer error instead of a bad signature error + when asked to verify a server certificate. + +* Error specificity for revoked certificates was improved. + +### Removed + +* The `ALL_CIPHER_SUITES` and `DEFAULT_CIPHER_SUITES` constants and associated + functions (`rustls_all_ciphersuites_len()`, + `rustls_all_ciphersuites_get_entry()`, `rustls_default_ciphersuites_len()` and + `rustls_default_ciphersuites_get_entry()`) have been + removed. Ciphersuite support is dictated by the `rustls_crypto_provider`. + * Use `rustls_default_supported_ciphersuites()` to retrieve + a `rustls_supported_ciphersuites` for the default `rustls_crypto_provider`. + * Use `rustls_crypto_provider_ciphersuites()` to retrieve a + `rustls_supported_ciphersuites` for a given `rustls_crypto_provider`. + * Use `rustls_supported_ciphersuites_len()` and + `rustls_supported_ciphersuites_get()` to iterate the + `rustls_supported_ciphersuites`. + ## 0.13.0 (2024-03-28) This release updates to [Rustls 0.23.4] and continues to use `*ring*` as the From a58238616fefdc14dde3124f35f0f342b2962fd4 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 8 Jul 2024 16:12:04 -0400 Subject: [PATCH 25/25] Cargo: version 0.13.0 -> 0.14.0-rc1 Keeping as a release candidate while we debug one remaining issue with a downstream HTTPD mod_tls update. --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c41ea05..e451d65e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,7 +508,7 @@ dependencies = [ [[package]] name = "rustls-ffi" -version = "0.13.0" +version = "0.14.0-rc1" dependencies = [ "libc", "log", diff --git a/Cargo.toml b/Cargo.toml index e1077904..99d4e5fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustls-ffi" -version = "0.13.0" +version = "0.14.0-rc1" license = "Apache-2.0 OR ISC OR MIT" readme = "README-crates.io.md" description = "Rustls bindings for non-Rust languages"