rustls/keylog.rs
1use std::env;
2use std::fs::{File, OpenOptions};
3use std::io;
4use std::io::Write;
5use std::path::Path;
6use std::sync::Mutex;
7
8#[cfg(feature = "logging")]
9use 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.
23pub 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
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.
51 fn will_log(&self, _label: &str) -> bool {
52 true
53 }
54}
55
56/// KeyLog that does exactly nothing.
57pub struct NoKeyLog;
58
59impl KeyLog for NoKeyLog {
60 fn log(&self, _: &str, _: &[u8], _: &[u8]) {}
61 #[inline]
62 fn will_log(&self, _label: &str) -> bool {
63 false
64 }
65}
66
67// Internal mutable state for KeyLogFile
68struct KeyLogFileInner {
69 file: Option<File>,
70 buf: Vec<u8>,
71}
72
73impl KeyLogFileInner {
74 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
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.
135pub struct KeyLogFile(Mutex<KeyLogFileInner>);
136
137impl KeyLogFile {
138 /// Makes a new `KeyLogFile`. The environment variable is
139 /// inspected and the named file is opened during this call.
140 pub fn new() -> Self {
141 let var = env::var("SSLKEYLOGFILE");
142 KeyLogFile(Mutex::new(KeyLogFileInner::new(var)))
143 }
144}
145
146impl KeyLog for KeyLogFile {
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"))]
164mod test {
165 use super::*;
166
167 fn init() {
168 let _ = env_logger::builder()
169 .is_test(true)
170 .try_init();
171 }
172
173 #[test]
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]
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]
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]
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}