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