diff --git a/src/client.rs b/src/client.rs index a1679050..363354b8 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::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; use rustls::{ - sign::CertifiedKey, ClientConfig, ClientConnection, DigitallySignedStruct, Error, - ProtocolVersion, SignatureScheme, SupportedProtocolVersion, + sign::CertifiedKey, ClientConfig, ClientConnection, DigitallySignedStruct, Error, KeyLog, + KeyLogFile, ProtocolVersion, SignatureScheme, SupportedProtocolVersion, }; use crate::cipher::{rustls_certified_key, rustls_server_cert_verifier}; @@ -18,6 +18,7 @@ use crate::connection::{rustls_connection, Connection}; use crate::crypto_provider::rustls_crypto_provider; use crate::error::rustls_result::{InvalidParameter, NullParameter}; use crate::error::{self, map_error, rustls_result}; +use crate::keylog::{rustls_keylog_log_callback, rustls_keylog_will_log_callback, CallbackKeyLog}; use crate::rslice::NulByte; use crate::rslice::{rustls_slice_bytes, rustls_slice_slice_bytes, rustls_str}; use crate::{ @@ -50,6 +51,7 @@ pub(crate) struct ClientConfigBuilder { alpn_protocols: Vec>, enable_sni: bool, cert_resolver: Option>, + key_log: Option>, } arc_castable! { @@ -84,6 +86,7 @@ impl rustls_client_config_builder { cert_resolver: None, alpn_protocols: vec![], enable_sni: true, + key_log: None, }; to_boxed_mut_ptr(builder) } @@ -137,6 +140,7 @@ impl rustls_client_config_builder { cert_resolver: None, alpn_protocols: vec![], enable_sni: true, + key_log: None, }; set_boxed_mut_ptr(builder_out, config_builder); @@ -422,6 +426,71 @@ impl rustls_client_config_builder { rustls_result::Ok } } + + /// Log key material to the file specified by the `SSLKEYLOGFILE` environment variable. + /// + /// The key material will be logged in the NSS key log format, + /// and is + /// compatible with tools like Wireshark. + /// + /// Secrets logged in this manner are **extremely sensitive** and can break the security + /// of past, present and future sessions. + /// + /// For more control over which secrets are logged, or to customize the format, prefer + /// `rustls_client_config_builder_set_key_log`. + #[no_mangle] + pub extern "C" fn rustls_client_config_builder_set_key_log_file( + builder: *mut rustls_client_config_builder, + ) -> rustls_result { + ffi_panic_boundary! { + let builder = try_mut_from_ptr!(builder); + builder.key_log = Some(Arc::new(KeyLogFile::new())); + rustls_result::Ok + } + } + + /// Provide callbacks to manage logging key material. + /// + /// The `log_cb` argument is mandatory and must not be `NULL` or a `NullParameter` error is + /// returned. The `log_cb` will be invoked with a `client_random` to identify the relevant session, + /// a `label` to identify the purpose of the `secret`, and the `secret` itself. See the + /// Rustls documentation of the `KeyLog` trait for more information on possible labels: + /// + /// + /// The `will_log_cb` may be `NULL`, in which case all key material will be provided to + /// the `log_cb`. By providing a custom `will_log_cb` you may return `0` for labels you don't + /// wish to log, and non-zero for labels you _do_ wish to log as a performance optimization. + /// + /// Both callbacks **must** be thread-safe. Arguments provided to the callback live only for as + /// long as the callback is executing and are not valid after the callback returns. The + /// callbacks must not retain references to the provided data. + /// + /// Secrets provided to the `log_cb` are **extremely sensitive** and can break the security + /// of past, present and future sessions. + /// + /// See also `rustls_client_config_builder_set_key_log_file` for a simpler way to log + /// to a file specified by the `SSLKEYLOGFILE` environment variable. + #[no_mangle] + pub extern "C" fn rustls_client_config_builder_set_key_log( + builder: *mut rustls_client_config_builder, + log_cb: rustls_keylog_log_callback, + will_log_cb: rustls_keylog_will_log_callback, + ) -> rustls_result { + ffi_panic_boundary! { + let builder = try_mut_from_ptr!(builder); + let log_cb = match log_cb { + Some(cb) => cb, + None => return NullParameter, + }; + + builder.key_log = Some(Arc::new(CallbackKeyLog { + log_cb, + will_log_cb, + })); + + rustls_result::Ok + } + } } /// Always send the same client certificate. @@ -488,6 +557,10 @@ impl rustls_client_config_builder { config.alpn_protocols = builder.alpn_protocols; config.enable_sni = builder.enable_sni; + if let Some(key_log) = builder.key_log { + config.key_log = key_log; + } + set_arc_mut_ptr(config_out, config); rustls_result::Ok } diff --git a/src/keylog.rs b/src/keylog.rs new file mode 100644 index 00000000..a2b2c218 --- /dev/null +++ b/src/keylog.rs @@ -0,0 +1,88 @@ +//! Provides FFI abstractions for the [`rustls::KeyLog`] trait. + +use std::ffi::c_int; +use std::fmt; + +use crate::rslice::rustls_str; + +/// An optional callback for logging key material. +/// +/// See the documentation on `rustls_client_config_builder_set_key_log` and +/// `rustls_server_config_builder_set_key_log` for more information about the +/// lifetimes of the parameters. +pub type rustls_keylog_log_callback = Option< + unsafe extern "C" fn( + label: rustls_str, + client_random: *const u8, + client_random_len: usize, + secret: *const u8, + secret_len: usize, + ), +>; + +/// An optional callback for deciding if key material will be logged. +/// +/// See the documentation on `rustls_client_config_builder_set_key_log` and +/// `rustls_server_config_builder_set_key_log` for more information about the +/// lifetimes of the parameters. +pub type rustls_keylog_will_log_callback = Option c_int>; + +/// A type alias for a keylog log callback that has been extracted from an option. +pub(crate) type KeylogLogCallback = unsafe extern "C" fn( + label: rustls_str, + client_random: *const u8, + client_random_len: usize, + secret: *const u8, + secret_len: usize, +); + +/// An implementation of `rustls::KeyLog` based on C callbacks. +pub(crate) struct CallbackKeyLog { + // We use the crate-internal rust type here - it is _not_ Option wrapped. + pub(crate) log_cb: KeylogLogCallback, + // We use the pub type alias here - it is Option wrapped and may be None. + pub(crate) will_log_cb: rustls_keylog_will_log_callback, +} + +impl rustls::KeyLog for CallbackKeyLog { + fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) { + unsafe { + (self.log_cb)( + // Safety: Rustls will never give us a label containing NULL. + rustls_str::try_from(label).unwrap(), + client_random.as_ptr(), + client_random.len(), + secret.as_ptr(), + secret.len(), + ); + } + } + + fn will_log(&self, label: &str) -> bool { + match self.will_log_cb { + Some(cb) => { + // Safety: Rustls will never give us a label containing NULL. + let label = rustls_str::try_from(label).unwrap(); + // Log iff the cb returned non-zero. + !matches!(unsafe { (cb)(label) }, 0) + } + // Default to logging everything. + None => true, + } + } +} + +impl fmt::Debug for CallbackKeyLog { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CallbackKeyLog").finish() + } +} + +/// Safety: `CallbackKeyLog` is Send because we don't allocate or deallocate any of its +/// fields. +unsafe impl Send for CallbackKeyLog {} + +/// Safety: Verifier is Sync if the C code passes us a callback that +/// obeys the concurrency safety requirements documented in +/// `rustls_client_config_builder_set_key_log` and `rustls_server_config_builder_set_key_log`. +unsafe impl Sync for CallbackKeyLog {} diff --git a/src/lib.rs b/src/lib.rs index be565a73..044f3c43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ pub mod crypto_provider; pub mod enums; mod error; pub mod io; +pub mod keylog; pub mod log; mod panic; pub mod rslice; diff --git a/src/rustls.h b/src/rustls.h index 47f093de..e4f9be1d 100644 --- a/src/rustls.h +++ b/src/rustls.h @@ -499,6 +499,28 @@ typedef struct rustls_verify_server_cert_params { typedef uint32_t (*rustls_verify_server_cert_callback)(rustls_verify_server_cert_user_data userdata, const struct rustls_verify_server_cert_params *params); +/** + * An optional callback for logging key material. + * + * See the documentation on `rustls_client_config_builder_set_key_log` and + * `rustls_server_config_builder_set_key_log` for more information about the + * lifetimes of the parameters. + */ +typedef void (*rustls_keylog_log_callback)(struct rustls_str label, + const uint8_t *client_random, + size_t client_random_len, + const uint8_t *secret, + size_t secret_len); + +/** + * An optional callback for deciding if key material will be logged. + * + * See the documentation on `rustls_client_config_builder_set_key_log` and + * `rustls_server_config_builder_set_key_log` for more information about the + * lifetimes of the parameters. + */ +typedef int (*rustls_keylog_will_log_callback)(struct rustls_str label); + typedef size_t rustls_log_level; typedef struct rustls_log_params { @@ -1613,6 +1635,48 @@ rustls_result rustls_client_config_builder_set_certified_key(struct rustls_clien const struct rustls_certified_key *const *certified_keys, size_t certified_keys_len); +/** + * Log key material to the file specified by the `SSLKEYLOGFILE` environment variable. + * + * The key material will be logged in the NSS key log format, + * and is + * compatible with tools like Wireshark. + * + * Secrets logged in this manner are **extremely sensitive** and can break the security + * of past, present and future sessions. + * + * For more control over which secrets are logged, or to customize the format, prefer + * `rustls_client_config_builder_set_key_log`. + */ +rustls_result rustls_client_config_builder_set_key_log_file(struct rustls_client_config_builder *builder); + +/** + * Provide callbacks to manage logging key material. + * + * The `log_cb` argument is mandatory and must not be `NULL` or a `NullParameter` error is + * returned. The `log_cb` will be invoked with a `client_random` to identify the relevant session, + * a `label` to identify the purpose of the `secret`, and the `secret` itself. See the + * Rustls documentation of the `KeyLog` trait for more information on possible labels: + * + * + * The `will_log_cb` may be `NULL`, in which case all key material will be provided to + * the `log_cb`. By providing a custom `will_log_cb` you may return `0` for labels you don't + * wish to log, and non-zero for labels you _do_ wish to log as a performance optimization. + * + * Both callbacks **must** be thread-safe. Arguments provided to the callback live only for as + * long as the callback is executing and are not valid after the callback returns. The + * callbacks must not retain references to the provided data. + * + * Secrets provided to the `log_cb` are **extremely sensitive** and can break the security + * of past, present and future sessions. + * + * See also `rustls_client_config_builder_set_key_log_file` for a simpler way to log + * to a file specified by the `SSLKEYLOGFILE` environment variable. + */ +rustls_result rustls_client_config_builder_set_key_log(struct rustls_client_config_builder *builder, + rustls_keylog_log_callback log_cb, + rustls_keylog_will_log_callback will_log_cb); + /** * Turn a *rustls_client_config_builder (mutable) into a const *rustls_client_config * (read-only). @@ -2218,6 +2282,48 @@ rustls_result rustls_server_config_builder_new_custom(const struct rustls_crypto void rustls_server_config_builder_set_client_verifier(struct rustls_server_config_builder *builder, const struct rustls_client_cert_verifier *verifier); +/** + * Log key material to the file specified by the `SSLKEYLOGFILE` environment variable. + * + * The key material will be logged in the NSS key log format, + * and is + * compatible with tools like Wireshark. + * + * Secrets logged in this manner are **extremely sensitive** and can break the security + * of past, present and future sessions. + * + * For more control over which secrets are logged, or to customize the format, prefer + * `rustls_server_config_builder_set_key_log`. + */ +rustls_result rustls_server_config_builder_set_key_log_file(struct rustls_server_config_builder *builder); + +/** + * Provide callbacks to manage logging key material. + * + * The `log_cb` argument is mandatory and must not be `NULL` or a `NullParameter` error is + * returned. The `log_cb` will be invoked with a `client_random` to identify the relevant session, + * a `label` to identify the purpose of the `secret`, and the `secret` itself. See the + * Rustls documentation of the `KeyLog` trait for more information on possible labels: + * + * + * The `will_log_cb` may be `NULL`, in which case all key material will be provided to + * the `log_cb`. By providing a custom `will_log_cb` you may return `0` for labels you don't + * wish to log, and non-zero for labels you _do_ wish to log as a performance optimization. + * + * Both callbacks **must** be thread-safe. Arguments provided to the callback live only for as + * long as the callback is executing and are not valid after the callback returns. The + * callbacks must not retain references to the provided data. + * + * Secrets provided to the `log_cb` are **extremely sensitive** and can break the security + * of past, present and future sessions. + * + * See also `rustls_server_config_builder_set_key_log_file` for a simpler way to log + * to a file specified by the `SSLKEYLOGFILE` environment variable. + */ +rustls_result rustls_server_config_builder_set_key_log(struct rustls_server_config_builder *builder, + rustls_keylog_log_callback log_cb, + rustls_keylog_will_log_callback will_log_cb); + /** * "Free" a server_config_builder without building it into a rustls_server_config. * diff --git a/src/server.rs b/src/server.rs index 0e94ac6b..a2b97d85 100644 --- a/src/server.rs +++ b/src/server.rs @@ -11,13 +11,14 @@ use rustls::server::{ WebPkiClientVerifier, }; use rustls::sign::CertifiedKey; -use rustls::{ProtocolVersion, SignatureScheme, SupportedProtocolVersion}; +use rustls::{KeyLog, KeyLogFile, ProtocolVersion, SignatureScheme, SupportedProtocolVersion}; use crate::cipher::{rustls_certified_key, rustls_client_cert_verifier}; use crate::connection::{rustls_connection, Connection}; use crate::crypto_provider::rustls_crypto_provider; use crate::error::rustls_result::NullParameter; use crate::error::{map_error, rustls_result}; +use crate::keylog::{rustls_keylog_log_callback, rustls_keylog_will_log_callback, CallbackKeyLog}; 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, @@ -53,6 +54,7 @@ pub(crate) struct ServerConfigBuilder { session_storage: Option>, alpn_protocols: Vec>, ignore_client_order: Option, + key_log: Option>, } arc_castable! { @@ -85,6 +87,7 @@ impl rustls_server_config_builder { session_storage: None, alpn_protocols: vec![], ignore_client_order: None, + key_log: None, }; to_boxed_mut_ptr(builder) } @@ -139,6 +142,7 @@ impl rustls_server_config_builder { session_storage: None, alpn_protocols: vec![], ignore_client_order: None, + key_log: None, }; set_boxed_mut_ptr(builder_out, builder); rustls_result::Ok @@ -161,6 +165,71 @@ impl rustls_server_config_builder { } } + /// Log key material to the file specified by the `SSLKEYLOGFILE` environment variable. + /// + /// The key material will be logged in the NSS key log format, + /// and is + /// compatible with tools like Wireshark. + /// + /// Secrets logged in this manner are **extremely sensitive** and can break the security + /// of past, present and future sessions. + /// + /// For more control over which secrets are logged, or to customize the format, prefer + /// `rustls_server_config_builder_set_key_log`. + #[no_mangle] + pub extern "C" fn rustls_server_config_builder_set_key_log_file( + builder: *mut rustls_server_config_builder, + ) -> rustls_result { + ffi_panic_boundary! { + let builder = try_mut_from_ptr!(builder); + builder.key_log = Some(Arc::new(KeyLogFile::new())); + rustls_result::Ok + } + } + + /// Provide callbacks to manage logging key material. + /// + /// The `log_cb` argument is mandatory and must not be `NULL` or a `NullParameter` error is + /// returned. The `log_cb` will be invoked with a `client_random` to identify the relevant session, + /// a `label` to identify the purpose of the `secret`, and the `secret` itself. See the + /// Rustls documentation of the `KeyLog` trait for more information on possible labels: + /// + /// + /// The `will_log_cb` may be `NULL`, in which case all key material will be provided to + /// the `log_cb`. By providing a custom `will_log_cb` you may return `0` for labels you don't + /// wish to log, and non-zero for labels you _do_ wish to log as a performance optimization. + /// + /// Both callbacks **must** be thread-safe. Arguments provided to the callback live only for as + /// long as the callback is executing and are not valid after the callback returns. The + /// callbacks must not retain references to the provided data. + /// + /// Secrets provided to the `log_cb` are **extremely sensitive** and can break the security + /// of past, present and future sessions. + /// + /// See also `rustls_server_config_builder_set_key_log_file` for a simpler way to log + /// to a file specified by the `SSLKEYLOGFILE` environment variable. + #[no_mangle] + pub extern "C" fn rustls_server_config_builder_set_key_log( + builder: *mut rustls_server_config_builder, + log_cb: rustls_keylog_log_callback, + will_log_cb: rustls_keylog_will_log_callback, + ) -> rustls_result { + ffi_panic_boundary! { + let builder = try_mut_from_ptr!(builder); + let log_cb = match log_cb { + Some(cb) => cb, + None => return NullParameter, + }; + + builder.key_log = Some(Arc::new(CallbackKeyLog { + log_cb, + will_log_cb, + })); + + rustls_result::Ok + } + } + /// "Free" a server_config_builder without building it into a rustls_server_config. /// /// Normally builders are built into rustls_server_configs via `rustls_server_config_builder_build` @@ -297,6 +366,10 @@ impl rustls_server_config_builder { config.ignore_client_order = ignore_client_order; } + if let Some(key_log) = builder.key_log { + config.key_log = key_log; + } + set_arc_mut_ptr(config_out, config); rustls_result::Ok } diff --git a/tests/client.c b/tests/client.c index 65ee6218..a147023f 100644 --- a/tests/client.c +++ b/tests/client.c @@ -506,6 +506,22 @@ main(int argc, const char **argv) goto cleanup; } + if(getenv("SSLKEYLOGFILE")) { + result = rustls_client_config_builder_set_key_log_file(config_builder); + if(result != RUSTLS_RESULT_OK) { + print_error("enabling keylog", result); + goto cleanup; + } + } + else if(getenv("STDERRKEYLOG")) { + result = rustls_client_config_builder_set_key_log( + config_builder, stderr_key_log_cb, NULL); + if(result != RUSTLS_RESULT_OK) { + print_error("enabling keylog", result); + goto cleanup; + } + } + char *auth_cert = getenv("AUTH_CERT"); char *auth_key = getenv("AUTH_KEY"); if((auth_cert && !auth_key) || (!auth_cert && auth_key)) { diff --git a/tests/client_server.rs b/tests/client_server.rs index d0820c2a..f794240f 100644 --- a/tests/client_server.rs +++ b/tests/client_server.rs @@ -33,6 +33,24 @@ fn client_server_integration() { client_tests: standard_client_tests(valgrind.clone()), }; + let keylogfile_server = TestCase { + name: "SSLKEYLOG server", + server_opts: ServerOptions { + valgrind: valgrind.clone(), + env: vec![("SSLKEYLOGFILE", "/tmp/rustls-ffi.server.key")], + }, + client_tests: standard_client_tests(valgrind.clone()), + }; + + let stderrkeylog_server = TestCase { + name: "STDERRKEYLOG server", + server_opts: ServerOptions { + valgrind: valgrind.clone(), + env: vec![("STDERRKEYLOG", "1")], + }, + client_tests: standard_client_tests(valgrind.clone()), + }; + let mandatory_client_auth_server = TestCase { name: "Mandatory client auth tests", server_opts: ServerOptions { @@ -123,6 +141,8 @@ fn client_server_integration() { TestCases(vec![ standard_server, vectored_server, + keylogfile_server, + stderrkeylog_server, mandatory_client_auth_server, mandatory_client_auth_server_with_crls, custom_ciphersuites, @@ -166,6 +186,21 @@ fn standard_client_tests(valgrind: Option) -> Vec { ], expect_error: false, }, + ClientTest { + name: "SSLKEYLOGFILE", + valgrind: valgrind.clone(), + env: vec![ + ("CA_FILE", "testdata/minica.pem"), + ("SSLKEYLOGFILE", "/tmp/rustls-ffi.client.key"), + ], + expect_error: false, + }, + ClientTest { + name: "STDERRKEYLOG", + valgrind: valgrind.clone(), + env: vec![("CA_FILE", "testdata/minica.pem"), ("STDERRKEYLOG", "1")], + expect_error: false, + }, ] } diff --git a/tests/common.c b/tests/common.c index 55d2084a..b03df1a2 100644 --- a/tests/common.c +++ b/tests/common.c @@ -443,6 +443,61 @@ default_provider_with_custom_ciphersuite(const char *custom_ciphersuite_name) return custom_provider; } +// hex encode the given data buffer, returning a new NULL terminated buffer +// with the result, or NULL if memory allocation fails. +// +// Caller owns the returned buffer and must free it. +static char * +hex_encode(const unsigned char *data, size_t len) +{ + // Two output chars per input char, plus the NULL terminator. + char *hex_str = (char *)malloc((len * 2) + 1); + if(!hex_str) { + return NULL; + } + + for(size_t i = 0; i < len; i++) { + snprintf(hex_str + (i * 2), 3, "%02x", data[i]); + } + + hex_str[len * 2] = '\0'; + return hex_str; +} + +void +stderr_key_log_cb(rustls_str label, const unsigned char *client_random, + size_t client_random_len, const unsigned char *secret, + size_t secret_len) +{ + char *client_random_str = NULL; + char *secret_str = NULL; + + client_random_str = hex_encode(client_random, client_random_len); + if(client_random_str == NULL) { + goto cleanup; + } + + secret_str = hex_encode(secret, secret_len); + if(secret_str == NULL) { + goto cleanup; + } + + fprintf(stderr, + "SSLKEYLOG: label=%.*s client_random=%s secret=%s\n", + (int)label.len, + label.data, + client_random_str, + secret_str); + +cleanup: + if(client_random_str != NULL) { + free(client_random_str); + } + if(secret_str != NULL) { + free(secret_str); + } +} + // TLS 1.2 and TLS 1.3, matching Rustls default. const uint16_t default_tls_versions[] = { 0x0303, 0x0304 }; diff --git a/tests/common.h b/tests/common.h index 1980c055..fd234904 100644 --- a/tests/common.h +++ b/tests/common.h @@ -136,6 +136,10 @@ const struct rustls_certified_key *load_cert_and_key(const char *certfile, const struct rustls_crypto_provider *default_provider_with_custom_ciphersuite( const char *custom_ciphersuite_name); +void stderr_key_log_cb(rustls_str label, const unsigned char *client_random, + size_t client_random_len, const unsigned char *secret, + size_t secret_len); + extern const uint16_t default_tls_versions[]; extern const size_t default_tls_versions_len; diff --git a/tests/server.c b/tests/server.c index b3532720..3c6846fc 100644 --- a/tests/server.c +++ b/tests/server.c @@ -360,6 +360,22 @@ main(int argc, const char **argv) client_cert_verifier); } + if(getenv("SSLKEYLOGFILE")) { + result = rustls_server_config_builder_set_key_log_file(config_builder); + if(result != RUSTLS_RESULT_OK) { + print_error("enabling keylog", result); + goto cleanup; + } + } + else if(getenv("STDERRKEYLOG")) { + result = rustls_server_config_builder_set_key_log( + config_builder, stderr_key_log_cb, NULL); + if(result != RUSTLS_RESULT_OK) { + print_error("enabling keylog", result); + goto cleanup; + } + } + result = rustls_server_config_builder_build(config_builder, &server_config); if(result != RUSTLS_RESULT_OK) { print_error("building server config", result);