use core::fmt;
use p256::elliptic_curve::sec1::ToSec1Point as _;
use thiserror::Error as ThisError;
use x25519_dalek::{PublicKey as DalekPublicKey, StaticSecret as DalekStaticSecret};
use zeroize::{Zeroize, Zeroizing};
use crate::random::{RandomError, fill};
#[derive(Clone, Copy, Debug, Eq, PartialEq, ThisError)]
pub enum KeyAgreementError {
#[error(transparent)]
Random(#[from] RandomError),
#[error("invalid peer key")]
InvalidPeerKey,
#[error("invalid generated key representation")]
InvalidGeneratedKey,
}
macro_rules! public_key {
($(#[$metadata:meta])* $name:ident, $length:expr) => {
$(#[$metadata])*
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct $name([u8; $length]);
impl $name {
pub const fn as_bytes(&self) -> &[u8; $length] {
&self.0
}
}
impl AsRef<[u8]> for $name {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<[u8; $length]> for $name {
fn from(bytes: [u8; $length]) -> Self {
Self(bytes)
}
}
impl TryFrom<&[u8]> for $name {
type Error = KeyAgreementError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
<[u8; $length]>::try_from(bytes)
.map(Self)
.map_err(|_| KeyAgreementError::InvalidPeerKey)
}
}
};
}
macro_rules! shared_secret {
($(#[$metadata:meta])* $name:ident, $length:expr) => {
$(#[$metadata])*
pub struct $name([u8; $length]);
impl $name {
pub const fn as_bytes(&self) -> &[u8; $length] {
&self.0
}
}
impl AsRef<[u8]> for $name {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl fmt::Debug for $name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_struct(stringify!($name)).finish_non_exhaustive()
}
}
impl Drop for $name {
fn drop(&mut self) {
self.0.zeroize();
}
}
};
}
public_key!(
X25519PublicKey,
32
);
public_key!(
P256PublicKey,
65
);
public_key!(
P384PublicKey,
97
);
shared_secret!(
X25519SharedSecret,
32
);
shared_secret!(
P256SharedSecret,
32
);
shared_secret!(
P384SharedSecret,
48
);
pub struct X25519Ephemeral {
secret: DalekStaticSecret,
public_key: X25519PublicKey,
}
impl X25519Ephemeral {
pub fn generate() -> Result<Self, KeyAgreementError> {
let mut bytes = Zeroizing::new([0u8; 32]);
fill(bytes.as_mut())?;
let secret = DalekStaticSecret::from(*bytes);
let public_key = X25519PublicKey::from(DalekPublicKey::from(&secret).to_bytes());
Ok(Self { secret, public_key })
}
pub const fn public_key(&self) -> X25519PublicKey {
self.public_key
}
pub fn complete(self, peer: &X25519PublicKey) -> Result<X25519SharedSecret, KeyAgreementError> {
let shared = self
.secret
.diffie_hellman(&DalekPublicKey::from(*peer.as_bytes()));
if !shared.was_contributory() {
return Err(KeyAgreementError::InvalidPeerKey);
}
Ok(X25519SharedSecret(shared.to_bytes()))
}
}
impl fmt::Debug for X25519Ephemeral {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("X25519Ephemeral")
.field("public_key", &self.public_key)
.finish_non_exhaustive()
}
}
pub struct P256Ephemeral {
secret: p256::SecretKey,
public_key: P256PublicKey,
}
impl P256Ephemeral {
pub fn generate() -> Result<Self, KeyAgreementError> {
let secret = loop {
let mut candidate = Zeroizing::new([0u8; 32]);
fill(candidate.as_mut())?;
if let Ok(secret) = p256::SecretKey::from_slice(candidate.as_ref()) {
break secret;
}
};
let encoded = secret.public_key().to_sec1_point(false);
let public_key = P256PublicKey::try_from(encoded.as_bytes())
.map_err(|_| KeyAgreementError::InvalidGeneratedKey)?;
Ok(Self { secret, public_key })
}
pub const fn public_key(&self) -> P256PublicKey {
self.public_key
}
pub fn complete(self, peer: &P256PublicKey) -> Result<P256SharedSecret, KeyAgreementError> {
if peer.as_bytes().first() != Some(&0x04) {
return Err(KeyAgreementError::InvalidPeerKey);
}
let peer = p256::PublicKey::from_sec1_bytes(peer.as_ref())
.map_err(|_| KeyAgreementError::InvalidPeerKey)?;
let shared = p256::ecdh::diffie_hellman(self.secret.to_nonzero_scalar(), peer.as_affine());
let raw = shared.raw_secret_bytes();
let bytes = <[u8; 32]>::try_from(raw.as_slice())
.map_err(|_| KeyAgreementError::InvalidGeneratedKey)?;
Ok(P256SharedSecret(bytes))
}
}
impl fmt::Debug for P256Ephemeral {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("P256Ephemeral")
.field("public_key", &self.public_key)
.finish_non_exhaustive()
}
}
pub struct P384Ephemeral {
secret: p384::SecretKey,
public_key: P384PublicKey,
}
impl P384Ephemeral {
pub fn generate() -> Result<Self, KeyAgreementError> {
let secret = loop {
let mut candidate = Zeroizing::new([0u8; 48]);
fill(candidate.as_mut())?;
if let Ok(secret) = p384::SecretKey::from_slice(candidate.as_ref()) {
break secret;
}
};
let encoded = secret.public_key().to_sec1_point(false);
let public_key = P384PublicKey::try_from(encoded.as_bytes())
.map_err(|_| KeyAgreementError::InvalidGeneratedKey)?;
Ok(Self { secret, public_key })
}
pub const fn public_key(&self) -> P384PublicKey {
self.public_key
}
pub fn complete(self, peer: &P384PublicKey) -> Result<P384SharedSecret, KeyAgreementError> {
if peer.as_bytes().first() != Some(&0x04) {
return Err(KeyAgreementError::InvalidPeerKey);
}
let peer = p384::PublicKey::from_sec1_bytes(peer.as_ref())
.map_err(|_| KeyAgreementError::InvalidPeerKey)?;
let shared = p384::ecdh::diffie_hellman(self.secret.to_nonzero_scalar(), peer.as_affine());
let raw = shared.raw_secret_bytes();
let bytes = <[u8; 48]>::try_from(raw.as_slice())
.map_err(|_| KeyAgreementError::InvalidGeneratedKey)?;
Ok(P384SharedSecret(bytes))
}
}
impl fmt::Debug for P384Ephemeral {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("P384Ephemeral")
.field("public_key", &self.public_key)
.finish_non_exhaustive()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn x25519_round_trip_and_low_order_rejection() {
let alice = X25519Ephemeral::generate().unwrap();
let bob = X25519Ephemeral::generate().unwrap();
let alice_public = alice.public_key();
let bob_public = bob.public_key();
let alice_secret = alice.complete(&bob_public).unwrap();
let bob_secret = bob.complete(&alice_public).unwrap();
assert_eq!(alice_secret.as_ref(), bob_secret.as_ref());
let ephemeral = X25519Ephemeral::generate().unwrap();
assert!(ephemeral.complete(&X25519PublicKey::from([0; 32])).is_err());
}
#[test]
fn p256_round_trip_and_invalid_point_rejection() {
let alice = P256Ephemeral::generate().unwrap();
let bob = P256Ephemeral::generate().unwrap();
let alice_public = alice.public_key();
let bob_public = bob.public_key();
assert_eq!(
alice.complete(&bob_public).unwrap().as_ref(),
bob.complete(&alice_public).unwrap().as_ref()
);
let ephemeral = P256Ephemeral::generate().unwrap();
assert!(ephemeral.complete(&P256PublicKey::from([0; 65])).is_err());
}
#[test]
fn p384_round_trip_and_invalid_point_rejection() {
let alice = P384Ephemeral::generate().unwrap();
let bob = P384Ephemeral::generate().unwrap();
let alice_public = alice.public_key();
let bob_public = bob.public_key();
assert_eq!(
alice.complete(&bob_public).unwrap().as_ref(),
bob.complete(&alice_public).unwrap().as_ref()
);
let ephemeral = P384Ephemeral::generate().unwrap();
assert!(ephemeral.complete(&P384PublicKey::from([0; 97])).is_err());
}
#[test]
fn public_key_newtypes_reject_wrong_lengths() {
assert!(X25519PublicKey::try_from([0u8; 31].as_slice()).is_err());
assert!(P256PublicKey::try_from([0u8; 64].as_slice()).is_err());
assert!(P384PublicKey::try_from([0u8; 96].as_slice()).is_err());
}
}