1use std::{
4 fmt,
5 time::{SystemTime, UNIX_EPOCH},
6};
7
8use hkdf::Hkdf;
9use hpke::{
10 Deserializable, Kem as KemTrait, OpModeR, OpModeS, PskBundle, Serializable,
11 aead::{AeadCtxR, AeadTag, ChaCha20Poly1305 as HpkeChaCha20Poly1305},
12 inout::InOutBuf,
13 kdf::HkdfSha256,
14 kem::X25519HkdfSha256,
15 setup_receiver, setup_sender_with_rng,
16};
17use rand_chacha::ChaCha20Rng;
18use rand_core::SeedableRng;
19use sha2::{Digest, Sha256};
20use zeroize::Zeroizing;
21
22use crate::{
23 CLOCK_SKEW_SECS, CompressionCoding, EntropySource, Error, HARD_MAX_ID_LEN, Limits, Method,
24 REQUEST_LIFETIME_SECS, SystemEntropy,
25 codec::{
26 ENC_LEN, RESPONSE_NONCE_LEN, TAG_LEN, encode_request, encode_request_header, parse_request,
27 },
28 compression,
29 message::{HeaderField, Request, Response},
30 response::{ResponseMode, ResponseOpener, ResponseRecord, ResponseSealer},
31};
32
33type Kem = X25519HkdfSha256;
34type Kdf = HkdfSha256;
35type HpkeAead = HpkeChaCha20Poly1305;
36type RecipientContext = AeadCtxR<HpkeAead, Kdf, Kem>;
37
38const REQUEST_INFO_LABEL: &[u8] = b"message/hpke-http request\0v2";
39const RESPONSE_EXPORT_LABEL: &[u8] = b"message/hpke-http response\0v2";
40const REPLAY_LABEL: &[u8] = b"hpke-http/replay\0v2\0";
41const RESPONSE_KEY_INFO: &[u8] = b"hpke-http/2 response key";
42const RESPONSE_NONCE_INFO: &[u8] = b"hpke-http/2 response nonce";
43const RESPONSE_SECRET_LEN: usize = 32;
44const RESPONSE_KEY_LEN: usize = 32;
45const AEAD_NONCE_LEN: usize = 12;
46const MIN_PSK_LEN: usize = 32;
47pub(crate) type ResponseKey = Zeroizing<[u8; RESPONSE_KEY_LEN]>;
48pub(crate) type ResponseNonce = Zeroizing<[u8; AEAD_NONCE_LEN]>;
49
50pub struct KeyPair {
52 private_key: Zeroizing<Vec<u8>>,
53 public_key: Vec<u8>,
54}
55
56impl fmt::Debug for KeyPair {
57 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
58 formatter
59 .debug_struct("KeyPair")
60 .field("private_key", &"[REDACTED]")
61 .field("public_key_len", &self.public_key.len())
62 .finish()
63 }
64}
65
66impl KeyPair {
67 #[must_use]
69 pub fn into_parts(self) -> (Zeroizing<Vec<u8>>, Vec<u8>) {
70 (self.private_key, self.public_key)
71 }
72
73 #[must_use]
75 pub fn public_key(&self) -> &[u8] {
76 &self.public_key
77 }
78}
79
80pub fn generate_key_pair() -> Result<KeyPair, Error> {
86 generate_key_pair_with_entropy(&mut SystemEntropy)
87}
88
89pub fn generate_key_pair_with_entropy(entropy: &mut impl EntropySource) -> Result<KeyPair, Error> {
95 let mut input_key_material = Zeroizing::new([0_u8; 32]);
96 entropy.fill(input_key_material.as_mut())?;
97 let (private_key, public_key) = Kem::derive_keypair(input_key_material.as_ref());
98 Ok(KeyPair {
99 private_key: Zeroizing::new(private_key.to_bytes().to_vec()),
100 public_key: public_key.to_bytes().to_vec(),
101 })
102}
103
104#[must_use]
106pub struct ProtectedRequest {
107 envelope: Vec<u8>,
108 response_token: ResponseToken,
109}
110
111impl fmt::Debug for ProtectedRequest {
112 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
113 formatter
114 .debug_struct("ProtectedRequest")
115 .field("envelope_len", &self.envelope.len())
116 .field("response_token", &self.response_token)
117 .finish()
118 }
119}
120
121impl ProtectedRequest {
122 #[must_use]
124 pub fn envelope(&self) -> &[u8] {
125 &self.envelope
126 }
127
128 pub fn into_parts(self) -> (Vec<u8>, ResponseToken) {
130 (self.envelope, self.response_token)
131 }
132}
133
134pub struct Client {
136 recipient_public_key: <Kem as KemTrait>::PublicKey,
137 recipient_key_id: Vec<u8>,
138 psk: Zeroizing<Vec<u8>>,
139 psk_id: Vec<u8>,
140 limits: Limits,
141 compression: Option<CompressionCoding>,
142}
143
144impl fmt::Debug for Client {
145 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
146 formatter
147 .debug_struct("Client")
148 .field("recipient_key_id_len", &self.recipient_key_id.len())
149 .field("psk", &"[REDACTED]")
150 .field("psk_id_len", &self.psk_id.len())
151 .field("limits", &self.limits)
152 .field("compression", &self.compression)
153 .finish_non_exhaustive()
154 }
155}
156
157impl Client {
158 pub fn new(
165 recipient_public_key: &[u8],
166 recipient_key_id: Vec<u8>,
167 psk: Vec<u8>,
168 psk_id: Vec<u8>,
169 limits: Limits,
170 ) -> Result<Self, Error> {
171 let psk = Zeroizing::new(psk);
172 validate_id(&recipient_key_id)?;
173 validate_credential(psk.as_slice(), &psk_id)?;
174 let limits = limits.validate()?;
175 let recipient_public_key = <Kem as KemTrait>::PublicKey::from_bytes(recipient_public_key)
176 .map_err(|_| Error::InvalidConfiguration)?;
177 Ok(Self {
178 recipient_public_key,
179 recipient_key_id,
180 psk,
181 psk_id,
182 limits,
183 compression: None,
184 })
185 }
186
187 #[must_use]
193 pub fn with_compression(mut self, coding: CompressionCoding) -> Self {
194 self.compression = Some(coding);
195 self
196 }
197
198 pub fn protect(&self, request: &Request) -> Result<ProtectedRequest, Error> {
205 self.protect_at_with_entropy(request, unix_time_seconds()?, &mut SystemEntropy)
206 }
207
208 pub fn protect_at(
217 &self,
218 request: &Request,
219 now_unix_s: u64,
220 ) -> Result<ProtectedRequest, Error> {
221 self.protect_at_with_entropy(request, now_unix_s, &mut SystemEntropy)
222 }
223
224 pub fn protect_with_entropy(
231 &self,
232 request: &Request,
233 entropy: &mut impl EntropySource,
234 ) -> Result<ProtectedRequest, Error> {
235 self.protect_at_with_entropy(request, unix_time_seconds()?, entropy)
236 }
237
238 pub fn protect_at_with_entropy(
245 &self,
246 request: &Request,
247 now_unix_s: u64,
248 entropy: &mut impl EntropySource,
249 ) -> Result<ProtectedRequest, Error> {
250 let mut ciphertext = compression::encode_request(request, self.limits, self.compression)?;
251 let header = encode_request_header(&self.recipient_key_id, &self.psk_id, now_unix_s)?;
252 let info = request_info(&header);
253 let psk_bundle = PskBundle::new(self.psk.as_slice(), &self.psk_id)
254 .map_err(|_| Error::InvalidConfiguration)?;
255 let mut random_seed = Zeroizing::new([0_u8; 32]);
256 entropy.fill(random_seed.as_mut())?;
257 let mut random = ChaCha20Rng::from_seed(*random_seed);
258 let (encapped_key, mut context) = setup_sender_with_rng::<HpkeAead, Kdf, Kem>(
259 &OpModeS::Psk(psk_bundle),
260 &self.recipient_public_key,
261 &info,
262 &mut random,
263 )
264 .map_err(|_| Error::CryptoFailure)?;
265 let enc = encapped_key.to_bytes();
266 let tag = context
267 .seal_inout_detached(InOutBuf::from(ciphertext.as_mut_slice()), &[])
268 .map_err(|_| Error::CryptoFailure)?;
269 ciphertext.extend_from_slice(tag.to_bytes().as_ref());
270 let mut response_material =
271 export_response_material(&context, enc.as_ref(), request.method, self.limits)?;
272 response_material.compression = self.compression;
273 let envelope = encode_request(&header, enc.as_ref(), &ciphertext)?;
274 Ok(ProtectedRequest {
275 envelope,
276 response_token: ResponseToken(response_material),
277 })
278 }
279}
280
281#[derive(Clone, Debug, Eq, PartialEq)]
283pub struct CredentialRequest {
284 pub psk_id: Vec<u8>,
286}
287
288#[must_use]
290pub struct PreparsedRequest {
291 pub credential: CredentialRequest,
293 pub token: StartToken,
295}
296
297#[must_use]
299pub struct StartToken {
300 envelope: Vec<u8>,
301}
302
303impl fmt::Debug for StartToken {
304 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
305 formatter
306 .debug_struct("StartToken")
307 .field("envelope_len", &self.envelope.len())
308 .finish()
309 }
310}
311
312#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
314pub struct ReplayRequest {
315 pub id: [u8; 32],
317 pub retain_until_exclusive: u64,
319}
320
321impl ReplayRequest {
322 #[must_use]
324 pub const fn decision(self, admitted: bool) -> ReplayDecision {
325 ReplayDecision {
326 id: self.id,
327 retain_until_exclusive: self.retain_until_exclusive,
328 admitted,
329 }
330 }
331}
332
333#[derive(Clone, Copy, Debug, Eq, PartialEq)]
335pub struct ReplayDecision {
336 id: [u8; 32],
337 retain_until_exclusive: u64,
338 admitted: bool,
339}
340
341#[must_use]
343pub struct AuthenticatedRequest {
344 pub replay: ReplayRequest,
346 pub token: ReplayToken,
348}
349
350#[must_use]
352pub struct ReplayToken {
353 replay_id: [u8; 32],
354 retain_until_exclusive: u64,
355 request: Request,
356 request_coding: Option<CompressionCoding>,
357 response_material: ResponseMaterial,
358}
359
360impl fmt::Debug for ReplayToken {
361 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
362 formatter
363 .debug_struct("ReplayToken")
364 .field("replay_id", &self.replay_id)
365 .field("retain_until_exclusive", &self.retain_until_exclusive)
366 .field("request", &"[REDACTED]")
367 .field("request_coding", &self.request_coding)
368 .field("response_material", &self.response_material)
369 .finish()
370 }
371}
372
373impl ReplayToken {
374 pub fn admit(self, decision: ReplayDecision) -> Result<OpenedRequest, Error> {
385 self.admit_at(decision, unix_time_seconds()?)
386 }
387
388 pub fn admit_at(
398 self,
399 decision: ReplayDecision,
400 now_unix_s: u64,
401 ) -> Result<OpenedRequest, Error> {
402 if self.replay_id != decision.id
403 || self.retain_until_exclusive != decision.retain_until_exclusive
404 {
405 return Err(Error::ReplayDecisionMismatch);
406 }
407 if !decision.admitted {
408 return Err(Error::ReplayRejected);
409 }
410 if now_unix_s >= self.retain_until_exclusive {
411 return Err(Error::InvalidRequestTime);
412 }
413 let request = compression::finish_request(
414 self.request,
415 self.request_coding,
416 self.response_material.limits,
417 )?;
418 Ok(OpenedRequest {
419 request,
420 response: ResponseCapability(self.response_material),
421 })
422 }
423}
424
425#[must_use]
427pub struct OpenedRequest {
428 pub request: Request,
430 pub response: ResponseCapability,
432}
433
434pub struct Server {
436 recipient_private_key: <Kem as KemTrait>::PrivateKey,
437 recipient_key_id: Vec<u8>,
438 limits: Limits,
439 compression_enabled: bool,
440}
441
442impl fmt::Debug for Server {
443 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
444 formatter
445 .debug_struct("Server")
446 .field("recipient_private_key", &"[REDACTED]")
447 .field("recipient_key_id_len", &self.recipient_key_id.len())
448 .field("limits", &self.limits)
449 .field("compression_enabled", &self.compression_enabled)
450 .finish()
451 }
452}
453
454impl Server {
455 pub fn new(
462 recipient_private_key: &[u8],
463 recipient_key_id: Vec<u8>,
464 limits: Limits,
465 ) -> Result<Self, Error> {
466 validate_id(&recipient_key_id)?;
467 let limits = limits.validate()?;
468 let recipient_private_key =
469 <Kem as KemTrait>::PrivateKey::from_bytes(recipient_private_key)
470 .map_err(|_| Error::InvalidConfiguration)?;
471 Ok(Self {
472 recipient_private_key,
473 recipient_key_id,
474 limits,
475 compression_enabled: false,
476 })
477 }
478
479 #[must_use]
482 pub fn with_compression(mut self) -> Self {
483 self.compression_enabled = true;
484 self
485 }
486
487 #[must_use]
489 pub fn public_key(&self) -> Vec<u8> {
490 Kem::sk_to_pk(&self.recipient_private_key)
491 .to_bytes()
492 .to_vec()
493 }
494
495 pub fn preparse(&self, envelope: &[u8]) -> Result<PreparsedRequest, Error> {
501 let credential = self.preparse_credential(envelope)?;
502 Ok(PreparsedRequest {
503 credential,
504 token: StartToken {
505 envelope: envelope.to_vec(),
506 },
507 })
508 }
509
510 pub fn preparse_owned(&self, envelope: Vec<u8>) -> Result<PreparsedRequest, Error> {
520 let credential = self.preparse_credential(&envelope)?;
521 Ok(PreparsedRequest {
522 credential,
523 token: StartToken { envelope },
524 })
525 }
526
527 fn preparse_credential(&self, envelope: &[u8]) -> Result<CredentialRequest, Error> {
528 let parsed = parse_request(envelope, self.limits)?;
529 if parsed.key_id != self.recipient_key_id {
530 return Err(Error::UnknownRecipientKey);
531 }
532 Ok(CredentialRequest {
533 psk_id: parsed.psk_id.to_vec(),
534 })
535 }
536
537 pub fn authenticate(
546 &self,
547 token: StartToken,
548 psk: &[u8],
549 ) -> Result<AuthenticatedRequest, Error> {
550 self.authenticate_at(token, psk, unix_time_seconds()?)
551 }
552
553 pub fn authenticate_at(
559 &self,
560 token: StartToken,
561 psk: &[u8],
562 now_unix_s: u64,
563 ) -> Result<AuthenticatedRequest, Error> {
564 let StartToken { mut envelope } = token;
565 let parsed = parse_request(&envelope, self.limits)?;
566 if parsed.key_id != self.recipient_key_id {
567 return Err(Error::UnknownRecipientKey);
568 }
569 validate_credential(psk, parsed.psk_id).map_err(|_| Error::InvalidCredential)?;
570 let encapped_key = <Kem as KemTrait>::EncappedKey::from_bytes(parsed.enc)
571 .map_err(|_| Error::AuthenticationFailed)?;
572 let psk_bundle =
573 PskBundle::new(psk, parsed.psk_id).map_err(|_| Error::InvalidCredential)?;
574 let info = request_info(parsed.header);
575 let mut context = setup_receiver::<HpkeAead, Kdf, Kem>(
576 &OpModeR::Psk(psk_bundle),
577 &self.recipient_private_key,
578 &encapped_key,
579 &info,
580 )
581 .map_err(|_| Error::AuthenticationFailed)?;
582 let ciphertext_start = envelope.len() - parsed.ciphertext.len();
583 let plaintext_end = envelope.len() - TAG_LEN;
584 let tag = AeadTag::<HpkeAead>::from_bytes(&envelope[plaintext_end..])
585 .map_err(|_| Error::AuthenticationFailed)?;
586 let replay_id = replay_id(parsed.header, parsed.enc);
587 let issued_at_unix_s = parsed.issued_at_unix_s;
588 let enc = parsed.enc.to_vec();
589 context
590 .open_inout_detached(
591 InOutBuf::from(&mut envelope[ciphertext_start..plaintext_end]),
592 &[],
593 &tag,
594 )
595 .map_err(|_| Error::AuthenticationFailed)?;
596 let (request, request_coding, response_coding) = compression::decode_request(
597 &envelope[ciphertext_start..plaintext_end],
598 self.limits,
599 self.compression_enabled,
600 )?;
601 let retain_until_exclusive = replay_retention_deadline(issued_at_unix_s, now_unix_s)?;
602 let mut response_material =
603 export_response_material(&context, &enc, request.method, self.limits)?;
604 response_material.compression = response_coding;
605 Ok(AuthenticatedRequest {
606 replay: ReplayRequest {
607 id: replay_id,
608 retain_until_exclusive,
609 },
610 token: ReplayToken {
611 replay_id,
612 retain_until_exclusive,
613 request,
614 request_coding,
615 response_material,
616 },
617 })
618 }
619}
620
621pub(crate) struct ResponseMaterial {
622 pub(crate) secret: Zeroizing<[u8; RESPONSE_SECRET_LEN]>,
623 pub(crate) enc: [u8; ENC_LEN],
624 pub(crate) method: Method,
625 pub(crate) limits: Limits,
626 pub(crate) compression: Option<CompressionCoding>,
627}
628
629impl fmt::Debug for ResponseMaterial {
630 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
631 formatter
632 .debug_struct("ResponseMaterial")
633 .field("secret", &"[REDACTED]")
634 .field("enc_len", &self.enc.len())
635 .field("method", &self.method)
636 .field("limits", &self.limits)
637 .field("compression", &self.compression)
638 .finish()
639 }
640}
641
642#[must_use]
644pub struct ResponseToken(ResponseMaterial);
645
646impl fmt::Debug for ResponseToken {
647 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
648 formatter
649 .debug_tuple("ResponseToken")
650 .field(&self.0)
651 .finish()
652 }
653}
654
655impl ResponseToken {
656 #[must_use]
658 pub fn into_opener(self) -> ResponseOpener {
659 ResponseOpener::new(self.0)
660 }
661
662 pub fn open_finite(self, envelope: &[u8]) -> Result<Response, Error> {
667 let mut opener = self.into_opener();
668 let mut offset = 0;
669 while offset < envelope.len() {
670 let (used, record) = opener.feed(&envelope[offset..])?;
671 offset += used;
672 if matches!(record, Some(ResponseRecord::Start(ref head)) if head.mode == ResponseMode::Sse)
673 {
674 return Err(Error::InvalidConfiguration);
675 }
676 if used == 0 {
677 return Err(Error::MalformedEnvelope);
678 }
679 }
680 opener.finish_eof()?.ok_or(Error::MalformedEnvelope)
681 }
682}
683
684#[must_use]
686pub struct ResponseCapability(ResponseMaterial);
687
688impl fmt::Debug for ResponseCapability {
689 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
690 formatter
691 .debug_tuple("ResponseCapability")
692 .field(&self.0)
693 .finish()
694 }
695}
696
697impl ResponseCapability {
698 pub fn into_sealer(
703 self,
704 status: u16,
705 headers: Vec<HeaderField>,
706 coding: Option<CompressionCoding>,
707 ) -> Result<(ResponseSealer, Vec<u8>), Error> {
708 self.into_sealer_with_entropy(status, headers, coding, &mut SystemEntropy)
709 }
710
711 pub fn into_sealer_with_entropy(
716 self,
717 status: u16,
718 headers: Vec<HeaderField>,
719 coding: Option<CompressionCoding>,
720 entropy: &mut impl EntropySource,
721 ) -> Result<(ResponseSealer, Vec<u8>), Error> {
722 ResponseSealer::new(&self.0, status, headers, coding, entropy)
723 }
724
725 pub fn protect_finite(self, response: &Response) -> Result<Vec<u8>, Error> {
730 self.protect_finite_with_entropy(response, &mut SystemEntropy)
731 }
732
733 pub fn protect_finite_with_entropy(
738 self,
739 response: &Response,
740 entropy: &mut impl EntropySource,
741 ) -> Result<Vec<u8>, Error> {
742 let (coding, compressed) = match self.0.compression {
743 Some(coding) if !response.body.is_empty() => {
744 let compressed = compression::maybe_compress(&response.body, coding)?;
745 (compressed.as_ref().map(|_| coding), compressed)
746 }
747 _ => (None, None),
748 };
749 let (mut sealer, mut output) = self.into_sealer_with_entropy(
750 response.status,
751 response.headers.clone(),
752 coding,
753 entropy,
754 )?;
755 if sealer.head_mode() != ResponseMode::Finite {
756 return Err(Error::InvalidConfiguration);
757 }
758 let data =
759 sealer.seal_finite_body_with_precompressed(&response.body, compressed.as_deref())?;
760 let end = sealer.finish()?;
761 output.reserve(data.as_ref().map_or(0, Vec::len) + end.len());
762 if let Some(data) = data {
763 output.extend_from_slice(&data);
764 }
765 output.extend_from_slice(&end);
766 Ok(output)
767 }
768}
769
770fn validate_id(identifier: &[u8]) -> Result<(), Error> {
771 if identifier.is_empty() || identifier.len() > HARD_MAX_ID_LEN {
772 return Err(Error::InvalidConfiguration);
773 }
774 Ok(())
775}
776
777fn validate_credential(psk: &[u8], psk_id: &[u8]) -> Result<(), Error> {
778 validate_id(psk_id)?;
779 if psk.len() < MIN_PSK_LEN || psk == psk_id {
780 return Err(Error::InvalidConfiguration);
781 }
782 Ok(())
783}
784
785fn unix_time_seconds() -> Result<u64, Error> {
786 SystemTime::now()
787 .duration_since(UNIX_EPOCH)
788 .map_err(|_| Error::ClockUnavailable)
789 .map(|duration| duration.as_secs())
790}
791
792fn replay_retention_deadline(issued_at_unix_s: u64, now_unix_s: u64) -> Result<u64, Error> {
793 let latest_issued_at = now_unix_s
794 .checked_add(CLOCK_SKEW_SECS)
795 .ok_or(Error::InvalidRequestTime)?;
796 let retain_until_exclusive = issued_at_unix_s
797 .checked_add(REQUEST_LIFETIME_SECS)
798 .and_then(|value| value.checked_add(CLOCK_SKEW_SECS))
799 .ok_or(Error::InvalidRequestTime)?;
800 if issued_at_unix_s > latest_issued_at || now_unix_s >= retain_until_exclusive {
801 return Err(Error::InvalidRequestTime);
802 }
803 Ok(retain_until_exclusive)
804}
805
806fn request_info(header: &[u8]) -> Vec<u8> {
807 let mut info = Vec::with_capacity(REQUEST_INFO_LABEL.len() + 1 + header.len());
808 info.extend_from_slice(REQUEST_INFO_LABEL);
809 info.push(0);
810 info.extend_from_slice(header);
811 info
812}
813
814fn replay_id(header: &[u8], enc: &[u8]) -> [u8; 32] {
815 let mut digest = Sha256::new();
816 digest.update(REPLAY_LABEL);
817 digest.update(header);
818 digest.update(enc);
819 digest.finalize().into()
820}
821
822fn export_response_material<C>(
823 context: &C,
824 enc: &[u8],
825 method: Method,
826 limits: Limits,
827) -> Result<ResponseMaterial, Error>
828where
829 C: Exporter,
830{
831 let enc: [u8; ENC_LEN] = enc.try_into().map_err(|_| Error::CryptoFailure)?;
832 let mut secret = Zeroizing::new([0_u8; RESPONSE_SECRET_LEN]);
833 context.export_secret(RESPONSE_EXPORT_LABEL, secret.as_mut())?;
834 Ok(ResponseMaterial {
835 secret,
836 enc,
837 method,
838 limits,
839 compression: None,
840 })
841}
842
843pub(crate) fn derive_response_key_nonce(
844 material: &ResponseMaterial,
845 response_nonce: &[u8],
846) -> Result<(ResponseKey, ResponseNonce), Error> {
847 if response_nonce.len() != RESPONSE_NONCE_LEN {
848 return Err(Error::MalformedEnvelope);
849 }
850 let mut salt = Zeroizing::new(Vec::with_capacity(ENC_LEN + RESPONSE_NONCE_LEN));
851 salt.extend_from_slice(&material.enc);
852 salt.extend_from_slice(response_nonce);
853 let hkdf = Hkdf::<Sha256>::new(Some(&salt), material.secret.as_ref());
854 let mut key = Zeroizing::new([0_u8; RESPONSE_KEY_LEN]);
855 hkdf.expand(RESPONSE_KEY_INFO, key.as_mut())
856 .map_err(|_| Error::CryptoFailure)?;
857 let mut nonce = Zeroizing::new([0_u8; AEAD_NONCE_LEN]);
858 hkdf.expand(RESPONSE_NONCE_INFO, nonce.as_mut())
859 .map_err(|_| Error::CryptoFailure)?;
860 Ok((key, nonce))
861}
862
863trait Exporter {
864 fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error>;
865}
866
867impl Exporter for hpke::aead::AeadCtxS<HpkeAead, Kdf, Kem> {
868 fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error> {
869 self.export(context, output)
870 .map_err(|_| Error::CryptoFailure)
871 }
872}
873
874impl Exporter for RecipientContext {
875 fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error> {
876 self.export(context, output)
877 .map_err(|_| Error::CryptoFailure)
878 }
879}