use crate::srtp::{
SrtpCryptoSuite, SrtpCryptoKey, SrtpEncryptionAlgorithm, SrtpAuthenticationAlgorithm,
SRTP_AES128_CM_SHA1_80, SRTP_AES128_CM_SHA1_32, SRTP_AEAD_AES_128_GCM, SRTP_AEAD_AES_256_GCM,
SRTP_NULL_NULL
};
use crate::api::common::config::SrtpProfile;
use crate::api::common::error::MediaTransportError;
#[derive(Debug, Clone)]
pub struct SrtpConfig {
pub profile: SrtpProfile,
pub master_key: Option<Vec<u8>>,
pub master_salt: Option<Vec<u8>>,
pub key_derivation_rate: u64,
pub allow_unprotected: bool,
}
impl Default for SrtpConfig {
fn default() -> Self {
Self {
profile: SrtpProfile::AesCm128HmacSha1_80,
master_key: None,
master_salt: None,
key_derivation_rate: 0,
allow_unprotected: false,
}
}
}
impl SrtpConfig {
pub fn with_profile(profile: SrtpProfile) -> Self {
Self {
profile,
..Default::default()
}
}
pub fn with_key_material(mut self, key: Vec<u8>, salt: Vec<u8>) -> Self {
self.master_key = Some(key);
self.master_salt = Some(salt);
self
}
pub fn with_key_derivation_rate(mut self, rate: u64) -> Self {
self.key_derivation_rate = rate;
self
}
pub fn allow_unprotected(mut self, allow: bool) -> Self {
self.allow_unprotected = allow;
self
}
pub fn to_crypto_suite(&self) -> SrtpCryptoSuite {
match self.profile {
SrtpProfile::AesCm128HmacSha1_80 => SRTP_AES128_CM_SHA1_80,
SrtpProfile::AesCm128HmacSha1_32 => SRTP_AES128_CM_SHA1_32,
SrtpProfile::AesGcm128 => SRTP_AEAD_AES_128_GCM,
SrtpProfile::AesGcm256 => SRTP_AEAD_AES_256_GCM,
}
}
pub fn to_crypto_key(&self) -> Result<SrtpCryptoKey, MediaTransportError> {
match (&self.master_key, &self.master_salt) {
(Some(key), Some(salt)) => {
let key_clone = key.clone();
let salt_clone = salt.clone();
Ok(SrtpCryptoKey::new(key_clone, salt_clone))
},
_ => Err(MediaTransportError::ConfigError(
"Missing SRTP master key or salt".to_string()
)),
}
}
pub fn to_base64_keysalt(&self) -> Result<String, MediaTransportError> {
match (&self.master_key, &self.master_salt) {
(Some(key), Some(salt)) => {
let mut combined = Vec::with_capacity(key.len() + salt.len());
combined.extend_from_slice(key);
combined.extend_from_slice(salt);
Ok(base64::encode(&combined))
},
_ => Err(MediaTransportError::ConfigError(
"Missing SRTP master key or salt".to_string()
)),
}
}
pub fn from_base64(data: &str) -> Result<Self, MediaTransportError> {
let decoded = base64::decode(data)
.map_err(|e| MediaTransportError::ConfigError(
format!("Failed to decode base64 key: {}", e)
))?;
if decoded.len() < 16 {
return Err(MediaTransportError::ConfigError(
"Key material too short".to_string()
));
}
let key = decoded[0..16].to_vec();
let salt = if decoded.len() > 16 {
decoded[16..].to_vec()
} else {
Vec::new()
};
Ok(Self {
profile: SrtpProfile::AesCm128HmacSha1_80, master_key: Some(key),
master_salt: Some(salt),
key_derivation_rate: 0,
allow_unprotected: false,
})
}
pub fn get_crypto_name(&self) -> &'static str {
match self.profile {
SrtpProfile::AesCm128HmacSha1_80 => "AES_CM_128_HMAC_SHA1_80",
SrtpProfile::AesCm128HmacSha1_32 => "AES_CM_128_HMAC_SHA1_32",
SrtpProfile::AesGcm128 => "AEAD_AES_128_GCM",
SrtpProfile::AesGcm256 => "AEAD_AES_256_GCM",
}
}
pub fn to_sdp_crypto_line(&self) -> Result<String, MediaTransportError> {
let base64_key = self.to_base64_keysalt()?;
let crypto_name = self.get_crypto_name();
if self.key_derivation_rate > 0 {
Ok(format!("1 {} inline:{} KDR={}", crypto_name, base64_key, self.key_derivation_rate))
} else {
Ok(format!("1 {} inline:{}", crypto_name, base64_key))
}
}
pub fn from_sdp_crypto_line(line: &str) -> Result<Self, MediaTransportError> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 3 {
return Err(MediaTransportError::ConfigError(
"Invalid crypto line format".to_string()
));
}
let profile = match parts[1] {
"AES_CM_128_HMAC_SHA1_80" => SrtpProfile::AesCm128HmacSha1_80,
"AES_CM_128_HMAC_SHA1_32" => SrtpProfile::AesCm128HmacSha1_32,
"AEAD_AES_128_GCM" => SrtpProfile::AesGcm128,
"AEAD_AES_256_GCM" => SrtpProfile::AesGcm256,
_ => return Err(MediaTransportError::ConfigError(
format!("Unsupported crypto suite: {}", parts[1])
)),
};
let key_parts: Vec<&str> = parts[2].split(':').collect();
if key_parts.len() < 2 || key_parts[0] != "inline" {
return Err(MediaTransportError::ConfigError(
"Invalid key format".to_string()
));
}
let base64_key = key_parts[1].split('|').next().unwrap_or("");
let mut kdr = 0;
for part in &parts[3..] {
if part.starts_with("KDR=") {
if let Ok(value) = part[4..].parse::<u64>() {
kdr = value;
}
}
}
let mut config = Self::from_base64(base64_key)?;
config.profile = profile;
config.key_derivation_rate = kdr;
Ok(config)
}
}
#[derive(Debug, Clone)]
pub struct SrtpContextInfo {
pub profile: SrtpProfile,
pub algorithm_name: String,
pub key_size: usize,
pub salt_size: usize,
pub is_ready: bool,
}
pub fn create_srtp_context(config: &SrtpConfig) -> Result<crate::srtp::SrtpContext, MediaTransportError> {
let suite = config.to_crypto_suite();
let key = config.to_crypto_key()?;
crate::srtp::SrtpContext::new(suite, key)
.map_err(|e| MediaTransportError::Security(format!("Failed to create SRTP context: {}", e)))
}
pub fn get_srtp_context_info(context: &crate::srtp::SrtpContext) -> SrtpContextInfo {
let (profile, algorithm_name) = match context.get_crypto_params() {
(alg, auth) => {
match (alg, auth) {
(SrtpEncryptionAlgorithm::AesCm128, SrtpAuthenticationAlgorithm::HmacSha1_80) =>
(SrtpProfile::AesCm128HmacSha1_80, "AES_CM_128_HMAC_SHA1_80"),
(SrtpEncryptionAlgorithm::AesCm128, SrtpAuthenticationAlgorithm::HmacSha1_32) =>
(SrtpProfile::AesCm128HmacSha1_32, "AES_CM_128_HMAC_SHA1_32"),
(SrtpEncryptionAlgorithm::AesGcm128, _) =>
(SrtpProfile::AesGcm128, "AEAD_AES_128_GCM"),
(SrtpEncryptionAlgorithm::AesGcm256, _) =>
(SrtpProfile::AesGcm256, "AEAD_AES_256_GCM"),
_ => (SrtpProfile::AesCm128HmacSha1_80, "UNKNOWN"),
}
}
};
let key_size = context.get_key_size();
let salt_size = context.get_salt_size();
SrtpContextInfo {
profile,
algorithm_name: algorithm_name.to_string(),
key_size,
salt_size,
is_ready: key_size > 0 && salt_size > 0,
}
}