quinn_noise/
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/// This trait represents the ability to do something useful
9/// with key material, such as logging it to a file for debugging.
10///
11/// Naturally, secrets passed over the interface are *extremely*
12/// sensitive and can break the security of past, present and
13/// future sessions.
14///
15/// You'll likely want some interior mutability in your
16/// implementation to make this useful.
17///
18/// See `KeyLogFile` that implements the standard `SSLKEYLOGFILE`
19/// environment variable behaviour.
20pub trait KeyLog: Send + Sync {
21    /// Log the given `secret`.  `client_random` is provided for
22    /// session identification.  `label` describes precisely what
23    /// `secret` means:
24    ///
25    /// - `CLIENT_RANDOM`: `secret` is the master secret for a TLSv1.2 session.
26    /// - `CLIENT_EARLY_TRAFFIC_SECRET`: `secret` encrypts early data
27    ///   transmitted by a client
28    /// - `SERVER_HANDSHAKE_TRAFFIC_SECRET`: `secret` encrypts
29    ///   handshake messages from the server during a TLSv1.3 handshake.
30    /// - `CLIENT_HANDSHAKE_TRAFFIC_SECRET`: `secret` encrypts
31    ///   handshake messages from the client during a TLSv1.3 handshake.
32    /// - `SERVER_TRAFFIC_SECRET_0`: `secret` encrypts post-handshake data
33    ///   from the server in a TLSv1.3 session.
34    /// - `CLIENT_TRAFFIC_SECRET_0`: `secret` encrypts post-handshake data
35    ///   from the client in a TLSv1.3 session.
36    /// - `EXPORTER_SECRET`: `secret` is the post-handshake exporter secret
37    ///   in a TLSv1.3 session.
38    ///
39    /// These strings are selected to match the NSS key log format:
40    /// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
41    fn log(&self, label: &str, client_random: &[u8], secret: &[u8]);
42}
43
44/// `KeyLog` implementation that opens a file whose name is
45/// given by the `SSLKEYLOGFILE` environment variable, and writes
46/// keys into it.
47///
48/// If `SSLKEYLOGFILE` is not set, this does nothing.
49///
50/// If such a file cannot be opened, or cannot be written then
51/// this does nothing but logs errors at warning-level.
52pub struct KeyLogFile(Mutex<KeyLogFileInner>);
53
54impl KeyLogFile {
55    /// Makes a new `KeyLogFile`.  The environment variable is
56    /// inspected and the named file is opened during this call.
57    pub fn new() -> Self {
58        let var = env::var("SSLKEYLOGFILE");
59        KeyLogFile(Mutex::new(KeyLogFileInner::new(var)))
60    }
61}
62
63impl KeyLog for KeyLogFile {
64    fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
65        match self
66            .0
67            .lock()
68            .unwrap()
69            .try_write(label, client_random, secret)
70        {
71            Ok(()) => {}
72            Err(e) => {
73                tracing::warn!("error writing to key log file: {}", e);
74            }
75        }
76    }
77}
78
79// Internal mutable state for KeyLogFile
80struct KeyLogFileInner {
81    file: Option<File>,
82    buf: Vec<u8>,
83}
84
85impl KeyLogFileInner {
86    fn new(var: Result<String, env::VarError>) -> Self {
87        let path = match var {
88            Ok(ref s) => Path::new(s),
89            Err(env::VarError::NotUnicode(ref s)) => Path::new(s),
90            Err(env::VarError::NotPresent) => {
91                return KeyLogFileInner {
92                    file: None,
93                    buf: Vec::new(),
94                };
95            }
96        };
97
98        let file = match OpenOptions::new().append(true).create(true).open(path) {
99            Ok(f) => Some(f),
100            Err(e) => {
101                tracing::warn!("unable to create key log file {:?}: {}", path, e);
102                None
103            }
104        };
105
106        KeyLogFileInner {
107            file,
108            buf: Vec::new(),
109        }
110    }
111
112    fn try_write(&mut self, label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<()> {
113        let mut file = match self.file {
114            None => {
115                return Ok(());
116            }
117            Some(ref f) => f,
118        };
119
120        self.buf.truncate(0);
121        write!(self.buf, "{} ", label)?;
122        for b in client_random.iter() {
123            write!(self.buf, "{:02x}", b)?;
124        }
125        write!(self.buf, " ")?;
126        for b in secret.iter() {
127            write!(self.buf, "{:02x}", b)?;
128        }
129        writeln!(self.buf)?;
130        file.write_all(&self.buf)
131    }
132}
133
134#[cfg(all(test, target_os = "linux"))]
135mod test {
136    use super::*;
137
138    #[test]
139    fn test_env_var_is_not_unicode() {
140        let mut inner = KeyLogFileInner::new(Err(env::VarError::NotUnicode(
141            "/tmp/keylogfileinnertest".into(),
142        )));
143        assert!(inner.try_write("label", b"random", b"secret").is_ok());
144    }
145
146    #[test]
147    fn test_env_var_is_not_set() {
148        let mut inner = KeyLogFileInner::new(Err(env::VarError::NotPresent));
149        assert!(inner.try_write("label", b"random", b"secret").is_ok());
150    }
151
152    #[test]
153    fn test_env_var_cannot_be_opened() {
154        let mut inner = KeyLogFileInner::new(Ok("/dev/does-not-exist".into()));
155        assert!(inner.try_write("label", b"random", b"secret").is_ok());
156    }
157
158    #[test]
159    fn test_env_var_cannot_be_written() {
160        let mut inner = KeyLogFileInner::new(Ok("/dev/full".into()));
161        assert!(inner.try_write("label", b"random", b"secret").is_err());
162    }
163}