diff --git a/Cargo.lock b/Cargo.lock index c96e2732..7c0173e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,9 +159,9 @@ checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -170,9 +170,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.3" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1233,9 +1233,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "aws-lc-rs", "brotli", @@ -1325,9 +1325,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437" dependencies = [ "aws-lc-rs", "ring", diff --git a/README.md b/README.md index 5e2a855d..8a628598 100644 --- a/README.md +++ b/README.md @@ -101,11 +101,10 @@ platforms see the upstream documentation: #### Post-Quantum X25519MLKEM768 Key Exchange Post-quantum-secure key exchange using [X25519MLKEM768][] is supported when using the `aws-lc-rs` -cryptography provider. At this time default support places `X25519MLKEM768` at a lower negotiation priority. +cryptography provider and offered by default at the highest priority. -By enabling the `prefer-post-quantum` feature flag the `X25519MLKEM768` key exchange will be used as the most -preferred key exchange algorithm. We expect to add this feature to the crate's default features in a future -release. +By disabling the `prefer-post-quantum` feature flag the `X25519MLKEM768` key exchange will be +offered at a lower negotiation priority. [X25519MLKEM768]: https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem diff --git a/librustls/Cargo.toml b/librustls/Cargo.toml index 1907ca6d..01a0a5b7 100644 --- a/librustls/Cargo.toml +++ b/librustls/Cargo.toml @@ -12,7 +12,7 @@ links = "rustls_ffi" rust-version = "1.71" [features] -default = ["aws-lc-rs"] +default = ["aws-lc-rs", "prefer-post-quantum"] # 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 @@ -29,7 +29,7 @@ prefer-post-quantum = ["aws-lc-rs", "rustls/prefer-post-quantum"] [dependencies] # Keep in sync with RUSTLS_CRATE_VERSION in build.rs -rustls = { version = "0.23.25", default-features = false, features = ["std", "tls12"] } +rustls = { version = "0.23.27", default-features = false, features = ["std", "tls12"] } webpki = { workspace = true } libc = { workspace = true } log = { workspace = true } diff --git a/librustls/build.rs b/librustls/build.rs index 0a8962d4..4af344a0 100644 --- a/librustls/build.rs +++ b/librustls/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.25"; +const RUSTLS_CRATE_VERSION: &str = "0.23.27"; fn main() { let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); diff --git a/librustls/src/client.rs b/librustls/src/client.rs index 94fa50bf..8e18ae15 100644 --- a/librustls/src/client.rs +++ b/librustls/src/client.rs @@ -743,17 +743,19 @@ impl rustls_client_config { } } - /// Create a new rustls_connection containing a client connection and return - /// it in the output parameter `conn_out`. + /// Create a new client `rustls_connection`. + /// + /// If this returns `RUSTLS_RESULT_OK`, the memory pointed to by `conn_out` is modified to + /// point at a valid `rustls_connection`. The caller now owns the `rustls_connection` + /// and must call `rustls_connection_free` when done with it. + /// + /// Uses the `rustls_client_config` to determine ALPN protocol support. Prefer + /// `rustls_client_connection_new_alpn` to customize this per-connection. /// /// If this returns an error code, the memory pointed to by `conn_out` remains /// unchanged. /// - /// If this returns a non-error, the memory pointed to by `conn_out` - /// is modified to point at a valid `rustls_connection`. The caller now owns - /// the `rustls_connection` and must call `rustls_connection_free` when done with it. - /// - /// The server_name parameter can contain a hostname or an IP address in + /// The `server_name` parameter can contain a hostname or an IP address in /// textual form (IPv4 or IPv6). This function will return an error if it /// cannot be parsed as one of those types. #[no_mangle] @@ -763,35 +765,96 @@ impl rustls_client_config { conn_out: *mut *mut rustls_connection, ) -> rustls_result { ffi_panic_boundary! { - if conn_out.is_null() { - return rustls_result::NullParameter; + Self::rustls_client_connection_new_alpn_inner( + config, + server_name, + try_clone_arc!(config).alpn_protocols.clone(), + conn_out, + ) + } + } + + /// Create a new client `rustls_connection` with custom ALPN protocols. + /// + /// Operates the same as `rustls_client_connection_new`, but allows specifying + /// custom per-connection ALPN protocols instead of inheriting ALPN protocols + /// from the `rustls_clinet_config`. + /// + /// If this returns `RUSTLS_RESULT_OK`, the memory pointed to by `conn_out` is modified to + /// point at a valid `rustls_connection`. The caller now owns the `rustls_connection` + /// and must call `rustls_connection_free` when done with it. + /// + /// If this returns an error code, the memory pointed to by `conn_out` remains + /// unchanged. + /// + /// The `server_name` parameter can contain a hostname or an IP address in + /// textual form (IPv4 or IPv6). This function will return an error if it + /// cannot be parsed as one of those types. + /// + /// `alpn_protocols` must point to a buffer of `rustls_slice_bytes` (built by the caller) + /// with `alpn_protocols_len` elements. Each element of the buffer must be a `rustls_slice_bytes` + /// whose data field points to a single ALPN protocol ID. This function makes a copy of the + /// data in `alpn_protocols` and does not retain any pointers, so the caller can free the + /// pointed-to memory after calling. + /// + /// Standard ALPN protocol IDs are defined at + /// . + #[no_mangle] + pub extern "C" fn rustls_client_connection_new_alpn( + config: *const rustls_client_config, + server_name: *const c_char, + alpn_protocols: *const rustls_slice_bytes, + alpn_protocols_len: size_t, + conn_out: *mut *mut rustls_connection, + ) -> rustls_result { + ffi_panic_boundary! { + let raw_protocols = try_slice!(alpn_protocols, alpn_protocols_len); + let mut alpn_protocols = Vec::with_capacity(raw_protocols.len()); + for p in raw_protocols { + alpn_protocols.push(try_slice!(p.data, p.len).to_vec()); } - let server_name = unsafe { - if server_name.is_null() { - return rustls_result::NullParameter; - } - CStr::from_ptr(server_name) - }; - let config = try_clone_arc!(config); - let conn_out = try_mut_from_ptr_ptr!(conn_out); - let server_name = match server_name.to_str() { - Ok(s) => s, - Err(std::str::Utf8Error { .. }) => return rustls_result::InvalidDnsNameError, - }; - let server_name = match server_name.try_into() { - Ok(sn) => sn, - Err(_) => return rustls_result::InvalidDnsNameError, - }; - let client = ClientConnection::new(config, server_name).unwrap(); - // We've succeeded. Put the client on the heap, and transfer ownership - // to the caller. After this point, we must return rustls_result::Ok so the - // caller knows it is responsible for this memory. - let c = Connection::from_client(client); - set_boxed_mut_ptr(conn_out, c); - rustls_result::Ok + Self::rustls_client_connection_new_alpn_inner( + config, + server_name, + alpn_protocols, + conn_out, + ) } } + + fn rustls_client_connection_new_alpn_inner( + config: *const rustls_client_config, + server_name: *const c_char, + alpn_protocols: Vec>, + conn_out: *mut *mut rustls_connection, + ) -> rustls_result { + let server_name = unsafe { + if server_name.is_null() { + return rustls_result::NullParameter; + } + CStr::from_ptr(server_name) + }; + let Ok(server_name) = server_name.to_str() else { + return rustls_result::InvalidDnsNameError; + }; + let Ok(server_name) = server_name.try_into() else { + return rustls_result::InvalidDnsNameError; + }; + + set_boxed_mut_ptr( + try_mut_from_ptr_ptr!(conn_out), + Connection::from_client( + ClientConnection::new_with_alpn( + try_clone_arc!(config), + server_name, + alpn_protocols, + ) + .unwrap(), + ), + ); + rustls_result::Ok + } } #[cfg(all(test, any(feature = "ring", feature = "aws-lc-rs")))] @@ -838,20 +901,8 @@ mod tests { #[test] #[cfg_attr(miri, ignore)] fn test_client_connection_new() { - let builder = rustls_client_config_builder::rustls_client_config_builder_new(); - 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, - ); - 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 (config, verifier) = test_config(); + let mut conn = null_mut(); let result = rustls_client_config::rustls_client_connection_new( config, @@ -895,6 +946,53 @@ mod tests { rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); } + // Build a client connection w/ custom ALPN and ensure no error occurs. + #[test] + #[cfg_attr(miri, ignore)] + fn test_client_connection_new_alpn() { + let (config, verifier) = test_config(); + let alpn_protocols = [ + rustls_slice_bytes::from(b"h2".as_ref()), + rustls_slice_bytes::from(b"http/1.1".as_ref()), + ]; + + let mut conn = null_mut(); + let result = rustls_client_config::rustls_client_connection_new_alpn( + config, + "example.com\0".as_ptr() as *const c_char, + alpn_protocols.as_ptr(), + alpn_protocols.len() as size_t, + &mut conn, + ); + if !matches!(result, rustls_result::Ok) { + panic!("expected RUSTLS_RESULT_OK, got {result:?}"); + } + + rustls_connection::rustls_connection_free(conn); + rustls_server_cert_verifier::rustls_server_cert_verifier_free(verifier); + } + + fn test_config() -> ( + *const rustls_client_config, + *mut rustls_server_cert_verifier, + ) { + let builder = rustls_client_config_builder::rustls_client_config_builder_new(); + 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, + ); + 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()); + (config, verifier) + } + #[test] #[cfg_attr(miri, ignore)] fn test_client_connection_new_ipaddress() { diff --git a/librustls/src/error.rs b/librustls/src/error.rs index 301f37e9..5832ceff 100644 --- a/librustls/src/error.rs +++ b/librustls/src/error.rs @@ -743,7 +743,9 @@ fn map_invalid_certificate_error(err: CertificateError) -> rustls_result { CertificateError::NotValidForName | CertificateError::NotValidForNameContext { .. } => { CertNotValidForName } - CertificateError::InvalidPurpose => CertInvalidPurpose, + CertificateError::InvalidPurpose | CertificateError::InvalidPurposeContext { .. } => { + CertInvalidPurpose + } CertificateError::ApplicationVerificationFailure => CertApplicationVerificationFailure, _ => CertOtherError, } diff --git a/librustls/src/rustls.h b/librustls/src/rustls.h index 34dd0f28..db54b7d7 100644 --- a/librustls/src/rustls.h +++ b/librustls/src/rustls.h @@ -1580,17 +1580,19 @@ bool rustls_client_config_fips(const struct rustls_client_config *config); void rustls_client_config_free(const struct rustls_client_config *config); /** - * Create a new rustls_connection containing a client connection and return - * it in the output parameter `conn_out`. + * Create a new client `rustls_connection`. + * + * If this returns `RUSTLS_RESULT_OK`, the memory pointed to by `conn_out` is modified to + * point at a valid `rustls_connection`. The caller now owns the `rustls_connection` + * and must call `rustls_connection_free` when done with it. + * + * Uses the `rustls_client_config` to determine ALPN protocol support. Prefer + * `rustls_client_connection_new_alpn` to customize this per-connection. * * If this returns an error code, the memory pointed to by `conn_out` remains * unchanged. * - * If this returns a non-error, the memory pointed to by `conn_out` - * is modified to point at a valid `rustls_connection`. The caller now owns - * the `rustls_connection` and must call `rustls_connection_free` when done with it. - * - * The server_name parameter can contain a hostname or an IP address in + * The `server_name` parameter can contain a hostname or an IP address in * textual form (IPv4 or IPv6). This function will return an error if it * cannot be parsed as one of those types. */ @@ -1598,6 +1600,39 @@ rustls_result rustls_client_connection_new(const struct rustls_client_config *co const char *server_name, struct rustls_connection **conn_out); +/** + * Create a new client `rustls_connection` with custom ALPN protocols. + * + * Operates the same as `rustls_client_connection_new`, but allows specifying + * custom per-connection ALPN protocols instead of inheriting ALPN protocols + * from the `rustls_clinet_config`. + * + * If this returns `RUSTLS_RESULT_OK`, the memory pointed to by `conn_out` is modified to + * point at a valid `rustls_connection`. The caller now owns the `rustls_connection` + * and must call `rustls_connection_free` when done with it. + * + * If this returns an error code, the memory pointed to by `conn_out` remains + * unchanged. + * + * The `server_name` parameter can contain a hostname or an IP address in + * textual form (IPv4 or IPv6). This function will return an error if it + * cannot be parsed as one of those types. + * + * `alpn_protocols` must point to a buffer of `rustls_slice_bytes` (built by the caller) + * with `alpn_protocols_len` elements. Each element of the buffer must be a `rustls_slice_bytes` + * whose data field points to a single ALPN protocol ID. This function makes a copy of the + * data in `alpn_protocols` and does not retain any pointers, so the caller can free the + * pointed-to memory after calling. + * + * Standard ALPN protocol IDs are defined at + * . + */ +rustls_result rustls_client_connection_new_alpn(const struct rustls_client_config *config, + const char *server_name, + const struct rustls_slice_bytes *alpn_protocols, + size_t alpn_protocols_len, + struct rustls_connection **conn_out); + /** * Set the userdata pointer associated with this connection. This will be passed * to any callbacks invoked by the connection, if you've set up callbacks in the config.