use crate::application_crypto::APPLICATION_KEY_BYTES;
use hkdf::Hkdf;
use sha2::Digest;
use sha2::Sha256;
use x25519_dalek::{PublicKey, StaticSecret};
pub const KEY_AGREEMENT_PUBLIC_KEY_BYTES: usize = 32;
pub const KEY_AGREEMENT_ALGORITHM: &str = "openrtc:x25519-hkdf:v1";
pub const KEY_AGREEMENT_HKDF_INFO: &[u8] = b"openrtc:application-crypto:v1";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyAgreementError {
InvalidLocalSecretLength,
InvalidRemotePublicKeyLength,
InvalidRemotePublicKey,
HkdfExpandFailed,
RandomSecretFailed,
}
#[derive(Clone)]
pub struct EphemeralKeyAgreement {
secret: StaticSecret,
public: PublicKey,
}
impl EphemeralKeyAgreement {
pub fn generate() -> Result<Self, KeyAgreementError> {
let mut secret_bytes = [0u8; KEY_AGREEMENT_PUBLIC_KEY_BYTES];
getrandom::getrandom(&mut secret_bytes)
.map_err(|_| KeyAgreementError::RandomSecretFailed)?;
Ok(Self::from_secret_bytes(secret_bytes))
}
pub fn from_secret_bytes(bytes: [u8; KEY_AGREEMENT_PUBLIC_KEY_BYTES]) -> Self {
let secret = StaticSecret::from(bytes);
let public = PublicKey::from(&secret);
Self { secret, public }
}
pub fn public_key_bytes(&self) -> [u8; KEY_AGREEMENT_PUBLIC_KEY_BYTES] {
*self.public.as_bytes()
}
pub fn derive_application_crypto_key(
&self,
remote_public_key: &[u8; KEY_AGREEMENT_PUBLIC_KEY_BYTES],
local_node_id: &str,
remote_node_id: &str,
) -> Result<[u8; APPLICATION_KEY_BYTES], KeyAgreementError> {
derive_application_crypto_key(
&self.secret.to_bytes(),
remote_public_key,
local_node_id,
remote_node_id,
)
}
}
pub fn derive_application_crypto_key(
local_secret: &[u8; KEY_AGREEMENT_PUBLIC_KEY_BYTES],
remote_public_key: &[u8; KEY_AGREEMENT_PUBLIC_KEY_BYTES],
local_node_id: &str,
remote_node_id: &str,
) -> Result<[u8; APPLICATION_KEY_BYTES], KeyAgreementError> {
let local_secret = StaticSecret::from(*local_secret);
let remote_public = PublicKey::from(*remote_public_key);
let shared = local_secret.diffie_hellman(&remote_public);
let info = build_hkdf_info(local_node_id, remote_node_id);
let hkdf = Hkdf::<Sha256>::new(None, shared.as_bytes());
let mut okm = [0u8; APPLICATION_KEY_BYTES];
hkdf.expand(&info, &mut okm)
.map_err(|_| KeyAgreementError::HkdfExpandFailed)?;
Ok(okm)
}
pub fn key_agreement_fingerprint(bytes: &[u8]) -> String {
let digest = Sha256::digest(bytes);
hex::encode(&digest[..6])
}
fn build_hkdf_info(local_node_id: &str, remote_node_id: &str) -> Vec<u8> {
let (first, second) = if local_node_id <= remote_node_id {
(local_node_id, remote_node_id)
} else {
(remote_node_id, local_node_id)
};
let mut info =
Vec::with_capacity(KEY_AGREEMENT_HKDF_INFO.len() + 1 + first.len() + 1 + second.len());
info.extend_from_slice(KEY_AGREEMENT_HKDF_INFO);
info.push(0);
info.extend_from_slice(first.as_bytes());
info.push(0);
info.extend_from_slice(second.as_bytes());
info
}
#[cfg(test)]
mod tests {
use super::*;
const ALICE_SECRET: [u8; 32] = [
0x77, 0x00, 0x76, 0x6d, 0x0a, 0x73, 0x18, 0x65, 0x7d, 0x3c, 0x16, 0xc1, 0x72, 0x51, 0xb2,
0x66, 0x45, 0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a, 0xb1, 0x77, 0xfb, 0xa5, 0x1d,
0xb9, 0x2c,
];
const BOB_SECRET: [u8; 32] = [
0x5d, 0xab, 0x08, 0x7e, 0x62, 0x4a, 0x8a, 0x4b, 0x79, 0xe1, 0x7f, 0x8b, 0x83, 0x80, 0x0e,
0xe6, 0x6f, 0x3b, 0xb1, 0x29, 0x26, 0x18, 0xb6, 0xfd, 0x1c, 0x2f, 0x8b, 0x27, 0xff, 0x88,
0xe0, 0xeb,
];
#[test]
fn derive_application_crypto_key_is_symmetric_and_matches_typescript_fixture() {
let alice = EphemeralKeyAgreement::from_secret_bytes(ALICE_SECRET);
let bob = EphemeralKeyAgreement::from_secret_bytes(BOB_SECRET);
let local = "node-alice";
let remote = "node-bob";
let alice_key = alice
.derive_application_crypto_key(&bob.public_key_bytes(), local, remote)
.unwrap();
let bob_key = bob
.derive_application_crypto_key(&alice.public_key_bytes(), remote, local)
.unwrap();
assert_eq!(alice_key, bob_key);
assert_eq!(
hex::encode(alice_key),
"42aca5e6940349d360f4ae11e9cd0afeac27ed3d80d0d29b477165956fdf7a6c",
);
}
#[test]
fn derive_application_crypto_key_orders_node_ids_deterministically() {
let alice = EphemeralKeyAgreement::from_secret_bytes(ALICE_SECRET);
let bob = EphemeralKeyAgreement::from_secret_bytes(BOB_SECRET);
let forward = alice
.derive_application_crypto_key(&bob.public_key_bytes(), "zzz-node", "aaa-node")
.unwrap();
let reverse = bob
.derive_application_crypto_key(&alice.public_key_bytes(), "aaa-node", "zzz-node")
.unwrap();
assert_eq!(forward, reverse);
}
}