use std::collections::HashMap;
use std::sync::{Arc, Mutex, PoisonError};
use serde::{Deserialize, Serialize};
use typesec_core::{SecureValue, resource::GenericResource, secure_value::Secret};
use super::crypto::{hex_decode, unix_time};
use super::document::DidResolver;
use super::envelope::{DidEnvelope, DidMessageBody, DidMessageReference};
use super::error::DidError;
use super::identifier::Did;
use super::keystore::DidKeyStore;
use super::typedid::{TypeDidConversation, TypeDidMode};
#[derive(Debug)]
pub struct VerifiedTypeDidMessage {
pub subject: Did,
pub message_ref: DidMessageReference,
pub body: DidMessageBody,
pub conversation: TypeDidConversation,
pub resource: GenericResource,
pub payload: SecureValue<Secret, Vec<u8>, GenericResource>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeDidAttestation {
pub subject: Did,
pub envelope_id: String,
pub envelope_digest: String,
pub action: String,
pub resource: String,
pub privacy: String,
pub conversation_id: String,
pub protocol: String,
pub mode: TypeDidMode,
pub profile: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<u64>,
}
impl VerifiedTypeDidMessage {
pub fn attestation(&self) -> TypeDidAttestation {
TypeDidAttestation {
subject: self.subject.clone(),
envelope_id: self.message_ref.id.clone(),
envelope_digest: self.message_ref.digest.clone(),
action: self.body.action.clone(),
resource: self.body.resource.clone(),
privacy: self.body.privacy.clone(),
conversation_id: self.conversation.conversation_id.clone(),
protocol: self.conversation.protocol.clone(),
mode: self.conversation.mode,
profile: self.conversation.profile.clone(),
expires_at: self.conversation.expires_at,
}
}
}
#[derive(Debug)]
pub struct VerifiedDidPrompt {
pub subject: Did,
pub prompt_ref: DidMessageReference,
pub body: DidMessageBody,
pub resource: GenericResource,
pub prompt: SecureValue<Secret, String, GenericResource>,
}
const CLOCK_SKEW_SECS: u64 = 300;
pub struct DidMessageGateway {
resolver: Arc<dyn DidResolver>,
key_store: Arc<dyn DidKeyStore>,
recipient: Did,
seen: Mutex<HashMap<String, u64>>,
}
impl DidMessageGateway {
pub fn new(
resolver: Arc<dyn DidResolver>,
key_store: Arc<dyn DidKeyStore>,
recipient: Did,
) -> Self {
Self {
resolver,
key_store,
recipient,
seen: Mutex::new(HashMap::new()),
}
}
fn guard_replay(&self, envelope: &DidEnvelope, now: u64) -> Result<(), DidError> {
let mut seen = self.seen.lock().unwrap_or_else(PoisonError::into_inner);
seen.retain(|_, expires| *expires >= now);
if seen
.insert(envelope.signature.clone(), envelope.expires_time)
.is_some()
{
return Err(DidError::Replayed(envelope.id.clone()));
}
Ok(())
}
pub fn open_prompt(&self, envelope: &DidEnvelope) -> Result<VerifiedDidPrompt, DidError> {
let opened = self.open_bytes(envelope)?;
let prompt = String::from_utf8(opened.plaintext).map_err(|_| DidError::InvalidUtf8)?;
Ok(VerifiedDidPrompt {
subject: opened.subject,
prompt_ref: opened.message_ref,
body: opened.body,
prompt: SecureValue::protect(prompt, &opened.resource),
resource: opened.resource,
})
}
pub(super) fn open_bytes(&self, envelope: &DidEnvelope) -> Result<OpenedDidEnvelope, DidError> {
if !envelope.to.iter().any(|did| did == &self.recipient) {
return Err(DidError::WrongRecipient(self.recipient.to_string()));
}
let now = unix_time();
if envelope.expires_time < now {
return Err(DidError::Expired);
}
if envelope.created_time > now.saturating_add(CLOCK_SKEW_SECS) {
return Err(DidError::NotYetValid {
created: envelope.created_time,
now,
});
}
let sender_document = self.resolver.resolve(&envelope.from)?;
let sender_key = sender_document.authentication_key(&envelope.kid)?;
self.key_store.verify(
sender_key,
envelope.signing_input().as_bytes(),
&envelope.signature,
)?;
self.guard_replay(envelope, now)?;
let sender_agreement_keys = sender_document.key_agreement_keys()?;
let nonce = hex_decode(&envelope.nonce)?;
let aad = envelope.associated_data();
let mut plaintext = None;
for sender_agreement_key in sender_agreement_keys {
match self.key_store.decrypt_for(
&self.recipient,
&sender_agreement_key.public_key()?,
&nonce,
&envelope.ciphertext,
&aad,
) {
Ok(opened) => {
plaintext = Some(opened);
break;
}
Err(DidError::DecryptionFailed) => {}
Err(err) => return Err(err),
}
}
let plaintext = plaintext.ok_or(DidError::DecryptionFailed)?;
let resource = GenericResource::new(&envelope.body.resource, "did-prompt");
Ok(OpenedDidEnvelope {
subject: envelope.from.clone(),
message_ref: envelope.reference(),
body: envelope.body.clone(),
resource,
plaintext,
})
}
}
#[derive(Debug)]
pub(super) struct OpenedDidEnvelope {
pub(super) subject: Did,
pub(super) message_ref: DidMessageReference,
pub(super) body: DidMessageBody,
pub(super) resource: GenericResource,
pub(super) plaintext: Vec<u8>,
}
pub struct TypeDidGateway {
inner: DidMessageGateway,
}
impl TypeDidGateway {
pub fn new(
resolver: Arc<dyn DidResolver>,
key_store: Arc<dyn DidKeyStore>,
recipient: Did,
) -> Self {
Self {
inner: DidMessageGateway::new(resolver, key_store, recipient),
}
}
pub fn open_message(&self, envelope: &DidEnvelope) -> Result<VerifiedTypeDidMessage, DidError> {
let conversation = envelope
.typedid
.clone()
.ok_or(DidError::MissingTypeDidMetadata)?;
let opened = self.inner.open_bytes(envelope)?;
Ok(VerifiedTypeDidMessage {
subject: opened.subject,
message_ref: opened.message_ref,
body: opened.body,
conversation,
payload: SecureValue::protect(opened.plaintext, &opened.resource),
resource: opened.resource,
})
}
}