use std::{
collections::HashMap,
fmt::Display,
fs::{self, File},
io::{BufWriter, Write},
path::{Path, PathBuf},
};
use is_empty::IsEmpty;
use josekit::JoseError;
use josekit::{jwk::Jwk, jwt::JwtPayload};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use serde::{Deserialize, Serialize};
use serde_json::{to_string_pretty as to_json, Value};
use serde_with::skip_serializing_none;
use ssi::{did::Service, jwk::JWK};
use ssi::{did::ServiceEndpoint, one_or_many::OneOrMany};
use std::fs::OpenOptions;
use thiserror::Error;
use trustchain_core::{attestor::AttestorError, key_manager::KeyManagerError, TRUSTCHAIN_DATA};
#[derive(Error, Debug)]
pub enum TrustchainCRError {
#[error("Wrapped serialization error: {0}")]
Serde(serde_json::Error),
#[error("Wrapped jose error: {0}")]
Jose(JoseError),
#[error("Missing JWK from verification method of a DID document.")]
MissingJWK,
#[error("Key id not found.")]
KeyNotFound,
#[error("Claim not found in JWTPayload.")]
ClaimNotFound,
#[error("Claim cannot be constructed from: {0}")]
ClaimCannotBeConstructed(String),
#[error("Invalid nonce type.")]
InvalidNonceType,
#[error("Failed to open file.")]
FailedToOpen,
#[error("Failed to serialize to file.")]
FailedToSerialize,
#[error("Failed to set permissions on file.")]
FailedToSetPermissions,
#[error("Failed to deserialize.")]
FailedToDeserialize,
#[error("Value is not a string: {0}")]
FailedToConvertToStr(Value),
#[error("Failed to deserialize with error: {0}.")]
FailedToDeserializeWithError(serde_json::Error),
#[error("Wrapped SSI JWK error: {0}.")]
WrappedSSIJWKError(ssi::jwk::Error),
#[error("Failed to determine CR status.")]
FailedStatusCheck,
#[error("Path does not exist. No challenge-response record for this temporary key id.")]
CRPathNotFound,
#[error("Failed to generate key.")]
FailedToGenerateKey,
#[error("Network request failed.")]
Reqwest(reqwest::Error),
#[error("Invalid service endpoint.")]
InvalidServiceEndpoint,
#[error("Failed to initiate challenge-response.")]
FailedToInitiateCR,
#[error("Failed attestation request.")]
FailedAttestationRequest,
#[error("Field not found.")]
FieldNotFound,
#[error("Response to challenge failed.")]
FailedToRespond(reqwest::Response),
#[error("Failed to verify nonce.")]
FailedToVerifyNonce,
#[error("IO error: {0}")]
IOError(std::io::Error),
#[error("KeyManager error: {0}")]
KeyManagerError(#[from] KeyManagerError),
#[error("Attestor error: {0}")]
AttestorError(#[from] AttestorError),
#[error("SSI JWK error: {0}")]
SSIJwkError(#[from] ssi::jwk::Error),
#[error("Must contain data but custom response contained no data")]
ResponseMustContainData,
}
impl From<JoseError> for TrustchainCRError {
fn from(err: JoseError) -> Self {
Self::Jose(err)
}
}
#[derive(Serialize, Deserialize)]
pub struct CustomResponse {
pub message: String,
pub data: Option<String>,
}
#[derive(Debug, PartialEq)]
pub enum CurrentCRState {
NotStarted,
IdentityCRInitiated,
IdentityChallengeComplete,
IdentityResponseComplete,
ContentCRInitiated,
ContentChallengeComplete,
ContentResponseComplete,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Nonce(String);
impl Default for Nonce {
fn default() -> Self {
Self::new()
}
}
impl Nonce {
pub fn new() -> Self {
Self(
thread_rng()
.sample_iter(&Alphanumeric)
.take(32)
.map(char::from)
.collect(),
)
}
}
impl AsRef<str> for Nonce {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Display for Nonce {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for Nonce {
fn from(s: String) -> Self {
Self(s)
}
}
impl TryFrom<&Nonce> for JwtPayload {
type Error = TrustchainCRError;
fn try_from(value: &Nonce) -> Result<Self, Self::Error> {
let mut payload = JwtPayload::new();
payload.set_claim("nonce", Some(Value::from(value.to_string())))?;
Ok(payload)
}
}
impl From<serde_json::Error> for TrustchainCRError {
fn from(value: serde_json::Error) -> Self {
TrustchainCRError::FailedToDeserializeWithError(value)
}
}
pub trait ElementwiseSerializeDeserialize
where
Self: Serialize,
{
fn elementwise_serialize(&self, path: &PathBuf) -> Result<(), TrustchainCRError> {
let serialized = serde_json::to_value(self)?;
if let Value::Object(fields) = serialized {
for (field_name, field_value) in fields {
if !field_value.is_null() {
let json_filename = format!("{}.json", field_name);
let file_path = path.join(json_filename);
self.save_to_file(&file_path, &to_json(&field_value)?)?;
}
}
}
Ok(())
}
fn elementwise_deserialize(self, path: &PathBuf) -> Result<Option<Self>, TrustchainCRError>
where
Self: Sized;
fn save_to_file(&self, path: &PathBuf, data: &str) -> Result<(), TrustchainCRError> {
if path.exists() {
println!("File already exists: {:?}", path);
return Ok(());
}
let new_file = OpenOptions::new()
.create(true)
.append(false)
.truncate(false)
.write(true)
.open(path);
match new_file {
Ok(file) => {
let mut writer = BufWriter::new(file);
match writer.write_all(data.as_bytes()) {
Ok(_) => {
let mut permissions = fs::metadata(path)
.map_err(|_| TrustchainCRError::FailedToSetPermissions)?
.permissions();
permissions.set_readonly(true);
fs::set_permissions(path, permissions)
.map_err(|_| TrustchainCRError::FailedToSetPermissions)?;
Ok(())
}
Err(_) => Err(TrustchainCRError::FailedToSerialize),
}
}
Err(_) => Err(TrustchainCRError::FailedToSerialize),
}
}
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RequesterDetails {
pub requester_org: String,
pub operator_name: String,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, IsEmpty, Clone)]
pub struct IdentityCRInitiation {
pub temp_p_key: Option<Jwk>,
pub temp_s_key: Option<Jwk>,
pub requester_details: Option<RequesterDetails>,
}
impl Default for IdentityCRInitiation {
fn default() -> Self {
Self::new()
}
}
impl IdentityCRInitiation {
pub fn new() -> Self {
Self {
temp_p_key: None,
temp_s_key: None,
requester_details: None,
}
}
pub fn is_complete(&self) -> bool {
self.temp_p_key.is_some() && self.requester_details.is_some()
}
pub fn temp_p_key(&self) -> Result<&Jwk, TrustchainCRError> {
self.temp_p_key
.as_ref()
.ok_or(TrustchainCRError::KeyNotFound)
}
pub fn temp_s_key(&self) -> Result<&Jwk, TrustchainCRError> {
self.temp_s_key
.as_ref()
.ok_or(TrustchainCRError::KeyNotFound)
}
}
impl ElementwiseSerializeDeserialize for IdentityCRInitiation {
fn elementwise_deserialize(
mut self,
path: &PathBuf,
) -> Result<Option<IdentityCRInitiation>, TrustchainCRError> {
let temp_p_key_path = path.join("temp_p_key.json");
self.temp_p_key = match File::open(temp_p_key_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
let temp_s_key_path = path.join("temp_s_key.json");
self.temp_s_key = match File::open(temp_s_key_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
let requester_details_path = path.join("requester_details.json");
self.requester_details = match File::open(requester_details_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
if self.temp_p_key.is_none()
&& self.temp_s_key.is_none()
&& self.requester_details.is_none()
{
return Ok(None);
}
Ok(Some(self))
}
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, IsEmpty)]
pub struct IdentityCRChallenge {
pub update_p_key: Option<Jwk>,
pub update_s_key: Option<Jwk>,
pub identity_nonce: Option<Nonce>, pub identity_challenge_signature: Option<String>,
pub identity_response_signature: Option<String>,
}
impl Default for IdentityCRChallenge {
fn default() -> Self {
Self::new()
}
}
impl IdentityCRChallenge {
pub fn new() -> Self {
Self {
update_p_key: None,
update_s_key: None,
identity_nonce: None,
identity_challenge_signature: None,
identity_response_signature: None,
}
}
fn challenge_complete(&self) -> bool {
self.update_p_key.is_some()
&& self.identity_nonce.is_some()
&& self.identity_challenge_signature.is_some()
}
fn is_complete(&self) -> bool {
self.challenge_complete() && self.identity_response_signature.is_some()
}
}
impl ElementwiseSerializeDeserialize for IdentityCRChallenge {
fn elementwise_deserialize(
mut self,
path: &PathBuf,
) -> Result<Option<IdentityCRChallenge>, TrustchainCRError> {
let full_path = path.join("update_p_key.json");
self.update_p_key = match File::open(full_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
let mut full_path = path.join("update_s_key.json");
self.update_s_key = match File::open(&full_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
full_path = path.join("identity_nonce.json");
self.identity_nonce = match File::open(&full_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
full_path = path.join("identity_challenge_signature.json");
self.identity_challenge_signature = match File::open(&full_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
full_path = path.join("identity_response_signature.json");
self.identity_response_signature = match File::open(&full_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
if self.update_p_key.is_none()
&& self.identity_nonce.is_none()
&& self.identity_challenge_signature.is_none()
&& self.identity_response_signature.is_none()
{
return Ok(None);
}
Ok(Some(self))
}
}
impl TryFrom<&IdentityCRChallenge> for JwtPayload {
type Error = TrustchainCRError;
fn try_from(value: &IdentityCRChallenge) -> Result<Self, Self::Error> {
let mut payload = JwtPayload::new();
payload.set_claim(
"identity_nonce",
Some(Value::from(
value
.identity_nonce
.as_ref()
.ok_or(TrustchainCRError::ClaimCannotBeConstructed(
"`identity_nonce` field in `IdentityCRChallenge` is missing (`None`)"
.to_string(),
))?
.to_string(),
)),
)?;
payload.set_claim(
"update_p_key",
Some(Value::from(
value
.update_p_key
.as_ref()
.ok_or(TrustchainCRError::ClaimCannotBeConstructed(
"`update_p_key` field in `IdentityCRChallenge` is missing (`None`)"
.to_string(),
))?
.to_string(),
)),
)?;
Ok(payload)
}
}
impl TryFrom<&JwtPayload> for IdentityCRChallenge {
type Error = TrustchainCRError;
fn try_from(value: &JwtPayload) -> Result<Self, Self::Error> {
let mut challenge = IdentityCRChallenge {
update_p_key: None,
update_s_key: None,
identity_nonce: None,
identity_challenge_signature: None,
identity_response_signature: None,
};
challenge.update_p_key = Some(serde_json::from_str(
value
.claim("update_p_key")
.ok_or(TrustchainCRError::ClaimNotFound)?
.as_str()
.ok_or(TrustchainCRError::FailedToConvertToStr(
value.claim("update_p_key").unwrap().clone(),
))?,
)?);
challenge.identity_nonce = Some(Nonce::from(
value
.claim("identity_nonce")
.ok_or(TrustchainCRError::ClaimNotFound)?
.as_str()
.ok_or(TrustchainCRError::FailedToConvertToStr(
value.claim("identity_nonce").unwrap().clone(),
))?
.to_string(),
));
Ok(challenge)
}
}
#[derive(Debug, Serialize, Deserialize, Clone, IsEmpty)]
pub struct ContentCRInitiation {
pub requester_did: Option<String>,
}
impl Default for ContentCRInitiation {
fn default() -> Self {
Self::new()
}
}
impl ContentCRInitiation {
pub fn new() -> Self {
Self {
requester_did: None,
}
}
fn is_complete(&self) -> bool {
self.requester_did.is_some()
}
}
impl ElementwiseSerializeDeserialize for ContentCRInitiation {
fn elementwise_deserialize(
mut self,
path: &PathBuf,
) -> Result<Option<ContentCRInitiation>, TrustchainCRError> {
let requester_details_path = path.join("requester_did.json");
self.requester_did = match File::open(requester_details_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
if self.requester_did.is_none() {
return Ok(None);
}
Ok(Some(self))
}
}
#[derive(Debug, Serialize, Deserialize, IsEmpty)]
pub struct ContentCRChallenge {
pub content_nonce: Option<HashMap<String, Nonce>>,
pub content_challenge_signature: Option<String>,
pub content_response_signature: Option<String>,
}
impl Default for ContentCRChallenge {
fn default() -> Self {
Self::new()
}
}
impl ContentCRChallenge {
pub fn new() -> Self {
Self {
content_nonce: None,
content_challenge_signature: None,
content_response_signature: None,
}
}
fn challenge_complete(&self) -> bool {
self.content_nonce.is_some() && self.content_challenge_signature.is_some()
}
fn is_complete(&self) -> bool {
self.challenge_complete() && self.content_response_signature.is_some()
}
}
impl ElementwiseSerializeDeserialize for ContentCRChallenge {
fn elementwise_deserialize(
mut self,
path: &PathBuf,
) -> Result<Option<ContentCRChallenge>, TrustchainCRError> {
let mut full_path = path.join("content_nonce.json");
self.content_nonce = match File::open(&full_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
full_path = path.join("content_challenge_signature.json");
self.content_challenge_signature = match File::open(&full_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
full_path = path.join("content_response_signature.json");
self.content_response_signature = match File::open(&full_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
let deserialized = serde_json::from_reader(reader)?;
Some(deserialized)
}
Err(_) => None,
};
if self.content_nonce.is_none()
&& self.content_challenge_signature.is_none()
&& self.content_response_signature.is_none()
{
return Ok(None);
}
Ok(Some(self))
}
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, IsEmpty)]
pub struct CRState {
pub identity_cr_initiation: Option<IdentityCRInitiation>,
pub identity_challenge_response: Option<IdentityCRChallenge>,
pub content_cr_initiation: Option<ContentCRInitiation>,
pub content_challenge_response: Option<ContentCRChallenge>,
}
impl Default for CRState {
fn default() -> Self {
Self::new()
}
}
impl CRState {
pub fn new() -> Self {
Self {
identity_cr_initiation: None,
identity_challenge_response: None,
content_cr_initiation: None,
content_challenge_response: None,
}
}
pub fn is_complete(&self) -> bool {
if let (Some(ici), Some(icr), Some(cci), Some(ccr)) = (
self.identity_cr_initiation.as_ref(),
self.identity_challenge_response.as_ref(),
self.content_cr_initiation.as_ref(),
self.content_challenge_response.as_ref(),
) {
return ici.is_complete()
&& icr.is_complete()
&& cci.is_complete()
&& ccr.is_complete();
}
false
}
pub fn check_cr_status(&self) -> Result<CurrentCRState, TrustchainCRError> {
println!("Checking current challenge-response status...");
println!(" ");
let mut current_state = CurrentCRState::NotStarted;
if self.is_empty() {
println!("{}", get_status_message(¤t_state));
return Ok(current_state);
}
if self.is_complete() {
current_state = CurrentCRState::ContentResponseComplete;
println!("{}", get_status_message(¤t_state));
return Ok(current_state);
}
if self.identity_cr_initiation.is_none()
|| !self.identity_cr_initiation.as_ref().unwrap().is_complete()
{
println!("{}", get_status_message(¤t_state));
return Ok(current_state);
}
current_state = CurrentCRState::IdentityCRInitiated;
println!("{}", get_status_message(¤t_state));
if self.identity_challenge_response.is_none()
|| !self
.identity_challenge_response
.as_ref()
.unwrap()
.challenge_complete()
{
return Ok(current_state);
}
current_state = CurrentCRState::IdentityChallengeComplete;
println!("{}", get_status_message(¤t_state));
if self
.identity_challenge_response
.is_none()
|| !self
.identity_challenge_response
.as_ref()
.unwrap()
.is_complete()
{
return Ok(current_state);
}
current_state = CurrentCRState::IdentityResponseComplete;
if self.content_cr_initiation.is_none()
|| !self.content_cr_initiation.as_ref().unwrap().is_complete()
{
return Ok(current_state);
}
current_state = CurrentCRState::ContentCRInitiated;
if self.content_challenge_response.is_none()
|| !self
.content_challenge_response
.as_ref()
.unwrap()
.challenge_complete()
{
return Ok(current_state);
}
current_state = CurrentCRState::ContentChallengeComplete;
if self.content_challenge_response.is_none()
|| !self
.content_challenge_response
.as_ref()
.unwrap()
.is_complete()
{
return Ok(current_state);
}
Ok(current_state)
}
}
impl ElementwiseSerializeDeserialize for CRState {
fn elementwise_serialize(&self, path: &PathBuf) -> Result<(), TrustchainCRError> {
if let Some(identity_initiation) = &self.identity_cr_initiation {
identity_initiation.elementwise_serialize(path)?;
}
if let Some(identity_challenge_response) = &self.identity_challenge_response {
identity_challenge_response.elementwise_serialize(path)?;
}
if let Some(content_cr_initiation) = &self.content_cr_initiation {
content_cr_initiation.elementwise_serialize(path)?;
}
if let Some(content_challenge_response) = &self.content_challenge_response {
content_challenge_response.elementwise_serialize(path)?;
}
Ok(())
}
fn elementwise_deserialize(
mut self,
path: &PathBuf,
) -> Result<Option<CRState>, TrustchainCRError> {
self.identity_cr_initiation = IdentityCRInitiation::new().elementwise_deserialize(path)?;
self.identity_challenge_response =
IdentityCRChallenge::new().elementwise_deserialize(path)?;
self.content_cr_initiation = ContentCRInitiation::new().elementwise_deserialize(path)?;
self.content_challenge_response =
ContentCRChallenge::new().elementwise_deserialize(path)?;
Ok(Some(self))
}
}
fn get_status_message(current_state: &CurrentCRState) -> String {
match current_state {
CurrentCRState::NotStarted => {
String::from("No records found for this challenge-response identifier or entity. \nThe challenge-response process has not been initiated yet.")
}
CurrentCRState::IdentityCRInitiated => {
String::from("Identity challenge-response initiated. Await response.")
}
CurrentCRState::IdentityChallengeComplete => {
String::from("Identity challenge has been presented. Await response.")
}
CurrentCRState::IdentityResponseComplete => {
String::from("Identity challenge-response complete.")
}
CurrentCRState::ContentCRInitiated => {
String::from("Content challenge-response initiated. Await response.")
}
CurrentCRState::ContentChallengeComplete => {
String::from("Content challenge has been presented. Await response.")
}
CurrentCRState::ContentResponseComplete => {
String::from("Challenge-response complete.")
}
}
}
pub fn matching_endpoint(
services: &[Service],
fragment: &str,
) -> Result<String, TrustchainCRError> {
let mut endpoints = Vec::new();
for service in services {
if service.id.eq(fragment) {
match &service.service_endpoint {
Some(OneOrMany::One(ServiceEndpoint::URI(uri))) => {
endpoints.push(uri.to_string());
}
_ => return Err(TrustchainCRError::InvalidServiceEndpoint),
}
}
}
if endpoints.len() != 1 {
return Err(TrustchainCRError::InvalidServiceEndpoint);
}
Ok(endpoints[0].clone())
}
pub fn attestation_request_path(key: &JWK, prefix: &str) -> Result<PathBuf, TrustchainCRError> {
let path = attestation_request_basepath(prefix)?;
let key_id = key.thumbprint()?; Ok(path.join(key_id))
}
pub fn attestation_request_basepath(prefix: &str) -> Result<PathBuf, TrustchainCRError> {
let path: String = std::env::var(TRUSTCHAIN_DATA)
.expect("`TRUSTCHAIN_DATA` environment variable must be set.");
Ok(Path::new(path.as_str())
.join(prefix)
.join("attestation_requests"))
}
#[cfg(test)]
mod tests {
use crate::attestation_encryption_utils::extract_key_ids_and_jwk;
use crate::data::{TEST_CANDIDATE_DDID_DOCUMENT, TEST_TEMP_KEY, TEST_UPDATE_KEY};
use ssi::did::Document;
use tempfile::tempdir;
use super::*;
#[test]
fn test_elementwise_serialize() {
let temp_s_key: Jwk = serde_json::from_str(TEST_TEMP_KEY).unwrap();
let initiation = IdentityCRInitiation {
temp_p_key: None,
temp_s_key: Some(temp_s_key.to_public_key().unwrap()),
requester_details: Some(RequesterDetails {
requester_org: String::from("My Org"),
operator_name: String::from("John Doe"),
}),
};
let identity_challenge = IdentityCRChallenge {
update_p_key: serde_json::from_str(TEST_UPDATE_KEY).unwrap(),
update_s_key: None,
identity_nonce: Some(Nonce::new()),
identity_challenge_signature: Some(String::from("some challenge signature string")),
identity_response_signature: Some(String::from("some response signature string")),
};
let content_initiation = ContentCRInitiation {
requester_did: Some("did:example:123456789abcdefghi".to_string()),
};
let doc: Document = serde_json::from_str(TEST_CANDIDATE_DDID_DOCUMENT).unwrap();
let test_keys_map = extract_key_ids_and_jwk(&doc).unwrap();
let nonces: HashMap<String, Nonce> =
test_keys_map
.iter()
.fold(HashMap::new(), |mut acc, (key_id, _)| {
acc.insert(String::from(key_id), Nonce::new());
acc
});
let content_challenge_response = ContentCRChallenge {
content_nonce: Some(nonces),
content_challenge_signature: Some(String::from(
"some content challenge signature string",
)),
content_response_signature: Some(String::from(
"some content response signature string",
)),
};
let cr_state = CRState {
identity_cr_initiation: Some(initiation),
identity_challenge_response: Some(identity_challenge),
content_cr_initiation: Some(content_initiation),
content_challenge_response: Some(content_challenge_response),
};
let path = tempdir().unwrap().into_path();
let result = cr_state.elementwise_serialize(&path);
assert!(result.is_ok());
let result = cr_state.elementwise_serialize(&path);
assert!(result.is_ok());
}
#[test]
fn test_elementwise_deserialize_initiation() {
let cr_initiation = IdentityCRInitiation::new();
let temp_path = tempdir().unwrap().into_path();
let result = cr_initiation.elementwise_deserialize(&temp_path);
assert!(result.is_ok());
let initiation = result.unwrap();
assert!(initiation.is_none());
let cr_initiation = IdentityCRInitiation::new();
let temp_p_key_path = temp_path.join("temp_p_key.json");
let temp_p_key_file = File::create(&temp_p_key_path).unwrap();
let temp_p_key: Jwk = serde_json::from_str(TEST_TEMP_KEY).unwrap();
serde_json::to_writer(temp_p_key_file, &temp_p_key).unwrap();
let result = cr_initiation.elementwise_deserialize(&temp_path);
assert!(result.is_ok());
let initiation = result.unwrap().unwrap();
assert!(initiation.temp_s_key.is_none());
assert!(initiation.temp_p_key.is_some());
assert!(initiation.requester_details.is_none());
let cr_initiation = IdentityCRInitiation::new();
let requester_details_path = temp_path.join("requester_details.json");
let requester_details_file = File::create(requester_details_path).unwrap();
let requester_details = RequesterDetails {
requester_org: String::from("My Org"),
operator_name: String::from("John Doe"),
};
serde_json::to_writer(requester_details_file, &requester_details).unwrap();
let result = cr_initiation.elementwise_deserialize(&temp_path);
assert!(result.is_ok());
let initiation = result.unwrap().unwrap();
assert!(initiation.temp_p_key.is_some());
assert!(initiation.requester_details.is_some());
let cr_initiation = IdentityCRInitiation::new();
let temp_p_key_file = File::create(&temp_p_key_path).unwrap();
serde_json::to_writer(temp_p_key_file, "this is not valid json").unwrap();
let result = cr_initiation.elementwise_deserialize(&temp_path);
assert!(result.is_err());
}
#[test]
fn test_elementwise_deserialize_identity_challenge() {
let identity_challenge = IdentityCRChallenge::new();
let temp_path = tempdir().unwrap().into_path();
let result = identity_challenge.elementwise_deserialize(&temp_path);
assert!(result.is_ok());
let identity_challenge = result.unwrap();
assert!(identity_challenge.is_none());
let update_p_key_path = temp_path.join("update_p_key.json");
let update_p_key_file = File::create(update_p_key_path).unwrap();
let update_p_key: Jwk = serde_json::from_str(TEST_UPDATE_KEY).unwrap();
serde_json::to_writer(update_p_key_file, &update_p_key).unwrap();
let identity_challenge = IdentityCRChallenge::new();
let result = identity_challenge.elementwise_deserialize(&temp_path);
assert!(result.is_ok());
let identity_challenge = result.unwrap().unwrap();
assert_eq!(identity_challenge.update_p_key, Some(update_p_key));
assert!(identity_challenge.identity_nonce.is_none());
assert!(identity_challenge.identity_challenge_signature.is_none());
assert!(identity_challenge.identity_response_signature.is_none());
let identity_nonce_path = temp_path.join("identity_nonce.json");
let identity_nonce_file = File::create(identity_nonce_path).unwrap();
serde_json::to_writer(identity_nonce_file, &42).unwrap();
let identity_challenge = IdentityCRChallenge::new();
let result = identity_challenge.elementwise_deserialize(&temp_path);
assert!(result.is_err());
println!("Error: {:?}", result.unwrap_err());
}
#[test]
fn test_elementwise_deserialize_content_challenge() {
let content_challenge = ContentCRChallenge::new();
let temp_path = tempdir().unwrap().into_path();
let result = content_challenge.elementwise_deserialize(&temp_path);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
let content_challenge = ContentCRChallenge::new();
let content_nonce_path = temp_path.join("content_nonce.json");
let content_nonce_file = File::create(&content_nonce_path).unwrap();
let mut nonces_map: HashMap<&str, Nonce> = HashMap::new();
nonces_map.insert("test_id", Nonce::new());
serde_json::to_writer(content_nonce_file, &nonces_map).unwrap();
let result = content_challenge.elementwise_deserialize(&temp_path);
assert!(result.is_ok());
let content_challenge = result.unwrap().unwrap();
assert!(content_challenge.content_nonce.is_some());
assert!(content_challenge.content_challenge_signature.is_none());
assert!(content_challenge.content_response_signature.is_none());
let content_nonce_file = File::create(&content_nonce_path).unwrap();
serde_json::to_writer(content_nonce_file, "thisisinvalid").unwrap();
let result = content_challenge.elementwise_deserialize(&temp_path);
print!("Result: {:?}", result);
assert!(result.is_err());
}
#[test]
fn test_deserialize_challenge_state() {
let path = tempdir().unwrap().into_path();
let challenge_state = CRState::new();
let identity_initiatiation = IdentityCRInitiation {
temp_s_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()),
temp_p_key: None,
requester_details: Some(RequesterDetails {
requester_org: String::from("My Org"),
operator_name: String::from("John Doe"),
}),
};
let _ = identity_initiatiation.elementwise_serialize(&path);
let identity_challenge = IdentityCRChallenge {
update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()),
update_s_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()),
identity_nonce: Some(Nonce::new()),
identity_challenge_signature: Some(String::from("some challenge signature string")),
identity_response_signature: Some(String::from("some response signature string")),
};
let _ = identity_challenge.elementwise_serialize(&path);
let content_cr_initiation = ContentCRInitiation {
requester_did: Some("did:example:123456789abcdefghi".to_string()),
};
let _ = content_cr_initiation.elementwise_serialize(&path);
let result = challenge_state.elementwise_deserialize(&path);
assert!(result.is_ok());
let challenge_state = result.unwrap().unwrap();
println!(
"Challenge state deserialized from files: {:?}",
challenge_state
);
assert!(challenge_state.identity_cr_initiation.is_some());
assert!(challenge_state.identity_challenge_response.is_some());
assert!(challenge_state.content_cr_initiation.is_some());
assert!(challenge_state.content_challenge_response.is_none());
let identity_nonce_path = path.join("content_nonce.json");
let identity_nonce_file = File::create(identity_nonce_path).unwrap();
serde_json::to_writer(identity_nonce_file, &42).unwrap();
let challenge_state = CRState::new().elementwise_deserialize(&path);
assert!(challenge_state.is_err());
}
#[test]
fn test_matching_endpoint() {
let services = vec![
Service {
id: String::from("#service-1"),
service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from(
"https://example.com/endpoint-1",
)))),
type_: ssi::one_or_many::OneOrMany::One("Service1".to_string()),
property_set: None,
},
Service {
id: String::from("#service-2"),
service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from(
"https://example.com/endpoint-2",
)))),
type_: ssi::one_or_many::OneOrMany::One("Service2".to_string()),
property_set: None,
},
];
let result = matching_endpoint(&services, "#service-1");
assert_eq!(result.unwrap(), "https://example.com/endpoint-1");
let result = matching_endpoint(&services, "service-1");
assert!(result.is_err());
}
#[test]
fn test_matching_endpoint_multiple_endpoints_found() {
let services = vec![
Service {
id: String::from("#service-1"),
service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from(
"https://example.com/endpoint-1",
)))),
type_: ssi::one_or_many::OneOrMany::One("Service1".to_string()),
property_set: None,
},
Service {
id: String::from("#service-1"),
service_endpoint: Some(OneOrMany::One(ServiceEndpoint::URI(String::from(
"https://example.com/endpoint-2",
)))),
type_: ssi::one_or_many::OneOrMany::One("Service1".to_string()),
property_set: None,
},
];
let result = matching_endpoint(&services, "#service-1");
assert!(result.is_err());
}
#[test]
fn test_check_cr_status() {
let mut cr_state = CRState::new();
let result = cr_state.check_cr_status().unwrap();
assert_eq!(result, CurrentCRState::NotStarted);
cr_state.identity_cr_initiation = Some(IdentityCRInitiation {
temp_p_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()),
temp_s_key: None,
requester_details: None,
});
let result = cr_state.check_cr_status();
assert_eq!(result.unwrap(), CurrentCRState::NotStarted);
cr_state.identity_cr_initiation = Some(IdentityCRInitiation {
temp_p_key: Some(serde_json::from_str(TEST_TEMP_KEY).unwrap()),
temp_s_key: None,
requester_details: Some(RequesterDetails {
requester_org: String::from("My Org"),
operator_name: String::from("John Doe"),
}),
});
cr_state.identity_challenge_response = Some(IdentityCRChallenge {
update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()),
update_s_key: None,
identity_nonce: Some(Nonce::new()),
identity_challenge_signature: Some(String::from("some challenge signature string")),
identity_response_signature: None,
});
let result = cr_state.check_cr_status();
assert_eq!(result.unwrap(), CurrentCRState::IdentityChallengeComplete);
cr_state.identity_challenge_response = Some(IdentityCRChallenge {
update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()),
update_s_key: None,
identity_nonce: Some(Nonce::new()),
identity_challenge_signature: Some(String::from("some challenge signature string")),
identity_response_signature: Some(String::from("some response signature string")),
});
cr_state.content_cr_initiation = {
Some(ContentCRInitiation {
requester_did: Some("did:example:123456789abcdefghi".to_string()),
})
};
let result = cr_state.check_cr_status();
assert_eq!(result.unwrap(), CurrentCRState::ContentCRInitiated);
cr_state.content_challenge_response = Some(ContentCRChallenge {
content_nonce: Some(HashMap::new()),
content_challenge_signature: Some(String::from(
"some content challenge signature string",
)),
content_response_signature: Some(String::from(
"some content response signature string",
)),
});
let result = cr_state.check_cr_status();
assert_eq!(result.unwrap(), CurrentCRState::ContentResponseComplete);
}
#[test]
fn test_check_cr_status_inconsistent_order() {
let mut cr_state = CRState::new();
cr_state.identity_challenge_response = Some(IdentityCRChallenge {
update_s_key: None,
update_p_key: Some(serde_json::from_str(TEST_UPDATE_KEY).unwrap()),
identity_nonce: Some(Nonce::new()),
identity_challenge_signature: Some(String::from("some challenge signature string")),
identity_response_signature: Some(String::from("some response signature string")),
});
let result = cr_state.check_cr_status();
assert_eq!(result.unwrap(), CurrentCRState::NotStarted);
}
}