typesec_integrations/did/
gateway.rs1use std::collections::HashMap;
4use std::sync::{Arc, Mutex, PoisonError};
5
6use serde::{Deserialize, Serialize};
7use typesec_core::{SecureValue, resource::GenericResource, secure_value::Secret};
8
9use super::crypto::{hex_decode, unix_time};
10use super::document::DidResolver;
11use super::envelope::{DidEnvelope, DidMessageBody, DidMessageReference};
12use super::error::DidError;
13use super::identifier::Did;
14use super::keystore::DidKeyStore;
15use super::typedid::{TypeDidConversation, TypeDidMode};
16
17#[derive(Debug)]
19pub struct VerifiedTypeDidMessage {
20 pub subject: Did,
22 pub message_ref: DidMessageReference,
24 pub body: DidMessageBody,
26 pub conversation: TypeDidConversation,
28 pub resource: GenericResource,
30 pub payload: SecureValue<Secret, Vec<u8>, GenericResource>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct TypeDidAttestation {
42 pub subject: Did,
44 pub envelope_id: String,
46 pub envelope_digest: String,
48 pub action: String,
50 pub resource: String,
52 pub privacy: String,
54 pub conversation_id: String,
56 pub protocol: String,
58 pub mode: TypeDidMode,
60 pub profile: String,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub expires_at: Option<u64>,
65}
66
67impl VerifiedTypeDidMessage {
68 pub fn attestation(&self) -> TypeDidAttestation {
70 TypeDidAttestation {
71 subject: self.subject.clone(),
72 envelope_id: self.message_ref.id.clone(),
73 envelope_digest: self.message_ref.digest.clone(),
74 action: self.body.action.clone(),
75 resource: self.body.resource.clone(),
76 privacy: self.body.privacy.clone(),
77 conversation_id: self.conversation.conversation_id.clone(),
78 protocol: self.conversation.protocol.clone(),
79 mode: self.conversation.mode,
80 profile: self.conversation.profile.clone(),
81 expires_at: self.conversation.expires_at,
82 }
83 }
84}
85
86#[derive(Debug)]
88pub struct VerifiedDidPrompt {
89 pub subject: Did,
91 pub prompt_ref: DidMessageReference,
93 pub body: DidMessageBody,
95 pub resource: GenericResource,
97 pub prompt: SecureValue<Secret, String, GenericResource>,
99}
100
101const CLOCK_SKEW_SECS: u64 = 300;
103
104pub struct DidMessageGateway {
106 resolver: Arc<dyn DidResolver>,
107 key_store: Arc<dyn DidKeyStore>,
108 recipient: Did,
109 seen: Mutex<HashMap<String, u64>>,
112}
113
114impl DidMessageGateway {
115 pub fn new(
117 resolver: Arc<dyn DidResolver>,
118 key_store: Arc<dyn DidKeyStore>,
119 recipient: Did,
120 ) -> Self {
121 Self {
122 resolver,
123 key_store,
124 recipient,
125 seen: Mutex::new(HashMap::new()),
126 }
127 }
128
129 fn guard_replay(&self, envelope: &DidEnvelope, now: u64) -> Result<(), DidError> {
133 let mut seen = self.seen.lock().unwrap_or_else(PoisonError::into_inner);
134 seen.retain(|_, expires| *expires >= now);
135 if seen
136 .insert(envelope.signature.clone(), envelope.expires_time)
137 .is_some()
138 {
139 return Err(DidError::Replayed(envelope.id.clone()));
140 }
141 Ok(())
142 }
143
144 pub fn open_prompt(&self, envelope: &DidEnvelope) -> Result<VerifiedDidPrompt, DidError> {
146 let opened = self.open_bytes(envelope)?;
147 let prompt = String::from_utf8(opened.plaintext).map_err(|_| DidError::InvalidUtf8)?;
148 Ok(VerifiedDidPrompt {
149 subject: opened.subject,
150 prompt_ref: opened.message_ref,
151 body: opened.body,
152 prompt: SecureValue::protect(prompt, &opened.resource),
153 resource: opened.resource,
154 })
155 }
156
157 pub(super) fn open_bytes(&self, envelope: &DidEnvelope) -> Result<OpenedDidEnvelope, DidError> {
158 if !envelope.to.iter().any(|did| did == &self.recipient) {
159 return Err(DidError::WrongRecipient(self.recipient.to_string()));
160 }
161 let now = unix_time();
162 if envelope.expires_time < now {
163 return Err(DidError::Expired);
164 }
165 if envelope.created_time > now.saturating_add(CLOCK_SKEW_SECS) {
168 return Err(DidError::NotYetValid {
169 created: envelope.created_time,
170 now,
171 });
172 }
173
174 let sender_document = self.resolver.resolve(&envelope.from)?;
175 let sender_key = sender_document.authentication_key(&envelope.kid)?;
176 self.key_store.verify(
177 sender_key,
178 envelope.signing_input().as_bytes(),
179 &envelope.signature,
180 )?;
181
182 self.guard_replay(envelope, now)?;
184
185 let sender_agreement_keys = sender_document.key_agreement_keys()?;
191 let nonce = hex_decode(&envelope.nonce)?;
192 let aad = envelope.associated_data();
193 let mut plaintext = None;
194 for sender_agreement_key in sender_agreement_keys {
195 match self.key_store.decrypt_for(
196 &self.recipient,
197 &sender_agreement_key.public_key()?,
198 &nonce,
199 &envelope.ciphertext,
200 &aad,
201 ) {
202 Ok(opened) => {
203 plaintext = Some(opened);
204 break;
205 }
206 Err(DidError::DecryptionFailed) => {}
207 Err(err) => return Err(err),
208 }
209 }
210 let plaintext = plaintext.ok_or(DidError::DecryptionFailed)?;
211 let resource = GenericResource::new(&envelope.body.resource, "did-prompt");
212
213 Ok(OpenedDidEnvelope {
214 subject: envelope.from.clone(),
215 message_ref: envelope.reference(),
216 body: envelope.body.clone(),
217 resource,
218 plaintext,
219 })
220 }
221}
222
223#[derive(Debug)]
224pub(super) struct OpenedDidEnvelope {
225 pub(super) subject: Did,
226 pub(super) message_ref: DidMessageReference,
227 pub(super) body: DidMessageBody,
228 pub(super) resource: GenericResource,
229 pub(super) plaintext: Vec<u8>,
230}
231
232pub struct TypeDidGateway {
234 inner: DidMessageGateway,
235}
236
237impl TypeDidGateway {
238 pub fn new(
240 resolver: Arc<dyn DidResolver>,
241 key_store: Arc<dyn DidKeyStore>,
242 recipient: Did,
243 ) -> Self {
244 Self {
245 inner: DidMessageGateway::new(resolver, key_store, recipient),
246 }
247 }
248
249 pub fn open_message(&self, envelope: &DidEnvelope) -> Result<VerifiedTypeDidMessage, DidError> {
251 let conversation = envelope
252 .typedid
253 .clone()
254 .ok_or(DidError::MissingTypeDidMetadata)?;
255 let opened = self.inner.open_bytes(envelope)?;
256 Ok(VerifiedTypeDidMessage {
257 subject: opened.subject,
258 message_ref: opened.message_ref,
259 body: opened.body,
260 conversation,
261 payload: SecureValue::protect(opened.plaintext, &opened.resource),
262 resource: opened.resource,
263 })
264 }
265}