1use std::{
2 collections::HashMap,
3 fmt::Display,
4 fs::{self, File},
5 io::{BufWriter, Write},
6 path::{Path, PathBuf},
7};
8
9use is_empty::IsEmpty;
11use josekit::JoseError;
12use josekit::{jwk::Jwk, jwt::JwtPayload};
13use rand::{distributions::Alphanumeric, thread_rng, Rng};
14use serde::{Deserialize, Serialize};
15use serde_json::{to_string_pretty as to_json, Value};
16use serde_with::skip_serializing_none;
17use ssi::{did::Service, jwk::JWK};
18use ssi::{did::ServiceEndpoint, one_or_many::OneOrMany};
19use std::fs::OpenOptions;
20use thiserror::Error;
21use trustchain_core::{attestor::AttestorError, key_manager::KeyManagerError, TRUSTCHAIN_DATA};
22
23#[derive(Error, Debug)]
24pub enum TrustchainCRError {
25 #[error("Wrapped serialization error: {0}")]
27 Serde(serde_json::Error),
28 #[error("Wrapped jose error: {0}")]
30 Jose(JoseError),
31 #[error("Missing JWK from verification method of a DID document.")]
33 MissingJWK,
34 #[error("Key id not found.")]
36 KeyNotFound,
37 #[error("Claim not found in JWTPayload.")]
39 ClaimNotFound,
40 #[error("Claim cannot be constructed from: {0}")]
42 ClaimCannotBeConstructed(String),
43 #[error("Invalid nonce type.")]
45 InvalidNonceType,
46 #[error("Failed to open file.")]
48 FailedToOpen,
49 #[error("Failed to serialize to file.")]
51 FailedToSerialize,
52 #[error("Failed to set permissions on file.")]
54 FailedToSetPermissions,
55 #[error("Failed to deserialize.")]
57 FailedToDeserialize,
58 #[error("Value is not a string: {0}")]
60 FailedToConvertToStr(Value),
61 #[error("Failed to deserialize with error: {0}.")]
63 FailedToDeserializeWithError(serde_json::Error),
64 #[error("Wrapped SSI JWK error: {0}.")]
65 WrappedSSIJWKError(ssi::jwk::Error),
66 #[error("Failed to determine CR status.")]
68 FailedStatusCheck,
69 #[error("Path does not exist. No challenge-response record for this temporary key id.")]
71 CRPathNotFound,
72 #[error("Failed to generate key.")]
74 FailedToGenerateKey,
75 #[error("Network request failed.")]
77 Reqwest(reqwest::Error),
78 #[error("Invalid service endpoint.")]
80 InvalidServiceEndpoint,
81 #[error("Failed to initiate challenge-response.")]
83 FailedToInitiateCR,
84 #[error("Failed attestation request.")]
86 FailedAttestationRequest,
87 #[error("Field not found.")]
89 FieldNotFound,
90 #[error("Response to challenge failed.")]
92 FailedToRespond(reqwest::Response),
93 #[error("Failed to verify nonce.")]
95 FailedToVerifyNonce,
96 #[error("IO error: {0}")]
98 IOError(std::io::Error),
99 #[error("KeyManager error: {0}")]
101 KeyManagerError(#[from] KeyManagerError),
102 #[error("Attestor error: {0}")]
104 AttestorError(#[from] AttestorError),
105 #[error("SSI JWK error: {0}")]
107 SSIJwkError(#[from] ssi::jwk::Error),
108 #[error("Must contain data but custom response contained no data")]
110 ResponseMustContainData,
111}
112
113impl From<JoseError> for TrustchainCRError {
114 fn from(err: JoseError) -> Self {
115 Self::Jose(err)
116 }
117}
118
119#[derive(Serialize, Deserialize)]
120pub struct CustomResponse {
122 pub message: String,
123 pub data: Option<String>,
124}
125
126#[derive(Debug, PartialEq)]
127pub enum CurrentCRState {
129 NotStarted,
130 IdentityCRInitiated,
131 IdentityChallengeComplete,
132 IdentityResponseComplete,
133 ContentCRInitiated,
134 ContentChallengeComplete,
135 ContentResponseComplete,
136}
137
138#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
140pub struct Nonce(String);
142
143impl Default for Nonce {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149impl Nonce {
150 pub fn new() -> Self {
151 Self(
152 thread_rng()
153 .sample_iter(&Alphanumeric)
154 .take(32)
155 .map(char::from)
156 .collect(),
157 )
158 }
159}
160
161impl AsRef<str> for Nonce {
162 fn as_ref(&self) -> &str {
163 &self.0
164 }
165}
166
167impl Display for Nonce {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 write!(f, "{}", self.0)
170 }
171}
172
173impl From<String> for Nonce {
174 fn from(s: String) -> Self {
175 Self(s)
176 }
177}
178
179impl TryFrom<&Nonce> for JwtPayload {
180 type Error = TrustchainCRError;
181 fn try_from(value: &Nonce) -> Result<Self, Self::Error> {
182 let mut payload = JwtPayload::new();
183 payload.set_claim("nonce", Some(Value::from(value.to_string())))?;
184 Ok(payload)
185 }
186}
187
188impl From<serde_json::Error> for TrustchainCRError {
189 fn from(value: serde_json::Error) -> Self {
190 TrustchainCRError::FailedToDeserializeWithError(value)
191 }
192}
193
194pub trait ElementwiseSerializeDeserialize
196where
197 Self: Serialize,
198{
199 fn elementwise_serialize(&self, path: &PathBuf) -> Result<(), TrustchainCRError> {
201 let serialized = serde_json::to_value(self)?;
202 if let Value::Object(fields) = serialized {
203 for (field_name, field_value) in fields {
204 if !field_value.is_null() {
205 let json_filename = format!("{}.json", field_name);
206 let file_path = path.join(json_filename);
207 self.save_to_file(&file_path, &to_json(&field_value)?)?;
208 }
209 }
210 }
211 Ok(())
212 }
213 fn elementwise_deserialize(self, path: &PathBuf) -> Result<Option<Self>, TrustchainCRError>
215 where
216 Self: Sized;
217 fn save_to_file(&self, path: &PathBuf, data: &str) -> Result<(), TrustchainCRError> {
219 if path.exists() {
220 println!("File already exists: {:?}", path);
221 return Ok(());
222 }
223
224 let new_file = OpenOptions::new()
226 .create(true)
227 .append(false)
228 .truncate(false)
229 .write(true)
230 .open(path);
231
232 match new_file {
234 Ok(file) => {
235 let mut writer = BufWriter::new(file);
236 match writer.write_all(data.as_bytes()) {
237 Ok(_) => {
238 let mut permissions = fs::metadata(path)
240 .map_err(|_| TrustchainCRError::FailedToSetPermissions)?
241 .permissions();
242 permissions.set_readonly(true);
243 fs::set_permissions(path, permissions)
244 .map_err(|_| TrustchainCRError::FailedToSetPermissions)?;
245 Ok(())
246 }
247 Err(_) => Err(TrustchainCRError::FailedToSerialize),
248 }
249 }
250
251 Err(_) => Err(TrustchainCRError::FailedToSerialize),
252 }
253 }
254}
255
256#[skip_serializing_none]
257#[derive(Debug, Serialize, Deserialize, Clone)]
258pub struct RequesterDetails {
260 pub requester_org: String,
261 pub operator_name: String,
262}
263
264#[skip_serializing_none]
265#[derive(Debug, Serialize, Deserialize, IsEmpty, Clone)]
266pub struct IdentityCRInitiation {
268 pub temp_p_key: Option<Jwk>,
269 pub temp_s_key: Option<Jwk>,
270 pub requester_details: Option<RequesterDetails>,
271}
272
273impl Default for IdentityCRInitiation {
274 fn default() -> Self {
275 Self::new()
276 }
277}
278
279impl IdentityCRInitiation {
280 pub fn new() -> Self {
281 Self {
282 temp_p_key: None,
283 temp_s_key: None,
284 requester_details: None,
285 }
286 }
287 pub fn is_complete(&self) -> bool {
290 self.temp_p_key.is_some() && self.requester_details.is_some()
291 }
292
293 pub fn temp_p_key(&self) -> Result<&Jwk, TrustchainCRError> {
294 self.temp_p_key
295 .as_ref()
296 .ok_or(TrustchainCRError::KeyNotFound)
297 }
298 pub fn temp_s_key(&self) -> Result<&Jwk, TrustchainCRError> {
299 self.temp_s_key
300 .as_ref()
301 .ok_or(TrustchainCRError::KeyNotFound)
302 }
303}
304
305impl ElementwiseSerializeDeserialize for IdentityCRInitiation {
306 fn elementwise_deserialize(
308 mut self,
309 path: &PathBuf,
310 ) -> Result<Option<IdentityCRInitiation>, TrustchainCRError> {
311 let temp_p_key_path = path.join("temp_p_key.json");
312 self.temp_p_key = match File::open(temp_p_key_path) {
314 Ok(file) => {
315 let reader = std::io::BufReader::new(file);
316 let deserialized = serde_json::from_reader(reader)?;
317 Some(deserialized)
318 }
319 Err(_) => None,
320 };
321 let temp_s_key_path = path.join("temp_s_key.json");
333 self.temp_s_key = match File::open(temp_s_key_path) {
334 Ok(file) => {
335 let reader = std::io::BufReader::new(file);
336 let deserialized = serde_json::from_reader(reader)?;
337 Some(deserialized)
338 }
339 Err(_) => None,
340 };
341
342 let requester_details_path = path.join("requester_details.json");
343 self.requester_details = match File::open(requester_details_path) {
344 Ok(file) => {
345 let reader = std::io::BufReader::new(file);
346 let deserialized = serde_json::from_reader(reader)?;
347 Some(deserialized)
348 }
349 Err(_) => None,
350 };
351
352 if self.temp_p_key.is_none()
353 && self.temp_s_key.is_none()
354 && self.requester_details.is_none()
355 {
356 return Ok(None);
357 }
358
359 Ok(Some(self))
360 }
361}
362
363#[skip_serializing_none]
364#[derive(Debug, Serialize, Deserialize, Clone, IsEmpty)]
365pub struct IdentityCRChallenge {
367 pub update_p_key: Option<Jwk>,
368 pub update_s_key: Option<Jwk>,
369 pub identity_nonce: Option<Nonce>, pub identity_challenge_signature: Option<String>,
372 pub identity_response_signature: Option<String>,
373}
374
375impl Default for IdentityCRChallenge {
376 fn default() -> Self {
377 Self::new()
378 }
379}
380
381impl IdentityCRChallenge {
382 pub fn new() -> Self {
383 Self {
384 update_p_key: None,
385 update_s_key: None,
386 identity_nonce: None,
387 identity_challenge_signature: None,
388 identity_response_signature: None,
389 }
390 }
391 fn challenge_complete(&self) -> bool {
394 self.update_p_key.is_some()
395 && self.identity_nonce.is_some()
396 && self.identity_challenge_signature.is_some()
397 }
398 fn is_complete(&self) -> bool {
400 self.challenge_complete() && self.identity_response_signature.is_some()
401 }
402}
403
404impl ElementwiseSerializeDeserialize for IdentityCRChallenge {
405 fn elementwise_deserialize(
407 mut self,
408 path: &PathBuf,
409 ) -> Result<Option<IdentityCRChallenge>, TrustchainCRError> {
410 let full_path = path.join("update_p_key.json");
412 self.update_p_key = match File::open(full_path) {
413 Ok(file) => {
414 let reader = std::io::BufReader::new(file);
415 let deserialized = serde_json::from_reader(reader)?;
416 Some(deserialized)
417 }
418 Err(_) => None,
419 };
420 let mut full_path = path.join("update_s_key.json");
422 self.update_s_key = match File::open(&full_path) {
423 Ok(file) => {
424 let reader = std::io::BufReader::new(file);
425 let deserialized = serde_json::from_reader(reader)?;
426 Some(deserialized)
427 }
428 Err(_) => None,
429 };
430 full_path = path.join("identity_nonce.json");
432 self.identity_nonce = match File::open(&full_path) {
433 Ok(file) => {
434 let reader = std::io::BufReader::new(file);
435 let deserialized = serde_json::from_reader(reader)?;
436 Some(deserialized)
437 }
438 Err(_) => None,
439 };
440 full_path = path.join("identity_challenge_signature.json");
442 self.identity_challenge_signature = match File::open(&full_path) {
443 Ok(file) => {
444 let reader = std::io::BufReader::new(file);
445 let deserialized = serde_json::from_reader(reader)?;
446 Some(deserialized)
447 }
448 Err(_) => None,
449 };
450 full_path = path.join("identity_response_signature.json");
452 self.identity_response_signature = match File::open(&full_path) {
453 Ok(file) => {
454 let reader = std::io::BufReader::new(file);
455 let deserialized = serde_json::from_reader(reader)?;
456 Some(deserialized)
457 }
458 Err(_) => None,
459 };
460
461 if self.update_p_key.is_none()
462 && self.identity_nonce.is_none()
463 && self.identity_challenge_signature.is_none()
464 && self.identity_response_signature.is_none()
465 {
466 return Ok(None);
467 }
468
469 Ok(Some(self))
470 }
471}
472
473impl TryFrom<&IdentityCRChallenge> for JwtPayload {
474 type Error = TrustchainCRError;
475 fn try_from(value: &IdentityCRChallenge) -> Result<Self, Self::Error> {
476 let mut payload = JwtPayload::new();
477 payload.set_claim(
478 "identity_nonce",
479 Some(Value::from(
480 value
481 .identity_nonce
482 .as_ref()
483 .ok_or(TrustchainCRError::ClaimCannotBeConstructed(
484 "`identity_nonce` field in `IdentityCRChallenge` is missing (`None`)"
485 .to_string(),
486 ))?
487 .to_string(),
488 )),
489 )?;
490 payload.set_claim(
491 "update_p_key",
492 Some(Value::from(
493 value
494 .update_p_key
495 .as_ref()
496 .ok_or(TrustchainCRError::ClaimCannotBeConstructed(
497 "`update_p_key` field in `IdentityCRChallenge` is missing (`None`)"
498 .to_string(),
499 ))?
500 .to_string(),
501 )),
502 )?;
503 Ok(payload)
504 }
505}
506
507impl TryFrom<&JwtPayload> for IdentityCRChallenge {
508 type Error = TrustchainCRError;
509 fn try_from(value: &JwtPayload) -> Result<Self, Self::Error> {
510 let mut challenge = IdentityCRChallenge {
511 update_p_key: None,
512 update_s_key: None,
513 identity_nonce: None,
514 identity_challenge_signature: None,
515 identity_response_signature: None,
516 };
517 challenge.update_p_key = Some(serde_json::from_str(
518 value
519 .claim("update_p_key")
520 .ok_or(TrustchainCRError::ClaimNotFound)?
521 .as_str()
522 .ok_or(TrustchainCRError::FailedToConvertToStr(
523 value.claim("update_p_key").unwrap().clone(),
525 ))?,
526 )?);
527 challenge.identity_nonce = Some(Nonce::from(
528 value
531 .claim("identity_nonce")
532 .ok_or(TrustchainCRError::ClaimNotFound)?
533 .as_str()
534 .ok_or(TrustchainCRError::FailedToConvertToStr(
535 value.claim("identity_nonce").unwrap().clone(),
537 ))?
538 .to_string(),
539 ));
540 Ok(challenge)
541 }
542}
543
544#[derive(Debug, Serialize, Deserialize, Clone, IsEmpty)]
545pub struct ContentCRInitiation {
547 pub requester_did: Option<String>,
548}
549
550impl Default for ContentCRInitiation {
551 fn default() -> Self {
552 Self::new()
553 }
554}
555
556impl ContentCRInitiation {
557 pub fn new() -> Self {
558 Self {
559 requester_did: None,
560 }
561 }
562
563 fn is_complete(&self) -> bool {
564 self.requester_did.is_some()
565 }
566}
567
568impl ElementwiseSerializeDeserialize for ContentCRInitiation {
569 fn elementwise_deserialize(
571 mut self,
572 path: &PathBuf,
573 ) -> Result<Option<ContentCRInitiation>, TrustchainCRError> {
574 let requester_details_path = path.join("requester_did.json");
575 self.requester_did = match File::open(requester_details_path) {
576 Ok(file) => {
577 let reader = std::io::BufReader::new(file);
578 let deserialized = serde_json::from_reader(reader)?;
579 Some(deserialized)
580 }
581 Err(_) => None,
582 };
583
584 if self.requester_did.is_none() {
585 return Ok(None);
586 }
587
588 Ok(Some(self))
589 }
590}
591
592#[derive(Debug, Serialize, Deserialize, IsEmpty)]
593pub struct ContentCRChallenge {
595 pub content_nonce: Option<HashMap<String, Nonce>>,
596 pub content_challenge_signature: Option<String>,
597 pub content_response_signature: Option<String>,
598}
599
600impl Default for ContentCRChallenge {
601 fn default() -> Self {
602 Self::new()
603 }
604}
605
606impl ContentCRChallenge {
607 pub fn new() -> Self {
608 Self {
609 content_nonce: None,
610 content_challenge_signature: None,
611 content_response_signature: None,
612 }
613 }
614 fn challenge_complete(&self) -> bool {
616 self.content_nonce.is_some() && self.content_challenge_signature.is_some()
617 }
618 fn is_complete(&self) -> bool {
620 self.challenge_complete() && self.content_response_signature.is_some()
621 }
622}
623
624impl ElementwiseSerializeDeserialize for ContentCRChallenge {
625 fn elementwise_deserialize(
627 mut self,
628 path: &PathBuf,
629 ) -> Result<Option<ContentCRChallenge>, TrustchainCRError> {
630 let mut full_path = path.join("content_nonce.json");
632 self.content_nonce = match File::open(&full_path) {
633 Ok(file) => {
634 let reader = std::io::BufReader::new(file);
635 let deserialized = serde_json::from_reader(reader)?;
636 Some(deserialized)
637 }
638 Err(_) => None,
639 };
640
641 full_path = path.join("content_challenge_signature.json");
643 self.content_challenge_signature = match File::open(&full_path) {
644 Ok(file) => {
645 let reader = std::io::BufReader::new(file);
646 let deserialized = serde_json::from_reader(reader)?;
647 Some(deserialized)
648 }
649 Err(_) => None,
650 };
651 full_path = path.join("content_response_signature.json");
653 self.content_response_signature = match File::open(&full_path) {
654 Ok(file) => {
655 let reader = std::io::BufReader::new(file);
656 let deserialized = serde_json::from_reader(reader)?;
657 Some(deserialized)
658 }
659 Err(_) => None,
660 };
661
662 if self.content_nonce.is_none()
663 && self.content_challenge_signature.is_none()
664 && self.content_response_signature.is_none()
665 {
666 return Ok(None);
667 }
668
669 Ok(Some(self))
670 }
671}
672
673#[skip_serializing_none]
674#[derive(Debug, Serialize, Deserialize, IsEmpty)]
675pub struct CRState {
678 pub identity_cr_initiation: Option<IdentityCRInitiation>,
679 pub identity_challenge_response: Option<IdentityCRChallenge>,
680 pub content_cr_initiation: Option<ContentCRInitiation>,
681 pub content_challenge_response: Option<ContentCRChallenge>,
682}
683
684impl Default for CRState {
685 fn default() -> Self {
686 Self::new()
687 }
688}
689
690impl CRState {
691 pub fn new() -> Self {
692 Self {
693 identity_cr_initiation: None,
694 identity_challenge_response: None,
695 content_cr_initiation: None,
696 content_challenge_response: None,
697 }
698 }
699 pub fn is_complete(&self) -> bool {
701 if let (Some(ici), Some(icr), Some(cci), Some(ccr)) = (
702 self.identity_cr_initiation.as_ref(),
703 self.identity_challenge_response.as_ref(),
704 self.content_cr_initiation.as_ref(),
705 self.content_challenge_response.as_ref(),
706 ) {
707 return ici.is_complete()
708 && icr.is_complete()
709 && cci.is_complete()
710 && ccr.is_complete();
711 }
712 false
713 }
714 pub fn check_cr_status(&self) -> Result<CurrentCRState, TrustchainCRError> {
716 println!("Checking current challenge-response status...");
717 println!(" ");
718 let mut current_state = CurrentCRState::NotStarted;
719 if self.is_empty() {
720 println!("{}", get_status_message(¤t_state));
721 return Ok(current_state);
722 }
723
724 if self.is_complete() {
726 current_state = CurrentCRState::ContentResponseComplete;
727 println!("{}", get_status_message(¤t_state));
728 return Ok(current_state);
729 }
730
731 if self.identity_cr_initiation.is_none()
733 || !self.identity_cr_initiation.as_ref().unwrap().is_complete()
735 {
736 println!("{}", get_status_message(¤t_state));
737 return Ok(current_state);
738 }
739 current_state = CurrentCRState::IdentityCRInitiated;
740 println!("{}", get_status_message(¤t_state));
741
742 if self.identity_challenge_response.is_none()
744 || !self
746 .identity_challenge_response
747 .as_ref()
748 .unwrap()
749 .challenge_complete()
750 {
751 return Ok(current_state);
752 }
753 current_state = CurrentCRState::IdentityChallengeComplete;
754 println!("{}", get_status_message(¤t_state));
755
756 if self
758 .identity_challenge_response
759 .is_none()
760 || !self
762 .identity_challenge_response
763 .as_ref()
764 .unwrap()
765 .is_complete()
766 {
767 return Ok(current_state);
768 }
769 current_state = CurrentCRState::IdentityResponseComplete;
770
771 if self.content_cr_initiation.is_none()
773 || !self.content_cr_initiation.as_ref().unwrap().is_complete()
775 {
776 return Ok(current_state);
777 }
778 current_state = CurrentCRState::ContentCRInitiated;
779
780 if self.content_challenge_response.is_none()
782 || !self
784 .content_challenge_response
785 .as_ref()
786 .unwrap()
787 .challenge_complete()
788 {
789 return Ok(current_state);
790 }
791 current_state = CurrentCRState::ContentChallengeComplete;
792
793 if self.content_challenge_response.is_none()
795 || !self
797 .content_challenge_response
798 .as_ref()
799 .unwrap()
800 .is_complete()
801 {
802 return Ok(current_state);
803 }
804
805 Ok(current_state)
806 }
807}
808
809impl ElementwiseSerializeDeserialize for CRState {
810 fn elementwise_serialize(&self, path: &PathBuf) -> Result<(), TrustchainCRError> {
812 if let Some(identity_initiation) = &self.identity_cr_initiation {
813 identity_initiation.elementwise_serialize(path)?;
814 }
815 if let Some(identity_challenge_response) = &self.identity_challenge_response {
816 identity_challenge_response.elementwise_serialize(path)?;
817 }
818 if let Some(content_cr_initiation) = &self.content_cr_initiation {
819 content_cr_initiation.elementwise_serialize(path)?;
820 }
821 if let Some(content_challenge_response) = &self.content_challenge_response {
822 content_challenge_response.elementwise_serialize(path)?;
823 }
824 Ok(())
825 }
826 fn elementwise_deserialize(
828 mut self,
829 path: &PathBuf,
830 ) -> Result<Option<CRState>, TrustchainCRError> {
831 self.identity_cr_initiation = IdentityCRInitiation::new().elementwise_deserialize(path)?;
832 self.identity_challenge_response =
833 IdentityCRChallenge::new().elementwise_deserialize(path)?;
834 self.content_cr_initiation = ContentCRInitiation::new().elementwise_deserialize(path)?;
835 self.content_challenge_response =
836 ContentCRChallenge::new().elementwise_deserialize(path)?;
837 Ok(Some(self))
838 }
839}
840
841fn get_status_message(current_state: &CurrentCRState) -> String {
843 match current_state {
844 CurrentCRState::NotStarted => {
845 String::from("No records found for this challenge-response identifier or entity. \nThe challenge-response process has not been initiated yet.")
846 }
847 CurrentCRState::IdentityCRInitiated => {
848 String::from("Identity challenge-response initiated. Await response.")
849 }
850 CurrentCRState::IdentityChallengeComplete => {
851 String::from("Identity challenge has been presented. Await response.")
852 }
853 CurrentCRState::IdentityResponseComplete => {
854 String::from("Identity challenge-response complete.")
855 }
856 CurrentCRState::ContentCRInitiated => {
857 String::from("Content challenge-response initiated. Await response.")
858 }
859 CurrentCRState::ContentChallengeComplete => {
860 String::from("Content challenge has been presented. Await response.")
861 }
862 CurrentCRState::ContentResponseComplete => {
863 String::from("Challenge-response complete.")
864 }
865 }
866}
867
868pub fn matching_endpoint(
871 services: &[Service],
872 fragment: &str,
873) -> Result<String, TrustchainCRError> {
874 let mut endpoints = Vec::new();
875 for service in services {
876 if service.id.eq(fragment) {
877 match &service.service_endpoint {
878 Some(OneOrMany::One(ServiceEndpoint::URI(uri))) => {
879 endpoints.push(uri.to_string());
880 }
881
882 _ => return Err(TrustchainCRError::InvalidServiceEndpoint),
883 }
884 }
885 }
886 if endpoints.len() != 1 {
887 return Err(TrustchainCRError::InvalidServiceEndpoint);
888 }
889 Ok(endpoints[0].clone())
890}
891
892pub fn attestation_request_path(key: &JWK, prefix: &str) -> Result<PathBuf, TrustchainCRError> {
894 let path = attestation_request_basepath(prefix)?;
896 let key_id = key.thumbprint()?; Ok(path.join(key_id))
898}
899
900pub fn attestation_request_basepath(prefix: &str) -> Result<PathBuf, TrustchainCRError> {
902 let path: String = std::env::var(TRUSTCHAIN_DATA)
904 .expect("`TRUSTCHAIN_DATA` environment variable must be set.");
905 Ok(Path::new(path.as_str())
906 .join(prefix)
907 .join("attestation_requests"))
908}
909
910#[cfg(test)]
911mod tests {
912 use crate::attestation_encryption_utils::extract_key_ids_and_jwk;
913 use crate::data::{TEST_CANDIDATE_DDID_DOCUMENT, TEST_TEMP_KEY, TEST_UPDATE_KEY};
914 use ssi::did::Document;
915 use tempfile::tempdir;
916
917 use super::*;
918
919 #[test]
920 fn test_elementwise_serialize() {
921 let temp_s_key: Jwk = serde_json::from_str(TEST_TEMP_KEY).unwrap();
923 let initiation = IdentityCRInitiation {
924 temp_p_key: None,
925 temp_s_key: Some(temp_s_key.to_public_key().unwrap()),
926 requester_details: Some(RequesterDetails {
927 requester_org: String::from("My Org"),
928 operator_name: String::from("John Doe"),
929 }),
930 };
931
932 let identity_challenge = IdentityCRChallenge {
934 update_p_key: serde_json::from_str(TEST_UPDATE_KEY).unwrap(),
935 update_s_key: None,
936 identity_nonce: Some(Nonce::new()),
937 identity_challenge_signature: Some(String::from("some challenge signature string")),
938 identity_response_signature: Some(String::from("some response signature string")),
939 };
940
941 let content_initiation = ContentCRInitiation {
943 requester_did: Some("did:example:123456789abcdefghi".to_string()),
945 };
946 let doc: Document = serde_json::from_str(TEST_CANDIDATE_DDID_DOCUMENT).unwrap();
948 let test_keys_map = extract_key_ids_and_jwk(&doc).unwrap();
949
950 let nonces: HashMap<String, Nonce> =
952 test_keys_map
953 .iter()
954 .fold(HashMap::new(), |mut acc, (key_id, _)| {
955 acc.insert(String::from(key_id), Nonce::new());
956 acc
957 });
958 let content_challenge_response = ContentCRChallenge {
959 content_nonce: Some(nonces),
960 content_challenge_signature: Some(String::from(
961 "some content challenge signature string",
962 )),
963 content_response_signature: Some(String::from(
964 "some content response signature string",
965 )),
966 };
967
968 let cr_state = CRState {
970 identity_cr_initiation: Some(initiation),
971 identity_challenge_response: Some(identity_challenge),
972 content_cr_initiation: Some(content_initiation),
973 content_challenge_response: Some(content_challenge_response),
974 };
975 let path = tempdir().unwrap().into_path();
977 let result = cr_state.elementwise_serialize(&path);
978 assert!(result.is_ok());
979
980 let result = cr_state.elementwise_serialize(&path);
982 assert!(result.is_ok());
983 }
984
985 #[test]
986 fn test_elementwise_deserialize_initiation() {
987 let cr_initiation = IdentityCRInitiation::new();
988 let temp_path = tempdir().unwrap().into_path();
989
990 let result = cr_initiation.elementwise_deserialize(&temp_path);
992 assert!(result.is_ok());
993 let initiation = result.unwrap();
994 assert!(initiation.is_none());
995
996 let cr_initiation = IdentityCRInitiation::new();
998 let temp_p_key_path = temp_path.join("temp_p_key.json");
999 let temp_p_key_file = File::create(&temp_p_key_path).unwrap();
1000 let temp_p_key: Jwk = serde_json::from_str(TEST_TEMP_KEY).unwrap();
1001 serde_json::to_writer(temp_p_key_file, &temp_p_key).unwrap();
1002
1003 let result = cr_initiation.elementwise_deserialize(&temp_path);
1004 assert!(result.is_ok());
1005 let initiation = result.unwrap().unwrap();
1006 assert!(initiation.temp_s_key.is_none());
1007 assert!(initiation.temp_p_key.is_some());
1008 assert!(initiation.requester_details.is_none());
1009
1010 let cr_initiation = IdentityCRInitiation::new();
1012 let requester_details_path = temp_path.join("requester_details.json");
1013 let requester_details_file = File::create(requester_details_path).unwrap();
1014 let requester_details = RequesterDetails {
1015 requester_org: String::from("My Org"),
1016 operator_name: String::from("John Doe"),
1017 };
1018 serde_json::to_writer(requester_details_file, &requester_details).unwrap();
1019 let result = cr_initiation.elementwise_deserialize(&temp_path);
1020 assert!(result.is_ok());
1021 let initiation = result.unwrap().unwrap();
1022 assert!(initiation.temp_p_key.is_some());
1023 assert!(initiation.requester_details.is_some());
1024
1025 let cr_initiation = IdentityCRInitiation::new();
1028 let temp_p_key_file = File::create(&temp_p_key_path).unwrap();
1030 serde_json::to_writer(temp_p_key_file, "this is not valid json").unwrap();
1031 let result = cr_initiation.elementwise_deserialize(&temp_path);
1032 assert!(result.is_err());
1033 }
1034
1035 #[test]
1036 fn test_elementwise_deserialize_identity_challenge() {
1037 let identity_challenge = IdentityCRChallenge::new();
1038 let temp_path = tempdir().unwrap().into_path();
1039
1040 let result = identity_challenge.elementwise_deserialize(&temp_path);
1042 assert!(result.is_ok());
1043 let identity_challenge = result.unwrap();
1044 assert!(identity_challenge.is_none());
1045
1046 let update_p_key_path = temp_path.join("update_p_key.json");
1048 let update_p_key_file = File::create(update_p_key_path).unwrap();
1049 let update_p_key: Jwk = serde_json::from_str(TEST_UPDATE_KEY).unwrap();
1050 serde_json::to_writer(update_p_key_file, &update_p_key).unwrap();
1051 let identity_challenge = IdentityCRChallenge::new();
1052 let result = identity_challenge.elementwise_deserialize(&temp_path);
1053 assert!(result.is_ok());
1054 let identity_challenge = result.unwrap().unwrap();
1055 assert_eq!(identity_challenge.update_p_key, Some(update_p_key));
1056 assert!(identity_challenge.identity_nonce.is_none());
1057 assert!(identity_challenge.identity_challenge_signature.is_none());
1058 assert!(identity_challenge.identity_response_signature.is_none());
1059
1060 let identity_nonce_path = temp_path.join("identity_nonce.json");
1062 let identity_nonce_file = File::create(identity_nonce_path).unwrap();
1063 serde_json::to_writer(identity_nonce_file, &42).unwrap();
1064 let identity_challenge = IdentityCRChallenge::new();
1065 let result = identity_challenge.elementwise_deserialize(&temp_path);
1066 assert!(result.is_err());
1067 println!("Error: {:?}", result.unwrap_err());
1068 }
1069
1070 #[test]
1071 fn test_elementwise_deserialize_content_challenge() {
1072 let content_challenge = ContentCRChallenge::new();
1073 let temp_path = tempdir().unwrap().into_path();
1074
1075 let result = content_challenge.elementwise_deserialize(&temp_path);
1077 assert!(result.is_ok());
1078 assert!(result.unwrap().is_none());
1079
1080 let content_challenge = ContentCRChallenge::new();
1082 let content_nonce_path = temp_path.join("content_nonce.json");
1083 let content_nonce_file = File::create(&content_nonce_path).unwrap();
1084 let mut nonces_map: HashMap<&str, Nonce> = HashMap::new();
1085 nonces_map.insert("test_id", Nonce::new());
1086 serde_json::to_writer(content_nonce_file, &nonces_map).unwrap();
1087 let result = content_challenge.elementwise_deserialize(&temp_path);
1088 assert!(result.is_ok());
1089 let content_challenge = result.unwrap().unwrap();
1090 assert!(content_challenge.content_nonce.is_some());
1091 assert!(content_challenge.content_challenge_signature.is_none());
1092 assert!(content_challenge.content_response_signature.is_none());
1093
1094 let content_nonce_file = File::create(&content_nonce_path).unwrap();
1096 serde_json::to_writer(content_nonce_file, "thisisinvalid").unwrap();
1097 let result = content_challenge.elementwise_deserialize(&temp_path);
1098 print!("Result: {:?}", result);
1099 assert!(result.is_err());
1100 }
1101
1102 #[test]
1103 fn test_deserialize_challenge_state() {
1104 let path = tempdir().unwrap().into_path();
1105 let challenge_state = CRState::new();
1106
1107 let identity_initiatiation = IdentityCRInitiation {
1109 temp_s_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()),
1110 temp_p_key: None,
1111 requester_details: Some(RequesterDetails {
1112 requester_org: String::from("My Org"),
1113 operator_name: String::from("John Doe"),
1114 }),
1115 };
1116 let _ = identity_initiatiation.elementwise_serialize(&path);
1117 let identity_challenge = IdentityCRChallenge {
1118 update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()),
1119 update_s_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()),
1120 identity_nonce: Some(Nonce::new()),
1121 identity_challenge_signature: Some(String::from("some challenge signature string")),
1122 identity_response_signature: Some(String::from("some response signature string")),
1123 };
1124 let _ = identity_challenge.elementwise_serialize(&path);
1125
1126 let content_cr_initiation = ContentCRInitiation {
1127 requester_did: Some("did:example:123456789abcdefghi".to_string()),
1129 };
1130 let _ = content_cr_initiation.elementwise_serialize(&path);
1131
1132 let result = challenge_state.elementwise_deserialize(&path);
1133 assert!(result.is_ok());
1134 let challenge_state = result.unwrap().unwrap();
1135 println!(
1136 "Challenge state deserialized from files: {:?}",
1137 challenge_state
1138 );
1139 assert!(challenge_state.identity_cr_initiation.is_some());
1140 assert!(challenge_state.identity_challenge_response.is_some());
1141 assert!(challenge_state.content_cr_initiation.is_some());
1142 assert!(challenge_state.content_challenge_response.is_none());
1143
1144 let identity_nonce_path = path.join("content_nonce.json");
1146 let identity_nonce_file = File::create(identity_nonce_path).unwrap();
1147 serde_json::to_writer(identity_nonce_file, &42).unwrap();
1148 let challenge_state = CRState::new().elementwise_deserialize(&path);
1149 assert!(challenge_state.is_err());
1150 }
1151
1152 #[test]
1153 fn test_matching_endpoint() {
1154 let services = vec![
1155 Service {
1156 id: String::from("#service-1"),
1157 service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from(
1158 "https://example.com/endpoint-1",
1159 )))),
1160 type_: ssi::one_or_many::OneOrMany::One("Service1".to_string()),
1161 property_set: None,
1162 },
1163 Service {
1164 id: String::from("#service-2"),
1165 service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from(
1166 "https://example.com/endpoint-2",
1167 )))),
1168 type_: ssi::one_or_many::OneOrMany::One("Service2".to_string()),
1169 property_set: None,
1170 },
1171 ];
1172 let result = matching_endpoint(&services, "#service-1");
1173 assert_eq!(result.unwrap(), "https://example.com/endpoint-1");
1174 let result = matching_endpoint(&services, "service-1");
1175 assert!(result.is_err());
1176 }
1177
1178 #[test]
1179 fn test_matching_endpoint_multiple_endpoints_found() {
1180 let services = vec![
1182 Service {
1183 id: String::from("#service-1"),
1184 service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from(
1185 "https://example.com/endpoint-1",
1186 )))),
1187 type_: ssi::one_or_many::OneOrMany::One("Service1".to_string()),
1188 property_set: None,
1189 },
1190 Service {
1191 id: String::from("#service-1"),
1192 service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from(
1193 "https://example.com/endpoint-2",
1194 )))),
1195 type_: ssi::one_or_many::OneOrMany::One("Service1".to_string()),
1196 property_set: None,
1197 },
1198 ];
1199 let result = matching_endpoint(&services, "#service-1");
1200 assert!(result.is_err());
1201 }
1202
1203 #[test]
1204 fn test_check_cr_status() {
1205 let mut cr_state = CRState::new();
1206 let result = cr_state.check_cr_status().unwrap();
1208 assert_eq!(result, CurrentCRState::NotStarted);
1209
1210 cr_state.identity_cr_initiation = Some(IdentityCRInitiation {
1212 temp_p_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()),
1214 temp_s_key: None,
1215 requester_details: None,
1216 });
1217 let result = cr_state.check_cr_status();
1218 assert_eq!(result.unwrap(), CurrentCRState::NotStarted);
1219
1220 cr_state.identity_cr_initiation = Some(IdentityCRInitiation {
1222 temp_p_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()),
1224 temp_s_key: None,
1225 requester_details: Some(RequesterDetails {
1226 requester_org: String::from("My Org"),
1227 operator_name: String::from("John Doe"),
1228 }),
1229 });
1230 cr_state.identity_challenge_response = Some(IdentityCRChallenge {
1231 update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()),
1232 update_s_key: None,
1233 identity_nonce: Some(Nonce::new()),
1234 identity_challenge_signature: Some(String::from("some challenge signature string")),
1235 identity_response_signature: None,
1236 });
1237 let result = cr_state.check_cr_status();
1238 assert_eq!(result.unwrap(), CurrentCRState::IdentityChallengeComplete);
1239
1240 cr_state.identity_challenge_response = Some(IdentityCRChallenge {
1242 update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()),
1244 update_s_key: None,
1245 identity_nonce: Some(Nonce::new()),
1246 identity_challenge_signature: Some(String::from("some challenge signature string")),
1247 identity_response_signature: Some(String::from("some response signature string")),
1248 });
1249 cr_state.content_cr_initiation = {
1250 Some(ContentCRInitiation {
1251 requester_did: Some("did:example:123456789abcdefghi".to_string()),
1252 })
1253 };
1254 let result = cr_state.check_cr_status();
1255 assert_eq!(result.unwrap(), CurrentCRState::ContentCRInitiated);
1256
1257 cr_state.content_challenge_response = Some(ContentCRChallenge {
1259 content_nonce: Some(HashMap::new()),
1260 content_challenge_signature: Some(String::from(
1261 "some content challenge signature string",
1262 )),
1263 content_response_signature: Some(String::from(
1264 "some content response signature string",
1265 )),
1266 });
1267 let result = cr_state.check_cr_status();
1268 assert_eq!(result.unwrap(), CurrentCRState::ContentResponseComplete);
1269 }
1270 #[test]
1271 fn test_check_cr_status_inconsistent_order() {
1272 let mut cr_state = CRState::new();
1273 cr_state.identity_challenge_response = Some(IdentityCRChallenge {
1274 update_s_key: None,
1275 update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()),
1276 identity_nonce: Some(Nonce::new()),
1277 identity_challenge_signature: Some(String::from("some challenge signature string")),
1278 identity_response_signature: Some(String::from("some response signature string")),
1279 });
1280 let result = cr_state.check_cr_status();
1281 assert_eq!(result.unwrap(), CurrentCRState::NotStarted);
1282 }
1283}