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