use aes::cipher::{KeyIvInit, StreamCipher};
use aes::Aes128;
type Aes128Ctr = ctr::Ctr128BE<Aes128>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CryptoSuite {
AesCm128HmacSha1_80,
AesCm128HmacSha1_32,
}
impl CryptoSuite {
pub fn parse(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"AES_CM_128_HMAC_SHA1_80" => Some(Self::AesCm128HmacSha1_80),
"AES_CM_128_HMAC_SHA1_32" => Some(Self::AesCm128HmacSha1_32),
_ => None,
}
}
pub fn auth_tag_len(&self) -> usize {
match self {
Self::AesCm128HmacSha1_80 => 10, Self::AesCm128HmacSha1_32 => 4, }
}
pub fn master_key_len(&self) -> usize {
16 }
pub fn master_salt_len(&self) -> usize {
14 }
pub fn session_key_len(&self) -> usize {
16 }
pub fn session_salt_len(&self) -> usize {
14 }
pub fn session_auth_key_len(&self) -> usize {
20 }
pub fn as_str(&self) -> &'static str {
match self {
Self::AesCm128HmacSha1_80 => "AES_CM_128_HMAC_SHA1_80",
Self::AesCm128HmacSha1_32 => "AES_CM_128_HMAC_SHA1_32",
}
}
}
impl std::fmt::Display for CryptoSuite {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum Label {
SrtpEncryption = 0x00,
SrtpAuthentication = 0x01,
SrtpSalt = 0x02,
SrtcpEncryption = 0x03,
SrtcpAuthentication = 0x04,
SrtcpSalt = 0x05,
}
#[derive(Clone)]
pub struct SessionKeys {
pub srtp_enc_key: Vec<u8>,
pub srtp_auth_key: Vec<u8>,
pub srtp_salt: Vec<u8>,
pub srtcp_enc_key: Vec<u8>,
pub srtcp_auth_key: Vec<u8>,
pub srtcp_salt: Vec<u8>,
}
impl SessionKeys {
pub fn derive(
suite: CryptoSuite,
master_key: &[u8],
master_salt: &[u8],
) -> Result<Self, String> {
if master_key.len() != suite.master_key_len() {
return Err(format!(
"Invalid master key length: {} (expected {})",
master_key.len(),
suite.master_key_len()
));
}
if master_salt.len() != suite.master_salt_len() {
return Err(format!(
"Invalid master salt length: {} (expected {})",
master_salt.len(),
suite.master_salt_len()
));
}
let srtp_enc_key = derive_key(
master_key,
master_salt,
Label::SrtpEncryption,
0,
suite.session_key_len(),
);
let srtp_auth_key = derive_key(
master_key,
master_salt,
Label::SrtpAuthentication,
0,
suite.session_auth_key_len(),
);
let srtp_salt = derive_key(
master_key,
master_salt,
Label::SrtpSalt,
0,
suite.session_salt_len(),
);
let srtcp_enc_key = derive_key(
master_key,
master_salt,
Label::SrtcpEncryption,
0,
suite.session_key_len(),
);
let srtcp_auth_key = derive_key(
master_key,
master_salt,
Label::SrtcpAuthentication,
0,
suite.session_auth_key_len(),
);
let srtcp_salt = derive_key(
master_key,
master_salt,
Label::SrtcpSalt,
0,
suite.session_salt_len(),
);
Ok(Self {
srtp_enc_key,
srtp_auth_key,
srtp_salt,
srtcp_enc_key,
srtcp_auth_key,
srtcp_salt,
})
}
}
fn derive_key(
master_key: &[u8],
master_salt: &[u8],
label: Label,
index: u64,
len: usize,
) -> Vec<u8> {
let mut x = [0u8; 16];
x[..master_salt.len()].copy_from_slice(master_salt);
x[7] ^= label as u8;
let _ = index;
let mut key_stream = vec![0u8; len.div_ceil(16) * 16];
let mut iv = [0u8; 16];
iv[..16].copy_from_slice(&x);
let mut cipher = Aes128Ctr::new(master_key.into(), &iv.into());
cipher.apply_keystream(&mut key_stream);
key_stream.truncate(len);
key_stream
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crypto_suite_parse() {
assert_eq!(
CryptoSuite::parse("AES_CM_128_HMAC_SHA1_80"),
Some(CryptoSuite::AesCm128HmacSha1_80)
);
assert_eq!(
CryptoSuite::parse("AES_CM_128_HMAC_SHA1_32"),
Some(CryptoSuite::AesCm128HmacSha1_32)
);
assert_eq!(
CryptoSuite::parse("aes_cm_128_hmac_sha1_80"),
Some(CryptoSuite::AesCm128HmacSha1_80)
);
assert_eq!(CryptoSuite::parse("UNKNOWN"), None);
}
#[test]
fn test_crypto_suite_auth_tag_len() {
assert_eq!(CryptoSuite::AesCm128HmacSha1_80.auth_tag_len(), 10);
assert_eq!(CryptoSuite::AesCm128HmacSha1_32.auth_tag_len(), 4);
}
#[test]
fn test_derive_session_keys() {
let master_key = [0u8; 16];
let master_salt = [0u8; 14];
let keys = SessionKeys::derive(CryptoSuite::AesCm128HmacSha1_80, &master_key, &master_salt)
.unwrap();
assert_eq!(keys.srtp_enc_key.len(), 16);
assert_eq!(keys.srtp_auth_key.len(), 20);
assert_eq!(keys.srtp_salt.len(), 14);
assert_eq!(keys.srtcp_enc_key.len(), 16);
assert_eq!(keys.srtcp_auth_key.len(), 20);
assert_eq!(keys.srtcp_salt.len(), 14);
assert_ne!(keys.srtp_enc_key, keys.srtcp_enc_key);
assert_ne!(keys.srtp_auth_key, keys.srtcp_auth_key);
}
#[test]
fn test_derive_invalid_key_length() {
let master_key = [0u8; 8]; let master_salt = [0u8; 14];
let result =
SessionKeys::derive(CryptoSuite::AesCm128HmacSha1_80, &master_key, &master_salt);
assert!(result.is_err());
}
#[test]
fn test_derive_invalid_salt_length() {
let master_key = [0u8; 16];
let master_salt = [0u8; 8];
let result =
SessionKeys::derive(CryptoSuite::AesCm128HmacSha1_80, &master_key, &master_salt);
assert!(result.is_err());
}
#[test]
fn test_crypto_suite_32_bit_variant() {
let suite = CryptoSuite::AesCm128HmacSha1_32;
assert_eq!(suite.auth_tag_len(), 4);
assert_eq!(suite.master_key_len(), 16);
assert_eq!(suite.master_salt_len(), 14);
assert_eq!(suite.session_key_len(), 16);
assert_eq!(suite.session_salt_len(), 14);
assert_eq!(suite.session_auth_key_len(), 20);
assert_eq!(suite.as_str(), "AES_CM_128_HMAC_SHA1_32");
}
#[test]
fn test_crypto_suite_display() {
assert_eq!(
format!("{}", CryptoSuite::AesCm128HmacSha1_80),
"AES_CM_128_HMAC_SHA1_80"
);
assert_eq!(
format!("{}", CryptoSuite::AesCm128HmacSha1_32),
"AES_CM_128_HMAC_SHA1_32"
);
}
#[test]
fn test_derive_session_keys_32bit_suite() {
let master_key = [0u8; 16];
let master_salt = [0u8; 14];
let keys = SessionKeys::derive(CryptoSuite::AesCm128HmacSha1_32, &master_key, &master_salt)
.unwrap();
assert_eq!(keys.srtp_enc_key.len(), 16);
assert_eq!(keys.srtp_auth_key.len(), 20);
assert_eq!(keys.srtp_salt.len(), 14);
assert_eq!(keys.srtcp_enc_key.len(), 16);
assert_eq!(keys.srtcp_auth_key.len(), 20);
assert_eq!(keys.srtcp_salt.len(), 14);
}
#[test]
fn test_session_keys_clone() {
let master_key = [0u8; 16];
let master_salt = [0u8; 14];
let keys = SessionKeys::derive(CryptoSuite::AesCm128HmacSha1_80, &master_key, &master_salt)
.unwrap();
let cloned = keys.clone();
assert_eq!(keys.srtp_enc_key, cloned.srtp_enc_key);
assert_eq!(keys.srtcp_salt, cloned.srtcp_salt);
}
#[test]
fn test_crypto_suite_debug_and_clone() {
let suite = CryptoSuite::AesCm128HmacSha1_80;
let cloned = suite;
assert_eq!(suite, cloned);
let debug_str = format!("{:?}", suite);
assert!(debug_str.contains("AesCm128HmacSha1_80"));
}
#[test]
fn test_label_values() {
assert_eq!(Label::SrtpEncryption as u8, 0x00);
assert_eq!(Label::SrtpAuthentication as u8, 0x01);
assert_eq!(Label::SrtpSalt as u8, 0x02);
assert_eq!(Label::SrtcpEncryption as u8, 0x03);
assert_eq!(Label::SrtcpAuthentication as u8, 0x04);
assert_eq!(Label::SrtcpSalt as u8, 0x05);
}
}