diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f7529c..c79bf13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,12 @@ jobs: 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/CHANGELOG.md b/CHANGELOG.md index d468635..8584c3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `Serializable::write_exact` so serialization requires less stack space * Removed all impls of `serde::{Serialize, Deserailize}` from crate +* Added support for the P-521 curve ## [0.11.0] - 2023-10-11 diff --git a/Cargo.toml b/Cargo.toml index 65b3bdf..2979798 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ default = ["alloc", "p256", "x25519"] x25519 = ["dep:x25519-dalek"] p384 = ["dep:p384"] p256 = ["dep:p256"] +p521 = ["dep:p521"] # Include allocating methods like open() and seal() alloc = [] # Includes an implementation of `std::error::Error` for `HpkeError`. Also does what `alloc` does. @@ -36,6 +37,7 @@ hmac = "0.12" rand_core = { version = "0.6", default-features = false } 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.5", default-features = false } x25519-dalek = { version = "2", default-features = false, features = ["static_secrets"], optional = true } @@ -55,7 +57,7 @@ required-features = ["x25519"] [[example]] name = "agility" -required-features = ["p256", "p384", "x25519"] +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] diff --git a/README.md b/README.md index 1287ab3..2051647 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Here are all the primitives listed in the spec. The primitives with checked boxe - [ ] DHKEM(Curve448, HKDF-SHA512) - [X] DHKEM(P-256, HKDF-SHA256) - [X] DHKEM(P-384, HKDF-SHA384) - - [ ] DHKEM(P-521, HKDF-SHA512) + - [X] DHKEM(P-521, HKDF-SHA512) * KDFs - [X] HKDF-SHA256 - [X] HKDF-SHA384 @@ -51,6 +51,7 @@ Feature flag list: * `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 * `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). diff --git a/examples/agility.rs b/examples/agility.rs index fd67c00..0776c87 100644 --- a/examples/agility.rs +++ b/examples/agility.rs @@ -15,7 +15,9 @@ use hpke::{ aead::{Aead, AeadCtxR, AeadCtxS, AeadTag, AesGcm128, AesGcm256, ChaCha20Poly1305}, kdf::{HkdfSha256, HkdfSha384, HkdfSha512, Kdf as KdfTrait}, - kem::{DhP256HkdfSha256, DhP384HkdfSha384, Kem as KemTrait, X25519HkdfSha256}, + kem::{ + DhP256HkdfSha256, DhP384HkdfSha384, DhP521HkdfSha512, Kem as KemTrait, X25519HkdfSha256, + }, setup_receiver, setup_sender, Deserializable, HpkeError, OpModeR, OpModeS, PskBundle, Serializable, }; @@ -309,6 +311,7 @@ fn agile_gen_keypair(kem_alg: KemAlg, csprng: &mut R) -> 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!(), } } @@ -572,7 +575,7 @@ fn agile_setup_sender( res, to_match, (ChaCha20Poly1305, AesGcm128, AesGcm256), (HkdfSha256, HkdfSha384, HkdfSha512), - (X25519HkdfSha256, DhP256HkdfSha256), + (X25519HkdfSha256, DhP256HkdfSha256, DhP384HkdfSha384, DhP521HkdfSha512), R, do_setup_sender, mode, @@ -655,7 +658,7 @@ fn agile_setup_receiver( res, to_match, (ChaCha20Poly1305, AesGcm128, AesGcm256), (HkdfSha256, HkdfSha384, HkdfSha512), - (X25519HkdfSha256, DhP256HkdfSha256), + (X25519HkdfSha256, DhP256HkdfSha256, DhP384HkdfSha384, DhP521HkdfSha512), Unit, do_setup_receiver, mode, @@ -683,6 +686,7 @@ fn main() { KemAlg::X25519HkdfSha256, KemAlg::DhP256HkdfSha256, KemAlg::DhP384HkdfSha384, + KemAlg::DhP521HkdfSha512, ]; let supported_kdf_algs = &[KdfAlg::HkdfSha256, KdfAlg::HkdfSha384, KdfAlg::HkdfSha512]; diff --git a/src/dhkex.rs b/src/dhkex.rs index 2a3e29c..e2aca55 100644 --- a/src/dhkex.rs +++ b/src/dhkex.rs @@ -44,7 +44,7 @@ pub trait DhKeyExchange { ) -> (Self::PrivateKey, Self::PublicKey); } -#[cfg(any(feature = "p256", feature = "p384"))] +#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))] pub(crate) mod ecdh_nistp; #[cfg(feature = "x25519")] diff --git a/src/dhkex/ecdh_nistp.rs b/src/dhkex/ecdh_nistp.rs index 00d3fb8..bbe764c 100644 --- a/src/dhkex/ecdh_nistp.rs +++ b/src/dhkex/ecdh_nistp.rs @@ -259,6 +259,17 @@ nistp_dhkex!( 0xFF // RFC 9180 §7.1.3: The `bitmask` in DeriveKeyPair to be 0xFF for P-384 ); +#[cfg(feature = "p521")] +nistp_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(test)] mod tests { use crate::{dhkex::DhKeyExchange, test_util::dhkex_gen_keypair, Deserializable, Serializable}; @@ -267,12 +278,14 @@ mod tests { 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 and §8.2 + // Test vectors come from RFC 5903 §8.1, §8.2 and §8.3 // https://tools.ietf.org/html/rfc5903 // @@ -340,6 +353,51 @@ mod tests { "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" + ); + // // Some helper functions for tests // @@ -421,43 +479,70 @@ mod tests { 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 = "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 = "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 = "256")] + #[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 = "384")] + #[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::(); + } } diff --git a/src/kat_tests.rs b/src/kat_tests.rs index e1b2149..4751ecf 100644 --- a/src/kat_tests.rs +++ b/src/kat_tests.rs @@ -2,7 +2,8 @@ use crate::{ aead::{Aead, AesGcm128, AesGcm256, ChaCha20Poly1305, ExportOnlyAead}, kdf::{HkdfSha256, HkdfSha384, HkdfSha512, Kdf as KdfTrait}, kem::{ - self, DhP256HkdfSha256, DhP384HkdfSha384, Kem as KemTrait, SharedSecret, X25519HkdfSha256, + self, DhP256HkdfSha256, DhP384HkdfSha384, DhP521HkdfSha512, Kem as KemTrait, SharedSecret, + X25519HkdfSha256, }, op_mode::{OpModeR, PskBundle}, setup::setup_receiver, @@ -73,6 +74,20 @@ impl TestableKem for DhP384HkdfSha384 { } } +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 { ($a:expr, $b:expr, $args:tt) => { @@ -365,11 +380,12 @@ 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, or P384, since that's all we support + // Ignore everything that doesn't use X25519, P256, P384 or P521, 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 { continue; } @@ -379,7 +395,12 @@ fn kat_test() { tv, (AesGcm128, AesGcm256, ChaCha20Poly1305, ExportOnlyAead), (HkdfSha256, HkdfSha384, HkdfSha512), - (X25519HkdfSha256, DhP256HkdfSha256, DhP384HkdfSha384) + ( + X25519HkdfSha256, + DhP256HkdfSha256, + DhP384HkdfSha384, + DhP521HkdfSha512 + ) ); // The above macro has a `continue` in every branch. We only get to this line if it failed diff --git a/src/kem.rs b/src/kem.rs index 532714c..734eb3a 100644 --- a/src/kem.rs +++ b/src/kem.rs @@ -215,4 +215,12 @@ mod tests { 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); + } } diff --git a/src/kem/dhkem.rs b/src/kem/dhkem.rs index e5bb6a9..008204a 100644 --- a/src/kem/dhkem.rs +++ b/src/kem/dhkem.rs @@ -380,3 +380,14 @@ impl_dhkem!( 0x0011, "Represents DHKEM(P-384, HKDF-SHA384)" ); + +// Implement DHKEM(P-521, HKDF-SHA512) +#[cfg(feature = "p521")] +impl_dhkem!( + dhp521_hkdfsha512, + DhP521HkdfSha512, + crate::dhkex::ecdh_nistp::p521::DhP521, + crate::kdf::HkdfSha512, + 0x0012, + "Represents DHKEM(P-521, HKDF-SHA512)" +); diff --git a/src/lib.rs b/src/lib.rs index af55107..cc3a88b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,7 +101,8 @@ pub(crate) use alloc::vec::Vec; feature = "std", feature = "x25519", feature = "p256", - feature = "p384" + feature = "p384", + feature = "p521" ))] mod kat_tests; diff --git a/src/setup.rs b/src/setup.rs index 59b0a08..ebc5ccd 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -391,4 +391,23 @@ mod test { 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 + ); + } } diff --git a/src/single_shot.rs b/src/single_shot.rs index 9059d6a..fefb622 100644 --- a/src/single_shot.rs +++ b/src/single_shot.rs @@ -247,4 +247,12 @@ mod test { 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 + ); }