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)]
11pub mod action;
52pub mod clock;
53pub mod commit;
54pub mod commit_error;
55pub mod core;
56pub mod error;
57#[cfg(feature = "ffi")]
59pub mod ffi;
60pub mod keri;
61pub mod ssh_sig;
62pub mod types;
63pub mod verifier;
64pub mod verify;
65#[cfg(feature = "wasm")]
67pub mod wasm;
68pub mod witness;
69
70pub use types::{
72 AssuranceLevel, AssuranceLevelParseError, CanonicalDid, ChainLink, DeviceDID,
73 DidConversionError, DidParseError, IdentityDID, VerificationReport, VerificationStatus,
74 signer_hex_to_did, validate_did,
75};
76
77pub use action::ActionEnvelope;
79
80pub use core::{
82 Attestation, Capability, CapabilityError, CommitOid, CommitOidError, Ed25519KeyError,
83 Ed25519PublicKey, Ed25519Signature, IdentityBundle, MAX_ATTESTATION_JSON_SIZE,
84 MAX_JSON_BATCH_SIZE, OidcBinding, PolicyId, PublicKeyHex, PublicKeyHexError, ResourceId, Role,
85 RoleParseError, SignatureLengthError, ThresholdPolicy, VerifiedAttestation,
86};
87
88#[cfg(any(test, feature = "test-utils"))]
90pub use testing::AttestationBuilder;
91
92#[cfg(any(test, feature = "test-utils"))]
93pub use testing::MockClock;
94
95pub use commit_error::CommitVerificationError;
97pub use error::{AttestationError, AuthsErrorInfo};
98
99pub use verifier::Verifier;
101
102#[cfg(feature = "native")]
104pub use verify::{
105 verify_at_time, verify_chain, verify_chain_with_capability, verify_chain_with_witnesses,
106 verify_device_authorization, verify_with_capability, verify_with_keys,
107};
108
109pub use verify::{
111 DeviceLinkVerification, compute_attestation_seal_digest, did_to_ed25519, is_device_listed,
112 verify_device_link,
113};
114
115pub use witness::{WitnessQuorum, WitnessReceipt, WitnessReceiptResult, WitnessVerifyConfig};
117
118pub use keri::{
120 IcpEvent as KeriIcpEvent, IxnEvent as KeriIxnEvent, KeriEvent, KeriKeyState, KeriTypeError,
121 KeriVerifyError, Prefix, RotEvent as KeriRotEvent, Said, Seal as KeriSeal, compute_said,
122 find_seal_in_kel, parse_kel_json, verify_kel,
123};
124
125pub use commit::VerifiedCommit;
127pub use ssh_sig::SshSigEnvelope;
128
129pub use auths_crypto::CryptoProvider;
131
132pub use clock::{ClockProvider, SystemClock};
134
135#[cfg(any(test, feature = "test-utils"))]
137pub mod testing;
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use chrono::{TimeZone, Utc};
143
144 fn fixed_ts() -> chrono::DateTime<Utc> {
145 Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap()
146 }
147
148 #[test]
149 fn verification_report_is_valid_returns_true_for_valid_status() {
150 let report = VerificationReport::valid(vec![]);
151 assert!(report.is_valid());
152 }
153
154 #[test]
155 fn verification_report_is_valid_returns_false_for_expired_status() {
156 let report =
157 VerificationReport::with_status(VerificationStatus::Expired { at: fixed_ts() }, vec![]);
158 assert!(!report.is_valid());
159 }
160
161 #[test]
162 fn verification_report_is_valid_returns_false_for_revoked_status() {
163 let report = VerificationReport::with_status(
164 VerificationStatus::Revoked {
165 at: Some(fixed_ts()),
166 },
167 vec![],
168 );
169 assert!(!report.is_valid());
170 }
171
172 #[test]
173 fn verification_report_is_valid_returns_false_for_invalid_signature() {
174 let report = VerificationReport::with_status(
175 VerificationStatus::InvalidSignature { step: 0 },
176 vec![],
177 );
178 assert!(!report.is_valid());
179 }
180
181 #[test]
182 fn verification_report_is_valid_returns_false_for_broken_chain() {
183 let report = VerificationReport::with_status(
184 VerificationStatus::BrokenChain {
185 missing_link: "test".to_string(),
186 },
187 vec![],
188 );
189 assert!(!report.is_valid());
190 }
191
192 #[test]
193 fn verification_report_serializes_to_expected_json() {
194 let chain = vec![
195 ChainLink::valid(
196 "did:key:issuer1".to_string(),
197 "did:key:subject1".to_string(),
198 ),
199 ChainLink::invalid(
200 "did:key:issuer2".to_string(),
201 "did:key:subject2".to_string(),
202 "signature mismatch".to_string(),
203 ),
204 ];
205
206 let report = VerificationReport {
207 status: VerificationStatus::InvalidSignature { step: 1 },
208 chain,
209 warnings: vec!["Key expires soon".to_string()],
210 witness_quorum: None,
211 };
212
213 let json = serde_json::to_string(&report).expect("serialization failed");
214
215 let parsed: serde_json::Value = serde_json::from_str(&json).expect("parse failed");
217
218 assert_eq!(parsed["status"]["type"], "InvalidSignature");
220 assert_eq!(parsed["status"]["step"], 1);
221
222 assert_eq!(parsed["chain"].as_array().unwrap().len(), 2);
224 assert_eq!(parsed["chain"][0]["issuer"], "did:key:issuer1");
225 assert_eq!(parsed["chain"][0]["valid"], true);
226 assert_eq!(parsed["chain"][1]["valid"], false);
227 assert_eq!(parsed["chain"][1]["error"], "signature mismatch");
228
229 assert_eq!(parsed["warnings"][0], "Key expires soon");
231 }
232
233 #[test]
234 fn verification_status_valid_serializes_correctly() {
235 let status = VerificationStatus::Valid;
236 let json = serde_json::to_string(&status).unwrap();
237 assert_eq!(json, r#"{"type":"Valid"}"#);
238 }
239
240 #[test]
241 fn verification_status_expired_serializes_with_timestamp() {
242 let status = VerificationStatus::Expired {
243 at: chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
244 .unwrap()
245 .with_timezone(&Utc),
246 };
247 let json = serde_json::to_string(&status).unwrap();
248 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
249 assert_eq!(parsed["type"], "Expired");
250 assert_eq!(parsed["at"], "2024-01-01T00:00:00Z");
251 }
252
253 #[test]
254 fn verification_status_revoked_serializes_with_optional_timestamp() {
255 let status = VerificationStatus::Revoked {
257 at: Some(
258 chrono::DateTime::parse_from_rfc3339("2024-06-15T12:00:00Z")
259 .unwrap()
260 .with_timezone(&Utc),
261 ),
262 };
263 let json = serde_json::to_string(&status).unwrap();
264 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
265 assert_eq!(parsed["type"], "Revoked");
266 assert_eq!(parsed["at"], "2024-06-15T12:00:00Z");
267
268 let status = VerificationStatus::Revoked { at: None };
270 let json = serde_json::to_string(&status).unwrap();
271 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
272 assert_eq!(parsed["type"], "Revoked");
273 assert!(parsed["at"].is_null());
274 }
275
276 #[test]
277 fn chain_link_helpers_work() {
278 let valid = ChainLink::valid("issuer".to_string(), "subject".to_string());
279 assert!(valid.valid);
280 assert!(valid.error.is_none());
281
282 let invalid = ChainLink::invalid(
283 "issuer".to_string(),
284 "subject".to_string(),
285 "error".to_string(),
286 );
287 assert!(!invalid.valid);
288 assert_eq!(invalid.error, Some("error".to_string()));
289 }
290}