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