1 use std::env;
2 use std::fs::{File, OpenOptions};
3 use std::io;
4 use std::io::Write;
5 use std::path::Path;
6 use std::sync::Mutex;
7 
8 #[cfg(feature = "logging")]
9 use crate::log::warn;
10 
11 /// This trait represents the ability to do something useful
12 /// with key material, such as logging it to a file for debugging.
13 ///
14 /// Naturally, secrets passed over the interface are *extremely*
15 /// sensitive and can break the security of past, present and
16 /// future sessions.
17 ///
18 /// You'll likely want some interior mutability in your
19 /// implementation to make this useful.
20 ///
21 /// See `KeyLogFile` that implements the standard `SSLKEYLOGFILE`
22 /// environment variable behaviour.
23 pub trait KeyLog: Send + Sync {
24     /// Log the given `secret`.  `client_random` is provided for
25     /// session identification.  `label` describes precisely what
26     /// `secret` means:
27     ///
28     /// - `CLIENT_RANDOM`: `secret` is the master secret for a TLSv1.2 session.
29     /// - `CLIENT_EARLY_TRAFFIC_SECRET`: `secret` encrypts early data
30     ///   transmitted by a client
31     /// - `SERVER_HANDSHAKE_TRAFFIC_SECRET`: `secret` encrypts
32     ///   handshake messages from the server during a TLSv1.3 handshake.
33     /// - `CLIENT_HANDSHAKE_TRAFFIC_SECRET`: `secret` encrypts
34     ///   handshake messages from the client during a TLSv1.3 handshake.
35     /// - `SERVER_TRAFFIC_SECRET_0`: `secret` encrypts post-handshake data
36     ///   from the server in a TLSv1.3 session.
37     /// - `CLIENT_TRAFFIC_SECRET_0`: `secret` encrypts post-handshake data
38     ///   from the client in a TLSv1.3 session.
39     /// - `EXPORTER_SECRET`: `secret` is the post-handshake exporter secret
40     ///   in a TLSv1.3 session.
41     ///
42     /// These strings are selected to match the NSS key log format:
43     /// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
log(&self, label: &str, client_random: &[u8], secret: &[u8])44     fn log(&self, label: &str, client_random: &[u8], secret: &[u8]);
45 
46     /// Indicates whether the secret with label `label` will be logged.
47     ///
48     /// If `will_log` returns true then `log` will be called with the secret.
49     /// Otherwise, `log` will not be called for the secret. This is a
50     /// performance optimization.
will_log(&self, _label: &str) -> bool51     fn will_log(&self, _label: &str) -> bool {
52         true
53     }
54 }
55 
56 /// KeyLog that does exactly nothing.
57 pub struct NoKeyLog;
58 
59 impl KeyLog for NoKeyLog {
log(&self, _: &str, _: &[u8], _: &[u8])60     fn log(&self, _: &str, _: &[u8], _: &[u8]) {}
61     #[inline]
will_log(&self, _label: &str) -> bool62     fn will_log(&self, _label: &str) -> bool {
63         false
64     }
65 }
66 
67 // Internal mutable state for KeyLogFile
68 struct KeyLogFileInner {
69     file: Option<File>,
70     buf: Vec<u8>,
71 }
72 
73 impl KeyLogFileInner {
new(var: Result<String, env::VarError>) -> Self74     fn new(var: Result<String, env::VarError>) -> Self {
75         let path = match var {
76             Ok(ref s) => Path::new(s),
77             Err(env::VarError::NotUnicode(ref s)) => Path::new(s),
78             Err(env::VarError::NotPresent) => {
79                 return KeyLogFileInner {
80                     file: None,
81                     buf: Vec::new(),
82                 };
83             }
84         };
85 
86         #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
87         let file = match OpenOptions::new()
88             .append(true)
89             .create(true)
90             .open(path)
91         {
92             Ok(f) => Some(f),
93             Err(e) => {
94                 warn!("unable to create key log file {:?}: {}", path, e);
95                 None
96             }
97         };
98 
99         KeyLogFileInner {
100             file,
101             buf: Vec::new(),
102         }
103     }
104 
try_write(&mut self, label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<()>105     fn try_write(&mut self, label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<()> {
106         let mut file = match self.file {
107             None => {
108                 return Ok(());
109             }
110             Some(ref f) => f,
111         };
112 
113         self.buf.truncate(0);
114         write!(self.buf, "{} ", label)?;
115         for b in client_random.iter() {
116             write!(self.buf, "{:02x}", b)?;
117         }
118         write!(self.buf, " ")?;
119         for b in secret.iter() {
120             write!(self.buf, "{:02x}", b)?;
121         }
122         writeln!(self.buf)?;
123         file.write_all(&self.buf)
124     }
125 }
126 
127 /// `KeyLog` implementation that opens a file whose name is
128 /// given by the `SSLKEYLOGFILE` environment variable, and writes
129 /// keys into it.
130 ///
131 /// If `SSLKEYLOGFILE` is not set, this does nothing.
132 ///
133 /// If such a file cannot be opened, or cannot be written then
134 /// this does nothing but logs errors at warning-level.
135 pub struct KeyLogFile(Mutex<KeyLogFileInner>);
136 
137 impl KeyLogFile {
138     /// Makes a new `KeyLogFile`.  The environment variable is
139     /// inspected and the named file is opened during this call.
new() -> Self140     pub fn new() -> Self {
141         let var = env::var("SSLKEYLOGFILE");
142         KeyLogFile(Mutex::new(KeyLogFileInner::new(var)))
143     }
144 }
145 
146 impl KeyLog for KeyLogFile {
log(&self, label: &str, client_random: &[u8], secret: &[u8])147     fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
148         #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
149         match self
150             .0
151             .lock()
152             .unwrap()
153             .try_write(label, client_random, secret)
154         {
155             Ok(()) => {}
156             Err(e) => {
157                 warn!("error writing to key log file: {}", e);
158             }
159         }
160     }
161 }
162 
163 #[cfg(all(test, target_os = "linux"))]
164 mod test {
165     use super::*;
166 
init()167     fn init() {
168         let _ = env_logger::builder()
169             .is_test(true)
170             .try_init();
171     }
172 
173     #[test]
test_env_var_is_not_unicode()174     fn test_env_var_is_not_unicode() {
175         init();
176         let mut inner = KeyLogFileInner::new(Err(env::VarError::NotUnicode(
177             "/tmp/keylogfileinnertest".into(),
178         )));
179         assert!(
180             inner
181                 .try_write("label", b"random", b"secret")
182                 .is_ok()
183         );
184     }
185 
186     #[test]
test_env_var_is_not_set()187     fn test_env_var_is_not_set() {
188         init();
189         let mut inner = KeyLogFileInner::new(Err(env::VarError::NotPresent));
190         assert!(
191             inner
192                 .try_write("label", b"random", b"secret")
193                 .is_ok()
194         );
195     }
196 
197     #[test]
test_env_var_cannot_be_opened()198     fn test_env_var_cannot_be_opened() {
199         init();
200         let mut inner = KeyLogFileInner::new(Ok("/dev/does-not-exist".into()));
201         assert!(
202             inner
203                 .try_write("label", b"random", b"secret")
204                 .is_ok()
205         );
206     }
207 
208     #[test]
test_env_var_cannot_be_written()209     fn test_env_var_cannot_be_written() {
210         init();
211         let mut inner = KeyLogFileInner::new(Ok("/dev/full".into()));
212         assert!(
213             inner
214                 .try_write("label", b"random", b"secret")
215                 .is_err()
216         );
217     }
218 }
219