#![cfg_attr(docsrs, doc(cfg(feature = "safe_api")))]
pub use super::hltypes::SecretKey;
pub use crate::hazardous::ecc::x25519::PrivateKey;
pub use crate::hazardous::ecc::x25519::PublicKey;
use crate::errors::UnknownCryptoError;
use crate::hazardous::ecc::x25519;
use crate::hazardous::hash::blake2::blake2b::{Blake2b, Digest};
use core::convert::TryFrom;
#[derive(Debug, PartialEq)]
pub struct EphemeralClientSession {
private_key: PrivateKey,
public_key: PublicKey,
}
impl EphemeralClientSession {
pub fn new() -> Result<Self, UnknownCryptoError> {
let privkey = PrivateKey::generate();
let pubkey: PublicKey = PublicKey::try_from(&privkey)?;
Ok(Self {
private_key: privkey,
public_key: pubkey,
})
}
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
pub fn private_key(&self) -> &PrivateKey {
&self.private_key
}
pub fn establish_with_server(
self,
server_public_key: &PublicKey,
) -> Result<SessionKeys, UnknownCryptoError> {
let q = x25519::key_agreement(&self.private_key, server_public_key)?;
let keys = establish_session_keys(&q, &self.public_key, server_public_key)?;
Ok(SessionKeys {
rx: SecretKey::from_slice(&keys.as_ref()[..32])?,
tx: SecretKey::from_slice(&keys.as_ref()[32..])?,
})
}
}
#[derive(Debug, PartialEq)]
pub struct EphemeralServerSession {
private_key: PrivateKey,
public_key: PublicKey,
}
impl EphemeralServerSession {
pub fn new() -> Result<Self, UnknownCryptoError> {
let privkey = PrivateKey::generate();
let pubkey: PublicKey = PublicKey::try_from(&privkey)?;
Ok(Self {
private_key: privkey,
public_key: pubkey,
})
}
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
pub fn private_key(&self) -> &PrivateKey {
&self.private_key
}
pub fn establish_with_client(
self,
client_public_key: &PublicKey,
) -> Result<SessionKeys, UnknownCryptoError> {
let q = x25519::key_agreement(&self.private_key, client_public_key)?;
let keys = establish_session_keys(&q, client_public_key, &self.public_key)?;
Ok(SessionKeys {
rx: SecretKey::from_slice(&keys.as_ref()[32..])?,
tx: SecretKey::from_slice(&keys.as_ref()[..32])?,
})
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Debug, PartialEq)]
pub struct SessionKeys {
rx: SecretKey,
tx: SecretKey,
}
impl SessionKeys {
pub fn receiving(&self) -> &SecretKey {
&self.rx
}
pub fn transport(&self) -> &SecretKey {
&self.tx
}
}
fn establish_session_keys(
shared_secret: &x25519::SharedKey,
client_pk: &PublicKey,
server_pk: &PublicKey,
) -> Result<Digest, UnknownCryptoError> {
let mut ctx = Blake2b::new(64)?;
ctx.update(shared_secret.unprotected_as_bytes())?;
ctx.update(&client_pk.to_bytes())?;
ctx.update(&server_pk.to_bytes())?;
ctx.finalize()
}
#[cfg(test)]
mod public {
use super::*;
#[test]
fn test_basic_key_exchange() {
let session_server = EphemeralServerSession::new().unwrap();
let server_public_key = session_server.public_key();
let session_client = EphemeralClientSession::new().unwrap();
let client_public_key = session_client.public_key().clone();
assert_ne!(session_client.private_key(), session_server.private_key());
let client = session_client
.establish_with_server(server_public_key)
.unwrap();
let server = session_server
.establish_with_client(&client_public_key)
.unwrap();
assert_eq!(client.receiving(), server.transport());
assert_eq!(client.transport(), server.receiving());
assert_ne!(client.receiving(), server.receiving());
assert_ne!(client.transport(), server.transport());
}
#[test]
fn test_error_on_low_order_public() {
let low_order_public: [u8; 32] = [
0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f,
0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16,
0x5f, 0x49, 0xb8, 0x00,
];
let server_low_order_pk = PublicKey::from_slice(&low_order_public).unwrap();
let session_client = EphemeralClientSession::new().unwrap();
assert!(session_client
.establish_with_server(&server_low_order_pk)
.is_err());
}
#[test]
fn libsodium_compat_test_1() {
let client_pk = "299283d8713b7d430376cb257e13cd5ad1a6e5ebe6135417f4bb3b45bf42f31a";
let client_sk = "e026533c3efa096ce9c4d77ad7c3d6948af2f9ef628b88430228ca0465ec35b9";
let server_pk = "1716d0c006e5f3c2240b2ccec9357dbd04030f51d3e584923e70823cd6fcab1c";
let server_sk = "55a94da5003d7807850938e84a5082d3deba8e5bbf5c50f814e8160270c165b4";
let client_rx = "37830d33c5de06fbe246db5803ed70284fe9ab78bc6b896a3db3a9b8db50418b";
let client_tx = "201d1bb45d4b9164f269d59cc00ba1a49c1924c27485bb6e5cc77ea4cc38ec7e";
let server_rx = "201d1bb45d4b9164f269d59cc00ba1a49c1924c27485bb6e5cc77ea4cc38ec7e";
let server_tx = "37830d33c5de06fbe246db5803ed70284fe9ab78bc6b896a3db3a9b8db50418b";
let client_public = PublicKey::from_slice(&hex::decode(client_pk).unwrap()).unwrap();
let client_secret = PrivateKey::from_slice(&hex::decode(client_sk).unwrap()).unwrap();
let server_public = PublicKey::from_slice(&hex::decode(server_pk).unwrap()).unwrap();
let server_secret = PrivateKey::from_slice(&hex::decode(server_sk).unwrap()).unwrap();
let client_recv = SecretKey::from_slice(&hex::decode(client_rx).unwrap()).unwrap();
let client_trans = SecretKey::from_slice(&hex::decode(client_tx).unwrap()).unwrap();
let server_recv = SecretKey::from_slice(&hex::decode(server_rx).unwrap()).unwrap();
let server_trans = SecretKey::from_slice(&hex::decode(server_tx).unwrap()).unwrap();
let session_client = EphemeralClientSession {
private_key: client_secret,
public_key: client_public.clone(),
};
let session_server = EphemeralServerSession {
private_key: server_secret,
public_key: server_public.clone(),
};
let expected_client_shared = SessionKeys {
rx: client_recv,
tx: client_trans,
};
let expected_server_shared = SessionKeys {
rx: server_recv,
tx: server_trans,
};
assert_eq!(
session_client
.establish_with_server(&server_public)
.unwrap(),
expected_client_shared
);
assert_eq!(
session_server
.establish_with_client(&client_public)
.unwrap(),
expected_server_shared
);
}
#[test]
fn libsodium_compat_test_2() {
let client_pk = "31f8eed74832d31b106702ecdf464a54e9fb9514d241473c5b51deb0d126893a";
let client_sk = "dcb760f46480ea5c2647e8dcbdc30fff8da6811712b4d5c7144aca1b72d6adb7";
let server_pk = "ccf7a7d5d2973f517a2276f0ca6c15da3c90a85db12e3ec171c4441c2b48f15d";
let server_sk = "e73ebfde3907296e5452e1f22ea1a85d4f3cdbf3ff9f099d45a0853d3b87d64f";
let client_rx = "25789992d2eac8bc0e1c3322d9b8e26050064ea3cead77ca2cf36966dea54186";
let client_tx = "f69cf60f763fb2a9c47dc1b3237983ef79cecd26205c68f9c16e91db6c8f3f18";
let server_rx = "f69cf60f763fb2a9c47dc1b3237983ef79cecd26205c68f9c16e91db6c8f3f18";
let server_tx = "25789992d2eac8bc0e1c3322d9b8e26050064ea3cead77ca2cf36966dea54186";
let client_public = PublicKey::from_slice(&hex::decode(client_pk).unwrap()).unwrap();
let client_secret = PrivateKey::from_slice(&hex::decode(client_sk).unwrap()).unwrap();
let server_public = PublicKey::from_slice(&hex::decode(server_pk).unwrap()).unwrap();
let server_secret = PrivateKey::from_slice(&hex::decode(server_sk).unwrap()).unwrap();
let client_recv = SecretKey::from_slice(&hex::decode(client_rx).unwrap()).unwrap();
let client_trans = SecretKey::from_slice(&hex::decode(client_tx).unwrap()).unwrap();
let server_recv = SecretKey::from_slice(&hex::decode(server_rx).unwrap()).unwrap();
let server_trans = SecretKey::from_slice(&hex::decode(server_tx).unwrap()).unwrap();
let session_client = EphemeralClientSession {
private_key: client_secret,
public_key: client_public.clone(),
};
let session_server = EphemeralServerSession {
private_key: server_secret,
public_key: server_public.clone(),
};
let expected_client_shared = SessionKeys {
rx: client_recv,
tx: client_trans,
};
let expected_server_shared = SessionKeys {
rx: server_recv,
tx: server_trans,
};
assert_eq!(
session_client
.establish_with_server(&server_public)
.unwrap(),
expected_client_shared
);
assert_eq!(
session_server
.establish_with_client(&client_public)
.unwrap(),
expected_server_shared
);
}
#[test]
fn libsodium_compat_test_3() {
let client_pk = "57f57eda618289c8f55dee0a8069405d9874684282b8380878b180719055333a";
let client_sk = "7d613b91a1cf6a34787362229cfaf6d50613e276a20ef59eea7f02d9236cd9f7";
let server_pk = "997f12b2ef5ba2c639c4dc39f159ce5169b60b9a3b65365f958cfb822e37b513";
let server_sk = "c3156f11e0cc31ffb92dfd5e780738011cfe80cc4184f5a3f190892528a9bac3";
let client_rx = "89f90402d56d5e184b1682c21583e695560e0ab54459d09a51a596a8d33293da";
let client_tx = "547c1f1be7abe8d10bf92fb19f79edd2139441b4faa54976b5db90a50b7244c4";
let server_rx = "547c1f1be7abe8d10bf92fb19f79edd2139441b4faa54976b5db90a50b7244c4";
let server_tx = "89f90402d56d5e184b1682c21583e695560e0ab54459d09a51a596a8d33293da";
let client_public = PublicKey::from_slice(&hex::decode(client_pk).unwrap()).unwrap();
let client_secret = PrivateKey::from_slice(&hex::decode(client_sk).unwrap()).unwrap();
let server_public = PublicKey::from_slice(&hex::decode(server_pk).unwrap()).unwrap();
let server_secret = PrivateKey::from_slice(&hex::decode(server_sk).unwrap()).unwrap();
let client_recv = SecretKey::from_slice(&hex::decode(client_rx).unwrap()).unwrap();
let client_trans = SecretKey::from_slice(&hex::decode(client_tx).unwrap()).unwrap();
let server_recv = SecretKey::from_slice(&hex::decode(server_rx).unwrap()).unwrap();
let server_trans = SecretKey::from_slice(&hex::decode(server_tx).unwrap()).unwrap();
let session_client = EphemeralClientSession {
private_key: client_secret,
public_key: client_public.clone(),
};
let session_server = EphemeralServerSession {
private_key: server_secret,
public_key: server_public.clone(),
};
let expected_client_shared = SessionKeys {
rx: client_recv,
tx: client_trans,
};
let expected_server_shared = SessionKeys {
rx: server_recv,
tx: server_trans,
};
assert_eq!(
session_client
.establish_with_server(&server_public)
.unwrap(),
expected_client_shared
);
assert_eq!(
session_server
.establish_with_client(&client_public)
.unwrap(),
expected_server_shared
);
}
#[test]
fn libsodium_compat_test_4() {
let client_pk = "2df30ccfd5eb6cb1ae5428dd06129a22fe8eac2b8b0cfcc1876bbaeb2b515703";
let client_sk = "52b73937f462130c82c427d68b26b689d3c020169909fea3043882654fa8e1a3";
let server_pk = "ef09a7379139627b2a13d0376f7fea1e4e2c27859757b74282b4368d2701de1c";
let server_sk = "384de0b04d358ef0a99cec457507b83a9fcff0c9a2875d1fc771c1b203eb90f5";
let client_rx = "738d3ff37e8b5d58daf888111359693042508617ef088c2048c0d87bc002ca38";
let client_tx = "fd1ab19e5c6ac0c5508ba129ded170a25c04f6f1ab9ccc3e66cd73988ade8471";
let server_rx = "fd1ab19e5c6ac0c5508ba129ded170a25c04f6f1ab9ccc3e66cd73988ade8471";
let server_tx = "738d3ff37e8b5d58daf888111359693042508617ef088c2048c0d87bc002ca38";
let client_public = PublicKey::from_slice(&hex::decode(client_pk).unwrap()).unwrap();
let client_secret = PrivateKey::from_slice(&hex::decode(client_sk).unwrap()).unwrap();
let server_public = PublicKey::from_slice(&hex::decode(server_pk).unwrap()).unwrap();
let server_secret = PrivateKey::from_slice(&hex::decode(server_sk).unwrap()).unwrap();
let client_recv = SecretKey::from_slice(&hex::decode(client_rx).unwrap()).unwrap();
let client_trans = SecretKey::from_slice(&hex::decode(client_tx).unwrap()).unwrap();
let server_recv = SecretKey::from_slice(&hex::decode(server_rx).unwrap()).unwrap();
let server_trans = SecretKey::from_slice(&hex::decode(server_tx).unwrap()).unwrap();
let session_client = EphemeralClientSession {
private_key: client_secret,
public_key: client_public.clone(),
};
let session_server = EphemeralServerSession {
private_key: server_secret,
public_key: server_public.clone(),
};
let expected_client_shared = SessionKeys {
rx: client_recv,
tx: client_trans,
};
let expected_server_shared = SessionKeys {
rx: server_recv,
tx: server_trans,
};
assert_eq!(
session_client
.establish_with_server(&server_public)
.unwrap(),
expected_client_shared
);
assert_eq!(
session_server
.establish_with_client(&client_public)
.unwrap(),
expected_server_shared
);
}
#[test]
fn libsodium_compat_test_5() {
let client_pk = "c4be3dfb50430e57313b6eeafc40e9b432120c4dd3d34ca6dedae1391b898c43";
let client_sk = "23702adbbab5918b682b2a1b2b27c634865b3dcf51ed81e287da30edd4f7ef39";
let server_pk = "a241a5a8c16e3731ddc8d3e5e5890f85b9de2c87095be3239379b3c62f73f949";
let server_sk = "530493dddf346371ce562557d5b0ff40ddbff038cf6a20187c16510ce57cc93f";
let client_rx = "8fa8dfc483108262b058b60b11e2f9b5b47287061bde785827afafb102a09ec7";
let client_tx = "a01332e4cb85b2bfac65f86936f27058b339889442c13eee06414bfb2d68c58b";
let server_rx = "a01332e4cb85b2bfac65f86936f27058b339889442c13eee06414bfb2d68c58b";
let server_tx = "8fa8dfc483108262b058b60b11e2f9b5b47287061bde785827afafb102a09ec7";
let client_public = PublicKey::from_slice(&hex::decode(client_pk).unwrap()).unwrap();
let client_secret = PrivateKey::from_slice(&hex::decode(client_sk).unwrap()).unwrap();
let server_public = PublicKey::from_slice(&hex::decode(server_pk).unwrap()).unwrap();
let server_secret = PrivateKey::from_slice(&hex::decode(server_sk).unwrap()).unwrap();
let client_recv = SecretKey::from_slice(&hex::decode(client_rx).unwrap()).unwrap();
let client_trans = SecretKey::from_slice(&hex::decode(client_tx).unwrap()).unwrap();
let server_recv = SecretKey::from_slice(&hex::decode(server_rx).unwrap()).unwrap();
let server_trans = SecretKey::from_slice(&hex::decode(server_tx).unwrap()).unwrap();
let session_client = EphemeralClientSession {
private_key: client_secret,
public_key: client_public.clone(),
};
let session_server = EphemeralServerSession {
private_key: server_secret,
public_key: server_public.clone(),
};
let expected_client_shared = SessionKeys {
rx: client_recv,
tx: client_trans,
};
let expected_server_shared = SessionKeys {
rx: server_recv,
tx: server_trans,
};
assert_eq!(
session_client
.establish_with_server(&server_public)
.unwrap(),
expected_client_shared
);
assert_eq!(
session_server
.establish_with_client(&client_public)
.unwrap(),
expected_server_shared
);
}
}