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
15struct 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 #[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 .field("file", &self.file)
76 .finish()
77 }
78}
79
80pub struct KeyLogFile(Mutex<KeyLogFileInner>);
89
90impl KeyLogFile {
91 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}