1#[cfg(feature = "logging")]
2use crate::log::warn;
3use crate::KeyLog;
4
5use alloc::vec::Vec;
6use core::fmt::{Debug, Formatter};
7use std::env;
8use std::ffi::OsString;
9use std::fs::{File, OpenOptions};
10use std::io;
11use std::io::Write;
12use std::sync::Mutex;
13
14// Internal mutable state for KeyLogFile
15struct KeyLogFileInner {
16 file: Option<File>,
17 buf: Vec<u8>,
18}
19
20impl KeyLogFileInner {
21 fn new(var: Option<OsString>) -> Self {
22 let path = match &var {
23 Some(path) => path,
24 None => {
25 return Self {
26 file: None,
27 buf: Vec::new(),
28 };
29 }
30 };
31
32 #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
33 let file = match OpenOptions::new()
34 .append(true)
35 .create(true)
36 .open(path)
37 {
38 Ok(f) => Some(f),
39 Err(e) => {
40 warn!("unable to create key log file {:?}: {}", path, e);
41 None
42 }
43 };
44
45 Self {
46 file,
47 buf: Vec::new(),
48 }
49 }
50
51 fn try_write(&mut self, label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<()> {
52 let mut file = match self.file {
53 None => {
54 return Ok(());
55 }
56 Some(ref f) => f,
57 };
58
59 self.buf.truncate(0);
60 write!(self.buf, "{} ", label)?;
61 for b in client_random.iter() {
62 write!(self.buf, "{:02x}", b)?;
63 }
64 write!(self.buf, " ")?;
65 for b in secret.iter() {
66 write!(self.buf, "{:02x}", b)?;
67 }
68 writeln!(self.buf)?;
69 file.write_all(&self.buf)
70 }
71}
72
73impl Debug for KeyLogFileInner {
74 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
75 f&mut DebugStruct<'_, '_>.debug_struct("KeyLogFileInner")
76 // Note: we omit self.buf deliberately as it may contain key data.
77 .field(name:"file", &self.file)
78 .finish()
79 }
80}
81
82/// [`KeyLog`] implementation that opens a file whose name is
83/// given by the `SSLKEYLOGFILE` environment variable, and writes
84/// keys into it.
85///
86/// If `SSLKEYLOGFILE` is not set, this does nothing.
87///
88/// If such a file cannot be opened, or cannot be written then
89/// this does nothing but logs errors at warning-level.
90pub struct KeyLogFile(Mutex<KeyLogFileInner>);
91
92impl KeyLogFile {
93 /// Makes a new `KeyLogFile`. The environment variable is
94 /// inspected and the named file is opened during this call.
95 pub fn new() -> Self {
96 let var: Option = env::var_os(key:"SSLKEYLOGFILE");
97 Self(Mutex::new(KeyLogFileInner::new(var)))
98 }
99}
100
101impl KeyLog for KeyLogFile {
102 fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
103 #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
104 match self
105 .0
106 .lock()
107 .unwrap()
108 .try_write(label, client_random, secret)
109 {
110 Ok(()) => {}
111 Err(e: Error) => {
112 warn!("error writing to key log file: {}", e);
113 }
114 }
115 }
116}
117
118impl Debug for KeyLogFile {
119 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
120 match self.0.try_lock() {
121 Ok(key_log_file: MutexGuard<'_, KeyLogFileInner>) => write!(f, "{:?}", key_log_file),
122 Err(_) => write!(f, "KeyLogFile {{ <locked> }}"),
123 }
124 }
125}
126
127#[cfg(all(test, target_os = "linux"))]
128mod tests {
129 use super::*;
130
131 fn init() {
132 let _ = env_logger::builder()
133 .is_test(true)
134 .try_init();
135 }
136
137 #[test]
138 fn test_env_var_is_not_set() {
139 init();
140 let mut inner = KeyLogFileInner::new(None);
141 assert!(inner
142 .try_write("label", b"random", b"secret")
143 .is_ok());
144 }
145
146 #[test]
147 fn test_env_var_cannot_be_opened() {
148 init();
149 let mut inner = KeyLogFileInner::new(Some("/dev/does-not-exist".into()));
150 assert!(inner
151 .try_write("label", b"random", b"secret")
152 .is_ok());
153 }
154
155 #[test]
156 fn test_env_var_cannot_be_written() {
157 init();
158 let mut inner = KeyLogFileInner::new(Some("/dev/full".into()));
159 assert!(inner
160 .try_write("label", b"random", b"secret")
161 .is_err());
162 }
163}
164