pub mod card;
pub mod eligibility;
pub mod match_code;
pub mod requirements;
pub mod statement;
pub mod status;
pub mod ticket_uri;
use affinidi_data_integrity::DataIntegrityProof;
use serde_json::Value;
use crate::trust_task_proof::TrustTaskVmResolver;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum VettingError {
#[error("malformed {what}")]
Malformed {
what: &'static str,
detail: String,
},
#[error("{0} does not match this session")]
Binding(&'static str),
#[error("{0} is outside its validity window")]
Expired(&'static str),
#[error("{what} is not signed by its {role}")]
WrongSigner {
what: &'static str,
role: &'static str,
},
#[error("{what} proof verification failed")]
Proof {
what: &'static str,
detail: String,
},
#[error("identity commitment does not recompute from the card")]
Commitment,
#[error("required claim `{0}` is missing")]
MissingClaim(String),
#[error("no role credential names this vetter in that role for this community")]
NoRoleCredential,
#[error("{what} version `{version}` is not supported")]
UnsupportedVersion {
what: &'static str,
version: String,
},
#[error("signing failed")]
Sign(String),
#[error("digest computation failed")]
Digest(String),
#[error("no randomness available")]
Random(String),
}
impl VettingError {
#[must_use]
pub fn cause(&self) -> Option<&str> {
match self {
Self::Malformed { detail, .. }
| Self::Proof { detail, .. }
| Self::Sign(detail)
| Self::Digest(detail)
| Self::Random(detail) => Some(detail),
_ => None,
}
}
}
pub(crate) fn did_of(vm: &str) -> &str {
vm.split('#').next().unwrap_or_default()
}
pub(crate) async fn verify_attached_proof(
what: &'static str,
signed: &Value,
expected_purpose: &str,
resolver: &TrustTaskVmResolver,
) -> Result<String, VettingError> {
let proof_value = signed.get("proof").ok_or(VettingError::Proof {
what,
detail: "no proof".into(),
})?;
let proof: DataIntegrityProof =
serde_json::from_value(proof_value.clone()).map_err(|e| VettingError::Proof {
what,
detail: format!("not a Data Integrity proof: {e}"),
})?;
if proof.proof_purpose != expected_purpose {
return Err(VettingError::Proof {
what,
detail: format!(
"proofPurpose `{}`, expected `{expected_purpose}`",
proof.proof_purpose
),
});
}
let mut unsigned = signed.clone();
if let Some(map) = unsigned.as_object_mut() {
map.remove("proof");
}
proof
.verify(
&unsigned,
resolver,
affinidi_data_integrity::VerifyOptions::new(),
)
.await
.map_err(|e| VettingError::Proof {
what,
detail: e.to_string(),
})?;
Ok(did_of(&proof.verification_method).to_string())
}
pub(crate) fn digest(value: &Value) -> Result<String, VettingError> {
dtg_credentials::digest_multibase_json(value).map_err(|e| VettingError::Digest(e.to_string()))
}
#[cfg(test)]
pub(crate) mod test_support {
use affinidi_secrets_resolver::secrets::Secret;
pub fn secret(seed_byte: u8) -> Secret {
let seed = [seed_byte; 32];
let mut secret = Secret::generate_ed25519(None, Some(&seed));
let public = secret.get_public_keymultibase().unwrap();
secret.id = format!("did:key:{public}#{public}");
secret
}
pub fn did(secret: &Secret) -> String {
super::did_of(&secret.id).to_string()
}
}