rustls_util/
key_log_file.rs1use core::fmt::{Debug, Formatter};
2use std::env::var_os;
3use std::ffi::OsString;
4use std::fs::{File, OpenOptions};
5use std::io;
6use std::io::Write;
7use std::sync::Mutex;
8
9#[cfg(feature = "log")]
10use log::warn;
11use rustls::KeyLog;
12
13struct 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 = "log"), expect(clippy::manual_ok_err))]
29 let file = match OpenOptions::new()
30 .append(true)
31 .create(true)
32 .open(path)
33 {
34 Ok(f) => Some(f),
35 #[cfg_attr(not(feature = "log"), expect(unused_variables))]
36 Err(e) => {
37 #[cfg(feature = "log")]
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 Some(file) = &mut self.file else {
51 return Ok(());
52 };
53
54 self.buf.clear();
55 write!(self.buf, "{label} ")?;
56 for b in client_random.iter() {
57 write!(self.buf, "{b:02x}")?;
58 }
59 write!(self.buf, " ")?;
60 for b in secret.iter() {
61 write!(self.buf, "{b:02x}")?;
62 }
63 writeln!(self.buf)?;
64 file.write_all(&self.buf)
65 }
66}
67
68impl Debug for KeyLogFileInner {
69 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
70 f.debug_struct("KeyLogFileInner")
71 .field("file", &self.file)
73 .finish_non_exhaustive()
74 }
75}
76
77pub struct KeyLogFile(Mutex<KeyLogFileInner>);
86
87impl KeyLogFile {
88 pub fn new() -> Self {
91 let var = var_os("SSLKEYLOGFILE");
92 Self(Mutex::new(KeyLogFileInner::new(var)))
93 }
94}
95
96impl KeyLog for KeyLogFile {
97 fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
98 match self
99 .0
100 .lock()
101 .unwrap()
102 .try_write(label, client_random, secret)
103 {
104 Ok(()) => {}
105 #[cfg_attr(not(feature = "log"), expect(unused_variables))]
106 Err(e) => {
107 #[cfg(feature = "log")]
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, any(target_os = "linux", target_os = "macos")))]
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!(
138 inner
139 .try_write("label", b"random", b"secret")
140 .is_ok()
141 );
142 }
143
144 #[test]
145 fn test_env_var_cannot_be_opened() {
146 init();
147 let mut inner = KeyLogFileInner::new(Some("/dev/does-not-exist".into()));
148 assert!(
149 inner
150 .try_write("label", b"random", b"secret")
151 .is_ok()
152 );
153 }
154
155 #[test]
156 fn test_env_var_cannot_be_written() {
157 init();
158
159 #[cfg(target_os = "linux")]
160 const UNWRITABLE_FILE: &str = "/dev/full";
161
162 #[cfg(target_os = "macos")]
163 const UNWRITABLE_FILE: &str = "/dev/urandom";
164
165 let mut inner = KeyLogFileInner::new(Some(UNWRITABLE_FILE.into()));
166 assert!(
167 inner
168 .try_write("label", b"random", b"secret")
169 .is_err()
170 );
171 }
172}