use core::fmt;
use crate::error::Error;
use crate::low;
use crate::mid::{p256, p384, p521, x25519};
pub struct Algorithm {
id: AlgorithmId,
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum AlgorithmId {
X25519,
EcdhP256,
EcdhP384,
EcdhP521,
}
impl fmt::Debug for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.id.fmt(f)
}
}
pub static X25519: Algorithm = Algorithm {
id: AlgorithmId::X25519,
};
pub static ECDH_P256: Algorithm = Algorithm {
id: AlgorithmId::EcdhP256,
};
pub static ECDH_P384: Algorithm = Algorithm {
id: AlgorithmId::EcdhP384,
};
pub static ECDH_P521: Algorithm = Algorithm {
id: AlgorithmId::EcdhP521,
};
pub struct PrivateKey(Private);
#[repr(u8)]
enum Private {
X25519(x25519::PrivateKey),
P256(p256::PrivateKey),
P384(p384::PrivateKey),
P521(p521::PrivateKey),
}
impl low::Zeroable for Private {}
impl PrivateKey {
pub fn generate(algorithm: &'static Algorithm) -> Result<Self, Error> {
Ok(Self(match algorithm.id {
AlgorithmId::X25519 => Private::X25519(x25519::PrivateKey::new_random()?),
AlgorithmId::EcdhP256 => Private::P256(p256::PrivateKey::new_random()?),
AlgorithmId::EcdhP384 => Private::P384(p384::PrivateKey::new_random()?),
AlgorithmId::EcdhP521 => Private::P521(p521::PrivateKey::new_random()?),
}))
}
pub fn public_key(&self) -> PublicKey {
let mut public = PublicKey {
bytes: [0; PublicKey::MAX_LEN],
len: 0,
};
let encoded: &[u8] = match &self.0 {
Private::X25519(key) => &key.public_key().as_bytes(),
Private::P256(key) => &key.public_key_uncompressed(),
Private::P384(key) => &key.public_key_uncompressed(),
Private::P521(key) => &key.public_key_uncompressed(),
};
public.bytes[..encoded.len()].copy_from_slice(encoded);
public.len = encoded.len();
public
}
pub fn agree(self, peer: &[u8]) -> Result<SharedSecret, Error> {
Ok(SharedSecret(match &self.0 {
Private::X25519(key) => {
Secret::X25519(key.diffie_hellman(&x25519::PublicKey::try_from_slice(peer)?)?)
}
Private::P256(key) => {
Secret::P256(key.diffie_hellman(&p256::PublicKey::from_x962_uncompressed(peer)?)?)
}
Private::P384(key) => {
Secret::P384(key.diffie_hellman(&p384::PublicKey::from_x962_uncompressed(peer)?)?)
}
Private::P521(key) => {
Secret::P521(key.diffie_hellman(&p521::PublicKey::from_x962_uncompressed(peer)?)?)
}
}))
}
}
impl Drop for PrivateKey {
fn drop(&mut self) {
low::zeroise_value(&mut self.0);
}
}
impl fmt::Debug for PrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PrivateKey").finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct PublicKey {
bytes: [u8; Self::MAX_LEN],
len: usize,
}
impl PublicKey {
const MAX_LEN: usize = p521::PublicKey::BYTES;
}
impl AsRef<[u8]> for PublicKey {
fn as_ref(&self) -> &[u8] {
&self.bytes[..self.len]
}
}
impl fmt::Debug for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("PublicKey").field(&self.as_ref()).finish()
}
}
pub struct SharedSecret(Secret);
#[repr(u8)]
enum Secret {
X25519(x25519::SharedSecret),
P256(p256::SharedSecret),
P384(p384::SharedSecret),
P521(p521::SharedSecret),
}
impl AsRef<[u8]> for SharedSecret {
fn as_ref(&self) -> &[u8] {
match &self.0 {
Secret::X25519(secret) => &secret.0,
Secret::P256(secret) => &secret.0,
Secret::P384(secret) => &secret.0,
Secret::P521(secret) => &secret.0,
}
}
}
impl low::Zeroable for Secret {}
impl Drop for SharedSecret {
fn drop(&mut self) {
low::zeroise_value(&mut self.0);
}
}
impl fmt::Debug for SharedSecret {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SharedSecret").finish_non_exhaustive()
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use serde::Deserialize;
use super::*;
use crate::error::KeyFormatError;
use crate::test::{Expected, wycheproof};
#[test]
fn agrees() {
for algorithm in [&X25519, &ECDH_P256, &ECDH_P384, &ECDH_P521] {
let alice = PrivateKey::generate(algorithm).unwrap();
let bob = PrivateKey::generate(algorithm).unwrap();
let alice_public = alice.public_key();
let bob_public = bob.public_key();
let alice_secret = alice.agree(bob_public.as_ref()).unwrap();
let bob_secret = bob.agree(alice_public.as_ref()).unwrap();
low::ct::public_slice(alice_secret.as_ref());
low::ct::public_slice(bob_secret.as_ref());
assert_eq!(alice_secret.as_ref(), bob_secret.as_ref());
}
}
#[derive(Deserialize, Debug)]
struct EcdhTest {
#[serde(rename = "tcId")]
id: usize,
flags: Vec<String>,
#[serde(with = "hex::serde")]
public: Vec<u8>,
#[serde(with = "hex::serde")]
private: Vec<u8>,
#[serde(with = "hex::serde")]
shared: Vec<u8>,
result: Expected,
}
impl EcdhTest {
fn has_flag(&self, flag: &str) -> bool {
self.flags.iter().any(|f| f == flag)
}
}
fn check_nist_ecdh(file: &str, agree: impl Fn(&EcdhTest) -> Result<Vec<u8>, Error>) {
for test in wycheproof::<EcdhTest>(file) {
let result = agree(&test);
if test.has_flag("CompressedPublic") || test.has_flag("CompressedPoint") {
assert_eq!(
result,
Err(KeyFormatError::Compressed.into()),
"{}",
test.id
);
continue;
}
match (test.result, &result) {
(Expected::Valid, Ok(shared)) => assert_eq!(shared, &test.shared, "{}", test.id),
(Expected::Invalid, Err(e))
if test.has_flag("InvalidCurveAttack")
&& *e == KeyFormatError::NotOnCurve.into() => {}
(Expected::Invalid, Err(e))
if test.has_flag("InvalidEncoding")
&& *e == KeyFormatError::WrongLength.into() => {}
_ => panic!("{}: expected {:?} got {:?}", test.id, test.result, result),
}
}
}
#[test]
fn wycheproof_p256() {
check_nist_ecdh("ecdh_secp256r1_ecpoint_test.json", |test| {
let private = p256::StaticPrivateKey::from_bytes(&test.private).unwrap();
let peer = p256::PublicKey::from_x962_uncompressed(&test.public)?;
Ok(low::ct::into_public(private.diffie_hellman(&peer)?.0).to_vec())
});
}
#[test]
fn wycheproof_p384() {
check_nist_ecdh("ecdh_secp384r1_ecpoint_test.json", |test| {
let private = p384::StaticPrivateKey::from_bytes(&test.private).unwrap();
let peer = p384::PublicKey::from_x962_uncompressed(&test.public)?;
Ok(low::ct::into_public(private.diffie_hellman(&peer)?.0).to_vec())
});
}
#[test]
fn wycheproof_p521() {
check_nist_ecdh("ecdh_secp521r1_ecpoint_test.json", |test| {
let private = p521::StaticPrivateKey::from_bytes(&test.private).unwrap();
let peer = p521::PublicKey::from_x962_uncompressed(&test.public)?;
Ok(low::ct::into_public(private.diffie_hellman(&peer)?.0).to_vec())
});
}
#[test]
fn wycheproof_x25519() {
for test in wycheproof::<EcdhTest>("x25519_test.json") {
let private = x25519::StaticPrivateKey::try_from_slice(&test.private).unwrap();
let result = x25519::PublicKey::try_from_slice(&test.public)
.and_then(|peer| private.diffie_hellman(&peer))
.map(|shared| low::ct::into_public(shared.0));
match (test.result, &result) {
(Expected::Acceptable, Err(e))
if test.has_flag("ZeroSharedSecret")
&& *e == KeyFormatError::SmallOrder.into() => {}
(Expected::Valid | Expected::Acceptable, Ok(shared)) => {
assert_eq!(&shared[..], &test.shared, "{}", test.id)
}
_ => panic!("{}: expected {:?} got {:?}", test.id, test.result, result),
}
}
}
mod x25519_crosscheck {
use aws_lc_rs::agreement::{self, PrivateKey, UnparsedPublicKey, X25519};
use proptest::prelude::*;
use super::*;
fn peer() -> impl Strategy<Value = [u8; 32]> {
let mut one = [0u8; 32];
one[0] = 1;
prop_oneof![
8 => any::<[u8; 32]>(),
1 => Just([0u8; 32]),
1 => Just(one),
]
}
proptest! {
#[test]
fn ecdh(private in any::<[u8; 32]>(), peer in peer()) {
let ours_private = private;
let ours = x25519::StaticPrivateKey::from_array(&ours_private);
let theirs = PrivateKey::from_private_key(&X25519, &private).unwrap();
let theirs_public = theirs.compute_public_key().unwrap();
prop_assert_eq!(&ours.public_key().as_bytes()[..], theirs_public.as_ref());
let ours_secret = ours
.diffie_hellman(&x25519::PublicKey::from_array(&peer))
.map(|secret| low::ct::into_public(secret.0));
let theirs_secret = agreement::agree(
&theirs,
UnparsedPublicKey::new(&X25519, peer),
(),
|secret| Ok(secret.to_vec()),
);
match (ours_secret, theirs_secret) {
(Ok(ours), Ok(theirs)) => prop_assert_eq!(&ours[..], &theirs[..]),
(Err(_), Err(())) => {}
(ours, theirs) => prop_assert!(
false,
"rind gave {:?}, aws-lc-rs {:?}",
ours.err(),
theirs.err()
),
}
}
}
}
}