#![forbid(unsafe_code)]
use crate::backend::{require_supported, Operation};
use crate::error::{Error, Result};
use crate::key::{RustCryptoKey, SoftwareKey};
use zeroize::Zeroizing;
pub fn agree(
curve: crate::algorithm::EcCurve,
peer_public: &[u8],
private: &SoftwareKey,
) -> Result<Vec<u8>> {
match (curve, private.inner()) {
(
crate::algorithm::EcCurve::P256,
RustCryptoKey::EcP256 {
private: Some(key), ..
},
) => {
let scalar = Zeroizing::new(key.to_bytes());
let key = p256::SecretKey::from_slice(scalar.as_slice())
.map_err(|e| Error::Key(format!("P-256 private conversion failed: {e}")))?;
ecdh_p256(peer_public, &key)
}
(
crate::algorithm::EcCurve::P384,
RustCryptoKey::EcP384 {
private: Some(key), ..
},
) => {
let scalar = Zeroizing::new(key.to_bytes());
let key = p384::SecretKey::from_slice(scalar.as_slice())
.map_err(|e| Error::Key(format!("P-384 private conversion failed: {e}")))?;
ecdh_p384(peer_public, &key)
}
(
crate::algorithm::EcCurve::P521,
RustCryptoKey::EcP521 {
private: Some(key), ..
},
) => {
let scalar = Zeroizing::new(key.to_bytes());
let key = p521::SecretKey::from_slice(scalar.as_slice())
.map_err(|e| Error::Key(format!("P-521 private conversion failed: {e}")))?;
ecdh_p521(peer_public, &key)
}
_ => Err(Error::Key(format!(
"private {curve:?} key required for ECDH"
))),
}
}
pub fn agree_x25519(peer_public: &[u8], private: &SoftwareKey) -> Result<Vec<u8>> {
match private.inner() {
RustCryptoKey::X25519 {
private: Some(key), ..
} => ecdh_x25519(peer_public, key),
_ => Err(Error::Key("X25519 private key required".into())),
}
}
pub fn agree_dh(peer_public: &[u8], private: &SoftwareKey) -> Result<Vec<u8>> {
require_supported(Operation::DhAgreement)?;
match private.inner() {
RustCryptoKey::Dh {
private: Some(exponent),
parameters,
} => crate::hazmat::dh::compute(
peer_public,
exponent,
parameters.modulus(),
parameters.subgroup_order(),
),
_ => Err(Error::Key("finite-field DH private key required".into())),
}
}
pub fn ecdh_p256(originator_public: &[u8], recipient_private: &p256::SecretKey) -> Result<Vec<u8>> {
require_supported(Operation::Agreement(crate::algorithm::EcCurve::P256))?;
use p256::elliptic_curve::sec1::FromEncodedPoint;
let encoded_point = p256::EncodedPoint::from_bytes(originator_public)
.map_err(|e| Error::Key(format!("invalid P-256 public key: {e}")))?;
let public_key: p256::PublicKey =
Option::from(p256::PublicKey::from_encoded_point(&encoded_point))
.ok_or_else(|| Error::Key("invalid P-256 public key point".into()))?;
let shared_secret = p256::ecdh::diffie_hellman(
recipient_private.to_nonzero_scalar(),
public_key.as_affine(),
);
Ok(shared_secret.raw_secret_bytes().to_vec())
}
pub fn ecdh_p384(originator_public: &[u8], recipient_private: &p384::SecretKey) -> Result<Vec<u8>> {
require_supported(Operation::Agreement(crate::algorithm::EcCurve::P384))?;
use p384::elliptic_curve::sec1::FromEncodedPoint;
let encoded_point = p384::EncodedPoint::from_bytes(originator_public)
.map_err(|e| Error::Key(format!("invalid P-384 public key: {e}")))?;
let public_key: p384::PublicKey =
Option::from(p384::PublicKey::from_encoded_point(&encoded_point))
.ok_or_else(|| Error::Key("invalid P-384 public key point".into()))?;
let shared_secret = p384::ecdh::diffie_hellman(
recipient_private.to_nonzero_scalar(),
public_key.as_affine(),
);
Ok(shared_secret.raw_secret_bytes().to_vec())
}
pub fn ecdh_p521(originator_public: &[u8], recipient_private: &p521::SecretKey) -> Result<Vec<u8>> {
require_supported(Operation::Agreement(crate::algorithm::EcCurve::P521))?;
use p521::elliptic_curve::sec1::FromEncodedPoint;
let encoded_point = p521::EncodedPoint::from_bytes(originator_public)
.map_err(|e| Error::Key(format!("invalid P-521 public key: {e}")))?;
let public_key: p521::PublicKey =
Option::from(p521::PublicKey::from_encoded_point(&encoded_point))
.ok_or_else(|| Error::Key("invalid P-521 public key point".into()))?;
let shared_secret = p521::ecdh::diffie_hellman(
recipient_private.to_nonzero_scalar(),
public_key.as_affine(),
);
Ok(shared_secret.raw_secret_bytes().to_vec())
}
pub fn ecdh_x25519(originator_public: &[u8], recipient_private: &[u8]) -> Result<Vec<u8>> {
require_supported(Operation::X25519Agreement)?;
if originator_public.len() != 32 {
return Err(Error::Key(format!(
"invalid X25519 public key length: {} (expected 32)",
originator_public.len()
)));
}
if recipient_private.len() != 32 {
return Err(Error::Key(format!(
"invalid X25519 private key length: {} (expected 32)",
recipient_private.len()
)));
}
let mut pub_bytes = [0u8; 32];
pub_bytes.copy_from_slice(originator_public);
let their_public = x25519_dalek::PublicKey::from(pub_bytes);
let mut priv_bytes = [0u8; 32];
priv_bytes.copy_from_slice(recipient_private);
let my_secret = x25519_dalek::StaticSecret::from(priv_bytes);
{
use zeroize::Zeroize;
priv_bytes.zeroize();
}
let shared_secret = my_secret.diffie_hellman(&their_public);
if !shared_secret.was_contributory() {
return Err(Error::Key(
"X25519 peer public key is a low-order point (shared secret is identity)".into(),
));
}
Ok(shared_secret.as_bytes().to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn x25519_roundtrip() {
let alice_secret = x25519_dalek::StaticSecret::random_from_rng(rand::rngs::OsRng);
let alice_public = x25519_dalek::PublicKey::from(&alice_secret);
let bob_secret = x25519_dalek::StaticSecret::random_from_rng(rand::rngs::OsRng);
let bob_public = x25519_dalek::PublicKey::from(&bob_secret);
let shared_alice = ecdh_x25519(bob_public.as_bytes(), alice_secret.as_bytes()).unwrap();
let shared_bob = ecdh_x25519(alice_public.as_bytes(), bob_secret.as_bytes()).unwrap();
assert_eq!(shared_alice, shared_bob);
assert_eq!(shared_alice.len(), 32);
}
#[test]
fn x25519_rejects_low_order_public_key() {
let secret = x25519_dalek::StaticSecret::random_from_rng(rand::rngs::OsRng);
let low_order_pub = [0u8; 32];
let err = ecdh_x25519(&low_order_pub, secret.as_bytes()).unwrap_err();
assert!(
err.to_string().contains("low-order"),
"unexpected error: {err}"
);
}
#[test]
fn finite_field_dh_uses_opaque_private_key() {
let private =
SoftwareKey::from_dh_parameters(&[23], &[4], Some(&[11]), Some(&[5]), &[12]).unwrap();
assert_eq!(agree_dh(&[18], &private).unwrap(), vec![3]);
}
#[test]
fn x25519_invalid_public_key_length() {
let secret = [0u8; 32];
let short_pub = [0u8; 16];
let err = ecdh_x25519(&short_pub, &secret).unwrap_err();
assert!(
err.to_string().contains("invalid X25519 public key length"),
"unexpected error: {err}"
);
}
#[test]
fn x25519_invalid_private_key_length() {
let pub_key = [0u8; 32];
let short_priv = [0u8; 16];
let err = ecdh_x25519(&pub_key, &short_priv).unwrap_err();
assert!(
err.to_string()
.contains("invalid X25519 private key length"),
"unexpected error: {err}"
);
}
#[test]
fn x25519_deterministic() {
let alice_secret = x25519_dalek::StaticSecret::random_from_rng(rand::rngs::OsRng);
let bob_secret = x25519_dalek::StaticSecret::random_from_rng(rand::rngs::OsRng);
let bob_public = x25519_dalek::PublicKey::from(&bob_secret);
let shared1 = ecdh_x25519(bob_public.as_bytes(), alice_secret.as_bytes()).unwrap();
let shared2 = ecdh_x25519(bob_public.as_bytes(), alice_secret.as_bytes()).unwrap();
assert_eq!(shared1, shared2);
}
#[test]
fn p256_roundtrip() {
use p256::elliptic_curve::sec1::ToEncodedPoint;
let alice_secret = p256::SecretKey::random(&mut rand::rngs::OsRng);
let alice_public = alice_secret.public_key();
let bob_secret = p256::SecretKey::random(&mut rand::rngs::OsRng);
let bob_public = bob_secret.public_key();
let shared_alice =
ecdh_p256(bob_public.to_encoded_point(false).as_bytes(), &alice_secret).unwrap();
let shared_bob =
ecdh_p256(alice_public.to_encoded_point(false).as_bytes(), &bob_secret).unwrap();
assert_eq!(shared_alice, shared_bob);
assert_eq!(shared_alice.len(), 32);
}
#[test]
fn p384_roundtrip() {
use p384::elliptic_curve::sec1::ToEncodedPoint;
let alice_secret = p384::SecretKey::random(&mut rand::rngs::OsRng);
let alice_public = alice_secret.public_key();
let bob_secret = p384::SecretKey::random(&mut rand::rngs::OsRng);
let bob_public = bob_secret.public_key();
let shared_alice =
ecdh_p384(bob_public.to_encoded_point(false).as_bytes(), &alice_secret).unwrap();
let shared_bob =
ecdh_p384(alice_public.to_encoded_point(false).as_bytes(), &bob_secret).unwrap();
assert_eq!(shared_alice, shared_bob);
assert_eq!(shared_alice.len(), 48);
}
#[test]
fn p521_roundtrip() {
use p521::elliptic_curve::sec1::ToEncodedPoint;
let alice_secret = p521::SecretKey::random(&mut rand::rngs::OsRng);
let alice_public = alice_secret.public_key();
let bob_secret = p521::SecretKey::random(&mut rand::rngs::OsRng);
let bob_public = bob_secret.public_key();
let shared_alice =
ecdh_p521(bob_public.to_encoded_point(false).as_bytes(), &alice_secret).unwrap();
let shared_bob =
ecdh_p521(alice_public.to_encoded_point(false).as_bytes(), &bob_secret).unwrap();
assert_eq!(shared_alice, shared_bob);
assert_eq!(shared_alice.len(), 66);
}
}