feat: new encryption modes, minor code quality
This commit is contained in:
parent
cdba76bcf9
commit
03fd1a6787
|
@ -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"] }
|
||||||
|
|
|
@ -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}",
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
warn!("VUDP: Received encyrpted voice data, but no encryption key, CANNOT DECRYPT!");
|
match err {
|
||||||
return;
|
VoiceUdpError::NoKey => {
|
||||||
}
|
warn!("VUDP: Received encyrpted voice data, but no encryption key, CANNOT DECRYPT!");
|
||||||
|
return;
|
||||||
let session_description = session_description_result.unwrap();
|
}
|
||||||
|
VoiceUdpError::FailedDecryption => {
|
||||||
let nonce_bytes = match session_description.encryption_mode {
|
warn!("VUDP: Failed to decrypt voice data!");
|
||||||
crate::types::VoiceEncryptionMode::Xsalsa20Poly1305 => {
|
return;
|
||||||
get_xsalsa20_poly1305_nonce(rtp.packet())
|
}
|
||||||
|
_ => {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue