Skip to content

Commit

Permalink
client/server: support for KeyLog trait, SSLKEYLOGFILE
Browse files Browse the repository at this point in the history
For debugging purposes it's quite helpful to be able to log session
secrets to a file specified by the `SSLKEYLOGFILE`, for example to use
with Wireshark to decrypt session traffic.

This commit adds two methods to rustls-ffi for both client and server
configurations to facilitate this:

1. `rustls_server_config_builder_set_key_log_file()` and
   `rustls_client_config_builder_set_key_log_file()` enable using the
   Rustls `KeyLogFile` implementation of the `KeyLog` trait. This option
   simply honours the `SSLKEYLOGFILE` env var and spits out a NSS
   formatted key log file appropriate for use with Wireshark and other
   tools that support this format.

2. `rustls_server_config_builder_set_key_log()` and
   `rustls_client_config_builder_set_key_log()` enable providing
   C callbacks that will be invoked to decide which secrets are logged,
   and to do the logging. This allows for fine-grained control over how
   secrets are logged and may be more appropriate for applications that
   already handle this task for other TLS backends (e.g. curl).

The client and server examples are updated to optionally use these new
features. If the `SSLKEYLOG` env. var is set, both will use the
`_set_key_log_file()` fns to set up the standard file based key logging.
If the `STDERRKEYLOG` env var is set then both will use the
`_set_key_log()` fns to set up custom callbacks that will print the
hex-encoded secret data to stderr as a simple demonstration.
  • Loading branch information
cpu committed Sep 26, 2024
1 parent e5a7037 commit c73b2e1
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 3 deletions.
77 changes: 75 additions & 2 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ 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};
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::{
Expand Down Expand Up @@ -50,6 +51,7 @@ pub(crate) struct ClientConfigBuilder {
alpn_protocols: Vec<Vec<u8>>,
enable_sni: bool,
cert_resolver: Option<Arc<dyn ResolvesClientCert>>,
key_log: Option<Arc<dyn KeyLog>>,
}

arc_castable! {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
/// <https://developer.mozilla.org/en-US/docs/Mozilla/Projects/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:
/// <https://docs.rs/rustls/latest/rustls/trait.KeyLog.html#tymethod.log>
///
/// 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.
Expand Down Expand Up @@ -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
}
Expand Down
88 changes: 88 additions & 0 deletions src/keylog.rs
Original file line number Diff line number Diff line change
@@ -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<unsafe extern "C" fn(label: rustls_str) -> 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 {}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
106 changes: 106 additions & 0 deletions src/rustls.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
* <https://developer.mozilla.org/en-US/docs/Mozilla/Projects/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:
* <https://docs.rs/rustls/latest/rustls/trait.KeyLog.html#tymethod.log>
*
* 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).
Expand Down Expand Up @@ -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,
* <https://developer.mozilla.org/en-US/docs/Mozilla/Projects/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:
* <https://docs.rs/rustls/latest/rustls/trait.KeyLog.html#tymethod.log>
*
* 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.
*
Expand Down
Loading

0 comments on commit c73b2e1

Please sign in to comment.