Skip to main content

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;
8#[cfg(unix)]
9use std::os::unix::fs::OpenOptionsExt;
10use std::sync::Mutex;
11
12use crate::KeyLog;
13use crate::log::warn;
14
15// Internal mutable state for KeyLogFile
16struct KeyLogFileInner {
17    file: Option<File>,
18    buf: Vec<u8>,
19}
20
21impl KeyLogFileInner {
22    fn new(var: Option<OsString>) -> Self {
23        let Some(path) = &var else {
24            return Self {
25                file: None,
26                buf: Vec::new(),
27            };
28        };
29
30        let mut options = OpenOptions::new();
31        options.append(true).create(true);
32        // Key material is extremely sensitive. On Unix, create with owner-only
33        // access so a default umask does not leave the file world-readable.
34        #[cfg(unix)]
35        options.mode(0o600);
36
37        #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
38        let file = match options.open(path) {
39            Ok(f) => Some(f),
40            Err(e) => {
41                warn!("unable to create key log file {path:?}: {e}");
42                None
43            }
44        };
45
46        Self {
47            file,
48            buf: Vec::new(),
49        }
50    }
51
52    fn try_write(&mut self, label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<()> {
53        let Some(file) = &mut self.file else {
54            return Ok(());
55        };
56
57        self.buf.clear();
58        write!(self.buf, "{label} ")?;
59        for b in client_random.iter() {
60            write!(self.buf, "{b:02x}")?;
61        }
62        write!(self.buf, " ")?;
63        for b in secret.iter() {
64            write!(self.buf, "{b:02x}")?;
65        }
66        writeln!(self.buf)?;
67        file.write_all(&self.buf)
68    }
69}
70
71impl Debug for KeyLogFileInner {
72    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
73        f.debug_struct("KeyLogFileInner")
74            // Note: we omit self.buf deliberately as it may contain key data.
75            .field("file", &self.file)
76            .finish()
77    }
78}
79
80/// [`KeyLog`] implementation that opens a file whose name is
81/// given by the `SSLKEYLOGFILE` environment variable, and writes
82/// keys into it.
83///
84/// If `SSLKEYLOGFILE` is not set, this does nothing.
85///
86/// If such a file cannot be opened, or cannot be written then
87/// this does nothing but logs errors at warning-level.
88pub struct KeyLogFile(Mutex<KeyLogFileInner>);
89
90impl KeyLogFile {
91    /// Makes a new `KeyLogFile`.  The environment variable is
92    /// inspected and the named file is opened during this call.
93    pub fn new() -> Self {
94        let var = var_os("SSLKEYLOGFILE");
95        Self(Mutex::new(KeyLogFileInner::new(var)))
96    }
97}
98
99impl KeyLog for KeyLogFile {
100    fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
101        #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
102        match self
103            .0
104            .lock()
105            .unwrap()
106            .try_write(label, client_random, secret)
107        {
108            Ok(()) => {}
109            Err(e) => {
110                warn!("error writing to key log file: {e}");
111            }
112        }
113    }
114}
115
116impl Debug for KeyLogFile {
117    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
118        match self.0.try_lock() {
119            Ok(key_log_file) => write!(f, "{key_log_file:?}"),
120            Err(_) => write!(f, "KeyLogFile {{ <locked> }}"),
121        }
122    }
123}
124
125#[cfg(all(test, unix))]
126mod tests {
127    use std::os::unix::fs::PermissionsExt;
128    use std::time::{SystemTime, UNIX_EPOCH};
129    use std::{env, format, fs, process};
130
131    use super::*;
132
133    fn init() {
134        let _ = env_logger::builder()
135            .is_test(true)
136            .try_init();
137    }
138
139    #[test]
140    fn test_env_var_is_not_set() {
141        init();
142        let mut inner = KeyLogFileInner::new(None);
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_opened() {
152        init();
153        let mut inner = KeyLogFileInner::new(Some("/dev/does-not-exist".into()));
154        assert!(
155            inner
156                .try_write("label", b"random", b"secret")
157                .is_ok()
158        );
159    }
160
161    #[cfg(target_os = "linux")]
162    #[test]
163    fn test_env_var_cannot_be_written() {
164        init();
165        let mut inner = KeyLogFileInner::new(Some("/dev/full".into()));
166        assert!(
167            inner
168                .try_write("label", b"random", b"secret")
169                .is_err()
170        );
171    }
172
173    #[test]
174    fn test_created_file_has_owner_only_permissions() {
175        let path = env::temp_dir().join(format!(
176            "rustls-keylog-perm-{}-{}",
177            process::id(),
178            SystemTime::now()
179                .duration_since(UNIX_EPOCH)
180                .unwrap()
181                .as_nanos()
182        ));
183        let _ = fs::remove_file(&path);
184
185        let inner = KeyLogFileInner::new(Some(path.clone().into()));
186        assert!(inner.file.is_some(), "key log file should open");
187
188        let mode = fs::metadata(&path)
189            .expect("metadata")
190            .permissions()
191            .mode()
192            & 0o777;
193        let _ = fs::remove_file(&path);
194
195        assert_eq!(
196            mode, 0o600,
197            "SSLKEYLOGFILE must be created with mode 0o600, got {mode:#o}"
198        );
199    }
200}