trustchain_http/
attestation_utils.rs

1use std::{
2    collections::HashMap,
3    fmt::Display,
4    fs::{self, File},
5    io::{BufWriter, Write},
6    path::{Path, PathBuf},
7};
8
9// use axum::response::Response;
10use 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    /// Serde JSON error.
26    #[error("Wrapped serialization error: {0}")]
27    Serde(serde_json::Error),
28    /// Wrapped jose error.
29    #[error("Wrapped jose error: {0}")]
30    Jose(JoseError),
31    /// Missing JWK from verification method.
32    #[error("Missing JWK from verification method of a DID document.")]
33    MissingJWK,
34    /// Key not found in hashmap.
35    #[error("Key id not found.")]
36    KeyNotFound,
37    /// Claim not found in JWTPayload.
38    #[error("Claim not found in JWTPayload.")]
39    ClaimNotFound,
40    /// Claim cannot be constructed
41    #[error("Claim cannot be constructed from: {0}")]
42    ClaimCannotBeConstructed(String),
43    /// Nonce type invalid.
44    #[error("Invalid nonce type.")]
45    InvalidNonceType,
46    /// Failed to open file.
47    #[error("Failed to open file.")]
48    FailedToOpen,
49    /// Failed to serialize to file.
50    #[error("Failed to serialize to file.")]
51    FailedToSerialize,
52    /// Failed to set permissions on file.
53    #[error("Failed to set permissions on file.")]
54    FailedToSetPermissions,
55    /// Failed deserialize from file.
56    #[error("Failed to deserialize.")]
57    FailedToDeserialize,
58    /// Value is not a string.
59    #[error("Value is not a string: {0}")]
60    FailedToConvertToStr(Value),
61    /// Failed deserialize from file.
62    #[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    /// Failed to check CR status.
67    #[error("Failed to determine CR status.")]
68    FailedStatusCheck,
69    /// Path for CR does not exist.
70    #[error("Path does not exist. No challenge-response record for this temporary key id.")]
71    CRPathNotFound,
72    /// Failed to generate key.
73    #[error("Failed to generate key.")]
74    FailedToGenerateKey,
75    /// Reqwest error.
76    #[error("Network request failed.")]
77    Reqwest(reqwest::Error),
78    /// Invalid service endpoint.
79    #[error("Invalid service endpoint.")]
80    InvalidServiceEndpoint,
81    /// CR initiation failed
82    #[error("Failed to initiate challenge-response.")]
83    FailedToInitiateCR,
84    /// Failed attestation request
85    #[error("Failed attestation request.")]
86    FailedAttestationRequest,
87    /// Field of struct not found
88    #[error("Field not found.")]
89    FieldNotFound,
90    /// Field to respond
91    #[error("Response to challenge failed.")]
92    FailedToRespond(reqwest::Response),
93    /// Failed to verify nonce
94    #[error("Failed to verify nonce.")]
95    FailedToVerifyNonce,
96    /// Wrapped IO error
97    #[error("IO error: {0}")]
98    IOError(std::io::Error),
99    /// Wrapped KeyManager error
100    #[error("KeyManager error: {0}")]
101    KeyManagerError(#[from] KeyManagerError),
102    /// Wrapped Attestor error
103    #[error("Attestor error: {0}")]
104    AttestorError(#[from] AttestorError),
105    /// Wrapped SSI JWK error
106    #[error("SSI JWK error: {0}")]
107    SSIJwkError(#[from] ssi::jwk::Error),
108    /// Response from a `CustomResponse` must contain data
109    #[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)]
120/// Type for implementing custom response returned by the server. Provides a message and optional data field.
121pub struct CustomResponse {
122    pub message: String,
123    pub data: Option<String>,
124}
125
126#[derive(Debug, PartialEq)]
127/// Enumerates the possible states of the challenge-response process.
128pub enum CurrentCRState {
129    NotStarted,
130    IdentityCRInitiated,
131    IdentityChallengeComplete,
132    IdentityResponseComplete,
133    ContentCRInitiated,
134    ContentChallengeComplete,
135    ContentResponseComplete,
136}
137
138// TODO: Impose additional constraints on the nonce type.
139#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
140/// Nonce type for challenge-response.
141pub 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
194/// Interface for serializing and deserializing each field of structs to/from files.
195pub trait ElementwiseSerializeDeserialize
196where
197    Self: Serialize,
198{
199    /// Serialize each field of the struct to a file.
200    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    /// Deserializes each field of the struct from a file.
214    fn elementwise_deserialize(self, path: &PathBuf) -> Result<Option<Self>, TrustchainCRError>
215    where
216        Self: Sized;
217    /// Save data to file. If file already exists, do nothing.
218    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        // Open the new file if it doesn't exist yet
225        let new_file = OpenOptions::new()
226            .create(true)
227            .append(false)
228            .truncate(false)
229            .write(true)
230            .open(path);
231
232        // Write key to file
233        match new_file {
234            Ok(file) => {
235                let mut writer = BufWriter::new(file);
236                match writer.write_all(data.as_bytes()) {
237                    Ok(_) => {
238                        // Set file permissions to read-only (user, group, and others)
239                        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)]
258/// Type for storing details of the requester.
259pub 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)]
266/// Type for storing initiation details of the attestation request.
267pub 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    /// Returns true if all fields required for the initiation have a non-null value.
288    /// Note: temp_s_key is optional since only requester has it.
289    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    /// Deserialize each field of the struct from a file. Fields are optional. If no files are found, return None.
307    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        // TODO: refactor with e.g. std::fs::read_to_string
313        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        // TODO: complete refactor
322        // if !Path::new(&temp_p_key_path).exists() {
323        //     self.temp_p_key = None;
324        // }
325        // let deserialized = serde_json::from_str(
326        //     &fs::read_to_string(&temp_p_key_path)
327        //         .map_err(|_| TrustchainCRError::FailedToDeserialize)?,
328        // )
329        // .map_err(|_| TrustchainCRError::FailedToDeserialize)?;
330        // self.temp_p_key = Some(deserialized);
331
332        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)]
365/// Type for storing details of part one (identity challenge) of the challenge-response process.
366pub struct IdentityCRChallenge {
367    pub update_p_key: Option<Jwk>,
368    pub update_s_key: Option<Jwk>,
369    pub identity_nonce: Option<Nonce>, // make own Nonce type
370    /// Encrypted identity challenge, signed by the attestor.
371    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    /// Returns true if all fields required for the challenge have a non-null value.
392    /// Note: update_s_key is optional since only attestor has it.
393    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    /// Returns true if challenge-response is complete.
399    fn is_complete(&self) -> bool {
400        self.challenge_complete() && self.identity_response_signature.is_some()
401    }
402}
403
404impl ElementwiseSerializeDeserialize for IdentityCRChallenge {
405    /// Deserialize each field of the struct from a file. Fields are optional. If no files are found, return None.
406    fn elementwise_deserialize(
407        mut self,
408        path: &PathBuf,
409    ) -> Result<Option<IdentityCRChallenge>, TrustchainCRError> {
410        // update public key
411        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        // update secret key
421        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        // identity nonce
431        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        // identity challenge signature
441        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        // identity response signature
451        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                    // Unwrap: not None since error would have propagated above if None
524                    value.claim("update_p_key").unwrap().clone(),
525                ))?,
526        )?);
527        challenge.identity_nonce = Some(Nonce::from(
528            // TODO: refactor into function for a given payload and claim field,
529            // returns a Result<String>
530            value
531                .claim("identity_nonce")
532                .ok_or(TrustchainCRError::ClaimNotFound)?
533                .as_str()
534                .ok_or(TrustchainCRError::FailedToConvertToStr(
535                    // Unwrap: not None since error would have propagated above if None
536                    value.claim("identity_nonce").unwrap().clone(),
537                ))?
538                .to_string(),
539        ));
540        Ok(challenge)
541    }
542}
543
544#[derive(Debug, Serialize, Deserialize, Clone, IsEmpty)]
545/// Type for storing initiation details of part two (content challenge) of the challenge-response process.
546pub 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    /// Deserialize each field of the struct from a file. Fields are optional. If no files are found, return None.
570    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)]
593/// Type for storing details of part two (content challenge) of the challenge-response process.
594pub 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    /// Returns true if all fields required for the challenge have a non-null value.
615    fn challenge_complete(&self) -> bool {
616        self.content_nonce.is_some() && self.content_challenge_signature.is_some()
617    }
618    /// Returns true if all fields required for the challenge-response have a non-null value.
619    fn is_complete(&self) -> bool {
620        self.challenge_complete() && self.content_response_signature.is_some()
621    }
622}
623
624impl ElementwiseSerializeDeserialize for ContentCRChallenge {
625    /// Deserialize each field of the struct from a file. Fields are optional. If no files are found, return None.
626    fn elementwise_deserialize(
627        mut self,
628        path: &PathBuf,
629    ) -> Result<Option<ContentCRChallenge>, TrustchainCRError> {
630        // content nonce(s)
631        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        // content challenge signature
642        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        // content response signature
652        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)]
675/// Type for representing the state of the challenge-response process. Holds information about both
676/// identity (part one) and content challenge-response (part two) and their respective initiation.
677pub 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    /// Returns true if all fields are complete.
700    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    /// Determines current status of the challenge response process and accordingly prints messages to the console.
715    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(&current_state));
721            return Ok(current_state);
722        }
723
724        // CR complete
725        if self.is_complete() {
726            current_state = CurrentCRState::ContentResponseComplete;
727            println!("{}", get_status_message(&current_state));
728            return Ok(current_state);
729        }
730
731        // Identity CR initation
732        if self.identity_cr_initiation.is_none()
733            // Unwrap: first condition ensures is not None
734            || !self.identity_cr_initiation.as_ref().unwrap().is_complete()
735        {
736            println!("{}", get_status_message(&current_state));
737            return Ok(current_state);
738        }
739        current_state = CurrentCRState::IdentityCRInitiated;
740        println!("{}", get_status_message(&current_state));
741
742        // Identity challenge
743        if self.identity_challenge_response.is_none()
744            // Unwrap: first condition ensures is not None
745            || !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(&current_state));
755
756        // Identity response
757        if self
758            .identity_challenge_response
759            .is_none()
760            // Unwrap: first condition ensures is not None
761            || !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        // Content CR initation
772        if self.content_cr_initiation.is_none()
773        // Unwrap: first condition ensures is not None
774            || !self.content_cr_initiation.as_ref().unwrap().is_complete()
775        {
776            return Ok(current_state);
777        }
778        current_state = CurrentCRState::ContentCRInitiated;
779
780        // Content challenge
781        if self.content_challenge_response.is_none()
782            // Unwrap: first condition ensures is not None
783            || !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        // Content response
794        if self.content_challenge_response.is_none()
795            // Unwrap: first condition ensures is not None
796            || !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    /// Serialize each field of the struct to a file. Fields with null values are ignored.
811    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    /// Deserialize each field of the struct from a file. All fields are optional.
827    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
841/// Returns message that corresponds to the current state of the challenge-response process.
842fn 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
868/// Returns endpoint that contains the given fragment from the given list of service endpoints.
869/// Throws error if no or more than one matching endpoint is found.
870pub 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
892/// Returns unique path name for a specific attestation request derived from public key for the interaction.
893pub fn attestation_request_path(key: &JWK, prefix: &str) -> Result<PathBuf, TrustchainCRError> {
894    // Root path in TRUSTCHAIN_DATA
895    let path = attestation_request_basepath(prefix)?;
896    let key_id = key.thumbprint()?; // Use hash of temp_pub_key
897    Ok(path.join(key_id))
898}
899
900/// Returns the root path for storing attestation requests.
901pub fn attestation_request_basepath(prefix: &str) -> Result<PathBuf, TrustchainCRError> {
902    // Root path in TRUSTCHAIN_DATA
903    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        // ==========| Identity CR | ==============
922        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        // identity challenge
933        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        // ==========| Content CR | ==============
942        let content_initiation = ContentCRInitiation {
943            // temp_p_key: Some(temp_s_key.to_public_key().unwrap()),
944            requester_did: Some("did:example:123456789abcdefghi".to_string()),
945        };
946        // get signing keys for DE from did document
947        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        // generate map with unencrypted nonces so UE can store them for later verification
951        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        // ==========| CR state | ==============
969        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        // write to file
976        let path = tempdir().unwrap().into_path();
977        let result = cr_state.elementwise_serialize(&path);
978        assert!(result.is_ok());
979
980        // try to write to file again
981        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        // Test case 1: None of the json files exist
991        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        // Test case 2: Only one json file exists and can be deserialized
997        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        // Test case 3: Both json files exist and can be deserialized
1011        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        // Test case 4: Both json files exist but one is invalid json and cannot be
1026        // deserialized
1027        let cr_initiation = IdentityCRInitiation::new();
1028        // override temp key with invalid key
1029        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        // Test case 1: None of the json files exist
1041        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        // Test case 2: Only one json file exists and can be deserialized
1047        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        // Test case 3: One file exists but cannot be deserialized
1061        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        // Test case 1: None of the json files exist
1076        let result = content_challenge.elementwise_deserialize(&temp_path);
1077        assert!(result.is_ok());
1078        assert!(result.unwrap().is_none());
1079
1080        // Test case 2: Only one json file exists and can be deserialized
1081        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        // Test case 3: One file exists but cannot be deserialized
1095        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        // Test case 1: some files exist and can be deserialised
1108        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            // temp_p_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()),
1128            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        // Test case 2: one file cannot be deserialized
1145        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        // Test case: multiple endpoints found should throw error
1181        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        // Test case 1: CR State is empty
1207        let result = cr_state.check_cr_status().unwrap();
1208        assert_eq!(result, CurrentCRState::NotStarted);
1209
1210        // Test case 2: some, but not all, initation information exists
1211        cr_state.identity_cr_initiation = Some(IdentityCRInitiation {
1212            // Same key used here for testing purposes
1213            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        // Test case 3: identity initiation completed, identity challenge presented
1221        cr_state.identity_cr_initiation = Some(IdentityCRInitiation {
1222            // Same key used here for testing purposes
1223            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        // Test case 4: Identity challenge response complete, content challenge initiated
1241        cr_state.identity_challenge_response = Some(IdentityCRChallenge {
1242            // Same key used here for testing purposes
1243            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        // Test case 5: Content challenge-response complete
1258        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}