1use std::{
9 collections::{HashMap, HashSet},
10 sync::{Arc, Mutex},
11 time::{SystemTime, UNIX_EPOCH},
12};
13
14use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
15use iroh::PublicKey;
16use iroh_blobs::Hash;
17use iroh_docs::NamespaceId;
18use serde::{Deserialize, Serialize};
19
20use crate::{
21 error::{Result, SyncwebError},
22 indexing::{IndexingDatabase, IndexingService},
23};
24
25const METADATA_CONTEXT: &[u8] = b"syncweb/wot/metadata/v1\0";
26const DELEGATION_CONTEXT: &[u8] = b"syncweb/wot/delegation/v1\0";
27const REVOCATION_CONTEXT: &[u8] = b"syncweb/wot/revocation/v1\0";
28const MODERATION_CONTEXT: &[u8] = b"syncweb/wot/moderation/v1\0";
29const ATTESTATION_CONTEXT: &[u8] = b"syncweb/wot/attestation/v1\0";
30const PROVIDER_TRUST_CONTEXT: &[u8] = b"syncweb/wot/provider-trust/v1\0";
31
32#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
34#[non_exhaustive]
35pub struct MetadataEntry {
36 pub content: Hash,
37 pub key: String,
38 pub value: String,
39 pub author: String,
40 pub sequence: u64,
41 pub created_at: u64,
42 pub signature: Option<String>,
43}
44
45pub type WotMetadata = MetadataEntry;
47
48impl MetadataEntry {
49 pub fn new(
55 content: Hash,
56 key: impl Into<String>,
57 value: impl Into<String>,
58 sequence: u64,
59 signing_key: &SigningKey,
60 ) -> Result<Self> {
61 Self::new_with_time(content, key, value, sequence, current_epoch_seconds(), signing_key)
62 }
63
64 pub fn new_with_time(
70 content: Hash,
71 key: impl Into<String>,
72 value: impl Into<String>,
73 sequence: u64,
74 created_at: u64,
75 signing_key: &SigningKey,
76 ) -> Result<Self> {
77 let mut entry = Self {
78 content,
79 key: key.into(),
80 value: value.into(),
81 author: author_id(&signing_key.verifying_key()),
82 sequence,
83 created_at,
84 signature: None,
85 };
86 entry.sign(signing_key)?;
87 Ok(entry)
88 }
89
90 pub fn unsigned(
98 content: Hash,
99 key: impl Into<String>,
100 value: impl Into<String>,
101 sequence: u64,
102 created_at: u64,
103 author: &VerifyingKey,
104 ) -> Result<Self> {
105 let entry = Self {
106 content,
107 key: key.into(),
108 value: value.into(),
109 author: author_id(author),
110 sequence,
111 created_at,
112 signature: None,
113 };
114 entry.validate()?;
115 Ok(entry)
116 }
117
118 pub fn sign(&mut self, signing_key: &SigningKey) -> Result<()> {
124 ensure_signer(&self.author, &signing_key.verifying_key())?;
125 self.signature = Some(sign_text(METADATA_CONTEXT, &self.unsigned_bytes()?, signing_key));
126 Ok(())
127 }
128
129 pub fn verify_signature(&self) -> Result<()> {
135 self.validate()?;
136 verify_text(
137 METADATA_CONTEXT,
138 &self.unsigned_bytes()?,
139 &self.author,
140 self.signature.as_deref(),
141 )
142 }
143
144 pub fn validate(&self) -> Result<()> {
150 if self.key.trim().is_empty() {
151 return Err(SyncwebError::InvalidConfig(
152 "Web-of-Trust metadata key cannot be empty".to_owned(),
153 ));
154 }
155 if self.sequence == 0 {
156 return Err(SyncwebError::InvalidConfig(
157 "Web-of-Trust metadata sequence must be greater than zero".to_owned(),
158 ));
159 }
160 let _author = parse_author(&self.author)?;
161 Ok(())
162 }
163
164 pub fn to_bytes(&self) -> Result<Vec<u8>> {
170 self.validate()?;
171 serde_json::to_vec(self)
172 .map_err(|error| SyncwebError::operation("failed to serialize Web-of-Trust metadata", error))
173 }
174
175 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
181 let entry: Self = serde_json::from_slice(bytes.as_ref())
182 .map_err(|error| SyncwebError::operation("failed to deserialize Web-of-Trust metadata", error))?;
183 entry.validate()?;
184 Ok(entry)
185 }
186
187 fn unsigned_bytes(&self) -> Result<Vec<u8>> {
188 let mut unsigned = self.clone();
189 unsigned.signature = None;
190 signed_bytes(METADATA_CONTEXT, &unsigned)
191 }
192}
193
194#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
196#[non_exhaustive]
197pub struct TrustDelegation {
198 pub delegator: String,
199 pub delegate: String,
200 pub scope: Option<Hash>,
201 pub sequence: u64,
202 pub issued_at: u64,
203 pub expires_at: u64,
204 pub signature: Option<String>,
205}
206
207impl TrustDelegation {
208 pub fn new(
217 delegate: &VerifyingKey,
218 scope: Option<Hash>,
219 sequence: u64,
220 expires_at: u64,
221 signing_key: &SigningKey,
222 ) -> Result<Self> {
223 Self::new_with_time(
224 delegate,
225 scope,
226 sequence,
227 current_epoch_seconds(),
228 expires_at,
229 signing_key,
230 )
231 }
232
233 pub fn new_with_time(
239 delegate: &VerifyingKey,
240 scope: Option<Hash>,
241 sequence: u64,
242 issued_at: u64,
243 expires_at: u64,
244 signing_key: &SigningKey,
245 ) -> Result<Self> {
246 let mut delegation = Self {
247 delegator: author_id(&signing_key.verifying_key()),
248 delegate: author_id(delegate),
249 scope,
250 sequence,
251 issued_at,
252 expires_at,
253 signature: None,
254 };
255 delegation.sign(signing_key)?;
256 Ok(delegation)
257 }
258
259 pub fn sign(&mut self, signing_key: &SigningKey) -> Result<()> {
265 ensure_signer(&self.delegator, &signing_key.verifying_key())?;
266 self.signature = Some(sign_text(DELEGATION_CONTEXT, &self.unsigned_bytes()?, signing_key));
267 Ok(())
268 }
269
270 pub fn verify_signature(&self) -> Result<()> {
276 self.validate()?;
277 verify_text(
278 DELEGATION_CONTEXT,
279 &self.unsigned_bytes()?,
280 &self.delegator,
281 self.signature.as_deref(),
282 )
283 }
284
285 pub fn verify_at(&self, now: u64) -> Result<()> {
291 self.verify_signature()?;
292 if self.expires_at <= now {
293 return Err(SyncwebError::InvalidConfig("trust delegation has expired".to_owned()));
294 }
295 Ok(())
296 }
297
298 pub fn validate(&self) -> Result<()> {
304 if self.sequence == 0 {
305 return Err(SyncwebError::InvalidConfig(
306 "trust delegation sequence must be greater than zero".to_owned(),
307 ));
308 }
309 if self.expires_at <= self.issued_at {
310 return Err(SyncwebError::InvalidConfig(
311 "trust delegation expiration must be after its issue time".to_owned(),
312 ));
313 }
314 if self.delegator == self.delegate {
315 return Err(SyncwebError::InvalidConfig(
316 "trust delegation cannot delegate to itself".to_owned(),
317 ));
318 }
319 let _delegator = parse_author(&self.delegator)?;
320 let _delegate = parse_author(&self.delegate)?;
321 Ok(())
322 }
323
324 fn unsigned_bytes(&self) -> Result<Vec<u8>> {
325 let mut unsigned = self.clone();
326 unsigned.signature = None;
327 signed_bytes(DELEGATION_CONTEXT, &unsigned)
328 }
329}
330
331#[derive(Clone, Copy, Debug, Eq, PartialEq)]
333#[non_exhaustive]
334pub enum TrustDecision {
335 TrustedRoot,
336 TrustedDelegation,
337 Untrusted,
338 Revoked,
339}
340
341impl TrustDecision {
342 #[must_use]
343 pub const fn is_trusted(self) -> bool {
344 matches!(self, Self::TrustedRoot | Self::TrustedDelegation)
345 }
346}
347
348#[derive(Clone, Debug, Default)]
350pub struct TrustPolicy {
351 roots: HashSet<String>,
352 delegations: HashMap<String, Vec<TrustDelegation>>,
353 revoked_authors: HashSet<String>,
354}
355
356impl TrustPolicy {
357 #[must_use]
359 pub fn new() -> Self {
360 Self::default()
361 }
362
363 #[must_use]
365 pub fn with_root(signing_key: &SigningKey) -> Self {
366 let mut policy = Self::new();
367 policy.trust_author(&signing_key.verifying_key());
368 policy
369 }
370
371 pub fn trust_author(&mut self, author: &VerifyingKey) {
373 self.roots.insert(author_id(author));
374 self.revoked_authors.remove(&author_id(author));
375 }
376
377 pub fn revoke_author(&mut self, author: &VerifyingKey) {
379 self.revoked_authors.insert(author_id(author));
380 }
381
382 pub fn add_delegation(&mut self, delegation: TrustDelegation) -> Result<bool> {
389 self.add_delegation_at(delegation, current_epoch_seconds())
390 }
391
392 pub fn add_delegation_at(&mut self, delegation: TrustDelegation, now: u64) -> Result<bool> {
399 delegation.verify_at(now)?;
400 let scope = delegation.scope;
401 if !self.is_trusted_for_at(&delegation.delegator, scope.as_ref(), now) {
402 return Err(SyncwebError::InvalidIdentity(
403 "trust delegation issuer is not trusted for its scope".to_owned(),
404 ));
405 }
406 let entries = self.delegations.entry(delegation.delegator.clone()).or_default();
407 if entries.iter().any(|existing| {
408 existing.delegate == delegation.delegate
409 && existing.scope == delegation.scope
410 && existing.sequence >= delegation.sequence
411 }) {
412 return Ok(false);
413 }
414 entries.retain(|existing| {
415 !(existing.delegate == delegation.delegate
416 && existing.scope == delegation.scope
417 && existing.sequence < delegation.sequence)
418 });
419 entries.push(delegation);
420 Ok(true)
421 }
422
423 #[must_use]
425 pub fn evaluate(&self, author: &str) -> TrustDecision {
426 self.evaluate_at(author, current_epoch_seconds())
427 }
428
429 #[must_use]
431 pub fn evaluate_at(&self, author: &str, now: u64) -> TrustDecision {
432 self.evaluate_for_at(author, None, now)
433 }
434
435 #[must_use]
437 pub fn evaluate_for(&self, author: &str, content: Option<&Hash>) -> TrustDecision {
438 self.evaluate_for_at(author, content, current_epoch_seconds())
439 }
440
441 #[must_use]
444 pub fn evaluate_for_at(&self, author: &str, content: Option<&Hash>, now: u64) -> TrustDecision {
445 if self.revoked_authors.contains(author) {
446 return TrustDecision::Revoked;
447 }
448 if self.roots.contains(author) {
449 return TrustDecision::TrustedRoot;
450 }
451 if self.reaches_trusted_root(author, content, now, &mut HashSet::new()) {
452 TrustDecision::TrustedDelegation
453 } else {
454 TrustDecision::Untrusted
455 }
456 }
457
458 #[must_use]
460 pub fn is_trusted_for(&self, author: &str, content: Option<&Hash>) -> bool {
461 self.is_trusted_for_at(author, content, current_epoch_seconds())
462 }
463
464 #[must_use]
466 pub fn is_trusted_for_at(&self, author: &str, content: Option<&Hash>, now: u64) -> bool {
467 self.evaluate_for_at(author, content, now).is_trusted()
468 }
469
470 fn reaches_trusted_root(
471 &self,
472 target: &str,
473 content: Option<&Hash>,
474 now: u64,
475 visited: &mut HashSet<String>,
476 ) -> bool {
477 if self.revoked_authors.contains(target) {
478 return false;
479 }
480 if self.roots.contains(target) {
481 return true;
482 }
483 if !visited.insert(target.to_owned()) {
484 return false;
485 }
486 self.delegations.iter().any(|(delegator, delegations)| {
487 delegations.iter().any(|delegation| {
488 if delegation.delegate != target
489 || delegation.expires_at <= now
490 || (delegation.scope.is_some() && delegation.scope.as_ref() != content)
491 {
492 return false;
493 }
494 let mut branch_visited = visited.clone();
495 self.reaches_trusted_root(delegator, content, now, &mut branch_visited)
496 })
497 })
498 }
499}
500
501#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
503#[non_exhaustive]
504pub struct RevocationRecord {
505 pub content: Hash,
506 pub publisher: String,
507 pub sequence: u64,
508 pub revoked_at: u64,
509 pub reason: String,
510 pub signature: Option<String>,
511}
512
513impl RevocationRecord {
514 pub fn new(content: Hash, sequence: u64, reason: impl Into<String>, signing_key: &SigningKey) -> Result<Self> {
520 let mut record = Self {
521 content,
522 publisher: author_id(&signing_key.verifying_key()),
523 sequence,
524 revoked_at: current_epoch_seconds(),
525 reason: reason.into(),
526 signature: None,
527 };
528 record.sign(signing_key)?;
529 Ok(record)
530 }
531
532 pub fn sign(&mut self, signing_key: &SigningKey) -> Result<()> {
538 ensure_signer(&self.publisher, &signing_key.verifying_key())?;
539 self.signature = Some(sign_text(REVOCATION_CONTEXT, &self.unsigned_bytes()?, signing_key));
540 Ok(())
541 }
542
543 pub fn verify_signature(&self) -> Result<()> {
549 self.validate()?;
550 verify_text(
551 REVOCATION_CONTEXT,
552 &self.unsigned_bytes()?,
553 &self.publisher,
554 self.signature.as_deref(),
555 )
556 }
557
558 pub fn validate(&self) -> Result<()> {
564 if self.sequence == 0 {
565 return Err(SyncwebError::InvalidConfig(
566 "revocation sequence must be greater than zero".to_owned(),
567 ));
568 }
569 if self.reason.trim().is_empty() {
570 return Err(SyncwebError::InvalidConfig(
571 "revocation reason cannot be empty".to_owned(),
572 ));
573 }
574 let _publisher = parse_author(&self.publisher)?;
575 Ok(())
576 }
577
578 fn unsigned_bytes(&self) -> Result<Vec<u8>> {
579 let mut unsigned = self.clone();
580 unsigned.signature = None;
581 signed_bytes(REVOCATION_CONTEXT, &unsigned)
582 }
583}
584
585#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
587#[non_exhaustive]
588pub enum ModerationAction {
589 Show,
590 Hide,
591 Warn,
592 Quarantine,
593 Restore,
594}
595
596pub type ModerationDecision = ModerationAction;
598
599impl ModerationAction {
600 #[must_use]
601 pub const fn hides_content(&self) -> bool {
602 matches!(self, Self::Hide | Self::Quarantine)
603 }
604
605 #[must_use]
606 pub const fn is_visible(self) -> bool {
607 !self.hides_content()
608 }
609}
610
611#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
613#[non_exhaustive]
614pub enum ModerationScope {
615 #[default]
616 Global,
617 Network(String),
618 Folder(NamespaceId),
619 File {
620 namespace_id: NamespaceId,
621 key: Vec<u8>,
622 },
623}
624
625impl ModerationScope {
626 #[must_use]
627 pub fn applies_to(&self, context: &ModerationContext) -> bool {
628 match self {
629 Self::Global => true,
630 Self::Network(network) => context.network.as_deref() == Some(network),
631 Self::Folder(namespace_id) => context.namespace_id == Some(*namespace_id),
632 Self::File { namespace_id, key } => {
633 context.namespace_id == Some(*namespace_id) && context.key.as_deref() == Some(key.as_slice())
634 }
635 }
636 }
637
638 #[must_use]
639 pub const fn specificity(&self) -> u8 {
640 match self {
641 Self::Global => 0,
642 Self::Network(_) => 1,
643 Self::Folder(_) => 2,
644 Self::File { .. } => 3,
645 }
646 }
647}
648
649#[derive(Clone, Debug, Eq, PartialEq)]
651#[non_exhaustive]
652pub struct ModerationContext {
653 pub content: Hash,
654 pub network: Option<String>,
655 pub namespace_id: Option<NamespaceId>,
656 pub key: Option<Vec<u8>>,
657}
658
659impl ModerationContext {
660 #[must_use]
661 pub const fn new(content: Hash) -> Self {
662 Self {
663 content,
664 network: None,
665 namespace_id: None,
666 key: None,
667 }
668 }
669
670 #[must_use]
671 pub fn in_network(mut self, network: impl Into<String>) -> Self {
672 self.network = Some(network.into());
673 self
674 }
675
676 #[must_use]
677 pub fn for_file(mut self, namespace_id: NamespaceId, key: impl AsRef<[u8]>) -> Self {
678 self.namespace_id = Some(namespace_id);
679 self.key = Some(key.as_ref().to_vec());
680 self
681 }
682}
683
684#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
686#[non_exhaustive]
687pub struct ModerationRecord {
688 pub content: Hash,
689 pub moderator: String,
690 pub action: ModerationAction,
691 #[serde(default)]
692 pub scope: ModerationScope,
693 pub sequence: u64,
694 pub created_at: u64,
695 pub reason: String,
696 pub signature: Option<String>,
697}
698
699impl ModerationRecord {
700 pub fn new(
706 content: Hash,
707 action: ModerationAction,
708 sequence: u64,
709 reason: impl Into<String>,
710 signing_key: &SigningKey,
711 ) -> Result<Self> {
712 let mut record = Self {
713 content,
714 moderator: author_id(&signing_key.verifying_key()),
715 action,
716 scope: ModerationScope::Global,
717 sequence,
718 created_at: current_epoch_seconds(),
719 reason: reason.into(),
720 signature: None,
721 };
722 record.sign(signing_key)?;
723 Ok(record)
724 }
725
726 pub fn new_with_time(
732 content: Hash,
733 action: ModerationAction,
734 sequence: u64,
735 created_at: u64,
736 reason: impl Into<String>,
737 signing_key: &SigningKey,
738 ) -> Result<Self> {
739 Self::new_scoped_with_time(
740 content,
741 ModerationScope::Global,
742 action,
743 sequence,
744 created_at,
745 reason,
746 signing_key,
747 )
748 }
749
750 pub fn new_scoped(
756 content: Hash,
757 scope: ModerationScope,
758 action: ModerationAction,
759 sequence: u64,
760 reason: impl Into<String>,
761 signing_key: &SigningKey,
762 ) -> Result<Self> {
763 Self::new_scoped_with_time(
764 content,
765 scope,
766 action,
767 sequence,
768 current_epoch_seconds(),
769 reason,
770 signing_key,
771 )
772 }
773
774 pub fn new_scoped_with_time(
780 content: Hash,
781 scope: ModerationScope,
782 action: ModerationAction,
783 sequence: u64,
784 created_at: u64,
785 reason: impl Into<String>,
786 signing_key: &SigningKey,
787 ) -> Result<Self> {
788 let mut record = Self {
789 content,
790 moderator: author_id(&signing_key.verifying_key()),
791 action,
792 scope,
793 sequence,
794 created_at,
795 reason: reason.into(),
796 signature: None,
797 };
798 record.sign(signing_key)?;
799 Ok(record)
800 }
801
802 pub fn with_scope(mut self, scope: ModerationScope, signing_key: &SigningKey) -> Result<Self> {
808 self.scope = scope;
809 self.sign(signing_key)?;
810 Ok(self)
811 }
812
813 pub fn sign(&mut self, signing_key: &SigningKey) -> Result<()> {
819 ensure_signer(&self.moderator, &signing_key.verifying_key())?;
820 self.signature = Some(sign_text(MODERATION_CONTEXT, &self.unsigned_bytes()?, signing_key));
821 Ok(())
822 }
823
824 pub fn verify_signature(&self) -> Result<()> {
830 self.validate()?;
831 verify_text(
832 MODERATION_CONTEXT,
833 &self.unsigned_bytes()?,
834 &self.moderator,
835 self.signature.as_deref(),
836 )
837 }
838
839 pub fn validate(&self) -> Result<()> {
845 if self.sequence == 0 {
846 return Err(SyncwebError::InvalidConfig(
847 "moderation sequence must be greater than zero".to_owned(),
848 ));
849 }
850 if self.reason.trim().is_empty() {
851 return Err(SyncwebError::InvalidConfig(
852 "moderation reason cannot be empty".to_owned(),
853 ));
854 }
855 if let ModerationScope::Network(network) = &self.scope
856 && network.trim().is_empty()
857 {
858 return Err(SyncwebError::InvalidConfig(
859 "moderation network scope cannot be empty".to_owned(),
860 ));
861 }
862 if let ModerationScope::File { key, .. } = &self.scope
863 && key.is_empty()
864 {
865 return Err(SyncwebError::InvalidConfig(
866 "moderation file scope cannot be empty".to_owned(),
867 ));
868 }
869 let _moderator = parse_author(&self.moderator)?;
870 Ok(())
871 }
872
873 fn unsigned_bytes(&self) -> Result<Vec<u8>> {
874 let mut unsigned = self.clone();
875 unsigned.signature = None;
876 signed_bytes(MODERATION_CONTEXT, &unsigned)
877 }
878}
879
880#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
882#[non_exhaustive]
883pub enum AttestationKind {
884 License,
885 Provenance,
886 Derivative,
887 Other(String),
888}
889
890#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
892#[non_exhaustive]
893pub struct Attestation {
894 pub content: Hash,
895 pub issuer: String,
896 pub kind: AttestationKind,
897 pub value: String,
898 pub sequence: u64,
899 pub issued_at: u64,
900 pub signature: Option<String>,
901}
902
903impl Attestation {
904 pub fn new(
910 content: Hash,
911 kind: AttestationKind,
912 value: impl Into<String>,
913 sequence: u64,
914 signing_key: &SigningKey,
915 ) -> Result<Self> {
916 let mut attestation = Self {
917 content,
918 issuer: author_id(&signing_key.verifying_key()),
919 kind,
920 value: value.into(),
921 sequence,
922 issued_at: current_epoch_seconds(),
923 signature: None,
924 };
925 attestation.sign(signing_key)?;
926 Ok(attestation)
927 }
928
929 pub fn sign(&mut self, signing_key: &SigningKey) -> Result<()> {
935 ensure_signer(&self.issuer, &signing_key.verifying_key())?;
936 self.signature = Some(sign_text(ATTESTATION_CONTEXT, &self.unsigned_bytes()?, signing_key));
937 Ok(())
938 }
939
940 pub fn verify_signature(&self) -> Result<()> {
946 self.validate()?;
947 verify_text(
948 ATTESTATION_CONTEXT,
949 &self.unsigned_bytes()?,
950 &self.issuer,
951 self.signature.as_deref(),
952 )
953 }
954
955 pub fn validate(&self) -> Result<()> {
961 if self.sequence == 0 {
962 return Err(SyncwebError::InvalidConfig(
963 "attestation sequence must be greater than zero".to_owned(),
964 ));
965 }
966 if self.value.trim().is_empty() {
967 return Err(SyncwebError::InvalidConfig(
968 "attestation value cannot be empty".to_owned(),
969 ));
970 }
971 let _issuer = parse_author(&self.issuer)?;
972 Ok(())
973 }
974
975 fn unsigned_bytes(&self) -> Result<Vec<u8>> {
976 let mut unsigned = self.clone();
977 unsigned.signature = None;
978 signed_bytes(ATTESTATION_CONTEXT, &unsigned)
979 }
980}
981
982#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
984#[non_exhaustive]
985pub enum ProviderTrustAction {
986 Trust,
987 Distrust,
988 Vouch,
989 Warn,
990}
991
992#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
994#[non_exhaustive]
995pub struct ProviderTrustRecord {
996 pub provider: PublicKey,
997 pub action: ProviderTrustAction,
998 pub scope: Option<Hash>,
999 pub issuer: String,
1000 pub sequence: u64,
1001 pub issued_at: u64,
1002 pub expires_at: Option<u64>,
1003 pub reason: String,
1004 pub signature: Option<String>,
1005}
1006
1007impl ProviderTrustRecord {
1008 pub fn new(
1014 provider: PublicKey,
1015 action: ProviderTrustAction,
1016 scope: Option<Hash>,
1017 sequence: u64,
1018 expires_at: Option<u64>,
1019 reason: impl Into<String>,
1020 signing_key: &SigningKey,
1021 ) -> Result<Self> {
1022 let mut record = Self {
1023 provider,
1024 action,
1025 scope,
1026 issuer: author_id(&signing_key.verifying_key()),
1027 sequence,
1028 issued_at: current_epoch_seconds(),
1029 expires_at,
1030 reason: reason.into(),
1031 signature: None,
1032 };
1033 record.sign(signing_key)?;
1034 Ok(record)
1035 }
1036
1037 pub fn new_with_time(
1043 provider: PublicKey,
1044 action: ProviderTrustAction,
1045 scope: Option<Hash>,
1046 sequence: u64,
1047 issued_at: u64,
1048 reason: impl Into<String>,
1049 signing_key: &SigningKey,
1050 ) -> Result<Self> {
1051 let mut record = Self {
1052 provider,
1053 action,
1054 scope,
1055 issuer: author_id(&signing_key.verifying_key()),
1056 sequence,
1057 issued_at,
1058 expires_at: None,
1059 reason: reason.into(),
1060 signature: None,
1061 };
1062 record.sign(signing_key)?;
1063 Ok(record)
1064 }
1065
1066 pub fn with_expiration(mut self, expires_at: Option<u64>, signing_key: &SigningKey) -> Result<Self> {
1072 self.expires_at = expires_at;
1073 self.sign(signing_key)?;
1074 Ok(self)
1075 }
1076
1077 pub fn sign(&mut self, signing_key: &SigningKey) -> Result<()> {
1083 ensure_signer(&self.issuer, &signing_key.verifying_key())?;
1084 self.signature = Some(sign_text(PROVIDER_TRUST_CONTEXT, &self.unsigned_bytes()?, signing_key));
1085 Ok(())
1086 }
1087
1088 pub fn verify_signature(&self) -> Result<()> {
1094 self.validate()?;
1095 verify_text(
1096 PROVIDER_TRUST_CONTEXT,
1097 &self.unsigned_bytes()?,
1098 &self.issuer,
1099 self.signature.as_deref(),
1100 )
1101 }
1102
1103 pub fn verify(&self) -> Result<()> {
1109 self.verify_at(current_epoch_seconds())
1110 }
1111
1112 pub fn verify_at(&self, now: u64) -> Result<()> {
1118 self.verify_signature()?;
1119 if self.expires_at.is_some_and(|expires_at| expires_at <= now) {
1120 return Err(SyncwebError::InvalidConfig(
1121 "provider trust record has expired".to_owned(),
1122 ));
1123 }
1124 Ok(())
1125 }
1126
1127 pub fn validate(&self) -> Result<()> {
1133 if self.sequence == 0 {
1134 return Err(SyncwebError::InvalidConfig(
1135 "provider trust record sequence must be greater than zero".to_owned(),
1136 ));
1137 }
1138 if self.reason.trim().is_empty() {
1139 return Err(SyncwebError::InvalidConfig(
1140 "provider trust record reason cannot be empty".to_owned(),
1141 ));
1142 }
1143 if self.expires_at.is_some_and(|expires_at| expires_at <= self.issued_at) {
1144 return Err(SyncwebError::InvalidConfig(
1145 "provider trust record expiration must be after issue time".to_owned(),
1146 ));
1147 }
1148 VerifyingKey::from_bytes(self.provider.as_bytes())
1149 .map_err(|error| SyncwebError::InvalidIdentity(format!("invalid provider identity: {error}")))?;
1150 parse_author(&self.issuer)?;
1151 Ok(())
1152 }
1153
1154 pub fn to_bytes(&self) -> Result<Vec<u8>> {
1160 self.validate()?;
1161 serde_json::to_vec(self)
1162 .map_err(|error| SyncwebError::operation("failed to serialize provider trust record", error))
1163 }
1164
1165 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
1171 let record: Self = serde_json::from_slice(bytes.as_ref())
1172 .map_err(|error| SyncwebError::operation("failed to deserialize provider trust record", error))?;
1173 record.validate()?;
1174 Ok(record)
1175 }
1176
1177 fn unsigned_bytes(&self) -> Result<Vec<u8>> {
1178 let mut unsigned = self.clone();
1179 unsigned.signature = None;
1180 signed_bytes(PROVIDER_TRUST_CONTEXT, &unsigned)
1181 }
1182}
1183
1184#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1186#[non_exhaustive]
1187pub enum ProviderTrustDecision {
1188 Trusted,
1189 Distrusted,
1190 Unknown,
1191 Conflicting,
1192}
1193
1194impl ProviderTrustDecision {
1195 #[must_use]
1196 pub const fn is_trusted(self) -> bool {
1197 matches!(self, Self::Trusted)
1198 }
1199
1200 #[must_use]
1201 pub const fn is_distrusted(self) -> bool {
1202 matches!(self, Self::Distrusted)
1203 }
1204}
1205
1206#[derive(Clone, Debug, Default)]
1207struct WotState {
1208 revocations: HashMap<Hash, RevocationRecord>,
1209 moderation: HashMap<Hash, Vec<ModerationRecord>>,
1210 attestations: HashMap<(Hash, String, u64), Attestation>,
1211 provider_trust: HashMap<PublicKey, Vec<ProviderTrustRecord>>,
1212}
1213
1214#[derive(Clone)]
1216pub struct WotService {
1217 database: IndexingDatabase,
1218 policy: Arc<Mutex<TrustPolicy>>,
1219 state: Arc<Mutex<WotState>>,
1220}
1221
1222impl std::fmt::Debug for WotService {
1223 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1224 formatter.debug_struct("WotService").finish_non_exhaustive()
1225 }
1226}
1227
1228impl WotService {
1229 #[must_use]
1231 pub fn new(indexing: &IndexingService, policy: TrustPolicy) -> Self {
1232 Self::with_database(indexing.database().clone(), policy)
1233 }
1234
1235 #[must_use]
1237 pub fn with_database(database: IndexingDatabase, policy: TrustPolicy) -> Self {
1238 Self {
1239 database,
1240 policy: Arc::new(Mutex::new(policy)),
1241 state: Arc::new(Mutex::new(WotState::default())),
1242 }
1243 }
1244
1245 pub fn in_memory(policy: TrustPolicy) -> Result<Self> {
1251 Ok(Self::with_database(IndexingDatabase::in_memory()?, policy))
1252 }
1253
1254 #[must_use]
1255 pub const fn database(&self) -> &IndexingDatabase {
1256 &self.database
1257 }
1258
1259 pub fn policy(&self) -> Result<TrustPolicy> {
1265 Ok(self
1266 .policy
1267 .lock()
1268 .map_err(|error| SyncwebError::operation("Web-of-Trust policy lock poisoned", error))?
1269 .clone())
1270 }
1271
1272 pub fn trust_author(&self, author: &VerifyingKey) -> Result<()> {
1278 self.policy
1279 .lock()
1280 .map_err(|error| SyncwebError::operation("Web-of-Trust policy lock poisoned", error))?
1281 .trust_author(author);
1282 Ok(())
1283 }
1284
1285 pub fn add_delegation(&self, delegation: TrustDelegation) -> Result<bool> {
1292 self.add_delegation_at(delegation, current_epoch_seconds())
1293 }
1294
1295 pub fn add_delegation_at(&self, delegation: TrustDelegation, now: u64) -> Result<bool> {
1302 self.policy
1303 .lock()
1304 .map_err(|error| SyncwebError::operation("Web-of-Trust policy lock poisoned", error))?
1305 .add_delegation_at(delegation, now)
1306 }
1307
1308 pub fn apply_provider_trust(&self, record: ProviderTrustRecord) -> Result<bool> {
1317 let now = current_epoch_seconds();
1318 record.verify_at(now)?;
1319 let issuer_key = parse_author(&record.issuer)?;
1320 let is_self_distrust =
1321 issuer_key.as_bytes() == record.provider.as_bytes() && record.action == ProviderTrustAction::Distrust;
1322 if !is_self_distrust
1323 && !self
1324 .policy()?
1325 .is_trusted_for_at(&record.issuer, record.scope.as_ref(), now)
1326 {
1327 return Err(SyncwebError::InvalidIdentity(
1328 "provider trust record issuer is not trusted for its scope".to_owned(),
1329 ));
1330 }
1331 let mut state = self
1332 .state
1333 .lock()
1334 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?;
1335 let records = state.provider_trust.entry(record.provider).or_default();
1336 if records.iter().any(|existing| {
1337 existing.issuer == record.issuer && existing.scope == record.scope && existing.sequence >= record.sequence
1338 }) {
1339 return Ok(false);
1340 }
1341 records.retain(|existing| {
1342 !(existing.issuer == record.issuer && existing.scope == record.scope && existing.sequence < record.sequence)
1343 });
1344 records.push(record);
1345 drop(state);
1346 Ok(true)
1347 }
1348
1349 pub fn evaluate_provider_trust(
1354 &self,
1355 provider: PublicKey,
1356 hash: Option<&Hash>,
1357 now: u64,
1358 ) -> Result<ProviderTrustDecision> {
1359 let records = self
1360 .state
1361 .lock()
1362 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?
1363 .provider_trust
1364 .get(&provider)
1365 .cloned()
1366 .unwrap_or_default();
1367 let maybe_latest_sequence = records
1368 .iter()
1369 .filter(|record| {
1370 record.expires_at.is_none_or(|expires_at| expires_at > now)
1371 && record.scope.as_ref().is_none_or(|scope| Some(scope) == hash)
1372 })
1373 .map(|record| record.sequence)
1374 .max();
1375 let Some(latest_sequence) = maybe_latest_sequence else {
1376 return Ok(ProviderTrustDecision::Unknown);
1377 };
1378 let latest = records
1379 .iter()
1380 .filter(|record| {
1381 record.sequence == latest_sequence
1382 && record.expires_at.is_none_or(|expires_at| expires_at > now)
1383 && record.scope.as_ref().is_none_or(|scope| Some(scope) == hash)
1384 })
1385 .collect::<Vec<_>>();
1386 let trusted = latest
1387 .iter()
1388 .any(|record| matches!(record.action, ProviderTrustAction::Trust | ProviderTrustAction::Vouch));
1389 let distrusted = latest
1390 .iter()
1391 .any(|record| matches!(record.action, ProviderTrustAction::Distrust));
1392 Ok(match (trusted, distrusted) {
1393 (true, true) => ProviderTrustDecision::Conflicting,
1394 (true, false) => ProviderTrustDecision::Trusted,
1395 (false, true) => ProviderTrustDecision::Distrusted,
1396 (false, false) => ProviderTrustDecision::Unknown,
1397 })
1398 }
1399
1400 pub fn evaluate_provider_trust_for_hash(
1406 &self,
1407 provider: PublicKey,
1408 hash: Hash,
1409 now: u64,
1410 ) -> Result<ProviderTrustDecision> {
1411 self.evaluate_provider_trust(provider, Some(&hash), now)
1412 }
1413
1414 pub fn provider_trust_records(&self, provider: PublicKey) -> Result<Vec<ProviderTrustRecord>> {
1420 let mut records = self
1421 .state
1422 .lock()
1423 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?
1424 .provider_trust
1425 .get(&provider)
1426 .cloned()
1427 .unwrap_or_default();
1428 records.sort_by(|left, right| {
1429 right
1430 .sequence
1431 .cmp(&left.sequence)
1432 .then_with(|| right.issued_at.cmp(&left.issued_at))
1433 });
1434 Ok(records)
1435 }
1436
1437 pub fn append_metadata(&self, entry: &MetadataEntry) -> Result<bool> {
1444 entry.verify_signature()?;
1445 let now = current_epoch_seconds();
1446 let policy = self
1447 .policy
1448 .lock()
1449 .map_err(|error| SyncwebError::operation("Web-of-Trust policy lock poisoned", error))?;
1450 if !policy.is_trusted_for_at(&entry.author, Some(&entry.content), now) {
1451 return Err(SyncwebError::InvalidIdentity(
1452 "metadata author is not trusted for this content".to_owned(),
1453 ));
1454 }
1455 drop(policy);
1456 if self.is_revoked(&entry.content)? {
1457 return Err(SyncwebError::InvalidConfig(
1458 "metadata cannot be appended to self-revoked content".to_owned(),
1459 ));
1460 }
1461 self.database.append_wot_metadata(entry)
1462 }
1463
1464 pub fn search_metadata(&self, query: &str, limit: usize) -> Result<Vec<MetadataEntry>> {
1470 let records = self.database.search_wot_metadata(query, limit)?;
1471 let state = self
1472 .state
1473 .lock()
1474 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?;
1475 Ok(records
1476 .into_iter()
1477 .filter(|entry| !state.revocations.contains_key(&entry.content))
1478 .filter(|entry| {
1479 state.moderation.get(&entry.content).is_none_or(|moderation_records| {
1480 moderation_records
1481 .iter()
1482 .filter(|record| matches!(&record.scope, ModerationScope::Global))
1483 .max_by_key(|record| record.sequence)
1484 .is_none_or(|record| !record.action.hides_content())
1485 })
1486 })
1487 .collect())
1488 }
1489
1490 pub fn search(&self, query: &str, limit: usize) -> Result<Vec<MetadataEntry>> {
1496 self.search_metadata(query, limit)
1497 }
1498
1499 pub fn revoke_self(&self, record: RevocationRecord) -> Result<bool> {
1506 record.verify_signature()?;
1507 let mut state = self
1508 .state
1509 .lock()
1510 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?;
1511 if state
1512 .revocations
1513 .get(&record.content)
1514 .is_some_and(|existing| existing.sequence >= record.sequence)
1515 {
1516 return Ok(false);
1517 }
1518 state.revocations.insert(record.content, record);
1519 drop(state);
1520 Ok(true)
1521 }
1522
1523 pub fn is_revoked(&self, content: &Hash) -> Result<bool> {
1529 Ok(self
1530 .state
1531 .lock()
1532 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?
1533 .revocations
1534 .contains_key(content))
1535 }
1536
1537 pub fn apply_moderation(&self, record: ModerationRecord) -> Result<bool> {
1544 record.verify_signature()?;
1545 let now = current_epoch_seconds();
1546 if !self
1547 .policy()?
1548 .is_trusted_for_at(&record.moderator, Some(&record.content), now)
1549 {
1550 return Err(SyncwebError::InvalidIdentity(
1551 "moderator is not trusted for this content".to_owned(),
1552 ));
1553 }
1554 let mut state = self
1555 .state
1556 .lock()
1557 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?;
1558 let records = state.moderation.entry(record.content).or_default();
1559 if records
1560 .iter()
1561 .filter(|existing| existing.scope == record.scope)
1562 .any(|existing| existing.sequence >= record.sequence)
1563 {
1564 return Ok(false);
1565 }
1566 records.push(record);
1567 drop(state);
1568 Ok(true)
1569 }
1570
1571 pub fn moderation(&self, content: &Hash) -> Result<Option<ModerationRecord>> {
1577 Ok(self
1578 .state
1579 .lock()
1580 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?
1581 .moderation
1582 .get(content)
1583 .and_then(|records| {
1584 records
1585 .iter()
1586 .max_by_key(|record| (record.sequence, record.scope.specificity()))
1587 .cloned()
1588 }))
1589 }
1590
1591 pub fn moderation_records(&self, content: &Hash) -> Result<Vec<ModerationRecord>> {
1597 let mut records = self
1598 .state
1599 .lock()
1600 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?
1601 .moderation
1602 .get(content)
1603 .cloned()
1604 .unwrap_or_default();
1605 records.sort_by(|left, right| {
1606 right
1607 .sequence
1608 .cmp(&left.sequence)
1609 .then_with(|| right.scope.specificity().cmp(&left.scope.specificity()))
1610 });
1611 Ok(records)
1612 }
1613
1614 pub fn list_moderation(&self, content: Option<&Hash>) -> Result<Vec<ModerationRecord>> {
1620 let state = self
1621 .state
1622 .lock()
1623 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?;
1624 let mut records = state
1625 .moderation
1626 .iter()
1627 .filter(|(hash, _)| content.is_none_or(|requested| requested == *hash))
1628 .flat_map(|(_, records)| records.iter().cloned())
1629 .collect::<Vec<_>>();
1630 drop(state);
1631 records.sort_by(|left, right| {
1632 right
1633 .sequence
1634 .cmp(&left.sequence)
1635 .then_with(|| right.scope.specificity().cmp(&left.scope.specificity()))
1636 });
1637 Ok(records)
1638 }
1639
1640 pub fn moderation_decision(&self, context: &ModerationContext) -> Result<ModerationAction> {
1649 let selected_record = self
1650 .state
1651 .lock()
1652 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?
1653 .moderation
1654 .get(&context.content)
1655 .and_then(|records| {
1656 records
1657 .iter()
1658 .filter(|record| record.scope.applies_to(context))
1659 .max_by_key(|record| (record.sequence, record.scope.specificity()))
1660 .cloned()
1661 });
1662 Ok(selected_record.map_or(ModerationAction::Show, |selected| selected.action))
1663 }
1664
1665 pub fn evaluate_moderation(&self, context: &ModerationContext) -> Result<ModerationAction> {
1671 self.moderation_decision(context)
1672 }
1673
1674 pub fn append_attestation(&self, attestation: Attestation) -> Result<bool> {
1681 attestation.verify_signature()?;
1682 let now = current_epoch_seconds();
1683 if !self
1684 .policy()?
1685 .is_trusted_for_at(&attestation.issuer, Some(&attestation.content), now)
1686 {
1687 return Err(SyncwebError::InvalidIdentity(
1688 "attestation issuer is not trusted for this content".to_owned(),
1689 ));
1690 }
1691 let mut state = self
1692 .state
1693 .lock()
1694 .map_err(|error| SyncwebError::operation("Web-of-Trust state lock poisoned", error))?;
1695 let key = (attestation.content, attestation.issuer.clone(), attestation.sequence);
1696 if state.attestations.contains_key(&key) {
1697 return Ok(false);
1698 }
1699 state.attestations.insert(key, attestation);
1700 drop(state);
1701 Ok(true)
1702 }
1703
1704 pub fn verify_attestation(&self, attestation: &Attestation) -> Result<()> {
1711 attestation.verify_signature()
1712 }
1713}
1714
1715fn signed_bytes<T: Serialize>(context: &[u8], value: &T) -> Result<Vec<u8>> {
1716 let encoded = serde_json::to_vec(value)
1717 .map_err(|error| SyncwebError::operation("failed to serialize signed Web-of-Trust record", error))?;
1718 let mut bytes = Vec::with_capacity(context.len().saturating_add(encoded.len()));
1719 bytes.extend_from_slice(context);
1720 bytes.extend_from_slice(&encoded);
1721 Ok(bytes)
1722}
1723
1724fn sign_text(context: &[u8], unsigned: &[u8], signing_key: &SigningKey) -> String {
1725 let mut bytes = Vec::with_capacity(context.len().saturating_add(unsigned.len()));
1726 bytes.extend_from_slice(context);
1727 bytes.extend_from_slice(unsigned);
1728 hex::encode(signing_key.sign(&bytes).to_bytes())
1729}
1730
1731fn verify_text(context: &[u8], unsigned: &[u8], author: &str, signature: Option<&str>) -> Result<()> {
1732 let signature_text = signature
1733 .ok_or_else(|| SyncwebError::InvalidConfig("signed Web-of-Trust record has no signature".to_owned()))?;
1734 let signature_bytes = hex::decode(signature_text)
1735 .map_err(|error| SyncwebError::InvalidConfig(format!("invalid Web-of-Trust signature encoding: {error}")))?;
1736 let parsed_signature = Signature::from_slice(&signature_bytes)
1737 .map_err(|error| SyncwebError::InvalidConfig(format!("invalid Web-of-Trust signature: {error}")))?;
1738 let verifying_key = parse_author(author)?;
1739 let mut bytes = Vec::with_capacity(context.len().saturating_add(unsigned.len()));
1740 bytes.extend_from_slice(context);
1741 bytes.extend_from_slice(unsigned);
1742 verifying_key
1743 .verify(&bytes, &parsed_signature)
1744 .map_err(|error| SyncwebError::InvalidConfig(format!("Web-of-Trust signature is invalid: {error}")))
1745}
1746
1747fn ensure_signer(author: &str, signing_key: &VerifyingKey) -> Result<()> {
1748 if author == author_id(signing_key) {
1749 Ok(())
1750 } else {
1751 Err(SyncwebError::InvalidIdentity(
1752 "Web-of-Trust signer does not match the declared author".to_owned(),
1753 ))
1754 }
1755}
1756
1757fn author_id(author: &VerifyingKey) -> String {
1758 hex::encode(author.to_bytes())
1759}
1760
1761fn parse_author(author: &str) -> Result<VerifyingKey> {
1762 let bytes = hex::decode(author)
1763 .map_err(|error| SyncwebError::InvalidIdentity(format!("invalid Web-of-Trust author encoding: {error}")))?;
1764 let key_bytes: [u8; 32] = bytes.try_into().map_err(|error: Vec<u8>| {
1765 SyncwebError::InvalidIdentity(format!("Web-of-Trust author must be 32 bytes, got {}", error.len()))
1766 })?;
1767 VerifyingKey::from_bytes(&key_bytes)
1768 .map_err(|error| SyncwebError::InvalidIdentity(format!("invalid Web-of-Trust author key: {error}")))
1769}
1770
1771fn current_epoch_seconds() -> u64 {
1772 SystemTime::now()
1773 .duration_since(UNIX_EPOCH)
1774 .map_or(0, |duration| duration.as_secs())
1775}