use std::sync::Arc;
use std::collections::HashMap;
use tokio::sync::RwLock;
use crate::api::common::config::{KeyExchangeMethod, SecurityConfig, SecurityMode, SrtpProfile};
use crate::api::common::error::SecurityError;
use crate::api::common::unified_security::{UnifiedSecurityContext, SecurityState, SecurityContextFactory};
use crate::api::client::security::{ClientSecurityContext, DefaultClientSecurityContext};
use crate::api::server::security::{ServerSecurityContext, DefaultServerSecurityContext};
pub struct SecurityContextManager {
contexts: Arc<RwLock<HashMap<KeyExchangeMethod, SecurityContextType>>>,
method_preference: Vec<KeyExchangeMethod>,
active_method: Arc<RwLock<Option<KeyExchangeMethod>>>,
config: SecurityConfig,
}
#[derive(Clone)]
pub enum SecurityContextType {
Unified(Arc<UnifiedSecurityContext>),
DtlsClient(Arc<dyn ClientSecurityContext>),
DtlsServer(Arc<dyn ServerSecurityContext>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NegotiationStrategy {
FirstAvailable,
PreferenceWithFallback,
Strict,
AutoDetect,
}
#[derive(Debug, Clone)]
pub struct SecurityCapabilities {
pub supported_methods: Vec<KeyExchangeMethod>,
pub can_offer: bool,
pub can_answer: bool,
pub srtp_profiles: Vec<crate::api::common::config::SrtpProfile>,
}
impl SecurityContextManager {
pub fn new(config: SecurityConfig) -> Self {
let method_preference = vec![
KeyExchangeMethod::DtlsSrtp, KeyExchangeMethod::Sdes, KeyExchangeMethod::Zrtp, KeyExchangeMethod::Mikey, KeyExchangeMethod::PreSharedKey, ];
Self {
contexts: Arc::new(RwLock::new(HashMap::new())),
method_preference,
active_method: Arc::new(RwLock::new(None)),
config,
}
}
pub fn with_method_preference(config: SecurityConfig, preference: Vec<KeyExchangeMethod>) -> Self {
Self {
contexts: Arc::new(RwLock::new(HashMap::new())),
method_preference: preference,
active_method: Arc::new(RwLock::new(None)),
config,
}
}
pub async fn initialize(&self) -> Result<(), SecurityError> {
let mut contexts = self.contexts.write().await;
for method in &self.method_preference {
match method {
KeyExchangeMethod::DtlsSrtp => {
},
KeyExchangeMethod::Sdes
| KeyExchangeMethod::Mikey
| KeyExchangeMethod::Zrtp
| KeyExchangeMethod::PreSharedKey => {
let method_config = self.create_method_config(*method)?;
match SecurityContextFactory::create_context(method_config) {
Ok(unified_context) => {
contexts.insert(*method, SecurityContextType::Unified(Arc::new(unified_context)));
},
Err(e) => {
eprintln!("Warning: Failed to initialize {} context: {}",
self.method_name(*method), e);
}
}
},
}
}
Ok(())
}
fn create_method_config(&self, method: KeyExchangeMethod) -> Result<SecurityConfig, SecurityError> {
let mut config = self.config.clone();
config.mode = method.to_security_mode();
Ok(config)
}
fn method_name(&self, method: KeyExchangeMethod) -> &'static str {
match method {
KeyExchangeMethod::DtlsSrtp => "DTLS-SRTP",
KeyExchangeMethod::Sdes => "SDES-SRTP",
KeyExchangeMethod::Mikey => "MIKEY-SRTP",
KeyExchangeMethod::Zrtp => "ZRTP-SRTP",
KeyExchangeMethod::PreSharedKey => "PSK-SRTP",
}
}
pub async fn add_dtls_client_context(&self, context: Arc<dyn ClientSecurityContext>) {
let mut contexts = self.contexts.write().await;
contexts.insert(KeyExchangeMethod::DtlsSrtp, SecurityContextType::DtlsClient(context));
}
pub async fn add_dtls_server_context(&self, context: Arc<dyn ServerSecurityContext>) {
let mut contexts = self.contexts.write().await;
contexts.insert(KeyExchangeMethod::DtlsSrtp, SecurityContextType::DtlsServer(context));
}
pub async fn start_negotiation(&self, method: KeyExchangeMethod) -> Result<(), SecurityError> {
let contexts = self.contexts.read().await;
let context = contexts.get(&method)
.ok_or_else(|| SecurityError::Configuration(format!("Method {} not available", self.method_name(method))))?;
match context {
SecurityContextType::Unified(unified) => {
unified.initialize().await?;
*self.active_method.write().await = Some(method);
},
SecurityContextType::DtlsClient(_) | SecurityContextType::DtlsServer(_) => {
*self.active_method.write().await = Some(method);
},
}
Ok(())
}
pub async fn auto_negotiate(&self, strategy: NegotiationStrategy) -> Result<KeyExchangeMethod, SecurityError> {
let contexts = self.contexts.read().await;
match strategy {
NegotiationStrategy::FirstAvailable => {
for method in &self.method_preference {
if contexts.contains_key(method) {
let selected_method = *method;
drop(contexts);
self.start_negotiation(selected_method).await?;
return Ok(selected_method);
}
}
Err(SecurityError::Configuration("No security methods available".to_string()))
},
NegotiationStrategy::PreferenceWithFallback => {
let available_methods: Vec<KeyExchangeMethod> = self.method_preference.iter()
.filter(|method| contexts.contains_key(method))
.copied()
.collect();
drop(contexts);
for method in available_methods {
match self.start_negotiation(method).await {
Ok(_) => return Ok(method),
Err(_) => {
continue;
}
}
}
Err(SecurityError::Configuration("All security methods failed".to_string()))
},
NegotiationStrategy::Strict => {
let primary_method = self.config.mode.key_exchange_method()
.ok_or_else(|| SecurityError::Configuration("No primary method configured".to_string()))?;
if contexts.contains_key(&primary_method) {
drop(contexts);
self.start_negotiation(primary_method).await?;
Ok(primary_method)
} else {
Err(SecurityError::Configuration(format!("Primary method {} not available", self.method_name(primary_method))))
}
},
NegotiationStrategy::AutoDetect => {
drop(contexts);
Box::pin(self.auto_negotiate(NegotiationStrategy::FirstAvailable)).await
},
}
}
pub async fn process_signaling(&self, data: &[u8], method: Option<KeyExchangeMethod>) -> Result<Option<Vec<u8>>, SecurityError> {
let method = match method {
Some(m) => m,
None => {
self.detect_method_from_signaling(data)?
}
};
let contexts = self.contexts.read().await;
let context = contexts.get(&method)
.ok_or_else(|| SecurityError::Configuration(format!("Method {} not available", self.method_name(method))))?;
match context {
SecurityContextType::Unified(unified) => {
unified.process_message(data).await
},
SecurityContextType::DtlsClient(_) | SecurityContextType::DtlsServer(_) => {
Err(SecurityError::Configuration("DTLS signaling should be handled by DTLS contexts".to_string()))
},
}
}
fn detect_method_from_signaling(&self, data: &[u8]) -> Result<KeyExchangeMethod, SecurityError> {
let data_str = std::str::from_utf8(data).unwrap_or("");
if data_str.contains("a=crypto:") {
Ok(KeyExchangeMethod::Sdes)
} else if data_str.contains("MIKEY") {
Ok(KeyExchangeMethod::Mikey)
} else if data_str.contains("zrtp-version") {
Ok(KeyExchangeMethod::Zrtp)
} else {
Ok(KeyExchangeMethod::Sdes)
}
}
pub async fn get_active_method(&self) -> Option<KeyExchangeMethod> {
*self.active_method.read().await
}
pub async fn is_established(&self) -> Result<bool, SecurityError> {
let active_method = self.get_active_method().await
.ok_or_else(|| SecurityError::NotInitialized("No active security method".to_string()))?;
let contexts = self.contexts.read().await;
let context = contexts.get(&active_method)
.ok_or_else(|| SecurityError::NotInitialized("Active method context not found".to_string()))?;
match context {
SecurityContextType::Unified(unified) => {
Ok(unified.is_established().await)
},
SecurityContextType::DtlsClient(client) => {
client.is_handshake_complete().await
.map_err(|e| SecurityError::CryptoError(format!("DTLS client error: {}", e)))
},
SecurityContextType::DtlsServer(server) => {
server.is_ready().await
.map_err(|e| SecurityError::CryptoError(format!("DTLS server error: {}", e)))
},
}
}
pub async fn get_capabilities(&self) -> SecurityCapabilities {
let contexts = self.contexts.read().await;
let supported_methods: Vec<KeyExchangeMethod> = contexts.keys().copied().collect();
SecurityCapabilities {
supported_methods,
can_offer: true, can_answer: true, srtp_profiles: self.config.srtp_profiles.clone(),
}
}
pub async fn create_security_offer(&self, method: KeyExchangeMethod) -> Result<Vec<String>, SecurityError> {
let contexts = self.contexts.read().await;
let context = contexts.get(&method)
.ok_or_else(|| SecurityError::Configuration(format!("Method {} not available", self.method_name(method))))?;
match context {
SecurityContextType::Unified(unified) => {
if method == KeyExchangeMethod::Sdes {
Ok(vec!["a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:placeholder".to_string()])
} else {
Err(SecurityError::Configuration("Offer generation not implemented for this method".to_string()))
}
},
SecurityContextType::DtlsClient(client) => {
let fingerprint = client.get_fingerprint().await
.map_err(|e| SecurityError::CryptoError(format!("Failed to get fingerprint: {}", e)))?;
Ok(vec![
format!("a=fingerprint:sha-256 {}", fingerprint),
"a=setup:actpass".to_string(),
])
},
SecurityContextType::DtlsServer(server) => {
let fingerprint = server.get_fingerprint().await
.map_err(|e| SecurityError::CryptoError(format!("Failed to get fingerprint: {}", e)))?;
Ok(vec![
format!("a=fingerprint:sha-256 {}", fingerprint),
"a=setup:passive".to_string(),
])
},
}
}
pub async fn list_available_methods(&self) -> Vec<KeyExchangeMethod> {
let contexts = self.contexts.read().await;
contexts.keys().copied().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::common::config::{SecurityConfig, SecurityMode, SrtpProfile};
fn test_srtp_key() -> Vec<u8> {
vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E]
}
#[tokio::test]
async fn test_create_security_manager() {
let config = SecurityConfig::sdes_srtp();
let manager = SecurityContextManager::new(config);
assert_eq!(manager.get_active_method().await, None);
}
#[tokio::test]
async fn test_initialize_manager() {
let mut config = SecurityConfig::srtp_with_key(test_srtp_key());
let manager = SecurityContextManager::new(config);
let result = manager.initialize().await;
assert!(result.is_ok());
let methods = manager.list_available_methods().await;
assert!(methods.contains(&KeyExchangeMethod::PreSharedKey));
}
#[tokio::test]
async fn test_custom_method_preference() {
let config = SecurityConfig::sdes_srtp();
let preference = vec![
KeyExchangeMethod::Sdes,
KeyExchangeMethod::PreSharedKey,
KeyExchangeMethod::DtlsSrtp,
];
let manager = SecurityContextManager::with_method_preference(config, preference);
assert_eq!(manager.method_preference[0], KeyExchangeMethod::Sdes);
assert_eq!(manager.method_preference[1], KeyExchangeMethod::PreSharedKey);
}
#[tokio::test]
async fn test_method_detection() {
let config = SecurityConfig::sdes_srtp();
let manager = SecurityContextManager::new(config);
let sdes_sdp = b"a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:test";
let detected = manager.detect_method_from_signaling(sdes_sdp).unwrap();
assert_eq!(detected, KeyExchangeMethod::Sdes);
let mikey_data = b"MIKEY message content";
let detected = manager.detect_method_from_signaling(mikey_data).unwrap();
assert_eq!(detected, KeyExchangeMethod::Mikey);
let zrtp_data = b"zrtp-version: 1.10";
let detected = manager.detect_method_from_signaling(zrtp_data).unwrap();
assert_eq!(detected, KeyExchangeMethod::Zrtp);
let unknown_data = b"random signaling data";
let detected = manager.detect_method_from_signaling(unknown_data).unwrap();
assert_eq!(detected, KeyExchangeMethod::Sdes); }
#[tokio::test]
async fn test_method_name_mapping() {
let config = SecurityConfig::sdes_srtp();
let manager = SecurityContextManager::new(config);
assert_eq!(manager.method_name(KeyExchangeMethod::DtlsSrtp), "DTLS-SRTP");
assert_eq!(manager.method_name(KeyExchangeMethod::Sdes), "SDES-SRTP");
assert_eq!(manager.method_name(KeyExchangeMethod::Mikey), "MIKEY-SRTP");
assert_eq!(manager.method_name(KeyExchangeMethod::Zrtp), "ZRTP-SRTP");
assert_eq!(manager.method_name(KeyExchangeMethod::PreSharedKey), "PSK-SRTP");
}
#[tokio::test]
async fn test_security_capabilities() {
let config = SecurityConfig::srtp_with_key(test_srtp_key());
let manager = SecurityContextManager::new(config);
manager.initialize().await.unwrap();
let capabilities = manager.get_capabilities().await;
assert!(capabilities.can_offer);
assert!(capabilities.can_answer);
assert!(!capabilities.supported_methods.is_empty());
assert!(!capabilities.srtp_profiles.is_empty());
}
#[test]
fn test_negotiation_strategy_enum() {
let _ = NegotiationStrategy::FirstAvailable;
let _ = NegotiationStrategy::PreferenceWithFallback;
let _ = NegotiationStrategy::Strict;
let _ = NegotiationStrategy::AutoDetect;
}
#[test]
fn test_security_context_type_variants() {
use std::sync::Arc;
use crate::api::common::unified_security::{SecurityContextFactory};
let unified_context = SecurityContextFactory::create_sdes_context().unwrap();
let _context_type = SecurityContextType::Unified(Arc::new(unified_context));
}
#[tokio::test]
async fn test_psk_negotiation() {
let config = SecurityConfig::srtp_with_key(test_srtp_key());
let manager = SecurityContextManager::new(config);
manager.initialize().await.unwrap();
let result = manager.start_negotiation(KeyExchangeMethod::PreSharedKey).await;
assert!(result.is_ok());
assert_eq!(manager.get_active_method().await, Some(KeyExchangeMethod::PreSharedKey));
}
#[tokio::test]
async fn test_create_method_config() {
let config = SecurityConfig::sdes_srtp();
let manager = SecurityContextManager::new(config);
let method_config = manager.create_method_config(KeyExchangeMethod::Sdes).unwrap();
assert_eq!(method_config.mode, SecurityMode::SdesSrtp);
}
#[tokio::test]
async fn test_auto_negotiate_no_methods() {
let config = SecurityConfig::sdes_srtp();
let manager = SecurityContextManager::with_method_preference(config, vec![]);
let result = manager.auto_negotiate(NegotiationStrategy::FirstAvailable).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_manager_initialization_warnings() {
let config = SecurityConfig::zrtp_p2p(); let manager = SecurityContextManager::new(config);
let result = manager.initialize().await;
assert!(result.is_ok()); }
#[test]
fn test_security_capabilities_struct() {
let capabilities = SecurityCapabilities {
supported_methods: vec![KeyExchangeMethod::Sdes],
can_offer: true,
can_answer: false,
srtp_profiles: vec![SrtpProfile::AesCm128HmacSha1_80],
};
assert_eq!(capabilities.supported_methods.len(), 1);
assert!(capabilities.can_offer);
assert!(!capabilities.can_answer);
assert_eq!(capabilities.srtp_profiles.len(), 1);
}
}