Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for P-521 #54

Merged
merged 5 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 }
Expand All @@ -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]
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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).
Expand Down
10 changes: 7 additions & 3 deletions examples/agility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -309,6 +311,7 @@ fn agile_gen_keypair<R: CryptoRng + RngCore>(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!(),
}
}
Expand Down Expand Up @@ -572,7 +575,7 @@ fn agile_setup_sender<R: CryptoRng + RngCore>(
res, to_match,
(ChaCha20Poly1305, AesGcm128, AesGcm256),
(HkdfSha256, HkdfSha384, HkdfSha512),
(X25519HkdfSha256, DhP256HkdfSha256),
(X25519HkdfSha256, DhP256HkdfSha256, DhP384HkdfSha384, DhP521HkdfSha512),
R,
do_setup_sender,
mode,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -683,6 +686,7 @@ fn main() {
KemAlg::X25519HkdfSha256,
KemAlg::DhP256HkdfSha256,
KemAlg::DhP384HkdfSha384,
KemAlg::DhP521HkdfSha512,
];
let supported_kdf_algs = &[KdfAlg::HkdfSha256, KdfAlg::HkdfSha384, KdfAlg::HkdfSha512];

Expand Down
2 changes: 1 addition & 1 deletion src/dhkex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
91 changes: 88 additions & 3 deletions src/dhkex/ecdh_nistp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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
//

Expand Down Expand Up @@ -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
//
Expand Down Expand Up @@ -421,43 +479,70 @@ mod tests {
fn test_vector_ecdh_p256() {
test_vector_ecdh::<DhP256>(&P256_PRIVKEYS[0], &P256_PUBKEYS[1], &P256_DH_RES_XCOORD);
}

#[cfg(feature = "p384")]
#[test]
fn test_vector_ecdh_p384() {
test_vector_ecdh::<DhP384>(&P384_PRIVKEYS[0], &P384_PUBKEYS[1], &P384_DH_RES_XCOORD);
}

#[cfg(feature = "p521")]
#[test]
fn test_vector_ecdh_p521() {
test_vector_ecdh::<DhP521>(&P521_PRIVKEYS[0], &P521_PUBKEYS[1], &P521_DH_RES_XCOORD);
}

#[cfg(feature = "p256")]
#[test]
fn test_vector_corresponding_pubkey_p256() {
test_vector_corresponding_pubkey::<DhP256>(P256_PRIVKEYS, P256_PUBKEYS);
}

#[cfg(feature = "p384")]
#[test]
fn test_vector_corresponding_pubkey_p384() {
test_vector_corresponding_pubkey::<DhP384>(P384_PRIVKEYS, P384_PUBKEYS);
}

#[cfg(feature = "p521")]
#[test]
fn test_vector_corresponding_pubkey_p521() {
test_vector_corresponding_pubkey::<DhP521>(P521_PRIVKEYS, P521_PUBKEYS);
}

#[cfg(feature = "p256")]
#[test]
fn test_pubkey_serialize_correctness_p256() {
test_pubkey_serialize_correctness::<DhP256>();
}

#[cfg(feature = "p384")]
#[test]
fn test_pubkey_serialize_correctness_p384() {
test_pubkey_serialize_correctness::<DhP384>();
}

#[cfg(feature = "256")]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Dunno how this got here

#[cfg(feature = "p521")]
#[test]
fn test_pubkey_serialize_correctness_p521() {
test_pubkey_serialize_correctness::<DhP521>();
}

#[cfg(feature = "p256")]
#[test]
fn test_dh_serialize_correctness_p256() {
test_dh_serialize_correctness::<DhP256>();
}

#[cfg(feature = "384")]
#[cfg(feature = "p384")]
#[test]
fn test_dh_serialize_correctness_p384() {
test_dh_serialize_correctness::<DhP384>();
}

#[cfg(feature = "p521")]
#[test]
fn test_dh_serialize_correctness_p521() {
test_dh_serialize_correctness::<DhP521>();
}
}
27 changes: 24 additions & 3 deletions src/kat_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -73,6 +74,20 @@ impl TestableKem for DhP384HkdfSha384 {
}
}

impl TestableKem for DhP521HkdfSha512 {
// In DHKEM, ephemeral keys and private keys are both scalars
type EphemeralKey = <DhP521HkdfSha512 as KemTrait>::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>, 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) => {
Expand Down Expand Up @@ -365,11 +380,12 @@ fn kat_test() {
let tvs: Vec<MainTestVector> = 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;
}
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/kem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
11 changes: 11 additions & 0 deletions src/kem/dhkem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
);
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
19 changes: 19 additions & 0 deletions src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
}