use tracing::{debug, info, warn};
use ockam_core::compat::collections::BTreeMap;
use ockam_core::compat::sync::Arc;
use ockam_core::compat::vec::Vec;
use ockam_core::Result;
use ockam_vault::VaultForVerifyingSignatures;
use crate::identities::AttributesEntry;
use crate::models::{
CredentialAndPurposeKey, CredentialData, Identifier, PurposePublicKey, VersionedData,
};
use crate::utils::now;
use crate::{
CredentialAndPurposeKeyData, IdentityAttributesRepository, IdentityError,
PurposeKeyVerification, TimestampInSeconds,
};
const MAX_ALLOWED_TIME_DRIFT: TimestampInSeconds = TimestampInSeconds(60);
pub struct CredentialsVerification {
purpose_keys_verification: Arc<PurposeKeyVerification>,
verifying_vault: Arc<dyn VaultForVerifyingSignatures>,
identities_attributes_repository: Arc<dyn IdentityAttributesRepository>,
}
impl CredentialsVerification {
pub fn new(
purpose_keys_verification: Arc<PurposeKeyVerification>,
verifying_vault: Arc<dyn VaultForVerifyingSignatures>,
identities_attributes_repository: Arc<dyn IdentityAttributesRepository>,
) -> Self {
Self {
purpose_keys_verification,
verifying_vault,
identities_attributes_repository,
}
}
}
impl CredentialsVerification {
pub async fn verify_credential(
&self,
expected_subject: Option<&Identifier>,
authorities: &[Identifier],
credential_and_purpose_key: &CredentialAndPurposeKey,
) -> Result<CredentialAndPurposeKeyData> {
Self::verify_credential_static(
self.purpose_keys_verification.clone(),
self.verifying_vault.clone(),
expected_subject,
authorities,
credential_and_purpose_key,
)
.await
}
pub async fn verify_credential_static(
purpose_keys_verification: Arc<PurposeKeyVerification>,
verifying_vault: Arc<dyn VaultForVerifyingSignatures>,
expected_subject: Option<&Identifier>,
authorities: &[Identifier],
credential_and_purpose_key: &CredentialAndPurposeKey,
) -> Result<CredentialAndPurposeKeyData> {
debug!("verify purpose key attestation");
let purpose_key_data = purpose_keys_verification
.verify_purpose_key_attestation(
None,
&credential_and_purpose_key.purpose_key_attestation,
)
.await?;
debug!("verify issuer");
if !authorities.contains(&purpose_key_data.subject) {
warn!(
"unknown authority on a credential: {}. Accepted authorities: {:?}",
purpose_key_data.subject, authorities
);
return Err(IdentityError::UnknownAuthority)?;
}
debug!("verify purpose key type");
let public_key = match purpose_key_data.public_key.clone() {
PurposePublicKey::SecureChannelStatic(_) => {
return Err(IdentityError::InvalidKeyType)?;
}
PurposePublicKey::CredentialSigning(public_key) => public_key,
};
debug!("verify signature");
let public_key = public_key.into();
let versioned_data_hash = verifying_vault
.sha256(&credential_and_purpose_key.credential.data)
.await?;
let signature = credential_and_purpose_key
.credential
.signature
.clone()
.into();
if !verifying_vault
.verify_signature(&public_key, &versioned_data_hash.0, &signature)
.await?
{
return Err(IdentityError::CredentialVerificationFailed)?;
}
let versioned_data: VersionedData =
minicbor::decode(&credential_and_purpose_key.credential.data)?;
let credential_data = CredentialData::get_data(&versioned_data)?;
debug!(
"verify subject {:?}. Expected {:?}",
credential_data.subject, expected_subject
);
if credential_data.subject.is_none() {
return Err(IdentityError::CredentialVerificationFailed)?;
}
if credential_data.subject.is_none() && credential_data.subject_latest_change_hash.is_none()
{
return Err(IdentityError::CredentialVerificationFailed)?;
}
if expected_subject.is_some() && credential_data.subject.as_ref() != expected_subject {
return Err(IdentityError::CredentialVerificationFailed)?;
}
debug!("verify dates");
if credential_data.created_at < purpose_key_data.created_at {
return Err(IdentityError::CredentialVerificationFailed)?;
}
if credential_data.expires_at > purpose_key_data.expires_at {
return Err(IdentityError::CredentialVerificationFailed)?;
}
let now = now()?;
if credential_data.created_at > now
&& credential_data.created_at - now > MAX_ALLOWED_TIME_DRIFT
{
return Err(IdentityError::CredentialVerificationFailed)?;
}
if credential_data.expires_at < now {
return Err(IdentityError::CredentialVerificationFailed)?;
}
if let Some(_subject_latest_change_hash) = &credential_data.subject_latest_change_hash {
}
Ok(CredentialAndPurposeKeyData {
credential_data,
purpose_key_data,
})
}
pub async fn receive_presented_credential(
&self,
subject: &Identifier,
authorities: &[Identifier],
credential_and_purpose_key_attestation: &CredentialAndPurposeKey,
) -> Result<()> {
let credential = self
.verify_credential(
Some(subject),
authorities,
credential_and_purpose_key_attestation,
)
.await?;
let credential_data = credential.credential_data;
let purpose_key_data = credential.purpose_key_data;
let attributes_display = credential_data.get_attributes_display();
let attributes: BTreeMap<_, _> = credential_data
.subject_attributes
.map
.into_iter()
.map(|(k, v)| (Vec::<u8>::from(k), Vec::<u8>::from(v)))
.collect();
info! {
%subject,
attributes = attributes_display,
schema = %credential_data.subject_attributes.schema.0,
created_at = %credential_data.created_at,
expires_at = %credential_data.expires_at,
"presented credential"
}
debug! {
subject = %purpose_key_data.subject,
created_at = %purpose_key_data.created_at,
expires_at = %purpose_key_data.expires_at,
"presented credential - purpose key attestation"
}
self.identities_attributes_repository
.put_attributes(
subject,
AttributesEntry::new(
attributes,
now()?,
Some(credential_data.expires_at),
Some(purpose_key_data.subject),
),
)
.await?;
Ok(())
}
}