Skip to content

Commit

Permalink
Added support for P-521 (#54)
Browse files Browse the repository at this point in the history
* Added support for P-521

* Update agility example with support for p384 and p521
  • Loading branch information
OtaK committed Nov 18, 2023
1 parent 362d7bc commit e9d2c26
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 13 deletions.
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")]
#[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
);
}
}

0 comments on commit e9d2c26

Please sign in to comment.