use serde::Serialize;
use serde::de::DeserializeOwned;
use trust_tasks_rs::specs::external as ext;
use super::VtaClient;
use crate::error::VtaError;
use crate::trust_tasks;
const EXTERNAL_TT_TIMEOUT: u64 = 30;
const EXTERNAL_PROBE_TIMEOUT: u64 = 90;
impl VtaClient {
async fn external_call<P: Serialize, R: DeserializeOwned>(
&self,
uri: &str,
payload: &P,
timeout: u64,
) -> Result<R, VtaError> {
let payload = serde_json::to_value(payload)
.map_err(|e| VtaError::Validation(format!("{uri} request: {e}")))?;
let answer = self.dispatch_trust_task(uri, payload, timeout).await?;
serde_json::from_value(answer).map_err(|e| {
VtaError::Protocol(format!("{uri} response does not match its schema: {e}"))
})
}
pub async fn external_accounts_list(
&self,
req: &ext::accounts::list::v0_1::Payload,
) -> Result<ext::accounts::list::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_LIST_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_get(
&self,
req: &ext::accounts::get::v0_1::Payload,
) -> Result<ext::accounts::get::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_GET_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_create(
&self,
req: &ext::accounts::create::v0_1::Payload,
) -> Result<ext::accounts::create::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_CREATE_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_update(
&self,
req: &ext::accounts::update::v0_1::Payload,
) -> Result<ext::accounts::update::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_UPDATE_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_secret_set(
&self,
req: &ext::accounts::secret::set::v0_1::Payload,
) -> Result<ext::accounts::secret::set::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_SECRET_SET_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_bindings_grant(
&self,
req: &ext::accounts::bindings::grant::v0_1::Payload,
) -> Result<ext::accounts::bindings::grant::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_BINDINGS_GRANT_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_bindings_revoke(
&self,
req: &ext::accounts::bindings::revoke::v0_1::Payload,
) -> Result<ext::accounts::bindings::revoke::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_BINDINGS_REVOKE_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_setup(
&self,
req: &ext::accounts::setup::v0_1::Payload,
) -> Result<ext::accounts::setup::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_SETUP_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_probe(
&self,
req: &ext::accounts::probe::v0_1::Payload,
) -> Result<ext::accounts::probe::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_PROBE_0_1,
req,
EXTERNAL_PROBE_TIMEOUT,
)
.await
}
pub async fn external_accounts_keys_rotate(
&self,
req: &ext::accounts::keys::rotate::v0_1::Payload,
) -> Result<ext::accounts::keys::rotate::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_KEYS_ROTATE_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_suspend(
&self,
req: &ext::accounts::suspend::v0_1::Payload,
) -> Result<ext::accounts::suspend::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_SUSPEND_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_resume(
&self,
req: &ext::accounts::resume::v0_1::Payload,
) -> Result<ext::accounts::resume::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_RESUME_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_archive(
&self,
req: &ext::accounts::archive::v0_1::Payload,
) -> Result<ext::accounts::archive::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_ARCHIVE_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_restore(
&self,
req: &ext::accounts::restore::v0_1::Payload,
) -> Result<ext::accounts::restore::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_RESTORE_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_accounts_delete(
&self,
req: &ext::accounts::delete::v0_1::Payload,
) -> Result<ext::accounts::delete::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_ACCOUNTS_DELETE_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
pub async fn external_credentials_issue(
&self,
req: &ext::credentials::issue::v0_1::Payload,
) -> Result<ext::credentials::issue::v0_1::Response, VtaError> {
self.external_call(
trust_tasks::TASK_EXTERNAL_CREDENTIALS_ISSUE_0_1,
req,
EXTERNAL_TT_TIMEOUT,
)
.await
}
}
#[cfg(feature = "sealed-transfer")]
pub async fn seal_external_secret(
wrapping_did_key: &str,
context: &str,
account: &str,
secret: &str,
access_key_id: Option<&str>,
) -> Result<String, VtaError> {
use crate::sealed_transfer::{
AssertionProof, ExternalSecretPayload, InMemoryNonceStore, ProducerAssertion,
SealedPayloadV1, armor, generate_ed25519_keypair, seal_payload,
};
let recipient = affinidi_crypto::did_key::did_key_to_ed25519_pub(wrapping_did_key)
.and_then(|ed| affinidi_crypto::did_key::ed25519_pub_to_x25519_bytes(&ed))
.map_err(|e| {
VtaError::Validation(format!("wrapping key is not an Ed25519 did:key: {e}"))
})?;
let (_seed, producer) = generate_ed25519_keypair();
let bundle_id = uuid::Uuid::new_v4().into_bytes();
let payload = SealedPayloadV1::ExternalSecret(Box::new(ExternalSecretPayload {
context: context.to_string(),
account: account.to_string(),
secret: secret.to_string(),
access_key_id: access_key_id.map(str::to_string),
}));
let bundle = seal_payload(
&recipient,
bundle_id,
ProducerAssertion {
producer_did: affinidi_crypto::did_key::ed25519_pub_to_did_key(&producer),
proof: AssertionProof::PinnedOnly,
},
&payload,
&InMemoryNonceStore::new(),
)
.await
.map_err(|e| VtaError::Protocol(format!("sealing the secret failed: {e}")))?;
Ok(armor::encode(&bundle))
}
#[cfg(feature = "sealed-transfer")]
pub fn open_external_credential(
armored: &str,
x25519_secret: &[u8; 32],
) -> Result<crate::sealed_transfer::ExternalCredentialPayload, VtaError> {
use crate::sealed_transfer::{
PinnedOnlyPolicy, SealedPayloadV1, armor, open_bundle_with_policy,
};
let bundles = armor::decode(armored)
.map_err(|e| VtaError::Protocol(format!("sealed credential armor: {e}")))?;
let [bundle] = bundles.as_slice() else {
return Err(VtaError::Protocol(
"expected exactly one sealed bundle".into(),
));
};
let opened = open_bundle_with_policy(
x25519_secret,
bundle,
None,
PinnedOnlyPolicy::CallerHasIndependentTrustAnchor,
)
.map_err(|e| VtaError::Protocol(format!("sealed credential did not open: {e}")))?;
match opened.payload {
SealedPayloadV1::ExternalCredential(c) => Ok(*c),
_ => Err(VtaError::Protocol(
"the sealed bundle is not an external credential".into(),
)),
}
}