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