Skip to main content

solid_pod_rs_didkey/
verifier.rs

1//! [`solid_pod_rs::SelfSignedVerifier`] implementation for `did:key`.
2//!
3//! Plugs into a [`solid_pod_rs::CidVerifier`] so an
4//! `acl:IssuerCondition` with `cid:Verifier` accepts did:key-signed
5//! proofs alongside NIP-98 and did:nostr.
6
7use async_trait::async_trait;
8use solid_pod_rs::auth::self_signed::{
9    ProofEnvelope, SelfSignedError, SelfSignedVerifier, VerifiedSubject,
10};
11
12use crate::jwt::verify_self_signed_jwt;
13
14/// Default acceptance window for `iat` drift (seconds).
15pub const DEFAULT_SKEW_SECONDS: u64 = 60;
16
17/// Verifier for did:key self-signed compact JWTs.
18#[derive(Debug, Clone)]
19pub struct DidKeyVerifier {
20    skew: u64,
21}
22
23impl DidKeyVerifier {
24    /// Use [`DEFAULT_SKEW_SECONDS`] as the acceptance window.
25    pub fn new() -> Self {
26        Self {
27            skew: DEFAULT_SKEW_SECONDS,
28        }
29    }
30
31    /// Override the `iat` skew tolerance.
32    #[must_use]
33    pub fn with_skew(mut self, skew_seconds: u64) -> Self {
34        self.skew = skew_seconds;
35        self
36    }
37}
38
39impl Default for DidKeyVerifier {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45/// Heuristic: does this proof look like a compact JWS?
46///
47/// Used so the fan-out dispatcher can return `Ok(None)` for
48/// non-JWT inputs (letting the next verifier try) while still
49/// surfacing a real error for malformed JWTs.
50fn looks_like_compact_jws(s: &str) -> bool {
51    let dots = s.bytes().filter(|b| *b == b'.').count();
52    dots == 2
53        && s.bytes().all(|b| {
54            b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'=')
55        })
56}
57
58#[async_trait]
59impl SelfSignedVerifier for DidKeyVerifier {
60    async fn verify(
61        &self,
62        envelope: &ProofEnvelope<'_>,
63    ) -> Result<Option<VerifiedSubject>, SelfSignedError> {
64        if !looks_like_compact_jws(envelope.proof) {
65            return Ok(None);
66        }
67        match verify_self_signed_jwt(
68            envelope.proof,
69            envelope.uri,
70            envelope.method,
71            envelope.now_unix,
72            self.skew,
73        ) {
74            Ok(verified) => Ok(Some(VerifiedSubject {
75                did: verified.did,
76                verification_method: verified.verification_method,
77            })),
78            Err(crate::error::DidKeyError::MalformedJwt(m)) => {
79                Err(SelfSignedError::Malformed(m))
80            }
81            Err(crate::error::DidKeyError::InvalidHeader(m))
82            | Err(crate::error::DidKeyError::NotDidKey(m)) => {
83                // Header parseable but not bound to a did:key →
84                // not our format, let the next verifier try.
85                // If the envelope _was_ structurally JWT-shaped but
86                // pointed at another DID method we still want to
87                // give siblings a chance.
88                let _ = m;
89                Ok(None)
90            }
91            Err(crate::error::DidKeyError::InvalidClaims(m)) => {
92                Err(SelfSignedError::ScopeMismatch(m))
93            }
94            Err(crate::error::DidKeyError::BadSignature(m)) => {
95                Err(SelfSignedError::InvalidSignature(m))
96            }
97            Err(e) => Err(SelfSignedError::Other(e.to_string())),
98        }
99    }
100
101    fn name(&self) -> &'static str {
102        "did:key"
103    }
104}