diff --git a/dhkem/src/expander.rs b/dhkem/src/expander.rs index 6e1481f..4cdfcb3 100644 --- a/dhkem/src/expander.rs +++ b/dhkem/src/expander.rs @@ -12,17 +12,17 @@ const PREFIXES_MAX: usize = 256; /// Maximum size of input key material or info after applying prefixes. const LABELED_INPUT_MAX: usize = PREFIXES_MAX + 64; -/// HPKE version identifier from `RFC9810 §4`. +/// HPKE version identifier from `RFC9180 §4`. const HPKE_VERSION_ID: &[u8] = b"HPKE-v1"; -/// HPKE suite ID from `RFC9810 §4`. +/// HPKE suite ID from `RFC9180 §4`. const HPKE_SUITE_ID: &[u8] = b"KEM\x00\x10"; /// Expander: wrapper for [RFC5869] HKDF-Expand operation which can be used for HPKE's -/// `LabeledExtract` and `LabeledExpand` as described in [RFC9810 §4]. +/// `LabeledExtract` and `LabeledExpand` as described in [RFC9180 §4]. /// /// [RFC5869]: https://datatracker.ietf.org/doc/html/rfc5869 -/// [RFC9810 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4 +/// [RFC9180 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4 #[derive(Debug)] pub struct Expander { /// Inner HKDF instance @@ -65,12 +65,12 @@ impl Expander { } /// Create a new expander which uses the prefixes that implement HPKE `LabeledExtract` as - /// described in [RFC9810 §4]. + /// described in [RFC9180 §4]. /// /// # Errors /// Returns [`InvalidLength`] if the concatenated prefixes are too long. /// - /// [RFC9810 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4 + /// [RFC9180 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4 pub fn new_labeled_hpke( salt: &[u8], label: &[u8], @@ -92,7 +92,7 @@ impl Expander { /// Returns [`InvalidLength`] if info is too long. /// /// [RFC5869]: https://datatracker.ietf.org/doc/html/rfc5869 - /// [RFC9810 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4 + /// [RFC9180 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4 pub fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> { self.hkdf.expand(info, okm) } @@ -115,12 +115,12 @@ impl Expander { } /// Create a new expander which uses the prefixes that implement HPKE `LabeledExpand` as - /// described in [RFC9810 §4]. + /// described in [RFC9180 §4]. /// /// # Errors /// Returns [`InvalidLength`] if label and/or info is too long. /// - /// [RFC9810 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4 + /// [RFC9180 §4]: https://datatracker.ietf.org/doc/html/rfc9180#section-4 pub fn expand_labeled_hpke( &self, label: &[u8], diff --git a/dhkem/tests/hpke_p256_test.rs b/dhkem/tests/hpke_p256_test.rs index 3ca0770..8f2f416 100644 --- a/dhkem/tests/hpke_p256_test.rs +++ b/dhkem/tests/hpke_p256_test.rs @@ -1,4 +1,6 @@ -//! HPKE tests (P-256 only) +//! HPKE tests (P-256) +// +//! Test vectors from RFC 9180 Appendix A. #![cfg(feature = "p256")] #![allow(clippy::unwrap_in_result, reason = "tests")] @@ -48,7 +50,6 @@ fn extract_and_expand(shared_secret: &[u8], kem_context: &[u8]) -> [u8; 32] { expander .expand_labeled_hpke(b"shared_secret", kem_context, &mut out) .unwrap(); - out } @@ -85,3 +86,309 @@ fn test_dhkem_p256_hkdf_sha256() { assert_eq!(&shared_secret, &example_shared_secret); } + +#[test] +fn test_a_3_1_base() { + // RFC 9180 A.3.1 (Base) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.3.1 + let skrm = hex!("f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2"); + let enc = hex!( + "04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325a\ + c98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18\ + c4" + ); + let pkrm = hex!( + "04fe8c19ce0905191ebc298a9245792531f26f0cece2460639e8bc39cb7f706a\ + 826a779b4cf969b8a0e539c7f62fb3d30ad6aa8f80e30f1d128aafd68a2ce72e\ + a0" + ); + let expected = hex!("c0d26aeab536609a572b07695d933b589dcf363ff9d93c93adea537aeabb8cb8"); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh1, &kem_context), expected); +} + +#[test] +fn test_a_3_2_psk() { + // RFC 9180 A.3.2 (Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.3.2 + let skrm = hex!("438d8bcef33b89e0e9ae5eb0957c353c25a94584b0dd59c991372a75b43cb661"); + let enc = hex!( + "04305d35563527bce037773d79a13deabed0e8e7cde61eecee403496959e89e4\ + d0ca701726696d1485137ccb5341b3c1c7aaee90a4a02449725e744b1193b53b\ + 5f" + ); + let pkrm = hex!( + "040d97419ae99f13007a93996648b2674e5260a8ebd2b822e84899cd52d87446\ + ea394ca76223b76639eccdf00e1967db10ade37db4e7db476261fcc8df97c5ff\ + d1" + ); + let expected = hex!("2e783ad86a1beae03b5749e0f3f5e9bb19cb7eb382f2fb2dd64c99f15ae0661b"); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh1, &kem_context), expected); +} + +#[test] +fn test_a_3_3_auth() { + // RFC 9180 A.3.3 (Auth) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.3.3 + let skrm = hex!("d929ab4be2e59f6954d6bedd93e638f02d4046cef21115b00cdda2acb2a4440e"); + let enc = hex!( + "042224f3ea800f7ec55c03f29fc9865f6ee27004f818fcbdc6dc68932c1e52e1\ + 5b79e264a98f2c535ef06745f3d308624414153b22c7332bc1e691cb4af4d534\ + 54" + ); + let pkrm = hex!( + "04423e363e1cd54ce7b7573110ac121399acbc9ed815fae03b72ffbd4c18b018\ + 36835c5a09513f28fc971b7266cfde2e96afe84bb0f266920e82c4f53b36e1a7\ + 8d" + ); + let expected = hex!("d4aea336439aadf68f9348880aa358086f1480e7c167b6ef15453ba69b94b44f"); + let pksm = hex!( + "04a817a0902bf28e036d66add5d544cc3a0457eab150f104285df1e293b5c10e\ + ef8651213e43d9cd9086c80b309df22cf37609f58c1127f7607e85f210b2804f\ + 73" + ); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let dh2 = dk.try_decapsulate(&pksm.into()).unwrap(); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_3_4_auth_psk() { + // RFC 9180 A.3.4 (Auth Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.3.4 + let skrm = hex!("bdf4e2e587afdf0930644a0c45053889ebcadeca662d7c755a353d5b4e2a8394"); + let enc = hex!( + "046a1de3fc26a3d43f4e4ba97dbe24f7e99181136129c48fbe872d4743e2b131\ + 357ed4f29a7b317dc22509c7b00991ae990bf65f8b236700c82ab7c11a845114\ + 01" + ); + let pkrm = hex!( + "04d824d7e897897c172ac8a9e862e4bd820133b8d090a9b188b8233a64dfbc5f\ + 725aa0aa52c8462ab7c9188f1c4872f0c99087a867e8a773a13df48a627058e1\ + b3" + ); + let expected = hex!("d4c27698391db126f1612d9e91a767f10b9b19aa17e1695549203f0df7d9aebe"); + let pksm = hex!( + "049f158c750e55d8d5ad13ede66cf6e79801634b7acadcad72044eac2ae1d048\ + 0069133d6488bf73863fa988c4ba8bde1c2e948b761274802b4d8012af4f13af\ + 9e" + ); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let dh2 = dk.try_decapsulate(&pksm.into()).unwrap(); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_4_1_base() { + // RFC 9180 A.4.1 (Base) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.4.1 + let skrm = hex!("3ac8530ad1b01885960fab38cf3cdc4f7aef121eaa239f222623614b4079fb38"); + let enc = hex!( + "0493ed86735bdfb978cc055c98b45695ad7ce61ce748f4dd63c525a3b8d53a15\ + 565c6897888070070c1579db1f86aaa56deb8297e64db7e8924e72866f9a4725\ + 80" + ); + let pkrm = hex!( + "04085aa5b665dc3826f9650ccbcc471be268c8ada866422f739e2d531d4a8818\ + a9466bc6b449357096232919ec4fe9070ccbac4aac30f4a1a53efcf7af90610e\ + dd" + ); + let expected = hex!("02f584736390fc93f5b4ad039826a3fa08e9911bd1215a3db8e8791ba533cafd"); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh1, &kem_context), expected); +} + +#[test] +fn test_a_4_2_psk() { + // RFC 9180 A.4.2 (Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.4.2 + let skrm = hex!("bc6f0b5e22429e5ff47d5969003f3cae0f4fec50e23602e880038364f33b8522"); + let enc = hex!( + "04a307934180ad5287f95525fe5bc6244285d7273c15e061f0f2efb211c35057\ + f3079f6e0abae200992610b25f48b63aacfcb669106ddee8aa023feed3019013\ + 71" + ); + let pkrm = hex!( + "043f5266fba0742db649e1043102b8a5afd114465156719cea90373229aabdd8\ + 4d7f45dabfc1f55664b888a7e86d594853a6cccdc9b189b57839cbbe3b90b558\ + 73" + ); + let expected = hex!("2912aacc6eaebd71ff715ea50f6ef3a6637856b2a4c58ea61e0c3fc159e3bc16"); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh1, &kem_context), expected); +} + +#[test] +fn test_a_4_3_auth() { + // RFC 9180 A.4.3 (Auth) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.4.3 + let skrm = hex!("1ea4484be482bf25fdb2ed39e6a02ed9156b3e57dfb18dff82e4a048de990236"); + let enc = hex!( + "04fec59fa9f76f5d0f6c1660bb179cb314ed97953c53a60ab38f8e6ace60fd59\ + 178084d0dd66e0f79172992d4ddb2e91172ce24949bcebfff158dcc417f2c6e9\ + c6" + ); + let pkrm = hex!( + "04378bad519aab406e04d0e5608bcca809c02d6afd2272d4dd03e9357bd0eee8\ + adf84c8deba3155c9cf9506d1d4c8bfefe3cf033a75716cc3cc07295100ec962\ + 76" + ); + let expected = hex!("1ed49f6d7ada333d171cd63861a1cb700a1ec4236755a9cd5f9f8f67a2f8e7b3"); + let pksm = hex!( + "0404d3c1f9fca22eb4a6d326125f0814c35593b1da8ea0d11a640730b215a259\ + b9b98a34ad17e21617d19fe1d4fa39a4828bfdb306b729ec51c543caca3b2d95\ + 29" + ); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let dh2 = dk.try_decapsulate(&pksm.into()).unwrap(); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_4_4_auth_psk() { + // RFC 9180 A.4.4 (Auth Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.4.4 + let skrm = hex!("00510a70fde67af487c093234fc4215c1cdec09579c4b30cc8e48cb530414d0e"); + let enc = hex!( + "04801740f4b1b35823f7fb2930eac2efc8c4893f34ba111c0bb976e3c7d5dc0a\ + ef5a7ef0bf4057949a140285f774f1efc53b3860936b92279a11b68395d898d1\ + 38" + ); + let pkrm = hex!( + "04a4ca7af2fc2cce48edbf2f1700983e927743a4e85bb5035ad562043e25d9a1\ + 11cbf6f7385fac55edc5c9d2ca6ed351a5643de95c36748e11dbec98730f4d43\ + e9" + ); + let expected = hex!("02bee8be0dda755846115db45071c0cf59c25722e015bde1c124de849c0fea52"); + let pksm = hex!( + "04b59a4157a9720eb749c95f842a5e3e8acdccbe834426d405509ac3191e23f2\ + 165b5bb1f07a6240dd567703ae75e13182ee0f69fc102145cdb5abf681ff126d\ + 60" + ); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let dh2 = dk.try_decapsulate(&pksm.into()).unwrap(); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_5_1_base() { + // RFC 9180 A.5.1 (Base) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.5.1 + let skrm = hex!("a4d1c55836aa30f9b3fbb6ac98d338c877c2867dd3a77396d13f68d3ab150d3b"); + let enc = hex!( + "04c07836a0206e04e31d8ae99bfd549380b072a1b1b82e563c935c095827824f\ + c1559eac6fb9e3c70cd3193968994e7fe9781aa103f5b50e934b5b2f387e3812\ + 91" + ); + let pkrm = hex!( + "04a697bffde9405c992883c5c439d6cc358170b51af72812333b015621dc0f40\ + bad9bb726f68a5c013806a790ec716ab8669f84f6b694596c2987cf35baba2a0\ + 06" + ); + let expected = hex!("806520f82ef0b03c823b7fc524b6b55a088f566b9751b89551c170f4113bd850"); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh1, &kem_context), expected); +} + +#[test] +fn test_a_5_2_psk() { + // RFC 9180 A.5.2 (Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.5.2 + let skrm = hex!("12ecde2c8bc2d5d7ed2219c71f27e3943d92b344174436af833337c557c300b3"); + let enc = hex!( + "04f336578b72ad7932fe867cc4d2d44a718a318037a0ec271163699cee653fa8\ + 05c1fec955e562663e0c2061bb96a87d78892bff0cc0bad7906c2d998ebe1a72\ + 46" + ); + let pkrm = hex!( + "041eb8f4f20ab72661af369ff3231a733672fa26f385ffb959fd1bae46bfda43\ + ad55e2d573b880831381d9367417f554ce5b2134fbba5235b44db465feffc618\ + 9e" + ); + let expected = hex!("ac4f260dce4db6bf45435d9c92c0e11cfdd93743bd3075949975974cc2b3d79e"); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh1, &kem_context), expected); +} + +#[test] +fn test_a_5_3_auth() { + // RFC 9180 A.5.3 (Auth) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.5.3 + let skrm = hex!("3cb2c125b8c5a81d165a333048f5dcae29a2ab2072625adad66dbb0f48689af9"); + let enc = hex!( + "040d5176aedba55bc41709261e9195c5146bb62d783031280775f32e507d79b5\ + cbc5748b6be6359760c73cfe10ca19521af704ca6d91ff32fc0739527b9385d4\ + 15" + ); + let pkrm = hex!( + "0444f6ee41818d9fe0f8265bffd016b7e2dd3964d610d0f7514244a60dbb7a11\ + ece876bb110a97a2ac6a9542d7344bf7d2bd59345e3e75e497f7416cf38d2962\ + 33" + ); + let expected = hex!("1a45aa4792f4b166bfee7eeab0096c1a6e497480e2261b2a59aad12f2768d469"); + let pksm = hex!( + "04265529a04d4f46ab6fa3af4943774a9f1127821656a75a35fade898a9a1b01\ + 4f64d874e88cddb24c1c3d79004d3a587db67670ca357ff4fba7e8b56ec013b9\ + 8b" + ); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let dh2 = dk.try_decapsulate(&pksm.into()).unwrap(); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_5_4_auth_psk() { + // RFC 9180 A.5.4 (Auth Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.5.4 + let skrm = hex!("c29fc577b7e74d525c0043f1c27540a1248e4f2c8d297298e99010a92e94865c"); + let enc = hex!( + "043539917ee26f8ae0aa5f784a387981b13de33124a3cde88b94672030183110\ + f331400115855808244ff0c5b6ca6104483ac95724481d41bdcd9f15b430ad16\ + f6" + ); + let pkrm = hex!( + "04d383fd920c42d018b9d57fd73a01f1eee480008923f67d35169478e55d2e88\ + 17068daf62a06b10e0aad4a9e429fa7f904481be96b79a9c231a33e956c20b81\ + b6" + ); + let expected = hex!("87584311791036a3019bc36803cdd42e9a8931a98b13c88835f2f8a9036a4fd6"); + let pksm = hex!( + "0492cf8c9b144b742fe5a63d9a181a19d416f3ec8705f24308ad316564823c34\ + 4e018bd7c03a33c926bb271b28ef5bf28c0ca00abff249fee5ef7f33315ff34f\ + db" + ); + + let dk = NistP256DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let dh2 = dk.try_decapsulate(&pksm.into()).unwrap(); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} diff --git a/dhkem/tests/hpke_p521_test.rs b/dhkem/tests/hpke_p521_test.rs new file mode 100644 index 0000000..b6542ad --- /dev/null +++ b/dhkem/tests/hpke_p521_test.rs @@ -0,0 +1,172 @@ +//! HPKE tests (P-521) +// +//! Test vectors from RFC 9180 Appendix A. + +#![cfg(feature = "p521")] +#![allow(clippy::unwrap_in_result, reason = "tests")] +#![allow(clippy::unwrap_used, reason = "tests")] + +use hex_literal::hex; +use kem::{TryDecapsulate, TryKeyInit}; +use sha2::Sha512; + +type Expander = dhkem::Expander; + +fn extract_and_expand(shared_secret: &[u8], kem_context: &[u8]) -> [u8; 64] { + let mut out = [0u8; 64]; + let expander = Expander::new_labeled_hpke(b"", b"eae_prk", shared_secret).unwrap(); + expander + .expand_labeled_hpke(b"shared_secret", kem_context, &mut out) + .unwrap(); + out +} + +#[test] +fn test_a_6_1_base() { + // RFC 9180 A.6.1 (Base) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.6.1 + let skrm = hex!( + "01462680369ae375e4b3791070a7458ed527842f6a98a79ff5e0d4cbde83c271\ + 96a3916956655523a6a2556a7af62c5cadabe2ef9da3760bb21e005202f7b246\ + 2847" + ); + let enc = hex!( + "040138b385ca16bb0d5fa0c0665fbbd7e69e3ee29f63991d3e9b5fa740aab890\ + 0aaeed46ed73a49055758425a0ce36507c54b29cc5b85a5cee6bae0cf1c21f27\ + 31ece2013dc3fb7c8d21654bb161b463962ca19e8c654ff24c94dd2898de1205\ + 1f1ed0692237fb02b2f8d1dc1c73e9b366b529eb436e98a996ee522aef863dd5\ + 739d2f29b0" + ); + let pkrm = hex!( + "0401b45498c1714e2dce167d3caf162e45e0642afc7ed435df7902ccae0e84ba\ + 0f7d373f646b7738bbbdca11ed91bdeae3cdcba3301f2457be452f271fa68375\ + 80e661012af49583a62e48d44bed350c7118c0d8dc861c238c72a2bda17f6470\ + 4f464b57338e7f40b60959480c0e58e6559b190d81663ed816e523b6b6a418f6\ + 6d2451ec64" + ); + let expected = hex!( + "776ab421302f6eff7d7cb5cb1adaea0cd50872c71c2d63c30c4f1d5e43653336\ + fef33b103c67e7a98add2d3b66e2fda95b5b2a667aa9dac7e59cc1d46d30e818" + ); + + let dk = dhkem::NistP521DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh1, &kem_context), expected); +} + +#[test] +fn test_a_6_2_psk() { + // RFC 9180 A.6.2 (Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.6.2 + let skrm = hex!( + "011bafd9c7a52e3e71afbdab0d2f31b03d998a0dc875dd7555c63560e142bde2\ + 64428de03379863b4ec6138f813fa009927dc5d15f62314c56d4e7ff2b485753\ + eb72" + ); + let enc = hex!( + "040085eff0835cc84351f32471d32aa453cdc1f6418eaaecf1c2824210eb1d48\ + d0768b368110fab21407c324b8bb4bec63f042cfa4d0868d19b760eb4beba1bf\ + f793b30036d2c614d55730bd2a40c718f9466faf4d5f8170d22b6df98dfe0c06\ + 7d02b349ae4a142e0c03418f0a1479ff78a3db07ae2c2e89e5840f712c174ba2\ + 118e90fdcb" + ); + let pkrm = hex!( + "04006917e049a2be7e1482759fb067ddb94e9c4f7f5976f655088dec45246614\ + ff924ed3b385fc2986c0ecc39d14f907bf837d7306aada59dd5889086125ecd0\ + 38ead400603394b5d81f89ebfd556a898cc1d6a027e143d199d3db845cb91c52\ + 89fb26c5ff80832935b0e8dd08d37c6185a6f77683347e472d1edb6daa6bd765\ + 2fea628fae" + ); + let expected = hex!( + "0d52de997fdaa4797720e8b1bebd3df3d03c4cf38cc8c1398168d36c3fc76264\ + 28c9c254dd3f9274450909c64a5b3acbe45e2d850a2fd69ac0605fe5c8a057a5" + ); + + let dk = dhkem::NistP521DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh1, &kem_context), expected); +} + +#[test] +fn test_a_6_3_auth() { + // RFC 9180 A.6.3 (Auth) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.6.3 + let skrm = hex!( + "013ef326940998544a899e15e1726548ff43bbdb23a8587aa3bef9d1b857338d\ + 87287df5667037b519d6a14661e9503cfc95a154d93566d8c84e95ce93ad0529\ + 3a0b" + ); + let enc = hex!( + "04017de12ede7f72cb101dab36a111265c97b3654816dcd6183f809d4b3d111f\ + e759497f8aefdc5dbb40d3e6d21db15bdc60f15f2a420761bcaeef73b891c2b1\ + 17e9cf01e29320b799bbc86afdc5ea97d941ea1c5bd5ebeeac7a784b3bab5247\ + 46f3e640ec26ee1bd91255f9330d974f845084637ee0e6fe9f505c5b87c86a4e\ + 1a6c3096dd" + ); + let pkrm = hex!( + "04007d419b8834e7513d0e7cc66424a136ec5e11395ab353da324e3586673ee7\ + 3d53ab34f30a0b42a92d054d0db321b80f6217e655e304f72793767c4231785c\ + 4a4a6e008f31b93b7a4f2b8cd12e5fe5a0523dc71353c66cbdad51c86b9e0bdf\ + cd9a45698f2dab1809ab1b0f88f54227232c858accc44d9a8d41775ac0263415\ + 64a2d749f4" + ); + let expected = hex!( + "26648fa2a2deb0bfc56349a590fd4cb7108a51797b634694fc02061e8d91b357\ + 6ac736a68bf848fe2a58dfb1956d266e68209a4d631e513badf8f4dcfc00f30a" + ); + let pksm = hex!( + "04015cc3636632ea9a3879e43240beae5d15a44fba819282fac26a19c989fafd\ + d0f330b8521dff7dc393101b018c1e65b07be9f5fc9a28a1f450d6a541ee0d76\ + 221133001e8f0f6a05ab79f9b9bb9ccce142a453d59c5abebb5674839d935a3c\ + a1a3fbc328539a60b3bc3c05fed22838584a726b9c176796cad0169ba4093332\ + cbd2dc3a9f" + ); + + let dk = dhkem::NistP521DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let dh2 = dk.try_decapsulate(&pksm.into()).unwrap(); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_6_4_auth_psk() { + // RFC 9180 A.6.4 (Auth Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.6.4 + let skrm = hex!( + "0053c0bc8c1db4e9e5c3e3158bfdd7fc716aef12db13c8515adf821dd692ba3c\ + a53041029128ee19c8556e345c4bcb840bb7fd789f97fe10f17f0e2c6c252807\ + 2843" + ); + let enc = hex!( + "04000a5096a6e6e002c83517b494bfc2e36bfb8632fae8068362852b70d0ff71\ + e560b15aff96741ecffb63d8ac3090c3769679009ac59a99a1feb4713c5f090f\ + c0dbed01ad73c45d29d369e36744e9ed37d12f80700c16d816485655169a5dd6\ + 6e4ddf27f2acffe0f56f7f77ea2b473b4bf0518b975d9527009a3d14e5a4957e\ + 3e8a9074f8" + ); + let pkrm = hex!( + "0401655b5d3b7cfafaba30851d25edc44c6dd17d99410efbed8591303b4dbeea\ + 8cb1045d5255f9a60384c3bbd4a3386ae6e6fab341dc1f8db0eed5f0ab1aaac6\ + d7838e00dadf8a1c2c64b48f89c633721e88369e54104b31368f26e35d04a442\ + b0b428510fb23caada686add16492f333b0f7ba74c391d779b788df2c38d7a7f\ + 4778009d91" + ); + let expected = hex!( + "9e1d5f62cb38229f57f68948a0fbc1264499910cce50ec62cb24188c5b0a9886\ + 8f3c1cfa8c5baa97b3f24db3cdd30df6e04eae83dc4347be8a981066c3b5b945" + ); + let pksm = hex!( + "040013761e97007293d57de70962876b4926f69a52680b4714bee1d4236aa96c\ + 19b840c57e80b14e91258f0a350e3f7ba59f3f091633aede4c7ec4fa8918323a\ + a45d5901076dec8eeb22899fda9ab9e1960003ff0535f53c02c40f2ae4cdc607\ + 0a3870b85b4bdd0bb77f1f889e7ee51f465a308f08c666ad3407f75dc046b2ff\ + 5a24dbe2ed" + ); + + let dk = dhkem::NistP521DecapsulationKey::new(&skrm.into()).unwrap(); + let dh1 = dk.try_decapsulate(&enc.into()).unwrap(); + let dh2 = dk.try_decapsulate(&pksm.into()).unwrap(); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} diff --git a/dhkem/tests/hpke_x25519_test.rs b/dhkem/tests/hpke_x25519_test.rs new file mode 100644 index 0000000..a1de646 --- /dev/null +++ b/dhkem/tests/hpke_x25519_test.rs @@ -0,0 +1,208 @@ +//! HPKE tests (X25519) +// +//! Test vectors from RFC 9180 Appendix A. + +#![cfg(feature = "x25519")] +#![allow(clippy::unwrap_in_result, reason = "tests")] +#![allow(clippy::unwrap_used, reason = "tests")] + +use hex_literal::hex; +use kem::{Decapsulate, KeyInit}; +use sha2::Sha256; + +type Expander = dhkem::Expander; + +fn extract_and_expand(shared_secret: &[u8], kem_context: &[u8]) -> [u8; 32] { + let mut out = [0u8; 32]; + let expander = Expander::new_labeled_hpke(b"", b"eae_prk", shared_secret).unwrap(); + expander + .expand_labeled_hpke(b"shared_secret", kem_context, &mut out) + .unwrap(); + out +} + +#[test] +fn test_a_1_1_base() { + // RFC 9180 A.1.1 (Base) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.1.1 + let skrm = hex!("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8"); + let pkrm = hex!("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d"); + let enc = hex!("37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431"); + let expected = hex!("fe0e18c9f024ce43799ae393c7e8fe8fce9d218875e8227b0187c04e7d2ea1fc"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh = dk.decapsulate(&enc.into()); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_1_2_psk() { + // RFC 9180 A.1.2 (Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.1.2 + let skrm = hex!("c5eb01eb457fe6c6f57577c5413b931550a162c71a03ac8d196babbd4e5ce0fd"); + let pkrm = hex!("9fed7e8c17387560e92cc6462a68049657246a09bfa8ade7aefe589672016366"); + let enc = hex!("0ad0950d9fb9588e59690b74f1237ecdf1d775cd60be2eca57af5a4b0471c91b"); + let expected = hex!("727699f009ffe3c076315019c69648366b69171439bd7dd0807743bde76986cd"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh = dk.decapsulate(&enc.into()); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_1_3_auth() { + // RFC 9180 A.1.3 (Auth) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.1.3 + let skrm = hex!("fdea67cf831f1ca98d8e27b1f6abeb5b7745e9d35348b80fa407ff6958f9137e"); + let pkrm = hex!("1632d5c2f71c2b38d0a8fcc359355200caa8b1ffdf28618080466c909cb69b2e"); + let enc = hex!("23fb952571a14a25e3d678140cd0e5eb47a0961bb18afcf85896e5453c312e76"); + let expected = hex!("2d6db4cf719dc7293fcbf3fa64690708e44e2bebc81f84608677958c0d4448a7"); + let pksm = hex!("8b0c70873dc5aecb7f9ee4e62406a397b350e57012be45cf53b7105ae731790b"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh1 = dk.decapsulate(&enc.into()); + let dh2 = dk.decapsulate(&pksm.into()); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_1_4_auth_psk() { + // RFC 9180 A.1.4 (Auth Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.1.4 + let skrm = hex!("cb29a95649dc5656c2d054c1aa0d3df0493155e9d5da6d7e344ed8b6a64a9423"); + let pkrm = hex!("1d11a3cd247ae48e901939659bd4d79b6b959e1f3e7d66663fbc9412dd4e0976"); + let enc = hex!("820818d3c23993492cc5623ab437a48a0a7ca3e9639c140fe1e33811eb844b7c"); + let expected = hex!("f9d0e870aba28d04709b2680cb8185466c6a6ff1d6e9d1091d5bf5e10ce3a577"); + let pksm = hex!("2bfb2eb18fcad1af0e4f99142a1c474ae74e21b9425fc5c589382c69b50cc57e"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh1 = dk.decapsulate(&enc.into()); + let dh2 = dk.decapsulate(&pksm.into()); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_2_1_base() { + // RFC 9180 A.2.1 (Base) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.2.1 + let skrm = hex!("8057991eef8f1f1af18f4a9491d16a1ce333f695d4db8e38da75975c4478e0fb"); + let pkrm = hex!("4310ee97d88cc1f088a5576c77ab0cf5c3ac797f3d95139c6c84b5429c59662a"); + let enc = hex!("1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a"); + let expected = hex!("0bbe78490412b4bbea4812666f7916932b828bba79942424abb65244930d69a7"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh = dk.decapsulate(&enc.into()); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_2_2_psk() { + // RFC 9180 A.2.2 (Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.2.2 + let skrm = hex!("77d114e0212be51cb1d76fa99dd41cfd4d0166b08caa09074430a6c59ef17879"); + let pkrm = hex!("13640af826b722fc04feaa4de2f28fbd5ecc03623b317834e7ff4120dbe73062"); + let enc = hex!("2261299c3f40a9afc133b969a97f05e95be2c514e54f3de26cbe5644ac735b04"); + let expected = hex!("4be079c5e77779d0215b3f689595d59e3e9b0455d55662d1f3666ec606e50ea7"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh = dk.decapsulate(&enc.into()); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_2_3_auth() { + // RFC 9180 A.2.3 (Auth) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.2.3 + let skrm = hex!("3ca22a6d1cda1bb9480949ec5329d3bf0b080ca4c45879c95eddb55c70b80b82"); + let pkrm = hex!("1a478716d63cb2e16786ee93004486dc151e988b34b475043d3e0175bdb01c44"); + let enc = hex!("f7674cc8cd7baa5872d1f33dbaffe3314239f6197ddf5ded1746760bfc847e0e"); + let expected = hex!("d2d67828c8bc9fa661cf15a31b3ebf1febe0cafef7abfaaca580aaf6d471e3eb"); + let pksm = hex!("f0f4f9e96c54aeed3f323de8534fffd7e0577e4ce269896716bcb95643c8712b"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh1 = dk.decapsulate(&enc.into()); + let dh2 = dk.decapsulate(&pksm.into()); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_2_4_auth_psk() { + // RFC 9180 A.2.4 (Auth Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.2.4 + let skrm = hex!("7b36a42822e75bf3362dfabbe474b3016236408becb83b859a6909e22803cb0c"); + let pkrm = hex!("a5099431c35c491ec62ca91df1525d6349cb8aa170c51f9581f8627be6334851"); + let enc = hex!("656a2e00dc9990fd189e6e473459392df556e9a2758754a09db3f51179a3fc02"); + let expected = hex!("86a6c0ed17714f11d2951747e660857a5fd7616c933ef03207808b7a7123fe67"); + let pksm = hex!("3ac5bd4dd66ff9f2740bef0d6ccb66daa77bff7849d7895182b07fb74d087c45"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh1 = dk.decapsulate(&enc.into()); + let dh2 = dk.decapsulate(&pksm.into()); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_7_1_base() { + // RFC 9180 A.7.1 (Base) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.7.1 + let skrm = hex!("33d196c830a12f9ac65d6e565a590d80f04ee9b19c83c87f2c170d972a812848"); + let pkrm = hex!("194141ca6c3c3beb4792cd97ba0ea1faff09d98435012345766ee33aae2d7664"); + let enc = hex!("e5e8f9bfff6c2f29791fc351d2c25ce1299aa5eaca78a757c0b4fb4bcd830918"); + let expected = hex!("e81716ce8f73141d4f25ee9098efc968c91e5b8ce52ffff59d64039e82918b66"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh = dk.decapsulate(&enc.into()); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_7_2_psk() { + // RFC 9180 A.7.2 (Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.7.2 + let skrm = hex!("98f304d4ecb312689690b113973c61ffe0aa7c13f2fbe365e48f3ed09e5a6a0c"); + let pkrm = hex!("d53af36ea5f58f8868bb4a1333ed4cc47e7a63b0040eb54c77b9c8ec456da824"); + let enc = hex!("d3805a97cbcd5f08babd21221d3e6b362a700572d14f9bbeb94ec078d051ae3d"); + let expected = hex!("024573db58c887decb4c57b6ed39f2c9a09c85600a8a0ecb11cac24c6aaec195"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh = dk.decapsulate(&enc.into()); + let kem_context = [enc.as_slice(), pkrm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_7_3_auth() { + // RFC 9180 A.7.3 (Auth) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.7.3 + let skrm = hex!("ed88cda0e91ca5da64b6ad7fc34a10f096fa92f0b9ceff9d2c55124304ed8b4a"); + let pkrm = hex!("ffd7ac24694cb17939d95feb7c4c6539bb31621deb9b96d715a64abdd9d14b10"); + let enc = hex!("5ac1671a55c5c3875a8afe74664aa8bc68830be9ded0c5f633cd96400e8b5c05"); + let expected = hex!("e204156fd17fd65b132d53a0558cd67b7c0d7095ee494b00f47d686eb78f8fb3"); + let pksm = hex!("89eb1feae431159a5250c5186f72a15962c8d0debd20a8389d8b6e4996e14306"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh1 = dk.decapsulate(&enc.into()); + let dh2 = dk.decapsulate(&pksm.into()); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +} + +#[test] +fn test_a_7_4_auth_psk() { + // RFC 9180 A.7.4 (Auth Psk) https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.7.4 + let skrm = hex!("c4962a7f97d773a47bdf40db4b01dc6a56797c9e0deaab45f4ea3aa9b1d72904"); + let pkrm = hex!("f47cd9d6993d2e2234eb122b425accfb486ee80f89607b087094e9f413253c2d"); + let enc = hex!("81cbf4bd7eee97dd0b600252a1c964ea186846252abb340be47087cc78f3d87c"); + let expected = hex!("d69246bcd767e579b1eec80956d7e7dfbd2902dad920556f0de69bd54054a2d1"); + let pksm = hex!("29a5bf3867a6128bbdf8e070abe7fe70ca5e07b629eba5819af73810ee20112f"); + + let dk = dhkem::X25519DecapsulationKey::new(&skrm.into()); + let dh1 = dk.decapsulate(&enc.into()); + let dh2 = dk.decapsulate(&pksm.into()); + let dh = [&dh1[..], &dh2[..]].concat(); + let kem_context = [enc.as_slice(), pkrm.as_slice(), pksm.as_slice()].concat(); + assert_eq!(extract_and_expand(&dh, &kem_context), expected); +}