1use std::fmt;
12use std::str::FromStr;
13
14use crate::cbor::{self, Value};
15use crate::frame::{
16 self, Sealed, StreamEncoding, StreamFields, VerifiedReply, VerifiedRequest, VerifiedStreamFrame,
17};
18use crate::profile::Profile;
19use crate::record::CLOCK_TOLERANCE_MS;
20use crate::seal::{self, Direction, Keyring, Parties, KEY_ID_SIZE, NONCE_SIZE};
21
22use super::LinkError;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum Confidentiality {
40 #[default]
41 Preferred,
42 Required,
43 Off,
44}
45
46impl FromStr for Confidentiality {
47 type Err = String;
48
49 fn from_str(s: &str) -> Result<Self, String> {
52 match s {
53 "" | "preferred" => Ok(Confidentiality::Preferred),
54 "required" => Ok(Confidentiality::Required),
55 "off" => Ok(Confidentiality::Off),
56 other => Err(format!(
57 "confidential is preferred, required or off, not {other:?}"
58 )),
59 }
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum Seal {
66 Clear,
71 To(Vec<u8>),
74}
75
76pub(super) const CODE_SEALED_REFUSED: &str = "sealed_refused";
79pub(super) const CODE_SEALED_REQUIRED: &str = "sealed_required";
80
81pub(super) const MAX_ADVERTISEMENT_TTL_MS: i64 = 5 * 60 * 1000;
84
85pub(super) const NO_KEY_DETAIL: &str = "this node opens no sealed payload";
87
88const CLEAR_REFUSALS: &[&str] = &[
93 "expired",
94 "not_yet_valid",
95 "request_id_reused",
96 "request_copy",
97 "reply_not_kept",
98 "caller_quota",
99 "share_full",
100 "admission_full",
101 "too_many_sessions",
102 "unavailable",
103];
104
105pub fn is_clear_refusal(code: &str) -> bool {
107 CLEAR_REFUSALS.contains(&code)
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum ConfidentialityReason {
114 NoKemKey,
117 KeyMismatch,
120 ReplyNotOpened,
123 NoSignedState,
126}
127
128impl ConfidentialityReason {
129 pub fn name(self) -> &'static str {
131 match self {
132 ConfidentialityReason::NoKemKey => "no_kem_key",
133 ConfidentialityReason::KeyMismatch => "key_mismatch",
134 ConfidentialityReason::ReplyNotOpened => "reply_not_opened",
135 ConfidentialityReason::NoSignedState => "no_signed_state",
136 }
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct ConfidentialityError {
146 pub reason: ConfidentialityReason,
147 pub advertised: Vec<[u8; KEY_ID_SIZE]>,
148 pub named: Option<[u8; KEY_ID_SIZE]>,
149}
150
151impl ConfidentialityError {
152 pub fn new(reason: ConfidentialityReason) -> ConfidentialityError {
154 ConfidentialityError {
155 reason,
156 advertised: Vec::new(),
157 named: None,
158 }
159 }
160}
161
162impl fmt::Display for ConfidentialityError {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 write!(f, "confidentiality: {}", self.reason.name())?;
165 for id in &self.advertised {
166 write!(f, " {}", hex(id))?;
167 }
168 if let Some(named) = &self.named {
169 write!(f, " (the provider named {})", hex(named))?;
170 }
171 Ok(())
172 }
173}
174
175fn hex(id: &[u8]) -> String {
176 id.iter().map(|b| format!("{b:02x}")).collect()
177}
178
179fn confidentiality(reason: ConfidentialityReason) -> LinkError {
180 LinkError::Confidentiality(ConfidentialityError::new(reason))
181}
182
183pub(super) fn stated(
187 target: &[u8; 32],
188 station: &[u8; 32],
189 seal: &Option<Seal>,
190) -> Result<(), LinkError> {
191 match seal {
192 None if target != &[0; 32] && target != station => {
193 Err(confidentiality(ConfidentialityReason::NoSignedState))
194 }
195 _ => Ok(()),
196 }
197}
198
199#[derive(Clone)]
203pub(super) struct CallSeal {
204 pub(super) key_id: [u8; KEY_ID_SIZE],
205 k_rep: [u8; 32],
206 pub(super) k_c2p: [u8; 32],
207 pub(super) k_p2c: [u8; 32],
208 request: seal::Request,
209}
210
211impl fmt::Debug for CallSeal {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 write!(f, "CallSeal(key id {})", hex(&self.key_id))
214 }
215}
216
217#[allow(clippy::too_many_arguments)]
221pub(super) fn sealed_request(
222 profile: Profile,
223 to: &[u8],
224 frame_type: &str,
225 realm: [u8; 32],
226 procedure: &str,
227 caller: [u8; 32],
228 target: [u8; 32],
229 request_id: [u8; 16],
230 deadline: u64,
231 payload: &Value,
232) -> Result<(Sealed, CallSeal), LinkError> {
233 let key = seal::parse_public_key(profile, to)
234 .map_err(|_| confidentiality(ConfidentialityReason::NoKemKey))?;
235 frame::check_payload(payload)?;
236 let (secret, kem_ct) =
237 seal::sender_secret(&key).map_err(|_| confidentiality(ConfidentialityReason::NoKemKey))?;
238 let parties = Parties {
239 request_id,
240 caller,
241 target,
242 };
243 let (k_req, k_rep) = seal::call_keys(&secret, frame_type, &parties);
244 let (k_c2p, k_p2c) = seal::stream_keys(&secret, &parties);
245 let request = seal::Request {
246 frame_type: frame_type.to_string(),
247 realm,
248 procedure: procedure.to_string(),
249 caller,
250 target,
251 request_id,
252 deadline,
253 };
254 let plain = cbor::encode(payload)
255 .map_err(|e| LinkError::Frame(frame::FrameError::Payload(e.to_string())))?;
256 let key_id = key.key_id();
257 let sealed = Sealed {
258 key_id,
259 kem_ct: Some(kem_ct),
260 nonce: None,
261 ct: seal::seal(
262 &k_req,
263 &[0; NONCE_SIZE],
264 &seal::request_aad(&request),
265 &plain,
266 ),
267 };
268 Ok((
269 sealed,
270 CallSeal {
271 key_id,
272 k_rep,
273 k_c2p,
274 k_p2c,
275 request,
276 },
277 ))
278}
279
280pub(super) fn refused_key(detail: Option<&str>) -> Option<[u8; KEY_ID_SIZE]> {
283 let detail = detail?;
284 let lowercase_hex = detail.len() == 2 * KEY_ID_SIZE
285 && detail
286 .bytes()
287 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b));
288 if !lowercase_hex {
289 return None;
290 }
291 let mut id = [0; KEY_ID_SIZE];
292 for (i, byte) in id.iter_mut().enumerate() {
293 *byte = u8::from_str_radix(&detail[2 * i..2 * i + 2], 16).ok()?;
294 }
295 Some(id)
296}
297
298pub(super) fn reply_outcome(
304 reply: VerifiedReply,
305 request: &VerifiedRequest,
306 seal: Option<&CallSeal>,
307) -> Result<Value, LinkError> {
308 let provider = |code: String, detail: Option<String>| LinkError::Provider {
309 responded_by: reply.responded_by,
310 code,
311 detail,
312 };
313 let Some(s) = seal else {
314 return match (reply.sealed.is_some(), reply.frame_type) {
315 (true, _) => Err(provider(CODE_SEALED_REFUSED.into(), None)),
316 (false, frame::ReplyType::Result) => Ok(reply.payload.unwrap_or(Value::Null)),
317 (false, frame::ReplyType::Error) => {
318 Err(provider(reply.code.unwrap_or_default(), reply.detail))
319 }
320 };
321 };
322 let not_opened = || confidentiality(ConfidentialityReason::ReplyNotOpened);
323 match (&reply.sealed, reply.frame_type, reply.code.as_deref()) {
324 (Some(sealed), frame_type, _) => {
325 if sealed.key_id != s.key_id {
328 return Err(not_opened());
329 }
330 let nonce: [u8; NONCE_SIZE] = sealed
331 .nonce
332 .as_deref()
333 .and_then(|n| n.try_into().ok())
334 .ok_or_else(not_opened)?;
335 let name = match frame_type {
336 frame::ReplyType::Result => seal::FRAME_RESULT,
337 frame::ReplyType::Error => seal::FRAME_ERROR,
338 };
339 let aad = seal::reply_aad(&s.request, name, &request.request_hash, &reply.responded_by);
340 let plain = seal::open(&s.k_rep, &nonce, &aad, &sealed.ct).map_err(|_| not_opened())?;
341 match frame_type {
342 frame::ReplyType::Result => cbor::decode(&plain).map_err(|_| not_opened()),
343 frame::ReplyType::Error => {
344 let (code, detail) =
345 seal::open_error_plain(&plain).map_err(|_| not_opened())?;
346 Err(provider(code, detail))
347 }
348 }
349 }
350 (None, frame::ReplyType::Error, Some(CODE_SEALED_REFUSED)) => {
351 Err(LinkError::SealedRefused {
352 named: refused_key(reply.detail.as_deref()),
353 })
354 }
355 (None, frame::ReplyType::Error, Some(code)) if is_clear_refusal(code) => {
356 Err(provider(code.to_string(), reply.detail.clone()))
357 }
358 _ => Err(LinkError::ClearAnswerToSealed),
359 }
360}
361
362pub(super) fn clear_allowed(
369 confidential: Confidentiality,
370 keyed_since: Option<i64>,
371 now_ms: i64,
372) -> bool {
373 match (confidential, keyed_since) {
374 (Confidentiality::Required, _) => false,
375 (_, None) => true,
376 (_, Some(since)) => now_ms <= since + MAX_ADVERTISEMENT_TTL_MS + CLOCK_TOLERANCE_MS as i64,
377 }
378}
379
380pub(super) fn opened_request(
384 keyring: Option<&Keyring>,
385 request: &VerifiedRequest,
386) -> Result<(Value, CallSeal), String> {
387 let Some(keyring) = keyring else {
388 return Err(NO_KEY_DETAIL.into());
389 };
390 let refused = hex(&keyring.current_id());
391 let Some(sealed) = &request.sealed else {
392 return Err(refused);
393 };
394 let key = keyring
395 .find(&sealed.key_id)
396 .ok_or_else(|| refused.clone())?;
397 let kem_ct = sealed.kem_ct.as_deref().ok_or_else(|| refused.clone())?;
398 let secret = seal::recipient_secret(&key, kem_ct).map_err(|_| refused.clone())?;
399 let frame_type = match request.frame_type {
400 frame::RequestType::Call => seal::FRAME_CALL,
401 frame::RequestType::StreamOpen => seal::FRAME_STREAM_OPEN,
402 };
403 let parties = Parties {
404 request_id: request.request_id,
405 caller: request.caller,
406 target: request.target,
407 };
408 let (k_req, k_rep) = seal::call_keys(&secret, frame_type, &parties);
409 let (k_c2p, k_p2c) = seal::stream_keys(&secret, &parties);
410 let bound = seal::Request {
411 frame_type: frame_type.to_string(),
412 realm: request.realm,
413 procedure: request.procedure.clone(),
414 caller: request.caller,
415 target: request.target,
416 request_id: request.request_id,
417 deadline: request.deadline,
418 };
419 let plain = seal::open(
420 &k_req,
421 &[0; NONCE_SIZE],
422 &seal::request_aad(&bound),
423 &sealed.ct,
424 )
425 .map_err(|_| refused.clone())?;
426 let payload = cbor::decode(&plain).map_err(|_| refused)?;
427 Ok((
428 payload,
429 CallSeal {
430 key_id: sealed.key_id,
431 k_rep,
432 k_c2p,
433 k_p2c,
434 request: bound,
435 },
436 ))
437}
438
439impl CallSeal {
440 pub(super) fn sealed_answer(
444 &self,
445 frame_type: &str,
446 plain: &[u8],
447 request_hash: &[u8; 48],
448 responded_by: &[u8; 32],
449 ) -> Result<Sealed, LinkError> {
450 let nonce = seal::random_nonce().map_err(|_| LinkError::Io("no randomness".into()))?;
451 let aad = seal::reply_aad(&self.request, frame_type, request_hash, responded_by);
452 Ok(Sealed {
453 key_id: self.key_id,
454 kem_ct: None,
455 nonce: Some(nonce.to_vec()),
456 ct: seal::seal(&self.k_rep, &nonce, &aad, plain),
457 })
458 }
459}
460
461const MAX_SEALED_PROVIDER_FRAMES: u64 = 1 << 32;
464
465#[derive(Clone)]
471pub(super) struct StreamSeal {
472 key_id: [u8; KEY_ID_SIZE],
473 request_id: [u8; 16],
474 caller: bool,
475 send: [u8; 32],
476 recv: [u8; 32],
477}
478
479impl fmt::Debug for StreamSeal {
480 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
481 write!(f, "StreamSeal(key id {})", hex(&self.key_id))
482 }
483}
484
485pub(super) struct StreamPlain {
487 frame_type: &'static str,
488 plain: Vec<u8>,
489}
490
491impl StreamSeal {
492 pub(super) fn caller(s: &CallSeal) -> StreamSeal {
494 StreamSeal {
495 key_id: s.key_id,
496 request_id: s.request.request_id,
497 caller: true,
498 send: s.k_c2p,
499 recv: s.k_p2c,
500 }
501 }
502
503 pub(super) fn key_id(&self) -> [u8; KEY_ID_SIZE] {
505 self.key_id
506 }
507
508 pub(super) fn provider(s: &CallSeal) -> StreamSeal {
510 StreamSeal {
511 key_id: s.key_id,
512 request_id: s.request.request_id,
513 caller: false,
514 send: s.k_p2c,
515 recv: s.k_c2p,
516 }
517 }
518
519 fn directions(&self) -> (Direction, Direction) {
520 match self.caller {
521 true => (Direction::CallerToProvider, Direction::ProviderToCaller),
522 false => (Direction::ProviderToCaller, Direction::CallerToProvider),
523 }
524 }
525
526 pub(super) fn plain_of(&self, fields: &StreamFields) -> Result<Option<StreamPlain>, LinkError> {
532 let (frame_type, plain) = match fields {
533 StreamFields::Data {
534 encoding: StreamEncoding::Raw,
535 body: Value::Bytes(b),
536 ..
537 } => ("stream_data", b.clone()),
538 StreamFields::Data {
539 encoding: StreamEncoding::Raw,
540 ..
541 } => {
542 return Err(LinkError::Frame(frame::FrameError::OutOfRange(
543 "a raw body that is not a byte string".into(),
544 )))
545 }
546 StreamFields::Data { body, .. } => {
547 frame::check_payload(body)?;
548 ("stream_data", encoded(body)?)
549 }
550 StreamFields::Reply { payload, .. } => {
551 frame::check_payload(payload)?;
552 ("stream_reply", encoded(payload)?)
553 }
554 StreamFields::Error { code, message, .. } => {
555 ("stream_error", seal::error_plain(code, message))
556 }
557 _ => return Ok(None),
558 };
559 if !self.caller && fields.seq() >= MAX_SEALED_PROVIDER_FRAMES {
560 return Err(LinkError::SealedFramesExhausted);
561 }
562 Ok(Some(StreamPlain { frame_type, plain }))
563 }
564
565 pub(super) fn sealed(
570 &self,
571 fields: StreamFields,
572 p: StreamPlain,
573 ) -> Result<StreamFields, LinkError> {
574 let seq = fields.seq();
575 let (nonce, carried) = match self.caller {
576 true => (seal::stream_nonce(seq), None),
577 false => {
578 let nonce =
579 seal::random_nonce().map_err(|_| LinkError::Io("no randomness".into()))?;
580 (nonce, Some(nonce.to_vec()))
581 }
582 };
583 let aad = seal::stream_aad(p.frame_type, &self.request_id, seq, self.directions().0);
584 let sealed = Sealed {
585 key_id: self.key_id,
586 kem_ct: None,
587 nonce: carried,
588 ct: seal::seal(&self.send, &nonce, &aad, &p.plain),
589 };
590 Ok(match fields {
591 StreamFields::Data { encoding, .. } => StreamFields::SealedData {
592 seq,
593 encoding,
594 sealed,
595 },
596 StreamFields::Reply { .. } => StreamFields::SealedReply { seq, sealed },
597 _ => StreamFields::SealedError { seq, sealed },
598 })
599 }
600
601 pub(super) fn opened(&self, frame: VerifiedStreamFrame) -> Result<StreamFields, LinkError> {
605 let not_opened = || confidentiality(ConfidentialityReason::ReplyNotOpened);
606 let (seq, frame_type, sealed) = match &frame.fields {
607 StreamFields::SealedData { seq, sealed, .. } => (*seq, "stream_data", sealed),
608 StreamFields::SealedReply { seq, sealed } => (*seq, "stream_reply", sealed),
609 StreamFields::SealedError { seq, sealed } => (*seq, "stream_error", sealed),
610 _ => return Ok(frame.fields),
611 };
612 if sealed.key_id != self.key_id {
615 return Err(not_opened());
616 }
617 let nonce: [u8; NONCE_SIZE] = match self.caller {
619 true => sealed
620 .nonce
621 .as_deref()
622 .and_then(|n| n.try_into().ok())
623 .ok_or_else(not_opened)?,
624 false => seal::stream_nonce(seq),
625 };
626 let aad = seal::stream_aad(frame_type, &self.request_id, seq, self.directions().1);
627 let plain = seal::open(&self.recv, &nonce, &aad, &sealed.ct).map_err(|_| not_opened())?;
628 match frame.fields {
629 StreamFields::SealedData {
630 encoding: StreamEncoding::Raw,
631 ..
632 } => Ok(StreamFields::Data {
633 seq,
634 encoding: StreamEncoding::Raw,
635 body: Value::Bytes(plain),
636 }),
637 StreamFields::SealedData { encoding, .. } => Ok(StreamFields::Data {
638 seq,
639 encoding,
640 body: cbor::decode(&plain).map_err(|_| not_opened())?,
641 }),
642 StreamFields::SealedReply { .. } => Ok(StreamFields::Reply {
643 seq,
644 payload: cbor::decode(&plain).map_err(|_| not_opened())?,
645 }),
646 _ => {
647 let (code, message) = seal::open_error_plain(&plain).map_err(|_| not_opened())?;
648 Ok(StreamFields::Error {
649 seq,
650 code,
651 message: message.unwrap_or_default(),
652 })
653 }
654 }
655 }
656}
657
658fn encoded(v: &Value) -> Result<Vec<u8>, LinkError> {
659 cbor::encode(v).map_err(|e| LinkError::Frame(frame::FrameError::Payload(e.to_string())))
660}
661
662pub(super) fn unsealed(
670 frame: VerifiedStreamFrame,
671 sealing: Option<&StreamSeal>,
672) -> Result<StreamFields, LinkError> {
673 let sealed = matches!(
674 frame.fields,
675 StreamFields::SealedData { .. }
676 | StreamFields::SealedReply { .. }
677 | StreamFields::SealedError { .. }
678 );
679 match (sealing, sealed, &frame.fields) {
680 (None, true, _) => Err(LinkError::Stream {
681 code: CODE_SEALED_REFUSED.into(),
682 message: NO_KEY_DETAIL.into(),
683 relay: false,
684 }),
685 (None, false, _) => Ok(frame.fields),
686 (Some(s), true, _) => s.opened(frame),
687 (Some(_), false, StreamFields::End { .. }) => Ok(frame.fields),
688 (Some(s), false, StreamFields::Error { seq: 0, code, .. })
689 if s.caller && (code == CODE_SEALED_REFUSED || is_clear_refusal(code)) =>
690 {
691 Ok(frame.fields)
692 }
693 (Some(_), false, _) => Err(LinkError::ClearAnswerToSealed),
694 }
695}
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700 use crate::frame::{ReplyType, RequestType};
701
702 const CALLER: [u8; 32] = [1; 32];
703 const PROVIDER: [u8; 32] = [2; 32];
704
705 fn sealed_to(keyring: &Keyring, frame_type: RequestType) -> (VerifiedRequest, CallSeal) {
708 let name = match frame_type {
709 RequestType::Call => seal::FRAME_CALL,
710 RequestType::StreamOpen => seal::FRAME_STREAM_OPEN,
711 };
712 let to = keyring.current().public_key().carried().to_vec();
713 let (sealed, caller) = sealed_request(
714 keyring.profile(),
715 &to,
716 name,
717 [3; 32],
718 "~ring",
719 CALLER,
720 PROVIDER,
721 [4; 16],
722 1_790_000_000_000,
723 &Value::text("secret"),
724 )
725 .unwrap();
726 let request = VerifiedRequest {
727 frame_type,
728 key: Vec::new(),
729 request_hash: [5; 48],
730 caller: CALLER,
731 request_id: [4; 16],
732 realm: [3; 32],
733 procedure: "~ring".into(),
734 target: PROVIDER,
735 deadline: 1_790_000_000_000,
736 payload: Value::Null,
737 sealed: Some(sealed),
738 mode: None,
739 token: None,
740 proofs: None,
741 };
742 (request, caller)
743 }
744
745 fn clear_reply(frame_type: ReplyType, code: Option<&str>) -> VerifiedReply {
746 VerifiedReply {
747 frame_type,
748 responded_by: PROVIDER,
749 payload: code.is_none().then(|| Value::text("in the clear")),
750 code: code.map(str::to_string),
751 detail: None,
752 sealed: None,
753 }
754 }
755
756 #[test]
757 fn a_provider_opens_what_a_caller_sealed_and_its_answer_opens_to_the_caller() {
758 let keyring = Keyring::system(Profile::PqHybrid).unwrap();
759 let (request, caller) = sealed_to(&keyring, RequestType::Call);
760 let (payload, provider) = opened_request(Some(&keyring), &request).unwrap();
761 assert_eq!(payload, Value::text("secret"));
762
763 let plain = cbor::encode(&Value::text("answer")).unwrap();
764 let sealed = provider
765 .sealed_answer(seal::FRAME_RESULT, &plain, &request.request_hash, &PROVIDER)
766 .unwrap();
767 let reply = VerifiedReply {
768 frame_type: ReplyType::Result,
769 responded_by: PROVIDER,
770 payload: None,
771 code: None,
772 detail: None,
773 sealed: Some(sealed),
774 };
775 assert_eq!(
776 reply_outcome(reply, &request, Some(&caller)),
777 Ok(Value::text("answer"))
778 );
779 }
780
781 #[test]
782 fn a_sealed_request_this_node_cannot_open_is_refused_naming_its_key() {
783 let keyring = Keyring::system(Profile::PqPure).unwrap();
784 let other = Keyring::system(Profile::PqPure).unwrap();
785 let (request, _) = sealed_to(&other, RequestType::Call);
786 assert_eq!(
787 opened_request(Some(&keyring), &request).err(),
788 Some(hex(&keyring.current_id()))
789 );
790 assert_eq!(
791 opened_request(None, &request).err().as_deref(),
792 Some(NO_KEY_DETAIL)
793 );
794 }
795
796 #[test]
800 fn a_forged_clear_answer_to_a_sealed_call_is_refused() {
801 let keyring = Keyring::system(Profile::PqPure).unwrap();
802 let (request, caller) = sealed_to(&keyring, RequestType::Call);
803 for forged in [
804 clear_reply(ReplyType::Result, None),
805 clear_reply(ReplyType::Error, Some("handler_error")),
806 clear_reply(ReplyType::Error, Some("sealed_required")),
807 ] {
808 assert_eq!(
809 reply_outcome(forged.clone(), &request, Some(&caller)),
810 Err(LinkError::ClearAnswerToSealed),
811 "{forged:?}"
812 );
813 }
814 assert!(matches!(
815 reply_outcome(
816 clear_reply(ReplyType::Error, Some("caller_quota")),
817 &request,
818 Some(&caller)
819 ),
820 Err(LinkError::Provider { code, .. }) if code == "caller_quota"
821 ));
822 assert_eq!(
823 reply_outcome(
824 clear_reply(ReplyType::Error, Some(CODE_SEALED_REFUSED)),
825 &request,
826 Some(&caller)
827 ),
828 Err(LinkError::SealedRefused { named: None })
829 );
830 }
831
832 fn data(seq: u64, body: &[u8]) -> StreamFields {
833 StreamFields::Data {
834 seq,
835 encoding: StreamEncoding::Raw,
836 body: Value::Bytes(body.to_vec()),
837 }
838 }
839
840 fn sealed(side: &StreamSeal, fields: StreamFields) -> StreamFields {
841 let plain = side.plain_of(&fields).unwrap().unwrap();
842 side.sealed(fields, plain).unwrap()
843 }
844
845 fn from(signer: [u8; 32], fields: StreamFields) -> VerifiedStreamFrame {
846 VerifiedStreamFrame { signer, fields }
847 }
848
849 #[test]
850 fn each_side_of_a_sealed_stream_opens_what_the_other_sealed() {
851 let keyring = Keyring::system(Profile::PqHybrid).unwrap();
852 let (open, caller_seal) = sealed_to(&keyring, RequestType::StreamOpen);
853 let (_, provider_seal) = opened_request(Some(&keyring), &open).unwrap();
854 let (caller, provider) = (
855 StreamSeal::caller(&caller_seal),
856 StreamSeal::provider(&provider_seal),
857 );
858
859 let up = sealed(&caller, data(0, b"up"));
860 assert_eq!(
861 unsealed(from(CALLER, up), Some(&provider)),
862 Ok(data(0, b"up"))
863 );
864 let (one, two) = (
866 sealed(&provider, data(0, b"down")),
867 sealed(&provider, data(0, b"down")),
868 );
869 let nonce = |f: &StreamFields| match f {
870 StreamFields::SealedData { sealed, .. } => sealed.nonce.clone().unwrap(),
871 other => panic!("{other:?}"),
872 };
873 assert_ne!(nonce(&one), nonce(&two));
874 assert_eq!(
875 unsealed(from(PROVIDER, one), Some(&caller)),
876 Ok(data(0, b"down"))
877 );
878 let up = sealed(&caller, data(1, b"up"));
880 assert!(matches!(
881 unsealed(from(CALLER, up), Some(&caller)),
882 Err(LinkError::Confidentiality(_))
883 ));
884 }
885
886 #[test]
890 fn a_forged_clear_frame_on_a_sealed_stream_is_refused() {
891 let keyring = Keyring::system(Profile::PqPure).unwrap();
892 let (open, caller_seal) = sealed_to(&keyring, RequestType::StreamOpen);
893 let (_, provider_seal) = opened_request(Some(&keyring), &open).unwrap();
894 let (caller, provider) = (
895 StreamSeal::caller(&caller_seal),
896 StreamSeal::provider(&provider_seal),
897 );
898 let refusal = StreamFields::Error {
899 seq: 0,
900 code: CODE_SEALED_REFUSED.into(),
901 message: String::new(),
902 };
903 for side in [&caller, &provider] {
904 assert_eq!(
905 unsealed(from(PROVIDER, data(0, b"clear")), Some(side)),
906 Err(LinkError::ClearAnswerToSealed)
907 );
908 let reply = StreamFields::Reply {
909 seq: 1,
910 payload: Value::Null,
911 };
912 assert_eq!(
913 unsealed(from(PROVIDER, reply), Some(side)),
914 Err(LinkError::ClearAnswerToSealed)
915 );
916 }
917 assert_eq!(
918 unsealed(from(PROVIDER, refusal.clone()), Some(&caller)),
919 Ok(refusal.clone())
920 );
921 assert_eq!(
922 unsealed(from(CALLER, refusal), Some(&provider)),
923 Err(LinkError::ClearAnswerToSealed)
924 );
925 }
926
927 #[test]
928 fn a_keyed_procedure_takes_clear_calls_only_within_its_keyless_window() {
929 let since = 1_790_000_000_000;
930 let window = MAX_ADVERTISEMENT_TTL_MS + CLOCK_TOLERANCE_MS as i64;
931 for c in [Confidentiality::Preferred, Confidentiality::Off] {
932 assert!(clear_allowed(c, None, since + 10 * window), "{c:?} keyless");
933 }
934 assert!(clear_allowed(
935 Confidentiality::Preferred,
936 Some(since),
937 since + window
938 ));
939 assert!(!clear_allowed(
940 Confidentiality::Preferred,
941 Some(since),
942 since + window + 1
943 ));
944 for keyed_since in [None, Some(since)] {
945 assert!(!clear_allowed(
946 Confidentiality::Required,
947 keyed_since,
948 since
949 ));
950 }
951 }
952
953 #[test]
954 fn a_refusal_names_a_key_only_as_sixteen_lowercase_hex_digits() {
955 assert_eq!(
956 refused_key(Some("0966848943d688e2")),
957 Some([0x09, 0x66, 0x84, 0x89, 0x43, 0xd6, 0x88, 0xe2])
958 );
959 for not_one in [
960 "+1+2+3+4+5+6+7+8",
961 "0966848943D688E2",
962 "0966848943d688e",
963 "0966848943d688e2f",
964 NO_KEY_DETAIL,
965 "",
966 ] {
967 assert_eq!(refused_key(Some(not_one)), None, "{not_one:?}");
968 }
969 assert_eq!(refused_key(None), None);
970 }
971}