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}