1use std::{
4 fmt,
5 time::{SystemTime, UNIX_EPOCH},
6};
7
8use chacha20poly1305::{
9 ChaCha20Poly1305,
10 aead::{Aead, AeadInOut, KeyInit, Payload, array::Array},
11};
12use hkdf::Hkdf;
13use hpke::{
14 Deserializable, Kem as KemTrait, OpModeR, OpModeS, PskBundle, Serializable,
15 aead::{AeadCtxR, AeadTag, ChaCha20Poly1305 as HpkeChaCha20Poly1305},
16 inout::InOutBuf,
17 kdf::HkdfSha256,
18 kem::X25519HkdfSha256,
19 setup_receiver, setup_sender_with_rng,
20};
21use rand_chacha::ChaCha20Rng;
22use rand_core::SeedableRng;
23use sha2::{Digest, Sha256};
24use zeroize::Zeroizing;
25
26use crate::{
27 CLOCK_SKEW_SECS, CompressionCoding, EntropySource, Error, HARD_MAX_ID_LEN, Limits, Method,
28 REQUEST_LIFETIME_SECS, SystemEntropy,
29 codec::{
30 ENC_LEN, RESPONSE_NONCE_LEN, TAG_LEN, encode_request, encode_request_header,
31 encode_response, parse_request, parse_response,
32 },
33 compression,
34 message::{Request, Response},
35};
36
37type Kem = X25519HkdfSha256;
38type Kdf = HkdfSha256;
39type HpkeAead = HpkeChaCha20Poly1305;
40type RecipientContext = AeadCtxR<HpkeAead, Kdf, Kem>;
41
42const REQUEST_INFO_LABEL: &[u8] = b"message/hpke-http request\0v1";
43const RESPONSE_EXPORT_LABEL: &[u8] = b"message/hpke-http response\0v1";
44const REPLAY_LABEL: &[u8] = b"hpke-http/replay\0v1\0";
45const RESPONSE_KEY_INFO: &[u8] = b"key";
46const RESPONSE_NONCE_INFO: &[u8] = b"nonce";
47const RESPONSE_SECRET_LEN: usize = 32;
48const RESPONSE_KEY_LEN: usize = 32;
49const AEAD_NONCE_LEN: usize = 12;
50const MIN_PSK_LEN: usize = 32;
51type ResponseKey = Zeroizing<[u8; RESPONSE_KEY_LEN]>;
52type ResponseNonce = Zeroizing<[u8; AEAD_NONCE_LEN]>;
53
54pub struct KeyPair {
56 private_key: Zeroizing<Vec<u8>>,
57 public_key: Vec<u8>,
58}
59
60impl fmt::Debug for KeyPair {
61 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
62 formatter
63 .debug_struct("KeyPair")
64 .field("private_key", &"[REDACTED]")
65 .field("public_key_len", &self.public_key.len())
66 .finish()
67 }
68}
69
70impl KeyPair {
71 #[must_use]
73 pub fn into_parts(self) -> (Zeroizing<Vec<u8>>, Vec<u8>) {
74 (self.private_key, self.public_key)
75 }
76
77 #[must_use]
79 pub fn public_key(&self) -> &[u8] {
80 &self.public_key
81 }
82}
83
84pub fn generate_key_pair() -> Result<KeyPair, Error> {
90 generate_key_pair_with_entropy(&mut SystemEntropy)
91}
92
93pub fn generate_key_pair_with_entropy(entropy: &mut impl EntropySource) -> Result<KeyPair, Error> {
99 let mut input_key_material = Zeroizing::new([0_u8; 32]);
100 entropy.fill(input_key_material.as_mut())?;
101 let (private_key, public_key) = Kem::derive_keypair(input_key_material.as_ref());
102 Ok(KeyPair {
103 private_key: Zeroizing::new(private_key.to_bytes().to_vec()),
104 public_key: public_key.to_bytes().to_vec(),
105 })
106}
107
108#[must_use]
110pub struct ProtectedRequest {
111 envelope: Vec<u8>,
112 response_token: ResponseToken,
113}
114
115impl fmt::Debug for ProtectedRequest {
116 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
117 formatter
118 .debug_struct("ProtectedRequest")
119 .field("envelope_len", &self.envelope.len())
120 .field("response_token", &self.response_token)
121 .finish()
122 }
123}
124
125impl ProtectedRequest {
126 #[must_use]
128 pub fn envelope(&self) -> &[u8] {
129 &self.envelope
130 }
131
132 pub fn into_parts(self) -> (Vec<u8>, ResponseToken) {
134 (self.envelope, self.response_token)
135 }
136}
137
138pub struct Client {
140 recipient_public_key: <Kem as KemTrait>::PublicKey,
141 recipient_key_id: Vec<u8>,
142 psk: Zeroizing<Vec<u8>>,
143 psk_id: Vec<u8>,
144 limits: Limits,
145 compression: Option<CompressionCoding>,
146}
147
148impl fmt::Debug for Client {
149 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
150 formatter
151 .debug_struct("Client")
152 .field("recipient_key_id_len", &self.recipient_key_id.len())
153 .field("psk", &"[REDACTED]")
154 .field("psk_id_len", &self.psk_id.len())
155 .field("limits", &self.limits)
156 .field("compression", &self.compression)
157 .finish_non_exhaustive()
158 }
159}
160
161impl Client {
162 pub fn new(
169 recipient_public_key: &[u8],
170 recipient_key_id: Vec<u8>,
171 psk: Vec<u8>,
172 psk_id: Vec<u8>,
173 limits: Limits,
174 ) -> Result<Self, Error> {
175 let psk = Zeroizing::new(psk);
176 validate_id(&recipient_key_id)?;
177 validate_credential(psk.as_slice(), &psk_id)?;
178 let limits = limits.validate()?;
179 let recipient_public_key = <Kem as KemTrait>::PublicKey::from_bytes(recipient_public_key)
180 .map_err(|_| Error::InvalidConfiguration)?;
181 Ok(Self {
182 recipient_public_key,
183 recipient_key_id,
184 psk,
185 psk_id,
186 limits,
187 compression: None,
188 })
189 }
190
191 #[must_use]
197 pub fn with_compression(mut self, coding: CompressionCoding) -> Self {
198 self.compression = Some(coding);
199 self
200 }
201
202 pub fn protect(&self, request: &Request) -> Result<ProtectedRequest, Error> {
209 self.protect_at_with_entropy(request, unix_time_seconds()?, &mut SystemEntropy)
210 }
211
212 pub fn protect_at(
221 &self,
222 request: &Request,
223 now_unix_s: u64,
224 ) -> Result<ProtectedRequest, Error> {
225 self.protect_at_with_entropy(request, now_unix_s, &mut SystemEntropy)
226 }
227
228 pub fn protect_with_entropy(
235 &self,
236 request: &Request,
237 entropy: &mut impl EntropySource,
238 ) -> Result<ProtectedRequest, Error> {
239 self.protect_at_with_entropy(request, unix_time_seconds()?, entropy)
240 }
241
242 pub fn protect_at_with_entropy(
249 &self,
250 request: &Request,
251 now_unix_s: u64,
252 entropy: &mut impl EntropySource,
253 ) -> Result<ProtectedRequest, Error> {
254 let mut ciphertext = compression::encode_request(request, self.limits, self.compression)?;
255 let header = encode_request_header(&self.recipient_key_id, &self.psk_id, now_unix_s)?;
256 let info = request_info(&header);
257 let psk_bundle = PskBundle::new(self.psk.as_slice(), &self.psk_id)
258 .map_err(|_| Error::InvalidConfiguration)?;
259 let mut random_seed = Zeroizing::new([0_u8; 32]);
260 entropy.fill(random_seed.as_mut())?;
261 let mut random = ChaCha20Rng::from_seed(*random_seed);
262 let (encapped_key, mut context) = setup_sender_with_rng::<HpkeAead, Kdf, Kem>(
263 &OpModeS::Psk(psk_bundle),
264 &self.recipient_public_key,
265 &info,
266 &mut random,
267 )
268 .map_err(|_| Error::CryptoFailure)?;
269 let enc = encapped_key.to_bytes();
270 let tag = context
271 .seal_inout_detached(InOutBuf::from(ciphertext.as_mut_slice()), &[])
272 .map_err(|_| Error::CryptoFailure)?;
273 ciphertext.extend_from_slice(tag.to_bytes().as_ref());
274 let mut response_material =
275 export_response_material(&context, enc.as_ref(), request.method, self.limits)?;
276 response_material.compression = self.compression;
277 let envelope = encode_request(&header, enc.as_ref(), &ciphertext)?;
278 Ok(ProtectedRequest {
279 envelope,
280 response_token: ResponseToken(response_material),
281 })
282 }
283}
284
285#[derive(Clone, Debug, Eq, PartialEq)]
287pub struct CredentialRequest {
288 pub psk_id: Vec<u8>,
290}
291
292#[must_use]
294pub struct PreparsedRequest {
295 pub credential: CredentialRequest,
297 pub token: StartToken,
299}
300
301#[must_use]
303pub struct StartToken {
304 envelope: Vec<u8>,
305}
306
307impl fmt::Debug for StartToken {
308 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
309 formatter
310 .debug_struct("StartToken")
311 .field("envelope_len", &self.envelope.len())
312 .finish()
313 }
314}
315
316#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
318pub struct ReplayRequest {
319 pub id: [u8; 32],
321 pub retain_until_exclusive: u64,
323}
324
325impl ReplayRequest {
326 #[must_use]
328 pub const fn decision(self, admitted: bool) -> ReplayDecision {
329 ReplayDecision {
330 id: self.id,
331 retain_until_exclusive: self.retain_until_exclusive,
332 admitted,
333 }
334 }
335}
336
337#[derive(Clone, Copy, Debug, Eq, PartialEq)]
339pub struct ReplayDecision {
340 id: [u8; 32],
341 retain_until_exclusive: u64,
342 admitted: bool,
343}
344
345#[must_use]
347pub struct AuthenticatedRequest {
348 pub replay: ReplayRequest,
350 pub token: ReplayToken,
352}
353
354#[must_use]
356pub struct ReplayToken {
357 replay_id: [u8; 32],
358 retain_until_exclusive: u64,
359 request: Request,
360 request_coding: Option<CompressionCoding>,
361 response_material: ResponseMaterial,
362}
363
364impl fmt::Debug for ReplayToken {
365 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
366 formatter
367 .debug_struct("ReplayToken")
368 .field("replay_id", &self.replay_id)
369 .field("retain_until_exclusive", &self.retain_until_exclusive)
370 .field("request", &"[REDACTED]")
371 .field("request_coding", &self.request_coding)
372 .field("response_material", &self.response_material)
373 .finish()
374 }
375}
376
377impl ReplayToken {
378 pub fn admit(self, decision: ReplayDecision) -> Result<OpenedRequest, Error> {
389 self.admit_at(decision, unix_time_seconds()?)
390 }
391
392 pub fn admit_at(
402 self,
403 decision: ReplayDecision,
404 now_unix_s: u64,
405 ) -> Result<OpenedRequest, Error> {
406 if self.replay_id != decision.id
407 || self.retain_until_exclusive != decision.retain_until_exclusive
408 {
409 return Err(Error::ReplayDecisionMismatch);
410 }
411 if !decision.admitted {
412 return Err(Error::ReplayRejected);
413 }
414 if now_unix_s >= self.retain_until_exclusive {
415 return Err(Error::InvalidRequestTime);
416 }
417 let request = compression::finish_request(
418 self.request,
419 self.request_coding,
420 self.response_material.limits,
421 )?;
422 Ok(OpenedRequest {
423 request,
424 response: ResponseCapability(self.response_material),
425 })
426 }
427}
428
429#[must_use]
431pub struct OpenedRequest {
432 pub request: Request,
434 pub response: ResponseCapability,
436}
437
438pub struct Server {
440 recipient_private_key: <Kem as KemTrait>::PrivateKey,
441 recipient_key_id: Vec<u8>,
442 limits: Limits,
443 compression_enabled: bool,
444}
445
446impl fmt::Debug for Server {
447 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
448 formatter
449 .debug_struct("Server")
450 .field("recipient_private_key", &"[REDACTED]")
451 .field("recipient_key_id_len", &self.recipient_key_id.len())
452 .field("limits", &self.limits)
453 .field("compression_enabled", &self.compression_enabled)
454 .finish()
455 }
456}
457
458impl Server {
459 pub fn new(
466 recipient_private_key: &[u8],
467 recipient_key_id: Vec<u8>,
468 limits: Limits,
469 ) -> Result<Self, Error> {
470 validate_id(&recipient_key_id)?;
471 let limits = limits.validate()?;
472 let recipient_private_key =
473 <Kem as KemTrait>::PrivateKey::from_bytes(recipient_private_key)
474 .map_err(|_| Error::InvalidConfiguration)?;
475 Ok(Self {
476 recipient_private_key,
477 recipient_key_id,
478 limits,
479 compression_enabled: false,
480 })
481 }
482
483 #[must_use]
486 pub fn with_compression(mut self) -> Self {
487 self.compression_enabled = true;
488 self
489 }
490
491 pub fn preparse(&self, envelope: &[u8]) -> Result<PreparsedRequest, Error> {
497 let credential = self.preparse_credential(envelope)?;
498 Ok(PreparsedRequest {
499 credential,
500 token: StartToken {
501 envelope: envelope.to_vec(),
502 },
503 })
504 }
505
506 pub fn preparse_owned(&self, envelope: Vec<u8>) -> Result<PreparsedRequest, Error> {
516 let credential = self.preparse_credential(&envelope)?;
517 Ok(PreparsedRequest {
518 credential,
519 token: StartToken { envelope },
520 })
521 }
522
523 fn preparse_credential(&self, envelope: &[u8]) -> Result<CredentialRequest, Error> {
524 let parsed = parse_request(envelope, self.limits)?;
525 if parsed.key_id != self.recipient_key_id {
526 return Err(Error::UnknownRecipientKey);
527 }
528 Ok(CredentialRequest {
529 psk_id: parsed.psk_id.to_vec(),
530 })
531 }
532
533 pub fn authenticate(
542 &self,
543 token: StartToken,
544 psk: &[u8],
545 ) -> Result<AuthenticatedRequest, Error> {
546 self.authenticate_at(token, psk, unix_time_seconds()?)
547 }
548
549 pub fn authenticate_at(
555 &self,
556 token: StartToken,
557 psk: &[u8],
558 now_unix_s: u64,
559 ) -> Result<AuthenticatedRequest, Error> {
560 let StartToken { mut envelope } = token;
561 let parsed = parse_request(&envelope, self.limits)?;
562 if parsed.key_id != self.recipient_key_id {
563 return Err(Error::UnknownRecipientKey);
564 }
565 validate_credential(psk, parsed.psk_id).map_err(|_| Error::InvalidCredential)?;
566 let encapped_key = <Kem as KemTrait>::EncappedKey::from_bytes(parsed.enc)
567 .map_err(|_| Error::AuthenticationFailed)?;
568 let psk_bundle =
569 PskBundle::new(psk, parsed.psk_id).map_err(|_| Error::InvalidCredential)?;
570 let info = request_info(parsed.header);
571 let mut context = setup_receiver::<HpkeAead, Kdf, Kem>(
572 &OpModeR::Psk(psk_bundle),
573 &self.recipient_private_key,
574 &encapped_key,
575 &info,
576 )
577 .map_err(|_| Error::AuthenticationFailed)?;
578 let ciphertext_start = envelope.len() - parsed.ciphertext.len();
579 let plaintext_end = envelope.len() - TAG_LEN;
580 let tag = AeadTag::<HpkeAead>::from_bytes(&envelope[plaintext_end..])
581 .map_err(|_| Error::AuthenticationFailed)?;
582 let replay_id = replay_id(parsed.header, parsed.enc);
583 let issued_at_unix_s = parsed.issued_at_unix_s;
584 let enc = parsed.enc.to_vec();
585 context
586 .open_inout_detached(
587 InOutBuf::from(&mut envelope[ciphertext_start..plaintext_end]),
588 &[],
589 &tag,
590 )
591 .map_err(|_| Error::AuthenticationFailed)?;
592 let (request, request_coding, response_coding) = compression::decode_request(
593 &envelope[ciphertext_start..plaintext_end],
594 self.limits,
595 self.compression_enabled,
596 )?;
597 let retain_until_exclusive = replay_retention_deadline(issued_at_unix_s, now_unix_s)?;
598 let mut response_material =
599 export_response_material(&context, &enc, request.method, self.limits)?;
600 response_material.compression = response_coding;
601 Ok(AuthenticatedRequest {
602 replay: ReplayRequest {
603 id: replay_id,
604 retain_until_exclusive,
605 },
606 token: ReplayToken {
607 replay_id,
608 retain_until_exclusive,
609 request,
610 request_coding,
611 response_material,
612 },
613 })
614 }
615}
616
617struct ResponseMaterial {
618 secret: Zeroizing<[u8; RESPONSE_SECRET_LEN]>,
619 enc: [u8; ENC_LEN],
620 method: Method,
621 limits: Limits,
622 compression: Option<CompressionCoding>,
623}
624
625impl fmt::Debug for ResponseMaterial {
626 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
627 formatter
628 .debug_struct("ResponseMaterial")
629 .field("secret", &"[REDACTED]")
630 .field("enc_len", &self.enc.len())
631 .field("method", &self.method)
632 .field("limits", &self.limits)
633 .field("compression", &self.compression)
634 .finish()
635 }
636}
637
638#[must_use]
640pub struct ResponseToken(ResponseMaterial);
641
642impl fmt::Debug for ResponseToken {
643 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
644 formatter
645 .debug_tuple("ResponseToken")
646 .field(&self.0)
647 .finish()
648 }
649}
650
651impl ResponseToken {
652 pub fn open(self, envelope: &[u8]) -> Result<Response, Error> {
658 let parsed = parse_response(envelope, self.0.limits)?;
659 let (key, nonce) = derive_response_key_nonce(&self.0, parsed.nonce)?;
660 let cipher = ChaCha20Poly1305::new(&Array(*key));
661 let plaintext = cipher
662 .decrypt(
663 &Array(*nonce),
664 Payload {
665 msg: parsed.ciphertext,
666 aad: &[],
667 },
668 )
669 .map_err(|_| Error::AuthenticationFailed)?;
670 let response = compression::decode_response(
671 &plaintext,
672 self.0.method,
673 self.0.limits,
674 self.0.compression,
675 )?;
676 validate_response_body(self.0.method, &response).map_err(|_| Error::MalformedEnvelope)?;
677 Ok(response)
678 }
679}
680
681#[must_use]
683pub struct ResponseCapability(ResponseMaterial);
684
685impl fmt::Debug for ResponseCapability {
686 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
687 formatter
688 .debug_tuple("ResponseCapability")
689 .field(&self.0)
690 .finish()
691 }
692}
693
694impl ResponseCapability {
695 pub fn protect(self, response: &Response) -> Result<Vec<u8>, Error> {
701 self.protect_with_entropy(response, &mut SystemEntropy)
702 }
703
704 pub fn protect_with_entropy(
711 self,
712 response: &Response,
713 entropy: &mut impl EntropySource,
714 ) -> Result<Vec<u8>, Error> {
715 validate_response_body(self.0.method, response)?;
716 let mut ciphertext = compression::encode_response(
717 response,
718 self.0.method,
719 self.0.limits,
720 self.0.compression,
721 )?;
722 let mut response_nonce = [0_u8; RESPONSE_NONCE_LEN];
723 entropy.fill(&mut response_nonce)?;
724 let (key, nonce) = derive_response_key_nonce(&self.0, &response_nonce)?;
725 let cipher = ChaCha20Poly1305::new(&Array(*key));
726 let tag = cipher
727 .encrypt_inout_detached(
728 &Array(*nonce),
729 &[],
730 InOutBuf::from(ciphertext.as_mut_slice()),
731 )
732 .map_err(|_| Error::CryptoFailure)?;
733 ciphertext.extend_from_slice(&tag);
734 encode_response(&response_nonce, &ciphertext)
735 }
736}
737
738fn validate_id(identifier: &[u8]) -> Result<(), Error> {
739 if identifier.is_empty() || identifier.len() > HARD_MAX_ID_LEN {
740 return Err(Error::InvalidConfiguration);
741 }
742 Ok(())
743}
744
745fn validate_credential(psk: &[u8], psk_id: &[u8]) -> Result<(), Error> {
746 validate_id(psk_id)?;
747 if psk.len() < MIN_PSK_LEN || psk == psk_id {
748 return Err(Error::InvalidConfiguration);
749 }
750 Ok(())
751}
752
753fn unix_time_seconds() -> Result<u64, Error> {
754 SystemTime::now()
755 .duration_since(UNIX_EPOCH)
756 .map_err(|_| Error::ClockUnavailable)
757 .map(|duration| duration.as_secs())
758}
759
760fn replay_retention_deadline(issued_at_unix_s: u64, now_unix_s: u64) -> Result<u64, Error> {
761 let latest_issued_at = now_unix_s
762 .checked_add(CLOCK_SKEW_SECS)
763 .ok_or(Error::InvalidRequestTime)?;
764 let retain_until_exclusive = issued_at_unix_s
765 .checked_add(REQUEST_LIFETIME_SECS)
766 .and_then(|value| value.checked_add(CLOCK_SKEW_SECS))
767 .ok_or(Error::InvalidRequestTime)?;
768 if issued_at_unix_s > latest_issued_at || now_unix_s >= retain_until_exclusive {
769 return Err(Error::InvalidRequestTime);
770 }
771 Ok(retain_until_exclusive)
772}
773
774fn request_info(header: &[u8]) -> Vec<u8> {
775 let mut info = Vec::with_capacity(REQUEST_INFO_LABEL.len() + 1 + header.len());
776 info.extend_from_slice(REQUEST_INFO_LABEL);
777 info.push(0);
778 info.extend_from_slice(header);
779 info
780}
781
782fn replay_id(header: &[u8], enc: &[u8]) -> [u8; 32] {
783 let mut digest = Sha256::new();
784 digest.update(REPLAY_LABEL);
785 digest.update(header);
786 digest.update(enc);
787 digest.finalize().into()
788}
789
790fn export_response_material<C>(
791 context: &C,
792 enc: &[u8],
793 method: Method,
794 limits: Limits,
795) -> Result<ResponseMaterial, Error>
796where
797 C: Exporter,
798{
799 let enc: [u8; ENC_LEN] = enc.try_into().map_err(|_| Error::CryptoFailure)?;
800 let mut secret = Zeroizing::new([0_u8; RESPONSE_SECRET_LEN]);
801 context.export_secret(RESPONSE_EXPORT_LABEL, secret.as_mut())?;
802 Ok(ResponseMaterial {
803 secret,
804 enc,
805 method,
806 limits,
807 compression: None,
808 })
809}
810
811fn validate_response_body(method: Method, response: &Response) -> Result<(), Error> {
812 if (!response.body.is_empty())
813 && (method == Method::Head || matches!(response.status, 204 | 205 | 304))
814 {
815 return Err(Error::InvalidConfiguration);
816 }
817 Ok(())
818}
819
820fn derive_response_key_nonce(
821 material: &ResponseMaterial,
822 response_nonce: &[u8],
823) -> Result<(ResponseKey, ResponseNonce), Error> {
824 if response_nonce.len() != RESPONSE_NONCE_LEN {
825 return Err(Error::MalformedEnvelope);
826 }
827 let mut salt = Zeroizing::new(Vec::with_capacity(ENC_LEN + RESPONSE_NONCE_LEN));
828 salt.extend_from_slice(&material.enc);
829 salt.extend_from_slice(response_nonce);
830 let hkdf = Hkdf::<Sha256>::new(Some(&salt), material.secret.as_ref());
831 let mut key = Zeroizing::new([0_u8; RESPONSE_KEY_LEN]);
832 hkdf.expand(RESPONSE_KEY_INFO, key.as_mut())
833 .map_err(|_| Error::CryptoFailure)?;
834 let mut nonce = Zeroizing::new([0_u8; AEAD_NONCE_LEN]);
835 hkdf.expand(RESPONSE_NONCE_INFO, nonce.as_mut())
836 .map_err(|_| Error::CryptoFailure)?;
837 Ok((key, nonce))
838}
839
840trait Exporter {
841 fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error>;
842}
843
844impl Exporter for hpke::aead::AeadCtxS<HpkeAead, Kdf, Kem> {
845 fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error> {
846 self.export(context, output)
847 .map_err(|_| Error::CryptoFailure)
848 }
849}
850
851impl Exporter for RecipientContext {
852 fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error> {
853 self.export(context, output)
854 .map_err(|_| Error::CryptoFailure)
855 }
856}