rustls/
key_log_file.rs

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