Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log handshake keys into a keylog file to allow WireGuard traffic decryption with Wireshark #427

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
751 changes: 347 additions & 404 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions boringtun-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: BSD-3-Clause

use boringtun::device::drop_privileges::drop_privileges;
use boringtun::device::keylog::set_keylog_file;
use boringtun::device::{DeviceConfig, DeviceHandle};
use clap::{Arg, Command};
use daemonize::Daemonize;
Expand Down Expand Up @@ -71,6 +72,11 @@ fn main() {
.env("WG_LOG_FILE")
.help("Log file")
.default_value("/tmp/boringtun.out"),
Arg::new("keylog")
.takes_value(true)
.long("keylog")
.env("WGKEYLOGFILE")
.help("WireGuard keyog file"),
Arg::new("disable-drop-privileges")
.long("disable-drop-privileges")
.env("WG_SUDO")
Expand Down Expand Up @@ -144,6 +150,22 @@ fn main() {
.init();
}

// Set keylog file if requested.
if let Some(path) = matches.get_one::<String>("keylog") {
match File::options().append(true).create(true).open(path) {
Ok(file) => {
if let Err(_err) = set_keylog_file(Box::new(file)) {
tracing::error!(message = "keylog file is already set");
exit(1);
}
}
Err(e) => {
tracing::error!(message = "Failed to open keylog file", error=?e);
exit(1);
}
}
}

let config = DeviceConfig {
n_threads,
#[cfg(target_os = "linux")]
Expand Down
2 changes: 1 addition & 1 deletion boringtun/src/device/dev_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl<'a, T: ?Sized> LockReadGuard<'a, T> {
/// this can be used to tell other threads to yield their read locks temporarily. It will be passed
/// an immutable reference to the inner value.
///
/// `mut_func` - A closure that will run once write access is gained. It iwll be passed a mutable reference
/// `mut_func` - A closure that will run once write access is gained. It will be passed a mutable reference
/// to the inner value.
///
pub fn try_writeable<U, P: FnOnce(&T), F: FnOnce(&mut T) -> U>(
Expand Down
18 changes: 18 additions & 0 deletions boringtun/src/device/keylog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::{
io,
sync::{Arc, OnceLock},
};

use crate::noise::keys_logger::KeyLogFile;

static KEYLOG: OnceLock<Arc<KeyLogFile>> = OnceLock::new();

pub fn set_keylog_file(stream: Box<dyn io::Write + Send>) -> Result<(), ()> {
KEYLOG
.set(Arc::new(KeyLogFile::new(stream)))
.map_err(|_| ())
}

pub fn get_keylog_file() -> Option<Arc<KeyLogFile>> {
KEYLOG.get().cloned()
}
9 changes: 8 additions & 1 deletion boringtun/src/device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod dev_lock;
pub mod drop_privileges;
#[cfg(test)]
mod integration_tests;
pub mod keylog;
pub mod peer;

#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos"))]
Expand Down Expand Up @@ -37,10 +38,12 @@ use std::thread::JoinHandle;

use crate::noise::errors::WireGuardError;
use crate::noise::handshake::parse_handshake_anon;
use crate::noise::keys_logger::KeysLogger;
use crate::noise::rate_limiter::RateLimiter;
use crate::noise::{Packet, Tunn, TunnResult};
use crate::x25519;
use allowed_ips::AllowedIps;
use keylog::get_keylog_file;
use parking_lot::Mutex;
use peer::{AllowedIP, Peer};
use poll::{EventPoll, EventRef, WaitResult};
Expand Down Expand Up @@ -327,7 +330,7 @@ impl Device {
.as_ref()
.expect("Private key must be set first");

let tunn = Tunn::new(
let mut tunn = Tunn::new(
device_key_pair.0.clone(),
pub_key,
preshared_key,
Expand All @@ -336,6 +339,10 @@ impl Device {
None,
);

if let Some(keylogfile) = get_keylog_file() {
tunn.set_handshake_keys_listener(Arc::new(KeysLogger::new(keylogfile)));
}

let peer = Peer::new(tunn, next_index, endpoint, allowed_ips, preshared_key);

let peer = Arc::new(Mutex::new(peer));
Expand Down
41 changes: 38 additions & 3 deletions boringtun/src/noise/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use chacha20poly1305::XChaCha20Poly1305;
use rand_core::OsRng;
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305};
use std::convert::TryInto;
use std::sync::Arc;
use std::time::{Duration, SystemTime};

#[cfg(feature = "mock-instant")]
Expand Down Expand Up @@ -261,7 +262,7 @@ struct HandshakeInitSentState {
local_index: u32,
hash: [u8; KEY_LEN],
chaining_key: [u8; KEY_LEN],
ephemeral_private: x25519::ReusableSecret,
ephemeral_private: x25519::StaticSecret,
time_sent: Instant,
}

Expand Down Expand Up @@ -308,6 +309,7 @@ pub struct Handshake {
// TODO: make TimeStamper a singleton
stamper: TimeStamper,
pub(super) last_rtt: Option<u32>,
keys_listener: Option<Arc<dyn HandshakeKeysListener>>,
}

#[derive(Default)]
Expand Down Expand Up @@ -368,6 +370,17 @@ pub fn parse_handshake_anon(
})
}

pub struct HandshakeKeys<'a> {
pub static_private: &'a [u8],
pub peer_static_public: &'a [u8],
pub ephemeral_private: &'a [u8],
pub preshared_key: Option<&'a [u8]>,
}

pub trait HandshakeKeysListener: Send + Sync {
fn publish_handshake_keys(&self, keys: HandshakeKeys<'_>);
}

impl NoiseParams {
/// New noise params struct from our secret key, peers public key, and optional preshared key
fn new(
Expand Down Expand Up @@ -431,6 +444,7 @@ impl Handshake {
stamper: TimeStamper::new(),
cookies: Default::default(),
last_rtt: None,
keys_listener: None,
}
}

Expand Down Expand Up @@ -636,6 +650,8 @@ impl Handshake {
let rtt_time = Instant::now().duration_since(state.time_sent);
self.last_rtt = Some(rtt_time.as_millis() as u32);

self.log_successful_handshake_keys(state.ephemeral_private.as_bytes());

if is_previous {
self.previous = HandshakeState::None;
} else {
Expand Down Expand Up @@ -728,7 +744,7 @@ impl Handshake {
let mut hash = INITIAL_CHAIN_HASH;
hash = b2s_hash(&hash, self.params.peer_static_public.as_bytes());
// initiator.ephemeral_private = DH_GENERATE()
let ephemeral_private = x25519::ReusableSecret::random_from_rng(OsRng);
let ephemeral_private = x25519::StaticSecret::random_from_rng(OsRng);
// msg.message_type = 1
// msg.reserved_zero = { 0, 0, 0 }
message_type.copy_from_slice(&super::HANDSHAKE_INIT.to_le_bytes());
Expand Down Expand Up @@ -814,7 +830,7 @@ impl Handshake {
let (encrypted_nothing, _) = rest.split_at_mut(16);

// responder.ephemeral_private = DH_GENERATE()
let ephemeral_private = x25519::ReusableSecret::random_from_rng(OsRng);
let ephemeral_private = x25519::StaticSecret::random_from_rng(OsRng);
let local_index = self.inc_index();
// msg.message_type = 2
// msg.reserved_zero = { 0, 0, 0 }
Expand Down Expand Up @@ -876,8 +892,27 @@ impl Handshake {

let dst = self.append_mac1_and_mac2(local_index, &mut dst[..super::HANDSHAKE_RESP_SZ])?;

self.log_successful_handshake_keys(ephemeral_private.as_bytes());

Ok((dst, Session::new(local_index, peer_index, temp2, temp3)))
}

pub fn set_handshake_keys_listener(&mut self, keys_listener: Arc<dyn HandshakeKeysListener>) {
self.keys_listener.replace(keys_listener);
}

fn log_successful_handshake_keys(&self, ephemeral_private: &[u8]) {
let Some(keys_listener) = self.keys_listener.as_ref() else {
return;
};

keys_listener.publish_handshake_keys(HandshakeKeys {
static_private: self.params.static_private.as_bytes(),
peer_static_public: self.params.peer_static_public.as_bytes(),
ephemeral_private: ephemeral_private,
preshared_key: self.params.preshared_key.as_ref().map(|x| x.as_ref()),
});
}
}

#[cfg(test)]
Expand Down
67 changes: 67 additions & 0 deletions boringtun/src/noise/keys_logger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::{io, sync::Arc};

use parking_lot::Mutex;

use super::{handshake::HandshakeKeys, HandshakeKeysListener};

pub struct KeyLogFile {
stream: Mutex<Box<dyn io::Write + Send>>,
}

impl KeyLogFile {
pub fn new(stream: Box<dyn io::Write + Send>) -> Self {
Self {
stream: Mutex::new(stream),
}
}
}

impl KeyLogger for KeyLogFile {
fn log_key(&self, name: &str, keymaterial: &str) {
let mut locked_stream = self.stream.lock();

// Errors are intentionally ignored.
let _ = writeln!(&mut *locked_stream, "{} = {}", name, keymaterial);
}
}

impl KeyLogger for Arc<KeyLogFile> {
fn log_key(&self, name: &str, keymaterial: &str) {
self.as_ref().log_key(name, keymaterial)
}
}

pub trait KeyLogger: Send + Sync {
fn log_key(&self, name: &str, keymaterial: &str);
}

pub struct KeysLogger<T> {
output: T,
}

impl<T: KeyLogger> KeysLogger<T> {
pub fn new(output: T) -> Self {
Self { output }
}

fn log_handshake_keys(&self, keys: HandshakeKeys<'_>) {
self.log_key("LOCAL_STATIC_PRIVATE_KEY", keys.static_private);
self.log_key("REMOTE_STATIC_PUBLIC_KEY", keys.peer_static_public);
self.log_key("LOCAL_EPHEMERAL_PRIVATE_KEY", keys.ephemeral_private);

if let Some(preshared_key) = keys.preshared_key.as_ref() {
self.log_key("PRESHARED_KEY", preshared_key);
}
}

fn log_key(&self, name: &str, content: &[u8]) {
let encoded = base64::encode(content);
self.output.log_key(name, &encoded);
}
}

impl<T: KeyLogger> HandshakeKeysListener for KeysLogger<T> {
fn publish_handshake_keys(&self, keys: HandshakeKeys<'_>) {
self.log_handshake_keys(keys);
}
}
7 changes: 7 additions & 0 deletions boringtun/src/noise/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

pub mod errors;
pub mod handshake;
pub mod keys_logger;
pub mod rate_limiter;

mod session;
mod timers;

use handshake::HandshakeKeysListener;

use crate::noise::errors::WireGuardError;
use crate::noise::handshake::Handshake;
use crate::noise::rate_limiter::RateLimiter;
Expand Down Expand Up @@ -241,6 +244,10 @@ impl Tunn {
}
}

pub fn set_handshake_keys_listener(&mut self, keys_listener: Arc<dyn HandshakeKeysListener>) {
self.handshake.set_handshake_keys_listener(keys_listener);
}

/// Encapsulate a single packet from the tunnel interface.
/// Returns TunnResult.
///
Expand Down