Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update eddsa-jcs-2022 hashing for compliance #43

Merged
merged 1 commit into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 40 additions & 54 deletions did-utils/src/proof/eddsa_jcs_2022.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
use multibase::Base;

use crate::crypto::{ed25519::Ed25519KeyPair, traits::{CoreSign, Error}, sha256_hash::sha256_hash};

use super::{model::{Proof, UnsecuredDocument}, traits::CryptoProof};
use crate::crypto::{
ed25519::Ed25519KeyPair,
sha256_hash::sha256_hash,
traits::{CoreSign, Error},
};

use super::{model::Proof, traits::CryptoProof};

pub const CRYPRO_SUITE_EDDSA_JCS_2022: &str = "eddsa-jcs-2022";
pub const PROOF_TYPE_DATA_INTEGRITY_PROOF: &str ="DataIntegrityProof";
pub const PROOF_TYPE_DATA_INTEGRITY_PROOF: &str = "DataIntegrityProof";

pub struct EdDsaJcs2022 {
/// The proof object
///
/// In a proof creation process, it does not contain the proof value, but
///
/// In a proof creation process, it does not contain the proof value, but
/// carries info like challenge, nonce, etc.
///
/// In a proof verification process, it contains the proof as found in the
///
/// In a proof verification process, it contains the proof as found in the
/// secured document, including the proof value
pub proof: Proof,

/// The keypair used to sreate the proof: in which case the signing key must be present.
///
///
/// The keypair used to verify the proof: in which case only the public key must be present.
///
///
/// This module does not perform resolution of the verification method. Module assumes calles
/// extracted the public key prior to calling this module.
pub key_pair: Ed25519KeyPair,

/// The proof value codec. This is important for the encoding of the proof.
///
///
/// For the decoding, codec is automaticaly infered from the string.
pub proof_value_codec: Option<Base>,
}
Expand All @@ -45,37 +48,28 @@ impl CryptoProof for EdDsaJcs2022 {
None => Some(chrono::Utc::now()),
},
proof_value: None,
.. self.proof.clone()
};

// Create the unsecure document
let unsecured_document = UnsecuredDocument {
content: payload,
proof: super::model::Proofs::SingleProof(Box::new(normalized_proof.clone())),
..self.proof.clone()
};

// let hash = transform(unsecured_document);
// JCS unsecure document
json_canon::to_string(&unsecured_document)
.map_err(|_| Error::InvalidProof)
.and_then(|canonicalized_doc| {
let hash = sha256_hash(canonicalized_doc.as_bytes());
self.key_pair.sign(&hash[..])
.map(|signature| Proof {
proof_value: Some(multibase::encode(self.proof_value_codec.unwrap(), signature)),
..normalized_proof
})
})

// Canonicalization
let canon_proof = json_canon::to_string(&normalized_proof).map_err(|_| Error::InvalidProof)?;
let canon_doc = json_canon::to_string(&payload).map_err(|_| Error::InvalidProof)?;

// Compute hash to sign
let hash = [sha256_hash(canon_proof.as_bytes()), sha256_hash(canon_doc.as_bytes())].concat();

return self.key_pair.sign(&hash[..]).map(|signature| Proof {
proof_value: Some(multibase::encode(self.proof_value_codec.unwrap(), signature)),
..normalized_proof
});
}
}

}

fn verify(&self, payload: serde_json::Value) -> Result<(), Error> {
match self.proof.proof_value.clone() {
None => Err(Error::InvalidProof),
Some(proof_value) => {

// Clone the proof
// - droping the proof value
// - normalyzing proof type fields
Expand All @@ -94,29 +88,21 @@ impl CryptoProof for EdDsaJcs2022 {
let mut naked_payload = payload.clone();
naked_payload.as_object_mut().unwrap().remove("proof");
naked_payload
},
}
};

// Create the unsecure document
let unsecured_document = UnsecuredDocument {
content: naked_payload,
proof: super::model::Proofs::SingleProof(Box::new(normalized_proof)),
};
// Canonicalization
let canon_proof = json_canon::to_string(&normalized_proof).map_err(|_| Error::InvalidProof)?;
let canon_doc = json_canon::to_string(&naked_payload).map_err(|_| Error::InvalidProof)?;

// Compute hash to verify
let hash = [sha256_hash(canon_proof.as_bytes()), sha256_hash(canon_doc.as_bytes())].concat();

// JCS unsecure document
json_canon::to_string(&unsecured_document)
return multibase::decode(proof_value)
.map_err(|_| Error::InvalidProof)
.and_then(|canonicalized_doc| {
// hash
let hash = sha256_hash(canonicalized_doc.as_bytes());
// decode the signature
multibase::decode(proof_value)
.map_err(|_| Error::InvalidProof)
.and_then(|signature| self.key_pair.verify(&hash, &(signature.1)))
})
.and_then(|signature| self.key_pair.verify(&hash, &(signature.1)));
}
}

}
}

Expand All @@ -125,7 +111,7 @@ impl CryptoProof for EdDsaJcs2022 {
mod tests {
use serde_json::Value;

use crate::crypto::traits::Generate;
use crate::{crypto::traits::Generate, proof::model::UnsecuredDocument};

// create an EdDsaJcs2022 object and use it to produce a proof.
// The proof is then verified.
Expand Down Expand Up @@ -177,8 +163,8 @@ mod tests {
content: payload,
proof: crate::proof::model::Proofs::SingleProof(Box::new(secured_proof.clone())),
};
let expected_canonicalized_proof = r#"{"challenge":"523452345234asfdasdfasdfa","created":"2023-03-05T19:23:24Z","cryptosuite":"eddsa-jcs-2022","domain":"vc-demo.adorsys.com","nonce":"1234567890","proofPurpose":"assertionMethod","proofValue":"z3EK3FRmocPnbnrQCKWYiaG9dTUgVKLgefb1EmLEZPUVW4RXpL9HBuxpD27zAVESfjvjTDJ7PmCPBP4MBT7oVFdbH","type":"DataIntegrityProof","verificationMethod":"https://di.example/issuer#z6MkjLrk3gKS2nnkeWcmcxiZPGskmesDpuwRBorgHxUXfxnG"}"#;

let expected_canonicalized_proof = r#"{"challenge":"523452345234asfdasdfasdfa","created":"2023-03-05T19:23:24Z","cryptosuite":"eddsa-jcs-2022","domain":"vc-demo.adorsys.com","nonce":"1234567890","proofPurpose":"assertionMethod","proofValue":"z2DbDNkE47SquDQ7wM6p3RjNdFB1FG7Num2w9kprZjUB2gNZvz7bYgcT5XCe3TdjfxxWfKkup1ZdrRhfEMLsk2kmr","type":"DataIntegrityProof","verificationMethod":"https://di.example/issuer#z6MkjLrk3gKS2nnkeWcmcxiZPGskmesDpuwRBorgHxUXfxnG"}"#;
let canonicalized_proof = json_canon::to_string(&secured_proof).unwrap();
assert_eq!(expected_canonicalized_proof, canonicalized_proof);

Expand All @@ -194,4 +180,4 @@ mod tests {

ed_dsa_jcs_2022_verifier.verify(secure_doc_json_value).unwrap();
}
}
}
25 changes: 12 additions & 13 deletions did-utils/src/vc/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ pub struct VerifiableCredential {
pub additional_properties: Option<HashMap<String, Value>>,

// Set of proofs
// We allow a vc to created without the proof block.
// We allow a vc to created without the proof block.
// Event though it is required. As we want to produce
// the unsecured vesion before proof production or proof
// the unsecured vesion before proof production or proof
// verification.
#[serde(skip_serializing_if = "Option::is_none")]
pub proof: Option<Proofs>,
Expand Down Expand Up @@ -109,8 +109,8 @@ pub enum CredentialSubjects {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CredentialSubject {
// Identifies the subject of the verifiable credential
// (the thing the claims are about) and
// Identifies the subject of the verifiable credential
// (the thing the claims are about) and
// uses a decentralized identifier, also known as a DID
// see https://www.w3.org/TR/vc-data-model-2.0/#identifiers
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -273,9 +273,9 @@ pub struct VerifiablePresentation {
pub holder: Option<String>,

// Set of proofs
// We allow a VP to created without the proof block.
// We allow a VP to created without the proof block.
// Event though it is required. As we want to produce
// the unsecured vesion before proof production or proof
// the unsecured vesion before proof production or proof
// verification.
#[serde(skip_serializing_if = "Option::is_none")]
pub proof: Option<Proofs>,
Expand Down Expand Up @@ -329,7 +329,6 @@ mod tests {
proof: crate::proof::model::Proofs::SingleProof(Box::new(secured_proof)),
};
let vc_canon = json_canon::to_string(&secured_doc).unwrap();
println!("{}", vc_canon);
assert_eq!(SECURED_VC, vc_canon);
}

Expand Down Expand Up @@ -357,7 +356,7 @@ mod tests {

// Initialize with zeros or any default value
let mut public_key_bytes_slice: [u8; 32] = [0; 32];
public_key_bytes_slice.copy_from_slice(public_key_bytes_vector.1.as_slice());
public_key_bytes_slice.copy_from_slice(public_key_bytes_vector.1.as_slice());
let ed_dsa_jcs_2022_verifier = EdDsaJcs2022 {
proof: *secured_proof,
key_pair: Ed25519KeyPair::from_public_key(&public_key_bytes_slice).unwrap(),
Expand All @@ -379,7 +378,7 @@ mod tests {
const CONTEXTS: &[&str] = &[
"https://www.w3.org/ns/credentials/v2",
"https://www.w3.org/ns/credentials/examples/v2",
];
];

fn make_context() -> Context {
let mut contexts: Vec<String> = Vec::new();
Expand Down Expand Up @@ -423,7 +422,7 @@ mod tests {
credential_schemas: None,
related_resource: None,
refresh_service: None,
}
}
}

fn make_vp(vc: VerifiableCredential) -> VerifiablePresentation {
Expand Down Expand Up @@ -470,9 +469,9 @@ mod tests {
proof_value: None,
previous_proof: None,
nonce: Some("1234567890".to_string()),
}
}
}

const SECURED_VC: &str = r#"{"@context":["https://www.w3.org/ns/credentials/v2","https://www.w3.org/ns/credentials/examples/v2"],"credentialSubject":{"alumniOf":{"id":"did:key#z38w6kKWT7hesyxuuVUSH4LsxbcRof4ra1QBDtR1qrc1q","name":"Example University"},"id":"did:key#z38w6kKWT7hesyxuuVUSH4LsxbcRof4ra1QBDtR1qrc1q"},"description":"Graduated from Example University","id":"http://university.example/credentials/3732","issuer":"did:key#z7dNyxjs9BUfsbX11VG4BGDMB3Wg1Pq2NqhSwTBT8UuRC","name":"Jayden Doe","proof":{"challenge":"523452345234asfdasdfasdfa","created":"2023-03-05T19:23:24Z","cryptosuite":"eddsa-jcs-2022","domain":"vc-demo.adorsys.com","nonce":"1234567890","proofPurpose":"assertionMethod","proofValue":"z2dCvjLCkbA9sCqx3NzP7YVQBPTcvdgVjgQVJDYircHho4mjL8wbwqCWS2oPzpwvWMRDRGp5qmD4Ktab6M76kp7Vi","type":"DataIntegrityProof","verificationMethod":"did:key#z7dNyxjs9BUfsbX11VG4BGDMB3Wg1Pq2NqhSwTBT8UuRC"},"type":["VerifiableCredential","AlumniCredential"],"validFrom":"2023-03-05T19:23:24Z","validUntil":"2023-12-31T19:23:24Z"}"#;
const SECURED_VC: &str = r#"{"@context":["https://www.w3.org/ns/credentials/v2","https://www.w3.org/ns/credentials/examples/v2"],"credentialSubject":{"alumniOf":{"id":"did:key#z38w6kKWT7hesyxuuVUSH4LsxbcRof4ra1QBDtR1qrc1q","name":"Example University"},"id":"did:key#z38w6kKWT7hesyxuuVUSH4LsxbcRof4ra1QBDtR1qrc1q"},"description":"Graduated from Example University","id":"http://university.example/credentials/3732","issuer":"did:key#z7dNyxjs9BUfsbX11VG4BGDMB3Wg1Pq2NqhSwTBT8UuRC","name":"Jayden Doe","proof":{"challenge":"523452345234asfdasdfasdfa","created":"2023-03-05T19:23:24Z","cryptosuite":"eddsa-jcs-2022","domain":"vc-demo.adorsys.com","nonce":"1234567890","proofPurpose":"assertionMethod","proofValue":"z4DEMwgRCZnRddGPPevbaafihRwj4ng3dn5EwmnnaeMVMp25niKWZ3cW1rdfWMtfp5dpCmNEjfJtvbnnpUsZcy9c6","type":"DataIntegrityProof","verificationMethod":"did:key#z7dNyxjs9BUfsbX11VG4BGDMB3Wg1Pq2NqhSwTBT8UuRC"},"type":["VerifiableCredential","AlumniCredential"],"validFrom":"2023-03-05T19:23:24Z","validUntil":"2023-12-31T19:23:24Z"}"#;

}
Loading