#![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 clock;
pub mod commit;
pub mod commit_error;
pub mod commit_kel;
pub mod contract;
pub mod core;
pub mod credential;
pub mod duplicity;
pub mod error;
#[cfg(feature = "ffi")]
pub mod ffi;
pub mod presentation;
mod software_verify;
pub mod ssh_sig;
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 core::{
ATTESTATION_VERSION, Attestation, 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,
};
#[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,
};
pub use witness::{SignedReceipt, WitnessQuorum, WitnessReceiptResult, WitnessVerifyConfig};
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;
pub use commit_kel::{
ANCHOR_SEQ_TRAILER, CommitVerdict, SCOPE_TRAILER, VerifierWitnessPolicy, WitnessGateStatus,
WitnessedVerdict, anchor_seq_trailer, scope_trailer, verify_commit_against_kel,
verify_commit_against_kel_scoped, verify_commit_against_kel_witnessed,
};
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 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 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,
};
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()));
}
}