rustls/
key_log_file.rs

1use alloc::vec::Vec;
2use core::fmt::{Debug, Formatter};
3use std::env::var_os;
4use std::ffi::OsString;
5use std::fs::{File, OpenOptions};
6use std::io;
7use std::io::Write;
8use std::sync::Mutex;
9
10use crate::KeyLog;
11use crate::log::warn;
12
13// Internal mutable state for KeyLogFile
14struct KeyLogFileInner {
15    file: Option<File>,
16    buf: Vec<u8>,
17}
18
19impl KeyLogFileInner {
20    fn new(var: Option<OsString>) -> Self {
21        let Some(path) = &var else {
22            return Self {
23                file: None,
24                buf: Vec::new(),
25            };
26        };
27
28        #[cfg_attr(not(feature = "log"), allow(unused_variables))]
29        let file = match OpenOptions::new()
30            .append(true)
31            .create(true)
32            .open(path)
33        {
34            Ok(f) => Some(f),
35            Err(e) => {
36                warn!("unable to create key log file {path:?}: {e}");
37                None
38            }
39        };
40
41        Self {
42            file,
43            buf: Vec::new(),
44        }
45    }
46
47    fn try_write(&mut self, label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<()> {
48        let Some(file) = &mut self.file else {
49            return Ok(());
50        };
51
52        self.buf.truncate(0);
53        write!(self.buf, "{label} ")?;
54        for b in client_random.iter() {
55            write!(self.buf, "{b:02x}")?;
56        }
57        write!(self.buf, " ")?;
58        for b in secret.iter() {
59            write!(self.buf, "{b:02x}")?;
60        }
61        writeln!(self.buf)?;
62        file.write_all(&self.buf)
63    }
64}
65
66impl Debug for KeyLogFileInner {
67    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
68        f.debug_struct("KeyLogFileInner")
69            // Note: we omit self.buf deliberately as it may contain key data.
70            .field("file", &self.file)
71            .finish()
72    }
73}
74
75/// [`KeyLog`] implementation that opens a file whose name is
76/// given by the `SSLKEYLOGFILE` environment variable, and writes
77/// keys into it.
78///
79/// If `SSLKEYLOGFILE` is not set, this does nothing.
80///
81/// If such a file cannot be opened, or cannot be written then
82/// this does nothing but logs errors at warning-level.
83pub struct KeyLogFile(Mutex<KeyLogFileInner>);
84
85impl KeyLogFile {
86    /// Makes a new `KeyLogFile`.  The environment variable is
87    /// inspected and the named file is opened during this call.
88    pub fn new() -> Self {
89        let var = var_os("SSLKEYLOGFILE");
90        Self(Mutex::new(KeyLogFileInner::new(var)))
91    }
92}
93
94impl KeyLog for KeyLogFile {
95    fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
96        #[cfg_attr(not(feature = "log"), allow(unused_variables))]
97        match self
98            .0
99            .lock()
100            .unwrap()
101            .try_write(label, client_random, secret)
102        {
103            Ok(()) => {}
104            Err(e) => {
105                warn!("error writing to key log file: {e}");
106            }
107        }
108    }
109}
110
111impl Debug for KeyLogFile {
112    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
113        match self.0.try_lock() {
114            Ok(key_log_file) => write!(f, "{key_log_file:?}"),
115            Err(_) => write!(f, "KeyLogFile {{ <locked> }}"),
116        }
117    }
118}
119
120#[cfg(all(test, target_os = "linux"))]
121mod tests {
122    use super::*;
123
124    fn init() {
125        let _ = env_logger::builder()
126            .is_test(true)
127            .try_init();
128    }
129
130    #[test]
131    fn test_env_var_is_not_set() {
132        init();
133        let mut inner = KeyLogFileInner::new(None);
134        assert!(
135            inner
136                .try_write("label", b"random", b"secret")
137                .is_ok()
138        );
139    }
140
141    #[test]
142    fn test_env_var_cannot_be_opened() {
143        init();
144        let mut inner = KeyLogFileInner::new(Some("/dev/does-not-exist".into()));
145        assert!(
146            inner
147                .try_write("label", b"random", b"secret")
148                .is_ok()
149        );
150    }
151
152    #[test]
153    fn test_env_var_cannot_be_written() {
154        init();
155        let mut inner = KeyLogFileInner::new(Some("/dev/full".into()));
156        assert!(
157            inner
158                .try_write("label", b"random", b"secret")
159                .is_err()
160        );
161    }
162}