use crate::security::SecurityKeyExchange;
use crate::srtp::crypto::SrtpCryptoKey;
use crate::srtp::{SrtpCryptoSuite, SRTP_AES128_CM_SHA1_80};
use crate::Error;
use hmac::{Hmac, Mac};
use p256::ecdh::EphemeralSecret;
use p256::PublicKey;
use rand::{rngs::OsRng, RngCore};
use sha2::{Digest, Sha256};
pub mod hash;
pub mod packet;
use packet::{ZrtpMessageType, ZrtpPacket, ZrtpVersion};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZrtpCipher {
Aes1,
Aes3,
TwoF,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZrtpHash {
S256,
S384,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZrtpAuthTag {
HS32,
HS80,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZrtpKeyAgreement {
DH3k,
DH4k,
EC25,
EC38,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZrtpSasType {
B32,
B32E,
}
#[derive(Debug, Clone)]
pub struct ZrtpConfig {
pub ciphers: Vec<ZrtpCipher>,
pub hashes: Vec<ZrtpHash>,
pub auth_tags: Vec<ZrtpAuthTag>,
pub key_agreements: Vec<ZrtpKeyAgreement>,
pub sas_types: Vec<ZrtpSasType>,
pub client_id: String,
pub srtp_profile: SrtpCryptoSuite,
}
impl Default for ZrtpConfig {
fn default() -> Self {
Self {
ciphers: vec![ZrtpCipher::Aes1],
hashes: vec![ZrtpHash::S256],
auth_tags: vec![ZrtpAuthTag::HS80, ZrtpAuthTag::HS32],
key_agreements: vec![ZrtpKeyAgreement::EC25, ZrtpKeyAgreement::DH3k],
sas_types: vec![ZrtpSasType::B32],
client_id: "RVOIP ZRTP 1.0".to_string(),
srtp_profile: SRTP_AES128_CM_SHA1_80,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZrtpRole {
Initiator,
Responder,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ZrtpState {
Initial,
HelloSent,
HelloReceived,
HelloAckSent,
HelloAckReceived,
CommitSent,
CommitReceived,
DhPart1Sent,
DhPart1Received,
DhPart2Sent,
DhPart2Received,
Confirm1Sent,
Confirm1Received,
Confirm2Sent,
Confirm2Received,
ConfirmAckSent,
ConfirmAckReceived,
Completed,
Error,
}
pub struct Zrtp {
config: ZrtpConfig,
role: ZrtpRole,
state: ZrtpState,
zid: [u8; 12],
peer_zid: Option<[u8; 12]>,
hello_hash: Option<[u8; 32]>,
peer_hello_hash: Option<[u8; 32]>,
selected_cipher: Option<ZrtpCipher>,
selected_hash: Option<ZrtpHash>,
selected_auth_tag: Option<ZrtpAuthTag>,
selected_key_agreement: Option<ZrtpKeyAgreement>,
selected_sas_type: Option<ZrtpSasType>,
dh_key: Option<EphemeralSecret>,
peer_public_key: Option<PublicKey>,
shared_secret: Option<Vec<u8>>,
srtp_initiator_key: Option<SrtpCryptoKey>,
srtp_responder_key: Option<SrtpCryptoKey>,
}
impl Zrtp {
pub fn new(config: ZrtpConfig, role: ZrtpRole) -> Self {
let mut zid = [0u8; 12];
OsRng.fill_bytes(&mut zid);
Self {
config,
role,
state: ZrtpState::Initial,
zid,
peer_zid: None,
hello_hash: None,
peer_hello_hash: None,
selected_cipher: None,
selected_hash: None,
selected_auth_tag: None,
selected_key_agreement: None,
selected_sas_type: None,
dh_key: None,
peer_public_key: None,
shared_secret: None,
srtp_initiator_key: None,
srtp_responder_key: None,
}
}
fn create_hello(&mut self) -> Result<ZrtpPacket, Error> {
let mut hello = ZrtpPacket::new(ZrtpMessageType::Hello);
hello.set_version(ZrtpVersion::V12);
hello.set_client_id(&self.config.client_id);
hello.set_zid(&self.zid);
for cipher in &self.config.ciphers {
hello.add_cipher(*cipher);
}
for hash in &self.config.hashes {
hello.add_hash(*hash);
}
for auth_tag in &self.config.auth_tags {
hello.add_auth_tag(*auth_tag);
}
for key_agreement in &self.config.key_agreements {
hello.add_key_agreement(*key_agreement);
}
for sas_type in &self.config.sas_types {
hello.add_sas_type(*sas_type);
}
let hello_bytes = hello.to_bytes();
let mut hasher = Sha256::new();
hasher.update(&hello_bytes);
let mut hash = [0u8; 32];
hash.copy_from_slice(&hasher.finalize());
self.hello_hash = Some(hash);
self.state = ZrtpState::HelloSent;
Ok(hello)
}
fn process_hello(&mut self, packet: &ZrtpPacket) -> Result<ZrtpPacket, Error> {
if packet.message_type() != ZrtpMessageType::Hello {
return Err(Error::InvalidMessage("Expected Hello message".into()));
}
let peer_zid = packet
.zid()
.ok_or_else(|| Error::InvalidMessage("Missing ZID in Hello".into()))?;
self.peer_zid = Some(peer_zid);
let hello_bytes = packet.to_bytes();
let mut hasher = Sha256::new();
hasher.update(&hello_bytes);
let mut hash = [0u8; 32];
hash.copy_from_slice(&hasher.finalize());
self.peer_hello_hash = Some(hash);
for our_cipher in &self.config.ciphers {
if packet.ciphers().contains(our_cipher) {
self.selected_cipher = Some(*our_cipher);
break;
}
}
for our_hash in &self.config.hashes {
if packet.hashes().contains(our_hash) {
self.selected_hash = Some(*our_hash);
break;
}
}
for our_auth_tag in &self.config.auth_tags {
if packet.auth_tags().contains(our_auth_tag) {
self.selected_auth_tag = Some(*our_auth_tag);
break;
}
}
for our_key_agreement in &self.config.key_agreements {
if packet.key_agreements().contains(our_key_agreement) {
self.selected_key_agreement = Some(*our_key_agreement);
break;
}
}
for our_sas_type in &self.config.sas_types {
if packet.sas_types().contains(our_sas_type) {
self.selected_sas_type = Some(*our_sas_type);
break;
}
}
if self.selected_cipher.is_none()
|| self.selected_hash.is_none()
|| self.selected_auth_tag.is_none()
|| self.selected_key_agreement.is_none()
|| self.selected_sas_type.is_none()
{
return Err(Error::NegotiationFailed("No common algorithms".into()));
}
let hello_ack = ZrtpPacket::new(ZrtpMessageType::HelloAck);
self.state = ZrtpState::HelloReceived;
Ok(hello_ack)
}
fn process_hello_ack(&mut self, packet: &ZrtpPacket) -> Result<ZrtpPacket, Error> {
if packet.message_type() != ZrtpMessageType::HelloAck {
return Err(Error::InvalidMessage("Expected HelloAck message".into()));
}
let mut commit = ZrtpPacket::new(ZrtpMessageType::Commit);
commit.set_zid(&self.zid);
commit.set_cipher(self.selected_cipher.unwrap());
commit.set_hash(self.selected_hash.unwrap());
commit.set_auth_tag(self.selected_auth_tag.unwrap());
commit.set_key_agreement(self.selected_key_agreement.unwrap());
commit.set_sas_type(self.selected_sas_type.unwrap());
if self.selected_key_agreement == Some(ZrtpKeyAgreement::EC25) {
self.dh_key = Some(EphemeralSecret::random(&mut OsRng));
} else {
self.dh_key = Some(EphemeralSecret::random(&mut OsRng));
}
self.state = ZrtpState::HelloAckReceived;
Ok(commit)
}
fn process_commit(&mut self, packet: &ZrtpPacket) -> Result<ZrtpPacket, Error> {
if packet.message_type() != ZrtpMessageType::Commit {
return Err(Error::InvalidMessage("Expected Commit message".into()));
}
self.selected_cipher = Some(packet.cipher().unwrap());
self.selected_hash = Some(packet.hash().unwrap());
self.selected_auth_tag = Some(packet.auth_tag().unwrap());
self.selected_key_agreement = Some(packet.key_agreement().unwrap());
self.selected_sas_type = Some(packet.sas_type().unwrap());
if self.selected_key_agreement == Some(ZrtpKeyAgreement::EC25) {
self.dh_key = Some(EphemeralSecret::random(&mut OsRng));
} else {
self.dh_key = Some(EphemeralSecret::random(&mut OsRng));
}
let mut dh_part1 = ZrtpPacket::new(ZrtpMessageType::DHPart1);
if let Some(key) = &self.dh_key {
let public_key = PublicKey::from(key);
let public_key_bytes = public_key.to_sec1_bytes();
dh_part1.set_public_key(&public_key_bytes);
}
self.state = ZrtpState::CommitReceived;
Ok(dh_part1)
}
fn process_dh_part1(&mut self, packet: &ZrtpPacket) -> Result<ZrtpPacket, Error> {
if packet.message_type() != ZrtpMessageType::DHPart1 {
return Err(Error::InvalidMessage("Expected DHPart1 message".into()));
}
let peer_public_key_bytes = packet
.public_key()
.ok_or_else(|| Error::InvalidMessage("Missing public key in DHPart1".into()))?;
let peer_public_key = PublicKey::from_sec1_bytes(&peer_public_key_bytes)
.map_err(|_| Error::CryptoError("Invalid peer public key".into()))?;
self.peer_public_key = Some(peer_public_key);
let mut dh_part2 = ZrtpPacket::new(ZrtpMessageType::DHPart2);
if let Some(key) = &self.dh_key {
let public_key = PublicKey::from(key);
let public_key_bytes = public_key.to_sec1_bytes();
dh_part2.set_public_key(&public_key_bytes);
}
self.state = ZrtpState::DhPart1Received;
Ok(dh_part2)
}
fn process_dh_part2(&mut self, packet: &ZrtpPacket) -> Result<ZrtpPacket, Error> {
if packet.message_type() != ZrtpMessageType::DHPart2 {
return Err(Error::InvalidMessage("Expected DHPart2 message".into()));
}
let peer_public_key_bytes = packet
.public_key()
.ok_or_else(|| Error::InvalidMessage("Missing public key in DHPart2".into()))?;
let peer_public_key = PublicKey::from_sec1_bytes(&peer_public_key_bytes)
.map_err(|_| Error::CryptoError("Invalid peer public key".into()))?;
self.peer_public_key = Some(peer_public_key);
if let (Some(dh_key), Some(peer_key)) = (&self.dh_key, &self.peer_public_key) {
let shared_secret = dh_key.diffie_hellman(peer_key);
let shared_secret_bytes = shared_secret.raw_secret_bytes().to_vec();
self.shared_secret = Some(shared_secret_bytes.clone());
self.derive_srtp_keys()?;
} else {
return Err(Error::CryptoError("Missing keys for DH exchange".into()));
}
let mut confirm1 = ZrtpPacket::new(ZrtpMessageType::Confirm1);
confirm1.set_zid(&self.zid);
if let Some(shared_secret) = &self.shared_secret {
let mut mac = Hmac::<Sha256>::new_from_slice(shared_secret)
.map_err(|_| Error::CryptoError("Failed to create HMAC".into()))?;
mac.update(&self.zid);
if let Some(peer_zid) = &self.peer_zid {
mac.update(peer_zid);
}
let mac_result = mac.finalize().into_bytes();
confirm1.set_mac(&mac_result);
}
self.state = ZrtpState::DhPart2Received;
Ok(confirm1)
}
fn process_confirm1(&mut self, packet: &ZrtpPacket) -> Result<ZrtpPacket, Error> {
if packet.message_type() != ZrtpMessageType::Confirm1 {
return Err(Error::InvalidMessage("Expected Confirm1 message".into()));
}
if let (Some(shared_secret), Some(mac)) = (&self.shared_secret, packet.mac()) {
let mut expected_mac = Hmac::<Sha256>::new_from_slice(shared_secret)
.map_err(|_| Error::CryptoError("Failed to create HMAC".into()))?;
if let Some(peer_zid) = &self.peer_zid {
expected_mac.update(peer_zid);
}
expected_mac.update(&self.zid);
expected_mac
.verify_slice(&mac)
.map_err(|_| Error::AuthenticationFailed("ZRTP MAC verification failed".into()))?;
} else {
return Err(Error::CryptoError("Missing shared secret or MAC".into()));
}
let mut confirm2 = ZrtpPacket::new(ZrtpMessageType::Confirm2);
confirm2.set_zid(&self.zid);
if let Some(shared_secret) = &self.shared_secret {
let mut mac = Hmac::<Sha256>::new_from_slice(shared_secret)
.map_err(|_| Error::CryptoError("Failed to create HMAC".into()))?;
mac.update(&self.zid);
if let Some(peer_zid) = &self.peer_zid {
mac.update(peer_zid);
}
let mac_result = mac.finalize().into_bytes();
confirm2.set_mac(&mac_result);
}
self.state = ZrtpState::Confirm1Received;
Ok(confirm2)
}
fn process_confirm2(&mut self, packet: &ZrtpPacket) -> Result<ZrtpPacket, Error> {
if packet.message_type() != ZrtpMessageType::Confirm2 {
return Err(Error::InvalidMessage("Expected Confirm2 message".into()));
}
if let (Some(shared_secret), Some(mac)) = (&self.shared_secret, packet.mac()) {
let mut expected_mac = Hmac::<Sha256>::new_from_slice(shared_secret)
.map_err(|_| Error::CryptoError("Failed to create HMAC".into()))?;
if let Some(peer_zid) = &self.peer_zid {
expected_mac.update(peer_zid);
}
expected_mac.update(&self.zid);
expected_mac
.verify_slice(&mac)
.map_err(|_| Error::AuthenticationFailed("ZRTP MAC verification failed".into()))?;
} else {
return Err(Error::CryptoError("Missing shared secret or MAC".into()));
}
let conf2_ack = ZrtpPacket::new(ZrtpMessageType::Conf2Ack);
self.state = ZrtpState::Confirm2Received;
Ok(conf2_ack)
}
fn process_conf2_ack(&mut self, packet: &ZrtpPacket) -> Result<(), Error> {
if packet.message_type() != ZrtpMessageType::Conf2Ack {
return Err(Error::InvalidMessage("Expected Conf2Ack message".into()));
}
self.state = ZrtpState::Completed;
Ok(())
}
fn derive_srtp_keys(&mut self) -> Result<(), Error> {
if let Some(shared_secret) = &self.shared_secret {
let mut hasher = Sha256::new();
hasher.update(b"Initiator SRTP master key");
hasher.update(shared_secret);
let initiator_key_hash = hasher.finalize();
let initiator_key = initiator_key_hash[0..16].to_vec();
let mut hasher = Sha256::new();
hasher.update(b"Initiator SRTP master salt");
hasher.update(shared_secret);
let initiator_salt_hash = hasher.finalize();
let initiator_salt = initiator_salt_hash[0..14].to_vec();
self.srtp_initiator_key = Some(SrtpCryptoKey::new(initiator_key, initiator_salt));
let mut hasher = Sha256::new();
hasher.update(b"Responder SRTP master key");
hasher.update(shared_secret);
let responder_key_hash = hasher.finalize();
let responder_key = responder_key_hash[0..16].to_vec();
let mut hasher = Sha256::new();
hasher.update(b"Responder SRTP master salt");
hasher.update(shared_secret);
let responder_salt_hash = hasher.finalize();
let responder_salt = responder_salt_hash[0..14].to_vec();
self.srtp_responder_key = Some(SrtpCryptoKey::new(responder_key, responder_salt));
Ok(())
} else {
Err(Error::CryptoError(
"Missing shared secret for key derivation".into(),
))
}
}
pub fn generate_sas(&self) -> Result<String, Error> {
if !self.is_complete() {
return Err(Error::InvalidState("ZRTP exchange not complete".into()));
}
let shared_secret = self
.shared_secret
.as_ref()
.ok_or_else(|| Error::CryptoError("No shared secret available".into()))?;
let hello_hash_i = self
.hello_hash
.as_ref()
.ok_or_else(|| Error::CryptoError("Missing local Hello hash".into()))?;
let hello_hash_r = self
.peer_hello_hash
.as_ref()
.ok_or_else(|| Error::CryptoError("Missing peer Hello hash".into()))?;
let mut sas_input = Vec::new();
sas_input.extend_from_slice(shared_secret);
match self.role {
ZrtpRole::Initiator => {
sas_input.extend_from_slice(hello_hash_i); sas_input.extend_from_slice(hello_hash_r); }
ZrtpRole::Responder => {
sas_input.extend_from_slice(hello_hash_r); sas_input.extend_from_slice(hello_hash_i); }
}
let mut hasher = Sha256::new();
hasher.update(&sas_input);
let sas_hash = hasher.finalize();
let sas_type = self
.selected_sas_type
.ok_or_else(|| Error::InvalidState("No SAS type selected".into()))?;
match sas_type {
ZrtpSasType::B32 => {
let sas_value =
u32::from_be_bytes([sas_hash[0], sas_hash[1], sas_hash[2], sas_hash[3]])
& 0x000FFFFF;
let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
let mut sas = String::new();
let mut value = sas_value;
for _ in 0..4 {
let index = (value & 0x1F) as usize;
sas.insert(0, charset.chars().nth(index).unwrap());
value >>= 5;
}
Ok(sas)
}
ZrtpSasType::B32E => {
let sas_value =
u32::from_be_bytes([sas_hash[0], sas_hash[1], sas_hash[2], sas_hash[3]])
& 0x000FFFFF;
Ok(format!("{:04}", sas_value % 10000))
}
}
}
pub fn verify_sas(&self, user_sas: &str) -> Result<bool, Error> {
let generated_sas = self.generate_sas()?;
Ok(generated_sas.eq_ignore_ascii_case(user_sas))
}
pub fn get_sas_display(&self) -> Result<String, Error> {
let sas = self.generate_sas()?;
let sas_type = self
.selected_sas_type
.ok_or_else(|| Error::InvalidState("No SAS type selected".into()))?;
match sas_type {
ZrtpSasType::B32 => Ok(format!(
"SAS: {} (Read aloud: \"{}\")",
sas,
sas.chars()
.map(|c| c.to_string())
.collect::<Vec<String>>()
.join(" ")
)),
ZrtpSasType::B32E => Ok(format!(
"SAS: {} (Read as: \"{}\")",
sas,
sas.chars()
.map(|c| c.to_string())
.collect::<Vec<String>>()
.join(" ")
)),
}
}
}
impl SecurityKeyExchange for Zrtp {
fn init(&mut self) -> Result<(), Error> {
match self.role {
ZrtpRole::Initiator => {
let _ = self.create_hello()?;
Ok(())
}
ZrtpRole::Responder => {
Ok(())
}
}
}
fn process_message(&mut self, message: &[u8]) -> Result<Option<Vec<u8>>, Error> {
let packet = ZrtpPacket::parse(message)
.map_err(|_| Error::ParseError("Failed to parse ZRTP packet".into()))?;
let response = match (self.role, &self.state) {
(ZrtpRole::Initiator, ZrtpState::HelloSent) => {
if packet.message_type() == ZrtpMessageType::Hello {
let hello_ack = self.process_hello(&packet)?;
self.state = ZrtpState::HelloAckSent;
Ok(Some(hello_ack.to_bytes()))
} else {
Err(Error::InvalidMessage("Expected Hello message".into()))
}
}
(ZrtpRole::Initiator, ZrtpState::HelloAckSent) => {
let dh_part1 = self.process_commit(&packet)?;
self.state = ZrtpState::DhPart1Sent;
Ok(Some(dh_part1.to_bytes()))
}
(ZrtpRole::Initiator, ZrtpState::DhPart1Sent) => {
let confirm1 = self.process_dh_part2(&packet)?;
self.state = ZrtpState::Confirm1Sent;
Ok(Some(confirm1.to_bytes()))
}
(ZrtpRole::Initiator, ZrtpState::Confirm1Sent) => {
let conf2_ack = self.process_confirm2(&packet)?;
self.state = ZrtpState::ConfirmAckSent;
Ok(Some(conf2_ack.to_bytes()))
}
(ZrtpRole::Responder, ZrtpState::Initial) => {
let hello = self.create_hello()?;
let hello_bytes = hello.to_bytes();
let hello_ack = self.process_hello(&packet)?;
let hello_ack_bytes = hello_ack.to_bytes();
let mut combined = Vec::new();
combined.extend_from_slice(&hello_bytes);
combined.extend_from_slice(&hello_ack_bytes);
Ok(Some(combined))
}
(ZrtpRole::Responder, ZrtpState::HelloReceived) => {
let commit = self.process_hello_ack(&packet)?;
self.state = ZrtpState::CommitSent;
Ok(Some(commit.to_bytes()))
}
(ZrtpRole::Responder, ZrtpState::CommitSent) => {
let dh_part2 = self.process_dh_part1(&packet)?;
self.state = ZrtpState::DhPart2Sent;
Ok(Some(dh_part2.to_bytes()))
}
(ZrtpRole::Responder, ZrtpState::DhPart2Sent) => {
let confirm2 = self.process_confirm1(&packet)?;
self.state = ZrtpState::Confirm2Sent;
Ok(Some(confirm2.to_bytes()))
}
(ZrtpRole::Responder, ZrtpState::Confirm2Sent) => {
self.process_conf2_ack(&packet)?;
self.state = ZrtpState::Completed;
Ok(None)
}
_ => Err(Error::InvalidState(format!(
"Invalid state {:?} for message processing",
self.state
))),
};
response
}
fn get_srtp_key(&self) -> Option<SrtpCryptoKey> {
match self.role {
ZrtpRole::Initiator => self.srtp_initiator_key.clone(),
ZrtpRole::Responder => self.srtp_responder_key.clone(),
}
}
fn get_srtp_suite(&self) -> Option<SrtpCryptoSuite> {
Some(self.config.srtp_profile.clone())
}
fn is_complete(&self) -> bool {
self.state == ZrtpState::Completed
}
}
#[cfg(test)]
mod tests;