Skip to content

Commit

Permalink
feat: v8 encryption modes (#264)
Browse files Browse the repository at this point in the history
This PR adds support for the new AEAD cryptosystems advertised by Discord, AES256-GCM and XChaCha20Poly1305. These schemes will shortly become mandatory, and provider stronger integrity/authentication guarantees over the cleartext portions of any voice packet by correctly specifying additional authenticated data.

To provide smooth switchover, we've added basic negotiation over the `CryptoMode`. This ensures that any clients who are manually specifying one of the legacy modes will automatically migrate to `Aes256Gcm` when Discord cease to advertise their original preference.

Closes #246.

---------

Co-authored-by: Kyle Simpson <[email protected]>
  • Loading branch information
tignear and FelixMcFelix authored Nov 11, 2024
1 parent fe9b156 commit 10ce458
Show file tree
Hide file tree
Showing 17 changed files with 703 additions and 171 deletions.
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ rust-version = "1.74"
version = "0.4.3"

[dependencies]
aead = { optional = true, version = "0.5.2" }
aes-gcm = { optional = true, version = "0.10.3" }
async-trait = { optional = true, version = "0.1" }
audiopus = { optional = true, version = "0.3.0-rc.0" }
byteorder = { optional = true, version = "1" }
bytes = { optional = true, version = "1" }
chacha20poly1305 = { optional = true, version = "0.10.1" }
crypto_secretbox = { optional = true, features = ["std"], version = "0.1" }
dashmap = { optional = true, version = "5" }
derivative = "2"
Expand Down Expand Up @@ -52,6 +55,7 @@ tracing-futures = "0.2"
twilight-gateway = { default-features = false, optional = true, version = "0.15.0" }
twilight-model = { default-features = false, optional = true, version = "0.15.0" }
typemap_rev = { optional = true, version = "0.3" }
typenum = { optional = true, version = "1.17.0" }
url = { optional = true, version = "2" }
uuid = { features = ["v4"], optional = true, version = "1" }

Expand Down Expand Up @@ -81,10 +85,13 @@ gateway = [
"tokio?/time",
]
driver = [
"dep:aead",
"dep:aes-gcm",
"dep:async-trait",
"dep:audiopus",
"dep:byteorder",
"dep:bytes",
"dep:chacha20poly1305",
"dep:crypto_secretbox",
"dep:discortp",
"dep:flume",
Expand All @@ -107,6 +114,7 @@ driver = [
"dep:tokio-tungstenite",
"dep:tokio-util",
"dep:typemap_rev",
"dep:typenum",
"dep:url",
"dep:uuid",
"tokio?/fs",
Expand Down
2 changes: 0 additions & 2 deletions benches/mixing-task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ use songbird::{
};
use std::{io::Cursor, net::UdpSocket, sync::Arc};
use tokio::runtime::{Handle, Runtime};
use xsalsa20poly1305::{KeyInit, XSalsa20Poly1305 as Cipher, KEY_SIZE};

fn no_passthrough(c: &mut Criterion) {
let rt = Runtime::new().unwrap();

Expand Down
6 changes: 3 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ pub struct Config {
#[cfg(feature = "driver")]
/// Selected tagging mode for voice packet encryption.
///
/// Defaults to [`CryptoMode::Normal`].
/// Defaults to [`CryptoMode::Aes256Gcm`].
///
/// Changes to this field will not immediately apply if the
/// driver is actively connected, but will apply to subsequent
/// sessions.
///
/// [`CryptoMode::Normal`]: CryptoMode::Normal
/// [`CryptoMode::Aes256Gcm`]: CryptoMode::Aes256Gcm
pub crypto_mode: CryptoMode,

#[cfg(all(feature = "driver", feature = "receive"))]
Expand Down Expand Up @@ -211,7 +211,7 @@ impl Default for Config {
fn default() -> Self {
Self {
#[cfg(feature = "driver")]
crypto_mode: CryptoMode::Normal,
crypto_mode: CryptoMode::Aes256Gcm,
#[cfg(all(feature = "driver", feature = "receive"))]
decode_mode: DecodeMode::Decrypt,
#[cfg(all(feature = "driver", feature = "receive"))]
Expand Down
2 changes: 1 addition & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub const RTP_VERSION: u8 = 2;
pub const RTP_PROFILE_TYPE: RtpType = RtpType::Dynamic(120);

#[cfg(test)]
#[allow(clippy::doc_markdown)]
#[allow(clippy::doc_markdown, missing_docs)]
pub mod test_data {
/// URL for a source which YTDL must extract.
///
Expand Down
2 changes: 1 addition & 1 deletion src/driver/bench_internals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

pub use super::tasks::{message as task_message, mixer};

pub use super::crypto::CryptoState;
pub use super::crypto::{Cipher, CryptoState};

use crate::{
driver::tasks::message::TrackContext,
Expand Down
39 changes: 16 additions & 23 deletions src/driver/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod error;
#[cfg(feature = "receive")]
use super::tasks::udp_rx;
use super::{
crypto::Cipher,
tasks::{
message::*,
ws::{self as ws_task, AuxNetwork},
Expand All @@ -20,7 +21,6 @@ use crate::{
ws::WsStream,
ConnectionInfo,
};
use crypto_secretbox::{KeyInit, XSalsa20Poly1305 as Cipher};
use discortp::discord::{IpDiscoveryPacket, IpDiscoveryType, MutableIpDiscoveryPacket};
use error::{Error, Result};
use flume::Sender;
Expand Down Expand Up @@ -103,9 +103,12 @@ impl Connection {
let ready =
ready.expect("Ready packet expected in connection initialisation, but not found.");

if !has_valid_mode(&ready.modes, config.crypto_mode) {
return Err(Error::CryptoModeUnavailable);
}
let chosen_crypto = CryptoMode::negotiate(&ready.modes, Some(config.crypto_mode))?;

info!(
"Crypto scheme negotiation -- wanted {:?}. Chose {:?} from modes {:?}.",
config.crypto_mode, chosen_crypto, ready.modes
);

let udp = UdpSocket::bind("0.0.0.0:0").await?;

Expand All @@ -115,7 +118,7 @@ impl Connection {
} else {
let socket = Socket::from(udp.into_std()?);

// Some operating systems does not allow setting the recv buffer to 0.
// Some operating systems do not allow setting the recv buffer to 0.
#[cfg(any(target_os = "linux", target_os = "windows"))]
socket.set_recv_buffer_size(0)?;

Expand Down Expand Up @@ -159,28 +162,25 @@ impl Connection {
let address_str = std::str::from_utf8(&view.get_address_raw()[..nul_byte_index])
.map_err(|_| Error::IllegalIp)?;

let address = IpAddr::from_str(address_str).map_err(|e| {
println!("{e:?}");
Error::IllegalIp
})?;
let address = IpAddr::from_str(address_str).map_err(|_| Error::IllegalIp)?;

client
.send_json(&GatewayEvent::from(SelectProtocol {
protocol: "udp".into(),
data: ProtocolData {
address,
mode: config.crypto_mode.to_request_str().into(),
mode: chosen_crypto.to_request_str().into(),
port: view.get_port(),
},
}))
.await?;
}

let cipher = init_cipher(&mut client, config.crypto_mode).await?;
let cipher = init_cipher(&mut client, chosen_crypto).await?;

info!("Connected to: {}", info.endpoint);

info!("WS heartbeat duration {}ms.", hello.heartbeat_interval,);
info!("WS heartbeat duration {}ms.", hello.heartbeat_interval);

let (ws_msg_tx, ws_msg_rx) = flume::unbounded();
#[cfg(feature = "receive")]
Expand Down Expand Up @@ -209,7 +209,7 @@ impl Connection {
cipher: cipher.clone(),
#[cfg(not(feature = "receive"))]
cipher,
crypto_state: config.crypto_mode.into(),
crypto_state: chosen_crypto.into(),
#[cfg(feature = "receive")]
udp_rx: udp_receiver_msg_tx,
udp_tx,
Expand Down Expand Up @@ -244,6 +244,7 @@ impl Connection {
interconnect.clone(),
udp_receiver_msg_rx,
cipher,
chosen_crypto,
config.clone(),
udp_rx,
ssrc_tracker,
Expand Down Expand Up @@ -349,7 +350,8 @@ async fn init_cipher(client: &mut WsStream, mode: CryptoMode) -> Result<Cipher>
return Err(Error::CryptoModeInvalid);
}

return Cipher::new_from_slice(&desc.secret_key)
return mode
.cipher_from_key(&desc.secret_key)
.map_err(|_| Error::CryptoInvalidLength);
},
other => {
Expand All @@ -362,12 +364,3 @@ async fn init_cipher(client: &mut WsStream, mode: CryptoMode) -> Result<Cipher>
}
}
}

#[inline]
fn has_valid_mode<T, It>(modes: It, mode: CryptoMode) -> bool
where
T: for<'a> PartialEq<&'a str>,
It: IntoIterator<Item = T>,
{
modes.into_iter().any(|s| s == mode.to_request_str())
}
Loading

0 comments on commit 10ce458

Please sign in to comment.