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::log::warn;
11use crate::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 = "logging"), 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 mut file = match self.file {
49 None => {
50 return Ok(());
51 }
52 Some(ref f) => f,
53 };
54
55 self.buf.truncate(0);
56 write!(self.buf, "{} ", label)?;
57 for b in client_random.iter() {
58 write!(self.buf, "{:02x}", b)?;
59 }
60 write!(self.buf, " ")?;
61 for b in secret.iter() {
62 write!(self.buf, "{:02x}", b)?;
63 }
64 writeln!(self.buf)?;
65 file.write_all(&self.buf)
66 }
67}
68
69impl Debug for KeyLogFileInner {
70 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
71 f.debug_struct("KeyLogFileInner")
72 .field("file", &self.file)
74 .finish()
75 }
76}
77
78pub struct KeyLogFile(Mutex<KeyLogFileInner>);
87
88impl KeyLogFile {
89 pub fn new() -> Self {
92 let var = var_os("SSLKEYLOGFILE");
93 Self(Mutex::new(KeyLogFileInner::new(var)))
94 }
95}
96
97impl KeyLog for KeyLogFile {
98 fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
99 #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
100 match self
101 .0
102 .lock()
103 .unwrap()
104 .try_write(label, client_random, secret)
105 {
106 Ok(()) => {}
107 Err(e) => {
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, target_os = "linux"))]
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!(inner
138 .try_write("label", b"random", b"secret")
139 .is_ok());
140 }
141
142 #[test]
143 fn test_env_var_cannot_be_opened() {
144 init();
145 let mut inner = KeyLogFileInner::new(Some("/dev/does-not-exist".into()));
146 assert!(inner
147 .try_write("label", b"random", b"secret")
148 .is_ok());
149 }
150
151 #[test]
152 fn test_env_var_cannot_be_written() {
153 init();
154 let mut inner = KeyLogFileInner::new(Some("/dev/full".into()));
155 assert!(inner
156 .try_write("label", b"random", b"secret")
157 .is_err());
158 }
159}