Skip to main content

auths_verifier/
lib.rs

1#![deny(
2    clippy::print_stdout,
3    clippy::print_stderr,
4    clippy::exit,
5    clippy::dbg_macro
6)]
7#![deny(clippy::disallowed_methods)]
8#![deny(rustdoc::broken_intra_doc_links)]
9#![warn(clippy::too_many_lines, clippy::cognitive_complexity)]
10#![warn(missing_docs)]
11//! # auths-verifier
12//!
13//! Attestation verification library for Auths.
14//!
15//! This crate provides signature and chain verification without requiring
16//! access to private keys or platform keychains. It's designed to be:
17//! - **Lightweight** — minimal dependencies
18//! - **Cross-platform** — works on any target including WASM
19//! - **FFI-friendly** — C-compatible interface available
20//!
21//! ## Quick Start
22//!
23//! ```rust,ignore
24//! use auths_verifier::{verify_chain, VerificationStatus};
25//!
26//! let report = verify_chain(&attestations)?;
27//!
28//! match report.status {
29//!     VerificationStatus::Valid => println!("Chain verified!"),
30//!     VerificationStatus::Expired { at } => println!("Expired at {}", at),
31//!     VerificationStatus::InvalidSignature { step } => {
32//!         println!("Bad signature at step {}", step);
33//!     }
34//!     _ => println!("Verification failed"),
35//! }
36//! ```
37//!
38//! ## With Capability Checking
39//!
40//! ```rust,ignore
41//! use auths_verifier::{verify_with_capability, Capability};
42//!
43//! // Verify device has sign-commit permission
44//! let report = verify_with_capability(&chain, Capability::SignCommit)?;
45//! ```
46//!
47//! ## Feature Flags
48//!
49//! - `wasm` — Enable WASM bindings via wasm-bindgen
50
51pub mod clock;
52pub mod core;
53pub mod error;
54/// C-compatible FFI bindings for attestation and chain verification.
55#[cfg(feature = "ffi")]
56pub mod ffi;
57pub mod keri;
58pub mod types;
59pub mod verifier;
60pub mod verify;
61/// WASM bindings for browser and edge-runtime verification.
62#[cfg(feature = "wasm")]
63pub mod wasm;
64pub mod witness;
65
66// Re-export verification types for convenience
67pub use types::{ChainLink, DeviceDID, IdentityDID, VerificationReport, VerificationStatus};
68
69// Re-export core types
70pub use core::{
71    Capability, CapabilityError, Ed25519KeyError, Ed25519PublicKey, Ed25519Signature,
72    IdentityBundle, MAX_ATTESTATION_JSON_SIZE, MAX_JSON_BATCH_SIZE, ResourceId, Role,
73    RoleParseError, SignatureLengthError, ThresholdPolicy, VerifiedAttestation,
74};
75
76// Re-export error types
77pub use error::{AttestationError, AuthsErrorInfo};
78
79// Re-export Verifier struct
80pub use verifier::Verifier;
81
82// Re-export verification functions (native-only, async)
83#[cfg(feature = "native")]
84pub use verify::{
85    verify_at_time, verify_chain, verify_chain_with_capability, verify_chain_with_witnesses,
86    verify_device_authorization, verify_with_capability, verify_with_keys,
87};
88
89// Re-export sync utility functions (always available)
90pub use verify::{
91    DeviceLinkVerification, compute_attestation_seal_digest, did_to_ed25519, is_device_listed,
92    verify_device_link,
93};
94
95// Re-export witness types
96pub use witness::{WitnessQuorum, WitnessReceipt, WitnessReceiptResult, WitnessVerifyConfig};
97
98// Re-export KERI verification types (key parsing lives in auths-crypto)
99pub use keri::{
100    IcpEvent as KeriIcpEvent, IxnEvent as KeriIxnEvent, KeriEvent, KeriKeyState, KeriTypeError,
101    KeriVerifyError, Prefix, RotEvent as KeriRotEvent, Said, Seal as KeriSeal, compute_said,
102    find_seal_in_kel, parse_kel_json, verify_kel,
103};
104
105// Re-export crypto provider trait for downstream consumers
106pub use auths_crypto::CryptoProvider;
107
108// Re-export clock types for downstream consumers (auths-core re-exports from here)
109pub use clock::{ClockProvider, SystemClock};
110
111/// Test utilities for auths-verifier consumers (behind `test-utils` feature).
112#[cfg(any(test, feature = "test-utils"))]
113pub mod testing;
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use chrono::{TimeZone, Utc};
119
120    fn fixed_ts() -> chrono::DateTime<Utc> {
121        Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap()
122    }
123
124    #[test]
125    fn verification_report_is_valid_returns_true_for_valid_status() {
126        let report = VerificationReport::valid(vec![]);
127        assert!(report.is_valid());
128    }
129
130    #[test]
131    fn verification_report_is_valid_returns_false_for_expired_status() {
132        let report =
133            VerificationReport::with_status(VerificationStatus::Expired { at: fixed_ts() }, vec![]);
134        assert!(!report.is_valid());
135    }
136
137    #[test]
138    fn verification_report_is_valid_returns_false_for_revoked_status() {
139        let report = VerificationReport::with_status(
140            VerificationStatus::Revoked {
141                at: Some(fixed_ts()),
142            },
143            vec![],
144        );
145        assert!(!report.is_valid());
146    }
147
148    #[test]
149    fn verification_report_is_valid_returns_false_for_invalid_signature() {
150        let report = VerificationReport::with_status(
151            VerificationStatus::InvalidSignature { step: 0 },
152            vec![],
153        );
154        assert!(!report.is_valid());
155    }
156
157    #[test]
158    fn verification_report_is_valid_returns_false_for_broken_chain() {
159        let report = VerificationReport::with_status(
160            VerificationStatus::BrokenChain {
161                missing_link: "test".to_string(),
162            },
163            vec![],
164        );
165        assert!(!report.is_valid());
166    }
167
168    #[test]
169    fn verification_report_serializes_to_expected_json() {
170        let chain = vec![
171            ChainLink::valid(
172                "did:key:issuer1".to_string(),
173                "did:key:subject1".to_string(),
174            ),
175            ChainLink::invalid(
176                "did:key:issuer2".to_string(),
177                "did:key:subject2".to_string(),
178                "signature mismatch".to_string(),
179            ),
180        ];
181
182        let report = VerificationReport {
183            status: VerificationStatus::InvalidSignature { step: 1 },
184            chain,
185            warnings: vec!["Key expires soon".to_string()],
186            witness_quorum: None,
187        };
188
189        let json = serde_json::to_string(&report).expect("serialization failed");
190
191        // Verify structure
192        let parsed: serde_json::Value = serde_json::from_str(&json).expect("parse failed");
193
194        // Check status has "type" tag
195        assert_eq!(parsed["status"]["type"], "InvalidSignature");
196        assert_eq!(parsed["status"]["step"], 1);
197
198        // Check chain structure
199        assert_eq!(parsed["chain"].as_array().unwrap().len(), 2);
200        assert_eq!(parsed["chain"][0]["issuer"], "did:key:issuer1");
201        assert_eq!(parsed["chain"][0]["valid"], true);
202        assert_eq!(parsed["chain"][1]["valid"], false);
203        assert_eq!(parsed["chain"][1]["error"], "signature mismatch");
204
205        // Check warnings
206        assert_eq!(parsed["warnings"][0], "Key expires soon");
207    }
208
209    #[test]
210    fn verification_status_valid_serializes_correctly() {
211        let status = VerificationStatus::Valid;
212        let json = serde_json::to_string(&status).unwrap();
213        assert_eq!(json, r#"{"type":"Valid"}"#);
214    }
215
216    #[test]
217    fn verification_status_expired_serializes_with_timestamp() {
218        let status = VerificationStatus::Expired {
219            at: chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
220                .unwrap()
221                .with_timezone(&Utc),
222        };
223        let json = serde_json::to_string(&status).unwrap();
224        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
225        assert_eq!(parsed["type"], "Expired");
226        assert_eq!(parsed["at"], "2024-01-01T00:00:00Z");
227    }
228
229    #[test]
230    fn verification_status_revoked_serializes_with_optional_timestamp() {
231        // With timestamp
232        let status = VerificationStatus::Revoked {
233            at: Some(
234                chrono::DateTime::parse_from_rfc3339("2024-06-15T12:00:00Z")
235                    .unwrap()
236                    .with_timezone(&Utc),
237            ),
238        };
239        let json = serde_json::to_string(&status).unwrap();
240        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
241        assert_eq!(parsed["type"], "Revoked");
242        assert_eq!(parsed["at"], "2024-06-15T12:00:00Z");
243
244        // Without timestamp
245        let status = VerificationStatus::Revoked { at: None };
246        let json = serde_json::to_string(&status).unwrap();
247        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
248        assert_eq!(parsed["type"], "Revoked");
249        assert!(parsed["at"].is_null());
250    }
251
252    #[test]
253    fn chain_link_helpers_work() {
254        let valid = ChainLink::valid("issuer".to_string(), "subject".to_string());
255        assert!(valid.valid);
256        assert!(valid.error.is_none());
257
258        let invalid = ChainLink::invalid(
259            "issuer".to_string(),
260            "subject".to_string(),
261            "error".to_string(),
262        );
263        assert!(!invalid.valid);
264        assert_eq!(invalid.error, Some("error".to_string()));
265    }
266}