use ockam_core::compat::sync::Arc;
use ockam_core::Result;
use ockam_vault::VaultForVerifyingSignatures;
use tracing::trace;
use crate::models::{Identifier, PurposeKeyAttestation, PurposeKeyAttestationData, VersionedData};
use crate::utils::now;
use crate::{ChangeHistoryRepository, IdentitiesVerification, IdentityError, TimestampInSeconds};
const MAX_ALLOWED_TIME_DRIFT: TimestampInSeconds = TimestampInSeconds(60);
#[derive(Clone)]
pub struct PurposeKeyVerification {
verifying_vault: Arc<dyn VaultForVerifyingSignatures>,
change_history_repository: Arc<dyn ChangeHistoryRepository>,
}
impl PurposeKeyVerification {
pub fn new(
verifying_vault: Arc<dyn VaultForVerifyingSignatures>,
change_history_repository: Arc<dyn ChangeHistoryRepository>,
) -> Self {
Self {
verifying_vault,
change_history_repository,
}
}
pub fn identities_verification(&self) -> Arc<IdentitiesVerification> {
Arc::new(IdentitiesVerification::new(
self.change_history_repository.clone(),
self.verifying_vault.clone(),
))
}
}
impl PurposeKeyVerification {
pub async fn verify_purpose_key_attestation(
&self,
expected_subject: Option<&Identifier>,
attestation: &PurposeKeyAttestation,
) -> Result<PurposeKeyAttestationData> {
trace!(?expected_subject, "verifying purpose key attestation");
let versioned_data_hash = self.verifying_vault.sha256(&attestation.data).await?;
let versioned_data: VersionedData = minicbor::decode(&attestation.data)?;
if versioned_data.version != 1 {
return Err(IdentityError::PurposeKeyAttestationVerificationFailed)?;
}
let purpose_key_data = PurposeKeyAttestationData::get_data(&versioned_data)?;
if let Some(expected_subject) = expected_subject {
if expected_subject != &purpose_key_data.subject {
return Err(IdentityError::PurposeKeyAttestationVerificationFailed)?;
}
}
let identity = self
.identities_verification()
.get_identity(&purpose_key_data.subject)
.await?;
let latest_change = identity.get_latest_change()?;
if &purpose_key_data.subject_latest_change_hash != latest_change.change_hash() {
return Err(IdentityError::PurposeKeyAttestationVerificationFailed)?;
}
if purpose_key_data.expires_at > latest_change.data().attestations_valid_until {
return Err(IdentityError::PurposeKeyAttestationVerificationFailed)?;
}
if purpose_key_data.created_at < latest_change.data().attestations_valid_from {
return Err(IdentityError::PurposeKeyAttestationVerificationFailed)?;
}
let now = now()?;
if purpose_key_data.created_at > now
&& purpose_key_data.created_at - now > MAX_ALLOWED_TIME_DRIFT
{
return Err(IdentityError::PurposeKeyAttestationVerificationFailed)?;
}
if purpose_key_data.expires_at < now {
return Err(IdentityError::PurposeKeyAttestationVerificationFailed)?;
}
let identity_public_key = latest_change.primary_public_key();
if !self
.verifying_vault
.verify_signature(
identity_public_key,
&versioned_data_hash.0,
&attestation.signature.clone().into(),
)
.await?
{
return Err(IdentityError::PurposeKeyAttestationVerificationFailed)?;
}
trace!(?expected_subject, "verified purpose key attestation");
Ok(purpose_key_data)
}
}