#![allow(clippy::disallowed_methods)]
#![deny(
clippy::print_stdout,
clippy::print_stderr,
clippy::exit,
clippy::dbg_macro
)]
#![deny(rustdoc::broken_intra_doc_links)]
#![warn(clippy::too_many_lines, clippy::cognitive_complexity)]
#![warn(missing_docs)]
pub mod action;
pub mod authorization_summary;
pub mod clock;
pub mod commit;
pub mod commit_bundle;
pub mod commit_error;
pub mod commit_kel;
pub mod contract;
pub mod core;
pub mod credential;
pub mod duplicity;
pub mod error;
pub mod evidence_pack;
#[cfg(feature = "ffi")]
pub mod ffi;
pub mod freshness;
pub mod oidc_policy;
pub mod org_bundle;
pub mod presentation;
mod software_verify;
pub mod ssh_sig;
pub mod tlog;
pub mod types;
pub mod verifier;
pub mod verify;
#[cfg(feature = "wasm")]
pub mod wasm;
pub mod witness;
pub use types::{
AssuranceLevel, AssuranceLevelParseError, CanonicalDid, ChainLink, DidConversionError,
DidParseError, IdentityDID, VerificationReport, VerificationStatus, signer_hex_to_did,
validate_did,
};
pub use action::ActionEnvelope;
pub use authorization_summary::AuthorizationSummary;
pub use core::{
ATTESTATION_VERSION, Attestation, BundleDeviceKel, Capability, CapabilityError, CommitOid,
CommitOidError, DevicePublicKey, EcdsaP256Error, EcdsaP256PublicKey, EcdsaP256Signature,
Ed25519KeyError, Ed25519PublicKey, Ed25519Signature, IdentityBundle, InvalidKeyError,
MAX_ATTESTATION_JSON_SIZE, MAX_JSON_BATCH_SIZE, OidcBinding, PolicyId, PublicKeyDecodeError,
PublicKeyHex, PublicKeyHexError, ResourceId, Role, RoleParseError, SignatureAlgorithm,
SignatureLengthError, SignatureVerifyError, ThresholdPolicy, TypedSignature,
VerifiedAttestation, decode_public_key_bytes, decode_public_key_hex,
};
pub use oidc_policy::{OidcPolicyError, OidcPolicyJoin, OidcSubjectPolicy};
#[cfg(any(test, feature = "test-utils"))]
pub use testing::AttestationBuilder;
#[cfg(any(test, feature = "test-utils"))]
pub use testing::MockClock;
pub use commit_error::CommitVerificationError;
pub use error::{AttestationError, AuthsErrorInfo};
pub use verifier::Verifier;
#[cfg(feature = "native")]
pub use verify::{
verify_at_time, verify_chain, verify_chain_with_witnesses, verify_device_authorization,
verify_with_keys,
};
pub use verify::{
DeviceLinkVerification, compute_attestation_seal_digest, is_device_listed, verify_device_link,
};
#[cfg(feature = "native")]
pub use witness::verify_build_attestation_offline;
pub use witness::{
OfflineBuildVerdict, OfflineReceiptVerdict, SignedReceipt, WitnessQuorum, WitnessReceiptResult,
WitnessVerifyConfig, verify_receipt_offline,
};
pub use auths_keri::{
Event as KeriEvent, IcpEvent as KeriIcpEvent, IxnEvent as KeriIxnEvent, KeriTypeError, Prefix,
RotEvent as KeriRotEvent, Said, Seal as KeriSeal, ValidationError, compute_said,
find_seal_in_kel, parse_kel_json,
};
pub use commit::{VerifiedCommit, commit_object_is_signed};
pub use commit_bundle::{
AuthenticatedDeviceKel, BundleTrust, BundleTrustError, verify_commit_with_bundle_json,
};
pub use commit_kel::{
ANCHOR_SEQ_TRAILER, CommitVerdict, DEVICE_TRAILER, ID_TRAILER, SCOPE_TRAILER,
VerifierWitnessPolicy, WitnessGateStatus, WitnessedVerdict, anchor_seq_trailer,
commit_signer_trailers, scope_trailer, verify_commit_against_kel,
verify_commit_against_kel_scoped, verify_commit_against_kel_witnessed,
verify_commit_against_kel_witnessed_scoped,
};
pub use ssh_sig::{SshKeyType, SshSigEnvelope};
pub use credential::{
CredentialVerdict, LifecycleEvent, SignedAcdc, verify_credential, verify_credential_sync,
};
pub use presentation::{
PresentationBinding, PresentationEnvelope, PresentationVerdict, verify_presentation,
verify_presentation_sync,
};
pub use freshness::{Freshness, FreshnessEvidence, FreshnessPolicy};
pub use contract::{SCHEMA_VERSION, verify_credential_json, verify_presentation_json};
pub use auths_crypto::CryptoProvider;
pub use auths_crypto::Hash256;
pub use clock::{ClockProvider, SystemClock};
#[cfg(any(test, feature = "test-utils"))]
pub mod testing;
#[cfg(test)]
mod tests {
use super::*;
use chrono::{TimeZone, Utc};
fn fixed_ts() -> chrono::DateTime<Utc> {
Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap()
}
#[test]
fn verification_report_is_valid_returns_true_for_valid_status() {
let report = VerificationReport::valid(vec![]);
assert!(report.is_valid());
}
#[test]
fn verification_report_is_valid_returns_false_for_expired_status() {
let report =
VerificationReport::with_status(VerificationStatus::Expired { at: fixed_ts() }, vec![]);
assert!(!report.is_valid());
}
#[test]
fn verification_report_is_valid_returns_false_for_revoked_status() {
let report = VerificationReport::with_status(
VerificationStatus::Revoked {
at: Some(fixed_ts()),
},
vec![],
);
assert!(!report.is_valid());
}
#[test]
fn verification_report_is_valid_returns_false_for_invalid_signature() {
let report = VerificationReport::with_status(
VerificationStatus::InvalidSignature { step: 0 },
vec![],
);
assert!(!report.is_valid());
}
#[test]
fn verification_report_is_valid_returns_false_for_broken_chain() {
let report = VerificationReport::with_status(
VerificationStatus::BrokenChain {
missing_link: "test".to_string(),
},
vec![],
);
assert!(!report.is_valid());
}
#[test]
fn is_valid_is_false_when_a_duplicity_warning_is_present() {
let report = VerificationReport::with_status(VerificationStatus::Valid, vec![])
.with_duplicity_warning(crate::duplicity::DuplicityReport::Diverging {
#[allow(clippy::disallowed_methods)]
shared_kel_prefix: crate::types::IdentityDID::new_unchecked(
"did:keri:EShared".to_string(),
),
seq: 2,
event_saids: vec!["ErotA".into(), "ErotB".into()],
});
assert!(matches!(report.status, VerificationStatus::Valid));
assert!(
!report.is_valid(),
"a diverging KEL must fail closed, not warn-and-pass"
);
}
#[test]
fn verification_report_serializes_to_expected_json() {
let chain = vec![
ChainLink::valid(
"did:key:issuer1".to_string(),
"did:key:subject1".to_string(),
),
ChainLink::invalid(
"did:key:issuer2".to_string(),
"did:key:subject2".to_string(),
"signature mismatch".to_string(),
),
];
let report = VerificationReport {
status: VerificationStatus::InvalidSignature { step: 1 },
chain,
warnings: vec!["Key expires soon".to_string()],
witness_quorum: None,
anchored: None,
duplicity_warning: None,
freshness: crate::freshness::Freshness::Unknown,
as_of: None,
};
let json = serde_json::to_string(&report).expect("serialization failed");
let parsed: serde_json::Value = serde_json::from_str(&json).expect("parse failed");
assert_eq!(parsed["status"]["type"], "InvalidSignature");
assert_eq!(parsed["status"]["step"], 1);
assert_eq!(parsed["chain"].as_array().unwrap().len(), 2);
assert_eq!(parsed["chain"][0]["issuer"], "did:key:issuer1");
assert_eq!(parsed["chain"][0]["valid"], true);
assert_eq!(parsed["chain"][1]["valid"], false);
assert_eq!(parsed["chain"][1]["error"], "signature mismatch");
assert_eq!(parsed["warnings"][0], "Key expires soon");
}
#[test]
fn verification_status_valid_serializes_correctly() {
let status = VerificationStatus::Valid;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, r#"{"type":"Valid"}"#);
}
#[test]
fn verification_status_expired_serializes_with_timestamp() {
let status = VerificationStatus::Expired {
at: chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
.unwrap()
.with_timezone(&Utc),
};
let json = serde_json::to_string(&status).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["type"], "Expired");
assert_eq!(parsed["at"], "2024-01-01T00:00:00Z");
}
#[test]
fn verification_status_revoked_serializes_with_optional_timestamp() {
let status = VerificationStatus::Revoked {
at: Some(
chrono::DateTime::parse_from_rfc3339("2024-06-15T12:00:00Z")
.unwrap()
.with_timezone(&Utc),
),
};
let json = serde_json::to_string(&status).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["type"], "Revoked");
assert_eq!(parsed["at"], "2024-06-15T12:00:00Z");
let status = VerificationStatus::Revoked { at: None };
let json = serde_json::to_string(&status).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["type"], "Revoked");
assert!(parsed["at"].is_null());
}
#[test]
fn chain_link_helpers_work() {
let valid = ChainLink::valid("issuer".to_string(), "subject".to_string());
assert!(valid.valid);
assert!(valid.error.is_none());
let invalid = ChainLink::invalid(
"issuer".to_string(),
"subject".to_string(),
"error".to_string(),
);
assert!(!invalid.valid);
assert_eq!(invalid.error, Some("error".to_string()));
}
}