hpke_core/lib.rs
1#![doc = include_str!("../README.md")]
2#![no_std]
3#![forbid(unsafe_code, unused_must_use, unstable_features)]
4#![deny(
5 trivial_casts,
6 trivial_numeric_casts,
7 missing_docs,
8 unused_import_braces,
9 unused_extern_crates,
10 unused_qualifications
11)]
12#![allow(clippy::must_use_candidate)]
13
14pub mod error;
15pub mod kem;
16
17extern crate alloc;
18#[cfg(feature = "std")]
19extern crate std;
20
21use alloc::vec::Vec;
22use core::fmt;
23use core::marker::PhantomData;
24
25pub use hpke_crypto::*;
26
27pub use crate::error::Error;
28
29/// The HPKE configuration.
30#[derive(Debug, Clone, Copy)]
31pub struct Hpke<C> {
32 /// The HPKE ciphersuite in use.
33 cipher_suite: HpkeCipherSuite,
34
35 /// The crypto backend.
36 _crypto_backend: PhantomData<C>,
37}
38
39impl<C: Crypto> Hpke<C> {
40 /// Create a new HPKE configuration with the given ciphersuite.
41 pub fn prepare(cipher_suite: HpkeCipherSuite) -> Self {
42 Self {
43 cipher_suite,
44 _crypto_backend: PhantomData,
45 }
46 }
47
48 #[allow(clippy::too_many_arguments)]
49 /// 5.1. Creating the Encryption Context
50 ///
51 /// This is a convenience function that wraps all four setup functions.
52 ///
53 /// See [RFC 9180, Section 5.1] for details.
54 ///
55 /// # Errors
56 ///
57 /// Various errors may occur during the key encapsulation or decapsulation
58 /// process, or if the provided PSK does not meet security requirements.
59 ///
60 /// [RFC 9180, Section 5.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1
61 pub fn setup_s(
62 &self,
63 crypto_backend: C,
64 mode: HpkeMode,
65 pk_r: HpkePublicKeyRef<'_>,
66 info: &[u8],
67 psk: Option<&[u8]>,
68 psk_id: Option<&[u8]>,
69 sk_s: Option<HpkePrivateKeyRef<'_>>,
70 ) -> Result<(EncapsulatedSecret, Context<C, Sender>), Error> {
71 match mode {
72 HpkeMode::Base => self.setup_base_s(crypto_backend, pk_r, info),
73 HpkeMode::Psk => self.setup_psk_s(
74 crypto_backend,
75 pk_r,
76 info,
77 psk.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk"))?,
78 psk_id.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk_id"))?,
79 ),
80 HpkeMode::Auth => self.setup_auth_s(
81 crypto_backend,
82 pk_r,
83 info,
84 sk_s.ok_or_else(|| Error::InvalidInput("For Auth mode, must provide sk_s"))?,
85 ),
86 HpkeMode::AuthPsk => self.setup_auth_psk_s(
87 crypto_backend,
88 pk_r,
89 info,
90 psk.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk"))?,
91 psk_id.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk_id"))?,
92 sk_s.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide sk_s"))?,
93 ),
94 }
95 }
96
97 #[allow(clippy::too_many_arguments)]
98 /// 5.1. Creating the Encryption Context
99 ///
100 /// This is a convenience function that wraps all four setup functions.
101 ///
102 /// See [RFC 9180, Section 5.1] for details.
103 ///
104 /// # Errors
105 ///
106 /// Various errors may occur during the key encapsulation or decapsulation
107 /// process, or if the provided PSK does not meet security requirements.
108 ///
109 /// [RFC 9180, Section 5.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1
110 pub fn setup_r(
111 &self,
112 crypto_backend: C,
113 mode: HpkeMode,
114 enc: EncapsulatedSecretRef<'_>,
115 sk_r: HpkePrivateKeyRef<'_>,
116 info: &[u8],
117 psk: Option<&[u8]>,
118 psk_id: Option<&[u8]>,
119 pk_s: Option<HpkePublicKeyRef<'_>>,
120 ) -> Result<Context<C, Recipient>, Error> {
121 match mode {
122 HpkeMode::Base => self.setup_base_r(crypto_backend, enc, sk_r, info),
123 HpkeMode::Psk => self.setup_psk_r(
124 crypto_backend,
125 enc,
126 sk_r,
127 info,
128 psk.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk"))?,
129 psk_id.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk_id"))?,
130 ),
131 HpkeMode::Auth => self.setup_auth_r(
132 crypto_backend,
133 enc,
134 sk_r,
135 info,
136 pk_s.ok_or_else(|| Error::InvalidInput("For Auth mode, must provide pk_s"))?,
137 ),
138 HpkeMode::AuthPsk => self.setup_auth_psk_r(
139 crypto_backend,
140 enc,
141 sk_r,
142 info,
143 psk.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk"))?,
144 psk_id.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk_id"))?,
145 pk_s.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide pk_s"))?,
146 ),
147 }
148 }
149
150 /// 5.1.1. Encryption to a Public Key
151 ///
152 /// The most basic function of an HPKE scheme is to enable encryption to the
153 /// holder of a given KEM private key. The `SetupBaseS()` and `SetupBaseR()`
154 /// procedures establish contexts that can be used to encrypt and decrypt,
155 /// respectively, for a given private key.
156 ///
157 /// The KEM shared secret is combined via the KDF with information
158 /// describing the key exchange, as well as the explicit `info` parameter
159 /// provided by the caller.
160 ///
161 /// The parameter `pkR` is a public key, and `enc` is an encapsulated KEM
162 /// shared secret.
163 ///
164 /// ```no_run
165 /// def SetupBaseS(pkR, info):
166 /// shared_secret, enc = Encap(pkR)
167 /// return enc, KeyScheduleS(mode_base, shared_secret, info,
168 /// default_psk, default_psk_id)
169 /// ```
170 ///
171 /// See [RFC 9180, Section 5.1.1] for details.
172 ///
173 /// # Errors
174 ///
175 /// Various errors may occur during the key encapsulation or decapsulation
176 /// process.
177 ///
178 /// [RFC 9180, Section 5.1.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.1
179 pub fn setup_base_s(
180 &self,
181 mut crypto_backend: C,
182 pk_r: HpkePublicKeyRef<'_>,
183 info: &[u8],
184 ) -> Result<(EncapsulatedSecret, Context<C, Sender>), Error> {
185 let (shared_secret, enc) = kem::encap(&mut crypto_backend, self.cipher_suite.kem_id, pk_r)?;
186
187 let context = self.key_schedule(
188 crypto_backend,
189 HpkeMode::Base,
190 &shared_secret,
191 info,
192 &[],
193 &[],
194 )?;
195
196 Ok((enc, context))
197 }
198
199 /// 5.1.1. Encryption to a Public Key
200 ///
201 /// The most basic function of an HPKE scheme is to enable encryption to the
202 /// holder of a given KEM private key. The `SetupBaseS()` and `SetupBaseR()`
203 /// procedures establish contexts that can be used to encrypt and decrypt,
204 /// respectively, for a given private key.
205 ///
206 /// The KEM shared secret is combined via the KDF with information
207 /// describing the key exchange, as well as the explicit `info` parameter
208 /// provided by the caller.
209 ///
210 /// The parameter `pkR` is a public key, and `enc` is an encapsulated KEM
211 /// shared secret.
212 ///
213 /// ```no_run
214 /// def SetupBaseR(enc, skR, info):
215 /// shared_secret = Decap(enc, skR)
216 /// return KeyScheduleR(mode_base, shared_secret, info,
217 /// default_psk, default_psk_id)
218 /// ```
219 ///
220 /// See [RFC 9180, Section 5.1.1] for details.
221 ///
222 /// # Errors
223 ///
224 /// Various errors may occur during the key encapsulation or decapsulation
225 /// process.
226 ///
227 /// [RFC 9180, Section 5.1.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.1
228 pub fn setup_base_r(
229 &self,
230 mut crypto_backend: C,
231 enc: EncapsulatedSecretRef<'_>,
232 sk_r: HpkePrivateKeyRef<'_>,
233 info: &[u8],
234 ) -> Result<Context<C, Recipient>, Error> {
235 let shared_secret = kem::decap(&mut crypto_backend, self.cipher_suite.kem_id, enc, sk_r)?;
236
237 self.key_schedule(
238 crypto_backend,
239 HpkeMode::Base,
240 shared_secret.as_ref(),
241 info,
242 &[],
243 &[],
244 )
245 }
246
247 /// 5.1.2. Authentication Using a Pre-Shared Key
248 ///
249 /// This variant extends the base mechanism by allowing the recipient to
250 /// authenticate that the sender possessed a given PSK. The PSK also
251 /// improves confidentiality guarantees in certain adversary models, as
252 /// described in more detail in [RFC 9180, Section 9.1]. We assume that both
253 /// parties have been provisioned with both the PSK value `psk` and
254 /// another byte string `psk_id` that is used to identify which PSK
255 /// should be used.
256 ///
257 /// The primary difference from the base case is that the `psk` and `psk_id`
258 /// values are used as `ikm` inputs to the KDF (instead of using the empty
259 /// string).
260 ///
261 /// The PSK MUST have at least 32 bytes of entropy and SHOULD be of length
262 /// `Nh` bytes or longer. See [RFC 9180, Section 9.5] for a more detailed
263 /// discussion.
264 ///
265 /// ```no_run
266 /// def SetupPSKS(pkR, info, psk, psk_id):
267 /// shared_secret, enc = Encap(pkR)
268 /// return enc, KeyScheduleS(mode_psk, shared_secret, info, psk, psk_id)
269 /// ```
270 ///
271 /// See [RFC 9180, Section 5.1.2] for details.
272 ///
273 /// # Errors
274 ///
275 /// Various errors may occur during the key encapsulation or decapsulation
276 /// process, or if the provided PSK does not meet security requirements.
277 ///
278 /// [RFC 9180, Section 5.1.2]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.2
279 /// [RFC 9180, Section 9.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.1
280 /// [RFC 9180, Section 9.5]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.5
281 pub fn setup_psk_s(
282 &self,
283 mut crypto_backend: C,
284 pk_r: HpkePublicKeyRef<'_>,
285 info: &[u8],
286 psk: &[u8],
287 psk_id: &[u8],
288 ) -> Result<(EncapsulatedSecret, Context<C, Sender>), Error> {
289 let (shared_secret, enc) = kem::encap(&mut crypto_backend, self.cipher_suite.kem_id, pk_r)?;
290
291 let context = self.key_schedule(
292 crypto_backend,
293 HpkeMode::Psk,
294 shared_secret.as_ref(),
295 info,
296 psk,
297 psk_id,
298 )?;
299
300 Ok((enc, context))
301 }
302
303 /// 5.1.2. Authentication Using a Pre-Shared Key
304 ///
305 /// This variant extends the base mechanism by allowing the recipient to
306 /// authenticate that the sender possessed a given PSK. The PSK also
307 /// improves confidentiality guarantees in certain adversary models, as
308 /// described in more detail in [RFC 9180, Section 9.1]. We assume that both
309 /// parties have been provisioned with both the PSK value `psk` and
310 /// another byte string `psk_id` that is used to identify which PSK
311 /// should be used.
312 ///
313 /// The primary difference from the base case is that the `psk` and `psk_id`
314 /// values are used as `ikm` inputs to the KDF (instead of using the empty
315 /// string).
316 ///
317 /// The PSK MUST have at least 32 bytes of entropy and SHOULD be of length
318 /// `Nh` bytes or longer. See [RFC 9180, Section 9.5] for a more detailed
319 /// discussion.
320 ///
321 /// ```no_run
322 /// def SetupPSKR(enc, skR, info, psk, psk_id):
323 /// shared_secret = Decap(enc, skR)
324 /// return KeyScheduleR(mode_psk, shared_secret, info, psk, psk_id)
325 /// ```
326 ///
327 /// See [RFC 9180, Section 5.1.2] for details.
328 ///
329 /// # Errors
330 ///
331 /// Various errors may occur during the key encapsulation or decapsulation
332 /// process, or if the provided PSK does not meet security requirements.
333 ///
334 /// [RFC 9180, Section 5.1.2]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.2
335 /// [RFC 9180, Section 9.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.1
336 /// [RFC 9180, Section 9.5]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.5
337 pub fn setup_psk_r(
338 &self,
339 mut crypto_backend: C,
340 enc: EncapsulatedSecretRef<'_>,
341 sk_r: HpkePrivateKeyRef<'_>,
342 info: &[u8],
343 psk: &[u8],
344 psk_id: &[u8],
345 ) -> Result<Context<C, Recipient>, Error> {
346 let shared_secret = kem::decap(&mut crypto_backend, self.cipher_suite.kem_id, enc, sk_r)?;
347
348 self.key_schedule(
349 crypto_backend,
350 HpkeMode::Psk,
351 shared_secret.as_ref(),
352 info,
353 psk,
354 psk_id,
355 )
356 }
357
358 /// 5.1.3. Authentication Using an Asymmetric Key
359 ///
360 /// This variant extends the base mechanism by allowing the recipient to
361 /// authenticate that the sender possessed a given KEM private key. This is
362 /// because `AuthDecap(enc, skR, pkS)` produces the correct KEM shared
363 /// secret only if the encapsulated value `enc` was produced by
364 /// `AuthEncap(pkR, skS)`, where `skS` is the private key corresponding
365 /// to `pkS`. In other words, at most two entities (precisely two, in
366 /// the case of DHKEM) could have produced this secret, so if the
367 /// recipient is at most one, then the sender is the other with
368 /// overwhelming probability.
369 ///
370 /// The primary difference from the base case is that the calls to `Encap()`
371 /// and `Decap()` are replaced with calls to `AuthEncap()` and
372 /// `AuthDecap()`, which add the sender public key to their internal
373 /// context string. The function parameters `pkR` and `pkS` are public
374 /// keys, and `enc` is an encapsulated KEM shared secret.
375 ///
376 /// Obviously, this variant can only be used with a KEM that provides
377 /// `AuthEncap()` and `AuthDecap()` procedures.
378 ///
379 /// This mechanism authenticates only the key pair of the sender, not any
380 /// other identifier. If an application wishes to bind HPKE ciphertexts or
381 /// exported secrets to another identity for the sender (e.g., an email
382 /// address or domain name), then this identifier should be included in the
383 /// info parameter to avoid identity misbinding issues.
384 ///
385 /// ```no_run
386 /// def SetupAuthS(pkR, info, skS):
387 /// shared_secret, enc = AuthEncap(pkR, skS)
388 /// return enc, KeyScheduleS(mode_auth, shared_secret, info,
389 /// default_psk, default_psk_id)
390 /// ```
391 ///
392 /// See [RFC 9180, Section 5.1.3] for details.
393 ///
394 /// # Errors
395 ///
396 /// Various errors may occur during the key encapsulation or decapsulation
397 /// process.
398 ///
399 /// [RFC 9180, Section 5.1.3]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.3
400 pub fn setup_auth_s(
401 &self,
402 mut crypto_backend: C,
403 pk_r: HpkePublicKeyRef<'_>,
404 info: &[u8],
405 sk_s: HpkePrivateKeyRef<'_>,
406 ) -> Result<(EncapsulatedSecret, Context<C, Sender>), Error> {
407 let (shared_secret, enc) =
408 kem::auth_encap(&mut crypto_backend, self.cipher_suite.kem_id, pk_r, sk_s)?;
409
410 let context = self.key_schedule(
411 crypto_backend,
412 HpkeMode::Auth,
413 shared_secret.as_ref(),
414 info,
415 &[],
416 &[],
417 )?;
418
419 Ok((enc, context))
420 }
421
422 /// 5.1.3. Authentication Using an Asymmetric Key
423 ///
424 /// This variant extends the base mechanism by allowing the recipient to
425 /// authenticate that the sender possessed a given KEM private key. This is
426 /// because `AuthDecap(enc, skR, pkS)` produces the correct KEM shared
427 /// secret only if the encapsulated value `enc` was produced by
428 /// `AuthEncap(pkR, skS)`, where `skS` is the private key corresponding
429 /// to `pkS`. In other words, at most two entities (precisely two, in
430 /// the case of DHKEM) could have produced this secret, so if the
431 /// recipient is at most one, then the sender is the other with
432 /// overwhelming probability.
433 ///
434 /// The primary difference from the base case is that the calls to `Encap()`
435 /// and `Decap()` are replaced with calls to `AuthEncap()` and
436 /// `AuthDecap()`, which add the sender public key to their internal
437 /// context string. The function parameters `pkR` and `pkS` are public
438 /// keys, and `enc` is an encapsulated KEM shared secret.
439 ///
440 /// Obviously, this variant can only be used with a KEM that provides
441 /// `AuthEncap()` and `AuthDecap()` procedures.
442 ///
443 /// This mechanism authenticates only the key pair of the sender, not any
444 /// other identifier. If an application wishes to bind HPKE ciphertexts or
445 /// exported secrets to another identity for the sender (e.g., an email
446 /// address or domain name), then this identifier should be included in the
447 /// info parameter to avoid identity misbinding issues.
448 ///
449 /// ```no_run
450 /// def SetupAuthR(enc, skR, info, pkS):
451 /// shared_secret = AuthDecap(enc, skR, pkS)
452 /// return KeyScheduleR(mode_auth, shared_secret, info,
453 /// default_psk, default_psk_id)
454 /// ```
455 ///
456 /// See [RFC 9180, Section 5.1.3] for details.
457 ///
458 /// # Errors
459 ///
460 /// Various errors may occur during the key encapsulation or decapsulation
461 /// process.
462 ///
463 /// [RFC 9180, Section 5.1.3]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.3
464 pub fn setup_auth_r(
465 &self,
466 mut crypto_backend: C,
467 enc: EncapsulatedSecretRef<'_>,
468 sk_r: HpkePrivateKeyRef<'_>,
469 info: &[u8],
470 pk_s: HpkePublicKeyRef<'_>,
471 ) -> Result<Context<C, Recipient>, Error> {
472 let shared_secret = kem::auth_decap(
473 &mut crypto_backend,
474 self.cipher_suite.kem_id,
475 enc,
476 sk_r,
477 pk_s,
478 )?;
479
480 self.key_schedule(
481 crypto_backend,
482 HpkeMode::Auth,
483 shared_secret.as_ref(),
484 info,
485 &[],
486 &[],
487 )
488 }
489
490 #[allow(clippy::too_many_arguments)]
491 /// 5.1.4. Authentication Using Both a PSK and an Asymmetric Key
492 ///
493 /// This mode is a straightforward combination of the PSK and authenticated
494 /// modes. Like the PSK mode, a PSK is provided as input to the key
495 /// schedule, and like the authenticated mode, authenticated KEM variants
496 /// are used.
497 ///
498 /// ```no_run
499 /// def SetupAuthPSKS(pkR, info, psk, psk_id, skS):
500 /// shared_secret, enc = AuthEncap(pkR, skS)
501 /// return enc, KeyScheduleS(mode_auth_psk, shared_secret, info,
502 /// psk, psk_id)
503 /// ```
504 ///
505 /// The PSK MUST have at least 32 bytes of entropy and SHOULD be of length
506 /// `Nh` bytes or longer. See [RFC 9180, Section 9.5] for a more detailed
507 /// discussion.
508 ///
509 /// See [RFC 9180, Section 5.1.4] for details.
510 ///
511 /// # Errors
512 ///
513 /// Various errors may occur during the key encapsulation or decapsulation
514 /// process, or if the provided PSK does not meet security requirements.
515 ///
516 /// [RFC 9180, Section 5.1.4]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.4
517 /// [RFC 9180, Section 9.5]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.5
518 pub fn setup_auth_psk_s(
519 &self,
520 mut crypto_backend: C,
521 pk_r: HpkePublicKeyRef<'_>,
522 info: &[u8],
523 psk: &[u8],
524 psk_id: &[u8],
525 sk_s: HpkePrivateKeyRef<'_>,
526 ) -> Result<(EncapsulatedSecret, Context<C, Sender>), Error> {
527 let (shared_secret, enc) =
528 kem::auth_encap(&mut crypto_backend, self.cipher_suite.kem_id, pk_r, sk_s)?;
529
530 let context = self.key_schedule(
531 crypto_backend,
532 HpkeMode::AuthPsk,
533 shared_secret.as_ref(),
534 info,
535 psk,
536 psk_id,
537 )?;
538
539 Ok((enc, context))
540 }
541
542 #[allow(clippy::too_many_arguments)]
543 /// 5.1.4. Authentication Using Both a PSK and an Asymmetric Key
544 ///
545 /// This mode is a straightforward combination of the PSK and authenticated
546 /// modes. Like the PSK mode, a PSK is provided as input to the key
547 /// schedule, and like the authenticated mode, authenticated KEM variants
548 /// are used.
549 ///
550 /// ```no_run
551 /// def SetupAuthPSKR(enc, skR, info, psk, psk_id, pkS):
552 /// shared_secret = AuthDecap(enc, skR, pkS)
553 /// return KeyScheduleR(mode_auth_psk, shared_secret, info,
554 /// psk, psk_id)
555 /// ```
556 ///
557 /// The PSK MUST have at least 32 bytes of entropy and SHOULD be of length
558 /// `Nh` bytes or longer. See [RFC 9180, Section 9.5] for a more detailed
559 /// discussion.
560 ///
561 /// See [RFC 9180, Section 5.1.4] for details.
562 ///
563 /// # Errors
564 ///
565 /// Various errors may occur during the key encapsulation or decapsulation
566 /// process, or if the provided PSK does not meet security requirements.
567 ///
568 /// [RFC 9180, Section 5.1.4]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.4
569 /// [RFC 9180, Section 9.5]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.5
570 pub fn setup_auth_psk_r(
571 &self,
572 mut crypto_backend: C,
573 enc: EncapsulatedSecretRef<'_>,
574 sk_r: HpkePrivateKeyRef<'_>,
575 info: &[u8],
576 psk: &[u8],
577 psk_id: &[u8],
578 pk_s: HpkePublicKeyRef<'_>,
579 ) -> Result<Context<C, Recipient>, Error> {
580 let shared_secret = kem::auth_decap(
581 &mut crypto_backend,
582 self.cipher_suite.kem_id,
583 enc,
584 sk_r,
585 pk_s,
586 )?;
587
588 self.key_schedule(
589 crypto_backend,
590 HpkeMode::AuthPsk,
591 shared_secret.as_ref(),
592 info,
593 psk,
594 psk_id,
595 )
596 }
597
598 #[inline]
599 /// ```text
600 /// def VerifyPSKInputs(mode, psk, psk_id):
601 /// got_psk = (psk != default_psk)
602 /// got_psk_id = (psk_id != default_psk_id)
603 /// if got_psk != got_psk_id:
604 /// raise Exception("Inconsistent PSK inputs")
605 ///
606 /// if got_psk and (mode in [mode_base, mode_auth]):
607 /// raise Exception("PSK input provided when not needed")
608 /// if (not got_psk) and (mode in [mode_psk, mode_auth_psk]):
609 /// raise Exception("Missing required PSK input")
610 /// ```
611 const fn verify_psk_inputs(mode: HpkeMode, psk: &[u8], psk_id: &[u8]) -> Result<(), Error> {
612 let got_psk = !psk.is_empty();
613 let got_psk_id = !psk_id.is_empty();
614
615 if got_psk != got_psk_id {
616 return Err(Error::InconsistentPsk);
617 }
618
619 if got_psk && matches!(mode, HpkeMode::Base | HpkeMode::Auth) {
620 return Err(Error::UnnecessaryPsk);
621 }
622
623 if !got_psk && matches!(mode, HpkeMode::Psk | HpkeMode::AuthPsk) {
624 return Err(Error::MissingPsk);
625 }
626
627 // Here different from RFC 9180's definition of `VerifyPSKInputs()`, we
628 // also check the PSK length requirement: the PSK MUST have at least 32 bytes of
629 // entropy and SHOULD be of length `Nh` bytes or longer. See [RFC 9180,
630 // Section 9.5] for a more detailed discussion.
631 if matches!(mode, HpkeMode::Psk | HpkeMode::AuthPsk) && psk.len() < 32 {
632 return Err(Error::InsecurePsk);
633 }
634
635 Ok(())
636 }
637
638 /// ```text
639 /// def KeySchedule<ROLE>(mode, shared_secret, info, psk, psk_id):
640 /// // ...
641 ///
642 /// psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
643 /// info_hash = LabeledExtract("", "info_hash", info)
644 /// key_schedule_context = concat(mode, psk_id_hash, info_hash)
645 ///
646 /// // ...
647 /// ```
648 ///
649 /// (Split out mainly for testing purposes.)
650 fn key_schedule_context(
651 &self,
652 crypto_backend: &C,
653 mode: HpkeMode,
654 info: &[u8],
655 psk_id: &[u8],
656 ) -> Result<Vec<u8>, Error> {
657 // psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
658 let psk_id_hash = kdf::labeled_extract(
659 crypto_backend,
660 self.cipher_suite.kdf_id,
661 &self.cipher_suite.suite_id(),
662 &[0],
663 "psk_id_hash",
664 IkmRef::from(psk_id),
665 )?;
666
667 // info_hash = LabeledExtract("", "info_hash", info)
668 let info_hash = kdf::labeled_extract(
669 crypto_backend,
670 self.cipher_suite.kdf_id,
671 &self.cipher_suite.suite_id(),
672 &[0],
673 "info_hash",
674 IkmRef::from(info),
675 )?;
676
677 // key_schedule_context = concat(mode, psk_id_hash, info_hash)
678 Ok([&[mode as u8], &*psk_id_hash, &*info_hash].concat())
679 }
680
681 #[allow(clippy::needless_pass_by_value)]
682 /// ```text
683 /// def KeySchedule<ROLE>(mode, shared_secret, info, psk, psk_id):
684 /// // ...
685 ///
686 /// secret = LabeledExtract(shared_secret, "secret", psk)
687 ///
688 /// // ...
689 /// ```
690 ///
691 /// (Split out mainly for testing purposes.)
692 fn key_schedule_secret(
693 &self,
694 crypto_backend: &C,
695 shared_secret: SharedSecretRef<'_>,
696 psk: &[u8],
697 ) -> Result<Prk, Error> {
698 // secret = LabeledExtract(shared_secret, "secret", psk)
699 kdf::labeled_extract(
700 crypto_backend,
701 self.cipher_suite.kdf_id,
702 &self.cipher_suite.suite_id(),
703 &shared_secret,
704 "secret",
705 IkmRef::from(psk),
706 )
707 .map_err(Into::into)
708 }
709
710 /// ```text
711 /// def KeySchedule<ROLE>(mode, shared_secret, info, psk, psk_id):
712 /// VerifyPSKInputs(mode, psk, psk_id)
713 ///
714 /// psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
715 /// info_hash = LabeledExtract("", "info_hash", info)
716 /// key_schedule_context = concat(mode, psk_id_hash, info_hash)
717 ///
718 /// secret = LabeledExtract(shared_secret, "secret", psk)
719 ///
720 /// key = LabeledExpand(secret, "key", key_schedule_context, Nk)
721 /// base_nonce = LabeledExpand(secret, "base_nonce",
722 /// key_schedule_context, Nn)
723 /// exporter_secret = LabeledExpand(secret, "exp",
724 /// key_schedule_context, Nh)
725 ///
726 /// return Context<ROLE>(key, base_nonce, 0, exporter_secret)
727 /// ```
728 fn key_schedule<'a, Role>(
729 &self,
730 crypto_backend: C,
731 mode: HpkeMode,
732 shared_secret: impl Into<SharedSecretRef<'a>>,
733 info: &[u8],
734 psk: &[u8],
735 psk_id: &[u8],
736 ) -> Result<Context<C, Role>, Error> {
737 Self::verify_psk_inputs(mode, psk, psk_id)?;
738
739 // psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
740 // info_hash = LabeledExtract("", "info_hash", info)
741 // key_schedule_context = concat(mode, psk_id_hash, info_hash)
742 let key_schedule_context =
743 self.key_schedule_context(&crypto_backend, mode, info, psk_id)?;
744
745 // secret = LabeledExtract(shared_secret, "secret", psk)
746 let secret = self.key_schedule_secret(&crypto_backend, shared_secret.into(), psk)?;
747
748 // key = LabeledExpand(secret, "key", key_schedule_context, Nk)
749 let key = kdf::labeled_expand(
750 &crypto_backend,
751 self.cipher_suite.kdf_id,
752 &self.cipher_suite.suite_id(),
753 PrkRef::from(&secret),
754 "key",
755 &key_schedule_context,
756 self.cipher_suite.aead_id.n_key(),
757 )?;
758 // base_nonce = LabeledExpand(secret, "base_nonce", key_schedule_context, Nn)
759 let base_nonce = kdf::labeled_expand(
760 &crypto_backend,
761 self.cipher_suite.kdf_id,
762 &self.cipher_suite.suite_id(),
763 PrkRef::from(&secret),
764 "base_nonce",
765 &key_schedule_context,
766 self.cipher_suite.aead_id.n_nonce(),
767 )?;
768 // exporter_secret = LabeledExpand(secret, "exp", key_schedule_context, Nh)
769 let exporter_secret = kdf::labeled_expand(
770 &crypto_backend,
771 self.cipher_suite.kdf_id,
772 &self.cipher_suite.suite_id(),
773 PrkRef::from(&secret),
774 "exp",
775 &key_schedule_context,
776 self.cipher_suite.kdf_id.n_hash(),
777 )?;
778
779 Ok(Context {
780 cipher_suite: self.cipher_suite,
781 aead: self
782 .cipher_suite
783 .aead_id
784 .new_crypto_info(&key, &base_nonce)
785 .expect("Must have valid key and nonce lengths"),
786 seq: 0,
787 exporter_secret: exporter_secret.to_vec(),
788 crypto_backend,
789 _role: PhantomData,
790 })
791 }
792}
793
794#[repr(u8)]
795#[derive(Debug, Clone, Copy, PartialEq, Eq)]
796/// The HPKE mode.
797///
798/// | Mode | Value |
799/// |:-:|:-:|
800/// | base | 0x00 |
801/// | psk | 0x01 |
802/// | auth | 0x02 |
803/// | auth_psk | 0x03 |
804pub enum HpkeMode {
805 /// Base mode.
806 Base = 0x00,
807
808 /// PSK mode.
809 Psk = 0x01,
810
811 /// Authenticated mode.
812 Auth = 0x02,
813
814 /// Authenticated PSK mode.
815 AuthPsk = 0x03,
816}
817
818impl HpkeMode {
819 #[inline]
820 /// Try to convert a `u8` into an `HpkeMode`.
821 ///
822 /// # Errors
823 ///
824 /// [`UnknownHpkeMode`] if the value does not correspond to a known mode.
825 pub const fn try_from(value: u8) -> Result<Self, UnknownHpkeMode> {
826 match value {
827 v if v == Self::Base as u8 => Ok(Self::Base),
828 v if v == Self::Psk as u8 => Ok(Self::Psk),
829 v if v == Self::Auth as u8 => Ok(Self::Auth),
830 v if v == Self::AuthPsk as u8 => Ok(Self::AuthPsk),
831 other => Err(UnknownHpkeMode(other)),
832 }
833 }
834}
835
836impl TryFrom<u8> for HpkeMode {
837 type Error = UnknownHpkeMode;
838
839 fn try_from(value: u8) -> Result<Self, Self::Error> {
840 Self::try_from(value)
841 }
842}
843
844#[cfg(feature = "serde")]
845impl serde::Serialize for HpkeMode {
846 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
847 where
848 S: serde::Serializer,
849 {
850 serializer.serialize_u8(*self as u8)
851 }
852}
853
854#[cfg(feature = "serde")]
855impl<'de> serde::Deserialize<'de> for HpkeMode {
856 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
857 where
858 D: serde::Deserializer<'de>,
859 {
860 let value = u8::deserialize(deserializer)?;
861 HpkeMode::try_from(value).map_err(serde::de::Error::custom)
862 }
863}
864
865#[derive(Debug, Clone, Copy, PartialEq, Eq)]
866/// Error indicating an unknown HPKE mode.
867pub struct UnknownHpkeMode(pub u8);
868
869impl core::error::Error for UnknownHpkeMode {}
870
871impl fmt::Display for UnknownHpkeMode {
872 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
873 write!(f, "Unknown HPKE mode: {}", self.0)
874 }
875}
876
877#[derive(Debug)]
878/// Marker type for [`Context`] that indicates the `sender` role.
879pub struct Sender;
880
881#[derive(Debug)]
882/// Marker type for [`Context`] that indicates the `recipient` role.
883pub struct Recipient;
884
885/// The HPKE cryptographic context.
886///
887/// HPKE allows multiple encryption operations to be done based on a given
888/// setup transaction. Since the public key operations involved in setup are
889/// typically more expensive than symmetric encryption or decryption, this
890/// allows applications to amortize the cost of the public key operations,
891/// reducing the overall overhead.
892///
893/// In order to avoid nonce reuse, however, this encryption must be stateful.
894/// Each of the setup procedures above produces a role-specific context object
895/// that stores the AEAD and secret export parameters. The AEAD parameters
896/// consist of:
897///
898/// - The AEAD algorithm in use
899/// - A secret `key`
900/// - A base nonce `base_nonce`
901/// - A sequence number (initially 0)
902///
903/// The secret export parameters consist of:
904///
905/// - The HPKE ciphersuite in use and
906/// - An `exporter_secret` used for the secret export interface (see [RFC 9180,
907/// Section 5.3])
908///
909/// Note that the RFC currently doesn't define this.
910/// Also see <https://github.com/cfrg/draft-irtf-cfrg-hpke/issues/161>.
911///
912/// TODO: need pub?
913///
914/// [RFC 9180, Section 5.3]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.3
915pub struct Context<C, Role> {
916 /// The HPKE `ciphersuite` in use.
917 cipher_suite: HpkeCipherSuite,
918
919 /// The AEAD algorithm, secret `key` and `base_nonce`.
920 ///
921 /// The only way to get `None` here is to use an export-only AEAD,
922 aead: Option<HpkeAead>,
923
924 /// The sequence number.
925 seq: u32,
926
927 /// The exporter secret.
928 exporter_secret: Vec<u8>,
929
930 /// The crypto backend.
931 crypto_backend: C,
932
933 /// The role marker.
934 _role: PhantomData<Role>,
935}
936
937impl<C: Crypto> Context<C, Sender> {
938 /// See [`seal_in_place`](Self::seal_in_place).
939 ///
940 /// # Errors
941 ///
942 /// See [`seal_in_place`](Self::seal_in_place).
943 pub fn seal(&mut self, aad: &[u8], pt: &[u8]) -> Result<Vec<u8>, Error> {
944 let mut in_out = pt.to_vec();
945
946 self.seal_in_place(aad, &mut in_out)?;
947
948 Ok(in_out)
949 }
950
951 /// 5.2. Encryption and Decryption
952 ///
953 /// Encryption is unidirectional from sender to recipient. The sender's
954 /// context can encrypt a plaintext `pt` with associated data `aad` as
955 /// follows:
956 ///
957 /// ```text
958 /// def Context.Seal(aad, pt):
959 /// ct = Seal(self.key, self.ComputeNonce(self.seq), aad, pt)
960 /// self.IncrementSeq()
961 /// return ct
962 /// ```
963 ///
964 /// See [RFC 9180, Section 5.2] for details.
965 ///
966 /// # Errors
967 ///
968 /// [`CryptoError`], or message limit reached.
969 ///
970 /// [RFC 9180, Section 5.2]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2
971 pub fn seal_in_place(&mut self, aad: &[u8], in_out: &mut Vec<u8>) -> Result<(), Error> {
972 self.crypto_backend.aead_seal_in_place(
973 &self
974 .aead
975 .as_ref()
976 .ok_or(Error::InvalidInput("Export-only AEAD"))?
977 .copied_updating_nonce(|base_nonce| {
978 Self::compute_nonce(base_nonce, self.seq);
979 }),
980 aad,
981 in_out,
982 )?;
983
984 self.increment_seq()?;
985
986 Ok(())
987 }
988}
989
990impl<C: Crypto> Context<C, Recipient> {
991 /// See [`open_in_place`](Self::open_in_place).
992 ///
993 /// # Errors
994 ///
995 /// See [`open_in_place`](Self::open_in_place).
996 pub fn open(&mut self, aad: &[u8], ct: &[u8]) -> Result<Vec<u8>, Error> {
997 let mut in_out = ct.to_vec();
998
999 self.open_in_place(aad, &mut in_out)?;
1000
1001 Ok(in_out)
1002 }
1003
1004 /// 5.2. Encryption and Decryption
1005 ///
1006 /// The recipient's context can decrypt a ciphertext `ct` with associated
1007 /// data `aad` as follows:
1008 ///
1009 /// ```no_run
1010 /// def Context.Open(aad, ct):
1011 /// pt = Open(self.key, self.ComputeNonce(self.seq), aad, ct)
1012 /// if pt == OpenError:
1013 /// raise OpenError
1014 /// self.IncrementSeq()
1015 /// return pt
1016 /// ```
1017 ///
1018 /// See [RFC 9180, Section 5.2] for details.
1019 ///
1020 /// # Errors
1021 ///
1022 /// [`CryptoError`], or message limit reached.
1023 ///
1024 /// [RFC 9180, Section 5.2]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2
1025 pub fn open_in_place(&mut self, aad: &[u8], in_out: &mut Vec<u8>) -> Result<(), Error> {
1026 self.crypto_backend.aead_open_in_place(
1027 &self
1028 .aead
1029 .as_ref()
1030 .ok_or(Error::InvalidInput("Export-only AEAD"))?
1031 .copied_updating_nonce(|base_nonce| {
1032 Self::compute_nonce(base_nonce, self.seq);
1033 }),
1034 aad,
1035 in_out,
1036 )?;
1037
1038 self.increment_seq()?;
1039
1040 Ok(())
1041 }
1042}
1043
1044impl<C: Crypto, Role> Context<C, Role> {
1045 /// 5.3. Secret Export
1046 ///
1047 /// Takes a serialised exporter context as byte slice and a length for the
1048 /// output secret and returns an exporter secret as byte vector.
1049 ///
1050 /// ```no_run
1051 /// def Context.Export(exporter_context, L):
1052 /// return LabeledExpand(self.exporter_secret, "sec", exporter_context, L)
1053 /// ```
1054 ///
1055 /// See [RFC 9180, Section 5.3] for details.
1056 ///
1057 /// # Errors
1058 ///
1059 /// See [`kdf::labeled_expand`].
1060 ///
1061 /// [RFC 9180, Section 5.3]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.3
1062 pub fn export(&self, exporter_context: &[u8], length: usize) -> Result<Okm, Error> {
1063 kdf::labeled_expand(
1064 &self.crypto_backend,
1065 self.cipher_suite.kdf_id,
1066 &self.cipher_suite.suite_id(),
1067 PrkRef::from(self.exporter_secret.as_slice()),
1068 "sec",
1069 exporter_context,
1070 length,
1071 )
1072 .map_err(Into::into)
1073 }
1074
1075 #[inline]
1076 /// ```no_run
1077 /// def Context<ROLE>.ComputeNonce(seq):
1078 /// seq_bytes = I2OSP(seq, Nn)
1079 /// return xor(self.base_nonce, seq_bytes)
1080 /// ```
1081 fn compute_nonce(base_nonce: &mut [u8], seq: u32) {
1082 // I2OSP: `to_be_bytes` then left padded with zeros to length `Nn`
1083 // We just XOR the bytes from right to left.
1084 for (o, i) in base_nonce
1085 .iter_mut()
1086 .rev()
1087 .zip(seq.to_be_bytes().into_iter().rev())
1088 {
1089 *o ^= i;
1090 }
1091 }
1092
1093 #[inline]
1094 /// ```no_run
1095 /// def Context<ROLE>.IncrementSeq():
1096 /// if self.seq >= (1 << (8*Nn)) - 1:
1097 /// raise MessageLimitReached
1098 /// self.seq += 1
1099 /// ```
1100 const fn increment_seq(&mut self) -> Result<(), Error> {
1101 let nn = self.cipher_suite.aead_id.n_nonce() as u128;
1102
1103 if self.seq as u128 >= (1 << (8 * nn)) - 1 {
1104 return Err(Error::MessageLimitReached);
1105 }
1106
1107 self.seq += 1;
1108
1109 Ok(())
1110 }
1111}
1112
1113#[cfg(feature = "test-vectors")]
1114#[allow(missing_docs)]
1115pub static HPKE_TEST_VECTORS: std::sync::LazyLock<Vec<HpkeTestVector>> =
1116 std::sync::LazyLock::new(|| {
1117 let data = include_str!("../tests/test-vectors.json");
1118
1119 serde_json::from_str(data).expect("Failed to parse HPKE test vectors")
1120 });
1121
1122#[cfg(feature = "test-vectors")]
1123#[allow(missing_docs)]
1124#[derive(Debug, Clone)]
1125#[derive(serde::Serialize, serde::Deserialize)]
1126pub struct HpkeTestVector {
1127 pub mode: HpkeMode,
1128 pub kem_id: HpkeKemId,
1129 pub kdf_id: HpkeKdfId,
1130 pub aead_id: HpkeAeadId,
1131 pub info: HexString,
1132 #[serde(rename = "ikmR")]
1133 pub ikm_r: HexString,
1134 #[serde(rename = "ikmS")]
1135 pub ikm_s: Option<HexString>,
1136 #[serde(rename = "ikmE")]
1137 pub ikm_e: HexString,
1138 #[serde(rename = "skRm")]
1139 pub sk_rm: HexString,
1140 #[serde(default)]
1141 #[serde(rename = "skSm")]
1142 pub sk_sm: Option<HexString>,
1143 #[serde(rename = "skEm")]
1144 pub sk_em: HexString,
1145 pub psk: Option<HexString>,
1146 pub psk_id: Option<HexString>,
1147 #[serde(rename = "pkRm")]
1148 pub pk_rm: HexString,
1149 #[serde(rename = "pkSm")]
1150 pub pk_sm: Option<HexString>,
1151 #[serde(rename = "pkEm")]
1152 pub pk_em: HexString,
1153 pub enc: HexString,
1154 pub shared_secret: HexString,
1155 pub key_schedule_context: HexString,
1156 pub secret: HexString,
1157 pub key: HexString,
1158 pub base_nonce: HexString,
1159 pub exporter_secret: HexString,
1160 pub encryptions: Vec<HpkeTestVectorEncryption>,
1161 pub exports: Vec<HpkeTestVectorExport>,
1162}
1163
1164#[cfg(feature = "test-vectors")]
1165#[allow(missing_docs)]
1166#[derive(Debug, Clone)]
1167#[derive(serde::Serialize, serde::Deserialize)]
1168pub struct HpkeTestVectorEncryption {
1169 pub aad: HexString,
1170 pub ct: HexString,
1171 pub nonce: HexString,
1172 pub pt: HexString,
1173}
1174
1175#[cfg(feature = "test-vectors")]
1176#[allow(missing_docs)]
1177#[derive(Debug, Clone)]
1178#[derive(serde::Serialize, serde::Deserialize)]
1179pub struct HpkeTestVectorExport {
1180 pub exporter_context: HexString,
1181 #[serde(rename = "L")]
1182 pub l: usize,
1183 pub exported_value: HexString,
1184}
1185
1186#[cfg(feature = "test-vectors")]
1187#[allow(missing_docs)]
1188#[derive(Debug, Clone)]
1189pub struct HexString {
1190 pub bytes: Vec<u8>,
1191}
1192
1193#[cfg(feature = "test-vectors")]
1194impl serde::Serialize for HexString {
1195 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1196 where
1197 S: serde::Serializer,
1198 {
1199 serializer.serialize_str(&const_hex::encode(&self.bytes))
1200 }
1201}
1202
1203#[cfg(feature = "test-vectors")]
1204impl<'de> serde::Deserialize<'de> for HexString {
1205 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1206 where
1207 D: serde::Deserializer<'de>,
1208 {
1209 let hex_str = <&str>::deserialize(deserializer)?;
1210 let bytes = const_hex::decode(hex_str).map_err(serde::de::Error::custom)?;
1211 Ok(HexString { bytes })
1212 }
1213}
1214
1215#[cfg(feature = "test-vectors")]
1216impl core::ops::Deref for HexString {
1217 type Target = [u8];
1218
1219 fn deref(&self) -> &Self::Target {
1220 &self.bytes
1221 }
1222}
1223
1224#[cfg(test)]
1225mod kat_tests {
1226 use alloc::format;
1227 use alloc::vec::Vec;
1228 use core::panic::UnwindSafe;
1229 use std::panic::catch_unwind;
1230 use std::println;
1231
1232 use super::*;
1233
1234 #[test_case::test_matrix(
1235 [
1236 hpke_crypto::backend::HpkeCryptoAwsLc::new,
1237 hpke_crypto::backend::HpkeCryptoGraviola::new,
1238 ]
1239 )]
1240 fn test_setup<C, F>(crypto_backend: F)
1241 where
1242 C: Crypto + Send + Sync + UnwindSafe,
1243 F: Fn() -> Result<C, CryptoError> + UnwindSafe + Copy,
1244 {
1245 let mut rets = Vec::new();
1246
1247 for (idx, test_case) in HPKE_TEST_VECTORS.iter().enumerate() {
1248 let ret = catch_unwind(move || {
1249 test_setup_each(
1250 crypto_backend,
1251 idx,
1252 Hpke::prepare(HpkeCipherSuite {
1253 kem_id: test_case.kem_id,
1254 kdf_id: test_case.kdf_id,
1255 aead_id: test_case.aead_id,
1256 }),
1257 test_case.mode,
1258 &test_case.pk_rm,
1259 &test_case.info,
1260 test_case.psk.as_deref(),
1261 test_case.psk_id.as_deref(),
1262 test_case.sk_sm.as_deref(),
1263 &test_case.sk_rm,
1264 test_case.pk_sm.as_deref(),
1265 );
1266 });
1267
1268 rets.push((format!("{}({})", test_case.kdf_id, idx), ret));
1269 }
1270
1271 let errors: Vec<_> = rets
1272 .iter()
1273 .filter(|(_, ret)| ret.is_err())
1274 .collect();
1275
1276 if !errors.is_empty() {
1277 for (name, err) in &errors {
1278 println!("[FAILED] {name}: {err:?}");
1279 }
1280
1281 panic!("{} test cases failed", errors.len());
1282 } else {
1283 println!("[OK] all {} test cases passed", rets.len());
1284 }
1285 }
1286
1287 fn test_setup_each<C: Crypto, F: Fn() -> Result<C, CryptoError>>(
1288 crypto_backend_f: F,
1289 idx: usize,
1290 hpke: Hpke<C>,
1291 mode: HpkeMode,
1292 pk_r: &[u8],
1293 info: &[u8],
1294 psk: Option<&[u8]>,
1295 psk_id: Option<&[u8]>,
1296 sk_s: Option<&[u8]>,
1297 sk_r: &[u8],
1298 pk_s: Option<&[u8]>,
1299 ) {
1300 let crypto_backend = crypto_backend_f().unwrap();
1301
1302 if !crypto_backend.is_kem_supported(&hpke.cipher_suite.kem_id) {
1303 // Skip unsupported KEMs.
1304 println!(
1305 "[{name}][{idx}] Skipping, unsupported KEM {alg:?}",
1306 name = core::any::type_name::<C>(),
1307 alg = hpke.cipher_suite.kem_id
1308 );
1309 return;
1310 }
1311
1312 if !crypto_backend.is_kdf_supported(&hpke.cipher_suite.kdf_id) {
1313 // Skip unsupported KDFs.
1314 println!(
1315 "[{name}][{idx}] Skipping, unsupported KDF {alg:?}",
1316 name = core::any::type_name::<C>(),
1317 alg = hpke.cipher_suite.kdf_id
1318 );
1319 return;
1320 }
1321
1322 let (enc_s, ctx_s) = hpke
1323 .setup_s(
1324 crypto_backend,
1325 mode,
1326 HpkePublicKeyRef::from(pk_r),
1327 info,
1328 psk,
1329 psk_id,
1330 sk_s.map(HpkePrivateKeyRef::from),
1331 )
1332 .unwrap_or_else(|e| {
1333 panic!("Failed to setup sender context: {e:?}, idx={idx}");
1334 });
1335
1336 let crypto_backend = crypto_backend_f().unwrap();
1337
1338 let ctx_r = hpke
1339 .setup_r(
1340 crypto_backend,
1341 mode,
1342 EncapsulatedSecretRef::from(&enc_s),
1343 HpkePrivateKeyRef::from(sk_r),
1344 info,
1345 psk,
1346 psk_id,
1347 pk_s.map(HpkePublicKeyRef::from),
1348 )
1349 .unwrap_or_else(|e| {
1350 panic!("Failed to setup recipient context: {e:?}, idx={idx}");
1351 });
1352
1353 assert_eq!(
1354 ctx_s.exporter_secret, ctx_r.exporter_secret,
1355 "Exporter secret mismatch"
1356 );
1357 }
1358
1359 #[test_case::test_matrix(
1360 [
1361 hpke_crypto::backend::HpkeCryptoAwsLc::new,
1362 hpke_crypto::backend::HpkeCryptoGraviola::new,
1363 ]
1364 )]
1365 fn test_key_schedule<C: Crypto + Send + Sync + UnwindSafe, F>(crypto_backend: F)
1366 where
1367 F: Fn() -> Result<C, CryptoError>,
1368 {
1369 let mut rets = Vec::new();
1370
1371 for (idx, test_case) in HPKE_TEST_VECTORS.iter().enumerate() {
1372 let crypto_backend = crypto_backend().unwrap();
1373
1374 let ret = catch_unwind(move || {
1375 test_key_schedule_each(
1376 crypto_backend,
1377 idx,
1378 "GENERIC",
1379 Hpke::prepare(HpkeCipherSuite {
1380 kem_id: test_case.kem_id,
1381 kdf_id: test_case.kdf_id,
1382 aead_id: test_case.aead_id,
1383 }),
1384 test_case.mode,
1385 &test_case.info,
1386 test_case
1387 .psk
1388 .as_deref()
1389 .unwrap_or_default(),
1390 test_case
1391 .psk_id
1392 .as_deref()
1393 .unwrap_or_default(),
1394 &test_case.shared_secret,
1395 test_case.aead_id,
1396 &test_case.key_schedule_context,
1397 &test_case.secret,
1398 &test_case.key,
1399 &test_case.base_nonce,
1400 &test_case.exporter_secret,
1401 );
1402 });
1403
1404 rets.push((format!("{}({})", test_case.kdf_id, idx), ret));
1405 }
1406
1407 let errors: Vec<_> = rets
1408 .iter()
1409 .filter(|(_, ret)| ret.is_err())
1410 .collect();
1411
1412 if !errors.is_empty() {
1413 for (name, err) in &errors {
1414 println!("[FAILED] {name}: {err:?}");
1415 }
1416
1417 panic!("{} test cases failed", errors.len());
1418 } else {
1419 println!("[OK] all {} test cases passed", rets.len());
1420 }
1421 }
1422
1423 fn test_key_schedule_each<C: Crypto>(
1424 crypto_backend: C,
1425 idx: usize,
1426 role: &'static str,
1427 hpke: Hpke<C>,
1428 mode: HpkeMode,
1429 info: &[u8],
1430 psk: &[u8],
1431 psk_id: &[u8],
1432 shared_secret: &[u8],
1433 aead_id: HpkeAeadId,
1434 expected_key_schedule_context: &[u8],
1435 expected_secret: &[u8],
1436 expected_key: &[u8],
1437 expected_base_nonce: &[u8],
1438 expected_exporter_secret: &[u8],
1439 ) {
1440 if !crypto_backend.is_kdf_supported(&hpke.cipher_suite.kdf_id) {
1441 // Skip unsupported KDFs.
1442 println!(
1443 "[{name}][{idx}][{role}] Skipping, unsupported KDF {alg:?}",
1444 name = core::any::type_name::<C>(),
1445 alg = hpke.cipher_suite.kdf_id
1446 );
1447 return;
1448 }
1449
1450 // Testing key schedule context manually here.
1451 {
1452 let key_schedule_context = hpke
1453 .key_schedule_context(&crypto_backend, mode, info, psk_id)
1454 .unwrap_or_else(|e| {
1455 panic!(
1456 "Failed to create key schedule context: {e:?}, mode={mode:?}, \
1457 info={info:?}, psk_id={psk_id:?}",
1458 );
1459 });
1460
1461 assert_eq!(
1462 key_schedule_context, expected_key_schedule_context,
1463 "Key schedule context mismatch"
1464 );
1465 }
1466
1467 // Testing key schedule secret manually here.
1468 {
1469 let secret = hpke
1470 .key_schedule_secret(&crypto_backend, SharedSecretRef::from(&shared_secret), psk)
1471 .unwrap_or_else(|e| {
1472 panic!(
1473 "Failed to create key schedule secret: {e:?}, mode={mode:?}, \
1474 shared_secret={shared_secret:?}, psk={psk:?}",
1475 );
1476 });
1477
1478 assert_eq!(&*secret, expected_secret, "Key schedule secret mismatch");
1479 }
1480
1481 // Testing key schedule here.
1482 {
1483 let context = hpke
1484 .key_schedule::<Sender>(
1485 crypto_backend,
1486 mode,
1487 SharedSecretRef::from(shared_secret),
1488 &info,
1489 psk,
1490 psk_id,
1491 )
1492 .unwrap_or_else(|e| {
1493 panic!(
1494 "Failed to create context: {e:?}, mode={mode:?}, \
1495 shared_secret={shared_secret:?}, \
1496 key_schedule_context:{expected_key_schedule_context:?}, info={info:?}, \
1497 psk={psk:?}, psk_id={psk_id:?}",
1498 );
1499 });
1500
1501 // Note that key and nonce are empty for exporter only key derivation.
1502 if matches!(aead_id, HpkeAeadId::EXPORT_ONLY) {
1503 assert!(
1504 context.aead.is_none(),
1505 "AEAD key / nonce should be None for EXPORT_ONLY"
1506 );
1507 } else {
1508 assert_eq!(
1509 context.aead.as_ref().unwrap().key(),
1510 expected_key,
1511 "AEAD key mismatch"
1512 );
1513 assert_eq!(
1514 context.aead.as_ref().unwrap().nonce(),
1515 expected_base_nonce,
1516 "AEAD base nonce mismatch"
1517 );
1518 }
1519 assert_eq!(
1520 context.seq, 0,
1521 "Initial sequence number must be 0 when initialized"
1522 );
1523 assert_eq!(
1524 context.exporter_secret, expected_exporter_secret,
1525 "Exporter secret mismatch"
1526 );
1527 };
1528 }
1529
1530 #[test_case::test_matrix(
1531 [
1532 hpke_crypto::backend::HpkeCryptoAwsLc::new,
1533 hpke_crypto::backend::HpkeCryptoGraviola::new,
1534 ]
1535 )]
1536 fn test_encryption<C: Crypto + Send + Sync + UnwindSafe, F>(crypto_backend: F)
1537 where
1538 F: Fn() -> Result<C, CryptoError>,
1539 {
1540 let mut rets = Vec::new();
1541
1542 for (idx, test_case) in HPKE_TEST_VECTORS.iter().enumerate() {
1543 for (enc_idx, enc) in test_case.encryptions.iter().enumerate() {
1544 let crypto_backend = crypto_backend().unwrap();
1545
1546 let context = Context {
1547 cipher_suite: HpkeCipherSuite {
1548 kem_id: test_case.kem_id,
1549 kdf_id: test_case.kdf_id,
1550 aead_id: test_case.aead_id,
1551 },
1552 aead: test_case
1553 .aead_id
1554 .new_crypto_info(&test_case.key, &test_case.base_nonce)
1555 .expect("Must have valid key and nonce lengths"),
1556 seq: 0,
1557 exporter_secret: test_case.exporter_secret.bytes.clone(),
1558 crypto_backend,
1559 _role: PhantomData,
1560 };
1561
1562 let ret = catch_unwind(move || {
1563 test_encryption_each(context, enc_idx as u32, &enc.aad, &enc.pt, &enc.ct);
1564 });
1565
1566 rets.push((format!("{}({})[{}]", test_case.kdf_id, idx, enc_idx), ret));
1567 }
1568 }
1569
1570 let errors: Vec<_> = rets
1571 .iter()
1572 .filter(|(_, ret)| ret.is_err())
1573 .collect();
1574
1575 if !errors.is_empty() {
1576 for (name, err) in &errors {
1577 println!("[FAILED] {name}: {err:?}");
1578 }
1579
1580 panic!("{} test cases failed", errors.len());
1581 } else {
1582 println!("[OK] all {} test cases passed", rets.len());
1583 }
1584 }
1585
1586 fn test_encryption_each<C: Crypto>(
1587 mut context: Context<C, Sender>,
1588 seq: u32,
1589 aad: &[u8],
1590 pt: &[u8],
1591 expected_ct: &[u8],
1592 ) {
1593 context.seq = seq;
1594
1595 let ct = context
1596 .seal(aad, pt)
1597 .unwrap_or_else(|e| {
1598 panic!("Failed to encrypt: {e:?}, seq={seq}, aad={aad:?}, pt={pt:?}",);
1599 });
1600
1601 assert_eq!(ct, expected_ct, "Ciphertext mismatch");
1602
1603 // Decrypt and verify.
1604 let mut context = Context {
1605 cipher_suite: context.cipher_suite,
1606 aead: context.aead,
1607 seq,
1608 exporter_secret: context.exporter_secret,
1609 crypto_backend: context.crypto_backend,
1610 _role: PhantomData::<Recipient>,
1611 };
1612
1613 let pt2 = context
1614 .open(aad, &ct)
1615 .unwrap_or_else(|e| {
1616 panic!("Failed to decrypt: {e:?}, seq={seq}, aad={aad:?}, ct={ct:?}");
1617 });
1618
1619 assert_eq!(pt2, pt, "Decrypted plaintext mismatch");
1620 }
1621
1622 #[test_case::test_matrix(
1623 [
1624 hpke_crypto::backend::HpkeCryptoAwsLc::new,
1625 hpke_crypto::backend::HpkeCryptoGraviola::new,
1626 ]
1627 )]
1628 fn test_exported_values<C: Crypto + Send + Sync + UnwindSafe, F>(crypto_backend: F)
1629 where
1630 F: Fn() -> Result<C, CryptoError>,
1631 {
1632 let mut rets = Vec::new();
1633
1634 for (idx, test_case) in HPKE_TEST_VECTORS.iter().enumerate() {
1635 for (enc_idx, enc) in test_case.exports.iter().enumerate() {
1636 let crypto_backend = crypto_backend().unwrap();
1637
1638 let context = Context {
1639 cipher_suite: HpkeCipherSuite {
1640 kem_id: test_case.kem_id,
1641 kdf_id: test_case.kdf_id,
1642 aead_id: test_case.aead_id,
1643 },
1644 aead: None,
1645 seq: 0,
1646 exporter_secret: test_case.exporter_secret.bytes.clone(),
1647 crypto_backend,
1648 _role: PhantomData,
1649 };
1650
1651 let ret = catch_unwind(move || {
1652 test_exported_values_each(
1653 context,
1654 &enc.exporter_context,
1655 enc.l,
1656 &enc.exported_value,
1657 );
1658 });
1659
1660 rets.push((format!("{}({})[{}]", test_case.kdf_id, idx, enc_idx), ret));
1661 }
1662 }
1663
1664 let errors: Vec<_> = rets
1665 .iter()
1666 .filter(|(_, ret)| ret.is_err())
1667 .collect();
1668
1669 if !errors.is_empty() {
1670 for (name, err) in &errors {
1671 println!("[FAILED] {name}: {err:?}");
1672 }
1673
1674 panic!("{} test cases failed", errors.len());
1675 } else {
1676 println!("[OK] all {} test cases passed", rets.len());
1677 }
1678 }
1679
1680 fn test_exported_values_each<C: Crypto>(
1681 context: Context<C, Sender>,
1682 exporter_context: &[u8],
1683 l: usize,
1684 expected_exported_value: &[u8],
1685 ) {
1686 let exported_value = context
1687 .export(exporter_context, l)
1688 .unwrap_or_else(|e| {
1689 panic!(
1690 "Failed to export secret: {e:?}, exporter_context={exporter_context:?}, l={l}",
1691 );
1692 });
1693
1694 assert_eq!(
1695 &*exported_value, expected_exported_value,
1696 "Exported value mismatch"
1697 );
1698 }
1699}