add support for session encryption

This commit is contained in:
joeyahines committed 2026-10-24 20:11:53 -06:00
1 parent ce112f31a1
commit 6a5df9054c
8 files changed
+138 -10

No files matched your search

Generated
+52
View File
@@ -504,6 +504,30 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "aws-lc-rs"
version = "1.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faac5829c2b74c28f830747e7818ccfb684261b5f48a1118b1e2a13d36dfab13"
dependencies = [
"aws-lc-sys",
"untrusted",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1622d8446a2d4b2ce0c7eefc73dd43a99779028d5ee5c2dd8073a658ba8a2bc"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
"pkg-config",
]
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.10.0" version = "0.10.0"
@@ -988,6 +1012,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]] [[package]]
name = "ecolor" name = "ecolor"
version = "0.36.2" version = "0.36.2"
@@ -1374,6 +1404,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.34" version = "0.3.34"
@@ -3527,6 +3563,7 @@ dependencies = [
"clap", "clap",
"cpal", "cpal",
"env_logger", "env_logger",
"hex",
"log", "log",
"tap_core", "tap_core",
] ]
@@ -3546,9 +3583,11 @@ dependencies = [
name = "tap_core" name = "tap_core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"aws-lc-rs",
"chrono", "chrono",
"circular-buffer", "circular-buffer",
"cpal", "cpal",
"hex",
"log", "log",
"opus", "opus",
"opusic-sys", "opusic-sys",
@@ -3565,6 +3604,7 @@ dependencies = [
"eframe", "eframe",
"egui", "egui",
"env_logger", "env_logger",
"hex",
"log", "log",
"tap_core", "tap_core",
"thiserror 2.0.21", "thiserror 2.0.21",
@@ -3802,6 +3842,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.8" version = "2.5.8"
@@ -4947,6 +4993,12 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zeroize"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
[[package]] [[package]]
name = "zerotrie" name = "zerotrie"
version = "0.2.3" version = "0.2.3"
+1
View File
@@ -9,6 +9,7 @@ log = "0.4.27"
env_logger = "0.11.8" env_logger = "0.11.8"
tap_core = {path = "tap_core"} tap_core = {path = "tap_core"}
cpal = "0.18.2" cpal = "0.18.2"
hex = "0.4.3"
[workspace] [workspace]
members = ["tap_core", "tap_gui"] members = ["tap_core", "tap_gui"]
+22 -2
View File
@@ -23,6 +23,8 @@ enum Tasks {
src: SocketAddr, src: SocketAddr,
/// UDP socket addr to connect to /// UDP socket addr to connect to
dest: SocketAddr, dest: SocketAddr,
/// Session key to use
session_key: String,
}, },
} }
@@ -36,7 +38,12 @@ fn main() {
let host = cpal::default_host(); let host = cpal::default_host();
match opt.command { match opt.command {
Tasks::Tap { device, src, dest } => { Tasks::Tap {
device,
src,
dest,
session_key,
} => {
let udp_connection = match OpusUdpConnection::new(src, dest) { let udp_connection = match OpusUdpConnection::new(src, dest) {
Ok(c) => c, Ok(c) => c,
Err(err) => { Err(err) => {
@@ -49,7 +56,20 @@ fn main() {
let audio_source_thread = AudioSourceThread::new(device, host, receiver); let audio_source_thread = AudioSourceThread::new(device, host, receiver);
if let Err(err) = audio_source_thread.worker(udp_connection) { let key = match hex::decode(session_key) {
Ok(key) => key,
Err(err) => {
error!("Failed to parse key: {err}");
return;
}
};
if key.len() != 32 {
error!("Invalid session key length");
return;
}
if let Err(err) = audio_source_thread.worker(udp_connection, &key) {
error!("Error running tap: {err}"); error!("Error running tap: {err}");
} }
} }
+2
View File
@@ -13,3 +13,5 @@ tap-interface = {version = "0.1.0", registry = "ahines"}
thiserror = "2.0.21" thiserror = "2.0.21"
opus= { version="0.4.0" } opus= { version="0.4.0" }
opusic-sys = { version = "0.7.5", features = ["bundled", "no-stack-protector", "no-hardening", "no-fortify-source"] } opusic-sys = { version = "0.7.5", features = ["bundled", "no-stack-protector", "no-hardening", "no-fortify-source"] }
aws-lc-rs = "1.18.2"
hex = "0.4.3"
+6 -2
View File
@@ -105,10 +105,14 @@ impl AudioSourceThread {
Ok(stream) Ok(stream)
} }
pub fn worker<T: Read + Write + Send + 'static>(mut self, connection: T) -> Result<(), Error> { pub fn worker<T: Read + Write + Send + 'static>(
mut self,
connection: T,
key: &[u8],
) -> Result<(), Error> {
let encoder = Self::setup_encoder()?; let encoder = Self::setup_encoder()?;
let (sender, receiver) = channel(); let (sender, receiver) = channel();
let tx_thread = TxThread::new(receiver, encoder, connection); let tx_thread = TxThread::new(receiver, encoder, connection, key);
let thread = thread::spawn(move || tx_thread.thread()); let thread = thread::spawn(move || tx_thread.thread());
let mut stream = self.setup_stream(&sender)?; let mut stream = self.setup_stream(&sender)?;
+29 -3
View File
@@ -1,5 +1,7 @@
use crate::util::STEREO_FRAME_SIZE; use crate::util::STEREO_FRAME_SIZE;
use crate::util::{CIRC_BUFFER_SIZE, TAP_PACKET_MAX_DATA_LEN, ThreadMessage}; use crate::util::{CIRC_BUFFER_SIZE, TAP_PACKET_MAX_DATA_LEN, ThreadMessage};
use aws_lc_rs::aead::{AES_256_GCM, Aad, RandomizedNonceKey};
use aws_lc_rs::error::Unspecified;
use chrono::Utc; use chrono::Utc;
use circular_buffer::FixedCircularBuffer; use circular_buffer::FixedCircularBuffer;
use log::{error, info, warn}; use log::{error, info, warn};
@@ -18,6 +20,8 @@ pub enum Error {
IoFailure(#[from] std::io::Error), IoFailure(#[from] std::io::Error),
#[error("Encode error: {0}")] #[error("Encode error: {0}")]
EncodeFailure(#[from] opus::Error), EncodeFailure(#[from] opus::Error),
#[error("Failed to encrypt data")]
EncryptionFailed(#[from] Unspecified),
} }
pub struct TxThread<T: Read + Write> { pub struct TxThread<T: Read + Write> {
@@ -27,10 +31,16 @@ pub struct TxThread<T: Read + Write> {
circular_buffer: FixedCircularBuffer<f32, CIRC_BUFFER_SIZE>, circular_buffer: FixedCircularBuffer<f32, CIRC_BUFFER_SIZE>,
seq_count: u32, seq_count: u32,
exit: bool, exit: bool,
key: RandomizedNonceKey,
} }
impl<T: Read + Write> TxThread<T> { impl<T: Read + Write> TxThread<T> {
pub fn new(receiver: Receiver<ThreadMessage>, encoder: Encoder, opus_connection: T) -> Self { pub fn new(
receiver: Receiver<ThreadMessage>,
encoder: Encoder,
opus_connection: T,
key: &[u8],
) -> Self {
Self { Self {
receiver, receiver,
encoder, encoder,
@@ -38,6 +48,7 @@ impl<T: Read + Write> TxThread<T> {
circular_buffer: Default::default(), circular_buffer: Default::default(),
seq_count: 0, seq_count: 0,
exit: false, exit: false,
key: RandomizedNonceKey::new(&AES_256_GCM, key).unwrap(),
} }
} }
@@ -46,6 +57,18 @@ impl<T: Read + Write> TxThread<T> {
Ok(self.encoder.encode_float(&encode_buffer, output_buffer)?) Ok(self.encoder.encode_float(&encode_buffer, output_buffer)?)
} }
fn encrypt_data(&self, frame: &[u8]) -> Result<Vec<u8>, Error> {
let mut enc_data = frame.to_vec();
let nonce = self
.key
.seal_in_place_append_tag(Aad::empty(), &mut enc_data)?;
let mut msg = nonce.as_ref().to_vec();
msg.append(&mut enc_data);
Ok(msg)
}
fn send_opus_frame(&mut self) -> Result<(), Error> { fn send_opus_frame(&mut self) -> Result<(), Error> {
let mut output_buffer = vec![0; TAP_PACKET_MAX_DATA_LEN]; let mut output_buffer = vec![0; TAP_PACKET_MAX_DATA_LEN];
let size = self.encode(&mut output_buffer)?; let size = self.encode(&mut output_buffer)?;
@@ -58,8 +81,11 @@ impl<T: Read + Write> TxThread<T> {
audio_data: output_buffer, audio_data: output_buffer,
}; };
self.opus_connection let tap_message = tap_message.encode_to_vec();
.write_all(tap_message.encode_to_vec().as_slice())?;
let enc_message = self.encrypt_data(&tap_message)?;
self.opus_connection.write_all(enc_message.as_slice())?;
(self.seq_count, _) = self.seq_count.overflowing_add(1); (self.seq_count, _) = self.seq_count.overflowing_add(1);
Ok(()) Ok(())
+1
View File
@@ -12,3 +12,4 @@ egui = "0.36.2"
eframe = "0.36.2" eframe = "0.36.2"
tokio = { version = "1.53.2", features = ["rt", "rt-multi-thread", "macros"] } tokio = { version = "1.53.2", features = ["rt", "rt-multi-thread", "macros"] }
thiserror = "2.0.21" thiserror = "2.0.21"
hex = "0.4.3"
+25 -3
View File
@@ -31,6 +31,8 @@ pub struct Tap {
device: String, device: String,
denny_jack_host: String, denny_jack_host: String,
warning: String, warning: String,
key: String,
decoded_key: Vec<u8>,
join_handle: Option<JoinHandle<Result<(), ThreadError>>>, join_handle: Option<JoinHandle<Result<(), ThreadError>>>,
channel: Option<std::sync::mpsc::Sender<Commands>>, channel: Option<std::sync::mpsc::Sender<Commands>>,
host: cpal::Host, host: cpal::Host,
@@ -49,6 +51,8 @@ impl Tap {
device: "".to_string(), device: "".to_string(),
denny_jack_host: "158.69.121.46:9999".to_string(), denny_jack_host: "158.69.121.46:9999".to_string(),
warning: "".to_string(), warning: "".to_string(),
key: "".to_string(),
decoded_key: vec![],
join_handle: None, join_handle: None,
channel: None, channel: None,
host: cpal::default_host(), host: cpal::default_host(),
@@ -58,6 +62,7 @@ impl Tap {
pub fn bg_threads( pub fn bg_threads(
device: &str, device: &str,
host: &str, host: &str,
key: &[u8],
channel: Receiver<Commands>, channel: Receiver<Commands>,
) -> Result<(), ThreadError> { ) -> Result<(), ThreadError> {
let udp_connection = let udp_connection =
@@ -72,7 +77,7 @@ impl Tap {
let audio_source_thread = AudioSourceThread::new(device.to_string(), host, channel); let audio_source_thread = AudioSourceThread::new(device.to_string(), host, channel);
// Run until completion // Run until completion
audio_source_thread.worker(udp_connection)?; audio_source_thread.worker(udp_connection, key)?;
Ok(()) Ok(())
} }
@@ -89,6 +94,9 @@ impl App for Tap {
State::Init => { State::Init => {
ui.label("Denny Jack Host"); ui.label("Denny Jack Host");
ui.text_edit_singleline(&mut self.denny_jack_host); ui.text_edit_singleline(&mut self.denny_jack_host);
ui.label("Denny Jack Session Key");
ui.text_edit_singleline(&mut self.key);
egui::containers::ComboBox::from_label("Device") egui::containers::ComboBox::from_label("Device")
.selected_text(&self.device) .selected_text(&self.device)
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
@@ -102,11 +110,21 @@ impl App for Tap {
}); });
if ui.button("Connect").clicked() { if ui.button("Connect").clicked() {
if self.device.is_empty() || self.denny_jack_host.is_empty() { if self.device.is_empty()
|| self.denny_jack_host.is_empty()
|| self.key.is_empty()
{
self.warning = "Enter connection details and try again".to_string(); self.warning = "Enter connection details and try again".to_string();
} else { } else {
if let Ok(key) = hex::decode(&self.key)
&& key.len() == 32
{
self.warning = "".to_string(); self.warning = "".to_string();
self.mode = State::Connecting; self.mode = State::Connecting;
self.decoded_key = key;
} else {
self.warning = "Invalid session key".to_string()
}
} }
} }
@@ -121,7 +139,10 @@ impl App for Tap {
let (sender, receiver) = channel(); let (sender, receiver) = channel();
let join = thread::spawn(move || Self::bg_threads(&device, &host, receiver)); let decoded_key = self.decoded_key.clone();
let join = thread::spawn(move || {
Self::bg_threads(&device, &host, &decoded_key, receiver)
});
self.join_handle = Some(join); self.join_handle = Some(join);
self.channel = Some(sender); self.channel = Some(sender);
@@ -148,6 +169,7 @@ impl App for Tap {
let _ = handle.join(); let _ = handle.join();
self.join_handle = None; self.join_handle = None;
self.mode = State::Init; self.mode = State::Init;
self.decoded_key = vec![];
} }
} }
} }