1use chacha20poly1305::{
2 aead::{Aead, AeadCore, KeyInit},
3 Key, XChaCha20Poly1305, XNonce,
4};
5use ed25519_dalek::{Signature, Verifier};
6use nanoid::nanoid;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use web_time::{SystemTime, UNIX_EPOCH};
10
11use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
12
13use crate::{
14 constants,
15 did::Did,
16 doc::Document,
17 error::{MaError, MaResult as Result},
18 key::{EncryptionKey, SigningKey},
19};
20
21pub const MESSAGE_PREFIX: &str = "/ma/";
22
23pub const DEFAULT_REPLAY_WINDOW_SECS: u64 = 120;
24pub const DEFAULT_MAX_CLOCK_SKEW_SECS: u64 = 30;
25pub const DEFAULT_MESSAGE_TTL_SECS: u64 = 3600;
26
27pub fn encode_content(codec: u64, payload: &[u8]) -> Vec<u8> {
32 crate::multiformat::multicodec_encode(codec, payload)
33}
34
35pub fn decode_content(content: &[u8]) -> crate::error::MaResult<(u64, Vec<u8>)> {
39 crate::multiformat::multicodec_decode(content)
40}
41
42#[must_use]
43pub fn default_protocol() -> String {
44 format!("{MESSAGE_PREFIX}{}", constants::VERSION)
45}
46
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52pub struct Headers {
53 pub id: String,
54 #[serde(rename = "protocol")]
55 pub protocol: String,
56 #[serde(rename = "type")]
57 pub message_type: String,
58 pub from: String,
59 pub to: String,
60 #[serde(rename = "createdAt")]
61 pub created_at: f64,
62 #[serde(default)]
63 pub exp: u64,
64 #[serde(rename = "contentType")]
65 pub content_type: String,
66 #[serde(default, skip_serializing_if = "Option::is_none", rename = "replyTo")]
67 pub reply_to: Option<String>,
68 #[serde(rename = "contentHash")]
69 pub content_hash: [u8; 32],
70 pub signature: Vec<u8>,
71}
72
73impl Headers {
74 pub fn validate(&self) -> Result<()> {
75 validate_message_id(&self.id)?;
76 validate_protocol(&self.protocol)?;
77 if let Some(reply_to) = &self.reply_to {
78 validate_message_id(reply_to)?;
79 }
80
81 if self.content_type.is_empty() {
82 return Err(MaError::MissingContentType);
83 }
84
85 Did::validate(&self.from)?;
86 let recipient_is_empty = self.to.trim().is_empty();
87
88 match self.message_type.as_str() {
89 "application/x-ma-broadcast" => {
90 if !recipient_is_empty {
91 return Err(MaError::BroadcastMustNotHaveRecipient);
92 }
93 }
94 "application/x-ma-message" => {
95 if recipient_is_empty {
96 return Err(MaError::MessageRequiresRecipient);
97 }
98 Did::validate(&self.to).map_err(|_| MaError::InvalidRecipient)?;
99 }
100 _ => {
101 if !recipient_is_empty {
102 Did::validate(&self.to).map_err(|_| MaError::InvalidRecipient)?;
103 }
104 }
105 }
106 validate_message_freshness(self.created_at, self.exp)?;
107
108 Ok(())
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151pub struct Message {
152 pub id: String,
153 #[serde(rename = "protocol")]
154 pub protocol: String,
155 #[serde(rename = "type")]
156 pub message_type: String,
157 pub from: String,
158 pub to: String,
159 #[serde(rename = "createdAt")]
160 pub created_at: f64,
161 #[serde(default)]
162 pub exp: u64,
163 #[serde(rename = "contentType")]
164 pub content_type: String,
165 #[serde(default, skip_serializing_if = "Option::is_none", rename = "replyTo")]
166 pub reply_to: Option<String>,
167 pub content: Vec<u8>,
168 pub signature: Vec<u8>,
169}
170
171impl Message {
172 pub fn new(
173 from: impl Into<String>,
174 to: impl Into<String>,
175 message_type: impl Into<String>,
176 content_type: impl Into<String>,
177 content: Vec<u8>,
178 signing_key: &SigningKey,
179 ) -> Result<Self> {
180 let exp = now_unix_nanos()? + DEFAULT_MESSAGE_TTL_SECS * 1_000_000_000;
181 Self::new_with_exp(
182 from,
183 to,
184 message_type,
185 content_type,
186 content,
187 exp,
188 signing_key,
189 )
190 }
191
192 pub fn new_with_exp(
193 from: impl Into<String>,
194 to: impl Into<String>,
195 message_type: impl Into<String>,
196 content_type: impl Into<String>,
197 content: Vec<u8>,
198 exp: u64,
199 signing_key: &SigningKey,
200 ) -> Result<Self> {
201 let mut message = Self {
202 id: nanoid!(),
203 protocol: default_protocol(),
204 message_type: message_type.into(),
205 from: from.into(),
206 to: to.into(),
207 created_at: now_unix_secs()?,
208 exp,
209 content_type: content_type.into(),
210 reply_to: None,
211 content,
212 signature: Vec::new(),
213 };
214
215 message.unsigned_headers().validate()?;
216 message.validate_content()?;
217 message.sign(signing_key)?;
218 Ok(message)
219 }
220
221 pub fn encode(&self) -> Result<Vec<u8>> {
222 let mut out = Vec::new();
223 ciborium::ser::into_writer(self, &mut out)
224 .map_err(|error| MaError::CborEncode(error.to_string()))?;
225 Ok(out)
226 }
227
228 pub fn decode(bytes: &[u8]) -> Result<Self> {
229 ciborium::de::from_reader(bytes).map_err(|error| MaError::CborDecode(error.to_string()))
230 }
231
232 #[must_use]
233 pub fn unsigned_headers(&self) -> Headers {
234 Headers {
235 id: self.id.clone(),
236 protocol: self.protocol.clone(),
237 message_type: self.message_type.clone(),
238 from: self.from.clone(),
239 to: self.to.clone(),
240 created_at: self.created_at,
241 exp: self.exp,
242 content_type: self.content_type.clone(),
243 reply_to: self.reply_to.clone(),
244 content_hash: content_hash(&self.content),
245 signature: Vec::new(),
246 }
247 }
248
249 #[must_use]
250 pub fn headers(&self) -> Headers {
251 let mut headers = self.unsigned_headers();
252 headers.signature.clone_from(&self.signature);
253 headers
254 }
255
256 pub fn sign(&mut self, signing_key: &SigningKey) -> Result<()> {
257 let bytes = self.unsigned_headers_cbor()?;
258 self.signature = signing_key.sign(&bytes);
259 Ok(())
260 }
261
262 pub fn verify_with_document(&self, sender_document: &Document) -> Result<()> {
263 if self.from.is_empty() {
264 return Err(MaError::MissingSender);
265 }
266
267 if self.signature.is_empty() {
268 return Err(MaError::MissingSignature);
269 }
270
271 let sender_did = Did::try_from(self.from.as_str())?;
272 if sender_document.id != sender_did.base_id() {
273 return Err(MaError::InvalidRecipient);
274 }
275
276 self.headers().validate()?;
277 let bytes = self.unsigned_headers_cbor()?;
278 let signature =
279 Signature::from_slice(&self.signature).map_err(|_| MaError::InvalidMessageSignature)?;
280 sender_document
281 .assertion_method_public_key()?
282 .verify(&bytes, &signature)
283 .map_err(|_| MaError::InvalidMessageSignature)
284 }
285
286 pub fn enclose_for(&self, recipient_document: &Document) -> Result<Envelope> {
287 self.headers().validate()?;
288
289 let recipient_public_key =
290 X25519PublicKey::from(recipient_document.key_agreement_public_key_bytes()?);
291 let ephemeral_secret = StaticSecret::random_from_rng(rand_core::OsRng);
292 let ephemeral_public = X25519PublicKey::from(&ephemeral_secret);
293 let shared_secret = ephemeral_secret
294 .diffie_hellman(&recipient_public_key)
295 .to_bytes();
296
297 let encrypted_headers = encrypt(
298 &self.headers_cbor()?,
299 derive_symmetric_key(&shared_secret, constants::BLAKE3_HEADERS_LABEL),
300 )?;
301
302 let encrypted_content = encrypt(
303 &self.content,
304 derive_symmetric_key(&shared_secret, constants::blake3_content_label()),
305 )?;
306
307 Ok(Envelope {
308 ephemeral_key: ephemeral_public.as_bytes().to_vec(),
309 encrypted_content,
310 encrypted_headers,
311 })
312 }
313
314 fn headers_cbor(&self) -> Result<Vec<u8>> {
315 let mut out = Vec::new();
316 ciborium::ser::into_writer(&self.headers(), &mut out)
317 .map_err(|error| MaError::CborEncode(error.to_string()))?;
318 Ok(out)
319 }
320
321 fn unsigned_headers_cbor(&self) -> Result<Vec<u8>> {
322 let mut out = Vec::new();
323 ciborium::ser::into_writer(&self.unsigned_headers(), &mut out)
324 .map_err(|error| MaError::CborEncode(error.to_string()))?;
325 Ok(out)
326 }
327
328 fn validate_content(&self) -> Result<()> {
329 if self.content.is_empty() {
330 return Err(MaError::MissingContent);
331 }
332 Ok(())
333 }
334
335 fn from_headers(headers: Headers) -> Result<Self> {
336 headers.validate()?;
337 Ok(Self {
338 id: headers.id,
339 protocol: headers.protocol,
340 message_type: headers.message_type,
341 from: headers.from,
342 to: headers.to,
343 created_at: headers.created_at,
344 exp: headers.exp,
345 content_type: headers.content_type,
346 reply_to: headers.reply_to,
347 content: Vec::new(),
348 signature: headers.signature,
349 })
350 }
351}
352
353#[derive(Debug, Clone)]
369pub struct ReplayGuard {
370 seen: HashMap<String, f64>,
371 window_secs: u64,
372}
373
374impl Default for ReplayGuard {
375 fn default() -> Self {
376 Self::new(DEFAULT_REPLAY_WINDOW_SECS)
377 }
378}
379
380impl ReplayGuard {
381 #[must_use]
382 pub fn new(window_secs: u64) -> Self {
383 Self {
384 seen: HashMap::new(),
385 window_secs,
386 }
387 }
388
389 pub fn check_and_insert(&mut self, headers: &Headers) -> Result<()> {
390 headers.validate()?;
391 self.prune_old()?;
392 if self.seen.contains_key(&headers.id) {
393 return Err(MaError::ReplayDetected);
394 }
395 self.seen.insert(headers.id.clone(), now_unix_secs()?);
396 Ok(())
397 }
398
399 fn prune_old(&mut self) -> Result<()> {
400 let now = now_unix_secs()?;
401 self.seen
402 .retain(|_, seen_at| now - *seen_at <= self.window_secs as f64);
403 Ok(())
404 }
405}
406
407#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
449pub struct Envelope {
450 #[serde(rename = "ephemeralKey")]
451 pub ephemeral_key: Vec<u8>,
452 #[serde(rename = "encryptedContent")]
453 pub encrypted_content: Vec<u8>,
454 #[serde(rename = "encryptedHeaders")]
455 pub encrypted_headers: Vec<u8>,
456}
457
458impl Envelope {
459 pub fn verify(&self) -> Result<()> {
460 if self.ephemeral_key.is_empty() {
461 return Err(MaError::MissingEnvelopeField("ephemeralKey"));
462 }
463 if self.ephemeral_key.len() != 32 {
464 return Err(MaError::InvalidEphemeralKeyLength);
465 }
466 if self.encrypted_content.is_empty() {
467 return Err(MaError::MissingEnvelopeField("encryptedContent"));
468 }
469 if self.encrypted_headers.is_empty() {
470 return Err(MaError::MissingEnvelopeField("encryptedHeaders"));
471 }
472 Ok(())
473 }
474
475 pub fn encode(&self) -> Result<Vec<u8>> {
476 let mut out = Vec::new();
477 ciborium::ser::into_writer(self, &mut out)
478 .map_err(|error| MaError::CborEncode(error.to_string()))?;
479 Ok(out)
480 }
481
482 pub fn decode(bytes: &[u8]) -> Result<Self> {
483 ciborium::de::from_reader(bytes).map_err(|error| MaError::CborDecode(error.to_string()))
484 }
485
486 pub fn open(
487 &self,
488 recipient_key: &EncryptionKey,
489 sender_document: &Document,
490 ) -> Result<Message> {
491 self.verify()?;
492
493 let shared_secret = compute_shared_secret(&self.ephemeral_key, recipient_key)?;
494 let headers = self.decrypt_headers(&shared_secret)?;
495 headers.validate()?;
496 let content = self.decrypt_content(&shared_secret)?;
497
498 let mut message = Message::from_headers(headers)?;
499 message.content = content;
500 message.verify_with_document(sender_document)?;
501 Ok(message)
502 }
503
504 pub fn open_with_replay_guard(
505 &self,
506 recipient_key: &EncryptionKey,
507 sender_document: &Document,
508 replay_guard: &mut ReplayGuard,
509 ) -> Result<Message> {
510 self.verify()?;
511
512 let shared_secret = compute_shared_secret(&self.ephemeral_key, recipient_key)?;
513 let headers = self.decrypt_headers(&shared_secret)?;
514 replay_guard.check_and_insert(&headers)?;
515 let content = self.decrypt_content(&shared_secret)?;
516
517 let mut message = Message::from_headers(headers)?;
518 message.content = content;
519 message.verify_with_document(sender_document)?;
520 Ok(message)
521 }
522
523 fn decrypt_headers(&self, shared_secret: &[u8; 32]) -> Result<Headers> {
524 let decrypted = decrypt(
525 &self.encrypted_headers,
526 shared_secret,
527 constants::BLAKE3_HEADERS_LABEL,
528 )?;
529 ciborium::de::from_reader(decrypted.as_slice())
530 .map_err(|error| MaError::CborDecode(error.to_string()))
531 }
532
533 fn decrypt_content(&self, shared_secret: &[u8; 32]) -> Result<Vec<u8>> {
534 decrypt(
535 &self.encrypted_content,
536 shared_secret,
537 constants::blake3_content_label(),
538 )
539 }
540}
541
542fn validate_message_id(id: &str) -> Result<()> {
543 if id.is_empty() {
544 return Err(MaError::EmptyMessageId);
545 }
546
547 if !id
548 .chars()
549 .all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-')
550 {
551 return Err(MaError::InvalidMessageId);
552 }
553
554 Ok(())
555}
556
557fn validate_protocol(kind: &str) -> Result<()> {
558 if kind == default_protocol() {
559 return Ok(());
560 }
561
562 Err(MaError::InvalidMessageType)
563}
564
565fn now_unix_secs() -> Result<f64> {
566 SystemTime::now()
567 .duration_since(UNIX_EPOCH)
568 .map(|duration| duration.as_nanos() as f64 / 1_000_000_000.0)
569 .map_err(|_| MaError::InvalidMessageTimestamp)
570}
571
572fn now_unix_nanos() -> Result<u64> {
573 SystemTime::now()
574 .duration_since(UNIX_EPOCH)
575 .map(|d| d.as_nanos() as u64)
576 .map_err(|_| MaError::InvalidMessageTimestamp)
577}
578
579fn validate_message_freshness(created_at: f64, exp: u64) -> Result<()> {
580 let now = now_unix_secs()?;
581
582 if created_at > now + DEFAULT_MAX_CLOCK_SKEW_SECS as f64 {
583 return Err(MaError::MessageFromFuture);
584 }
585
586 if exp == 0 {
587 return Ok(()); }
589
590 let exp_secs = exp as f64 / 1_000_000_000.0;
591 if now > exp_secs + DEFAULT_MAX_CLOCK_SKEW_SECS as f64 {
592 return Err(MaError::MessageTooOld);
593 }
594
595 Ok(())
596}
597
598fn compute_shared_secret(
599 ephemeral_key_bytes: &[u8],
600 recipient_key: &EncryptionKey,
601) -> Result<[u8; 32]> {
602 let ephemeral_public = X25519PublicKey::from(
603 <[u8; 32]>::try_from(ephemeral_key_bytes)
604 .map_err(|_| MaError::InvalidEphemeralKeyLength)?,
605 );
606 Ok(recipient_key.shared_secret(&ephemeral_public))
607}
608
609fn derive_symmetric_key(shared_secret: &[u8; 32], label: &str) -> Key {
610 let derived = blake3::derive_key(label, shared_secret);
611 *Key::from_slice(&derived)
612}
613
614fn encrypt(data: &[u8], key: Key) -> Result<Vec<u8>> {
615 let cipher = XChaCha20Poly1305::new(&key);
616 let nonce = XChaCha20Poly1305::generate_nonce(&mut rand_core::OsRng);
617 let encrypted = cipher.encrypt(&nonce, data).map_err(|_| MaError::Crypto)?;
618
619 let mut out = nonce.to_vec();
620 out.extend_from_slice(&encrypted);
621 Ok(out)
622}
623
624fn decrypt(data: &[u8], shared_secret: &[u8; 32], label: &str) -> Result<Vec<u8>> {
625 if data.len() < 24 {
626 return Err(MaError::CiphertextTooShort);
627 }
628
629 let key = derive_symmetric_key(shared_secret, label);
630 let cipher = XChaCha20Poly1305::new(&key);
631 let nonce = XNonce::from_slice(&data[..24]);
632
633 cipher
634 .decrypt(nonce, &data[24..])
635 .map_err(|_| MaError::Crypto)
636}
637
638fn content_hash(content: &[u8]) -> [u8; 32] {
639 blake3::hash(content).into()
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645 use crate::{doc::VerificationMethod, key::EncryptionKey};
646
647 fn fixture_documents() -> (
648 SigningKey,
649 EncryptionKey,
650 Document,
651 SigningKey,
652 EncryptionKey,
653 Document,
654 ) {
655 let sender_did = Did::new_url("k51sender", None::<String>).expect("sender did");
656 let sender_sign_url = Did::new_url("k51sender", None::<String>).expect("sender sign did");
657 let sender_enc_url = Did::new_url("k51sender", None::<String>).expect("sender enc did");
658 let sender_signing = SigningKey::generate(sender_sign_url).expect("sender signing key");
659 let sender_encryption =
660 EncryptionKey::generate(sender_enc_url).expect("sender encryption key");
661
662 let recipient_did = Did::new_url("k51recipient", None::<String>).expect("recipient did");
663 let recipient_sign_url =
664 Did::new_url("k51recipient", None::<String>).expect("recipient sign did");
665 let recipient_enc_url =
666 Did::new_url("k51recipient", None::<String>).expect("recipient enc did");
667 let recipient_signing =
668 SigningKey::generate(recipient_sign_url).expect("recipient signing key");
669 let recipient_encryption =
670 EncryptionKey::generate(recipient_enc_url).expect("recipient encryption key");
671
672 let mut sender_document = Document::new(&sender_did, &sender_did);
673 let sender_assertion = VerificationMethod::new(
674 sender_did.base_id(),
675 sender_did.base_id(),
676 sender_signing.key_type.clone(),
677 sender_signing.did.fragment.as_deref().unwrap_or_default(),
678 sender_signing.public_key_multibase.clone(),
679 )
680 .expect("sender assertion vm");
681 let sender_key_agreement = VerificationMethod::new(
682 sender_did.base_id(),
683 sender_did.base_id(),
684 sender_encryption.key_type.clone(),
685 sender_encryption
686 .did
687 .fragment
688 .as_deref()
689 .unwrap_or_default(),
690 sender_encryption.public_key_multibase.clone(),
691 )
692 .expect("sender key agreement vm");
693 sender_document
694 .add_verification_method(sender_assertion.clone())
695 .expect("add sender assertion");
696 sender_document
697 .add_verification_method(sender_key_agreement.clone())
698 .expect("add sender key agreement");
699 sender_document.assertion_method = vec![sender_assertion.id.clone()];
700 sender_document.key_agreement = vec![sender_key_agreement.id.clone()];
701 sender_document
702 .sign(&sender_signing, &sender_assertion)
703 .expect("sign sender doc");
704
705 let mut recipient_document = Document::new(&recipient_did, &recipient_did);
706 let recipient_assertion = VerificationMethod::new(
707 recipient_did.base_id(),
708 recipient_did.base_id(),
709 recipient_signing.key_type.clone(),
710 recipient_signing
711 .did
712 .fragment
713 .as_deref()
714 .unwrap_or_default(),
715 recipient_signing.public_key_multibase.clone(),
716 )
717 .expect("recipient assertion vm");
718 let recipient_key_agreement = VerificationMethod::new(
719 recipient_did.base_id(),
720 recipient_did.base_id(),
721 recipient_encryption.key_type.clone(),
722 recipient_encryption
723 .did
724 .fragment
725 .as_deref()
726 .unwrap_or_default(),
727 recipient_encryption.public_key_multibase.clone(),
728 )
729 .expect("recipient key agreement vm");
730 recipient_document
731 .add_verification_method(recipient_assertion.clone())
732 .expect("add recipient assertion");
733 recipient_document
734 .add_verification_method(recipient_key_agreement.clone())
735 .expect("add recipient key agreement");
736 recipient_document.assertion_method = vec![recipient_assertion.id.clone()];
737 recipient_document.key_agreement = vec![recipient_key_agreement.id.clone()];
738 recipient_document
739 .sign(&recipient_signing, &recipient_assertion)
740 .expect("sign recipient doc");
741
742 (
743 sender_signing,
744 sender_encryption,
745 sender_document,
746 recipient_signing,
747 recipient_encryption,
748 recipient_document,
749 )
750 }
751
752 #[test]
753 fn did_round_trip() {
754 let did = Did::new_url(
755 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
756 Some("bahner"),
757 )
758 .expect("did must build");
759 let parsed = Did::try_from(did.id().as_str()).expect("did must parse");
760 assert_eq!(did, parsed);
761 }
762
763 #[test]
764 fn subject_url_round_trip() {
765 let did = Did::new_url(
766 "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
767 None::<String>,
768 )
769 .expect("subject did must build");
770 let parsed = Did::try_from(did.id().as_str()).expect("subject did must parse");
771 assert_eq!(did, parsed);
772 }
773
774 #[test]
775 fn document_signs_and_verifies() {
776 let (sender_signing, _, sender_document, _, _, _) = fixture_documents();
777 sender_signing.validate().expect("signing key validates");
778 sender_document.validate().expect("document validates");
779 }
780
781 #[test]
782 fn envelope_round_trip() {
783 let (sender_signing, _, sender_document, _, recipient_encryption, recipient_document) =
784 fixture_documents();
785 let message = Message::new(
786 sender_document.id.clone(),
787 recipient_document.id.clone(),
788 "application/x-ma-message",
789 "text/plain",
790 b"look".to_vec(),
791 &sender_signing,
792 )
793 .expect("message creation");
794 message
795 .verify_with_document(&sender_document)
796 .expect("message signature verifies");
797
798 let envelope = message
799 .enclose_for(&recipient_document)
800 .expect("message encloses");
801 let opened = envelope
802 .open(&recipient_encryption, &sender_document)
803 .expect("envelope opens");
804
805 assert_eq!(opened.content, b"look");
806 assert_eq!(opened.from, sender_document.id);
807 assert_eq!(opened.to, recipient_document.id);
808 }
809
810 #[test]
811 fn tampered_content_fails_signature_verification() {
812 let (sender_signing, _, sender_document, _, _, recipient_document) = fixture_documents();
813 let mut message = Message::new(
814 sender_document.id.clone(),
815 recipient_document.id.clone(),
816 "application/x-ma-message",
817 "text/plain",
818 b"look".to_vec(),
819 &sender_signing,
820 )
821 .expect("message creation");
822
823 message.content = b"tampered".to_vec();
824 let result = message.verify_with_document(&sender_document);
825 assert!(matches!(result, Err(MaError::InvalidMessageSignature)));
826 }
827
828 #[test]
829 fn stale_message_is_rejected() {
830 let (sender_signing, _, sender_document, _, _, recipient_document) = fixture_documents();
831 let mut message = Message::new(
832 sender_document.id.clone(),
833 recipient_document.id.clone(),
834 "application/x-ma-message",
835 "text/plain",
836 b"look".to_vec(),
837 &sender_signing,
838 )
839 .expect("message creation");
840
841 message.created_at = 0.0;
842 message.exp = 1; message
844 .sign(&sender_signing)
845 .expect("re-sign with past timestamps");
846 let result = message.verify_with_document(&sender_document);
847 assert!(matches!(result, Err(MaError::MessageTooOld)));
848 }
849
850 #[test]
851 fn future_message_is_rejected() {
852 let (sender_signing, _, sender_document, _, _, recipient_document) = fixture_documents();
853 let mut message = Message::new(
854 sender_document.id.clone(),
855 recipient_document.id.clone(),
856 "application/x-ma-message",
857 "text/plain",
858 b"look".to_vec(),
859 &sender_signing,
860 )
861 .expect("message creation");
862
863 message.created_at =
864 now_unix_secs().expect("current timestamp") + DEFAULT_MAX_CLOCK_SKEW_SECS as f64 + 60.0;
865 message
866 .sign(&sender_signing)
867 .expect("re-sign with updated timestamp");
868
869 let result = message.verify_with_document(&sender_document);
870 assert!(matches!(result, Err(MaError::MessageFromFuture)));
871 }
872
873 #[test]
874 fn exp_zero_disables_expiration() {
875 let (sender_signing, _, sender_document, _, _, recipient_document) = fixture_documents();
876 let mut message = Message::new(
877 sender_document.id.clone(),
878 recipient_document.id.clone(),
879 "application/x-ma-message",
880 "text/plain",
881 b"look".to_vec(),
882 &sender_signing,
883 )
884 .expect("message creation");
885
886 message.created_at = 0.0;
887 message.exp = 0; message.sign(&sender_signing).expect("re-sign with exp=0");
889
890 message
891 .verify_with_document(&sender_document)
892 .expect("exp=0 should bypass expiration check");
893 }
894
895 #[test]
896 fn custom_ttl_rejects_expired_message() {
897 let (sender_signing, _, sender_document, _, _, recipient_document) = fixture_documents();
898 let now_nanos = now_unix_nanos().expect("current timestamp");
899 let mut message = Message::new_with_exp(
901 sender_document.id.clone(),
902 recipient_document.id.clone(),
903 "application/x-ma-message",
904 "text/plain",
905 b"look".to_vec(),
906 now_nanos + 60_000_000_000,
907 &sender_signing,
908 )
909 .expect("message creation with custom exp");
910
911 message.exp = 1;
913 message
914 .sign(&sender_signing)
915 .expect("re-sign with expired exp");
916
917 let result = message.verify_with_document(&sender_document);
918 assert!(matches!(result, Err(MaError::MessageTooOld)));
919 }
920
921 #[test]
922 fn replay_guard_rejects_duplicate_envelope() {
923 let (sender_signing, _, sender_document, _, recipient_encryption, recipient_document) =
924 fixture_documents();
925 let message = Message::new(
926 sender_document.id.clone(),
927 recipient_document.id.clone(),
928 "application/x-ma-message",
929 "text/plain",
930 b"look".to_vec(),
931 &sender_signing,
932 )
933 .expect("message creation");
934
935 let envelope = message
936 .enclose_for(&recipient_document)
937 .expect("message encloses");
938 let mut replay_guard = ReplayGuard::default();
939
940 envelope
941 .open_with_replay_guard(&recipient_encryption, &sender_document, &mut replay_guard)
942 .expect("first delivery accepted");
943
944 let second = envelope.open_with_replay_guard(
945 &recipient_encryption,
946 &sender_document,
947 &mut replay_guard,
948 );
949 assert!(matches!(second, Err(MaError::ReplayDetected)));
950 }
951
952 #[test]
953 fn broadcast_allows_empty_recipient() {
954 let (sender_signing, _, sender_document, _, _, _) = fixture_documents();
955 let message = Message::new(
956 sender_document.id.clone(),
957 String::new(),
958 "application/x-ma-broadcast",
959 "text/plain",
960 b"hello everyone".to_vec(),
961 &sender_signing,
962 )
963 .expect("broadcast message creation");
964
965 message
966 .verify_with_document(&sender_document)
967 .expect("broadcast with empty recipient verifies");
968 }
969
970 #[test]
971 fn broadcast_rejects_recipient() {
972 let (sender_signing, _, sender_document, _, _, recipient_document) = fixture_documents();
973 let result = Message::new(
974 sender_document.id.clone(),
975 recipient_document.id.clone(),
976 "application/x-ma-broadcast",
977 "text/plain",
978 b"hello everyone".to_vec(),
979 &sender_signing,
980 );
981
982 assert!(matches!(
983 result,
984 Err(MaError::BroadcastMustNotHaveRecipient)
985 ));
986 }
987
988 #[test]
989 fn message_requires_recipient() {
990 let (sender_signing, _, sender_document, _, _, _) = fixture_documents();
991 let result = Message::new(
992 sender_document.id.clone(),
993 String::new(),
994 "application/x-ma-message",
995 "text/plain",
996 b"secret".to_vec(),
997 &sender_signing,
998 );
999
1000 assert!(matches!(result, Err(MaError::MessageRequiresRecipient)));
1001 }
1002
1003 #[test]
1004 fn unknown_content_type_allows_empty_recipient() {
1005 let (sender_signing, _, sender_document, _, _, _) = fixture_documents();
1006 let message = Message::new(
1007 sender_document.id.clone(),
1008 String::new(),
1009 "application/x-ma-custom",
1010 "text/plain",
1011 b"whatever".to_vec(),
1012 &sender_signing,
1013 )
1014 .expect("custom content type message creation");
1015
1016 message
1017 .verify_with_document(&sender_document)
1018 .expect("custom type with empty recipient verifies");
1019 }
1020
1021 #[test]
1022 fn unknown_content_type_allows_recipient() {
1023 let (sender_signing, _, sender_document, _, _, recipient_document) = fixture_documents();
1024 let message = Message::new(
1025 sender_document.id.clone(),
1026 recipient_document.id.clone(),
1027 "application/x-ma-custom",
1028 "text/plain",
1029 b"whatever".to_vec(),
1030 &sender_signing,
1031 )
1032 .expect("custom content type with recipient");
1033
1034 message
1035 .verify_with_document(&sender_document)
1036 .expect("custom type with recipient verifies");
1037 }
1038}