feat: new encryption modes, minor code quality

This commit is contained in:
kozabrada123 2024-01-12 16:45:56 +01:00
parent 4573e605c9
commit 996cc1910b
10 changed files with 191 additions and 39 deletions

View File

@ -69,6 +69,7 @@ tokio-tungstenite = { version = "0.20.1", features = [
] } ] }
native-tls = "0.2.11" native-tls = "0.2.11"
hostname = "0.3.1" hostname = "0.3.1"
getrandom = { version = "0.2.11" }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.11", features = ["js"] } getrandom = { version = "0.2.11", features = ["js"] }

View File

@ -142,6 +142,8 @@ custom_error! {
// Encryption errors // Encryption errors
NoKey = "Tried to encrypt / decrypt rtp data, but no key has been received yet", NoKey = "Tried to encrypt / decrypt rtp data, but no key has been received yet",
FailedEncryption = "Tried to encrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new", FailedEncryption = "Tried to encrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new",
FailedDecryption = "Tried to decrypt rtp data, but failed. Most likely this is an issue chorus' nonce generation. Please open an issue on the chorus github: https://github.com/polyphony-chat/chorus/issues/new",
FailedNonceGeneration{error: String} = "Tried to generate nonce, but failed due to error: {error}.",
// Errors when initiating a socket connection // Errors when initiating a socket connection
CannotBind{error: String} = "Cannot bind socket due to a udp error: {error}", CannotBind{error: String} = "Cannot bind socket due to a udp error: {error}",

View File

@ -128,7 +128,10 @@ pub mod instance;
#[cfg(feature = "client")] #[cfg(feature = "client")]
pub mod ratelimiter; pub mod ratelimiter;
pub mod types; pub mod types;
#[cfg(all(feature = "client", any(feature = "voice_udp", feature = "voice_gateway")))] #[cfg(all(
feature = "client",
any(feature = "voice_udp", feature = "voice_gateway")
))]
pub mod voice; pub mod voice;
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]

View File

@ -12,7 +12,9 @@ use serde::{Deserialize, Serialize};
pub struct VoiceClientConnectFlags { pub struct VoiceClientConnectFlags {
pub user_id: Snowflake, pub user_id: Snowflake,
// Likely some sort of bitflags // Likely some sort of bitflags
pub flags: u8, //
// Not always sent, sometimes null?
pub flags: Option<u8>,
} }
impl WebSocketEvent for VoiceClientConnectFlags {} impl WebSocketEvent for VoiceClientConnectFlags {}

View File

@ -3,6 +3,10 @@ use serde::{Deserialize, Serialize};
/// Defines an event which provides ssrcs for voice and video for a user id. /// Defines an event which provides ssrcs for voice and video for a user id.
/// ///
/// This event is sent when we begin to speak.
///
/// It must be sent before sending audio, or else clients will not be able to play the stream.
///
/// This event is sent via opcode 12. /// This event is sent via opcode 12.
/// ///
/// Examples of the event: /// Examples of the event:
@ -28,12 +32,18 @@ pub struct SsrcDefinition {
/// Is always sent and received, though is 0 if describing only the video ssrc. /// Is always sent and received, though is 0 if describing only the video ssrc.
#[serde(default)] #[serde(default)]
pub audio_ssrc: usize, pub audio_ssrc: usize,
// Not sure what this is
// It is usually 0
#[serde(default)]
pub rtx_ssrc: usize,
/// The user id these ssrcs apply to. /// The user id these ssrcs apply to.
/// ///
/// Is never sent by the user and is filled in by the server /// Is never sent by the user and is filled in by the server
#[serde(skip_serializing)] #[serde(skip_serializing)]
pub user_id: Option<Snowflake>, pub user_id: Option<Snowflake>,
// TODO: Add video streams // TODO: Add video streams
#[serde(default)]
pub streams: Vec<String>,
} }
impl WebSocketEvent for SsrcDefinition {} impl WebSocketEvent for SsrcDefinition {}

View File

@ -2,15 +2,46 @@
//! //!
//! All functions in this module return a 24 byte long [Vec<u8>]. //! All functions in this module return a 24 byte long [Vec<u8>].
/// Gets an xsalsa20poly1305 nonce from an rtppacket. use crypto_secretbox::cipher::typenum::Len;
/// Gets an xsalsa20_poly1305 nonce from an rtppacket.
///
/// See <https://discord-userdoccers.vercel.app/topics/voice-connections#encryption-mode>
pub(crate) fn get_xsalsa20_poly1305_nonce(packet: &[u8]) -> Vec<u8> { pub(crate) fn get_xsalsa20_poly1305_nonce(packet: &[u8]) -> Vec<u8> {
let mut rtp_header = Vec::with_capacity(24); let mut rtp_header = Vec::with_capacity(24);
rtp_header.append(&mut packet[0..12].to_vec()); rtp_header.append(&mut packet[0..12].to_vec());
// The header is only 12 bytes, but the nonce has to be 24 // The header is only 12 bytes, but the nonce has to be 24
for _i in 0..12 { while rtp_header.len() < 24 {
rtp_header.push(0); rtp_header.push(0);
} }
rtp_header rtp_header
} }
/// Gets an xsalsa20_poly1305_suffix nonce from an rtppacket.
///
/// See <https://discord-userdoccers.vercel.app/topics/voice-connections#encryption-mode>
pub(crate) fn get_xsalsa20_poly1305_suffix_nonce(packet: &[u8]) -> Vec<u8> {
let mut nonce = Vec::with_capacity(24);
nonce.append(&mut packet[(packet.len() - 24)..packet.len()].to_vec());
nonce
}
/// Gets an xsalsa20_poly1305_lite nonce from an rtppacket.
///
/// See <https://discord-userdoccers.vercel.app/topics/voice-connections#encryption-mode>
pub(crate) fn get_xsalsa20_poly1305_lite_nonce(packet: &[u8]) -> Vec<u8> {
let mut nonce = Vec::with_capacity(24);
nonce.append(&mut packet[(packet.len() - 4)..packet.len()].to_vec());
// The suffix is only 4 bytes, but the nonce has to be 24
while nonce.len() < 24 {
nonce.push(0);
}
nonce
}

View File

@ -1,8 +1,8 @@
//! Module for all voice functionality within chorus. //! Module for all voice functionality within chorus.
mod crypto;
#[cfg(feature = "voice_gateway")] #[cfg(feature = "voice_gateway")]
pub mod gateway; pub mod gateway;
mod crypto;
#[cfg(all(feature = "voice_udp", feature = "voice_gateway"))] #[cfg(all(feature = "voice_udp", feature = "voice_gateway"))]
pub mod handler; pub mod handler;
#[cfg(feature = "voice_udp")] #[cfg(feature = "voice_udp")]

View File

@ -5,6 +5,7 @@ use crypto_secretbox::{
}; };
use discortp::Packet; use discortp::Packet;
use getrandom::getrandom;
use log::*; use log::*;
use tokio::{sync::Mutex, sync::RwLock}; use tokio::{sync::Mutex, sync::RwLock};
@ -13,7 +14,11 @@ use super::UdpSocket;
use crate::{ use crate::{
errors::VoiceUdpError, errors::VoiceUdpError,
voice::{crypto, voice_data::VoiceData}, types::VoiceEncryptionMode,
voice::{
crypto::{self, get_xsalsa20_poly1305_nonce},
voice_data::VoiceData,
},
}; };
use super::{events::VoiceUDPEvents, RTP_HEADER_SIZE}; use super::{events::VoiceUDPEvents, RTP_HEADER_SIZE};
@ -104,6 +109,8 @@ impl UdpHandle {
/// ///
/// # Errors /// # Errors
/// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error. /// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error.
///
/// When using voice encryption modes which require special nonce generation, and said generation fails, this returns a [VoiceUdpError::FailedNonceGeneration] error.
pub async fn encrypt_rtp_packet_payload( pub async fn encrypt_rtp_packet_payload(
&self, &self,
packet: &discortp::rtp::MutableRtpPacket<'_>, packet: &discortp::rtp::MutableRtpPacket<'_>,
@ -120,7 +127,42 @@ impl UdpHandle {
let session_description = session_description_result.unwrap(); let session_description = session_description_result.unwrap();
let nonce_bytes = crypto::get_xsalsa20_poly1305_nonce(packet.packet()); let mut nonce_bytes = match session_description.encryption_mode {
VoiceEncryptionMode::Xsalsa20Poly1305 => get_xsalsa20_poly1305_nonce(packet.packet()),
VoiceEncryptionMode::Xsalsa20Poly1305Suffix => {
// Generate 24 random bytes
let mut random_destinaton: Vec<u8> = vec![0; 24];
let random_result = getrandom(&mut random_destinaton);
if let Err(e) = random_result {
return Err(VoiceUdpError::FailedNonceGeneration {
error: format!("{:?}", e),
});
}
random_destinaton
}
VoiceEncryptionMode::Xsalsa20Poly1305Lite => {
// "Incremental 4 bytes (32bit) int value"
let mut data_lock = self.data.write().await;
let nonce = data_lock
.last_udp_encryption_nonce
.unwrap_or_default()
.wrapping_add(1);
data_lock.last_udp_encryption_nonce = Some(nonce);
drop(data_lock);
// TODO: Is le correct? This is not documented anywhere
let mut bytes = nonce.to_le_bytes().to_vec();
// This is 4 bytes, it has to be 24, so we need to append 20
while bytes.len() < 24 {
bytes.push(0);
}
bytes
}
_ => {
// TODO: Implement aead_aes256_gcm
todo!("This voice encryption mode is not yet implemented.");
}
};
let nonce = GenericArray::from_slice(&nonce_bytes); let nonce = GenericArray::from_slice(&nonce_bytes);
let key = GenericArray::from_slice(&session_description.secret_key); let key = GenericArray::from_slice(&session_description.secret_key);
@ -139,6 +181,13 @@ impl UdpHandle {
let mut encrypted_payload = encryption_result.unwrap(); let mut encrypted_payload = encryption_result.unwrap();
// Append the nonce bytes, if needed
// All other encryption modes have an explicit nonce, where as Xsalsa20Poly1305
// has the nonce as the rtp header.
if session_description.encryption_mode != VoiceEncryptionMode::Xsalsa20Poly1305 {
encrypted_payload.append(&mut nonce_bytes);
}
// We need to allocate a new buffer, since the old one is too small for our new encrypted // We need to allocate a new buffer, since the old one is too small for our new encrypted
// data // data
let buffer_size = encrypted_payload.len() + RTP_HEADER_SIZE as usize; let buffer_size = encrypted_payload.len() + RTP_HEADER_SIZE as usize;

View File

@ -19,7 +19,10 @@ use super::UdpSocket;
use super::RTP_HEADER_SIZE; use super::RTP_HEADER_SIZE;
use crate::errors::VoiceUdpError; use crate::errors::VoiceUdpError;
use crate::types::VoiceEncryptionMode;
use crate::voice::crypto::get_xsalsa20_poly1305_lite_nonce;
use crate::voice::crypto::get_xsalsa20_poly1305_nonce; use crate::voice::crypto::get_xsalsa20_poly1305_nonce;
use crate::voice::crypto::get_xsalsa20_poly1305_suffix_nonce;
use crate::voice::voice_data::VoiceData; use crate::voice::voice_data::VoiceData;
use super::{events::VoiceUDPEvents, UdpHandle}; use super::{events::VoiceUDPEvents, UdpHandle};
@ -167,41 +170,24 @@ impl UdpHandler {
match parsed { match parsed {
Demuxed::Rtp(rtp) => { Demuxed::Rtp(rtp) => {
let ciphertext = buf[(RTP_HEADER_SIZE as usize)..buf.len()].to_vec(); trace!("VUDP: Parsed packet as rtp! {:?}", buf);
trace!("VUDP: Parsed packet as rtp!");
let session_description_result = self.data.read().await.session_description.clone(); let decryption_result = self.decrypt_rtp_packet_payload(&rtp).await;
if session_description_result.is_none() { if let Err(err) = decryption_result {
match err {
VoiceUdpError::NoKey => {
warn!("VUDP: Received encyrpted voice data, but no encryption key, CANNOT DECRYPT!"); warn!("VUDP: Received encyrpted voice data, but no encryption key, CANNOT DECRYPT!");
return; return;
} }
VoiceUdpError::FailedDecryption => {
let session_description = session_description_result.unwrap(); warn!("VUDP: Failed to decrypt voice data!");
return;
let nonce_bytes = match session_description.encryption_mode {
crate::types::VoiceEncryptionMode::Xsalsa20Poly1305 => {
get_xsalsa20_poly1305_nonce(rtp.packet())
} }
_ => { _ => {
unimplemented!(); unreachable!();
}
} }
};
let nonce = GenericArray::from_slice(&nonce_bytes);
let key = GenericArray::from_slice(&session_description.secret_key);
let decryptor = XSalsa20Poly1305::new(key);
let decryption_result = decryptor.decrypt(nonce, ciphertext.as_ref());
if let Err(decryption_error) = decryption_result {
warn!(
"VUDP: Failed to decypt voice data! ({:?})",
decryption_error
);
return;
} }
let decrypted = decryption_result.unwrap(); let decrypted = decryption_result.unwrap();
@ -273,4 +259,69 @@ impl UdpHandler {
} }
} }
} }
/// Decrypts an encrypted rtp packet, returning a decrypted copy of the packet's payload
/// bytes.
///
/// # Errors
/// If we have not received an encryption key, this returns a [VoiceUdpError::NoKey] error.
///
/// If the decryption fails, this returns a [VoiceUdpError::FailedDecryption].
pub async fn decrypt_rtp_packet_payload(
&self,
rtp: &discortp::rtp::RtpPacket<'_>,
) -> Result<Vec<u8>, VoiceUdpError> {
let packet_bytes = rtp.packet();
let mut ciphertext: Vec<u8> =
packet_bytes[(RTP_HEADER_SIZE as usize)..packet_bytes.len()].to_vec();
let session_description_result = self.data.read().await.session_description.clone();
// We are trying to decrypt, but have not received SessionDescription yet,
// which contains the secret key
if session_description_result.is_none() {
return Err(VoiceUdpError::NoKey);
}
let session_description = session_description_result.unwrap();
let nonce_bytes = match session_description.encryption_mode {
VoiceEncryptionMode::Xsalsa20Poly1305 => get_xsalsa20_poly1305_nonce(packet_bytes),
VoiceEncryptionMode::Xsalsa20Poly1305Suffix => {
// Remove the suffix from the ciphertext
ciphertext = ciphertext[0..ciphertext.len() - 24].to_vec();
get_xsalsa20_poly1305_suffix_nonce(packet_bytes)
}
// Note: Rtpsize is documented by userdoccers to be the same, yet decryption
// doesn't work.
//
// I have no idea how Rtpsize works.
VoiceEncryptionMode::Xsalsa20Poly1305Lite => {
// Remove the suffix from the ciphertext
ciphertext = ciphertext[0..ciphertext.len() - 4].to_vec();
get_xsalsa20_poly1305_lite_nonce(packet_bytes)
}
_ => {
// TODO: Implement aead_aes256_gcm
todo!("This voice encryption mode is not yet implemented.");
}
};
let nonce = GenericArray::from_slice(&nonce_bytes);
let key = GenericArray::from_slice(&session_description.secret_key);
let decryptor = XSalsa20Poly1305::new(key);
let decryption_result = decryptor.decrypt(nonce, ciphertext.as_ref());
// Note: this may seem like we are throwing away valuable error handling data,
// but the decryption error provides no extra info.
if decryption_result.is_err() {
return Err(VoiceUdpError::FailedDecryption);
}
Ok(decryption_result.unwrap())
}
} }

View File

@ -15,4 +15,7 @@ pub struct VoiceData {
/// The last sequence number we used, has to be incremeted by one every time we send a message /// The last sequence number we used, has to be incremeted by one every time we send a message
pub last_sequence_number: u16, pub last_sequence_number: u16,
pub ip_discovery: Option<IpDiscovery>, pub ip_discovery: Option<IpDiscovery>,
/// The last udp encryption nonce, if we are using an encryption mode with incremental nonces.
pub last_udp_encryption_nonce: Option<u32>,
} }