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