diff --git a/Cargo.lock b/Cargo.lock index 3440075..429860d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,8 +403,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" version = "0.14.0-rc.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0f6cc67cc39a00bce2c6f8f8aced0e8c0a06eb1a30f9dd2a9c9f4618bdf3b4" +source = "git+https://github.com/RustCrypto/traits#43d020754c46cd81ca8d13c980f48215aa362627" dependencies = [ "base16ct", "crypto-bigint", diff --git a/Cargo.toml b/Cargo.toml index 254da3f..3a60872 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,5 @@ debug = true [patch.crates-io] ml-kem = { path = "./ml-kem" } module-lattice = { path = "./module-lattice" } + +elliptic-curve = { git = "https://github.com/RustCrypto/traits" } diff --git a/dhkem/Cargo.toml b/dhkem/Cargo.toml index e1b9af3..fc088c5 100644 --- a/dhkem/Cargo.toml +++ b/dhkem/Cargo.toml @@ -41,7 +41,7 @@ k256 = ["dep:k256", "ecdh"] p256 = ["dep:p256", "ecdh"] p384 = ["dep:p384", "ecdh"] p521 = ["dep:p521", "ecdh"] -x25519 = ["dep:x25519", "x25519/reusable_secrets"] +x25519 = ["dep:x25519", "x25519/static_secrets"] zeroize = ["dep:zeroize"] [package.metadata.docs.rs] diff --git a/dhkem/src/ecdh_kem.rs b/dhkem/src/ecdh_kem.rs index 92e5e3d..8c5dae3 100644 --- a/dhkem/src/ecdh_kem.rs +++ b/dhkem/src/ecdh_kem.rs @@ -3,7 +3,7 @@ use crate::{DecapsulationKey, EncapsulationKey}; use core::marker::PhantomData; use elliptic_curve::{ - AffinePoint, CurveArithmetic, Error, FieldBytesSize, PublicKey, + AffinePoint, CurveArithmetic, Error, FieldBytes, FieldBytesSize, PublicKey, SecretKey, ecdh::EphemeralSecret, sec1::{ FromEncodedPoint, ModulusSize, ToEncodedPoint, UncompressedPoint, UncompressedPointSize, @@ -15,16 +15,6 @@ use kem::{ }; use rand_core::{CryptoRng, TryCryptoRng}; -/// Elliptic Curve Diffie-Hellman Decapsulation Key (i.e. secret decryption key) -/// -/// Generic around an elliptic curve `C`. -pub type EcdhDecapsulationKey = DecapsulationKey, PublicKey>; - -/// Elliptic Curve Diffie-Hellman Encapsulation Key (i.e. public encryption key) -/// -/// Generic around an elliptic curve `C`. -pub type EcdhEncapsulationKey = EncapsulationKey>; - /// Generic Elliptic Curve Diffie-Hellman KEM adapter compatible with curves implemented using /// traits from the `elliptic-curve` crate. /// @@ -45,13 +35,94 @@ where type SharedKeySize = FieldBytesSize; } +/// Elliptic Curve Diffie-Hellman Decapsulation Key (i.e. secret decryption key) +/// +/// Generic around an elliptic curve `C`. +pub type EcdhDecapsulationKey = DecapsulationKey, PublicKey>; + +impl KeySizeUser for EcdhDecapsulationKey +where + C: CurveArithmetic, +{ + type KeySize = FieldBytesSize; +} + +/// From [RFC9810 §7.1.2]: `SerializePrivateKey` and `DeserializePrivateKey`: +/// +/// > DeserializePrivateKey() performs the Octet-String-to-Field-Element conversion +/// > according to [SECG]. +/// +/// [RFC9810 §7.1.2]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.2 +/// [SECG]: https://www.secg.org/sec1-v2.pdf +impl TryKeyInit for EcdhDecapsulationKey +where + C: CurveArithmetic, +{ + fn new(key: &FieldBytes) -> Result { + SecretKey::from_bytes(key) + .map(Into::into) + .map_err(|_| InvalidKey) + } +} + +/// From [RFC9810 §7.1.2]: `SerializePrivateKey` and `DeserializePrivateKey`: +/// +/// > the SerializePrivateKey() function of the KEM performs the Field-Element-to-Octet-String +/// > conversion according to [SECG]. If the private key is an integer outside the range +/// > `[0, order-1]`, where order is the order of the curve being used, the private key MUST be +/// > reduced to its representative in `[0, order-1]` before being serialized. +/// +/// [RFC9810 §7.1.2]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.2 +/// [SECG]: https://www.secg.org/sec1-v2.pdf +impl KeyExport for EcdhDecapsulationKey +where + C: CurveArithmetic, +{ + fn to_bytes(&self) -> FieldBytes { + self.dk.to_bytes() + } +} + +impl Generate for EcdhDecapsulationKey +where + C: CurveArithmetic, + FieldBytesSize: ModulusSize, +{ + fn try_generate_from_rng(rng: &mut R) -> Result { + Ok(SecretKey::try_generate_from_rng(rng)?.into()) + } +} + +impl TryDecapsulate> for EcdhDecapsulationKey +where + C: CurveArithmetic, + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, +{ + type Error = Error; + + fn try_decapsulate( + &self, + encapsulated_key: &Ciphertext>, + ) -> Result>, Error> { + let encapsulated_key = PublicKey::::from_sec1_bytes(encapsulated_key)?; + let shared_secret = self.dk.diffie_hellman(&encapsulated_key); + Ok(shared_secret.raw_secret_bytes().clone()) + } +} + +/// Elliptic Curve Diffie-Hellman Encapsulation Key (i.e. public encryption key) +/// +/// Generic around an elliptic curve `C`. +pub type EcdhEncapsulationKey = EncapsulationKey>; + /// From [RFC9810 §7.1.1]: `SerializePublicKey` and `DeserializePublicKey`: /// /// > For P-256, P-384, and P-521, the SerializePublicKey() function of the /// > KEM performs the uncompressed Elliptic-Curve-Point-to-Octet-String /// > conversion according to [SECG]. /// -/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#name-serializepublickey-and-dese +/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.1 /// [SECG]: https://www.secg.org/sec1-v2.pdf impl KeySizeUser for EcdhEncapsulationKey where @@ -66,7 +137,7 @@ where /// > DeserializePublicKey() performs the uncompressed /// > Octet-String-to-Elliptic-Curve-Point conversion. /// -/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#name-serializepublickey-and-dese +/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.1 impl TryKeyInit for EcdhEncapsulationKey where C: CurveArithmetic, @@ -86,7 +157,7 @@ where /// > KEM performs the uncompressed Elliptic-Curve-Point-to-Octet-String /// > conversion according to [SECG]. /// -/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#name-serializepublickey-and-dese +/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.1 /// [SECG]: https://www.secg.org/sec1-v2.pdf impl KeyExport for EcdhEncapsulationKey where @@ -95,20 +166,7 @@ where AffinePoint: FromEncodedPoint + ToEncodedPoint, { fn to_bytes(&self) -> UncompressedPoint { - // TODO(tarcieri): self.0.to_uncompressed_point() - let mut ret = UncompressedPoint::::default(); - ret.copy_from_slice(self.to_encoded_point(false).as_bytes()); - ret - } -} - -impl Generate for EcdhDecapsulationKey -where - C: CurveArithmetic, - FieldBytesSize: ModulusSize, -{ - fn try_generate_from_rng(rng: &mut R) -> Result { - Ok(EphemeralSecret::try_generate_from_rng(rng)?.into()) + self.0.to_uncompressed_point() } } @@ -133,21 +191,3 @@ where (pk, ss.raw_secret_bytes().clone()) } } - -impl TryDecapsulate> for EcdhDecapsulationKey -where - C: CurveArithmetic, - FieldBytesSize: ModulusSize, - AffinePoint: FromEncodedPoint + ToEncodedPoint, -{ - type Error = Error; - - fn try_decapsulate( - &self, - encapsulated_key: &Ciphertext>, - ) -> Result>, Error> { - let encapsulated_key = PublicKey::::from_sec1_bytes(encapsulated_key)?; - let shared_secret = self.dk.diffie_hellman(&encapsulated_key); - Ok(shared_secret.raw_secret_bytes().clone()) - } -} diff --git a/dhkem/src/x25519_kem.rs b/dhkem/src/x25519_kem.rs index bc3ab05..61813b5 100644 --- a/dhkem/src/x25519_kem.rs +++ b/dhkem/src/x25519_kem.rs @@ -1,20 +1,11 @@ use crate::{DecapsulationKey, EncapsulationKey}; use kem::{ - Decapsulate, Encapsulate, Generate, InvalidKey, Kem, Key, KeyExport, KeySizeUser, TryKeyInit, - common::array::Array, consts::U32, + Decapsulate, Encapsulate, Generate, InvalidKey, Kem, Key, KeyExport, KeyInit, KeySizeUser, + TryKeyInit, + common::array::{Array, sizes::U32}, }; -use rand_core::{CryptoRng, TryCryptoRng, UnwrapErr}; -use x25519::{PublicKey, ReusableSecret}; - -/// Elliptic Curve Diffie-Hellman Decapsulation Key (i.e. secret decryption key) -/// -/// Generic around an elliptic curve `C`. -pub type X25519DecapsulationKey = DecapsulationKey; - -/// Elliptic Curve Diffie-Hellman Encapsulation Key (i.e. public encryption key) -/// -/// Generic around an elliptic curve `C`. -pub type X25519EncapsulationKey = EncapsulationKey; +use rand_core::{CryptoRng, TryCryptoRng}; +use x25519::{PublicKey, StaticSecret}; /// X25519 ciphertexts are compressed Montgomery x/u-coordinates. type Ciphertext = Array; @@ -35,6 +26,59 @@ impl Kem for X25519Kem { type SharedKeySize = U32; } +/// Elliptic Curve Diffie-Hellman Decapsulation Key (i.e. secret decryption key) +/// +/// Generic around an elliptic curve `C`. +pub type X25519DecapsulationKey = DecapsulationKey; + +impl KeySizeUser for X25519DecapsulationKey { + type KeySize = U32; +} + +/// From [RFC9810 §7.1.2]: `SerializePrivateKey` and `DeserializePrivateKey`: +/// +/// > For X25519 and X448, private keys are identical to their byte string representation, +/// > so little processing has to be done. [...] +/// > DeserializePrivateKey() function MUST clamp its input +/// +/// [RFC9810 §7.1.2]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.2 +impl KeyInit for X25519DecapsulationKey { + fn new(key: &Key) -> Self { + StaticSecret::from(key.0).into() + } +} + +/// From [RFC9810 §7.1.2]: `SerializePrivateKey` and `DeserializePrivateKey`: +/// +/// > For X25519 and X448, private keys are identical to their byte string representation, +/// > so little processing has to be done. The SerializePrivateKey() function MUST clamp its output +/// +/// [RFC9810 §7.1.2]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.2 +impl KeyExport for X25519DecapsulationKey { + fn to_bytes(&self) -> Key { + self.dk.to_bytes().into() + } +} + +impl Generate for X25519DecapsulationKey { + fn try_generate_from_rng(rng: &mut R) -> Result { + let key = Key::::try_generate_from_rng(rng)?; + Ok(StaticSecret::from(key.0).into()) + } +} + +impl Decapsulate for X25519DecapsulationKey { + fn decapsulate(&self, encapsulated_key: &Ciphertext) -> SharedKey { + let public_key = PublicKey::from(encapsulated_key.0); + self.dk.diffie_hellman(&public_key).to_bytes().into() + } +} + +/// Elliptic Curve Diffie-Hellman Encapsulation Key (i.e. public encryption key) +/// +/// Generic around an elliptic curve `C`. +pub type X25519EncapsulationKey = EncapsulationKey; + /// From [RFC9810 §7.1.1]: `SerializePublicKey` and `DeserializePublicKey`: /// /// > For X25519 and X448, the SerializePublicKey() and @@ -72,31 +116,15 @@ impl KeyExport for X25519EncapsulationKey { } } -impl Generate for X25519DecapsulationKey { - fn try_generate_from_rng(rng: &mut R) -> Result { - // TODO(tarcieri): don't panic! Fallible `ReusableSecret` generation? - Ok(Self::from(ReusableSecret::random_from_rng(&mut UnwrapErr( - rng, - )))) - } -} - impl Encapsulate for X25519EncapsulationKey { fn encapsulate_with_rng(&self, rng: &mut R) -> (Ciphertext, SharedKey) where R: CryptoRng + ?Sized, { // ECDH encapsulation involves creating a new ephemeral key pair and then doing DH - let sk = ReusableSecret::random_from_rng(rng); + let sk = StaticSecret::random_from_rng(rng); let pk = PublicKey::from(&sk); let ss = sk.diffie_hellman(&self.0); (pk.to_bytes().into(), ss.to_bytes().into()) } } - -impl Decapsulate for X25519DecapsulationKey { - fn decapsulate(&self, encapsulated_key: &Ciphertext) -> SharedKey { - let public_key = PublicKey::from(encapsulated_key.0); - self.dk.diffie_hellman(&public_key).to_bytes().into() - } -} diff --git a/dhkem/tests/hpke_p256_test.rs b/dhkem/tests/hpke_p256_test.rs index 3663ae7..c426a6a 100644 --- a/dhkem/tests/hpke_p256_test.rs +++ b/dhkem/tests/hpke_p256_test.rs @@ -2,10 +2,9 @@ use core::convert::Infallible; use dhkem::NistP256DecapsulationKey; -use elliptic_curve::Generate; use hex_literal::hex; use hkdf::Hkdf; -use kem::{Encapsulate, KeyExport, TryDecapsulate}; +use kem::{Encapsulate, KeyExport, TryDecapsulate, TryKeyInit}; use rand_core::{TryCryptoRng, TryRng}; use sha2::Sha256; @@ -68,6 +67,7 @@ fn extract_and_expand(shared_secret: &[u8], kem_context: &[u8]) -> Vec { #[test] // section A.3.1 https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.3.1 fn test_dhkem_p256_hkdf_sha256() { + let example_key = hex!("f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2"); let example_pke = hex!( "04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b32\ 5ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4" @@ -79,10 +79,7 @@ fn test_dhkem_p256_hkdf_sha256() { let example_shared_secret = hex!("c0d26aeab536609a572b07695d933b589dcf363ff9d93c93adea537aeabb8cb8"); - let skr = NistP256DecapsulationKey::try_generate_from_rng(&mut ConstantRng(&hex!( - "f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2" - ))) - .unwrap(); + let skr = NistP256DecapsulationKey::new(&example_key.into()).unwrap(); let pkr = skr.as_ref(); assert_eq!(&pkr.to_bytes(), &example_pkr);