use core::fmt;
pub const ZRTP_PREAMBLE: u16 = 0x505a;
pub const ZRTP_MAGIC_COOKIE: [u8; 4] = *b"ZRTP";
pub const VERSION_1_10: [u8; 4] = *b"1.10";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
PacketTooShort,
InvalidPreamble(u16),
InvalidMagicCookie([u8; 4]),
InvalidLength,
InvalidCrc { expected: u32, actual: u32 },
Truncated(&'static str),
InvalidField(&'static str),
Unsupported(&'static str),
NonWordAligned,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::PacketTooShort => write!(f, "ZRTP packet too short"),
Error::InvalidPreamble(v) => write!(f, "invalid ZRTP preamble: 0x{v:04x}"),
Error::InvalidMagicCookie(v) => write!(f, "invalid ZRTP magic cookie: {v:?}"),
Error::InvalidLength => write!(f, "invalid message length"),
Error::InvalidCrc { expected, actual } => {
write!(
f,
"invalid CRC32C: expected 0x{expected:08x}, got 0x{actual:08x}"
)
}
Error::Truncated(name) => write!(f, "truncated field: {name}"),
Error::InvalidField(name) => write!(f, "invalid field: {name}"),
Error::Unsupported(name) => write!(f, "unsupported: {name}"),
Error::NonWordAligned => write!(f, "ZRTP payload must be 32-bit word aligned"),
}
}
}
impl std::error::Error for Error {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FourCc(pub [u8; 4]);
impl FourCc {
pub const fn new(bytes: [u8; 4]) -> Self {
Self(bytes)
}
}
impl fmt::Display for FourCc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match core::str::from_utf8(&self.0) {
Ok(v) => f.write_str(v),
Err(_) => write!(f, "{:02x?}", self.0),
}
}
}
pub mod algos {
use super::FourCc;
pub const HASH_S256: FourCc = FourCc(*b"S256");
pub const HASH_S384: FourCc = FourCc(*b"S384");
pub const HASH_N256: FourCc = FourCc(*b"N256");
pub const HASH_N384: FourCc = FourCc(*b"N384");
pub const CIPHER_AES1: FourCc = FourCc(*b"AES1");
pub const CIPHER_AES2: FourCc = FourCc(*b"AES2");
pub const CIPHER_AES3: FourCc = FourCc(*b"AES3");
pub const CIPHER_2FS1: FourCc = FourCc(*b"2FS1");
pub const CIPHER_2FS2: FourCc = FourCc(*b"2FS2");
pub const CIPHER_2FS3: FourCc = FourCc(*b"2FS3");
pub const AUTH_HS32: FourCc = FourCc(*b"HS32");
pub const AUTH_HS80: FourCc = FourCc(*b"HS80");
pub const AUTH_SK32: FourCc = FourCc(*b"SK32");
pub const AUTH_SK64: FourCc = FourCc(*b"SK64");
pub const KEYAGREE_DH3K: FourCc = FourCc(*b"DH3k");
pub const KEYAGREE_DH2K: FourCc = FourCc(*b"DH2k");
pub const KEYAGREE_EC25: FourCc = FourCc(*b"EC25");
pub const KEYAGREE_EC38: FourCc = FourCc(*b"EC38");
pub const KEYAGREE_EC52: FourCc = FourCc(*b"EC52");
pub const KEYAGREE_PRSH: FourCc = FourCc(*b"Prsh");
pub const KEYAGREE_MULT: FourCc = FourCc(*b"Mult");
pub const SAS_B32: FourCc = FourCc(*b"B32 ");
pub const SAS_B256: FourCc = FourCc(*b"B256");
pub const SIG_PGP: FourCc = FourCc(*b"PGP ");
pub const SIG_X509: FourCc = FourCc(*b"X509");
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum HashAlgorithm {
Sha256,
Sha384,
NistSha3_256,
NistSha3_384,
Unknown(FourCc),
}
impl HashAlgorithm {
pub fn as_fourcc(self) -> FourCc {
match self {
Self::Sha256 => algos::HASH_S256,
Self::Sha384 => algos::HASH_S384,
Self::NistSha3_256 => algos::HASH_N256,
Self::NistSha3_384 => algos::HASH_N384,
Self::Unknown(v) => v,
}
}
}
impl From<FourCc> for HashAlgorithm {
fn from(v: FourCc) -> Self {
match v {
algos::HASH_S256 => Self::Sha256,
algos::HASH_S384 => Self::Sha384,
algos::HASH_N256 => Self::NistSha3_256,
algos::HASH_N384 => Self::NistSha3_384,
other => Self::Unknown(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CipherAlgorithm {
Aes128,
Aes192,
Aes256,
TwoFish128,
TwoFish192,
TwoFish256,
Unknown(FourCc),
}
impl CipherAlgorithm {
pub fn as_fourcc(self) -> FourCc {
match self {
Self::Aes128 => algos::CIPHER_AES1,
Self::Aes192 => algos::CIPHER_AES2,
Self::Aes256 => algos::CIPHER_AES3,
Self::TwoFish128 => algos::CIPHER_2FS1,
Self::TwoFish192 => algos::CIPHER_2FS2,
Self::TwoFish256 => algos::CIPHER_2FS3,
Self::Unknown(v) => v,
}
}
pub fn key_len(self) -> Option<usize> {
match self {
Self::Aes128 | Self::TwoFish128 => Some(16),
Self::Aes192 | Self::TwoFish192 => Some(24),
Self::Aes256 | Self::TwoFish256 => Some(32),
Self::Unknown(_) => None,
}
}
}
impl From<FourCc> for CipherAlgorithm {
fn from(v: FourCc) -> Self {
match v {
algos::CIPHER_AES1 => Self::Aes128,
algos::CIPHER_AES2 => Self::Aes192,
algos::CIPHER_AES3 => Self::Aes256,
algos::CIPHER_2FS1 => Self::TwoFish128,
algos::CIPHER_2FS2 => Self::TwoFish192,
algos::CIPHER_2FS3 => Self::TwoFish256,
other => Self::Unknown(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AuthTagType {
HmacSha1_32,
HmacSha1_80,
Skein32,
Skein64,
Unknown(FourCc),
}
impl AuthTagType {
pub fn as_fourcc(self) -> FourCc {
match self {
Self::HmacSha1_32 => algos::AUTH_HS32,
Self::HmacSha1_80 => algos::AUTH_HS80,
Self::Skein32 => algos::AUTH_SK32,
Self::Skein64 => algos::AUTH_SK64,
Self::Unknown(v) => v,
}
}
}
impl From<FourCc> for AuthTagType {
fn from(v: FourCc) -> Self {
match v {
algos::AUTH_HS32 => Self::HmacSha1_32,
algos::AUTH_HS80 => Self::HmacSha1_80,
algos::AUTH_SK32 => Self::Skein32,
algos::AUTH_SK64 => Self::Skein64,
other => Self::Unknown(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum KeyAgreement {
Dh3072,
Dh2048,
EcP256,
EcP384,
EcP521,
Preshared,
Multistream,
Unknown(FourCc),
}
impl KeyAgreement {
pub fn as_fourcc(self) -> FourCc {
match self {
Self::Dh3072 => algos::KEYAGREE_DH3K,
Self::Dh2048 => algos::KEYAGREE_DH2K,
Self::EcP256 => algos::KEYAGREE_EC25,
Self::EcP384 => algos::KEYAGREE_EC38,
Self::EcP521 => algos::KEYAGREE_EC52,
Self::Preshared => algos::KEYAGREE_PRSH,
Self::Multistream => algos::KEYAGREE_MULT,
Self::Unknown(v) => v,
}
}
pub fn pv_words(self) -> Option<usize> {
match self {
Self::Dh3072 => Some(96),
Self::Dh2048 => Some(64),
Self::EcP256 => Some(16),
Self::EcP384 => Some(24),
Self::EcP521 => Some(33),
Self::Preshared | Self::Multistream | Self::Unknown(_) => None,
}
}
}
impl From<FourCc> for KeyAgreement {
fn from(v: FourCc) -> Self {
match v {
algos::KEYAGREE_DH3K => Self::Dh3072,
algos::KEYAGREE_DH2K => Self::Dh2048,
algos::KEYAGREE_EC25 => Self::EcP256,
algos::KEYAGREE_EC38 => Self::EcP384,
algos::KEYAGREE_EC52 => Self::EcP521,
algos::KEYAGREE_PRSH => Self::Preshared,
algos::KEYAGREE_MULT => Self::Multistream,
other => Self::Unknown(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SasType {
B32,
B256,
Unknown(FourCc),
}
impl SasType {
pub fn as_fourcc(self) -> FourCc {
match self {
Self::B32 => algos::SAS_B32,
Self::B256 => algos::SAS_B256,
Self::Unknown(v) => v,
}
}
}
impl From<FourCc> for SasType {
fn from(v: FourCc) -> Self {
match v {
algos::SAS_B32 => Self::B32,
algos::SAS_B256 => Self::B256,
other => Self::Unknown(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SignatureType {
OpenPgp,
X509Ecdsa,
Unknown(FourCc),
}
impl SignatureType {
pub fn as_fourcc(self) -> FourCc {
match self {
Self::OpenPgp => algos::SIG_PGP,
Self::X509Ecdsa => algos::SIG_X509,
Self::Unknown(v) => v,
}
}
}
impl From<FourCc> for SignatureType {
fn from(v: FourCc) -> Self {
match v {
algos::SIG_PGP => Self::OpenPgp,
algos::SIG_X509 => Self::X509Ecdsa,
other => Self::Unknown(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum ErrorCode {
MalformedPacket = 0x10,
CriticalSoftwareError = 0x20,
UnsupportedVersion = 0x30,
HelloComponentsMismatch = 0x40,
HashNotSupported = 0x51,
CipherNotSupported = 0x52,
KeyAgreementNotSupported = 0x53,
AuthTagNotSupported = 0x54,
SasNotSupported = 0x55,
DhRequired = 0x56,
BadPv = 0x61,
BadHvi = 0x62,
UntrustedSasRelay = 0x63,
ConfirmHmacFailed = 0x70,
NonceReused = 0x80,
EqualZidsInHello = 0x90,
ServiceUnavailable = 0xA0,
ProtocolTimeout = 0xB0,
Unknown(u32),
}
impl ErrorCode {
pub fn as_u32(self) -> u32 {
match self {
Self::MalformedPacket => 0x10,
Self::CriticalSoftwareError => 0x20,
Self::UnsupportedVersion => 0x30,
Self::HelloComponentsMismatch => 0x40,
Self::HashNotSupported => 0x51,
Self::CipherNotSupported => 0x52,
Self::KeyAgreementNotSupported => 0x53,
Self::AuthTagNotSupported => 0x54,
Self::SasNotSupported => 0x55,
Self::DhRequired => 0x56,
Self::BadPv => 0x61,
Self::BadHvi => 0x62,
Self::UntrustedSasRelay => 0x63,
Self::ConfirmHmacFailed => 0x70,
Self::NonceReused => 0x80,
Self::EqualZidsInHello => 0x90,
Self::ServiceUnavailable => 0xA0,
Self::ProtocolTimeout => 0xB0,
Self::Unknown(v) => v,
}
}
}
impl From<u32> for ErrorCode {
fn from(v: u32) -> Self {
match v {
0x10 => Self::MalformedPacket,
0x20 => Self::CriticalSoftwareError,
0x30 => Self::UnsupportedVersion,
0x40 => Self::HelloComponentsMismatch,
0x51 => Self::HashNotSupported,
0x52 => Self::CipherNotSupported,
0x53 => Self::KeyAgreementNotSupported,
0x54 => Self::AuthTagNotSupported,
0x55 => Self::SasNotSupported,
0x56 => Self::DhRequired,
0x61 => Self::BadPv,
0x62 => Self::BadHvi,
0x63 => Self::UntrustedSasRelay,
0x70 => Self::ConfirmHmacFailed,
0x80 => Self::NonceReused,
0x90 => Self::EqualZidsInHello,
0xA0 => Self::ServiceUnavailable,
0xB0 => Self::ProtocolTimeout,
other => Self::Unknown(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct MessageType(pub [u8; 8]);
impl MessageType {
pub const HELLO: Self = Self(*b"Hello ");
pub const HELLO_ACK: Self = Self(*b"HelloACK");
pub const COMMIT: Self = Self(*b"Commit ");
pub const DHPART1: Self = Self(*b"DHPart1 ");
pub const DHPART2: Self = Self(*b"DHPart2 ");
pub const CONFIRM1: Self = Self(*b"Confirm1");
pub const CONFIRM2: Self = Self(*b"Confirm2");
pub const CONF2ACK: Self = Self(*b"Conf2ACK");
pub const ERROR: Self = Self(*b"Error ");
pub const ERROR_ACK: Self = Self(*b"ErrorACK");
pub const GOCLEAR: Self = Self(*b"GoClear ");
pub const CLEAR_ACK: Self = Self(*b"ClearACK");
pub const SASRELAY: Self = Self(*b"SASrelay");
pub const RELAY_ACK: Self = Self(*b"RelayACK");
pub const PING: Self = Self(*b"Ping ");
pub const PING_ACK: Self = Self(*b"PingACK ");
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Packet {
pub sequence: u16,
pub ssrc: u32,
pub message: Message,
}
impl Packet {
pub fn encode(&self) -> Vec<u8> {
let message = self.message.encode();
let mut out = Vec::with_capacity(12 + message.len() + 4);
out.extend_from_slice(&ZRTP_PREAMBLE.to_be_bytes());
out.extend_from_slice(&self.sequence.to_be_bytes());
out.extend_from_slice(&ZRTP_MAGIC_COOKIE);
out.extend_from_slice(&self.ssrc.to_be_bytes());
out.extend_from_slice(&message);
out.extend_from_slice(&crc32c(&out).to_be_bytes());
out
}
pub fn decode(input: &[u8]) -> Result<Self, Error> {
if input.len() < 16 {
return Err(Error::PacketTooShort);
}
let preamble = u16::from_be_bytes([input[0], input[1]]);
if preamble != ZRTP_PREAMBLE {
return Err(Error::InvalidPreamble(preamble));
}
if input[4..8] != ZRTP_MAGIC_COOKIE {
return Err(Error::InvalidMagicCookie(input[4..8].try_into().unwrap()));
}
let actual = u32::from_be_bytes(input[input.len() - 4..].try_into().unwrap());
let expected = crc32c(&input[..input.len() - 4]);
if actual != expected {
return Err(Error::InvalidCrc { expected, actual });
}
Ok(Self {
sequence: u16::from_be_bytes([input[2], input[3]]),
ssrc: u32::from_be_bytes(input[8..12].try_into().unwrap()),
message: Message::decode(&input[12..input.len() - 4])?,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Message {
Hello(Hello),
HelloAck,
Commit(Commit),
DhPart1(DhPart1),
DhPart2(DhPart2),
Confirm1(Confirm),
Confirm2(Confirm),
Conf2Ack,
Error(ErrorMessage),
ErrorAck,
GoClear(GoClear),
ClearAck,
SasRelay(SasRelay),
RelayAck,
Ping(Ping),
PingAck(PingAck),
Unknown(RawMessage),
}
impl Message {
pub fn encode(&self) -> Vec<u8> {
match self {
Self::Hello(v) => v.encode(),
Self::HelloAck => encode_header_only(MessageType::HELLO_ACK, 3),
Self::Commit(v) => v.encode(),
Self::DhPart1(v) => v.encode(),
Self::DhPart2(v) => v.encode(),
Self::Confirm1(v) => v.encode(MessageType::CONFIRM1),
Self::Confirm2(v) => v.encode(MessageType::CONFIRM2),
Self::Conf2Ack => encode_header_only(MessageType::CONF2ACK, 3),
Self::Error(v) => v.encode(),
Self::ErrorAck => encode_header_only(MessageType::ERROR_ACK, 3),
Self::GoClear(v) => v.encode(),
Self::ClearAck => encode_header_only(MessageType::CLEAR_ACK, 3),
Self::SasRelay(v) => v.encode(),
Self::RelayAck => encode_header_only(MessageType::RELAY_ACK, 3),
Self::Ping(v) => v.encode(),
Self::PingAck(v) => v.encode(),
Self::Unknown(v) => v.encode(),
}
}
pub fn decode(input: &[u8]) -> Result<Self, Error> {
let (message_type, body) = decode_message_frame(input)?;
Ok(match message_type {
MessageType::HELLO => Self::Hello(Hello::decode(body)?),
MessageType::HELLO_ACK => Self::HelloAck,
MessageType::COMMIT => Self::Commit(Commit::decode(body)?),
MessageType::DHPART1 => Self::DhPart1(DhPart1::decode(body)?),
MessageType::DHPART2 => Self::DhPart2(DhPart2::decode(body)?),
MessageType::CONFIRM1 => Self::Confirm1(Confirm::decode(body)?),
MessageType::CONFIRM2 => Self::Confirm2(Confirm::decode(body)?),
MessageType::CONF2ACK => Self::Conf2Ack,
MessageType::ERROR => Self::Error(ErrorMessage::decode(body)?),
MessageType::ERROR_ACK => Self::ErrorAck,
MessageType::GOCLEAR => Self::GoClear(GoClear::decode(body)?),
MessageType::CLEAR_ACK => Self::ClearAck,
MessageType::SASRELAY => Self::SasRelay(SasRelay::decode(body)?),
MessageType::RELAY_ACK => Self::RelayAck,
MessageType::PING => Self::Ping(Ping::decode(body)?),
MessageType::PING_ACK => Self::PingAck(PingAck::decode(body)?),
other => Self::Unknown(RawMessage {
message_type: other,
body: body.to_vec(),
}),
})
}
}
fn encode_header_only(message_type: MessageType, words: usize) -> Vec<u8> {
let mut out = Vec::with_capacity(words * 4);
out.extend_from_slice(&ZRTP_PREAMBLE.to_be_bytes());
out.extend_from_slice(&(words as u16).to_be_bytes());
out.extend_from_slice(&message_type.0);
out
}
fn push_frame(message_type: MessageType, body: &[u8]) -> Vec<u8> {
let words = 3 + body.len() / 4;
let mut out = Vec::with_capacity(words * 4);
out.extend_from_slice(&ZRTP_PREAMBLE.to_be_bytes());
out.extend_from_slice(&(words as u16).to_be_bytes());
out.extend_from_slice(&message_type.0);
out.extend_from_slice(body);
out
}
fn decode_message_frame(input: &[u8]) -> Result<(MessageType, &[u8]), Error> {
if input.len() < 12 {
return Err(Error::PacketTooShort);
}
if input.len() % 4 != 0 {
return Err(Error::NonWordAligned);
}
let preamble = u16::from_be_bytes([input[0], input[1]]);
if preamble != ZRTP_PREAMBLE {
return Err(Error::InvalidPreamble(preamble));
}
let words = u16::from_be_bytes([input[2], input[3]]) as usize;
if words * 4 != input.len() {
return Err(Error::InvalidLength);
}
Ok((MessageType(input[4..12].try_into().unwrap()), &input[12..]))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Hello {
pub version: [u8; 4],
pub client_id: [u8; 16],
pub hash_image_h3: [u8; 32],
pub zid: [u8; 12],
pub signature_capable: bool,
pub mitm_capable: bool,
pub passive_capable: bool,
pub hashes: Vec<FourCc>,
pub ciphers: Vec<FourCc>,
pub auth_tags: Vec<FourCc>,
pub key_agreements: Vec<FourCc>,
pub sas_types: Vec<FourCc>,
pub mac: [u8; 8],
}
impl Hello {
pub fn validate(&self) -> Result<(), Error> {
if self.version != VERSION_1_10 {
return Err(Error::InvalidField("version"));
}
for (name, list) in [
("hashes", &self.hashes),
("ciphers", &self.ciphers),
("auth_tags", &self.auth_tags),
("key_agreements", &self.key_agreements),
("sas_types", &self.sas_types),
] {
if list.len() > 7 {
return Err(Error::InvalidField(name));
}
}
Ok(())
}
pub fn encode(&self) -> Vec<u8> {
let _ = self.validate();
let mut body = Vec::new();
body.extend_from_slice(&self.version);
body.extend_from_slice(&self.client_id);
body.extend_from_slice(&self.hash_image_h3);
body.extend_from_slice(&self.zid);
body.push(
((self.signature_capable as u8) << 6)
| ((self.mitm_capable as u8) << 5)
| ((self.passive_capable as u8) << 4)
| (self.hashes.len() as u8 & 0x7),
);
body.push(((self.ciphers.len() as u8 & 0x7) << 4) | (self.auth_tags.len() as u8 & 0x7));
body.push(
((self.key_agreements.len() as u8 & 0x7) << 4) | (self.sas_types.len() as u8 & 0x7),
);
body.push(0);
for list in [
&self.hashes,
&self.ciphers,
&self.auth_tags,
&self.key_agreements,
&self.sas_types,
] {
for item in list {
body.extend_from_slice(&item.0);
}
}
body.extend_from_slice(&self.mac);
push_frame(MessageType::HELLO, &body)
}
pub fn decode(body: &[u8]) -> Result<Self, Error> {
if body.len() < 76 || body.len() % 4 != 0 {
return Err(Error::Truncated("Hello"));
}
let version = body[0..4].try_into().unwrap();
let client_id = body[4..20].try_into().unwrap();
let hash_image_h3 = body[20..52].try_into().unwrap();
let zid = body[52..64].try_into().unwrap();
let flags = body[64];
let hc = (flags & 0x07) as usize;
let cc = ((body[65] >> 4) & 0x07) as usize;
let ac = (body[65] & 0x07) as usize;
let kc = ((body[66] >> 4) & 0x07) as usize;
let sc = (body[66] & 0x07) as usize;
let expected = 76 + 4 * (hc + cc + ac + kc + sc);
if body.len() != expected {
return Err(Error::InvalidLength);
}
let mut off = 68;
let mut take = |n: usize| {
let start = off;
off += n * 4;
(0..n)
.map(|i| FourCc(body[start + 4 * i..start + 4 * (i + 1)].try_into().unwrap()))
.collect::<Vec<_>>()
};
let hashes = take(hc);
let ciphers = take(cc);
let auth_tags = take(ac);
let key_agreements = take(kc);
let sas_types = take(sc);
let mac = body[off..off + 8].try_into().unwrap();
Ok(Self {
version,
client_id,
hash_image_h3,
zid,
signature_capable: flags & 0x40 != 0,
mitm_capable: flags & 0x20 != 0,
passive_capable: flags & 0x10 != 0,
hashes,
ciphers,
auth_tags,
key_agreements,
sas_types,
mac,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CommitNonce {
Hvi([u8; 32]),
MultistreamNonce([u8; 16]),
PresharedNonce { nonce: [u8; 16], key_id: [u8; 8] },
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Commit {
pub hash_image_h2: [u8; 32],
pub zid: [u8; 12],
pub hash: FourCc,
pub cipher: FourCc,
pub auth_tag: FourCc,
pub key_agreement: FourCc,
pub sas: FourCc,
pub nonce: CommitNonce,
pub mac: [u8; 8],
}
impl Commit {
pub fn validate(&self) -> Result<(), Error> {
match (&self.nonce, KeyAgreement::from(self.key_agreement)) {
(CommitNonce::MultistreamNonce(_), KeyAgreement::Multistream) => Ok(()),
(CommitNonce::PresharedNonce { .. }, KeyAgreement::Preshared) => Ok(()),
(CommitNonce::Hvi(_), KeyAgreement::Multistream) => {
Err(Error::InvalidField("multistream HVI"))
}
(CommitNonce::Hvi(_), KeyAgreement::Preshared) => {
Err(Error::InvalidField("preshared HVI"))
}
(CommitNonce::MultistreamNonce(_), _) => {
Err(Error::InvalidField("nonce without multistream"))
}
(CommitNonce::PresharedNonce { .. }, _) => {
Err(Error::InvalidField("preshared nonce without preshared"))
}
(CommitNonce::Hvi(_), _) => Ok(()),
}
}
pub fn encode(&self) -> Vec<u8> {
let _ = self.validate();
let mut body = Vec::new();
body.extend_from_slice(&self.hash_image_h2);
body.extend_from_slice(&self.zid);
body.extend_from_slice(&self.hash.0);
body.extend_from_slice(&self.cipher.0);
body.extend_from_slice(&self.auth_tag.0);
body.extend_from_slice(&self.key_agreement.0);
body.extend_from_slice(&self.sas.0);
match self.nonce {
CommitNonce::Hvi(v) => body.extend_from_slice(&v),
CommitNonce::MultistreamNonce(v) => body.extend_from_slice(&v),
CommitNonce::PresharedNonce { nonce, key_id } => {
body.extend_from_slice(&nonce);
body.extend_from_slice(&key_id);
}
}
body.extend_from_slice(&self.mac);
push_frame(MessageType::COMMIT, &body)
}
pub fn decode(body: &[u8]) -> Result<Self, Error> {
if body.len() < 88 || body.len() % 4 != 0 {
return Err(Error::Truncated("Commit"));
}
let hash_image_h2 = body[0..32].try_into().unwrap();
let zid = body[32..44].try_into().unwrap();
let hash = FourCc(body[44..48].try_into().unwrap());
let cipher = FourCc(body[48..52].try_into().unwrap());
let auth_tag = FourCc(body[52..56].try_into().unwrap());
let key_agreement = FourCc(body[56..60].try_into().unwrap());
let sas = FourCc(body[60..64].try_into().unwrap());
let remaining = &body[64..];
let (nonce, mac) = match remaining.len() {
40 => (
CommitNonce::Hvi(remaining[0..32].try_into().unwrap()),
remaining[32..40].try_into().unwrap(),
),
32 => (
CommitNonce::PresharedNonce {
nonce: remaining[0..16].try_into().unwrap(),
key_id: remaining[16..24].try_into().unwrap(),
},
remaining[24..32].try_into().unwrap(),
),
24 => (
CommitNonce::MultistreamNonce(remaining[0..16].try_into().unwrap()),
remaining[16..24].try_into().unwrap(),
),
_ => return Err(Error::InvalidLength),
};
Ok(Self {
hash_image_h2,
zid,
hash,
cipher,
auth_tag,
key_agreement,
sas,
nonce,
mac,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DhPart1 {
pub hash_image_h1: [u8; 32],
pub rs1_idr: [u8; 8],
pub rs2_idr: [u8; 8],
pub auxsecret_idr: [u8; 8],
pub pbxsecret_idr: [u8; 8],
pub pvr: Vec<u8>,
pub mac: [u8; 8],
}
impl DhPart1 {
pub fn encode(&self) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&self.hash_image_h1);
body.extend_from_slice(&self.rs1_idr);
body.extend_from_slice(&self.rs2_idr);
body.extend_from_slice(&self.auxsecret_idr);
body.extend_from_slice(&self.pbxsecret_idr);
body.extend_from_slice(&self.pvr);
body.extend_from_slice(&self.mac);
push_frame(MessageType::DHPART1, &body)
}
pub fn decode(body: &[u8]) -> Result<Self, Error> {
if body.len() < 72 || body.len() % 4 != 0 {
return Err(Error::InvalidLength);
}
let pvr_len = body.len() - 72;
if pvr_len == 0 {
return Err(Error::InvalidField("pvr"));
}
Ok(Self {
hash_image_h1: body[0..32].try_into().unwrap(),
rs1_idr: body[32..40].try_into().unwrap(),
rs2_idr: body[40..48].try_into().unwrap(),
auxsecret_idr: body[48..56].try_into().unwrap(),
pbxsecret_idr: body[56..64].try_into().unwrap(),
pvr: body[64..64 + pvr_len].to_vec(),
mac: body[64 + pvr_len..72 + pvr_len].try_into().unwrap(),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DhPart2 {
pub hash_image_h1: [u8; 32],
pub rs1_idi: [u8; 8],
pub rs2_idi: [u8; 8],
pub auxsecret_idi: [u8; 8],
pub pbxsecret_idi: [u8; 8],
pub pvi: Vec<u8>,
pub mac: [u8; 8],
}
impl DhPart2 {
pub fn encode(&self) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&self.hash_image_h1);
body.extend_from_slice(&self.rs1_idi);
body.extend_from_slice(&self.rs2_idi);
body.extend_from_slice(&self.auxsecret_idi);
body.extend_from_slice(&self.pbxsecret_idi);
body.extend_from_slice(&self.pvi);
body.extend_from_slice(&self.mac);
push_frame(MessageType::DHPART2, &body)
}
pub fn decode(body: &[u8]) -> Result<Self, Error> {
if body.len() < 72 || body.len() % 4 != 0 {
return Err(Error::InvalidLength);
}
let pvi_len = body.len() - 72;
if pvi_len == 0 {
return Err(Error::InvalidField("pvi"));
}
Ok(Self {
hash_image_h1: body[0..32].try_into().unwrap(),
rs1_idi: body[32..40].try_into().unwrap(),
rs2_idi: body[40..48].try_into().unwrap(),
auxsecret_idi: body[48..56].try_into().unwrap(),
pbxsecret_idi: body[56..64].try_into().unwrap(),
pvi: body[64..64 + pvi_len].to_vec(),
mac: body[64 + pvi_len..72 + pvi_len].try_into().unwrap(),
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct ConfirmFlags {
pub disclosure: bool,
pub allow_clear: bool,
pub sas_verified: bool,
pub pbx_enrollment: bool,
}
impl ConfirmFlags {
fn nibble(self) -> u8 {
(self.disclosure as u8)
| ((self.allow_clear as u8) << 1)
| ((self.sas_verified as u8) << 2)
| ((self.pbx_enrollment as u8) << 3)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Confirm {
pub confirm_mac: [u8; 8],
pub cfb_iv: [u8; 16],
pub encrypted: Vec<u8>,
}
impl Confirm {
pub fn encode(&self, mt: MessageType) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&self.confirm_mac);
body.extend_from_slice(&self.cfb_iv);
body.extend_from_slice(&self.encrypted);
push_frame(mt, &body)
}
pub fn decode(body: &[u8]) -> Result<Self, Error> {
if body.len() < 56 || body.len() % 4 != 0 {
return Err(Error::InvalidLength);
}
Ok(Self {
confirm_mac: body[0..8].try_into().unwrap(),
cfb_iv: body[8..24].try_into().unwrap(),
encrypted: body[24..].to_vec(),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConfirmContent {
pub h0: [u8; 32],
pub signature_length_words: u16,
pub flags: ConfirmFlags,
pub cache_expiration_interval: u32,
pub signature_type: Option<SignatureType>,
pub signature: Vec<u8>,
}
impl ConfirmContent {
pub fn encode_plaintext(&self) -> Result<Vec<u8>, Error> {
let mut out = Vec::new();
out.extend_from_slice(&self.h0);
let ctl = ((self.signature_length_words as u32 & 0x01ff) << 8) | self.flags.nibble() as u32;
out.extend_from_slice(&ctl.to_be_bytes());
out.extend_from_slice(&self.cache_expiration_interval.to_be_bytes());
if let Some(sig_type) = self.signature_type {
out.extend_from_slice(&sig_type.as_fourcc().0);
}
out.extend_from_slice(&self.signature);
if out.len() % 4 != 0 {
return Err(Error::NonWordAligned);
}
Ok(out)
}
pub fn decode_plaintext(input: &[u8]) -> Result<Self, Error> {
if input.len() < 40 || input.len() % 4 != 0 {
return Err(Error::InvalidLength);
}
let h0 = input[0..32].try_into().unwrap();
let ctl = u32::from_be_bytes(input[32..36].try_into().unwrap());
let signature_length_words = ((ctl >> 8) & 0x01ff) as u16;
let flags = ConfirmFlags {
disclosure: ctl & 0x1 != 0,
allow_clear: ctl & 0x2 != 0,
sas_verified: ctl & 0x4 != 0,
pbx_enrollment: ctl & 0x8 != 0,
};
let cache_expiration_interval = u32::from_be_bytes(input[36..40].try_into().unwrap());
let sig_octets = signature_length_words as usize * 4;
let extra = input.len() - 40;
if sig_octets == 0 {
if extra != 0 {
return Err(Error::InvalidLength);
}
return Ok(Self {
h0,
signature_length_words,
flags,
cache_expiration_interval,
signature_type: None,
signature: Vec::new(),
});
}
if extra != sig_octets || sig_octets < 4 {
return Err(Error::InvalidLength);
}
let signature_type = Some(SignatureType::from(FourCc(
input[40..44].try_into().unwrap(),
)));
Ok(Self {
h0,
signature_length_words,
flags,
cache_expiration_interval,
signature_type,
signature: input[44..].to_vec(),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ErrorMessage {
pub code: ErrorCode,
}
impl ErrorMessage {
pub fn encode(&self) -> Vec<u8> {
push_frame(MessageType::ERROR, &self.code.as_u32().to_be_bytes())
}
pub fn decode(body: &[u8]) -> Result<Self, Error> {
if body.len() != 4 {
return Err(Error::InvalidLength);
}
Ok(Self {
code: ErrorCode::from(u32::from_be_bytes(body.try_into().unwrap())),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GoClear {
pub clear_hmac: [u8; 8],
}
impl GoClear {
pub fn encode(&self) -> Vec<u8> {
push_frame(MessageType::GOCLEAR, &self.clear_hmac)
}
pub fn decode(body: &[u8]) -> Result<Self, Error> {
if body.len() != 8 {
return Err(Error::InvalidLength);
}
Ok(Self {
clear_hmac: body.try_into().unwrap(),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SasRelay {
pub confirm_mac: [u8; 8],
pub cfb_iv: [u8; 16],
pub encrypted: Vec<u8>,
}
impl SasRelay {
pub fn encode(&self) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&self.confirm_mac);
body.extend_from_slice(&self.cfb_iv);
body.extend_from_slice(&self.encrypted);
push_frame(MessageType::SASRELAY, &body)
}
pub fn decode(body: &[u8]) -> Result<Self, Error> {
if body.len() < 56 || body.len() % 4 != 0 {
return Err(Error::InvalidLength);
}
Ok(Self {
confirm_mac: body[0..8].try_into().unwrap(),
cfb_iv: body[8..24].try_into().unwrap(),
encrypted: body[24..].to_vec(),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SasRelayContent {
pub h0: [u8; 32],
pub trusted_sas: [u8; 4],
pub signature_length_words: u16,
pub flags: ConfirmFlags,
pub rendering_scheme: SasType,
pub sashash: [u8; 32],
pub signature_type: Option<SignatureType>,
pub signature: Vec<u8>,
}
impl SasRelayContent {
pub fn encode_plaintext(&self) -> Result<Vec<u8>, Error> {
let mut out = Vec::new();
out.extend_from_slice(&self.h0);
out.extend_from_slice(&self.trusted_sas);
let ctl = ((self.signature_length_words as u32 & 0x01ff) << 8) | self.flags.nibble() as u32;
out.extend_from_slice(&ctl.to_be_bytes());
out.extend_from_slice(&self.rendering_scheme.as_fourcc().0);
out.extend_from_slice(&self.sashash);
if let Some(sig) = self.signature_type {
out.extend_from_slice(&sig.as_fourcc().0);
}
out.extend_from_slice(&self.signature);
if out.len() % 4 != 0 {
return Err(Error::NonWordAligned);
}
Ok(out)
}
pub fn decode_plaintext(input: &[u8]) -> Result<Self, Error> {
if input.len() < 76 || input.len() % 4 != 0 {
return Err(Error::InvalidLength);
}
let h0 = input[0..32].try_into().unwrap();
let trusted_sas = input[32..36].try_into().unwrap();
let ctl = u32::from_be_bytes(input[36..40].try_into().unwrap());
let signature_length_words = ((ctl >> 8) & 0x01ff) as u16;
let flags = ConfirmFlags {
disclosure: ctl & 0x1 != 0,
allow_clear: ctl & 0x2 != 0,
sas_verified: ctl & 0x4 != 0,
pbx_enrollment: ctl & 0x8 != 0,
};
let rendering_scheme = SasType::from(FourCc(input[40..44].try_into().unwrap()));
let sashash = input[44..76].try_into().unwrap();
let rest = &input[76..];
let sig_octets = signature_length_words as usize * 4;
if sig_octets == 0 {
if !rest.is_empty() {
return Err(Error::InvalidLength);
}
return Ok(Self {
h0,
trusted_sas,
signature_length_words,
flags,
rendering_scheme,
sashash,
signature_type: None,
signature: Vec::new(),
});
}
if rest.len() != sig_octets || sig_octets < 4 {
return Err(Error::InvalidLength);
}
Ok(Self {
h0,
trusted_sas,
signature_length_words,
flags,
rendering_scheme,
sashash,
signature_type: Some(SignatureType::from(FourCc(rest[0..4].try_into().unwrap()))),
signature: rest[4..].to_vec(),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ping {
pub version: [u8; 4],
pub endpoint_hash: [u8; 8],
}
impl Ping {
pub fn encode(&self) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&self.version);
body.extend_from_slice(&self.endpoint_hash);
push_frame(MessageType::PING, &body)
}
pub fn decode(body: &[u8]) -> Result<Self, Error> {
if body.len() != 12 {
return Err(Error::InvalidLength);
}
Ok(Self {
version: body[0..4].try_into().unwrap(),
endpoint_hash: body[4..12].try_into().unwrap(),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PingAck {
pub version: [u8; 4],
pub local_endpoint_hash: [u8; 8],
pub remote_endpoint_hash: [u8; 8],
pub received_ping_ssrc: u32,
}
impl PingAck {
pub fn encode(&self) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(&self.version);
body.extend_from_slice(&self.local_endpoint_hash);
body.extend_from_slice(&self.remote_endpoint_hash);
body.extend_from_slice(&self.received_ping_ssrc.to_be_bytes());
push_frame(MessageType::PING_ACK, &body)
}
pub fn decode(body: &[u8]) -> Result<Self, Error> {
if body.len() != 24 {
return Err(Error::InvalidLength);
}
Ok(Self {
version: body[0..4].try_into().unwrap(),
local_endpoint_hash: body[4..12].try_into().unwrap(),
remote_endpoint_hash: body[12..20].try_into().unwrap(),
received_ping_ssrc: u32::from_be_bytes(body[20..24].try_into().unwrap()),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RawMessage {
pub message_type: MessageType,
pub body: Vec<u8>,
}
impl RawMessage {
pub fn encode(&self) -> Vec<u8> {
push_frame(self.message_type, &self.body)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NegotiatedAlgorithms {
pub hash: HashAlgorithm,
pub cipher: CipherAlgorithm,
pub auth_tag: AuthTagType,
pub key_agreement: KeyAgreement,
pub sas: SasType,
}
fn with_implicit<T: Copy + PartialEq>(list: &[T], mandatory: T) -> Vec<T> {
if list.is_empty() {
vec![mandatory]
} else if list.contains(&mandatory) {
list.to_vec()
} else {
let mut out = list.to_vec();
out.push(mandatory);
out
}
}
pub fn negotiate_algorithms(local: &Hello, remote: &Hello) -> Result<NegotiatedAlgorithms, Error> {
let lh = with_implicit(&local.hashes, algos::HASH_S256);
let rh = with_implicit(&remote.hashes, algos::HASH_S256);
let lc = with_implicit(&local.ciphers, algos::CIPHER_AES1);
let rc = with_implicit(&remote.ciphers, algos::CIPHER_AES1);
let la = with_implicit(&local.auth_tags, algos::AUTH_HS32);
let ra = with_implicit(&remote.auth_tags, algos::AUTH_HS32);
let lk = with_implicit(&local.key_agreements, algos::KEYAGREE_DH3K);
let rk = with_implicit(&remote.key_agreements, algos::KEYAGREE_DH3K);
let ls = with_implicit(&local.sas_types, algos::SAS_B32);
let rs = with_implicit(&remote.sas_types, algos::SAS_B32);
let hash = rh
.iter()
.copied()
.find(|v| lh.contains(v))
.ok_or(Error::InvalidField("hash"))?;
let cipher = rc
.iter()
.copied()
.find(|v| lc.contains(v))
.ok_or(Error::InvalidField("cipher"))?;
let auth_tag = ra
.iter()
.copied()
.find(|v| la.contains(v))
.ok_or(Error::InvalidField("auth_tag"))?;
let key_agreement = rk
.iter()
.copied()
.find(|v| lk.contains(v))
.ok_or(Error::InvalidField("key_agreement"))?;
let sas = rs
.iter()
.copied()
.find(|v| ls.contains(v))
.ok_or(Error::InvalidField("sas"))?;
let hash = HashAlgorithm::from(hash);
let cipher = CipherAlgorithm::from(cipher);
let auth_tag = AuthTagType::from(auth_tag);
let key_agreement = KeyAgreement::from(key_agreement);
let sas = SasType::from(sas);
if key_agreement == KeyAgreement::EcP384
&& !matches!(hash, HashAlgorithm::Sha384 | HashAlgorithm::NistSha3_384)
{
return Err(Error::InvalidField("EC38 requires S384/N384"));
}
Ok(NegotiatedAlgorithms {
hash,
cipher,
auth_tag,
key_agreement,
sas,
})
}
pub fn zid_matches_hello_commit(hello: &Hello, commit: &Commit) -> bool {
hello.zid == commit.zid
}
pub fn hello_hash_input(hello_message_bytes: &[u8]) -> Result<Vec<u8>, Error> {
if hello_message_bytes.len() < 12 {
return Err(Error::PacketTooShort);
}
Ok(hello_message_bytes.to_vec())
}
pub fn sas_base32(sasvalue: u32) -> [char; 4] {
let alphabet = b"ybndrfg8ejkmcpqxot1uwisza345h769";
let mut out = ['\0'; 4];
for (i, shift) in [27, 22, 17, 12].into_iter().enumerate() {
out[i] = alphabet[((sasvalue >> shift) & 31) as usize] as char;
}
out
}
pub fn crc32c(input: &[u8]) -> u32 {
let mut crc = !0u32;
for &byte in input {
crc ^= byte as u32;
for _ in 0..8 {
let mask = (crc & 1).wrapping_neg();
crc = (crc >> 1) ^ (0x82F63B78 & mask);
}
}
!crc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc32c_known_vector() {
assert_eq!(crc32c(b"123456789"), 0xe306_9283);
}
#[test]
fn sas_base32_known_vector() {
assert_eq!(sas_base32(0), ['y', 'y', 'y', 'y']);
}
}