diff --git a/Cargo.lock b/Cargo.lock index 1cf9a41f1..8b271acb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -552,6 +552,7 @@ dependencies = [ "hex-literal", "hkdf 0.13.0-pre.3", "hybrid-array", + "kem 0.3.0-pre.0 (registry+https://github.com/rust-lang/crates.io-index)", "pem-rfc7468 1.0.0-pre.0", "pkcs8 0.11.0-pre.0", "rand_core 0.6.4", @@ -844,6 +845,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "kem" +version = "0.3.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8645470337db67b01a7f966decf7d0bafedbae74147d33e641c67a91df239f" +dependencies = [ + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "libc" version = "0.2.153" diff --git a/elliptic-curve/Cargo.toml b/elliptic-curve/Cargo.toml index 388c12a1a..641469e7b 100644 --- a/elliptic-curve/Cargo.toml +++ b/elliptic-curve/Cargo.toml @@ -19,6 +19,7 @@ rust-version = "1.73" base16ct = "0.2" crypto-bigint = { version = "=0.6.0-pre.12", default-features = false, features = ["rand_core", "hybrid-array", "zeroize"] } hybrid-array = { version = "0.2.0-rc.8", default-features = false, features = ["zeroize"] } +kem = { version = "=0.3.0-pre.0", optional = true } rand_core = { version = "0.6.4", default-features = false } subtle = { version = "2", default-features = false } zeroize = { version = "1.7", default-features = false } @@ -64,6 +65,7 @@ bits = ["arithmetic", "ff/bits", "dep:tap"] dev = ["arithmetic", "dep:hex-literal", "pem", "pkcs8"] hash2curve = ["arithmetic", "digest"] ecdh = ["arithmetic", "digest", "dep:hkdf"] +ecdh-kem = ["ecdh", "kem"] group = ["dep:group", "ff"] hazmat = [] jwk = ["dep:base64ct", "dep:serde_json", "alloc", "serde", "zeroize/alloc"] diff --git a/elliptic-curve/src/ecdh_kem.rs b/elliptic-curve/src/ecdh_kem.rs new file mode 100644 index 000000000..9e31dbcac --- /dev/null +++ b/elliptic-curve/src/ecdh_kem.rs @@ -0,0 +1,53 @@ +//! # ECDH as a KEM +//! +//! This module turns the existing ECDH implementation into a suitable KEM by +//! modeling encapsulation of `g^x` as the generation of another ephemeral secret +//! `y`, computing the encapsulated ciphertext as `g^y`, and computing the shared +//! secret `g^xy`. Decapsulation of `x` is modelled as computing the shared secret +//! `g^xy` from `g^y`. +//! +//! # ECDH-KEM Usage +//! +//! ECDH-KEM allows for an unauthenticated key agreement protocol as follows +//! +//! 1. The client generates an [`EphemeralSecret`] value +//! 2. The client sends the corresponding [`PublicKey`] for their secret +//! 3. The server runs [`encapsulate`](Encapsulate::encapsulate) on the given +//! [`PublicKey`] and holds on to the resulting [`SharedSecret`] +//! 4. The client runs [`decapsulate`](Decapsulate::decapsulate) on the +//! "encapsulated" [`PublicKey`] returned by the server and uses the resulting +//! [`SharedSecret`] + +use crate::ecdh::{EphemeralSecret, SharedSecret}; +use crate::{CurveArithmetic, PublicKey}; +use kem::{Decapsulate, Encapsulate}; + +impl Decapsulate, SharedSecret> for EphemeralSecret +where + C: CurveArithmetic, +{ + type Error = (); + + fn decapsulate(&self, encapsulated_key: &PublicKey) -> Result, Self::Error> { + Ok(self.diffie_hellman(encapsulated_key)) + } +} + +impl Encapsulate, SharedSecret> for EphemeralSecret +where + C: CurveArithmetic, +{ + type Error = (); + + fn encapsulate( + &self, + rng: &mut impl rand_core::CryptoRngCore, + ) -> Result<(PublicKey, SharedSecret), Self::Error> { + // generate another ephemeral ecdh secret + let secret = EphemeralSecret::::random(rng); + let pk = secret.public_key(); + let ss = self.diffie_hellman(&pk); + + Ok((pk, ss)) + } +} diff --git a/elliptic-curve/src/lib.rs b/elliptic-curve/src/lib.rs index 32ce2fa1a..0459b745c 100644 --- a/elliptic-curve/src/lib.rs +++ b/elliptic-curve/src/lib.rs @@ -88,6 +88,8 @@ pub mod scalar; pub mod dev; #[cfg(feature = "ecdh")] pub mod ecdh; +#[cfg(feature = "ecdh-kem")] +pub mod ecdh_kem; #[cfg(feature = "hash2curve")] pub mod hash2curve; #[cfg(feature = "arithmetic")]