use minicbor::{CborLen, Decode, Encode};
use tracing::{debug, warn};
use ockam_core::compat::boxed::Box;
use ockam_core::compat::string::ToString;
use ockam_core::compat::sync::Arc;
use ockam_core::compat::vec::Vec;
use ockam_core::{async_trait, Result};
use ockam_vault::{AeadSecretKeyHandle, X25519PublicKey};
use crate::models::{
ChangeHistory, CredentialAndPurposeKey, PurposeKeyAttestation, PurposePublicKey,
};
use crate::{
CredentialRetriever, Identifier, Identities, IdentityError, SecureChannelTrustInfo, TrustPolicy,
};
#[async_trait]
pub(crate) trait StateMachine: Send + Sync + 'static {
async fn on_event(&mut self, event: Event) -> Result<Action>;
fn get_handshake_results(&self) -> Option<HandshakeResults>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Event {
Initialize,
ReceivedMessage(Vec<u8>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Action {
NoAction,
SendMessage(Vec<u8>),
}
#[derive(Debug, Clone)]
pub(crate) enum Status {
Initial,
WaitingForMessage1,
WaitingForMessage2,
WaitingForMessage3,
Ready(HandshakeKeys),
}
#[derive(Debug, Clone)]
pub(crate) struct HandshakeKeys {
pub(super) encryption_key: AeadSecretKeyHandle,
pub(super) decryption_key: AeadSecretKeyHandle,
}
#[derive(Debug, Clone)]
pub(crate) struct HandshakeResults {
pub(super) handshake_keys: HandshakeKeys,
pub(super) their_identifier: Identifier,
pub(super) presented_credential: Option<CredentialAndPurposeKey>,
}
pub(crate) struct CommonStateMachine {
pub(super) identities: Arc<Identities>,
pub(super) identifier: Identifier,
pub(super) purpose_key_attestation: PurposeKeyAttestation,
pub(super) credential_retriever: Option<Arc<dyn CredentialRetriever>>,
pub(super) trust_policy: Arc<dyn TrustPolicy>,
pub(super) authority: Option<Identifier>, pub(super) presented_credential: Option<CredentialAndPurposeKey>,
their_identifier: Option<Identifier>,
}
impl CommonStateMachine {
pub(super) fn new(
identities: Arc<Identities>,
identifier: Identifier,
purpose_key_attestation: PurposeKeyAttestation,
credential_retriever: Option<Arc<dyn CredentialRetriever>>,
trust_policy: Arc<dyn TrustPolicy>,
authority: Option<Identifier>,
) -> Self {
Self {
identities,
identifier,
purpose_key_attestation,
credential_retriever,
trust_policy,
authority,
presented_credential: None,
their_identifier: None,
}
}
pub(super) async fn make_identity_payload(&mut self) -> Result<Vec<u8>> {
let change_history = self.identities.get_change_history(&self.identifier).await?;
let credential = match &self.credential_retriever {
Some(credential_retriever) => Some(credential_retriever.retrieve().await?),
None => None,
};
self.presented_credential.clone_from(&credential);
let credentials = credential.map(|c| vec![c]).unwrap_or(vec![]);
let payload = IdentityAndCredentials {
change_history,
purpose_key_attestation: self.purpose_key_attestation.clone(),
credentials,
};
ockam_core::cbor_encode_preallocate(payload)
}
pub(super) async fn process_identity_payload(
&mut self,
peer: IdentityAndCredentials,
peer_public_key: X25519PublicKey,
) -> Result<()> {
let identifier = Self::process_identity_payload_static(
self.identities.clone(),
Some(self.trust_policy.clone()),
self.authority.clone(),
None,
peer.change_history,
peer.credentials,
Some((peer.purpose_key_attestation, peer_public_key)),
)
.await?;
self.their_identifier = Some(identifier);
Ok(())
}
pub(super) fn make_handshake_results(
&self,
handshake_keys: Option<HandshakeKeys>,
) -> Option<HandshakeResults> {
match (self.their_identifier.clone(), handshake_keys) {
(Some(their_identifier), Some(handshake_keys)) => Some(HandshakeResults {
their_identifier,
handshake_keys,
presented_credential: self.presented_credential.clone(),
}),
_ => None,
}
}
}
impl CommonStateMachine {
#[allow(clippy::too_many_arguments)]
pub(crate) async fn process_identity_payload_static(
identities: Arc<Identities>,
trust_policy: Option<Arc<dyn TrustPolicy>>,
authority: Option<Identifier>,
expected_identifier: Option<Identifier>,
change_history: ChangeHistory,
credentials: Vec<CredentialAndPurposeKey>,
peer_public_key: Option<(PurposeKeyAttestation, X25519PublicKey)>,
) -> Result<Identifier> {
let their_identifier = identities
.identities_verification()
.import_from_change_history(expected_identifier.as_ref(), change_history.clone())
.await?;
if let Some((purpose_key_attestation, peer_public_key)) = peer_public_key {
let purpose_key = identities
.purpose_keys()
.purpose_keys_verification()
.verify_purpose_key_attestation(Some(&their_identifier), &purpose_key_attestation)
.await?;
match &purpose_key.public_key {
PurposePublicKey::SecureChannelStatic(public_key) => {
if public_key.0 != peer_public_key.0 {
return Err(IdentityError::InvalidKeyData)?;
}
}
PurposePublicKey::CredentialSigning(_) => {
return Err(IdentityError::InvalidKeyType)?;
}
}
}
Self::check_trust_policy(trust_policy, &their_identifier).await?;
Self::verify_credentials(identities, authority, &their_identifier, credentials).await?;
Ok(their_identifier)
}
async fn check_trust_policy(
trust_policy: Option<Arc<dyn TrustPolicy>>,
their_identifier: &Identifier,
) -> Result<()> {
if let Some(trust_policy) = trust_policy {
let trust_info = SecureChannelTrustInfo::new(their_identifier.clone());
let trusted = trust_policy.check(&trust_info).await?;
if !trusted {
return Err(IdentityError::SecureChannelTrustCheckFailed)?;
}
debug!(
"Checked trust policy for SecureChannel from: {}",
their_identifier
);
}
Ok(())
}
async fn verify_credentials(
identities: Arc<Identities>,
authority: Option<Identifier>,
their_identifier: &Identifier,
credentials: Vec<CredentialAndPurposeKey>,
) -> Result<()> {
debug!("verifying {} credentials", credentials.len());
let Some(authority) = &authority else {
if !credentials.is_empty() {
warn!("credentials were presented, but Authority is missing");
}
return Ok(());
};
if credentials.is_empty() {
debug!(
"no credentials were received from {}. Expected authority: {}",
their_identifier, authority
);
return Ok(());
};
for credential in &credentials {
let res = identities
.credentials()
.credentials_verification()
.receive_presented_credential(their_identifier, &[authority.clone()], credential)
.await;
match res {
Ok(_) => {
debug!(
"Successfully validated credential from {}",
their_identifier,
);
}
Err(err) => {
warn!(
"a credential from {} could not be validated {}",
their_identifier,
err.to_string()
);
}
}
}
Ok(())
}
}
#[derive(Debug, Clone, Encode, Decode, CborLen)]
#[rustfmt::skip]
pub(super) struct IdentityAndCredentials {
#[n(0)] pub(super) change_history: ChangeHistory,
#[n(1)] pub(super) purpose_key_attestation: PurposeKeyAttestation,
#[n(2)] pub(super) credentials: Vec<CredentialAndPurposeKey>,
}