use hkdf::Hkdf;
use sha2::Sha256;
use x25519_dalek::{PublicKey, StaticSecret};
const CONTEXT: &[u8] = b"pamoja-session v1";
pub(crate) const OKM_LEN: usize = 35;
pub struct AgreementKey {
secret: StaticSecret,
}
impl AgreementKey {
pub fn from_seed(seed: &[u8; 32]) -> Self {
Self {
secret: StaticSecret::from(*seed),
}
}
pub fn public(&self) -> AgreementPublicKey {
AgreementPublicKey {
inner: PublicKey::from(&self.secret),
}
}
pub(crate) fn shared_secret(&self, peer: &AgreementPublicKey) -> [u8; 32] {
self.secret.diffie_hellman(&peer.inner).to_bytes()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AgreementPublicKey {
inner: PublicKey,
}
impl AgreementPublicKey {
pub fn from_bytes(bytes: &[u8; 32]) -> Self {
Self {
inner: PublicKey::from(*bytes),
}
}
pub fn to_bytes(&self) -> [u8; 32] {
self.inner.to_bytes()
}
}
pub(crate) fn derive(
shared: &[u8; 32],
salt: &[u8],
initiator: &[u8; 32],
responder: &[u8; 32],
) -> [u8; OKM_LEN] {
const INFO_LEN: usize = CONTEXT.len() + 64;
let mut info = [0u8; INFO_LEN];
info[..CONTEXT.len()].copy_from_slice(CONTEXT);
info[CONTEXT.len()..CONTEXT.len() + 32].copy_from_slice(initiator);
info[CONTEXT.len() + 32..].copy_from_slice(responder);
let mut okm = [0u8; OKM_LEN];
hkdf_sha256(salt, shared, &info, &mut okm);
okm
}
fn hkdf_sha256(salt: &[u8], ikm: &[u8], info: &[u8], out: &mut [u8]) {
Hkdf::<Sha256>::new(Some(salt), ikm)
.expand(info, out)
.expect("output length is within HKDF-SHA256's 255 * 32-byte limit");
}
#[cfg(test)]
mod tests {
use super::*;
const ALICE_SEED: [u8; 32] = [
0x77, 0x07, 0x6d, 0x0a, 0x73, 0x18, 0xa5, 0x7d, 0x3c, 0x16, 0xc1, 0x72, 0x51, 0xb2, 0x66,
0x45, 0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a, 0xb1, 0x77, 0xfb, 0xa5, 0x1d, 0xb9,
0x2c, 0x2a,
];
const ALICE_PUBLIC: [u8; 32] = [
0x85, 0x20, 0xf0, 0x09, 0x89, 0x30, 0xa7, 0x54, 0x74, 0x8b, 0x7d, 0xdc, 0xb4, 0x3e, 0xf7,
0x5a, 0x0d, 0xbf, 0x3a, 0x0d, 0x26, 0x38, 0x1a, 0xf4, 0xeb, 0xa4, 0xa9, 0x8e, 0xaa, 0x9b,
0x4e, 0x6a,
];
const BOB_SEED: [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,
];
const BOB_PUBLIC: [u8; 32] = [
0xde, 0x9e, 0xdb, 0x7d, 0x7b, 0x7d, 0xc1, 0xb4, 0xd3, 0x5b, 0x61, 0xc2, 0xec, 0xe4, 0x35,
0x37, 0x3f, 0x83, 0x43, 0xc8, 0x5b, 0x78, 0x67, 0x4d, 0xad, 0xfc, 0x7e, 0x14, 0x6f, 0x88,
0x2b, 0x4f,
];
const SHARED: [u8; 32] = [
0x4a, 0x5d, 0x9d, 0x5b, 0xa4, 0xce, 0x2d, 0xe1, 0x72, 0x8e, 0x3b, 0xf4, 0x80, 0x35, 0x0f,
0x25, 0xe0, 0x7e, 0x21, 0xc9, 0x47, 0xd1, 0x9e, 0x33, 0x76, 0xf0, 0x9b, 0x3c, 0x1e, 0x16,
0x17, 0x42,
];
#[test]
fn public_keys_match_the_rfc_7748_vector() {
assert_eq!(
AgreementKey::from_seed(&ALICE_SEED).public().to_bytes(),
ALICE_PUBLIC
);
assert_eq!(
AgreementKey::from_seed(&BOB_SEED).public().to_bytes(),
BOB_PUBLIC
);
}
#[test]
fn both_sides_agree_the_rfc_7748_shared_secret() {
let alice = AgreementKey::from_seed(&ALICE_SEED);
let bob = AgreementKey::from_seed(&BOB_SEED);
assert_eq!(alice.shared_secret(&bob.public()), SHARED);
assert_eq!(bob.shared_secret(&alice.public()), SHARED);
}
#[test]
fn a_public_key_round_trips_through_bytes() {
let public = AgreementKey::from_seed(&ALICE_SEED).public();
assert_eq!(AgreementPublicKey::from_bytes(&public.to_bytes()), public);
}
#[test]
fn hkdf_matches_the_rfc_5869_basic_vector() {
let ikm = [0x0bu8; 22];
let salt: [u8; 13] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
];
let info: [u8; 10] = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
let expected: [u8; 42] = [
0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
];
let mut okm = [0u8; 42];
hkdf_sha256(&salt, &ikm, &info, &mut okm);
assert_eq!(okm, expected);
}
#[test]
fn derive_changes_with_the_salt() {
let first = derive(&SHARED, b"salt-one", &ALICE_PUBLIC, &BOB_PUBLIC);
let second = derive(&SHARED, b"salt-two", &ALICE_PUBLIC, &BOB_PUBLIC);
assert_ne!(first, second);
}
}