ockam_entity 0.8.0

Ockam is a library for building devices that communicate securely, privately and trustfully with cloud services and other devices.
Documentation
use crate::{
    CredentialHolder, CredentialIssuer, CredentialProof, CredentialPublicKey,
    CredentialRequestFragment, CredentialVerifier, EntityError::IdentityApiFailed, Handle,
    Identity, IdentityRequest, IdentityRequest::*, IdentityResponse as Res, MaybeContact,
    NoOpTrustPolicy, Profile, ProfileIdentifier, ProfileState, SecureChannelTrait,
};
use async_trait::async_trait;
use ockam_core::{
    lib::{result::Result::Ok, HashMap},
    Address, Result, Routed, Worker,
};
use ockam_node::Context;
use ockam_vault_sync_core::{Vault, VaultSync};

#[derive(Default)]
pub struct EntityWorker {
    profiles: HashMap<ProfileIdentifier, ProfileState>,
}

impl EntityWorker {
    fn profile(&mut self, profile_id: &ProfileIdentifier) -> &mut ProfileState {
        self.profiles
            .get_mut(profile_id)
            .expect("default profile invalid")
    }
}

fn err<T>() -> Result<T> {
    Err(IdentityApiFailed.into())
}

#[async_trait]
impl Worker for EntityWorker {
    type Message = IdentityRequest;
    type Context = Context;

    async fn handle_message(
        &mut self,
        ctx: &mut Self::Context,
        msg: Routed<Self::Message>,
    ) -> Result<()> {
        let reply = msg.return_route();
        let req = msg.body();
        match req {
            CreateProfile => {
                let vault = Vault::create(ctx).expect("failed to create EntityWorker vault");
                let vault_sync = VaultSync::create_with_worker(ctx, &vault)
                    .expect("couldn't create profile vault");

                let profile_state =
                    ProfileState::create(vault_sync).expect("failed to create ProfileState");

                let id = profile_state
                    .identifier()
                    .expect("failed to get profile id");

                self.add_profile_state(profile_state)
                    .expect("failed to add profile state");

                ctx.send(reply, Res::CreateProfile(id)).await
            }
            RemoveProfile(profile_id) => self.remove_profile(profile_id),
            CreateKey(profile_id, label) => {
                let profile = self.profile(&profile_id);

                Identity::create_key(profile, label)
            }
            RotateKey(profile_id) => {
                let profile = self.profile(&profile_id);

                Identity::rotate_profile_key(profile)
            }
            GetProfilePublicKey(profile_id) => {
                if let Ok(public_key) = self.profile(&profile_id).get_profile_public_key() {
                    ctx.send(reply, Res::GetProfilePublicKey(public_key)).await
                } else {
                    err()
                }
            }
            GetProfileSecretKey(profile_id) => {
                if let Ok(secret) = self.profile(&profile_id).get_profile_secret_key() {
                    ctx.send(reply, Res::GetProfileSecretKey(secret)).await
                } else {
                    err()
                }
            }
            GetPublicKey(profile_id, label) => {
                if let Ok(public_key) = self.profile(&profile_id).get_public_key(label) {
                    ctx.send(reply, Res::GetPublicKey(public_key)).await
                } else {
                    err()
                }
            }
            GetSecretKey(profile_id, label) => {
                if let Ok(secret) = self.profile(&profile_id).get_secret_key(label) {
                    ctx.send(reply, Res::GetSecretKey(secret)).await
                } else {
                    err()
                }
            }
            CreateAuthenticationProof(profile_id, state) => {
                if let Ok(proof) = self
                    .profile(&profile_id)
                    .create_auth_proof(state.as_slice())
                {
                    ctx.send(reply, Res::CreateAuthenticationProof(proof)).await
                } else {
                    err()
                }
            }
            VerifyAuthenticationProof(profile_id, state, peer_id, proof) => {
                if let Ok(verified) = self.profile(&profile_id).verify_auth_proof(
                    state.as_slice(),
                    &peer_id,
                    proof.as_slice(),
                ) {
                    ctx.send(reply, Res::VerifyAuthenticationProof(verified))
                        .await
                } else {
                    err()
                }
            }
            AddChange(profile_id, change) => self.profile(&profile_id).add_change(change),
            GetChanges(profile_id) => {
                let changes = self
                    .profile(&profile_id)
                    .get_changes()
                    .expect("get_changes failed");
                ctx.send(reply, Res::GetChanges(changes)).await
            }
            VerifyChanges(profile_id) => {
                let verified = self.profile(&profile_id).verify_changes()?;
                ctx.send(reply, Res::VerifyChanges(verified)).await
            }
            VerifyAndAddContact(profile_id, contact_id) => {
                let verified_and_added = self
                    .profile(&profile_id)
                    .verify_and_add_contact(contact_id)?;
                ctx.send(reply, Res::VerifyAndAddContact(verified_and_added))
                    .await
            }
            GetContacts(profile_id) => {
                let contacts = self.profile(&profile_id).get_contacts()?;
                ctx.send(reply, Res::Contacts(contacts)).await
            }
            VerifyContact(profile_id, contact) => {
                let verified = self.profile(&profile_id).verify_contact(contact)?;
                ctx.send(reply, Res::VerifyContact(verified)).await
            }
            VerifyAndUpdateContact(profile_id, contact_id, changes) => {
                let verified = self
                    .profile(&profile_id)
                    .verify_and_update_contact(&contact_id, changes)?;
                ctx.send(reply, Res::VerifyAndUpdateContact(verified)).await
            }
            GetContact(profile_id, contact_id) => {
                let contact = self.profile(&profile_id).get_contact(&contact_id)?;
                let message = match contact {
                    None => MaybeContact::None,
                    Some(contact) => MaybeContact::Contact(contact),
                };
                ctx.send(reply, Res::GetContact(message)).await
            }
            CreateSecureChannelListener(profile_id, address) => {
                let vault_address = self.profile(&profile_id).vault().address();
                let handle = Handle::new(ctx.new_context(Address::random(0)).await?, ctx.address());
                let profile = Profile::new(profile_id, handle);
                SecureChannelTrait::create_secure_channel_listener_async(
                    profile,
                    &ctx,
                    address,
                    NoOpTrustPolicy, // TODO wire up some serializable policy descriptor
                    &vault_address,
                )
                .await?;
                ctx.send(reply, Res::CreateSecureChannelListener).await
            }
            CreateSecureChannel(profile_id, route) => {
                let vault_address = self.profile(&profile_id).vault().address();
                let handle = Handle::new(ctx.new_context(Address::random(0)).await?, ctx.address());
                let profile = Profile::new(profile_id.clone(), handle);

                let child_ctx = ctx.new_context(Address::random(0)).await?;
                let rt = ctx.runtime();
                rt.spawn(async move {
                    let address = SecureChannelTrait::create_secure_channel_async(
                        profile,
                        &child_ctx,
                        route,
                        NoOpTrustPolicy, // TODO policy
                        &vault_address,
                    )
                    .await?;
                    child_ctx
                        .send(reply, Res::CreateSecureChannel(address))
                        .await
                });

                Ok(())
            }
            GetSigningKey(profile_id) => {
                if let Ok(signing_key) = self.profile(&profile_id).get_signing_key() {
                    ctx.send(reply, Res::GetSigningKey(signing_key)).await
                } else {
                    err()
                }
            }
            GetIssuerPublicKey(profile_id) => {
                if let Ok(public_key) = self.profile(&profile_id).get_signing_public_key() {
                    ctx.send(
                        reply,
                        Res::GetIssuerPublicKey(CredentialPublicKey(public_key)),
                    )
                    .await
                } else {
                    err()
                }
            }
            CreateOffer(profile_id, schema) => {
                if let Ok(offer) = self.profile(&profile_id).create_offer(&schema) {
                    ctx.send(reply, Res::CreateOffer(offer)).await
                } else {
                    err()
                }
            }
            CreateProofOfPossession(profile_id) => {
                if let Ok(pop) = self.profile(&profile_id).create_proof_of_possession() {
                    ctx.send(reply, Res::CreateProofOfPossession(CredentialProof(pop)))
                        .await
                } else {
                    err()
                }
            }
            SignCredential(profile_id, schema, attributes) => {
                if let Ok(credential) = self
                    .profile(&profile_id)
                    .sign_credential(&schema, attributes.as_slice())
                {
                    ctx.send(reply, Res::SignCredential(credential)).await
                } else {
                    err()
                }
            }
            SignCredentialRequest(profile_id, request, schema, attributes, offer_id) => {
                if let Ok(frag) = self.profile(&profile_id).sign_credential_request(
                    &request,
                    &schema,
                    attributes.as_slice(),
                    offer_id,
                ) {
                    ctx.send(reply, Res::SignCredentialRequest(frag)).await
                } else {
                    err()
                }
            }
            AcceptCredentialOffer(profile_id, offer, signing_public_key) => {
                if let Ok(cred_and_fragment) = self
                    .profile(&profile_id)
                    .accept_credential_offer(&offer, signing_public_key.0)
                {
                    ctx.send(
                        reply,
                        Res::AcceptCredentialOffer(CredentialRequestFragment(
                            cred_and_fragment.0,
                            cred_and_fragment.1,
                        )),
                    )
                    .await
                } else {
                    err()
                }
            }
            CombineCredentialFragments(profile_id, frag1, frag2) => {
                if let Ok(credential) = self
                    .profile(&profile_id)
                    .combine_credential_fragments(frag1, frag2)
                {
                    ctx.send(reply, Res::CombineCredentialFragments(credential))
                        .await
                } else {
                    err()
                }
            }
            IsValidCredential(profile_id, credential, issuer_public_key) => {
                if let Ok(valid) = self
                    .profile(&profile_id)
                    .is_valid_credential(&credential, issuer_public_key.0)
                {
                    ctx.send(reply, Res::IsValidCredential(valid)).await
                } else {
                    err()
                }
            }
            PresentCredential(profile_id, credential, manifest, request_id) => {
                if let Ok(presentations) = self.profile(&profile_id).present_credentials(
                    &[credential],
                    &[manifest],
                    request_id,
                ) {
                    let presentation = presentations
                        .first()
                        .expect("expected at least one presentation");

                    ctx.send(reply, Res::PresentCredential(presentation.clone()))
                        .await
                } else {
                    err()
                }
            }
            CreateProofRequestId(profile_id) => {
                if let Ok(request_id) = self.profile(&profile_id).create_proof_request_id() {
                    ctx.send(reply, Res::CreateProofRequestId(request_id)).await
                } else {
                    err()
                }
            }
            VerifyProofOfPossession(profile_id, signing_public_key, proof_of_possession) => {
                if let Ok(valid) = self
                    .profile(&profile_id)
                    .verify_proof_of_possession(signing_public_key.0, proof_of_possession.0)
                {
                    ctx.send(reply, Res::VerifyProofOfPossession(valid)).await
                } else {
                    err()
                }
            }
            VerifyCredentialPresentation(profile_id, presentation, manifest, request_id) => {
                if let Ok(valid) = self.profile(&profile_id).verify_credential_presentations(
                    &[presentation],
                    &[manifest],
                    request_id,
                ) {
                    ctx.send(reply, Res::VerifyCredentialPresentation(valid))
                        .await
                } else {
                    err()
                }
            }
        }
    }
}

impl EntityWorker {
    fn add_profile_state(&mut self, profile_state: ProfileState) -> Result<()> {
        let id = profile_state.identifier().unwrap();
        self.profiles.insert(id.clone(), profile_state);
        Ok(())
    }

    fn remove_profile<I: Into<ProfileIdentifier>>(&mut self, profile_id: I) -> Result<()> {
        self.profiles
            .remove(&profile_id.into())
            .expect("remove_profile failed");
        Ok(())
    }
}