1#![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)]
12pub mod action;
51pub mod authorization_summary;
53pub mod clock;
54pub mod commit;
55pub mod commit_bundle;
57pub mod commit_error;
58pub mod commit_kel;
59pub mod contract;
61pub mod core;
62pub mod credential;
63pub mod duplicity;
64pub mod error;
65pub mod evidence_pack;
67#[cfg(feature = "ffi")]
69pub mod ffi;
70pub mod freshness;
72pub mod oidc_policy;
74pub mod org_bundle;
76pub mod presentation;
77mod software_verify;
78pub mod ssh_sig;
79pub mod tlog;
81pub mod types;
82pub mod verifier;
83pub mod verify;
84#[cfg(feature = "wasm")]
86pub mod wasm;
87pub mod witness;
88
89pub use types::{
91 AssuranceLevel, AssuranceLevelParseError, CanonicalDid, ChainLink, DidConversionError,
92 DidParseError, IdentityDID, VerificationReport, VerificationStatus, signer_hex_to_did,
93 validate_did,
94};
95
96pub use action::ActionEnvelope;
98
99pub use authorization_summary::AuthorizationSummary;
101
102pub use core::{
104 ATTESTATION_VERSION, Attestation, BundleDeviceKel, Capability, CapabilityError, CommitOid,
105 CommitOidError, DevicePublicKey, EcdsaP256Error, EcdsaP256PublicKey, EcdsaP256Signature,
106 Ed25519KeyError, Ed25519PublicKey, Ed25519Signature, IdentityBundle, InvalidKeyError,
107 MAX_ATTESTATION_JSON_SIZE, MAX_JSON_BATCH_SIZE, OidcBinding, PolicyId, PublicKeyDecodeError,
108 PublicKeyHex, PublicKeyHexError, ResourceId, Role, RoleParseError, SignatureAlgorithm,
109 SignatureLengthError, SignatureVerifyError, ThresholdPolicy, TypedSignature,
110 VerifiedAttestation, decode_public_key_bytes, decode_public_key_hex,
111};
112
113pub use oidc_policy::{OidcPolicyError, OidcPolicyJoin, OidcSubjectPolicy};
115
116#[cfg(any(test, feature = "test-utils"))]
118pub use testing::AttestationBuilder;
119
120#[cfg(any(test, feature = "test-utils"))]
121pub use testing::MockClock;
122
123pub use commit_error::CommitVerificationError;
125pub use error::{AttestationError, AuthsErrorInfo};
126
127pub use verifier::Verifier;
129
130#[cfg(feature = "native")]
132pub use verify::{
133 verify_at_time, verify_chain, verify_chain_with_witnesses, verify_device_authorization,
134 verify_with_keys,
135};
136
137pub use verify::{
139 DeviceLinkVerification, compute_attestation_seal_digest, is_device_listed, verify_device_link,
140};
141
142#[cfg(feature = "native")]
144pub use witness::verify_build_attestation_offline;
145pub use witness::{
146 OfflineBuildVerdict, OfflineReceiptVerdict, SignedReceipt, WitnessQuorum, WitnessReceiptResult,
147 WitnessVerifyConfig, verify_receipt_offline,
148};
149
150pub use auths_keri::{
152 Event as KeriEvent, IcpEvent as KeriIcpEvent, IxnEvent as KeriIxnEvent, KeriTypeError, Prefix,
153 RotEvent as KeriRotEvent, Said, Seal as KeriSeal, ValidationError, compute_said,
154 find_seal_in_kel, parse_kel_json,
155};
156
157pub use commit::{VerifiedCommit, commit_object_is_signed};
159pub use commit_bundle::{
160 AuthenticatedDeviceKel, BundleTrust, BundleTrustError, verify_commit_with_bundle_json,
161};
162pub use commit_kel::{
163 ANCHOR_SEQ_TRAILER, CommitVerdict, DEVICE_TRAILER, ID_TRAILER, SCOPE_TRAILER,
164 VerifierWitnessPolicy, WitnessGateStatus, WitnessedVerdict, anchor_seq_trailer,
165 commit_signer_trailers, scope_trailer, verify_commit_against_kel,
166 verify_commit_against_kel_scoped, verify_commit_against_kel_witnessed,
167 verify_commit_against_kel_witnessed_scoped,
168};
169pub use ssh_sig::{SshKeyType, SshSigEnvelope};
170
171pub use credential::{
175 CredentialVerdict, LifecycleEvent, SignedAcdc, verify_credential, verify_credential_sync,
176};
177
178pub use presentation::{
181 PresentationBinding, PresentationEnvelope, PresentationVerdict, verify_presentation,
182 verify_presentation_sync,
183};
184
185pub use freshness::{Freshness, FreshnessEvidence, FreshnessPolicy};
188
189pub use contract::{SCHEMA_VERSION, verify_credential_json, verify_presentation_json};
192
193pub use auths_crypto::CryptoProvider;
195pub use auths_crypto::Hash256;
196
197pub use clock::{ClockProvider, SystemClock};
199
200#[cfg(any(test, feature = "test-utils"))]
202pub mod testing;
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use chrono::{TimeZone, Utc};
208
209 fn fixed_ts() -> chrono::DateTime<Utc> {
210 Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap()
211 }
212
213 #[test]
214 fn verification_report_is_valid_returns_true_for_valid_status() {
215 let report = VerificationReport::valid(vec![]);
216 assert!(report.is_valid());
217 }
218
219 #[test]
220 fn verification_report_is_valid_returns_false_for_expired_status() {
221 let report =
222 VerificationReport::with_status(VerificationStatus::Expired { at: fixed_ts() }, vec![]);
223 assert!(!report.is_valid());
224 }
225
226 #[test]
227 fn verification_report_is_valid_returns_false_for_revoked_status() {
228 let report = VerificationReport::with_status(
229 VerificationStatus::Revoked {
230 at: Some(fixed_ts()),
231 },
232 vec![],
233 );
234 assert!(!report.is_valid());
235 }
236
237 #[test]
238 fn verification_report_is_valid_returns_false_for_invalid_signature() {
239 let report = VerificationReport::with_status(
240 VerificationStatus::InvalidSignature { step: 0 },
241 vec![],
242 );
243 assert!(!report.is_valid());
244 }
245
246 #[test]
247 fn verification_report_is_valid_returns_false_for_broken_chain() {
248 let report = VerificationReport::with_status(
249 VerificationStatus::BrokenChain {
250 missing_link: "test".to_string(),
251 },
252 vec![],
253 );
254 assert!(!report.is_valid());
255 }
256
257 #[test]
258 fn is_valid_is_false_when_a_duplicity_warning_is_present() {
259 let report = VerificationReport::with_status(VerificationStatus::Valid, vec![])
262 .with_duplicity_warning(crate::duplicity::DuplicityReport::Diverging {
263 #[allow(clippy::disallowed_methods)]
264 shared_kel_prefix: crate::types::IdentityDID::new_unchecked(
265 "did:keri:EShared".to_string(),
266 ),
267 seq: 2,
268 event_saids: vec!["ErotA".into(), "ErotB".into()],
269 });
270 assert!(matches!(report.status, VerificationStatus::Valid));
271 assert!(
272 !report.is_valid(),
273 "a diverging KEL must fail closed, not warn-and-pass"
274 );
275 }
276
277 #[test]
278 fn verification_report_serializes_to_expected_json() {
279 let chain = vec![
280 ChainLink::valid(
281 "did:key:issuer1".to_string(),
282 "did:key:subject1".to_string(),
283 ),
284 ChainLink::invalid(
285 "did:key:issuer2".to_string(),
286 "did:key:subject2".to_string(),
287 "signature mismatch".to_string(),
288 ),
289 ];
290
291 let report = VerificationReport {
292 status: VerificationStatus::InvalidSignature { step: 1 },
293 chain,
294 warnings: vec!["Key expires soon".to_string()],
295 witness_quorum: None,
296 anchored: None,
297 duplicity_warning: None,
298 freshness: crate::freshness::Freshness::Unknown,
299 as_of: None,
300 };
301
302 let json = serde_json::to_string(&report).expect("serialization failed");
303
304 let parsed: serde_json::Value = serde_json::from_str(&json).expect("parse failed");
306
307 assert_eq!(parsed["status"]["type"], "InvalidSignature");
309 assert_eq!(parsed["status"]["step"], 1);
310
311 assert_eq!(parsed["chain"].as_array().unwrap().len(), 2);
313 assert_eq!(parsed["chain"][0]["issuer"], "did:key:issuer1");
314 assert_eq!(parsed["chain"][0]["valid"], true);
315 assert_eq!(parsed["chain"][1]["valid"], false);
316 assert_eq!(parsed["chain"][1]["error"], "signature mismatch");
317
318 assert_eq!(parsed["warnings"][0], "Key expires soon");
320 }
321
322 #[test]
323 fn verification_status_valid_serializes_correctly() {
324 let status = VerificationStatus::Valid;
325 let json = serde_json::to_string(&status).unwrap();
326 assert_eq!(json, r#"{"type":"Valid"}"#);
327 }
328
329 #[test]
330 fn verification_status_expired_serializes_with_timestamp() {
331 let status = VerificationStatus::Expired {
332 at: chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
333 .unwrap()
334 .with_timezone(&Utc),
335 };
336 let json = serde_json::to_string(&status).unwrap();
337 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
338 assert_eq!(parsed["type"], "Expired");
339 assert_eq!(parsed["at"], "2024-01-01T00:00:00Z");
340 }
341
342 #[test]
343 fn verification_status_revoked_serializes_with_optional_timestamp() {
344 let status = VerificationStatus::Revoked {
346 at: Some(
347 chrono::DateTime::parse_from_rfc3339("2024-06-15T12:00:00Z")
348 .unwrap()
349 .with_timezone(&Utc),
350 ),
351 };
352 let json = serde_json::to_string(&status).unwrap();
353 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
354 assert_eq!(parsed["type"], "Revoked");
355 assert_eq!(parsed["at"], "2024-06-15T12:00:00Z");
356
357 let status = VerificationStatus::Revoked { at: None };
359 let json = serde_json::to_string(&status).unwrap();
360 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
361 assert_eq!(parsed["type"], "Revoked");
362 assert!(parsed["at"].is_null());
363 }
364
365 #[test]
366 fn chain_link_helpers_work() {
367 let valid = ChainLink::valid("issuer".to_string(), "subject".to_string());
368 assert!(valid.valid);
369 assert!(valid.error.is_none());
370
371 let invalid = ChainLink::invalid(
372 "issuer".to_string(),
373 "subject".to_string(),
374 "error".to_string(),
375 );
376 assert!(!invalid.valid);
377 assert_eq!(invalid.error, Some("error".to_string()));
378 }
379}