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