From 81a7782e5ad42a434f84a16234b24b85f57a1ccf Mon Sep 17 00:00:00 2001 From: Dan Gould Date: Fri, 5 Jul 2024 11:52:05 -0400 Subject: [PATCH] secp256k1 DHKEM (#59) * Impld secp256k1 DHKEX * Filled in k256 KEM and most automated tests * Removed serde from CI * Add DHKEM(secp256k1, HKDF-SHA256) to README KEM name listed at iana.org see: https://www.iana.org/assignments/hpke/hpke.xhtml --- Cargo.toml | 2 + README.md | 1 + src/aead.rs | 29 ++++++++++ src/dhkex.rs | 4 +- src/dhkex/{ecdh_nistp.rs => ecdh_nist.rs} | 68 +++++++++++++++++++++-- src/kem.rs | 8 +++ src/kem/dhkem.rs | 17 +++++- src/setup.rs | 18 ++++++ src/single_shot.rs | 8 +++ 9 files changed, 146 insertions(+), 9 deletions(-) rename src/dhkex/{ecdh_nistp.rs => ecdh_nist.rs} (91%) diff --git a/Cargo.toml b/Cargo.toml index ae65c16..b033403 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ x25519 = ["dep:x25519-dalek"] p384 = ["dep:p384"] p256 = ["dep:p256"] p521 = ["dep:p521"] +k256 = ["dep:k256"] # Include allocating methods like open() and seal() alloc = [] # Includes an implementation of `std::error::Error` for `HpkeError`. Also does what `alloc` does. @@ -34,6 +35,7 @@ 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} diff --git a/README.md b/README.md index 196e588..ded711b 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Here are all the primitives listed in the spec. The primitives with checked boxe - [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 - [X] HKDF-SHA384 diff --git a/src/aead.rs b/src/aead.rs index 3f0958d..2ccb98c 100644 --- a/src/aead.rs +++ b/src/aead.rs @@ -764,6 +764,35 @@ mod test { ); } + #[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 + ); + } + /// Tests that Serialize::write_exact() panics when given a buffer of incorrect length #[should_panic] #[test] diff --git a/src/dhkex.rs b/src/dhkex.rs index e2aca55..bf27779 100644 --- a/src/dhkex.rs +++ b/src/dhkex.rs @@ -44,8 +44,8 @@ pub trait DhKeyExchange { ) -> (Self::PrivateKey, Self::PublicKey); } -#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))] -pub(crate) mod ecdh_nistp; +#[cfg(any(feature = "p256", feature = "p384", feature = "k256", feature = "k256"))] +pub(crate) mod ecdh_nist; #[cfg(feature = "x25519")] pub(crate) mod x25519; diff --git a/src/dhkex/ecdh_nistp.rs b/src/dhkex/ecdh_nist.rs similarity index 91% rename from src/dhkex/ecdh_nistp.rs rename to src/dhkex/ecdh_nist.rs index bbe764c..ff1fb60 100644 --- a/src/dhkex/ecdh_nistp.rs +++ b/src/dhkex/ecdh_nist.rs @@ -1,5 +1,5 @@ // We define all the NIST P- curve ECDH functionalities in one macro -macro_rules! nistp_dhkex { +macro_rules! nist_dhkex { ( $curve_name:expr, $dh_name:ident, @@ -238,7 +238,7 @@ macro_rules! nistp_dhkex { use generic_array::typenum; #[cfg(feature = "p256")] -nistp_dhkex!( +nist_dhkex!( "P-256", DhP256, p256, @@ -249,7 +249,7 @@ nistp_dhkex!( ); #[cfg(feature = "p384")] -nistp_dhkex!( +nist_dhkex!( "P-384", DhP384, p384, @@ -260,7 +260,7 @@ nistp_dhkex!( ); #[cfg(feature = "p521")] -nistp_dhkex!( +nist_dhkex!( "P-521", DhP521, p521, @@ -270,10 +270,23 @@ nistp_dhkex!( 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")] @@ -398,6 +411,32 @@ mod tests { "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 // @@ -485,6 +524,11 @@ mod tests { 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] @@ -503,6 +547,11 @@ mod tests { 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] @@ -521,6 +570,11 @@ mod tests { 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] @@ -545,4 +599,10 @@ mod tests { 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/kem.rs b/src/kem.rs index 734eb3a..c74efe6 100644 --- a/src/kem.rs +++ b/src/kem.rs @@ -223,4 +223,12 @@ mod tests { 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); + } } diff --git a/src/kem/dhkem.rs b/src/kem/dhkem.rs index 008204a..44b7c8b 100644 --- a/src/kem/dhkem.rs +++ b/src/kem/dhkem.rs @@ -364,7 +364,7 @@ impl_dhkem!( impl_dhkem!( dhp256_hkdfsha256, DhP256HkdfSha256, - crate::dhkex::ecdh_nistp::p256::DhP256, + crate::dhkex::ecdh_nist::p256::DhP256, crate::kdf::HkdfSha256, 0x0010, "Represents DHKEM(P-256, HKDF-SHA256)" @@ -375,7 +375,7 @@ impl_dhkem!( impl_dhkem!( dhp384_hkdfsha384, DhP384HkdfSha384, - crate::dhkex::ecdh_nistp::p384::DhP384, + crate::dhkex::ecdh_nist::p384::DhP384, crate::kdf::HkdfSha384, 0x0011, "Represents DHKEM(P-384, HKDF-SHA384)" @@ -386,8 +386,19 @@ impl_dhkem!( impl_dhkem!( dhp521_hkdfsha512, DhP521HkdfSha512, - crate::dhkex::ecdh_nistp::p521::DhP521, + 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)" +); diff --git a/src/setup.rs b/src/setup.rs index ebc5ccd..7d321ee 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -410,4 +410,22 @@ mod test { 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 + ); + } } diff --git a/src/single_shot.rs b/src/single_shot.rs index fefb622..9dbef2f 100644 --- a/src/single_shot.rs +++ b/src/single_shot.rs @@ -255,4 +255,12 @@ mod test { 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 + ); }