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::KeyLog;
11use crate::log::warn;
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"), allow(unused_variables))]
29 let file = match OpenOptions::new()
30 .append(true)
31 .create(true)
32 .open(path)
33 {
34 Ok(f) => Some(f),
35 Err(e) => {
36 warn!("unable to create key log file {path:?}: {e}");
37 None
38 }
39 };
40
41 Self {
42 file,
43 buf: Vec::new(),
44 }
45 }
46
47 fn try_write(&mut self, label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<()> {
48 let Some(file) = &mut self.file else {
49 return Ok(());
50 };
51
52 self.buf.truncate(0);
53 write!(self.buf, "{label} ")?;
54 for b in client_random.iter() {
55 write!(self.buf, "{b:02x}")?;
56 }
57 write!(self.buf, " ")?;
58 for b in secret.iter() {
59 write!(self.buf, "{b:02x}")?;
60 }
61 writeln!(self.buf)?;
62 file.write_all(&self.buf)
63 }
64}
65
66impl Debug for KeyLogFileInner {
67 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
68 f.debug_struct("KeyLogFileInner")
69 .field("file", &self.file)
71 .finish()
72 }
73}
74
75pub struct KeyLogFile(Mutex<KeyLogFileInner>);
84
85impl KeyLogFile {
86 pub fn new() -> Self {
89 let var = var_os("SSLKEYLOGFILE");
90 Self(Mutex::new(KeyLogFileInner::new(var)))
91 }
92}
93
94impl KeyLog for KeyLogFile {
95 fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
96 #[cfg_attr(not(feature = "log"), allow(unused_variables))]
97 match self
98 .0
99 .lock()
100 .unwrap()
101 .try_write(label, client_random, secret)
102 {
103 Ok(()) => {}
104 Err(e) => {
105 warn!("error writing to key log file: {e}");
106 }
107 }
108 }
109}
110
111impl Debug for KeyLogFile {
112 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
113 match self.0.try_lock() {
114 Ok(key_log_file) => write!(f, "{key_log_file:?}"),
115 Err(_) => write!(f, "KeyLogFile {{ <locked> }}"),
116 }
117 }
118}
119
120#[cfg(all(test, target_os = "linux"))]
121mod tests {
122 use super::*;
123
124 fn init() {
125 let _ = env_logger::builder()
126 .is_test(true)
127 .try_init();
128 }
129
130 #[test]
131 fn test_env_var_is_not_set() {
132 init();
133 let mut inner = KeyLogFileInner::new(None);
134 assert!(
135 inner
136 .try_write("label", b"random", b"secret")
137 .is_ok()
138 );
139 }
140
141 #[test]
142 fn test_env_var_cannot_be_opened() {
143 init();
144 let mut inner = KeyLogFileInner::new(Some("/dev/does-not-exist".into()));
145 assert!(
146 inner
147 .try_write("label", b"random", b"secret")
148 .is_ok()
149 );
150 }
151
152 #[test]
153 fn test_env_var_cannot_be_written() {
154 init();
155 let mut inner = KeyLogFileInner::new(Some("/dev/full".into()));
156 assert!(
157 inner
158 .try_write("label", b"random", b"secret")
159 .is_err()
160 );
161 }
162}