use crate::dtls::crypto::cipher::{HashAlgorithm, MacAlgorithm};
use crate::dtls::Result;
use bytes::{Bytes, BytesMut};
use hmac::{Hmac, Mac};
use p256::{ecdh::diffie_hellman, PublicKey, SecretKey};
use sha1::Sha1;
use sha2::{Digest, Sha256, Sha384};
type HmacSha1 = Hmac<Sha1>;
type HmacSha256 = Hmac<Sha256>;
type HmacSha384 = Hmac<Sha384>;
#[derive(Debug, Clone)]
pub struct DtlsKeyingMaterial {
master_secret: Bytes,
client_random: Bytes,
server_random: Bytes,
client_write_mac_key: Bytes,
server_write_mac_key: Bytes,
client_write_key: Bytes,
server_write_key: Bytes,
client_write_iv: Bytes,
server_write_iv: Bytes,
}
impl DtlsKeyingMaterial {
pub fn new(
master_secret: Bytes,
client_random: Bytes,
server_random: Bytes,
client_write_mac_key: Bytes,
server_write_mac_key: Bytes,
client_write_key: Bytes,
server_write_key: Bytes,
client_write_iv: Bytes,
server_write_iv: Bytes,
) -> Self {
Self {
master_secret,
client_random,
server_random,
client_write_mac_key,
server_write_mac_key,
client_write_key,
server_write_key,
client_write_iv,
server_write_iv,
}
}
pub fn master_secret(&self) -> &Bytes {
&self.master_secret
}
pub fn client_random(&self) -> &Bytes {
&self.client_random
}
pub fn server_random(&self) -> &Bytes {
&self.server_random
}
pub fn client_write_mac_key(&self) -> &Bytes {
&self.client_write_mac_key
}
pub fn server_write_mac_key(&self) -> &Bytes {
&self.server_write_mac_key
}
pub fn client_write_key(&self) -> &Bytes {
&self.client_write_key
}
pub fn server_write_key(&self) -> &Bytes {
&self.server_write_key
}
pub fn client_write_iv(&self) -> &Bytes {
&self.client_write_iv
}
pub fn server_write_iv(&self) -> &Bytes {
&self.server_write_iv
}
pub fn export_keying_material(
&self,
label: &str,
context: Option<&[u8]>,
length: usize,
) -> Result<Bytes> {
let seed = if let Some(ctx) = context {
let mut seed_data = BytesMut::with_capacity(
self.client_random.len() + self.server_random.len() + ctx.len(),
);
seed_data.extend_from_slice(&self.client_random);
seed_data.extend_from_slice(&self.server_random);
seed_data.extend_from_slice(ctx);
seed_data.freeze()
} else {
let mut seed_data =
BytesMut::with_capacity(self.client_random.len() + self.server_random.len());
seed_data.extend_from_slice(&self.client_random);
seed_data.extend_from_slice(&self.server_random);
seed_data.freeze()
};
prf_tls12(
&self.master_secret,
label.as_bytes(),
&seed,
length,
HashAlgorithm::Sha256,
)
}
}
fn hmac(key: &[u8], message: &[u8], hash_algorithm: HashAlgorithm) -> Result<Bytes> {
match hash_algorithm {
HashAlgorithm::Sha1 => {
let mut mac = HmacSha1::new_from_slice(key)
.map_err(|e| crate::error::Error::CryptoError(format!("HMAC-SHA1 error: {}", e)))?;
mac.update(message);
let result = mac.finalize().into_bytes();
Ok(Bytes::copy_from_slice(&result))
}
HashAlgorithm::Sha256 => {
let mut mac = HmacSha256::new_from_slice(key).map_err(|e| {
crate::error::Error::CryptoError(format!("HMAC-SHA256 error: {}", e))
})?;
mac.update(message);
let result = mac.finalize().into_bytes();
Ok(Bytes::copy_from_slice(&result))
}
HashAlgorithm::Sha384 => {
let mut mac = HmacSha384::new_from_slice(key).map_err(|e| {
crate::error::Error::CryptoError(format!("HMAC-SHA384 error: {}", e))
})?;
mac.update(message);
let result = mac.finalize().into_bytes();
Ok(Bytes::copy_from_slice(&result))
}
}
}
fn p_hash(
secret: &[u8],
seed: &[u8],
output_len: usize,
hash_algorithm: HashAlgorithm,
) -> Result<Bytes> {
let mut result = BytesMut::with_capacity(output_len);
let hash_len = hash_algorithm.hash_size();
let mut a = Bytes::copy_from_slice(seed);
while result.len() < output_len {
a = hmac(secret, &a, hash_algorithm)?;
let mut hmac_input = BytesMut::with_capacity(a.len() + seed.len());
hmac_input.extend_from_slice(&a);
hmac_input.extend_from_slice(seed);
let hmac_output = hmac(secret, &hmac_input, hash_algorithm)?;
let to_copy = std::cmp::min(output_len - result.len(), hash_len);
result.extend_from_slice(&hmac_output[..to_copy]);
}
Ok(result.freeze())
}
pub fn prf_tls12(
secret: &[u8],
label: &[u8],
seed: &[u8],
output_len: usize,
hash_algorithm: HashAlgorithm,
) -> Result<Bytes> {
let mut combined_seed = BytesMut::with_capacity(label.len() + seed.len());
combined_seed.extend_from_slice(label);
combined_seed.extend_from_slice(seed);
if label == b"master secret" {
println!("PRF input for master secret:");
println!(
" - Label: {:?}",
std::str::from_utf8(label).unwrap_or("invalid utf8")
);
println!(" - Secret length: {}", secret.len());
println!(
" - Secret first bytes: {:02X?}",
&secret[..std::cmp::min(secret.len(), 8)]
);
println!(" - Seed length: {}", seed.len());
println!(
" - Seed first bytes: {:02X?}",
&seed[..std::cmp::min(seed.len(), 8)]
);
println!(" - Combined seed length: {}", combined_seed.len());
println!(
" - Combined seed first bytes: {:02X?}",
&combined_seed[..std::cmp::min(combined_seed.len(), 8)]
);
}
p_hash(secret, &combined_seed, output_len, hash_algorithm)
}
pub fn derive_key_material(
master_secret: &[u8],
client_random: &[u8],
server_random: &[u8],
mac_algorithm: MacAlgorithm,
key_size: usize,
iv_size: usize,
) -> Result<DtlsKeyingMaterial> {
let mac_key_size = mac_algorithm.hash_size();
let total_size = 2 * mac_key_size + 2 * key_size + 2 * iv_size;
let mut seed = BytesMut::with_capacity(client_random.len() + server_random.len());
seed.extend_from_slice(client_random);
seed.extend_from_slice(server_random);
let key_block = prf_tls12(
master_secret,
b"key expansion",
&seed,
total_size,
HashAlgorithm::Sha256,
)?;
let mut offset = 0;
let client_mac_key = Bytes::copy_from_slice(&key_block[offset..offset + mac_key_size]);
offset += mac_key_size;
let server_mac_key = Bytes::copy_from_slice(&key_block[offset..offset + mac_key_size]);
offset += mac_key_size;
let client_write_key = Bytes::copy_from_slice(&key_block[offset..offset + key_size]);
offset += key_size;
let server_write_key = Bytes::copy_from_slice(&key_block[offset..offset + key_size]);
offset += key_size;
let client_write_iv = Bytes::copy_from_slice(&key_block[offset..offset + iv_size]);
offset += iv_size;
let server_write_iv = Bytes::copy_from_slice(&key_block[offset..offset + iv_size]);
Ok(DtlsKeyingMaterial::new(
Bytes::copy_from_slice(master_secret),
Bytes::copy_from_slice(client_random),
Bytes::copy_from_slice(server_random),
client_mac_key,
server_mac_key,
client_write_key,
server_write_key,
client_write_iv,
server_write_iv,
))
}
pub fn generate_ecdhe_pre_master_secret(
public_key_bytes: &[u8],
private_key_bytes: &[u8],
) -> Result<Bytes> {
let public_key = PublicKey::from_sec1_bytes(public_key_bytes).map_err(|e| {
crate::error::Error::CryptoError(format!("Failed to parse public key: {}", e))
})?;
let private_key = SecretKey::from_bytes(private_key_bytes.into()).map_err(|e| {
crate::error::Error::CryptoError(format!("Failed to parse private key: {}", e))
})?;
let shared_secret = diffie_hellman(private_key.to_nonzero_scalar(), public_key.as_affine());
let shared_secret_bytes = shared_secret.raw_secret_bytes();
Ok(Bytes::copy_from_slice(shared_secret_bytes.as_slice()))
}
pub fn generate_ecdh_keypair() -> Result<(SecretKey, PublicKey)> {
use rand::rngs::OsRng;
let private_key = SecretKey::random(&mut OsRng);
let public_key = PublicKey::from_secret_scalar(&private_key.to_nonzero_scalar());
Ok((private_key, public_key))
}
pub fn encode_public_key(public_key: &PublicKey) -> Result<Bytes> {
let encoded = public_key.to_sec1_bytes();
Ok(Bytes::copy_from_slice(&encoded))
}
pub fn encode_private_key(private_key: &SecretKey) -> Result<Bytes> {
let bytes = private_key.to_bytes();
Ok(Bytes::copy_from_slice(bytes.as_slice()))
}
pub fn calculate_master_secret(
pre_master_secret: &[u8],
client_random: &[u8],
server_random: &[u8],
) -> Result<Bytes> {
const MASTER_SECRET_LENGTH: usize = 48;
println!("Calculate master secret inputs:");
println!(
" - Pre-master secret first bytes: {:02X?}",
&pre_master_secret[..std::cmp::min(pre_master_secret.len(), 8)]
);
println!(
" - Client random first bytes: {:02X?}",
&client_random[..std::cmp::min(client_random.len(), 8)]
);
println!(
" - Server random first bytes: {:02X?}",
&server_random[..std::cmp::min(server_random.len(), 8)]
);
let mut seed = BytesMut::with_capacity(client_random.len() + server_random.len());
seed.extend_from_slice(client_random);
seed.extend_from_slice(server_random);
println!(" - Combined seed size: {}", seed.len());
let master_secret = prf_tls12(
pre_master_secret,
b"master secret",
&seed,
MASTER_SECRET_LENGTH,
HashAlgorithm::Sha256,
)?;
println!(
" - Generated master secret first bytes: {:02X?}",
&master_secret[..std::cmp::min(master_secret.len(), 8)]
);
Ok(master_secret)
}
pub fn extract_srtp_keys(
keying_material: &DtlsKeyingMaterial,
profile_key_length: usize,
profile_salt_length: usize,
is_client: bool,
) -> Result<(Bytes, Bytes)> {
let key_length = profile_key_length;
let salt_length = profile_salt_length;
let total_length = (key_length + salt_length) * 2;
let key_material =
keying_material.export_keying_material("EXTRACTOR-dtls_srtp", None, total_length)?;
let mut offset = 0;
let client_master_key = Bytes::copy_from_slice(&key_material[offset..offset + key_length]);
offset += key_length;
let server_master_key = Bytes::copy_from_slice(&key_material[offset..offset + key_length]);
offset += key_length;
let client_master_salt = Bytes::copy_from_slice(&key_material[offset..offset + salt_length]);
offset += salt_length;
let server_master_salt = Bytes::copy_from_slice(&key_material[offset..offset + salt_length]);
if is_client {
let master_key = client_master_key;
let master_salt = client_master_salt;
Ok((master_key, master_salt))
} else {
let master_key = server_master_key;
let master_salt = server_master_salt;
Ok((master_key, master_salt))
}
}
pub fn calculate_verify_data(
master_secret: &[u8],
handshake_messages: &[u8],
is_client: bool,
hash_algorithm: HashAlgorithm,
) -> Result<Bytes> {
let handshake_hash = match hash_algorithm {
HashAlgorithm::Sha1 => {
let mut hasher = sha1::Sha1::new();
hasher.update(handshake_messages);
Bytes::copy_from_slice(&hasher.finalize())
}
HashAlgorithm::Sha256 => {
let mut hasher = sha2::Sha256::new();
hasher.update(handshake_messages);
Bytes::copy_from_slice(&hasher.finalize())
}
HashAlgorithm::Sha384 => {
let mut hasher = sha2::Sha384::new();
hasher.update(handshake_messages);
Bytes::copy_from_slice(&hasher.finalize())
}
};
println!(
"Handshake hash for {} verification: {:02X?}",
if is_client { "client" } else { "server" },
&handshake_hash[..std::cmp::min(handshake_hash.len(), 16)]
);
let label = if is_client {
b"client finished"
} else {
b"server finished"
};
let verify_data = prf_tls12(
master_secret,
label,
&handshake_hash,
12, hash_algorithm,
)?;
println!("PRF inputs for verification:");
println!(
" - Label: {:?}",
std::str::from_utf8(label).unwrap_or("invalid utf8")
);
println!(" - Master secret length: {}", master_secret.len());
println!(
" - Master secret first bytes: {:02X?}",
&master_secret[..std::cmp::min(master_secret.len(), 8)]
);
println!(" - Hash algorithm: {:?}", hash_algorithm);
Ok(verify_data)
}