Skip to main content

auths_verifier/
lib.rs

1// crate-level allow during curve-agnostic refactor.
2#![allow(clippy::disallowed_methods)]
3#![deny(
4    clippy::print_stdout,
5    clippy::print_stderr,
6    clippy::exit,
7    clippy::dbg_macro
8)]
9#![deny(rustdoc::broken_intra_doc_links)]
10#![warn(clippy::too_many_lines, clippy::cognitive_complexity)]
11#![warn(missing_docs)]
12//! # auths-verifier
13//!
14//! Attestation verification library for Auths.
15//!
16//! This crate provides signature and chain verification without requiring
17//! access to private keys or platform keychains. It's designed to be:
18//! - **Lightweight** — minimal dependencies
19//! - **Cross-platform** — works on any target including WASM
20//! - **FFI-friendly** — C-compatible interface available
21//!
22//! ## Quick Start
23//!
24//! ```rust,ignore
25//! use auths_verifier::{verify_chain, VerificationStatus};
26//!
27//! let report = verify_chain(&attestations)?;
28//!
29//! match report.status {
30//!     VerificationStatus::Valid => println!("Chain verified!"),
31//!     VerificationStatus::Expired { at } => println!("Expired at {}", at),
32//!     VerificationStatus::InvalidSignature { step } => {
33//!         println!("Bad signature at step {}", step);
34//!     }
35//!     _ => println!("Verification failed"),
36//! }
37//! ```
38//!
39//! ## Authority via Credentials
40//!
41//! The verifier checks **authenticity** only — signatures, chain linkage, expiry,
42//! and witness quorum. Capability/role **authority** is no longer read from the
43//! attestation; it flows exclusively through a holder-verified ACDC credential
44//! presentation (see `auths_id::policy::context_from_credential`).
45//!
46//! ## Feature Flags
47//!
48//! - `wasm` — Enable WASM bindings via wasm-bindgen
49
50pub mod action;
51pub mod clock;
52pub mod commit;
53pub mod commit_error;
54pub mod commit_kel;
55/// The single cross-boundary verify contract (JSON request → tagged verdict).
56pub mod contract;
57pub mod core;
58pub mod credential;
59pub mod duplicity;
60pub mod error;
61/// C-compatible FFI bindings for attestation and chain verification.
62#[cfg(feature = "ffi")]
63pub mod ffi;
64pub mod presentation;
65mod software_verify;
66pub mod ssh_sig;
67pub mod types;
68pub mod verifier;
69pub mod verify;
70/// WASM bindings for browser and edge-runtime verification.
71#[cfg(feature = "wasm")]
72pub mod wasm;
73pub mod witness;
74
75// Re-export verification types for convenience
76pub use types::{
77    AssuranceLevel, AssuranceLevelParseError, CanonicalDid, ChainLink, DidConversionError,
78    DidParseError, IdentityDID, VerificationReport, VerificationStatus, signer_hex_to_did,
79    validate_did,
80};
81
82// Re-export action envelope
83pub use action::ActionEnvelope;
84
85// Re-export core types
86pub use core::{
87    ATTESTATION_VERSION, Attestation, Capability, CapabilityError, CommitOid, CommitOidError,
88    DevicePublicKey, EcdsaP256Error, EcdsaP256PublicKey, EcdsaP256Signature, Ed25519KeyError,
89    Ed25519PublicKey, Ed25519Signature, IdentityBundle, InvalidKeyError, MAX_ATTESTATION_JSON_SIZE,
90    MAX_JSON_BATCH_SIZE, OidcBinding, PolicyId, PublicKeyDecodeError, PublicKeyHex,
91    PublicKeyHexError, ResourceId, Role, RoleParseError, SignatureAlgorithm, SignatureLengthError,
92    SignatureVerifyError, ThresholdPolicy, TypedSignature, VerifiedAttestation,
93    decode_public_key_bytes, decode_public_key_hex,
94};
95
96// Re-export test utilities
97#[cfg(any(test, feature = "test-utils"))]
98pub use testing::AttestationBuilder;
99
100#[cfg(any(test, feature = "test-utils"))]
101pub use testing::MockClock;
102
103// Re-export error types
104pub use commit_error::CommitVerificationError;
105pub use error::{AttestationError, AuthsErrorInfo};
106
107// Re-export Verifier struct
108pub use verifier::Verifier;
109
110// Re-export verification functions (native-only, async)
111#[cfg(feature = "native")]
112pub use verify::{
113    verify_at_time, verify_chain, verify_chain_with_witnesses, verify_device_authorization,
114    verify_with_keys,
115};
116
117// Re-export sync utility functions (always available)
118pub use verify::{
119    DeviceLinkVerification, compute_attestation_seal_digest, is_device_listed, verify_device_link,
120};
121
122// Re-export witness types
123pub use witness::{SignedReceipt, WitnessQuorum, WitnessReceiptResult, WitnessVerifyConfig};
124
125// Re-export KERI types directly from auths-keri
126pub use auths_keri::{
127    Event as KeriEvent, IcpEvent as KeriIcpEvent, IxnEvent as KeriIxnEvent, KeriTypeError, Prefix,
128    RotEvent as KeriRotEvent, Said, Seal as KeriSeal, ValidationError, compute_said,
129    find_seal_in_kel, parse_kel_json,
130};
131
132// Re-export commit verification types
133pub use commit::VerifiedCommit;
134pub use commit_kel::{
135    ANCHOR_SEQ_TRAILER, CommitVerdict, SCOPE_TRAILER, VerifierWitnessPolicy, WitnessGateStatus,
136    WitnessedVerdict, anchor_seq_trailer, scope_trailer, verify_commit_against_kel,
137    verify_commit_against_kel_scoped, verify_commit_against_kel_witnessed,
138};
139pub use ssh_sig::{SshKeyType, SshSigEnvelope};
140
141// Re-export ACDC credential verification (Epic F.5). The `_sync` entrypoint is the
142// executor-free core every non-Rust binding target (C-ABI, WASM, Node, Python, Go)
143// calls directly; the `async fn` is a thin wrapper kept for native Rust callers.
144pub use credential::{
145    CredentialVerdict, LifecycleEvent, SignedAcdc, verify_credential, verify_credential_sync,
146};
147
148// Re-export holder-binding presentation verification (Epic F.8). `verify_presentation_sync`
149// is the executor-free core; `verify_presentation` is the thin async wrapper over it.
150pub use presentation::{
151    PresentationBinding, PresentationEnvelope, PresentationVerdict, verify_presentation,
152    verify_presentation_sync,
153};
154
155// Re-export the cross-boundary JSON verify contract (Epic D2). One bundled request in, one
156// tagged discriminated-union verdict out — the single surface FFI/WASM/Node/Python/Go share.
157pub use contract::{SCHEMA_VERSION, verify_credential_json, verify_presentation_json};
158
159// Re-export crypto provider trait for downstream consumers
160pub use auths_crypto::CryptoProvider;
161pub use auths_crypto::Hash256;
162
163// Re-export clock types for downstream consumers (auths-core re-exports from here)
164pub use clock::{ClockProvider, SystemClock};
165
166/// Test utilities for auths-verifier consumers (behind `test-utils` feature).
167#[cfg(any(test, feature = "test-utils"))]
168pub mod testing;
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use chrono::{TimeZone, Utc};
174
175    fn fixed_ts() -> chrono::DateTime<Utc> {
176        Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap()
177    }
178
179    #[test]
180    fn verification_report_is_valid_returns_true_for_valid_status() {
181        let report = VerificationReport::valid(vec![]);
182        assert!(report.is_valid());
183    }
184
185    #[test]
186    fn verification_report_is_valid_returns_false_for_expired_status() {
187        let report =
188            VerificationReport::with_status(VerificationStatus::Expired { at: fixed_ts() }, vec![]);
189        assert!(!report.is_valid());
190    }
191
192    #[test]
193    fn verification_report_is_valid_returns_false_for_revoked_status() {
194        let report = VerificationReport::with_status(
195            VerificationStatus::Revoked {
196                at: Some(fixed_ts()),
197            },
198            vec![],
199        );
200        assert!(!report.is_valid());
201    }
202
203    #[test]
204    fn verification_report_is_valid_returns_false_for_invalid_signature() {
205        let report = VerificationReport::with_status(
206            VerificationStatus::InvalidSignature { step: 0 },
207            vec![],
208        );
209        assert!(!report.is_valid());
210    }
211
212    #[test]
213    fn verification_report_is_valid_returns_false_for_broken_chain() {
214        let report = VerificationReport::with_status(
215            VerificationStatus::BrokenChain {
216                missing_link: "test".to_string(),
217            },
218            vec![],
219        );
220        assert!(!report.is_valid());
221    }
222
223    #[test]
224    fn verification_report_serializes_to_expected_json() {
225        let chain = vec![
226            ChainLink::valid(
227                "did:key:issuer1".to_string(),
228                "did:key:subject1".to_string(),
229            ),
230            ChainLink::invalid(
231                "did:key:issuer2".to_string(),
232                "did:key:subject2".to_string(),
233                "signature mismatch".to_string(),
234            ),
235        ];
236
237        let report = VerificationReport {
238            status: VerificationStatus::InvalidSignature { step: 1 },
239            chain,
240            warnings: vec!["Key expires soon".to_string()],
241            witness_quorum: None,
242            anchored: None,
243            duplicity_warning: None,
244        };
245
246        let json = serde_json::to_string(&report).expect("serialization failed");
247
248        // Verify structure
249        let parsed: serde_json::Value = serde_json::from_str(&json).expect("parse failed");
250
251        // Check status has "type" tag
252        assert_eq!(parsed["status"]["type"], "InvalidSignature");
253        assert_eq!(parsed["status"]["step"], 1);
254
255        // Check chain structure
256        assert_eq!(parsed["chain"].as_array().unwrap().len(), 2);
257        assert_eq!(parsed["chain"][0]["issuer"], "did:key:issuer1");
258        assert_eq!(parsed["chain"][0]["valid"], true);
259        assert_eq!(parsed["chain"][1]["valid"], false);
260        assert_eq!(parsed["chain"][1]["error"], "signature mismatch");
261
262        // Check warnings
263        assert_eq!(parsed["warnings"][0], "Key expires soon");
264    }
265
266    #[test]
267    fn verification_status_valid_serializes_correctly() {
268        let status = VerificationStatus::Valid;
269        let json = serde_json::to_string(&status).unwrap();
270        assert_eq!(json, r#"{"type":"Valid"}"#);
271    }
272
273    #[test]
274    fn verification_status_expired_serializes_with_timestamp() {
275        let status = VerificationStatus::Expired {
276            at: chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
277                .unwrap()
278                .with_timezone(&Utc),
279        };
280        let json = serde_json::to_string(&status).unwrap();
281        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
282        assert_eq!(parsed["type"], "Expired");
283        assert_eq!(parsed["at"], "2024-01-01T00:00:00Z");
284    }
285
286    #[test]
287    fn verification_status_revoked_serializes_with_optional_timestamp() {
288        // With timestamp
289        let status = VerificationStatus::Revoked {
290            at: Some(
291                chrono::DateTime::parse_from_rfc3339("2024-06-15T12:00:00Z")
292                    .unwrap()
293                    .with_timezone(&Utc),
294            ),
295        };
296        let json = serde_json::to_string(&status).unwrap();
297        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
298        assert_eq!(parsed["type"], "Revoked");
299        assert_eq!(parsed["at"], "2024-06-15T12:00:00Z");
300
301        // Without timestamp
302        let status = VerificationStatus::Revoked { at: None };
303        let json = serde_json::to_string(&status).unwrap();
304        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
305        assert_eq!(parsed["type"], "Revoked");
306        assert!(parsed["at"].is_null());
307    }
308
309    #[test]
310    fn chain_link_helpers_work() {
311        let valid = ChainLink::valid("issuer".to_string(), "subject".to_string());
312        assert!(valid.valid);
313        assert!(valid.error.is_none());
314
315        let invalid = ChainLink::invalid(
316            "issuer".to_string(),
317            "subject".to_string(),
318            "error".to_string(),
319        );
320        assert!(!invalid.valid);
321        assert_eq!(invalid.error, Some("error".to_string()));
322    }
323}