use alloc::sync::Arc;
use crate::fs::AppendLog;
pub const HEADER: &[u8] = b"# TLS secrets log file, generated by OpenSSL / Python\n";
#[derive(Debug, Default)]
pub struct KeyLog(pub AppendLog);
#[derive(Debug)]
pub struct ConnectionKeyLog {
sink: parking_lot::RwLock<Arc<KeyLog>>,
master_secret: parking_lot::Mutex<Option<Vec<u8>>>,
}
impl ConnectionKeyLog {
pub fn new(sink: Arc<KeyLog>) -> Self {
Self {
sink: parking_lot::RwLock::new(sink),
master_secret: parking_lot::Mutex::new(None),
}
}
pub fn set_sink(&self, sink: Arc<KeyLog>) {
*self.sink.write() = sink;
}
pub fn master_secret(&self) -> Option<Vec<u8>> {
self.master_secret.lock().clone()
}
}
impl rustls::KeyLog for ConnectionKeyLog {
fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
if label == "CLIENT_RANDOM" {
*self.master_secret.lock() = Some(secret.to_vec());
}
self.sink.read().log(label, client_random, secret);
}
fn will_log(&self, label: &str) -> bool {
label == "CLIENT_RANDOM" || self.sink.read().will_log(label)
}
}
fn format_record(label: &str, client_random: &[u8], secret: &[u8]) -> Vec<u8> {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut line = Vec::with_capacity(label.len() + 2 * (client_random.len() + secret.len()) + 3);
line.extend_from_slice(label.as_bytes());
for bytes in [client_random, secret] {
line.push(b' ');
for byte in bytes {
line.push(HEX[(byte >> 4) as usize]);
line.push(HEX[(byte & 0xf) as usize]);
}
}
line.push(b'\n');
line
}
impl rustls::KeyLog for KeyLog {
fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
let _ = self.0.write(&format_record(label, client_random, secret));
}
fn will_log(&self, _label: &str) -> bool {
self.0.enabled()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nss_record_has_two_lowercase_hex_fields() {
assert_eq!(
format_record("CLIENT_RANDOM", &[0x00, 0x1f, 0xa0, 0xff], &[0x09, 0xbc]),
b"CLIENT_RANDOM 001fa0ff 09bc\n"
);
}
#[test]
fn disabled_by_default() {
use rustls::KeyLog as _;
assert!(!KeyLog::default().will_log("CLIENT_RANDOM"));
}
}