Skip to main content

auths_verifier/
verifier.rs

1//! Dependency-injected [`Verifier`] for attestation and chain verification.
2
3use std::sync::Arc;
4
5use auths_crypto::CryptoProvider;
6
7use crate::clock::ClockProvider;
8use crate::core::{Attestation, Capability, VerifiedAttestation};
9use crate::error::AttestationError;
10use crate::types::{DeviceDID, VerificationReport};
11use crate::verify;
12use crate::witness::WitnessVerifyConfig;
13
14/// Dependency-injected verifier for attestation and chain verification.
15///
16/// Uses `Arc<dyn CryptoProvider>` and `Arc<dyn ClockProvider>` for
17/// lifetime-free sharing across async tasks and web server handler state.
18///
19/// Usage:
20/// ```ignore
21/// use std::sync::Arc;
22/// use auths_verifier::{Verifier, SystemClock};
23/// use auths_crypto::RingCryptoProvider;
24///
25/// let verifier = Verifier::native();
26/// let result = verifier.verify_with_keys(&att, &pk).await;
27/// ```
28#[derive(Clone)]
29pub struct Verifier {
30    provider: Arc<dyn CryptoProvider>,
31    clock: Arc<dyn ClockProvider>,
32}
33
34impl Verifier {
35    /// Create a `Verifier` with the given crypto provider and clock.
36    ///
37    /// Args:
38    /// * `provider`: Ed25519 crypto backend.
39    /// * `clock`: Clock provider for expiry checks.
40    pub fn new(provider: Arc<dyn CryptoProvider>, clock: Arc<dyn ClockProvider>) -> Self {
41        Self { provider, clock }
42    }
43
44    /// Create a `Verifier` using the native Ring crypto provider and system clock.
45    #[cfg(feature = "native")]
46    pub fn native() -> Self {
47        Self {
48            provider: Arc::new(auths_crypto::RingCryptoProvider),
49            clock: Arc::new(crate::clock::SystemClock),
50        }
51    }
52
53    /// Verify an attestation's signatures against the issuer's public key.
54    ///
55    /// Args:
56    /// * `att`: The attestation to verify.
57    /// * `issuer_pk_bytes`: Raw Ed25519 public key of the issuer.
58    pub async fn verify_with_keys(
59        &self,
60        att: &Attestation,
61        issuer_pk_bytes: &[u8],
62    ) -> Result<VerifiedAttestation, AttestationError> {
63        verify::verify_with_keys_at(
64            att,
65            issuer_pk_bytes,
66            self.clock.now(),
67            true,
68            self.provider.as_ref(),
69        )
70        .await?;
71        Ok(VerifiedAttestation::from_verified(att.clone()))
72    }
73
74    /// Verify an attestation and check that it grants a required capability.
75    ///
76    /// Args:
77    /// * `att`: The attestation to verify.
78    /// * `required`: The capability that must be present.
79    /// * `issuer_pk_bytes`: Raw Ed25519 public key of the issuer.
80    pub async fn verify_with_capability(
81        &self,
82        att: &Attestation,
83        required: &Capability,
84        issuer_pk_bytes: &[u8],
85    ) -> Result<VerifiedAttestation, AttestationError> {
86        let verified = self.verify_with_keys(att, issuer_pk_bytes).await?;
87        if !att.capabilities.contains(required) {
88            return Err(AttestationError::MissingCapability {
89                required: required.clone(),
90                available: att.capabilities.clone(),
91            });
92        }
93        Ok(verified)
94    }
95
96    /// Verify an attestation against a specific point in time (skips clock-skew check).
97    ///
98    /// Args:
99    /// * `att`: The attestation to verify.
100    /// * `issuer_pk_bytes`: Raw Ed25519 public key of the issuer.
101    /// * `at`: The reference timestamp for expiry evaluation.
102    pub async fn verify_at_time(
103        &self,
104        att: &Attestation,
105        issuer_pk_bytes: &[u8],
106        at: chrono::DateTime<chrono::Utc>,
107    ) -> Result<VerifiedAttestation, AttestationError> {
108        verify::verify_with_keys_at(att, issuer_pk_bytes, at, false, self.provider.as_ref())
109            .await?;
110        Ok(VerifiedAttestation::from_verified(att.clone()))
111    }
112
113    /// Verify an ordered attestation chain starting from a known root public key.
114    ///
115    /// Args:
116    /// * `attestations`: Ordered attestation chain (root first).
117    /// * `root_pk`: Raw Ed25519 public key of the root identity.
118    pub async fn verify_chain(
119        &self,
120        attestations: &[Attestation],
121        root_pk: &[u8],
122    ) -> Result<VerificationReport, AttestationError> {
123        verify::verify_chain_inner(
124            attestations,
125            root_pk,
126            self.provider.as_ref(),
127            self.clock.now(),
128        )
129        .await
130    }
131
132    /// Verify a chain and assert that all attestations share a required capability.
133    ///
134    /// Args:
135    /// * `attestations`: Ordered attestation chain (root first).
136    /// * `required`: The capability that must appear in every link.
137    /// * `root_pk`: Raw Ed25519 public key of the root identity.
138    pub async fn verify_chain_with_capability(
139        &self,
140        attestations: &[Attestation],
141        required: &Capability,
142        root_pk: &[u8],
143    ) -> Result<VerificationReport, AttestationError> {
144        let report = self.verify_chain(attestations, root_pk).await?;
145        if !report.is_valid() {
146            return Ok(report);
147        }
148        if attestations.is_empty() {
149            return Ok(report);
150        }
151
152        use std::collections::HashSet;
153        let mut effective: HashSet<Capability> =
154            attestations[0].capabilities.iter().cloned().collect();
155        for att in attestations.iter().skip(1) {
156            let att_caps: HashSet<Capability> = att.capabilities.iter().cloned().collect();
157            effective = effective.intersection(&att_caps).cloned().collect();
158        }
159        if !effective.contains(required) {
160            return Err(AttestationError::MissingCapability {
161                required: required.clone(),
162                available: effective.into_iter().collect(),
163            });
164        }
165        Ok(report)
166    }
167
168    /// Verify a chain and additionally validate witness receipts against a quorum threshold.
169    ///
170    /// Args:
171    /// * `attestations`: Ordered attestation chain (root first).
172    /// * `root_pk`: Raw Ed25519 public key of the root identity.
173    /// * `witness_config`: Witness receipts and quorum threshold to validate.
174    pub async fn verify_chain_with_witnesses(
175        &self,
176        attestations: &[Attestation],
177        root_pk: &[u8],
178        witness_config: &WitnessVerifyConfig<'_>,
179    ) -> Result<VerificationReport, AttestationError> {
180        let mut report = self.verify_chain(attestations, root_pk).await?;
181        if !report.is_valid() {
182            return Ok(report);
183        }
184
185        let quorum =
186            crate::witness::verify_witness_receipts(witness_config, self.provider.as_ref()).await;
187        if quorum.verified < quorum.required {
188            report.status = crate::types::VerificationStatus::InsufficientWitnesses {
189                required: quorum.required,
190                verified: quorum.verified,
191            };
192            report.warnings.push(format!(
193                "Witness quorum not met: {}/{} verified",
194                quorum.verified, quorum.required
195            ));
196        }
197        report.witness_quorum = Some(quorum);
198        Ok(report)
199    }
200
201    /// Verify that a specific device is authorized under a given identity.
202    ///
203    /// Args:
204    /// * `identity_did`: The DID of the authorizing identity.
205    /// * `device_did`: The device DID to check authorization for.
206    /// * `attestations`: Pool of attestations to search.
207    /// * `identity_pk`: Raw Ed25519 public key of the identity.
208    pub async fn verify_device_authorization(
209        &self,
210        identity_did: &str,
211        device_did: &DeviceDID,
212        attestations: &[Attestation],
213        identity_pk: &[u8],
214    ) -> Result<VerificationReport, AttestationError> {
215        verify::verify_device_authorization_inner(
216            identity_did,
217            device_did,
218            attestations,
219            identity_pk,
220            self.provider.as_ref(),
221            self.clock.now(),
222        )
223        .await
224    }
225}