1#[cfg(feature = "data-integrity")]
8use affinidi_data_integrity::{
9 DataIntegrityError, DataIntegrityProof, VerifyOptions, verification_proof::VerificationProof,
10};
11use affinidi_did_resolver_cache_sdk::{DIDCacheClient, config::DIDCacheConfigBuilder};
12#[cfg(feature = "messaging")]
13use affinidi_messaging_sdk::ATM;
14#[cfg(feature = "messaging")]
15use affinidi_messaging_sdk::config::ATMConfigBuilder;
16use affinidi_secrets_resolver::{SecretsResolver, ThreadedSecretsResolver};
17use affinidi_tdk_common::{
18 TDKSharedState, create_http_client, environments::TDKEnvironments, errors::Result,
19 profiles::TDKProfile, tasks::authentication::AuthenticationCache,
20};
21use common::{config::TDKConfig, environments::TDKEnvironment};
22#[cfg(feature = "data-integrity")]
23use serde::Serialize;
24use std::sync::Arc;
25
26pub mod dids;
27pub mod secrets;
28
29#[cfg(feature = "meeting-place")]
31pub use affinidi_meeting_place as meeting_place;
32
33pub use affinidi_messaging_didcomm as didcomm;
34#[cfg(feature = "messaging")]
35pub use affinidi_messaging_sdk as messaging;
36
37#[cfg(feature = "data-integrity")]
41pub use affinidi_data_integrity as data_integrity;
42
43pub use affinidi_crypto;
45pub use affinidi_did_authentication as did_authentication;
46pub use affinidi_did_common as did_common;
47pub use affinidi_secrets_resolver as secrets_resolver;
48pub use affinidi_tdk_common as common;
49
50#[derive(Clone)]
52pub struct TDK {
53 pub(crate) inner: Arc<TDKSharedState>,
54 #[cfg(feature = "messaging")]
55 pub atm: Option<ATM>,
56 #[cfg(feature = "meeting-place")]
57 pub meeting_place: Option<meeting_place::MeetingPlace>,
58}
59
60impl TDK {
79 pub async fn new(
80 config: TDKConfig,
81 #[cfg(feature = "messaging")] atm: Option<ATM>,
82 ) -> Result<Self> {
83 let client = create_http_client();
84
85 let did_resolver = if let Some(did_resolver) = &config.did_resolver {
87 did_resolver.to_owned()
88 } else if let Some(did_resolver_config) = &config.did_resolver_config {
89 DIDCacheClient::new(did_resolver_config.to_owned()).await?
90 } else {
91 DIDCacheClient::new(DIDCacheConfigBuilder::default().build()).await?
92 };
93
94 let secrets_resolver = if let Some(secrets_resolver) = &config.secrets_resolver {
96 secrets_resolver.to_owned()
97 } else {
98 ThreadedSecretsResolver::new(None).await.0
99 };
100
101 let (authentication, _) = AuthenticationCache::new(
103 config.authentication_cache_limit as u64,
104 &did_resolver,
105 secrets_resolver.clone(),
106 &client,
107 config.custom_auth_handlers.clone(),
108 );
109
110 authentication.start().await;
111
112 let environment = if config.load_environment {
116 let mut environment = TDKEnvironments::fetch_from_file(
117 Some(&config.environment_path),
118 &config.environment_name,
119 )?;
120 for (_, profile) in environment.profiles.iter_mut() {
121 secrets_resolver
122 .insert_vec(profile.secrets.as_slice())
123 .await;
124
125 profile.secrets.clear();
127 }
128 environment
129 } else {
130 TDKEnvironment::default()
131 };
132
133 let shared_state = Arc::new(TDKSharedState {
135 config,
136 did_resolver,
137 secrets_resolver,
138 client,
139 environment,
140 authentication,
141 });
142
143 #[cfg(feature = "messaging")]
144 let atm = if shared_state.config.use_atm {
146 if let Some(atm) = atm {
147 Some(atm.to_owned())
148 } else {
149 Some(ATM::new(ATMConfigBuilder::default().build()?, shared_state.clone()).await?)
151 }
152 } else {
153 None
154 };
155
156 Ok(TDK {
157 inner: shared_state,
158 #[cfg(feature = "messaging")]
159 atm,
160 #[cfg(feature = "meeting-place")]
161 meeting_place: None,
162 })
163 }
164
165 pub fn get_shared_state(&self) -> Arc<TDKSharedState> {
167 self.inner.clone()
168 }
169
170 pub async fn add_profile(&self, profile: &TDKProfile) {
174 self.inner
175 .secrets_resolver
176 .insert_vec(&profile.secrets)
177 .await;
178 }
179
180 pub fn did_resolver(&self) -> &DIDCacheClient {
182 &self.inner.did_resolver
183 }
184
185 #[cfg(feature = "data-integrity")]
191 pub async fn verify_data<S>(
192 &self,
193 signed_doc: &S,
194 context: Option<Vec<String>>,
195 proof: &DataIntegrityProof,
196 ) -> Result<VerificationProof>
197 where
198 S: Serialize,
199 {
200 use affinidi_did_common::document::DocumentExt;
201 use affinidi_tdk_common::errors::TDKError;
202
203 let did = if let Some((did, _)) = proof.verification_method.split_once("#") {
204 did
205 } else {
206 return Err(TDKError::DataIntegrity(DataIntegrityError::MalformedProof(
207 "Invalid proof:verificationMethod. Must be DID#key-id format".to_string(),
208 )));
209 };
210
211 let resolved = self.inner.did_resolver.resolve(did).await?;
212 let public_bytes = if let Some(vm) = resolved
213 .doc
214 .get_verification_method(&proof.verification_method)
215 {
216 vm.get_public_key_bytes()
217 .map_err(|e| DataIntegrityError::InvalidPublicKey {
218 codec: None,
219 len: 0,
220 reason: format!("Failed to get public key bytes from verification method: {e}"),
221 })?
222 } else {
223 return Err(TDKError::DataIntegrity(DataIntegrityError::MalformedProof(
224 format!(
225 "Couldn't find key-id ({}) in resolved DID Document",
226 proof.verification_method
227 ),
228 )));
229 };
230
231 let mut options = VerifyOptions::new();
232 if let Some(ctx) = context {
233 options = options.with_expected_context(ctx);
234 }
235 proof
236 .verify_with_public_key(signed_doc, public_bytes.as_slice(), options)
237 .map_err(TDKError::DataIntegrity)?;
238
239 Ok(VerificationProof {
240 verified: true,
241 verified_document: None,
242 })
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use std::collections::HashMap;
249
250 use affinidi_data_integrity::{DataIntegrityError, crypto_suites::CryptoSuite};
251 use affinidi_tdk_common::{config::TDKConfig, errors::TDKError};
252
253 use crate::TDK;
254
255 #[tokio::test]
256 async fn invalid_verification_method() {
257 let proof = crate::DataIntegrityProof {
258 type_: "DataIntegrityProof".to_string(),
259 cryptosuite: CryptoSuite::EddsaJcs2022,
260 created: Some("2025-01-01T00:00:00Z".to_string()),
261 verification_method: "test".to_string(),
262 proof_purpose: "test".to_string(),
263 proof_value: Some("z2RPk8MWLoULfcbtpULoEsgfDsaAvyfD1PvQC2v3BjqqNtzGu8YJ4Nxq8CmJCZpPqA49uJhkxmxSztUQhBxqnVrYj".to_string()),
264 context: None,
265 };
266
267 let tdk = TDK::new(
268 TDKConfig::builder()
269 .with_load_environment(false)
270 .build()
271 .unwrap(),
272 None,
273 )
274 .await
275 .unwrap();
276 let result = tdk
277 .verify_data(&HashMap::<String, String>::new(), None, &proof)
278 .await;
279 assert!(result.is_err());
280 match result {
281 Err(TDKError::DataIntegrity(DataIntegrityError::MalformedProof(txt))) => {
282 assert_eq!(
283 txt,
284 "Invalid proof:verificationMethod. Must be DID#key-id format".to_string()
285 );
286 }
287 _ => panic!("Invalid return type"),
288 }
289 }
290
291 #[tokio::test]
292 async fn invalid_verification_method_2() {
293 let proof = crate::DataIntegrityProof {
294 type_: "DataIntegrityProof".to_string(),
295 cryptosuite: CryptoSuite::EddsaJcs2022,
296 created: Some("2025-01-01T00:00:00Z".to_string()),
297 verification_method: "did:key:not_a_key".to_string(),
298 proof_purpose: "test".to_string(),
299 proof_value: Some("z2RPk8MWLoULfcbtpULoEsgfDsaAvyfD1PvQC2v3BjqqNtzGu8YJ4Nxq8CmJCZpPqA49uJhkxmxSztUQhBxqnVrYj".to_string()),
300 context: None,
301 };
302
303 let tdk = TDK::new(
304 TDKConfig::builder()
305 .with_load_environment(false)
306 .build()
307 .unwrap(),
308 None,
309 )
310 .await
311 .unwrap();
312 let result = tdk
313 .verify_data(&HashMap::<String, String>::new(), None, &proof)
314 .await;
315 assert!(result.is_err());
316 match result {
317 Err(TDKError::DataIntegrity(DataIntegrityError::MalformedProof(txt))) => {
318 assert_eq!(
319 txt,
320 "Invalid proof:verificationMethod. Must be DID#key-id format".to_string()
321 );
322 }
323 _ => panic!("Invalid return type"),
324 }
325 }
326
327 #[tokio::test]
328 async fn invalid_verification_method_3() {
329 let proof = crate::DataIntegrityProof {
330 type_: "DataIntegrityProof".to_string(),
331 cryptosuite: CryptoSuite::EddsaJcs2022,
332 created: Some("2025-01-01T00:00:00Z".to_string()),
333 verification_method: "did:key:test#test".to_string(),
334 proof_purpose: "test".to_string(),
335 proof_value: Some("z2RPk8MWLoULfcbtpULoEsgfDsaAvyfD1PvQC2v3BjqqNtzGu8YJ4Nxq8CmJCZpPqA49uJhkxmxSztUQhBxqnVrYj".to_string()),
336 context: None,
337 };
338
339 let tdk = TDK::new(
340 TDKConfig::builder()
341 .with_load_environment(false)
342 .build()
343 .unwrap(),
344 None,
345 )
346 .await
347 .unwrap();
348 let result = tdk
349 .verify_data(&HashMap::<String, String>::new(), None, &proof)
350 .await;
351 assert!(result.is_err());
352 match result {
353 Err(TDKError::DIDResolver(txt)) => {
354 assert_eq!(
355 txt,
356 "DID error: Failed to parse DID: Invalid method-specific ID: invalid did:key encoding: Invalid multibase prefix: expected 'z' (base58btc), got 't'"
357 .to_string()
358 );
359 }
360 _ => panic!("Invalid return type {result:#?}"),
361 }
362 }
363}