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}