use crate::cache::RetainedSecrets;
use crate::wire::{CipherAlgorithm, Confirm, ConfirmContent, Error, HashAlgorithm, SasType};
#[cfg(feature = "crypto")]
use aes::{Aes128, Aes192, Aes256};
#[cfg(feature = "crypto")]
use base256::{WL_PGP_ENCODE_THREE_SYLLABLE, WL_PGP_ENCODE_TWO_SYLLABLE};
#[cfg(feature = "crypto")]
use cfb_mode::cipher::{AsyncStreamCipher, KeyIvInit};
#[cfg(feature = "crypto")]
use hmac::{Hmac, Mac};
#[cfg(feature = "crypto")]
use num_bigint::BigUint;
#[cfg(feature = "crypto")]
use num_traits::identities::One;
#[cfg(feature = "crypto")]
use rand::{rngs::OsRng, RngCore};
#[cfg(feature = "crypto")]
use sha2::{Digest, Sha256, Sha384};
#[cfg(feature = "crypto")]
use zeroize::Zeroizing;
#[cfg(feature = "crypto")]
type HmacSha256 = Hmac<Sha256>;
#[cfg(feature = "crypto")]
type HmacSha384 = Hmac<Sha384>;
#[cfg(feature = "crypto")]
type Aes128CfbEnc = cfb_mode::Encryptor<Aes128>;
#[cfg(feature = "crypto")]
type Aes192CfbEnc = cfb_mode::Encryptor<Aes192>;
#[cfg(feature = "crypto")]
type Aes256CfbEnc = cfb_mode::Encryptor<Aes256>;
#[cfg(feature = "crypto")]
type Aes128CfbDec = cfb_mode::Decryptor<Aes128>;
#[cfg(feature = "crypto")]
type Aes192CfbDec = cfb_mode::Decryptor<Aes192>;
#[cfg(feature = "crypto")]
type Aes256CfbDec = cfb_mode::Decryptor<Aes256>;
#[cfg(feature = "crypto")]
type SecretBytes = Zeroizing<Vec<u8>>;
#[cfg(not(feature = "crypto"))]
type SecretBytes = Vec<u8>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HashChain {
pub h0: [u8; 32],
pub h1: [u8; 32],
pub h2: [u8; 32],
pub h3: [u8; 32],
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SessionSecrets {
pub s0: Vec<u8>,
pub confirm_key_initiator: Vec<u8>,
pub confirm_key_responder: Vec<u8>,
pub confirm_mac_key_initiator: Vec<u8>,
pub confirm_mac_key_responder: Vec<u8>,
pub zrtp_session_key: Vec<u8>,
pub srtp_key_initiator: Vec<u8>,
pub srtp_salt_initiator: Vec<u8>,
pub srtp_key_responder: Vec<u8>,
pub srtp_salt_responder: Vec<u8>,
pub sas_value: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RetainedSecretIds {
pub rs1: [u8; 8],
pub rs2: [u8; 8],
pub aux: [u8; 8],
pub pbx: [u8; 8],
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RetainedSecretMatches {
pub rs1: bool,
pub rs2: bool,
pub aux: bool,
pub pbx: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SecretIdRole {
Initiator,
Responder,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SharedSecretSelection<'a> {
pub s1: Option<&'a [u8]>,
pub s2: Option<&'a [u8]>,
pub s3: Option<&'a [u8]>,
pub cache_mismatch: bool,
}
#[cfg(feature = "crypto")]
fn hash_bytes(hash: HashAlgorithm, input: &[u8]) -> Result<Vec<u8>, Error> {
Ok(match hash {
HashAlgorithm::Sha256 => Sha256::digest(input).to_vec(),
HashAlgorithm::Sha384 => Sha384::digest(input).to_vec(),
_ => return Err(Error::Unsupported("hash algorithm")),
})
}
#[cfg(feature = "crypto")]
pub fn implicit_hash_sha256(input: &[u8]) -> [u8; 32] {
Sha256::digest(input).into()
}
#[cfg(not(feature = "crypto"))]
pub fn implicit_hash_sha256(_input: &[u8]) -> [u8; 32] {
[0; 32]
}
#[cfg(feature = "crypto")]
pub fn generate_hash_chain(seed_h0: [u8; 32]) -> HashChain {
let h1 = implicit_hash_sha256(&seed_h0);
let h2 = implicit_hash_sha256(&h1);
let h3 = implicit_hash_sha256(&h2);
HashChain {
h0: seed_h0,
h1,
h2,
h3,
}
}
#[cfg(not(feature = "crypto"))]
pub fn generate_hash_chain(seed_h0: [u8; 32]) -> HashChain {
HashChain {
h0: seed_h0,
h1: [0; 32],
h2: [0; 32],
h3: [0; 32],
}
}
#[cfg(feature = "crypto")]
pub fn total_hash(hash: HashAlgorithm, parts: &[&[u8]]) -> Result<Vec<u8>, Error> {
let mut buf = Vec::new();
for part in parts {
buf.extend_from_slice(part);
}
hash_bytes(hash, &buf)
}
#[cfg(not(feature = "crypto"))]
pub fn total_hash(_hash: HashAlgorithm, _parts: &[&[u8]]) -> Result<Vec<u8>, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(feature = "crypto")]
pub fn kdf(
hash: HashAlgorithm,
key: &[u8],
label: &[u8],
context: &[u8],
out_len: usize,
) -> Result<Vec<u8>, Error> {
let mut out = Vec::with_capacity(out_len);
let mut counter: u32 = 1;
while out.len() < out_len {
let mut msg = Vec::new();
msg.extend_from_slice(&counter.to_be_bytes());
msg.extend_from_slice(label);
msg.push(0);
msg.extend_from_slice(context);
msg.extend_from_slice(&(out_len as u32 * 8).to_be_bytes());
let block = match hash {
HashAlgorithm::Sha256 => {
let mut mac =
HmacSha256::new_from_slice(key).map_err(|_| Error::InvalidField("kdf key"))?;
mac.update(&msg);
mac.finalize().into_bytes().to_vec()
}
HashAlgorithm::Sha384 => {
let mut mac =
HmacSha384::new_from_slice(key).map_err(|_| Error::InvalidField("kdf key"))?;
mac.update(&msg);
mac.finalize().into_bytes().to_vec()
}
_ => return Err(Error::Unsupported("hash algorithm")),
};
out.extend_from_slice(&block);
counter += 1;
}
out.truncate(out_len);
Ok(out)
}
#[cfg(not(feature = "crypto"))]
pub fn kdf(
_hash: HashAlgorithm,
_key: &[u8],
_label: &[u8],
_context: &[u8],
_out_len: usize,
) -> Result<Vec<u8>, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(feature = "crypto")]
pub fn derive_s0(
hash: HashAlgorithm,
dh_result: &[u8],
nonce_material: &[u8],
retained_secret: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
let mut material = Vec::new();
material.extend_from_slice(dh_result);
material.extend_from_slice(nonce_material);
if let Some(secret) = retained_secret {
material.extend_from_slice(secret);
}
hash_bytes(hash, &material)
}
#[cfg(feature = "crypto")]
pub fn derive_s0_full(
hash: HashAlgorithm,
dh_result: &[u8],
hello_hashes: &[&[u8]],
zid_initiator: &[u8; 12],
zid_responder: &[u8; 12],
retained: Option<&RetainedSecrets>,
) -> Result<Vec<u8>, Error> {
let mut material = Vec::new();
material.extend_from_slice(dh_result);
for h in hello_hashes {
material.extend_from_slice(h);
}
material.extend_from_slice(zid_initiator);
material.extend_from_slice(zid_responder);
if let Some(retained) = retained {
material.extend_from_slice(&retained.rs1);
material.extend_from_slice(&retained.rs2);
}
hash_bytes(hash, &material)
}
#[cfg(not(feature = "crypto"))]
pub fn derive_s0_full(
_hash: HashAlgorithm,
_dh_result: &[u8],
_hello_hashes: &[&[u8]],
_zid_initiator: &[u8; 12],
_zid_responder: &[u8; 12],
_retained: Option<&RetainedSecrets>,
) -> Result<Vec<u8>, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(not(feature = "crypto"))]
pub fn derive_s0(
_hash: HashAlgorithm,
_dh_result: &[u8],
_nonce_material: &[u8],
_retained_secret: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(feature = "crypto")]
pub fn sas_value(hash: HashAlgorithm, s0: &[u8], kdf_context: &[u8]) -> Result<u32, Error> {
let bytes = kdf(hash, s0, b"SAS", kdf_context, 4)?;
Ok(u32::from_be_bytes(bytes.try_into().unwrap()))
}
#[cfg(feature = "crypto")]
pub fn derive_session_secrets(
hash: HashAlgorithm,
cipher: CipherAlgorithm,
s0: &[u8],
zid_initiator: &[u8; 12],
zid_responder: &[u8; 12],
) -> Result<SessionSecrets, Error> {
let cipher_len = cipher
.key_len()
.ok_or(Error::InvalidField("cipher key length"))?;
let mut context = Vec::new();
context.extend_from_slice(zid_initiator);
context.extend_from_slice(zid_responder);
let confirm_key_initiator = kdf(hash, s0, b"Initiator ZRTP key", &context, cipher_len)?;
let confirm_key_responder = kdf(hash, s0, b"Responder ZRTP key", &context, cipher_len)?;
let confirm_mac_key_initiator = kdf(hash, s0, b"Initiator HMAC key", &context, 32)?;
let confirm_mac_key_responder = kdf(hash, s0, b"Responder HMAC key", &context, 32)?;
let zrtp_session_key = kdf(hash, s0, b"ZRTP Session Key", &context, 32)?;
let srtp_key_initiator = kdf(hash, s0, b"Initiator SRTP master key", &context, cipher_len)?;
let srtp_salt_initiator = kdf(hash, s0, b"Initiator SRTP master salt", &context, 14)?;
let srtp_key_responder = kdf(hash, s0, b"Responder SRTP master key", &context, cipher_len)?;
let srtp_salt_responder = kdf(hash, s0, b"Responder SRTP master salt", &context, 14)?;
let sas_value = sas_value(hash, s0, &context)?;
Ok(SessionSecrets {
s0: s0.to_vec(),
confirm_key_initiator,
confirm_key_responder,
confirm_mac_key_initiator,
confirm_mac_key_responder,
zrtp_session_key,
srtp_key_initiator,
srtp_salt_initiator,
srtp_key_responder,
srtp_salt_responder,
sas_value,
})
}
#[cfg(not(feature = "crypto"))]
pub fn derive_session_secrets(
_hash: HashAlgorithm,
_cipher: CipherAlgorithm,
_s0: &[u8],
_zid_initiator: &[u8; 12],
_zid_responder: &[u8; 12],
) -> Result<SessionSecrets, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(not(feature = "crypto"))]
pub fn sas_value(_hash: HashAlgorithm, _s0: &[u8], _kdf_context: &[u8]) -> Result<u32, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
pub fn sas_render_b32(sas_value: u32) -> [char; 4] {
crate::wire::sas_base32(sas_value)
}
pub fn sas_render_b256(sas_value: u32) -> [u8; 2] {
[(sas_value >> 24) as u8, (sas_value >> 16) as u8]
}
#[cfg(feature = "crypto")]
pub fn sas_render_b256_words(sas_value: u32) -> [String; 2] {
let bytes = sas_render_b256(sas_value);
[
WL_PGP_ENCODE_TWO_SYLLABLE[bytes[0] as usize].to_string(),
WL_PGP_ENCODE_THREE_SYLLABLE[bytes[1] as usize].to_string(),
]
}
#[cfg(not(feature = "crypto"))]
pub fn sas_render_b256_words(sas_value: u32) -> [String; 2] {
let bytes = sas_render_b256(sas_value);
[format!("{:02x}", bytes[0]), format!("{:02x}", bytes[1])]
}
#[cfg(feature = "crypto")]
pub fn confirm_mac(
hash: HashAlgorithm,
key: &[u8],
encrypted_part: &[u8],
) -> Result<[u8; 8], Error> {
let tag = match hash {
HashAlgorithm::Sha256 => {
let mut mac =
HmacSha256::new_from_slice(key).map_err(|_| Error::InvalidField("confirm key"))?;
mac.update(encrypted_part);
mac.finalize().into_bytes().to_vec()
}
HashAlgorithm::Sha384 => {
let mut mac =
HmacSha384::new_from_slice(key).map_err(|_| Error::InvalidField("confirm key"))?;
mac.update(encrypted_part);
mac.finalize().into_bytes().to_vec()
}
_ => return Err(Error::Unsupported("hash algorithm")),
};
Ok(tag[0..8].try_into().unwrap())
}
#[cfg(not(feature = "crypto"))]
pub fn confirm_mac(
_hash: HashAlgorithm,
_key: &[u8],
_encrypted_part: &[u8],
) -> Result<[u8; 8], Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(feature = "crypto")]
fn aes_cfb_apply(
cipher: CipherAlgorithm,
key: &[u8],
iv: &[u8; 16],
input: &[u8],
encrypt: bool,
) -> Result<Vec<u8>, Error> {
let mut buf = input.to_vec();
match (cipher, encrypt) {
(CipherAlgorithm::Aes128, true) => Aes128CfbEnc::new_from_slices(key, iv)
.map_err(|_| Error::InvalidField("key/iv"))?
.encrypt(&mut buf),
(CipherAlgorithm::Aes192, true) => Aes192CfbEnc::new_from_slices(key, iv)
.map_err(|_| Error::InvalidField("key/iv"))?
.encrypt(&mut buf),
(CipherAlgorithm::Aes256, true) => Aes256CfbEnc::new_from_slices(key, iv)
.map_err(|_| Error::InvalidField("key/iv"))?
.encrypt(&mut buf),
(CipherAlgorithm::Aes128, false) => Aes128CfbDec::new_from_slices(key, iv)
.map_err(|_| Error::InvalidField("key/iv"))?
.decrypt(&mut buf),
(CipherAlgorithm::Aes192, false) => Aes192CfbDec::new_from_slices(key, iv)
.map_err(|_| Error::InvalidField("key/iv"))?
.decrypt(&mut buf),
(CipherAlgorithm::Aes256, false) => Aes256CfbDec::new_from_slices(key, iv)
.map_err(|_| Error::InvalidField("key/iv"))?
.decrypt(&mut buf),
_ => return Err(Error::Unsupported("cipher algorithm")),
}
Ok(buf)
}
#[cfg(feature = "crypto")]
pub fn encrypt_confirm(
cipher: CipherAlgorithm,
key: &[u8],
plaintext: &[u8],
) -> Result<Confirm, Error> {
let mut iv = [0u8; 16];
OsRng.fill_bytes(&mut iv);
let encrypted = aes_cfb_apply(cipher, key, &iv, plaintext, true)?;
Ok(Confirm {
confirm_mac: [0; 8],
cfb_iv: iv,
encrypted,
})
}
#[cfg(feature = "crypto")]
pub fn finalize_confirm(
hash: HashAlgorithm,
mac_key: &[u8],
mut confirm: Confirm,
) -> Result<Confirm, Error> {
let mut protected = Vec::new();
protected.extend_from_slice(&confirm.cfb_iv);
protected.extend_from_slice(&confirm.encrypted);
confirm.confirm_mac = confirm_mac(hash, mac_key, &protected)?;
Ok(confirm)
}
#[cfg(feature = "crypto")]
pub fn decrypt_confirm(
cipher: CipherAlgorithm,
key: &[u8],
confirm: &Confirm,
) -> Result<Vec<u8>, Error> {
aes_cfb_apply(cipher, key, &confirm.cfb_iv, &confirm.encrypted, false)
}
#[cfg(feature = "crypto")]
pub fn verify_confirm(
hash: HashAlgorithm,
mac_key: &[u8],
confirm: &Confirm,
) -> Result<bool, Error> {
let mut protected = Vec::new();
protected.extend_from_slice(&confirm.cfb_iv);
protected.extend_from_slice(&confirm.encrypted);
Ok(confirm_mac(hash, mac_key, &protected)? == confirm.confirm_mac)
}
#[cfg(not(feature = "crypto"))]
pub fn encrypt_confirm(
_cipher: CipherAlgorithm,
_key: &[u8],
_plaintext: &[u8],
) -> Result<Confirm, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(not(feature = "crypto"))]
pub fn finalize_confirm(
_hash: HashAlgorithm,
_mac_key: &[u8],
_confirm: Confirm,
) -> Result<Confirm, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(not(feature = "crypto"))]
pub fn decrypt_confirm(
_cipher: CipherAlgorithm,
_key: &[u8],
_confirm: &Confirm,
) -> Result<Vec<u8>, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(not(feature = "crypto"))]
pub fn verify_confirm(
_hash: HashAlgorithm,
_mac_key: &[u8],
_confirm: &Confirm,
) -> Result<bool, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DhPublicKey {
EcP256(Vec<u8>),
EcP384(Vec<u8>),
EcP521(Vec<u8>),
Modp2048(Vec<u8>),
Modp3072(Vec<u8>),
Unsupported,
}
pub enum DhSecretKey {
#[cfg(feature = "crypto")]
EcP256(Zeroizing<p256::ecdh::EphemeralSecret>),
#[cfg(feature = "crypto")]
EcP384(Zeroizing<p384::ecdh::EphemeralSecret>),
#[cfg(feature = "crypto")]
EcP521(Zeroizing<p521::ecdh::EphemeralSecret>),
Modp2048(SecretBytes),
Modp3072(SecretBytes),
Unsupported,
}
impl core::fmt::Debug for DhSecretKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
#[cfg(feature = "crypto")]
Self::EcP256(_) => f.write_str("DhSecretKey::EcP256(<redacted>)"),
#[cfg(feature = "crypto")]
Self::EcP384(_) => f.write_str("DhSecretKey::EcP384(<redacted>)"),
#[cfg(feature = "crypto")]
Self::EcP521(_) => f.write_str("DhSecretKey::EcP521(<redacted>)"),
Self::Modp2048(_) => f.write_str("DhSecretKey::Modp2048(<redacted>)"),
Self::Modp3072(_) => f.write_str("DhSecretKey::Modp3072(<redacted>)"),
Self::Unsupported => f.write_str("DhSecretKey::Unsupported"),
}
}
}
#[cfg(feature = "crypto")]
const MODP_2048_PRIME_HEX: &str =
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF";
#[cfg(feature = "crypto")]
const MODP_3072_PRIME_HEX: &str =
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
#[cfg(feature = "crypto")]
fn modp_group(key_agreement: crate::wire::KeyAgreement) -> Option<(BigUint, usize)> {
let prime_hex = match key_agreement {
crate::wire::KeyAgreement::Dh2048 => MODP_2048_PRIME_HEX,
crate::wire::KeyAgreement::Dh3072 => MODP_3072_PRIME_HEX,
_ => return None,
};
let prime = BigUint::parse_bytes(prime_hex.as_bytes(), 16)?;
Some((prime, prime_hex.len() / 2))
}
#[cfg(feature = "crypto")]
fn left_pad_be(mut bytes: Vec<u8>, len: usize) -> Vec<u8> {
if bytes.len() >= len {
return bytes;
}
let mut padded = vec![0u8; len - bytes.len()];
padded.append(&mut bytes);
padded
}
#[cfg(feature = "crypto")]
fn zrtp_to_sec1_uncompressed(raw: &[u8]) -> Result<Vec<u8>, Error> {
if raw.len() % 2 != 0 {
return Err(Error::InvalidLength);
}
let mut out = Vec::with_capacity(raw.len() + 1);
out.push(0x04);
out.extend_from_slice(raw);
Ok(out)
}
#[cfg(feature = "crypto")]
fn sec1_uncompressed_to_zrtp(raw: &[u8]) -> Result<Vec<u8>, Error> {
if raw.first().copied() != Some(0x04) {
return Err(Error::InvalidField("SEC1 public key"));
}
Ok(raw[1..].to_vec())
}
#[cfg(feature = "crypto")]
pub fn generate_dh_keypair(
ka: crate::wire::KeyAgreement,
) -> Result<(DhSecretKey, DhPublicKey), Error> {
match ka {
crate::wire::KeyAgreement::EcP256 => {
let secret = p256::ecdh::EphemeralSecret::random(&mut OsRng);
let public =
sec1_uncompressed_to_zrtp(p256::PublicKey::from(&secret).to_sec1_bytes().as_ref())?;
Ok((
DhSecretKey::EcP256(Zeroizing::new(secret)),
DhPublicKey::EcP256(public),
))
}
crate::wire::KeyAgreement::EcP384 => {
let secret = p384::ecdh::EphemeralSecret::random(&mut OsRng);
let public =
sec1_uncompressed_to_zrtp(p384::PublicKey::from(&secret).to_sec1_bytes().as_ref())?;
Ok((
DhSecretKey::EcP384(Zeroizing::new(secret)),
DhPublicKey::EcP384(public),
))
}
crate::wire::KeyAgreement::EcP521 => {
let secret = p521::ecdh::EphemeralSecret::random(&mut OsRng);
let public =
sec1_uncompressed_to_zrtp(p521::PublicKey::from(&secret).to_sec1_bytes().as_ref())?;
Ok((
DhSecretKey::EcP521(Zeroizing::new(secret)),
DhPublicKey::EcP521(public),
))
}
crate::wire::KeyAgreement::Dh2048 | crate::wire::KeyAgreement::Dh3072 => {
let (prime, prime_len) = modp_group(ka).ok_or(Error::Unsupported("MODP group"))?;
let generator = BigUint::from(2u32);
let mut exponent = vec![
0u8;
match ka {
crate::wire::KeyAgreement::Dh2048 => 32,
_ => 48,
}
];
OsRng.fill_bytes(&mut exponent);
exponent[0] |= 0x01;
let secret = BigUint::from_bytes_be(&exponent);
let public = left_pad_be(generator.modpow(&secret, &prime).to_bytes_be(), prime_len);
Ok(match ka {
crate::wire::KeyAgreement::Dh2048 => (
DhSecretKey::Modp2048(Zeroizing::new(exponent)),
DhPublicKey::Modp2048(public),
),
_ => (
DhSecretKey::Modp3072(Zeroizing::new(exponent)),
DhPublicKey::Modp3072(public),
),
})
}
_ => Ok((DhSecretKey::Unsupported, DhPublicKey::Unsupported)),
}
}
#[cfg(feature = "crypto")]
pub fn compute_dh_shared(secret: &DhSecretKey, peer: &DhPublicKey) -> Result<Vec<u8>, Error> {
match (secret, peer) {
(DhSecretKey::EcP256(secret), DhPublicKey::EcP256(peer)) => {
let peer = p256::PublicKey::from_sec1_bytes(&zrtp_to_sec1_uncompressed(peer)?)
.map_err(|_| Error::InvalidField("peer key"))?;
Ok(secret.diffie_hellman(&peer).raw_secret_bytes().to_vec())
}
(DhSecretKey::EcP384(secret), DhPublicKey::EcP384(peer)) => {
let peer = p384::PublicKey::from_sec1_bytes(&zrtp_to_sec1_uncompressed(peer)?)
.map_err(|_| Error::InvalidField("peer key"))?;
Ok(secret.diffie_hellman(&peer).raw_secret_bytes().to_vec())
}
(DhSecretKey::EcP521(secret), DhPublicKey::EcP521(peer)) => {
let peer = p521::PublicKey::from_sec1_bytes(&zrtp_to_sec1_uncompressed(peer)?)
.map_err(|_| Error::InvalidField("peer key"))?;
Ok(secret.diffie_hellman(&peer).raw_secret_bytes().to_vec())
}
(DhSecretKey::Modp2048(secret), DhPublicKey::Modp2048(peer)) => {
let (prime, prime_len) = modp_group(crate::wire::KeyAgreement::Dh2048)
.ok_or(Error::Unsupported("MODP group"))?;
let peer = BigUint::from_bytes_be(peer);
if peer <= BigUint::one() || peer >= (&prime - BigUint::one()) {
return Err(Error::InvalidField("peer key"));
}
let secret = BigUint::from_bytes_be(secret);
Ok(left_pad_be(
peer.modpow(&secret, &prime).to_bytes_be(),
prime_len,
))
}
(DhSecretKey::Modp3072(secret), DhPublicKey::Modp3072(peer)) => {
let (prime, prime_len) = modp_group(crate::wire::KeyAgreement::Dh3072)
.ok_or(Error::Unsupported("MODP group"))?;
let peer = BigUint::from_bytes_be(peer);
if peer <= BigUint::one() || peer >= (&prime - BigUint::one()) {
return Err(Error::InvalidField("peer key"));
}
let secret = BigUint::from_bytes_be(secret);
Ok(left_pad_be(
peer.modpow(&secret, &prime).to_bytes_be(),
prime_len,
))
}
_ => Err(Error::Unsupported("key agreement backend")),
}
}
#[cfg(not(feature = "crypto"))]
pub fn generate_dh_keypair(
_ka: crate::wire::KeyAgreement,
) -> Result<(DhSecretKey, DhPublicKey), Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(not(feature = "crypto"))]
pub fn compute_dh_shared(_secret: &DhSecretKey, _peer: &DhPublicKey) -> Result<Vec<u8>, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
pub fn encode_confirm_content(content: &ConfirmContent) -> Result<Vec<u8>, Error> {
content.encode_plaintext()
}
pub fn decode_confirm_content(bytes: &[u8]) -> Result<ConfirmContent, Error> {
ConfirmContent::decode_plaintext(bytes)
}
#[cfg(feature = "crypto")]
pub fn compute_total_hash(hash: HashAlgorithm, messages: &[&[u8]]) -> Result<Vec<u8>, Error> {
total_hash(hash, messages)
}
#[cfg(not(feature = "crypto"))]
pub fn compute_total_hash(_hash: HashAlgorithm, _messages: &[&[u8]]) -> Result<Vec<u8>, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(feature = "crypto")]
pub fn compute_hvi(
hash: HashAlgorithm,
dhpart2_message: &[u8],
responder_hello_message: &[u8],
) -> Result<[u8; 32], Error> {
let bytes = total_hash(hash, &[dhpart2_message, responder_hello_message])?;
let mut out = [0u8; 32];
let take = bytes.len().min(32);
out[..take].copy_from_slice(&bytes[..take]);
Ok(out)
}
#[cfg(not(feature = "crypto"))]
pub fn compute_hvi(
_hash: HashAlgorithm,
_dhpart2_message: &[u8],
_responder_hello_message: &[u8],
) -> Result<[u8; 32], Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(feature = "crypto")]
pub fn derive_retained_secret(
hash: HashAlgorithm,
s0: &[u8],
zid_initiator: &[u8; 12],
zid_responder: &[u8; 12],
total_hash: &[u8],
) -> Result<Vec<u8>, Error> {
let mut context = Vec::new();
context.extend_from_slice(zid_initiator);
context.extend_from_slice(zid_responder);
context.extend_from_slice(total_hash);
kdf(hash, s0, b"retained secret", &context, 32)
}
#[cfg(not(feature = "crypto"))]
pub fn derive_retained_secret(
_hash: HashAlgorithm,
_s0: &[u8],
_zid_initiator: &[u8; 12],
_zid_responder: &[u8; 12],
_total_hash: &[u8],
) -> Result<Vec<u8>, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(feature = "crypto")]
pub fn compute_preshared_key(
hash: HashAlgorithm,
rs1: &[u8],
auxsecret: Option<&[u8]>,
pbxsecret: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
let aux = auxsecret.unwrap_or(&[]);
let pbx = pbxsecret.unwrap_or(&[]);
let mut material = Vec::new();
for part in [rs1, aux, pbx] {
material.extend_from_slice(&(part.len() as u32).to_be_bytes());
material.extend_from_slice(part);
}
total_hash(hash, &[&material])
}
#[cfg(not(feature = "crypto"))]
pub fn compute_preshared_key(
_hash: HashAlgorithm,
_rs1: &[u8],
_auxsecret: Option<&[u8]>,
_pbxsecret: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(feature = "crypto")]
pub fn compute_key_id(hash: HashAlgorithm, preshared_key: &[u8]) -> Result<[u8; 8], Error> {
confirm_mac(hash, preshared_key, b"Prsh")
}
#[cfg(not(feature = "crypto"))]
pub fn compute_key_id(_hash: HashAlgorithm, _preshared_key: &[u8]) -> Result<[u8; 8], Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(feature = "crypto")]
pub fn retained_secret_ids(
hash: HashAlgorithm,
rs1: Option<&[u8]>,
rs2: Option<&[u8]>,
aux: Option<&[u8]>,
pbx: Option<&[u8]>,
) -> Result<RetainedSecretIds, Error> {
fn id_or_zero(hash: HashAlgorithm, input: Option<&[u8]>) -> Result<[u8; 8], Error> {
match input {
Some(v) if !v.is_empty() => {
let full = hash_bytes(hash, v)?;
Ok(full[..8].try_into().unwrap())
}
_ => Ok([0u8; 8]),
}
}
Ok(RetainedSecretIds {
rs1: id_or_zero(hash, rs1)?,
rs2: id_or_zero(hash, rs2)?,
aux: id_or_zero(hash, aux)?,
pbx: id_or_zero(hash, pbx)?,
})
}
#[cfg(not(feature = "crypto"))]
pub fn retained_secret_ids(
_hash: HashAlgorithm,
_rs1: Option<&[u8]>,
_rs2: Option<&[u8]>,
_aux: Option<&[u8]>,
_pbx: Option<&[u8]>,
) -> Result<RetainedSecretIds, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(feature = "crypto")]
fn mac_truncated_64(hash: HashAlgorithm, secret: &[u8], data: &[u8]) -> Result<[u8; 8], Error> {
let full = match hash {
HashAlgorithm::Sha256 => {
let mut mac = HmacSha256::new_from_slice(secret)
.map_err(|_| Error::InvalidField("secret id key"))?;
mac.update(data);
mac.finalize().into_bytes().to_vec()
}
HashAlgorithm::Sha384 => {
let mut mac = HmacSha384::new_from_slice(secret)
.map_err(|_| Error::InvalidField("secret id key"))?;
mac.update(data);
mac.finalize().into_bytes().to_vec()
}
_ => return Err(Error::Unsupported("hash algorithm")),
};
Ok(full[..8].try_into().unwrap())
}
#[cfg(feature = "crypto")]
pub fn retained_secret_ids_for_role(
hash: HashAlgorithm,
role: SecretIdRole,
h3: &[u8; 32],
rs1: Option<&[u8]>,
rs2: Option<&[u8]>,
aux: Option<&[u8]>,
pbx: Option<&[u8]>,
) -> Result<RetainedSecretIds, Error> {
let role_label = match role {
SecretIdRole::Initiator => b"Initiator".as_slice(),
SecretIdRole::Responder => b"Responder".as_slice(),
};
fn id_or_zero<F>(input: Option<&[u8]>, f: F) -> Result<[u8; 8], Error>
where
F: FnOnce(&[u8]) -> Result<[u8; 8], Error>,
{
match input {
Some(v) if !v.is_empty() => f(v),
_ => Ok([0u8; 8]),
}
}
Ok(RetainedSecretIds {
rs1: id_or_zero(rs1, |v| mac_truncated_64(hash, v, role_label))?,
rs2: id_or_zero(rs2, |v| mac_truncated_64(hash, v, role_label))?,
aux: id_or_zero(aux, |v| mac_truncated_64(hash, v, h3))?,
pbx: id_or_zero(pbx, |v| mac_truncated_64(hash, v, role_label))?,
})
}
#[cfg(not(feature = "crypto"))]
pub fn retained_secret_ids_for_role(
_hash: HashAlgorithm,
_role: SecretIdRole,
_h3: &[u8; 32],
_rs1: Option<&[u8]>,
_rs2: Option<&[u8]>,
_aux: Option<&[u8]>,
_pbx: Option<&[u8]>,
) -> Result<RetainedSecretIds, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
pub fn match_retained_secret_ids(
expected: &RetainedSecretIds,
received_rs1: &[u8; 8],
received_rs2: &[u8; 8],
received_aux: &[u8; 8],
received_pbx: &[u8; 8],
) -> RetainedSecretMatches {
RetainedSecretMatches {
rs1: *received_rs1 != [0u8; 8] && expected.rs1 == *received_rs1,
rs2: *received_rs2 != [0u8; 8] && expected.rs2 == *received_rs2,
aux: *received_aux != [0u8; 8] && expected.aux == *received_aux,
pbx: *received_pbx != [0u8; 8] && expected.pbx == *received_pbx,
}
}
pub fn select_matching_shared_secrets<'a>(
local_rs1: Option<&'a [u8]>,
local_rs2: Option<&'a [u8]>,
local_aux: Option<&'a [u8]>,
local_pbx: Option<&'a [u8]>,
received_matches: &RetainedSecretMatches,
rs1_matches_remote_rs1_or_rs2: bool,
rs2_matches_remote_rs1_or_rs2: bool,
) -> SharedSecretSelection<'a> {
let s1 = if rs1_matches_remote_rs1_or_rs2 {
local_rs1
} else if rs2_matches_remote_rs1_or_rs2 {
local_rs2
} else {
None
};
let s2 = if received_matches.aux {
local_aux
} else {
None
};
let s3 = if received_matches.pbx {
local_pbx
} else {
None
};
let cache_mismatch = local_rs1.is_some() && s1.is_none();
SharedSecretSelection {
s1,
s2,
s3,
cache_mismatch,
}
}
#[cfg(feature = "crypto")]
pub fn build_confirm_message(
hash: HashAlgorithm,
cipher: CipherAlgorithm,
confirm_key: &[u8],
confirm_mac_key: &[u8],
content: &ConfirmContent,
) -> Result<Confirm, Error> {
let plaintext = encode_confirm_content(content)?;
let confirm = encrypt_confirm(cipher, confirm_key, &plaintext)?;
finalize_confirm(hash, confirm_mac_key, confirm)
}
#[cfg(feature = "crypto")]
pub fn open_confirm_message(
hash: HashAlgorithm,
cipher: CipherAlgorithm,
confirm_key: &[u8],
confirm_mac_key: &[u8],
confirm: &Confirm,
) -> Result<ConfirmContent, Error> {
if !verify_confirm(hash, confirm_mac_key, confirm)? {
return Err(Error::InvalidField("confirm mac"));
}
let plaintext = decrypt_confirm(cipher, confirm_key, confirm)?;
decode_confirm_content(&plaintext)
}
#[cfg(not(feature = "crypto"))]
pub fn build_confirm_message(
_hash: HashAlgorithm,
_cipher: CipherAlgorithm,
_confirm_key: &[u8],
_confirm_mac_key: &[u8],
_content: &ConfirmContent,
) -> Result<Confirm, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
#[cfg(not(feature = "crypto"))]
pub fn open_confirm_message(
_hash: HashAlgorithm,
_cipher: CipherAlgorithm,
_confirm_key: &[u8],
_confirm_mac_key: &[u8],
_confirm: &Confirm,
) -> Result<ConfirmContent, Error> {
Err(Error::Unsupported("crypto feature disabled"))
}
pub fn sas_render(sas_type: SasType, value: u32) -> Result<String, Error> {
Ok(match sas_type {
SasType::B32 => sas_render_b32(value).iter().collect(),
SasType::B256 => sas_render_b256_words(value).join(" "),
SasType::Unknown(_) => return Err(Error::Unsupported("SAS type")),
})
}