From cd56109700e95c7098d24e329ff098517f810d31 Mon Sep 17 00:00:00 2001 From: DanGould Date: Sat, 10 Aug 2024 12:56:26 -0400 Subject: [PATCH] Remove non-secp256k1 features --- .github/workflows/ci.yml | 24 -- Cargo.toml | 24 +- README.md | 27 +- benches/benches.rs | 23 +- examples/agility.rs | 762 -------------------------------------- examples/client_server.rs | 26 +- src/aead.rs | 116 ------ src/dhkex.rs | 6 - src/dhkex/ecdh_nist.rs | 608 ------------------------------ src/dhkex/x25519.rs | 227 ------------ src/kat_tests.rs | 61 +-- src/kem.rs | 40 -- src/kem/dhkem.rs | 55 --- src/lib.rs | 10 +- src/setup.rs | 92 ----- src/single_shot.rs | 40 -- 16 files changed, 40 insertions(+), 2101 deletions(-) delete mode 100644 examples/agility.rs delete mode 100644 src/dhkex/ecdh_nist.rs delete mode 100644 src/dhkex/x25519.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9116b58..c942a2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,30 +37,6 @@ jobs: RUSTFLAGS: -D warnings -A dead_code -A unused_imports run: cargo test --no-default-features --features="secp" - - name: Run cargo test with just X25519 enabled - env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: -D warnings -A dead_code -A unused_imports - run: cargo test --no-default-features --features="x25519" - - - name: Run cargo test with just P256 enabled - env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: -D warnings -A dead_code -A unused_imports - run: cargo test --no-default-features --features="p256" - - - name: Run cargo test with just P384 enabled - env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: -D warnings -A dead_code -A unused_imports - run: cargo test --no-default-features --features="p384" - - - name: Run cargo test with just P521 enabled - env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: -D warnings -A dead_code -A unused_imports - run: cargo test --no-default-features --features="p521" - - name: Run cargo test with all features enabled env: CARGO_INCREMENTAL: 0 diff --git a/Cargo.toml b/Cargo.toml index 848f430..0cce5c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,15 +12,7 @@ keywords = ["cryptography", "crypto", "key-exchange", "encryption", "aead", "sec categories = ["cryptography", "no-std"] [features] -# "p256" enables the use of ECDH-NIST-P256 as a KEM -# "p384" enables the use of ECDH-NIST-P384 as a KEM -# "x25519" enables the use of the X25519 as a KEM -default = ["alloc", "p256", "x25519"] -x25519 = ["dep:x25519-dalek"] -p384 = ["dep:p384"] -p256 = ["dep:p256"] -p521 = ["dep:p521"] -k256 = ["dep:k256"] +default = ["alloc", "secp"] secp = ["bitcoin", "secp256k1/global-context", "secp256k1/rand-std"] # Include allocating methods like open() and seal() alloc = [] @@ -32,20 +24,14 @@ aead = "0.5" aes-gcm = "0.10" bitcoin = { version = "0.32.0", optional = true } secp256k1 = { version = "0.29", optional = true } -# bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin", commit = "0d1cab68eee59f79c3ec76cf393438471b68fe69", optional = true } chacha20poly1305 = "0.10" generic-array = { version = "0.14", default-features = false } digest = "0.10" hkdf = "0.12" hmac = "0.12" rand_core = { version = "0.6", default-features = false } -k256 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdh"], optional = true} -p256 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdh"], optional = true} -p384 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdh"], optional = true} -p521 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdh"], optional = true} sha2 = { version = "0.10", default-features = false } subtle = { version = "2.6", default-features = false } -x25519-dalek = { version = "2", default-features = false, features = ["static_secrets"], optional = true } zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] } [dev-dependencies] @@ -56,14 +42,6 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rand = { version = "0.8", default-features = false, features = ["getrandom", "std_rng"] } -[[example]] -name = "client_server" -required-features = ["x25519"] - -[[example]] -name = "agility" -required-features = ["p256", "p384", "p521", "x25519"] - # Tell docs.rs to build docs with `--all-features` and `--cfg docsrs` (for nightly docs features) [package.metadata.docs.rs] all-features = true diff --git a/README.md b/README.md index ded711b..43ebf29 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -rust-hpke +bitcoin-hpke ========= -[![Version](https://img.shields.io/crates/v/hpke.svg)](https://crates.io/crates/hpke) -[![Docs](https://docs.rs/hpke/badge.svg)](https://docs.rs/hpke) -[![CI](https://github.com/rozbb/rust-hpke/workflows/CI/badge.svg)](https://github.com/rozbb/rust-hpke/actions) +[![Version](https://img.shields.io/crates/v/hpke.svg)](https://crates.io/crates/bitcoin-hpke) +[![Docs](https://docs.rs/bitcoin-hpke/badge.svg)](https://docs.rs/bitcoin-hpke) +[![CI](https://github.com/payjoin/bitcoin-hpke/workflows/CI/badge.svg)](https://github.com/payjoin/bitcoin-hpke/actions) -This is an implementation of the [HPKE](https://www.rfc-editor.org/rfc/rfc9180.html) hybrid encryption standard (RFC 9180). +This is an implementation of the [HPKE](https://www.rfc-editor.org/rfc/rfc9180.html) hybrid encryption standard (RFC 9180) on secp256k1. Warning ------- @@ -26,11 +26,6 @@ This implementation complies with the [HPKE standard](https://www.rfc-editor.org Here are all the primitives listed in the spec. The primitives with checked boxes are the ones that are implemented. * KEMs - - [X] DHKEM(Curve25519, HKDF-SHA256) - - [ ] DHKEM(Curve448, HKDF-SHA512) - - [X] DHKEM(P-256, HKDF-SHA256) - - [X] DHKEM(P-384, HKDF-SHA384) - - [X] DHKEM(P-521, HKDF-SHA512) - [X] DHKEM(secp256k1, HKDF-SHA256) * KDFs - [X] HKDF-SHA256 @@ -44,15 +39,12 @@ Here are all the primitives listed in the spec. The primitives with checked boxe Crate Features -------------- -Default features flags: `alloc`, `x25519`, `p256`. +Default features flags: `alloc`, `secp`. Feature flag list: * `alloc` - Includes allocating methods like `AeadCtxR::open()` and `AeadCtxS::seal()` -* `x25519` - Enables X25519-based KEMs -* `p256` - Enables NIST P-256-based KEMs -* `p384` - Enables NIST P-384-based KEMs -* `p521` - Enables NIST P-521-based KEMs +* `secp` - Enables secp256k1-based KEMs * `std` - Includes an implementation of `std::error::Error` for `HpkeError`. Also does what `alloc` does. For info on how to omit or include feature flags, see the [cargo docs on features](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#choosing-features). @@ -91,8 +83,7 @@ To run all benchmarks, execute `cargo bench --all-features`. If you set your own Ciphersuites benchmarked: -* NIST Ciphersuite with 128-bit security: AES-GCM-128, HKDF-SHA256, ECDH-P256 -* Non-NIST Ciphersuite with 128-bit security: ChaCha20-Poly1305, HKDF-SHA256, X25519 +* NIST Ciphersuite with 128-bit security: AES-GCM-128, HKDF-SHA256, secp256k1 Functions benchmarked in each ciphersuite: @@ -107,7 +98,7 @@ Agility A definition: *crypto agility* refers to the ability of a cryptosystem or protocol to vary its underlying primitives. For example, TLS has "crypto agility" in that you can run the protocol with many different ciphersuites. -This crate does not support crypto agility out of the box. This is because the cryptographic primitives are encoded as types satisfying certain constraints, and types need to be determined at compile time (broadly speaking). That said, there is nothing preventing you from implementing agility yourself. There is a [sample implementation](examples/agility.rs) in the examples folder. The sample implementation is messy because agility is messy. +This crate does not support crypto agility out of the box. This is because the cryptographic primitives are encoded as types satisfying certain constraints, and types need to be determined at compile time (broadly speaking). That said, there is nothing preventing you from implementing agility yourself. License ------- diff --git a/benches/benches.rs b/benches/benches.rs index fe1cf49..3d82ce8 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -210,25 +210,12 @@ where pub fn benches() { let mut c = Criterion::default().configure_from_args(); - // NIST ciphersuite at the 128-bit security level is AES-GCM-128, HKDF-SHA256, and ECDH-P256 - #[cfg(feature = "p256")] - bench_ciphersuite::( - "NIST[seclevel=128]", - &mut c, - ); - - // Non-NIST ciphersuite at the 128-bit security level is ChaCha20Poly1305, HKDF-SHA256, and X25519 - #[cfg(feature = "x25519")] - bench_ciphersuite::< - hpke::aead::ChaCha20Poly1305, - hpke::kdf::HkdfSha256, - hpke::kem::X25519HkdfSha256, - >("Non-NIST[seclevel=128]", &mut c); - #[cfg(feature = "secp")] - bench_ciphersuite::( - "secp", &mut c, - ); + bench_ciphersuite::< + bitcoin_hpke::aead::AesGcm128, + bitcoin_hpke::kdf::HkdfSha256, + bitcoin_hpke::kem::SecpK256HkdfSha256, + >("secp", &mut c); } criterion_main!(benches); diff --git a/examples/agility.rs b/examples/agility.rs deleted file mode 100644 index 0776c87..0000000 --- a/examples/agility.rs +++ /dev/null @@ -1,762 +0,0 @@ -#![allow(dead_code)] -//! Here's the gist of this file: Instead of doing things at the type level, you can use zero-sized -//! types and runtime validity checks to do all of HPKE. This file is a rough idea of how one would -//! go about implementing that. There isn't too much repetition. The main part where you have to -//! get clever is in `agile_setup_*`, where you have to have a match statement with up to 3·3·5 = -//! 45 branches for all the different AEAD-KEM-KDF combinations. Practically speaking, though, -//! that's not a big number, so writing that out and using a macro for the actual work (e.g., -//! `do_setup_sender!`) seems to be the way to go. -//! -//! The other point of this file is to demonstrate how messy crypto agility makes things. Many -//! people have different needs when it comes to agility, so I implore you **DO NOT COPY THIS FILE -//! BLINDLY**. Think about what you actually need, make that instead, and make sure to write lots -//! of runtime checks. - -use hpke::{ - aead::{Aead, AeadCtxR, AeadCtxS, AeadTag, AesGcm128, AesGcm256, ChaCha20Poly1305}, - kdf::{HkdfSha256, HkdfSha384, HkdfSha512, Kdf as KdfTrait}, - kem::{ - DhP256HkdfSha256, DhP384HkdfSha384, DhP521HkdfSha512, Kem as KemTrait, X25519HkdfSha256, - }, - setup_receiver, setup_sender, Deserializable, HpkeError, OpModeR, OpModeS, PskBundle, - Serializable, -}; - -use rand::{rngs::StdRng, CryptoRng, RngCore, SeedableRng}; - -trait AgileAeadCtxS { - fn seal_in_place_detached( - &mut self, - plaintext: &mut [u8], - aad: &[u8], - ) -> Result; - fn seal(&mut self, plaintext: &[u8], aad: &[u8]) -> Result, AgileHpkeError>; -} - -trait AgileAeadCtxR { - fn open_in_place_detached( - &mut self, - ciphertext: &mut [u8], - aad: &[u8], - tag_bytes: &[u8], - ) -> Result<(), AgileHpkeError>; - fn open(&mut self, ciphertext: &[u8], aad: &[u8]) -> Result, AgileHpkeError>; -} - -type AgileAeadTag = Vec; - -#[derive(Debug)] -enum AgileHpkeError { - /// When you don't give an algorithm an array of the length it wants. Error is of the form - /// `((alg1, alg1_location) , (alg2, alg2_location))`. - AlgMismatch((&'static str, &'static str), (&'static str, &'static str)), - /// When you get an algorithm identifier you don't recognize. Error is of the form - /// `(alg, given_id)`. - UnknownAlgIdent(&'static str, u16), - /// Represents an error in the `hpke` crate - HpkeError(HpkeError), -} - -// This just wraps the HpkeError -impl From for AgileHpkeError { - fn from(e: HpkeError) -> AgileHpkeError { - AgileHpkeError::HpkeError(e) - } -} - -impl AgileAeadCtxS for AeadCtxS { - fn seal_in_place_detached( - &mut self, - plaintext: &mut [u8], - aad: &[u8], - ) -> Result, AgileHpkeError> { - self.seal_in_place_detached(plaintext, aad) - .map(|tag| tag.to_bytes().to_vec()) - .map_err(Into::into) - } - fn seal(&mut self, plaintext: &[u8], aad: &[u8]) -> Result, AgileHpkeError> { - self.seal(plaintext, aad).map_err(Into::into) - } -} - -impl AgileAeadCtxR for AeadCtxR { - fn open_in_place_detached( - &mut self, - ciphertext: &mut [u8], - aad: &[u8], - tag_bytes: &[u8], - ) -> Result<(), AgileHpkeError> { - let tag = AeadTag::::from_bytes(tag_bytes)?; - self.open_in_place_detached(ciphertext, aad, &tag) - .map_err(Into::into) - } - fn open(&mut self, ciphertext: &[u8], aad: &[u8]) -> Result, AgileHpkeError> { - self.open(ciphertext, aad).map_err(Into::into) - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum AeadAlg { - AesGcm128, - AesGcm256, - ChaCha20Poly1305, -} - -impl AeadAlg { - fn name(&self) -> &'static str { - match self { - AeadAlg::AesGcm128 => "AesGcm128", - AeadAlg::AesGcm256 => "AesGcm256", - AeadAlg::ChaCha20Poly1305 => "ChaCha20Poly1305", - } - } - - fn try_from_u16(id: u16) -> Result { - let res = match id { - 0x01 => AeadAlg::AesGcm128, - 0x02 => AeadAlg::AesGcm256, - 0x03 => AeadAlg::ChaCha20Poly1305, - _ => return Err(AgileHpkeError::UnknownAlgIdent("AeadAlg", id)), - }; - - Ok(res) - } - - fn to_u16(self) -> u16 { - match self { - AeadAlg::AesGcm128 => 0x01, - AeadAlg::AesGcm256 => 0x02, - AeadAlg::ChaCha20Poly1305 => 0x03, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum KdfAlg { - HkdfSha256, - HkdfSha384, - HkdfSha512, -} - -impl KdfAlg { - fn name(&self) -> &'static str { - match self { - KdfAlg::HkdfSha256 => "HkdfSha256", - KdfAlg::HkdfSha384 => "HkdfSha384", - KdfAlg::HkdfSha512 => "HkdfSha512", - } - } - - fn try_from_u16(id: u16) -> Result { - let res = match id { - 0x01 => KdfAlg::HkdfSha256, - 0x02 => KdfAlg::HkdfSha384, - 0x03 => KdfAlg::HkdfSha512, - _ => return Err(AgileHpkeError::UnknownAlgIdent("KdfAlg", id)), - }; - - Ok(res) - } - - fn to_u16(self) -> u16 { - match self { - KdfAlg::HkdfSha256 => 0x01, - KdfAlg::HkdfSha384 => 0x02, - KdfAlg::HkdfSha512 => 0x03, - } - } - - fn get_digest_len(&self) -> usize { - match self { - KdfAlg::HkdfSha256 => 32, - KdfAlg::HkdfSha384 => 48, - KdfAlg::HkdfSha512 => 64, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum KemAlg { - X25519HkdfSha256, - X448HkdfSha512, - DhP256HkdfSha256, - DhP384HkdfSha384, - DhP521HkdfSha512, -} - -impl KemAlg { - fn name(&self) -> &'static str { - match self { - KemAlg::DhP256HkdfSha256 => "DhP256HkdfSha256", - KemAlg::DhP384HkdfSha384 => "DhP384HkdfSha384", - KemAlg::DhP521HkdfSha512 => "DhP521HkdfSha512", - KemAlg::X25519HkdfSha256 => "X25519HkdfSha256", - KemAlg::X448HkdfSha512 => "X448HkdfSha512", - } - } - - fn try_from_u16(id: u16) -> Result { - let res = match id { - 0x10 => KemAlg::DhP256HkdfSha256, - 0x11 => KemAlg::DhP384HkdfSha384, - 0x12 => KemAlg::DhP521HkdfSha512, - 0x20 => KemAlg::X25519HkdfSha256, - 0x21 => KemAlg::X448HkdfSha512, - _ => return Err(AgileHpkeError::UnknownAlgIdent("KemAlg", id)), - }; - - Ok(res) - } - - fn to_u16(self) -> u16 { - match self { - KemAlg::DhP256HkdfSha256 => 0x10, - KemAlg::DhP384HkdfSha384 => 0x11, - KemAlg::DhP521HkdfSha512 => 0x12, - KemAlg::X25519HkdfSha256 => 0x20, - KemAlg::X448HkdfSha512 => 0x21, - } - } - - fn kdf_alg(&self) -> KdfAlg { - match self { - KemAlg::X25519HkdfSha256 => KdfAlg::HkdfSha256, - KemAlg::X448HkdfSha512 => KdfAlg::HkdfSha512, - KemAlg::DhP256HkdfSha256 => KdfAlg::HkdfSha256, - KemAlg::DhP384HkdfSha384 => KdfAlg::HkdfSha384, - KemAlg::DhP521HkdfSha512 => KdfAlg::HkdfSha512, - } - } -} - -#[derive(Clone)] -struct AgilePublicKey { - kem_alg: KemAlg, - pubkey_bytes: Vec, -} - -impl AgilePublicKey { - fn try_lift(&self) -> Result { - Kem::PublicKey::from_bytes(&self.pubkey_bytes).map_err(|e| e.into()) - } -} - -#[derive(Clone)] -struct AgileEncappedKey { - kem_alg: KemAlg, - encapped_key_bytes: Vec, -} - -impl AgileEncappedKey { - fn try_lift(&self) -> Result { - Kem::EncappedKey::from_bytes(&self.encapped_key_bytes).map_err(|e| e.into()) - } -} - -#[derive(Clone)] -struct AgilePrivateKey { - kem_alg: KemAlg, - privkey_bytes: Vec, -} - -impl AgilePrivateKey { - fn try_lift(&self) -> Result { - Kem::PrivateKey::from_bytes(&self.privkey_bytes).map_err(|e| e.into()) - } -} - -#[derive(Clone)] -struct AgileKeypair(AgilePrivateKey, AgilePublicKey); - -impl AgileKeypair { - fn try_lift(&self) -> Result<(Kem::PrivateKey, Kem::PublicKey), AgileHpkeError> { - Ok((self.0.try_lift::()?, self.1.try_lift::()?)) - } - - fn validate(&self) -> Result<(), AgileHpkeError> { - if self.0.kem_alg != self.1.kem_alg { - Err(AgileHpkeError::AlgMismatch( - (self.0.kem_alg.name(), "AgileKeypair::privkey"), - (self.1.kem_alg.name(), "AgileKeypair::pubkey"), - )) - } else { - Ok(()) - } - } -} - -// The leg work of agile_gen_keypair -macro_rules! do_gen_keypair { - ($kem_ty:ty, $kem_alg:ident, $csprng:ident) => {{ - type Kem = $kem_ty; - let kem_alg = $kem_alg; - let csprng = $csprng; - - let (sk, pk) = Kem::gen_keypair(csprng); - let sk = AgilePrivateKey { - kem_alg, - privkey_bytes: sk.to_bytes().to_vec(), - }; - let pk = AgilePublicKey { - kem_alg, - pubkey_bytes: pk.to_bytes().to_vec(), - }; - - AgileKeypair(sk, pk) - }}; -} - -fn agile_gen_keypair(kem_alg: KemAlg, csprng: &mut R) -> AgileKeypair { - match kem_alg { - KemAlg::X25519HkdfSha256 => do_gen_keypair!(X25519HkdfSha256, kem_alg, csprng), - KemAlg::DhP256HkdfSha256 => do_gen_keypair!(DhP256HkdfSha256, kem_alg, csprng), - KemAlg::DhP384HkdfSha384 => do_gen_keypair!(DhP384HkdfSha384, kem_alg, csprng), - KemAlg::DhP521HkdfSha512 => do_gen_keypair!(DhP521HkdfSha512, kem_alg, csprng), - _ => unimplemented!(), - } -} - -#[derive(Clone)] -struct AgileOpModeR<'a> { - kem_alg: KemAlg, - op_mode_ty: AgileOpModeRTy<'a>, -} - -impl<'a> AgileOpModeR<'a> { - fn try_lift(self) -> Result, AgileHpkeError> { - let res = match self.op_mode_ty { - AgileOpModeRTy::Base => OpModeR::Base, - AgileOpModeRTy::Psk(bundle) => OpModeR::Psk(bundle.try_lift::()?), - AgileOpModeRTy::Auth(pk) => OpModeR::Auth(pk.try_lift::()?), - AgileOpModeRTy::AuthPsk(pk, bundle) => { - OpModeR::AuthPsk(pk.try_lift::()?, bundle.try_lift::()?) - } - }; - - Ok(res) - } - - fn validate(&self) -> Result<(), AgileHpkeError> { - match &self.op_mode_ty { - AgileOpModeRTy::Auth(pk) => { - if pk.kem_alg != self.kem_alg { - return Err(AgileHpkeError::AlgMismatch( - (self.kem_alg.name(), "AgileOpModeR::kem_alg"), - ( - pk.kem_alg.name(), - "AgileOpModeR::op_mode_ty::AgilePublicKey::kem_alg", - ), - )); - } - } - AgileOpModeRTy::AuthPsk(pk, _) => { - if pk.kem_alg != self.kem_alg { - return Err(AgileHpkeError::AlgMismatch( - (self.kem_alg.name(), "AgileOpModeR::kem_alg"), - ( - pk.kem_alg.name(), - "AgileOpModeR::op_mode_ty::AgilePublicKey::kem_alg", - ), - )); - } - } - _ => (), - } - - Ok(()) - } -} - -#[derive(Clone)] -enum AgileOpModeRTy<'a> { - Base, - Psk(AgilePskBundle<'a>), - Auth(AgilePublicKey), - AuthPsk(AgilePublicKey, AgilePskBundle<'a>), -} - -#[derive(Clone)] -struct AgileOpModeS<'a> { - kem_alg: KemAlg, - op_mode_ty: AgileOpModeSTy<'a>, -} - -impl<'a> AgileOpModeS<'a> { - fn try_lift(self) -> Result, AgileHpkeError> { - let res = match self.op_mode_ty { - AgileOpModeSTy::Base => OpModeS::Base, - AgileOpModeSTy::Psk(bundle) => OpModeS::Psk(bundle.try_lift::()?), - AgileOpModeSTy::Auth(keypair) => OpModeS::Auth(keypair.try_lift::()?), - AgileOpModeSTy::AuthPsk(keypair, bundle) => { - OpModeS::AuthPsk(keypair.try_lift::()?, bundle.try_lift::()?) - } - }; - - Ok(res) - } - - fn validate(&self) -> Result<(), AgileHpkeError> { - match &self.op_mode_ty { - AgileOpModeSTy::Auth(keypair) => { - keypair.validate()?; - if keypair.0.kem_alg != self.kem_alg { - return Err(AgileHpkeError::AlgMismatch( - (self.kem_alg.name(), "AgileOpModeS::kem_alg"), - ( - keypair.0.kem_alg.name(), - "AgileOpModeS::op_mode_ty::AgilePrivateKey::kem_alg", - ), - )); - } - } - AgileOpModeSTy::AuthPsk(keypair, _) => { - keypair.validate()?; - if keypair.0.kem_alg != self.kem_alg { - return Err(AgileHpkeError::AlgMismatch( - (self.kem_alg.name(), "AgileOpModeS::kem_alg"), - ( - keypair.0.kem_alg.name(), - "AgileOpModeS::op_mode_ty::AgilePrivateKey::kem_alg", - ), - )); - } - } - _ => (), - } - - Ok(()) - } -} - -#[derive(Clone)] -enum AgileOpModeSTy<'a> { - Base, - Psk(AgilePskBundle<'a>), - Auth(AgileKeypair), - AuthPsk(AgileKeypair, AgilePskBundle<'a>), -} - -#[derive(Clone, Copy)] -struct AgilePskBundle<'a>(PskBundle<'a>); - -impl<'a> AgilePskBundle<'a> { - fn try_lift(self) -> Result, AgileHpkeError> { - Ok(self.0) - } -} - -// This macro takes in all the supported AEADs, KDFs, and KEMs, and dispatches the given test -// vector to the test case with the appropriate types -macro_rules! hpke_dispatch { - // Step 1: Roll up the AEAD, KDF, and KEM types into tuples. We'll unroll them later - ($to_set:ident, $to_match:ident, - ($( $aead_ty:ident ),*), ($( $kdf_ty:ident ),*), ($( $kem_ty:ident ),*), $rng_ty:ident, - $callback:ident, $( $callback_args:ident ),* ) => { - hpke_dispatch!(@tup1 - $to_set, $to_match, - ($( $aead_ty ),*), ($( $kdf_ty ),*), ($( $kem_ty ),*), $rng_ty, - $callback, ($( $callback_args ),*) - ) - }; - - // Step 2: Expand with respect to every AEAD - (@tup1 - $to_set:ident, $to_match:ident, - ($( $aead_ty:ident ),*), $kdf_tup:tt, $kem_tup:tt, $rng_ty:tt, - $callback:ident, $callback_args:tt) => { - $( - hpke_dispatch!(@tup2 - $to_set, $to_match, - $aead_ty, $kdf_tup, $kem_tup, $rng_ty, - $callback, $callback_args - ); - )* - }; - - // Step 3: Expand with respect to every KDF - (@tup2 - $to_set:ident, $to_match:ident, - $aead_ty:ident, ($( $kdf_ty:ident ),*), $kem_tup:tt, $rng_ty:tt, - $callback:ident, $callback_args:tt) => { - $( - hpke_dispatch!(@tup3 - $to_set, $to_match, - $aead_ty, $kdf_ty, $kem_tup, $rng_ty, - $callback, $callback_args - ); - )* - }; - - // Step 4: Expand with respect to every KEM - (@tup3 - $to_set:ident, $to_match:ident, - $aead_ty:ident, $kdf_ty:ident, ($( $kem_ty:ident ),*), $rng_ty:tt, - $callback:ident, $callback_args:tt) => { - $( - hpke_dispatch!(@base - $to_set, $to_match, - $aead_ty, $kdf_ty, $kem_ty, $rng_ty, - $callback, $callback_args - ); - )* - }; - - // Step 5: Now that we're only dealing with 1 type of each kind, do the dispatch. If the test - // vector matches the IDs of these types, run the test case. - (@base - $to_set:ident, $to_match:ident, - $aead_ty:ident, $kdf_ty:ident, $kem_ty:ident, $rng_ty:ident, - $callback:ident, ($( $callback_args:ident ),*)) => { - if let (AeadAlg::$aead_ty, KemAlg::$kem_ty, KdfAlg::$kdf_ty) = $to_match - { - $to_set = Some($callback::<$aead_ty, $kdf_ty, $kem_ty, $rng_ty>($( $callback_args ),*)); - } - }; -} - -// The leg work of agile_setup_receiver -fn do_setup_sender( - mode: &AgileOpModeS, - pk_recip: &AgilePublicKey, - info: &[u8], - csprng: &mut R, -) -> Result<(AgileEncappedKey, Box), AgileHpkeError> -where - A: 'static + Aead, - Kdf: 'static + KdfTrait, - Kem: 'static + KemTrait, - R: CryptoRng + RngCore, -{ - let kem_alg = mode.kem_alg; - let mode = mode.clone().try_lift::()?; - let pk_recip = pk_recip.try_lift::()?; - - let (encapped_key, aead_ctx) = setup_sender::(&mode, &pk_recip, info, csprng)?; - let encapped_key = AgileEncappedKey { - kem_alg, - encapped_key_bytes: encapped_key.to_bytes().to_vec(), - }; - - Ok((encapped_key, Box::new(aead_ctx))) -} - -fn agile_setup_sender( - aead_alg: AeadAlg, - kdf_alg: KdfAlg, - kem_alg: KemAlg, - mode: &AgileOpModeS, - pk_recip: &AgilePublicKey, - info: &[u8], - csprng: &mut R, -) -> Result<(AgileEncappedKey, Box), AgileHpkeError> { - // Do all the necessary validation - mode.validate()?; - if mode.kem_alg != pk_recip.kem_alg { - return Err(AgileHpkeError::AlgMismatch( - (mode.kem_alg.name(), "mode::kem_alg"), - (pk_recip.kem_alg.name(), "pk_recip::kem_alg"), - )); - } - if kem_alg != mode.kem_alg { - return Err(AgileHpkeError::AlgMismatch( - (kem_alg.name(), "kem_alg::kem_alg"), - (mode.kem_alg.name(), "mode::kem_alg"), - )); - } - - // The triple we dispatch on - let to_match = (aead_alg, kem_alg, kdf_alg); - - // This gets overwritten by the below macro call. It's None iff dispatch failed. - let mut res: Option), AgileHpkeError>> = None; - - #[rustfmt::skip] - hpke_dispatch!( - res, to_match, - (ChaCha20Poly1305, AesGcm128, AesGcm256), - (HkdfSha256, HkdfSha384, HkdfSha512), - (X25519HkdfSha256, DhP256HkdfSha256, DhP384HkdfSha384, DhP521HkdfSha512), - R, - do_setup_sender, - mode, - pk_recip, - info, - csprng - ); - - if res.is_none() { - panic!("DHKEM({}) isn't impelmented yet!", kem_alg.name()); - } - - res.unwrap() -} - -// The leg work of agile_setup_receiver. The Dummy type parameter is so that it can be used with -// the hpke_dispatch! macro. The macro expects its callback function to have 4 type parameters -fn do_setup_receiver( - mode: &AgileOpModeR, - recip_keypair: &AgileKeypair, - encapped_key: &AgileEncappedKey, - info: &[u8], -) -> Result, AgileHpkeError> -where - A: 'static + Aead, - Kdf: 'static + KdfTrait, - Kem: 'static + KemTrait, -{ - let mode = mode.clone().try_lift::()?; - let (sk_recip, _) = recip_keypair.try_lift::()?; - let encapped_key = encapped_key.try_lift::()?; - - let aead_ctx = setup_receiver::(&mode, &sk_recip, &encapped_key, info)?; - Ok(Box::new(aead_ctx)) -} - -fn agile_setup_receiver( - aead_alg: AeadAlg, - kdf_alg: KdfAlg, - kem_alg: KemAlg, - mode: &AgileOpModeR, - recip_keypair: &AgileKeypair, - encapped_key: &AgileEncappedKey, - info: &[u8], -) -> Result, AgileHpkeError> { - // Do all the necessary validation - recip_keypair.validate()?; - mode.validate()?; - if mode.kem_alg != recip_keypair.0.kem_alg { - return Err(AgileHpkeError::AlgMismatch( - (mode.kem_alg.name(), "mode::kem_alg"), - (recip_keypair.0.kem_alg.name(), "recip_keypair::kem_alg"), - )); - } - if kem_alg != mode.kem_alg { - return Err(AgileHpkeError::AlgMismatch( - (kem_alg.name(), "kem_alg::kem_alg"), - (mode.kem_alg.name(), "mode::kem_alg"), - )); - } - if recip_keypair.0.kem_alg != encapped_key.kem_alg { - return Err(AgileHpkeError::AlgMismatch( - (recip_keypair.0.kem_alg.name(), "recip_keypair::kem_alg"), - (encapped_key.kem_alg.name(), "encapped_key::kem_alg"), - )); - } - - // The triple we dispatch on - let to_match = (aead_alg, kem_alg, kdf_alg); - - // This gets overwritten by the below macro call. It's None iff dispatch failed. - let mut res: Option, AgileHpkeError>> = None; - - // Dummy type to give to the macro. do_setup_receiver doesn't use an RNG, so it doesn't need a - // concrete RNG type. We give it the unit type to make it happy. - type Unit = (); - - #[rustfmt::skip] - hpke_dispatch!( - res, to_match, - (ChaCha20Poly1305, AesGcm128, AesGcm256), - (HkdfSha256, HkdfSha384, HkdfSha512), - (X25519HkdfSha256, DhP256HkdfSha256, DhP384HkdfSha384, DhP521HkdfSha512), - Unit, - do_setup_receiver, - mode, - recip_keypair, - encapped_key, - info - ); - - if res.is_none() { - panic!("DHKEM({}) isn't impelmented yet!", kem_alg.name()); - } - - res.unwrap() -} - -fn main() { - let mut csprng = StdRng::from_entropy(); - - let supported_aead_algs = &[ - AeadAlg::AesGcm128, - AeadAlg::AesGcm256, - AeadAlg::ChaCha20Poly1305, - ]; - let supported_kem_algs = &[ - KemAlg::X25519HkdfSha256, - KemAlg::DhP256HkdfSha256, - KemAlg::DhP384HkdfSha384, - KemAlg::DhP521HkdfSha512, - ]; - let supported_kdf_algs = &[KdfAlg::HkdfSha256, KdfAlg::HkdfSha384, KdfAlg::HkdfSha512]; - - // For every combination of supported algorithms, test an encryption-decryption round trip - for &aead_alg in supported_aead_algs { - for &kem_alg in supported_kem_algs { - for &kdf_alg in supported_kdf_algs { - let info = b"we're gonna agile him in his clavicle"; - - // Make a random sender keypair and PSK bundle - let sender_keypair = agile_gen_keypair(kem_alg, &mut csprng); - let mut psk_bytes = vec![0u8; kdf_alg.get_digest_len()]; - let psk_id = b"preshared key attempt #5, take 2. action"; - let psk_bundle = { - csprng.fill_bytes(&mut psk_bytes); - AgilePskBundle(PskBundle { - psk: &psk_bytes, - psk_id, - }) - }; - - // Make two agreeing OpModes (AuthPsk is the most complicated, so we're just using - // that). - let op_mode_s_ty = AgileOpModeSTy::AuthPsk(sender_keypair.clone(), psk_bundle); - let op_mode_s = AgileOpModeS { - kem_alg, - op_mode_ty: op_mode_s_ty, - }; - let op_mode_r_ty = AgileOpModeRTy::AuthPsk(sender_keypair.1, psk_bundle); - let op_mode_r = AgileOpModeR { - kem_alg, - op_mode_ty: op_mode_r_ty, - }; - - // Set up the sender's encryption context - let recip_keypair = agile_gen_keypair(kem_alg, &mut csprng); - let (encapped_key, mut aead_ctx1) = agile_setup_sender( - aead_alg, - kdf_alg, - kem_alg, - &op_mode_s, - &recip_keypair.1, - &info[..], - &mut csprng, - ) - .unwrap(); - - // Set up the receivers's encryption context - let mut aead_ctx2 = agile_setup_receiver( - aead_alg, - kdf_alg, - kem_alg, - &op_mode_r, - &recip_keypair, - &encapped_key, - &info[..], - ) - .unwrap(); - - // Test an encryption-decryption round trip - let msg = b"paper boy paper boy"; - let aad = b"all about that paper, boy"; - let ciphertext = aead_ctx1.seal(msg, aad).unwrap(); - let roundtrip_plaintext = aead_ctx2.open(&ciphertext, aad).unwrap(); - - // Assert that the derived plaintext equals the original message - assert_eq!(&roundtrip_plaintext, msg); - } - } - } - - println!("PEAK AGILITY ACHIEVED"); -} diff --git a/examples/client_server.rs b/examples/client_server.rs index 1190a25..147c4fd 100644 --- a/examples/client_server.rs +++ b/examples/client_server.rs @@ -15,10 +15,10 @@ // * Proper error handling. Everything here just panics when an error is encountered. It is up to // the user of this library to do the appropriate thing when a function returns an error. -use hpke::{ +use bitcoin_hpke::{ aead::{AeadTag, ChaCha20Poly1305}, kdf::HkdfSha384, - kem::X25519HkdfSha256, + kem::SecpK256HkdfSha256, Deserializable, Kem as KemTrait, OpModeR, OpModeS, Serializable, }; @@ -27,7 +27,7 @@ use rand::{rngs::StdRng, SeedableRng}; const INFO_STR: &[u8] = b"example session"; // These are the only algorithms we're gonna use for this example -type Kem = X25519HkdfSha256; +type Kem = SecpK256HkdfSha256; type Aead = ChaCha20Poly1305; type Kdf = HkdfSha384; @@ -48,9 +48,13 @@ fn client_encrypt_msg( // Encapsulate a key and use the resulting shared secret to encrypt a message. The AEAD context // is what you use to encrypt. - let (encapped_key, mut sender_ctx) = - hpke::setup_sender::(&OpModeS::Base, server_pk, INFO_STR, &mut csprng) - .expect("invalid server pubkey!"); + let (encapped_key, mut sender_ctx) = bitcoin_hpke::setup_sender::( + &OpModeS::Base, + server_pk, + INFO_STR, + &mut csprng, + ) + .expect("invalid server pubkey!"); // On success, seal_in_place_detached() will encrypt the plaintext in place let mut msg_copy = msg.to_vec(); @@ -81,9 +85,13 @@ fn server_decrypt_msg( .expect("could not deserialize the encapsulated pubkey!"); // Decapsulate and derive the shared secret. This creates a shared AEAD context. - let mut receiver_ctx = - hpke::setup_receiver::(&OpModeR::Base, &server_sk, &encapped_key, INFO_STR) - .expect("failed to set up receiver!"); + let mut receiver_ctx = bitcoin_hpke::setup_receiver::( + &OpModeR::Base, + &server_sk, + &encapped_key, + INFO_STR, + ) + .expect("failed to set up receiver!"); // On success, open_in_place_detached() will decrypt the ciphertext in place let mut ciphertext_copy = ciphertext.to_vec(); diff --git a/src/aead.rs b/src/aead.rs index c4c309a..99a762b 100644 --- a/src/aead.rs +++ b/src/aead.rs @@ -677,122 +677,6 @@ mod test { test_invalid_nonce!(test_invalid_nonce_aes256, AesGcm128); test_invalid_nonce!(test_invalid_nonce_chacha, ChaCha20Poly1305); - #[cfg(all(feature = "x25519", any(feature = "alloc", feature = "std")))] - mod x25519_tests { - use super::*; - - test_export_idempotence!(test_export_idempotence_x25519, crate::kem::X25519HkdfSha256); - test_exportonly_panics!( - test_exportonly_panics_x25519_seal, - test_exportonly_panics_x25519_open, - crate::kem::X25519HkdfSha256 - ); - test_overflow!(test_overflow_x25519, crate::kem::X25519HkdfSha256); - - test_ctx_correctness!( - test_ctx_correctness_aes128_x25519, - AesGcm128, - crate::kem::X25519HkdfSha256 - ); - test_ctx_correctness!( - test_ctx_correctness_aes256_x25519, - AesGcm256, - crate::kem::X25519HkdfSha256 - ); - test_ctx_correctness!( - test_ctx_correctness_chacha_x25519, - ChaCha20Poly1305, - crate::kem::X25519HkdfSha256 - ); - } - - #[cfg(all(feature = "p256", any(feature = "alloc", feature = "std")))] - mod p256_tests { - use super::*; - - test_export_idempotence!(test_export_idempotence_p256, crate::kem::DhP256HkdfSha256); - test_exportonly_panics!( - test_exportonly_panics_p256_seal, - test_exportonly_panics_p256_open, - crate::kem::DhP256HkdfSha256 - ); - test_overflow!(test_overflow_p256, crate::kem::DhP256HkdfSha256); - - test_ctx_correctness!( - test_ctx_correctness_aes128_p256, - AesGcm128, - crate::kem::DhP256HkdfSha256 - ); - test_ctx_correctness!( - test_ctx_correctness_aes256_p256, - AesGcm256, - crate::kem::DhP256HkdfSha256 - ); - test_ctx_correctness!( - test_ctx_correctness_chacha_p256, - ChaCha20Poly1305, - crate::kem::DhP256HkdfSha256 - ); - } - - #[cfg(all(feature = "p384", any(feature = "alloc", feature = "std")))] - mod p384_tests { - use super::*; - - test_export_idempotence!(test_export_idempotence_p384, crate::kem::DhP384HkdfSha384); - test_exportonly_panics!( - test_exportonly_panics_p384_seal, - test_exportonly_panics_p384_open, - crate::kem::DhP384HkdfSha384 - ); - test_overflow!(test_overflow_p384, crate::kem::DhP384HkdfSha384); - - test_ctx_correctness!( - test_ctx_correctness_aes128_p384, - AesGcm128, - crate::kem::DhP384HkdfSha384 - ); - test_ctx_correctness!( - test_ctx_correctness_aes256_p384, - AesGcm256, - crate::kem::DhP384HkdfSha384 - ); - test_ctx_correctness!( - test_ctx_correctness_chacha_p384, - ChaCha20Poly1305, - crate::kem::DhP384HkdfSha384 - ); - } - - #[cfg(all(feature = "k256", any(feature = "alloc", feature = "std")))] - mod k256_tests { - use super::*; - - test_export_idempotence!(test_export_idempotence_k256, crate::kem::DhP256HkdfSha256); - test_exportonly_panics!( - test_exportonly_panics_k256_seal, - test_exportonly_panics_k256_open, - crate::kem::DhK256HkdfSha256 - ); - test_overflow!(test_overflow_k256, crate::kem::DhK256HkdfSha256); - - test_ctx_correctness!( - test_ctx_correctness_aes128_k256, - AesGcm128, - crate::kem::DhK256HkdfSha256 - ); - test_ctx_correctness!( - test_ctx_correctness_aes256_k256, - AesGcm256, - crate::kem::DhK256HkdfSha256 - ); - test_ctx_correctness!( - test_ctx_correctness_chacha_k256, - ChaCha20Poly1305, - crate::kem::DhK256HkdfSha256 - ); - } - #[cfg(all(feature = "secp", any(feature = "alloc", feature = "std")))] mod secp_tests { use super::*; diff --git a/src/dhkex.rs b/src/dhkex.rs index 6e8220c..cbda1cc 100644 --- a/src/dhkex.rs +++ b/src/dhkex.rs @@ -44,11 +44,5 @@ pub trait DhKeyExchange { ) -> (Self::PrivateKey, Self::PublicKey); } -#[cfg(any(feature = "p256", feature = "p384", feature = "p521", feature = "k256"))] -pub(crate) mod ecdh_nist; - -#[cfg(feature = "x25519")] -pub(crate) mod x25519; - #[cfg(feature = "secp")] pub(crate) mod secp256k1; diff --git a/src/dhkex/ecdh_nist.rs b/src/dhkex/ecdh_nist.rs deleted file mode 100644 index ff1fb60..0000000 --- a/src/dhkex/ecdh_nist.rs +++ /dev/null @@ -1,608 +0,0 @@ -// We define all the NIST P- curve ECDH functionalities in one macro -macro_rules! nist_dhkex { - ( - $curve_name:expr, - $dh_name:ident, - $curve:ident, - $pubkey_size:ty, - $privkey_size:ty, - $ss_size:ty, - $keygen_bitmask:expr - ) => { - pub(crate) mod $curve { - use super::*; - - use crate::{ - dhkex::{DhError, DhKeyExchange}, - kdf::{labeled_extract, Kdf as KdfTrait, LabeledExpand}, - util::{enforce_equal_len, enforce_outbuf_len, KemSuiteId}, - Deserializable, HpkeError, Serializable, - }; - - use ::$curve as curve_crate; - use curve_crate::elliptic_curve::{ecdh::diffie_hellman, sec1::ToEncodedPoint}; - use generic_array::{typenum::Unsigned, GenericArray}; - use subtle::{Choice, ConstantTimeEq}; - - #[doc = concat!( - "An ECDH ", - $curve_name, - " public key. This is never the point at infinity." - )] - #[derive(Clone, Debug, Eq, PartialEq)] - pub struct PublicKey(curve_crate::PublicKey); - - // This is only ever constructed via its Deserializable::from_bytes, which checks for - // the 0 value. Also, the underlying type is zeroize-on-drop. - #[doc = concat!( - "An ECDH ", - $curve_name, - " private key. This is a scalar in the range `[1,p)` where `p` is the group order." - )] - #[derive(Clone, Eq, PartialEq)] - pub struct PrivateKey(curve_crate::SecretKey); - - impl ConstantTimeEq for PrivateKey { - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } - } - - // The underlying type is zeroize-on-drop - /// A bare DH computation result - pub struct KexResult(curve_crate::ecdh::SharedSecret); - - // Everything is serialized and deserialized in uncompressed form - impl Serializable for PublicKey { - type OutputSize = $pubkey_size; - - fn write_exact(&self, buf: &mut [u8]) { - // Check the length is correct and panic if not - enforce_outbuf_len::(buf); - - // Get the uncompressed pubkey encoding - let encoded = self.0.as_affine().to_encoded_point(false); - // Serialize it - buf.copy_from_slice(encoded.as_bytes()); - } - - } - - // Everything is serialized and deserialized in uncompressed form - impl Deserializable for PublicKey { - fn from_bytes(encoded: &[u8]) -> Result { - // In order to parse as an uncompressed curve point, we first make sure the - // input length is correct. This ensures we're receiving the uncompressed - // representation. - enforce_equal_len(Self::OutputSize::to_usize(), encoded.len())?; - - // Now just deserialize. The non-identity invariant is preserved because - // PublicKey::from_sec1_bytes() will error if it receives the point at - // infinity. This is because its submethod, PublicKey::from_encoded_point(), - // does this check explicitly. - let parsed = curve_crate::PublicKey::from_sec1_bytes(encoded) - .map_err(|_| HpkeError::ValidationError)?; - Ok(PublicKey(parsed)) - } - } - - impl Serializable for PrivateKey { - type OutputSize = $privkey_size; - - fn write_exact(&self, buf: &mut [u8]) { - // Check the length is correct and panic if not - enforce_outbuf_len::(buf); - - // SecretKeys already know how to convert to bytes - buf.copy_from_slice(&self.0.to_bytes()); - } - } - - impl Deserializable for PrivateKey { - fn from_bytes(encoded: &[u8]) -> Result { - // Check the length - enforce_equal_len(Self::OutputSize::to_usize(), encoded.len())?; - - // * Invariant: PrivateKey is in [1,p). This is preserved here. - // * SecretKey::from_be_bytes() directly checks that the value isn't zero. And - // its submethod, - // * ScalarCore::from_be_bytes() checks that the value doesn't exceed the - // modulus. - let sk = curve_crate::SecretKey::from_bytes(encoded.into()) - .map_err(|_| HpkeError::ValidationError)?; - - Ok(PrivateKey(sk)) - } - } - - // DH results are serialized in the same way as public keys - impl Serializable for KexResult { - // RFC 9180 §4.1 - // For P-256, P-384, and P-521, the size Ndh of the Diffie-Hellman shared secret is - // equal to 32, 48, and 66, respectively, corresponding to the x-coordinate of the - // resulting elliptic curve point. - type OutputSize = $ss_size; - - fn write_exact(&self, buf: &mut [u8]) { - // Check the length is correct and panic if not - enforce_outbuf_len::(buf); - - // elliptic_curve::ecdh::SharedSecret::raw_secret_bytes returns the serialized - // x-coordinate - buf.copy_from_slice(self.0.raw_secret_bytes()) - } - } - - #[doc = concat!("Represents ECDH functionality over NIST curve ", $curve_name, ".")] - pub struct $dh_name {} - - impl DhKeyExchange for $dh_name { - #[doc(hidden)] - type PublicKey = PublicKey; - #[doc(hidden)] - type PrivateKey = PrivateKey; - #[doc(hidden)] - type KexResult = KexResult; - - /// Converts a private key to a public key - #[doc(hidden)] - fn sk_to_pk(sk: &PrivateKey) -> PublicKey { - // pk = sk·G where G is the generator. This maintains the invariant of the - // public key not being the point at infinity, since ord(G) = p, and sk is not - // 0 mod p (by the invariant we keep on PrivateKeys) - PublicKey(sk.0.public_key()) - } - - /// Does the DH operation. This function is infallible, thanks to invariants on its - /// inputs. - #[doc(hidden)] - fn dh(sk: &PrivateKey, pk: &PublicKey) -> Result { - // Do the DH operation - let dh_res = diffie_hellman(sk.0.to_nonzero_scalar(), pk.0.as_affine()); - - // RFC 9180 §7.1.4: Senders and recipients MUST ensure that dh_res is not the - // point at infinity - // - // This is already true, since: - // 1. pk is not the point at infinity (due to the invariant we keep on - // PublicKeys) - // 2. sk is not 0 mod p (due to the invariant we keep on PrivateKeys) - // 3. Exponentiating a non-identity element of a prime-order group by something - // less than the order yields a non-identity value - // Therefore, dh_res cannot be the point at infinity - Ok(KexResult(dh_res)) - } - - // RFC 9180 §7.1.3: - // def DeriveKeyPair(ikm): - // dkp_prk = LabeledExtract("", "dkp_prk", ikm) - // sk = 0 - // counter = 0 - // while sk == 0 or sk >= order: - // if counter > 255: - // raise DeriveKeyPairError - // bytes = LabeledExpand(dkp_prk, "candidate", - // I2OSP(counter, 1), Nsk) - // bytes[0] = bytes[0] & bitmask - // sk = OS2IP(bytes) - // counter = counter + 1 - // return (sk, pk(sk)) - // where `bitmask` is defined to be 0xFF for P-256 and P-384, and 0x01 for P-521 - - /// Deterministically derives a keypair from the given input keying material and - /// ciphersuite ID. The keying material SHOULD have as many bits of entropy as the - /// bit length of a secret key - #[doc(hidden)] - fn derive_keypair( - suite_id: &KemSuiteId, - ikm: &[u8], - ) -> (PrivateKey, PublicKey) { - // Write the label into a byte buffer and extract from the IKM - let (_, hkdf_ctx) = labeled_extract::(&[], suite_id, b"dkp_prk", ikm); - - // The buffer we hold the candidate scalar bytes in. This is the size of a - // private key. - let mut buf = - GenericArray::::OutputSize>::default(); - - // Try to generate a key 256 times. Practically, this will succeed and return - // early on the first iteration. - for counter in 0u8..=255 { - // This unwrap is fine. It only triggers if buf is way too big. It's only - // 32 bytes. - hkdf_ctx - .labeled_expand(suite_id, b"candidate", &[counter], &mut buf) - .unwrap(); - - // Apply the bitmask - buf[0] &= $keygen_bitmask; - - // Try to convert to a valid secret key. If the conversion succeeded, - // return the keypair. Recall the invariant of PrivateKey: it is a value in - // the range [1,p). - if let Ok(sk) = PrivateKey::from_bytes(&buf) { - let pk = Self::sk_to_pk(&sk); - return (sk, pk); - } - } - - // The code should never ever get here. The likelihood that we get 256 bad - // samples in a row for P-256 is 2^-8192. For P-384 it's (2^-256)^256. - panic!("DeriveKeyPair failed all attempts"); - } - } - } - }; -} - -use generic_array::typenum; - -#[cfg(feature = "p256")] -nist_dhkex!( - "P-256", - DhP256, - p256, - typenum::U65, // RFC 9180 §7.1: Npk of DHKEM(P-256, HKDF-SHA256) is 65 - typenum::U32, // RFC 9180 §7.1: Nsk of DHKEM(P-256, HKDF-SHA256) is 32 - typenum::U32, // RFC 9180 §4.1: Ndh of P-256 is equal to 32 - 0xFF // RFC 9180 §7.1.3: The `bitmask` in DeriveKeyPair to be 0xFF for P-256 -); - -#[cfg(feature = "p384")] -nist_dhkex!( - "P-384", - DhP384, - p384, - typenum::U97, // RFC 9180 §7.1: Npk of DHKEM(P-384, HKDF-SHA384) is 97 - typenum::U48, // RFC 9180 §7.1: Nsk of DHKEM(P-384, HKDF-SHA384) is 48 - typenum::U48, // RFC 9180 §4.1: Ndh of P-384 is equal to 48 - 0xFF // RFC 9180 §7.1.3: The `bitmask` in DeriveKeyPair to be 0xFF for P-384 -); - -#[cfg(feature = "p521")] -nist_dhkex!( - "P-521", - DhP521, - p521, - typenum::U133, // RFC 9180 §7.1: Npk of DHKEM(P-521, HKDF-SHA512) is 133 - typenum::U66, // RFC 9180 §7.1: Nsk of DHKEM(P-521, HKDF-SHA512) is 66 - typenum::U66, // RFC 9180 §4.1: Ndh of P-521 is equal to 66 - 0x01 // RFC 9180 §7.1.3: The `bitmask` in DeriveKeyPair to be 0x01 for P-521 -); - -#[cfg(feature = "k256")] -nist_dhkex!( - "K-256", - DhK256, - k256, - typenum::U65, - typenum::U32, - typenum::U32, - 0xFF -); - -#[cfg(test)] -mod tests { - use crate::{dhkex::DhKeyExchange, test_util::dhkex_gen_keypair, Deserializable, Serializable}; - - #[cfg(feature = "k256")] - use super::k256::DhK256; - #[cfg(feature = "p256")] - use super::p256::DhP256; - #[cfg(feature = "p384")] - use super::p384::DhP384; - #[cfg(feature = "p521")] - use super::p521::DhP521; - - use hex_literal::hex; - use rand::{rngs::StdRng, SeedableRng}; - - // - // Test vectors come from RFC 5903 §8.1, §8.2 and §8.3 - // https://tools.ietf.org/html/rfc5903 - // - - #[cfg(feature = "p256")] - const P256_PRIVKEYS: &[&[u8]] = &[ - &hex!("C88F01F5 10D9AC3F 70A292DA A2316DE5 44E9AAB8 AFE84049 C62A9C57 862D1433"), - &hex!("C6EF9C5D 78AE012A 011164AC B397CE20 88685D8F 06BF9BE0 B283AB46 476BEE53"), - ]; - - // The public keys corresponding to the above private keys, in order - #[cfg(feature = "p256")] - const P256_PUBKEYS: &[&[u8]] = &[ - &hex!( - "04" // Uncompressed - "DAD0B653 94221CF9 B051E1FE CA5787D0 98DFE637 FC90B9EF 945D0C37 72581180" // x-coordinate - "5271A046 1CDB8252 D61F1C45 6FA3E59A B1F45B33 ACCF5F58 389E0577 B8990BB3" // y-coordinate - ), - &hex!( - "04" // Uncompressed - "D12DFB52 89C8D4F8 1208B702 70398C34 2296970A 0BCCB74C 736FC755 4494BF63" // x-coordinate - "56FBF3CA 366CC23E 8157854C 13C58D6A AC23F046 ADA30F83 53E74F33 039872AB" // y-coordinate - ), - ]; - - // The result of DH(privkey0, pubkey1) or equivalently, DH(privkey1, pubkey0) - #[cfg(feature = "p256")] - const P256_DH_RES_XCOORD: &[u8] = - &hex!("D6840F6B 42F6EDAF D13116E0 E1256520 2FEF8E9E CE7DCE03 812464D0 4B9442DE"); - - #[cfg(feature = "p384")] - const P384_PRIVKEYS: &[&[u8]] = &[ - &hex!( - "099F3C70 34D4A2C6 99884D73 A375A67F 7624EF7C 6B3C0F16 0647B674 14DCE655 E35B5380" - "41E649EE 3FAEF896 783AB194" - ), - &hex!( - "41CB0779 B4BDB85D 47846725 FBEC3C94 30FAB46C C8DC5060 855CC9BD A0AA2942 E0308312" - "916B8ED2 960E4BD5 5A7448FC" - ), - ]; - - // The public keys corresponding to the above private keys, in order - #[cfg(feature = "p384")] - const P384_PUBKEYS: &[&[u8]] = &[ - &hex!( - "04" // Uncompressed - "667842D7 D180AC2C DE6F74F3 7551F557 55C7645C 20EF73E3 1634FE72" // x-coordinate - "B4C55EE6 DE3AC808 ACB4BDB4 C88732AE E95F41AA" // ...cont - "9482ED1F C0EEB9CA FC498462 5CCFC23F 65032149 E0E144AD A0241815" // y-coordinate - "35A0F38E EB9FCFF3 C2C947DA E69B4C63 4573A81C" // ...cont - ), - &hex!( - "04" // Uncompressed - "E558DBEF 53EECDE3 D3FCCFC1 AEA08A89 A987475D 12FD950D 83CFA417" // x-coordinate - "32BC509D 0D1AC43A 0336DEF9 6FDA41D0 774A3571" // ...cont - "DCFBEC7A ACF31964 72169E83 8430367F 66EEBE3C 6E70C416 DD5F0C68" // y-coordinate - "759DD1FF F83FA401 42209DFF 5EAAD96D B9E6386C" // ...cont - ), - ]; - - // The result of DH(privkey0, pubkey1) or equivalently, DH(privkey1, pubkey0) - #[cfg(feature = "p384")] - const P384_DH_RES_XCOORD: &[u8] = &hex!( - "11187331 C279962D 93D60424 3FD592CB 9D0A926F 422E4718 7521287E 7156C5C4 D6031355" - "69B9E9D0 9CF5D4A2 70F59746" - ); - - #[cfg(feature = "p521")] - const P521_PRIVKEYS: &[&[u8]] = &[ - &hex!( - "0037ADE9 319A89F4 DABDB3EF 411AACCC A5123C61 ACAB57B5 393DCE47 608172A0" - "95AA85A3 0FE1C295 2C6771D9 37BA9777 F5957B26 39BAB072 462F68C2 7A57382D" - "4A52" - ), - &hex!( - "0145BA99 A847AF43 793FDD0E 872E7CDF A16BE30F DC780F97 BCCC3F07 8380201E" - "9C677D60 0B343757 A3BDBF2A 3163E4C2 F869CCA7 458AA4A4 EFFC311F 5CB15168" - "5EB9" - ), - ]; - - // The public keys corresponding to the above private keys, in order - #[cfg(feature = "p521")] - const P521_PUBKEYS: &[&[u8]] = &[ - &hex!( - "04" // Uncompressed - "0015417E 84DBF28C 0AD3C278 713349DC 7DF153C8 97A1891B D98BAB43 57C9ECBE" // x-coordinate - "E1E3BF42 E00B8E38 0AEAE57C 2D107564 94188594 2AF5A7F4 601723C4 195D176C" // ...cont - "ED3E" // ...cont - "017CAE20 B6641D2E EB695786 D8C94614 6239D099 E18E1D5A 514C739D 7CB4A10A" // y-coordinate - "D8A78801 5AC405D7 799DC75E 7B7D5B6C F2261A6A 7F150743 8BF01BEB 6CA3926F" // ...cont - "9582" // ...cont - ), - &hex!( - "04" // Uncompressed - "00D0B397 5AC4B799 F5BEA16D 5E13E9AF 971D5E9B 984C9F39 728B5E57 39735A21" // x-coordinate - "9B97C356 436ADC6E 95BB0352 F6BE64A6 C2912D4E F2D0433C ED2B6171 640012D9" // ...cont - "460F" // ...cont - "015C6822 6383956E 3BD066E7 97B623C2 7CE0EAC2 F551A10C 2C724D98 52077B87" // y-coordinate - "220B6536 C5C408A1 D2AEBB8E 86D678AE 49CB5709 1F473229 6579AB44 FCD17F0F" // ...cont - "C56A" // ...cont - ), - ]; - - // The result of DH(privkey0, pubkey1) or equivalently, DH(privkey1, pubkey0) - #[cfg(feature = "p521")] - const P521_DH_RES_XCOORD: &[u8] = &hex!( - "01144C7D 79AE6956 BC8EDB8E 7C787C45 21CB086F A64407F9 7894E5E6 B2D79B04" - "D1427E73 CA4BAA24 0A347868 59810C06 B3C715A3 A8CC3151 F2BEE417 996D19F3" - "DDEA" - ); - - #[cfg(feature = "k256")] - const K256_PRIVKEYS: &[&[u8]] = &[ - &hex!("30FBC0D4 1CD01885 333211FF 53B9ED29 BCBDCCC3 FF13625A 82DB61A7 BB8EAE19"), - &hex!("A795C287 C132154A 8B96DC81 DC8B4E2F 02BBBAD7 8DAB0567 B59DB1D1 540751F6"), - ]; - - // The public keys corresponding to the above private keys, in order - #[cfg(feature = "k256")] - const K256_PUBKEYS: &[&[u8]] = &[ - &hex!( - "04" // Uncompressed - "59177516 8F328A2A DBCB887A CD287D55 A1025D7D 2B15E193 7278A5EF D1D48B19" // x-coordinate - "C00CF075 59320E6D 278A71C9 E58BAE5D 9AB041D7 905C6629 1F4D0845 9C946E18" // y-coordinate - ), - &hex!( - "04" // Uncompressed - "3EE73144 07753D1B A296DE29 F07B2CD5 505CA94B 614F127E 71F3C19F C7845DAF" // x-coordinate - "49C9BB4B F4D00D3B 5411C8EB 86D59A2D CADC5A13 115FA9FE F44D1E0B 7EF11CAB" // y-coordinate - ), - ]; - - // The result of DH(privkey0, pubkey1) or equivalently, DH(privkey1, pubkey0) - #[cfg(feature = "k256")] - const K256_DH_RES_XCOORD: &[u8] = - &hex!("3ADDFBC2 B30E3D1B 1DF262A4 D6CECF73 A11DF8BD 93E0EB21 FC11847C 6F3DDBE2"); - - // - // Some helper functions for tests - // - - /// Tests the ECDH op against a known answer - #[allow(dead_code)] - fn test_vector_ecdh( - sk_recip_bytes: &[u8], - pk_sender_bytes: &[u8], - dh_res_xcoord_bytes: &[u8], - ) { - // Deserialize the pubkey and privkey and do a DH operation - let sk_recip = Kex::PrivateKey::from_bytes(&sk_recip_bytes).unwrap(); - let pk_sender = Kex::PublicKey::from_bytes(&pk_sender_bytes).unwrap(); - let derived_dh = Kex::dh(&sk_recip, &pk_sender).unwrap(); - - // Assert that the derived DH result matches the test vector. Recall that the HPKE DH - // result is just the x-coordinate, so that's all we can compare - assert_eq!(derived_dh.to_bytes().as_slice(), dh_res_xcoord_bytes,); - } - - /// Tests that an deserialize-serialize round-trip ends up at the same pubkey - #[allow(dead_code)] - fn test_pubkey_serialize_correctness() { - let mut csprng = StdRng::from_entropy(); - - // We can't do the same thing as in the X25519 tests, since a completely random point - // is not likely to lie on the curve. Instead, we just generate a random point, - // serialize it, deserialize it, and test whether it's the same using impl Eq for - // AffinePoint - - let (_, pubkey) = dhkex_gen_keypair::(&mut csprng); - let pubkey_bytes = pubkey.to_bytes(); - let rederived_pubkey = - ::PublicKey::from_bytes(&pubkey_bytes).unwrap(); - - // See if the re-serialized bytes are the same as the input - assert_eq!(pubkey, rederived_pubkey); - } - - /// Tests the `sk_to_pk` function against known answers - #[allow(dead_code)] - fn test_vector_corresponding_pubkey(sks: &[&[u8]], pks: &[&[u8]]) { - for (sk_bytes, pk_bytes) in sks.iter().zip(pks.iter()) { - // Deserialize the hex values - let sk = Kex::PrivateKey::from_bytes(sk_bytes).unwrap(); - let pk = Kex::PublicKey::from_bytes(pk_bytes).unwrap(); - - // Derive the secret key's corresponding pubkey and check that it matches the given - // pubkey - let derived_pk = Kex::sk_to_pk(&sk); - assert_eq!(derived_pk, pk); - } - } - - /// Tests that an deserialize-serialize round-trip on a DH keypair ends up at the same values - #[allow(dead_code)] - fn test_dh_serialize_correctness() - where - Kex::PrivateKey: PartialEq, - { - let mut csprng = StdRng::from_entropy(); - - // Make a random keypair and serialize it - let (sk, pk) = dhkex_gen_keypair::(&mut csprng); - let (sk_bytes, pk_bytes) = (sk.to_bytes(), pk.to_bytes()); - - // Now deserialize those bytes - let new_sk = Kex::PrivateKey::from_bytes(&sk_bytes).unwrap(); - let new_pk = Kex::PublicKey::from_bytes(&pk_bytes).unwrap(); - - // See if the deserialized values are the same as the initial ones - assert!(new_sk == sk, "private key doesn't serialize correctly"); - assert!(new_pk == pk, "public key doesn't serialize correctly"); - } - - #[cfg(feature = "p256")] - #[test] - fn test_vector_ecdh_p256() { - test_vector_ecdh::(&P256_PRIVKEYS[0], &P256_PUBKEYS[1], &P256_DH_RES_XCOORD); - } - - #[cfg(feature = "p384")] - #[test] - fn test_vector_ecdh_p384() { - test_vector_ecdh::(&P384_PRIVKEYS[0], &P384_PUBKEYS[1], &P384_DH_RES_XCOORD); - } - #[cfg(feature = "k256")] - #[test] - fn test_vector_ecdh_k256() { - test_vector_ecdh::(&K256_PRIVKEYS[0], &K256_PUBKEYS[1], &K256_DH_RES_XCOORD); - } - - #[cfg(feature = "p521")] - #[test] - fn test_vector_ecdh_p521() { - test_vector_ecdh::(&P521_PRIVKEYS[0], &P521_PUBKEYS[1], &P521_DH_RES_XCOORD); - } - - #[cfg(feature = "p256")] - #[test] - fn test_vector_corresponding_pubkey_p256() { - test_vector_corresponding_pubkey::(P256_PRIVKEYS, P256_PUBKEYS); - } - - #[cfg(feature = "p384")] - #[test] - fn test_vector_corresponding_pubkey_p384() { - test_vector_corresponding_pubkey::(P384_PRIVKEYS, P384_PUBKEYS); - } - #[cfg(feature = "k256")] - #[test] - fn test_vector_corresponding_pubkey_k256() { - test_vector_corresponding_pubkey::(K256_PRIVKEYS, K256_PUBKEYS); - } - - #[cfg(feature = "p521")] - #[test] - fn test_vector_corresponding_pubkey_p521() { - test_vector_corresponding_pubkey::(P521_PRIVKEYS, P521_PUBKEYS); - } - - #[cfg(feature = "p256")] - #[test] - fn test_pubkey_serialize_correctness_p256() { - test_pubkey_serialize_correctness::(); - } - - #[cfg(feature = "p384")] - #[test] - fn test_pubkey_serialize_correctness_p384() { - test_pubkey_serialize_correctness::(); - } - #[cfg(feature = "k256")] - #[test] - fn test_pubkey_serialize_correctness_k256() { - test_pubkey_serialize_correctness::(); - } - - #[cfg(feature = "p521")] - #[test] - fn test_pubkey_serialize_correctness_p521() { - test_pubkey_serialize_correctness::(); - } - - #[cfg(feature = "p256")] - #[test] - fn test_dh_serialize_correctness_p256() { - test_dh_serialize_correctness::(); - } - - #[cfg(feature = "p384")] - #[test] - fn test_dh_serialize_correctness_p384() { - test_dh_serialize_correctness::(); - } - - #[cfg(feature = "p521")] - #[test] - fn test_dh_serialize_correctness_p521() { - test_dh_serialize_correctness::(); - } - - #[cfg(feature = "k256")] - #[test] - fn test_dh_serialize_correctness_k256() { - test_dh_serialize_correctness::(); - } -} diff --git a/src/dhkex/x25519.rs b/src/dhkex/x25519.rs deleted file mode 100644 index f3f531a..0000000 --- a/src/dhkex/x25519.rs +++ /dev/null @@ -1,227 +0,0 @@ -use crate::{ - dhkex::{DhError, DhKeyExchange}, - kdf::{labeled_extract, Kdf as KdfTrait, LabeledExpand}, - util::{enforce_equal_len, enforce_outbuf_len, KemSuiteId}, - Deserializable, HpkeError, Serializable, -}; - -use generic_array::typenum::{self, Unsigned}; -use subtle::{Choice, ConstantTimeEq}; - -// We wrap the types in order to abstract away the dalek dep - -/// An X25519 public key -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PublicKey(x25519_dalek::PublicKey); - -// The underlying type is zeroize-on-drop -/// An X25519 private key -#[derive(Clone)] -pub struct PrivateKey(x25519_dalek::StaticSecret); - -impl ConstantTimeEq for PrivateKey { - fn ct_eq(&self, other: &Self) -> Choice { - // We can use to_bytes because StaticSecret is only ever constructed from a clamped scalar - self.0.to_bytes().ct_eq(&other.0.to_bytes()) - } -} - -impl PartialEq for PrivateKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} -impl Eq for PrivateKey {} - -// The underlying type is zeroize-on-drop -/// A bare DH computation result -pub struct KexResult(x25519_dalek::SharedSecret); - -// Oh I love an excuse to break out type-level integers -impl Serializable for PublicKey { - // RFC 9180 §7.1 Table 2: Npk of DHKEM(X25519, HKDF-SHA256) is 32 - type OutputSize = typenum::U32; - - // Dalek lets us convert pubkeys to [u8; 32] - fn write_exact(&self, buf: &mut [u8]) { - // Check the length is correct and panic if not - enforce_outbuf_len::(buf); - - buf.copy_from_slice(self.0.as_bytes()); - } -} - -impl Deserializable for PublicKey { - // Dalek lets us convert [u8; 32] to pubkeys. Assuming the input length is correct, this - // conversion is infallible, so no ValidationErrors are raised. - fn from_bytes(encoded: &[u8]) -> Result { - // Pubkeys must be 32 bytes - enforce_equal_len(Self::OutputSize::to_usize(), encoded.len())?; - - // Copy to a fixed-size array - let mut arr = [0u8; 32]; - arr.copy_from_slice(encoded); - Ok(PublicKey(x25519_dalek::PublicKey::from(arr))) - } -} - -impl Serializable for PrivateKey { - // RFC 9180 §7.1 Table 2: Nsk of DHKEM(X25519, HKDF-SHA256) is 32 - type OutputSize = typenum::U32; - - // Dalek lets us convert scalars to [u8; 32] - fn write_exact(&self, buf: &mut [u8]) { - // Check the length is correct and panic if not - enforce_outbuf_len::(buf); - - buf.copy_from_slice(self.0.as_bytes()); - } -} -impl Deserializable for PrivateKey { - // Dalek lets us convert [u8; 32] to scalars. Assuming the input length is correct, this - // conversion is infallible, so no ValidationErrors are raised. - fn from_bytes(encoded: &[u8]) -> Result { - // Privkeys must be 32 bytes - enforce_equal_len(Self::OutputSize::to_usize(), encoded.len())?; - - // Copy to a fixed-size array - let mut arr = [0u8; 32]; - arr.copy_from_slice(encoded); - // We don't have to do a zero-check for X25519 private keys. We clamp all private keys upon - // deserialization, and clamped private keys cannot ever be 0 mod curve_order. In fact, - // they can't even be 0 mod q where q is the order of the prime subgroup generated by the - // canonical generator. - // Why? - // A clamped key k is of the form 2^254 + 8j where j is in [0, 2^251-1]. If k = 0 (mod q) - // then k = nq for some n > 0. And since k is a multiple of 8 and q is prime, n must be a - // multiple of 8. However, 8q > 2^257 which is already out of representable range! So k - // cannot be 0 (mod q). - Ok(PrivateKey(x25519_dalek::StaticSecret::from(arr))) - } -} - -impl Serializable for KexResult { - // RFC 9180 §4.1: For X25519 and X448, the size Ndh is equal to 32 and 56, respectively - type OutputSize = typenum::U32; - - // curve25519's point representation is our DH result. We don't have to do anything special. - fn write_exact(&self, buf: &mut [u8]) { - // Check the length is correct and panic if not - enforce_outbuf_len::(buf); - - // Dalek lets us convert shared secrets to to [u8; 32] - buf.copy_from_slice(self.0.as_bytes()); - } -} - -/// Represents ECDH functionality over the X25519 group -pub struct X25519 {} - -impl DhKeyExchange for X25519 { - #[doc(hidden)] - type PublicKey = PublicKey; - #[doc(hidden)] - type PrivateKey = PrivateKey; - #[doc(hidden)] - type KexResult = KexResult; - - /// Converts an X25519 private key to a public key - #[doc(hidden)] - fn sk_to_pk(sk: &PrivateKey) -> PublicKey { - PublicKey(x25519_dalek::PublicKey::from(&sk.0)) - } - - /// Does the DH operation. Returns an error if and only if the DH result was all zeros. This is - /// required by the HPKE spec. The error is converted into the appropriate higher-level error - /// by the caller, i.e., `HpkeError::EncapError` or `HpkeError::DecapError`. - #[doc(hidden)] - fn dh(sk: &PrivateKey, pk: &PublicKey) -> Result { - let res = sk.0.diffie_hellman(&pk.0); - // "Senders and recipients MUST check whether the shared secret is the all-zero value - // and abort if so" - if res.as_bytes().ct_eq(&[0u8; 32]).into() { - Err(DhError) - } else { - Ok(KexResult(res)) - } - } - - // RFC 9180 §7.1.3 - // def DeriveKeyPair(ikm): - // dkp_prk = LabeledExtract("", "dkp_prk", ikm) - // sk = LabeledExpand(dkp_prk, "sk", "", Nsk) - // return (sk, pk(sk)) - - /// Deterministically derives a keypair from the given input keying material and ciphersuite - /// ID. The keying material SHOULD have as many bits of entropy as the bit length of a secret - /// key, i.e., 256. - #[doc(hidden)] - fn derive_keypair(suite_id: &KemSuiteId, ikm: &[u8]) -> (PrivateKey, PublicKey) { - // Write the label into a byte buffer and extract from the IKM - let (_, hkdf_ctx) = labeled_extract::(&[], suite_id, b"dkp_prk", ikm); - // The buffer we hold the candidate scalar bytes in. This is the size of a private key. - let mut buf = [0u8; 32]; - hkdf_ctx - .labeled_expand(suite_id, b"sk", &[], &mut buf) - .unwrap(); - - let sk = x25519_dalek::StaticSecret::from(buf); - let pk = x25519_dalek::PublicKey::from(&sk); - - (PrivateKey(sk), PublicKey(pk)) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - dhkex::{x25519::X25519, Deserializable, DhKeyExchange, Serializable}, - test_util::dhkex_gen_keypair, - }; - use generic_array::typenum::Unsigned; - use rand::{rngs::StdRng, RngCore, SeedableRng}; - - /// Tests that an serialize-deserialize round-trip ends up at the same pubkey - #[test] - fn test_pubkey_serialize_correctness() { - type Kex = X25519; - - let mut csprng = StdRng::from_entropy(); - - // Fill a buffer with randomness - let orig_bytes = { - let mut buf = - [0u8; <::PublicKey as Serializable>::OutputSize::USIZE]; - csprng.fill_bytes(buf.as_mut_slice()); - buf - }; - - // Make a pubkey with those random bytes. Note, that from_bytes() does not clamp the input - // bytes. This is why this test passes. - let pk = ::PublicKey::from_bytes(&orig_bytes).unwrap(); - let pk_bytes = pk.to_bytes(); - - // See if the re-serialized bytes are the same as the input - assert_eq!(orig_bytes.as_slice(), pk_bytes.as_slice()); - } - - /// Tests that an deserialize-serialize round trip on a DH keypair ends up at the same values - #[test] - fn test_dh_serialize_correctness() { - type Kex = X25519; - - let mut csprng = StdRng::from_entropy(); - - // Make a random keypair and serialize it - let (sk, pk) = dhkex_gen_keypair::(&mut csprng); - let (sk_bytes, pk_bytes) = (sk.to_bytes(), pk.to_bytes()); - - // Now deserialize those bytes - let new_sk = ::PrivateKey::from_bytes(&sk_bytes).unwrap(); - let new_pk = ::PublicKey::from_bytes(&pk_bytes).unwrap(); - - // See if the deserialized values are the same as the initial ones - assert!(new_sk == sk, "private key doesn't serialize correctly"); - assert!(new_pk == pk, "public key doesn't serialize correctly"); - } -} diff --git a/src/kat_tests.rs b/src/kat_tests.rs index 4751ecf..2617eaa 100644 --- a/src/kat_tests.rs +++ b/src/kat_tests.rs @@ -2,8 +2,7 @@ use crate::{ aead::{Aead, AesGcm128, AesGcm256, ChaCha20Poly1305, ExportOnlyAead}, kdf::{HkdfSha256, HkdfSha384, HkdfSha512, Kdf as KdfTrait}, kem::{ - self, DhP256HkdfSha256, DhP384HkdfSha384, DhP521HkdfSha512, Kem as KemTrait, SharedSecret, - X25519HkdfSha256, + self, SecpK256HkdfSha256, Kem as KemTrait, SharedSecret, }, op_mode::{OpModeR, PskBundle}, setup::setup_receiver, @@ -33,9 +32,9 @@ trait TestableKem: KemTrait { } // Now implement TestableKem for all the KEMs in the KAT -impl TestableKem for X25519HkdfSha256 { +impl TestableKem for SecpK256HkdfSha256 { // In DHKEM, ephemeral keys and private keys are both scalars - type EphemeralKey = ::PrivateKey; + type EphemeralKey = ::PrivateKey; // Call the x25519 deterministic encap function we defined in dhkem.rs fn encap_with_eph( @@ -43,50 +42,10 @@ impl TestableKem for X25519HkdfSha256 { sender_id_keypair: Option<(&Self::PrivateKey, &Self::PublicKey)>, sk_eph: Self::EphemeralKey, ) -> Result<(SharedSecret, Self::EncappedKey), HpkeError> { - kem::x25519_hkdfsha256::encap_with_eph(pk_recip, sender_id_keypair, sk_eph) + kem::secpk256_hkdfsha256::encap_with_eph(pk_recip, sender_id_keypair, sk_eph) } } -impl TestableKem for DhP256HkdfSha256 { - // In DHKEM, ephemeral keys and private keys are both scalars - type EphemeralKey = ::PrivateKey; - - // Call the p256 deterministic encap function we defined in dhkem.rs - fn encap_with_eph( - pk_recip: &Self::PublicKey, - sender_id_keypair: Option<(&Self::PrivateKey, &Self::PublicKey)>, - sk_eph: Self::EphemeralKey, - ) -> Result<(SharedSecret, Self::EncappedKey), HpkeError> { - kem::dhp256_hkdfsha256::encap_with_eph(pk_recip, sender_id_keypair, sk_eph) - } -} - -impl TestableKem for DhP384HkdfSha384 { - // In DHKEM, ephemeral keys and private keys are both scalars - type EphemeralKey = ::PrivateKey; - - // Call the p384 deterministic encap function we defined in dhkem.rs - fn encap_with_eph( - pk_recip: &Self::PublicKey, - sender_id_keypair: Option<(&Self::PrivateKey, &Self::PublicKey)>, - sk_eph: Self::EphemeralKey, - ) -> Result<(SharedSecret, Self::EncappedKey), HpkeError> { - kem::dhp384_hkdfsha384::encap_with_eph(pk_recip, sender_id_keypair, sk_eph) - } -} - -impl TestableKem for DhP521HkdfSha512 { - // In DHKEM, ephemeral keys and private keys are both scalars - type EphemeralKey = ::PrivateKey; - // Call the p521 deterministic encap function we defined in dhkem.rs - fn encap_with_eph( - pk_recip: &Self::PublicKey, - sender_id_keypair: Option<(&Self::PrivateKey, &Self::PublicKey)>, - sk_eph: Self::EphemeralKey, - ) -> Result<(SharedSecret, Self::EncappedKey), HpkeError> { - kem::dhp521_hkdfsha512::encap_with_eph(pk_recip, sender_id_keypair, sk_eph) - } -} /// Asserts that the given serializable values are equal macro_rules! assert_serializable_eq { @@ -380,12 +339,9 @@ fn kat_test() { let tvs: Vec = serde_json::from_reader(file).unwrap(); for tv in tvs.into_iter() { - // Ignore everything that doesn't use X25519, P256, P384 or P521, since that's all we support + // Ignore everything that doesn't use Secp256k1, since that's all we support // right now - if tv.kem_id != X25519HkdfSha256::KEM_ID - && tv.kem_id != DhP256HkdfSha256::KEM_ID - && tv.kem_id != DhP384HkdfSha384::KEM_ID - && tv.kem_id != DhP521HkdfSha512::KEM_ID + if tv.kem_id != SecpK256HkdfSha256::KEM_ID { continue; } @@ -396,10 +352,7 @@ fn kat_test() { (AesGcm128, AesGcm256, ChaCha20Poly1305, ExportOnlyAead), (HkdfSha256, HkdfSha384, HkdfSha512), ( - X25519HkdfSha256, - DhP256HkdfSha256, - DhP384HkdfSha384, - DhP521HkdfSha512 + SecpK256HkdfSha256, ) ); diff --git a/src/kem.rs b/src/kem.rs index c7b4991..e78dc33 100644 --- a/src/kem.rs +++ b/src/kem.rs @@ -192,46 +192,6 @@ mod tests { }; } - #[cfg(feature = "x25519")] - mod x25519_tests { - use super::*; - - test_encap_correctness!(test_encap_correctness_x25519, crate::kem::X25519HkdfSha256); - test_encapped_serialize!(test_encapped_serialize_x25519, crate::kem::X25519HkdfSha256); - } - - #[cfg(feature = "p256")] - mod p256_tests { - use super::*; - - test_encap_correctness!(test_encap_correctness_p256, crate::kem::DhP256HkdfSha256); - test_encapped_serialize!(test_encapped_serialize_p256, crate::kem::DhP256HkdfSha256); - } - - #[cfg(feature = "p384")] - mod p384_tests { - use super::*; - - test_encap_correctness!(test_encap_correctness_p384, crate::kem::DhP384HkdfSha384); - test_encapped_serialize!(test_encapped_serialize_p384, crate::kem::DhP384HkdfSha384); - } - - #[cfg(feature = "p521")] - mod p521_tests { - use super::*; - - test_encap_correctness!(test_encap_correctness_p521, crate::kem::DhP521HkdfSha512); - test_encapped_serialize!(test_encapped_serialize_p521, crate::kem::DhP521HkdfSha512); - } - - #[cfg(feature = "k256")] - mod k256_tests { - use super::*; - - test_encap_correctness!(test_encap_correctness_k256, crate::kem::DhK256HkdfSha256); - test_encapped_serialize!(test_encapped_serialize_k256, crate::kem::DhK256HkdfSha256); - } - #[cfg(feature = "secp")] mod secp_tests { use super::*; diff --git a/src/kem/dhkem.rs b/src/kem/dhkem.rs index 7313585..421e766 100644 --- a/src/kem/dhkem.rs +++ b/src/kem/dhkem.rs @@ -348,61 +348,6 @@ macro_rules! impl_dhkem { }; } -// Implement DHKEM(X25519, HKDF-SHA256) -#[cfg(feature = "x25519")] -impl_dhkem!( - x25519_hkdfsha256, - X25519HkdfSha256, - crate::dhkex::x25519::X25519, - crate::kdf::HkdfSha256, - 0x0020, - "Represents DHKEM(X25519, HKDF-SHA256)" -); - -// Implement DHKEM(P-256, HKDF-SHA256) -#[cfg(feature = "p256")] -impl_dhkem!( - dhp256_hkdfsha256, - DhP256HkdfSha256, - crate::dhkex::ecdh_nist::p256::DhP256, - crate::kdf::HkdfSha256, - 0x0010, - "Represents DHKEM(P-256, HKDF-SHA256)" -); - -// Implement DHKEM(P-384, HKDF-SHA384) -#[cfg(feature = "p384")] -impl_dhkem!( - dhp384_hkdfsha384, - DhP384HkdfSha384, - crate::dhkex::ecdh_nist::p384::DhP384, - crate::kdf::HkdfSha384, - 0x0011, - "Represents DHKEM(P-384, HKDF-SHA384)" -); - -// Implement DHKEM(P-521, HKDF-SHA512) -#[cfg(feature = "p521")] -impl_dhkem!( - dhp521_hkdfsha512, - DhP521HkdfSha512, - crate::dhkex::ecdh_nist::p521::DhP521, - crate::kdf::HkdfSha512, - 0x0012, - "Represents DHKEM(P-521, HKDF-SHA512)" -); - -// Implement DHKEM(K-256, HKDF-SHA256) -#[cfg(feature = "k256")] -impl_dhkem!( - dhk256_hkdfsha256, - DhK256HkdfSha256, - crate::dhkex::ecdh_nist::k256::DhK256, - crate::kdf::HkdfSha256, - 0x0016, - "Represents DHKEM(K-256, HKDF-SHA256)" -); - // Implement DHKEM(Secp256k1, HKDF-SHA256) #[cfg(feature = "secp")] impl_dhkem!( diff --git a/src/lib.rs b/src/lib.rs index 9ee14a8..5d0d996 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,15 +96,7 @@ pub(crate) use alloc::vec::Vec; // kat_tests tests all the implemented ciphersuites, and thus needs all the dependencies. It also // needs std for file IO. -#[cfg(all( - test, - feature = "std", - feature = "x25519", - feature = "p256", - feature = "p384", - feature = "p521", - feature = "secp" -))] +#[cfg(all(test, feature = "std", feature = "secp"))] mod kat_tests; #[cfg(test)] diff --git a/src/setup.rs b/src/setup.rs index 2c6d891..b057f4c 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -337,98 +337,6 @@ mod test { }; } - #[cfg(feature = "x25519")] - mod x25519_tests { - use super::*; - - test_setup_correctness!( - test_setup_correctness_x25519, - ChaCha20Poly1305, - HkdfSha256, - crate::kem::x25519_hkdfsha256::X25519HkdfSha256 - ); - test_setup_soundness!( - test_setup_soundness_x25519, - ChaCha20Poly1305, - HkdfSha256, - crate::kem::x25519_hkdfsha256::X25519HkdfSha256 - ); - } - - #[cfg(feature = "p256")] - mod p256_tests { - use super::*; - - test_setup_correctness!( - test_setup_correctness_p256, - ChaCha20Poly1305, - HkdfSha256, - crate::kem::dhp256_hkdfsha256::DhP256HkdfSha256 - ); - test_setup_soundness!( - test_setup_soundness_p256, - ChaCha20Poly1305, - HkdfSha256, - crate::kem::dhp256_hkdfsha256::DhP256HkdfSha256 - ); - } - - #[cfg(feature = "p384")] - mod p384_tests { - use super::*; - use crate::kdf::HkdfSha384; - - test_setup_correctness!( - test_setup_correctness_p384, - ChaCha20Poly1305, - HkdfSha384, - crate::kem::dhp384_hkdfsha384::DhP384HkdfSha384 - ); - test_setup_soundness!( - test_setup_soundness_p384, - ChaCha20Poly1305, - HkdfSha384, - crate::kem::dhp384_hkdfsha384::DhP384HkdfSha384 - ); - } - - #[cfg(feature = "p521")] - mod p521_tests { - use super::*; - use crate::kdf::HkdfSha512; - - test_setup_correctness!( - test_setup_correctness_p521, - ChaCha20Poly1305, - HkdfSha512, - crate::kem::dhp521_hkdfsha512::DhP521HkdfSha512 - ); - test_setup_soundness!( - test_setup_soundness_p521, - ChaCha20Poly1305, - HkdfSha512, - crate::kem::dhp521_hkdfsha512::DhP521HkdfSha512 - ); - } - - #[cfg(feature = "k256")] - mod k256_tests { - use super::*; - - test_setup_correctness!( - test_setup_correctness_k256, - ChaCha20Poly1305, - HkdfSha256, - crate::kem::dhk256_hkdfsha256::DhK256HkdfSha256 - ); - test_setup_soundness!( - test_setup_soundness_k256, - ChaCha20Poly1305, - HkdfSha256, - crate::kem::dhk256_hkdfsha256::DhK256HkdfSha256 - ); - } - #[cfg(feature = "secp")] mod secp_tests { use super::*; diff --git a/src/single_shot.rs b/src/single_shot.rs index 83fd60b..e3eabf3 100644 --- a/src/single_shot.rs +++ b/src/single_shot.rs @@ -224,46 +224,6 @@ mod test { }; } - #[cfg(feature = "x25519")] - test_single_shot_correctness!( - test_single_shot_correctness_x25519, - ChaCha20Poly1305, - crate::kdf::HkdfSha256, - crate::kem::x25519_hkdfsha256::X25519HkdfSha256 - ); - - #[cfg(feature = "p256")] - test_single_shot_correctness!( - test_single_shot_correctness_p256, - ChaCha20Poly1305, - crate::kdf::HkdfSha256, - crate::kem::dhp256_hkdfsha256::DhP256HkdfSha256 - ); - - #[cfg(feature = "p384")] - test_single_shot_correctness!( - test_single_shot_correctness_p384, - ChaCha20Poly1305, - crate::kdf::HkdfSha384, - crate::kem::dhp384_hkdfsha384::DhP384HkdfSha384 - ); - - #[cfg(feature = "p521")] - test_single_shot_correctness!( - test_single_shot_correctness_p521, - ChaCha20Poly1305, - crate::kdf::HkdfSha512, - crate::kem::dhp521_hkdfsha512::DhP521HkdfSha512 - ); - - #[cfg(feature = "k256")] - test_single_shot_correctness!( - test_single_shot_correctness_k256, - ChaCha20Poly1305, - crate::kdf::HkdfSha256, - crate::kem::dhk256_hkdfsha256::DhK256HkdfSha256 - ); - #[cfg(feature = "secp")] test_single_shot_correctness!( test_single_shot_correctness_secp,