use std::{collections::HashMap, path::PathBuf};
use josekit::{jwk::Jwk, jwt::JwtPayload};
use serde_json::Value;
use ssi::did::Service;
use trustchain_core::utils::generate_key;
use trustchain_ion::attestor::IONAttestor;
use crate::{
attestation_encryption_utils::{
josekit_to_ssi_jwk, ssi_to_josekit_jwk, DecryptVerify, Entity, SignEncrypt,
},
attestation_utils::{
attestation_request_path, matching_endpoint, ContentCRChallenge, ContentCRInitiation,
ElementwiseSerializeDeserialize, IdentityCRChallenge, IdentityCRInitiation,
RequesterDetails,
},
attestation_utils::{CustomResponse, Nonce, TrustchainCRError},
ATTESTATION_FRAGMENT,
};
pub async fn initiate_identity_challenge(
org_name: &str,
op_name: &str,
services: &[Service],
) -> Result<(IdentityCRInitiation, PathBuf), TrustchainCRError> {
let temp_s_key_ssi = generate_key();
let temp_p_key_ssi = temp_s_key_ssi.to_public();
let temp_s_key =
ssi_to_josekit_jwk(&temp_s_key_ssi).map_err(|_| TrustchainCRError::FailedToGenerateKey)?;
let temp_p_key =
ssi_to_josekit_jwk(&temp_p_key_ssi).map_err(|_| TrustchainCRError::FailedToGenerateKey)?;
let requester = RequesterDetails {
requester_org: org_name.to_owned(),
operator_name: op_name.to_owned(),
};
let mut identity_cr_initiation = IdentityCRInitiation {
temp_s_key: None,
temp_p_key: Some(temp_p_key.clone()),
requester_details: Some(requester.clone()),
};
let url_path = "/did/attestor/identity/initiate";
let endpoint = matching_endpoint(services, ATTESTATION_FRAGMENT)?;
let uri = format!("{}{}", endpoint, url_path);
let client = reqwest::Client::new();
let result = client
.post(uri)
.json(&identity_cr_initiation)
.send()
.await
.map_err(TrustchainCRError::Reqwest)?;
if result.status() != 200 {
return Err(TrustchainCRError::FailedToInitiateCR);
}
let path = attestation_request_path(&temp_s_key_ssi.to_public(), "requester")?;
std::fs::create_dir_all(&path).map_err(|_| TrustchainCRError::FailedAttestationRequest)?;
identity_cr_initiation.temp_s_key = Some(temp_s_key);
Ok((identity_cr_initiation, path))
}
pub async fn identity_response(
path: &PathBuf,
services: &[Service],
attestor_p_key: &Jwk,
) -> Result<IdentityCRChallenge, TrustchainCRError> {
let mut identity_challenge = IdentityCRChallenge::new()
.elementwise_deserialize(path)?
.ok_or(TrustchainCRError::FailedToDeserialize)?;
let identity_initiation = IdentityCRInitiation::new()
.elementwise_deserialize(path)?
.ok_or(TrustchainCRError::FailedToDeserialize)?;
let temp_s_key = identity_initiation.temp_s_key()?;
let temp_s_key_ssi = josekit_to_ssi_jwk(temp_s_key)?;
let requester = Entity {};
let decrypted_verified_payload = requester.decrypt_and_verify(
identity_challenge
.identity_challenge_signature
.clone()
.ok_or(TrustchainCRError::FieldNotFound)?,
temp_s_key,
attestor_p_key,
)?;
let signed_encrypted_response = requester.sign_and_encrypt_claim(
&decrypted_verified_payload,
temp_s_key,
attestor_p_key,
)?;
let key_id = temp_s_key_ssi.to_public().thumbprint()?;
let endpoint = matching_endpoint(services, ATTESTATION_FRAGMENT)?;
let url_path = "/did/attestor/identity/respond";
let uri = format!("{}{}/{}", endpoint, url_path, key_id);
let client = reqwest::Client::new();
let result = client
.post(uri)
.json(&signed_encrypted_response)
.send()
.await
.map_err(TrustchainCRError::Reqwest)?;
if result.status() != 200 {
return Err(TrustchainCRError::FailedToRespond(result));
}
let nonce_str = decrypted_verified_payload
.claim("identity_nonce")
.ok_or(TrustchainCRError::ClaimNotFound)?
.as_str()
.ok_or(TrustchainCRError::FailedToConvertToStr(
decrypted_verified_payload
.claim("identity_nonce")
.unwrap()
.clone(),
))?;
let nonce = Nonce::from(String::from(nonce_str));
identity_challenge.update_p_key = Some(attestor_p_key.clone());
identity_challenge.identity_nonce = Some(nonce);
identity_challenge.identity_response_signature = Some(signed_encrypted_response);
Ok(identity_challenge)
}
pub async fn initiate_content_challenge(
path: &PathBuf,
ddid: &str,
services: &[Service],
attestor_p_key: &Jwk,
) -> Result<(ContentCRInitiation, ContentCRChallenge), TrustchainCRError> {
let identity_cr_initiation = IdentityCRInitiation::new()
.elementwise_deserialize(path)?
.ok_or(TrustchainCRError::FailedToDeserialize)?;
let temp_s_key_ssi = josekit_to_ssi_jwk(&identity_cr_initiation.temp_s_key().cloned()?)?;
let key_id = temp_s_key_ssi.to_public().thumbprint()?;
let content_cr_initiation = ContentCRInitiation {
requester_did: Some(ddid.to_owned()),
};
let endpoint = matching_endpoint(services, ATTESTATION_FRAGMENT)?;
let url_path = "/did/attestor/content/initiate";
let uri = format!("{}{}/{}", endpoint, url_path, key_id);
let client = reqwest::Client::new();
let result = client
.post(uri)
.json(&ddid)
.send()
.await
.map_err(TrustchainCRError::Reqwest)?;
if result.status() != 200 {
println!("Status code: {}", result.status());
return Err(TrustchainCRError::FailedToRespond(result));
}
let response_body: CustomResponse = result.json().await.map_err(TrustchainCRError::Reqwest)?;
let signed_encrypted_challenge = response_body
.data
.ok_or(TrustchainCRError::ResponseMustContainData)?;
let (nonces, response) = content_response(
path,
&signed_encrypted_challenge.to_string(),
services,
attestor_p_key.clone(),
ddid,
)
.await?;
let content_challenge = ContentCRChallenge {
content_nonce: Some(nonces),
content_challenge_signature: Some(signed_encrypted_challenge.to_string()),
content_response_signature: Some(response),
};
Ok((content_cr_initiation, content_challenge))
}
pub async fn content_response(
path: &PathBuf,
challenge: &str,
services: &[Service],
attestor_p_key: Jwk,
ddid: &str,
) -> Result<(HashMap<String, Nonce>, String), TrustchainCRError> {
let identity_initiation = IdentityCRInitiation::new()
.elementwise_deserialize(path)?
.ok_or(TrustchainCRError::FailedToDeserialize)?;
let temp_s_key = identity_initiation.temp_s_key()?;
let temp_s_key_ssi = josekit_to_ssi_jwk(temp_s_key)?;
let key_id = temp_s_key_ssi.to_public().thumbprint()?;
let endpoint = matching_endpoint(services, ATTESTATION_FRAGMENT)?;
let url_path = "/did/attestor/content/respond";
let uri = format!("{}{}/{}", endpoint, url_path, key_id);
let requester = Entity {};
let decrypted_verified_payload =
requester.decrypt_and_verify(challenge.to_owned(), temp_s_key, &attestor_p_key)?;
let challenges_map: HashMap<String, String> = serde_json::from_value(
decrypted_verified_payload
.claim("challenges")
.ok_or(TrustchainCRError::ClaimNotFound)?
.clone(),
)?;
let ion_attestor = IONAttestor::new(ddid);
let signing_keys = ion_attestor.signing_keys()?;
let mut signing_keys_map: HashMap<String, Jwk> = HashMap::new();
for key in signing_keys {
let key_id = key.thumbprint()?;
let jwk = ssi_to_josekit_jwk(&key)?;
signing_keys_map.insert(key_id, jwk);
}
let mut decrypted_nonces: HashMap<String, Nonce> = HashMap::new();
for (key_id, nonce) in challenges_map.iter() {
let payload = requester.decrypt(
&Value::from(nonce.clone()),
signing_keys_map
.get(key_id)
.ok_or(TrustchainCRError::KeyNotFound)?,
)?;
decrypted_nonces.insert(
String::from(key_id),
Nonce::from(
payload
.claim("nonce")
.ok_or(TrustchainCRError::ClaimNotFound)?
.as_str()
.ok_or(TrustchainCRError::FailedToConvertToStr(
payload.claim("nonce").unwrap().clone(),
))?
.to_string(),
),
);
}
let value: serde_json::Value = serde_json::to_value(&decrypted_nonces)?;
let mut payload = JwtPayload::new();
payload.set_claim("nonces", Some(value))?;
let signed_encrypted_response =
requester.sign_and_encrypt_claim(&payload, temp_s_key, &attestor_p_key)?;
let client = reqwest::Client::new();
let result = client
.post(uri)
.json(&signed_encrypted_response)
.send()
.await
.map_err(TrustchainCRError::Reqwest)?;
if result.status() != 200 {
println!("Status code: {}", result.status());
return Err(TrustchainCRError::FailedToRespond(result));
}
Ok((decrypted_nonces, signed_encrypted_response))
}