diff --git a/CHANGELOG.md b/CHANGELOG.md index a58af9c..317bb1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Added support for content warnings. +- Support integrity proofs with `DataIntegrityProof` type. ### Changed diff --git a/mitra-utils/src/crypto_rsa.rs b/mitra-utils/src/crypto_rsa.rs index f39fdc1..650db98 100644 --- a/mitra-utils/src/crypto_rsa.rs +++ b/mitra-utils/src/crypto_rsa.rs @@ -47,7 +47,7 @@ pub fn deserialize_public_key( } /// RSASSA-PKCS1-v1_5 signature -pub fn create_rsa_signature( +pub fn create_rsa_sha256_signature( private_key: &RsaPrivateKey, message: &str, ) -> Result, rsa::errors::Error> { @@ -63,7 +63,7 @@ pub fn get_message_digest(message: &str) -> String { digest_b64 } -pub fn verify_rsa_signature( +pub fn verify_rsa_sha256_signature( public_key: &RsaPublicKey, message: &str, signature: &[u8], @@ -102,13 +102,13 @@ YsFtrgWDQ/s8k86sNBU+Ce2GOL7seh46kyAWgJeohh4Rcrr23rftHbvxOcRM8VzYuCeb1DgVhPGtA0xU fn test_verify_rsa_signature() { let private_key = generate_weak_rsa_key().unwrap(); let message = "test".to_string(); - let signature = create_rsa_signature( + let signature = create_rsa_sha256_signature( &private_key, &message, ).unwrap(); let public_key = RsaPublicKey::from(&private_key); - let is_valid = verify_rsa_signature( + let is_valid = verify_rsa_sha256_signature( &public_key, &message, &signature, diff --git a/src/http_signatures/create.rs b/src/http_signatures/create.rs index fb8edc8..37b7f6a 100644 --- a/src/http_signatures/create.rs +++ b/src/http_signatures/create.rs @@ -3,7 +3,7 @@ use chrono::Utc; use rsa::RsaPrivateKey; use mitra_utils::crypto_rsa::{ - create_rsa_signature, + create_rsa_sha256_signature, get_message_digest, }; @@ -72,7 +72,7 @@ pub fn create_http_signature( .map(|(name, _)| name.to_string()) .collect::>() .join(" "); - let signature = create_rsa_signature(signer_key, &message)?; + let signature = create_rsa_sha256_signature(signer_key, &message)?; let signature_parameter = base64::encode(signature); let signature_header = format!( r#"keyId="{}",algorithm="{}",headers="{}",signature="{}""#, diff --git a/src/http_signatures/verify.rs b/src/http_signatures/verify.rs index f8e4e58..e3ac6d6 100644 --- a/src/http_signatures/verify.rs +++ b/src/http_signatures/verify.rs @@ -5,7 +5,7 @@ use chrono::{DateTime, Duration, TimeZone, Utc}; use regex::Regex; use rsa::RsaPublicKey; -use mitra_utils::crypto_rsa::verify_rsa_signature; +use mitra_utils::crypto_rsa::verify_rsa_sha256_signature; const SIGNATURE_PARAMETER_RE: &str = r#"^(?P[a-zA-Z]+)="(?P.+)"$"#; @@ -134,7 +134,7 @@ pub fn verify_http_signature( log::warn!("signature has expired"); }; let signature = base64::decode(&signature_data.signature)?; - let is_valid_signature = verify_rsa_signature( + let is_valid_signature = verify_rsa_sha256_signature( signer_key, &signature_data.message, &signature, diff --git a/src/json_signatures/create.rs b/src/json_signatures/create.rs index 4a010f4..86ec214 100644 --- a/src/json_signatures/create.rs +++ b/src/json_signatures/create.rs @@ -8,7 +8,7 @@ use mitra_utils::{ canonicalize_object, CanonicalizationError, }, - crypto_rsa::create_rsa_signature, + crypto_rsa::create_rsa_sha256_signature, did_key::DidKey, did_pkh::DidPkh, multibase::encode_multibase_base58btc, @@ -30,6 +30,8 @@ pub(super) const PROOF_PURPOSE: &str = "assertionMethod"; pub struct IntegrityProof { #[serde(rename = "type")] pub proof_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub cryptosuite: Option, pub proof_purpose: String, pub verification_method: String, pub created: DateTime, @@ -43,6 +45,7 @@ impl IntegrityProof { ) -> Self { Self { proof_type: PROOF_TYPE_JCS_RSA.to_string(), + cryptosuite: None, proof_purpose: PROOF_PURPOSE.to_string(), verification_method: signer_key_id.to_string(), created: Utc::now(), @@ -56,6 +59,7 @@ impl IntegrityProof { ) -> Self { Self { proof_type: PROOF_TYPE_JCS_EIP191.to_string(), + cryptosuite: None, proof_purpose: PROOF_PURPOSE.to_string(), verification_method: signer.to_string(), created: Utc::now(), @@ -69,6 +73,7 @@ impl IntegrityProof { ) -> Self { Self { proof_type: PROOF_TYPE_JCS_ED25519.to_string(), + cryptosuite: None, proof_purpose: PROOF_PURPOSE.to_string(), verification_method: signer.to_string(), created: Utc::now(), @@ -115,14 +120,17 @@ pub fn sign_object( signer_key_id: &str, ) -> Result { // Canonicalize - let message = canonicalize_object(object)?; + let transformed_object = canonicalize_object(object)?; // Sign - let signature = create_rsa_signature(signer_key, &message)?; + let signature = create_rsa_sha256_signature( + signer_key, + &transformed_object, + )?; // Insert proof let proof = IntegrityProof::jcs_rsa(signer_key_id, &signature); - let mut object_value = serde_json::to_value(object)?; - add_integrity_proof(&mut object_value, proof)?; - Ok(object_value) + let mut signed_object = object.clone(); + add_integrity_proof(&mut signed_object, proof)?; + Ok(signed_object) } pub fn is_object_signed(object: &Value) -> bool { diff --git a/src/json_signatures/proofs.rs b/src/json_signatures/proofs.rs index 3dfe36f..103adfb 100644 --- a/src/json_signatures/proofs.rs +++ b/src/json_signatures/proofs.rs @@ -9,12 +9,16 @@ pub const PROOF_TYPE_ID_EIP191: &str = "ethereum-eip191-00"; // Identity proof, version 2022A pub const PROOF_TYPE_ID_MINISIGN: &str = "MitraMinisignSignature2022A"; +// https://w3c.github.io/vc-data-integrity/#dataintegrityproof +pub const DATA_INTEGRITY_PROOF: &str = "DataIntegrityProof"; + // Similar to https://identity.foundation/JcsEd25519Signature2020/ // - Canonicalization algorithm: JCS // - Digest algorithm: SHA-256 // - Signature algorithm: RSASSA-PKCS1-v1_5 pub const PROOF_TYPE_JCS_RSA: &str = "MitraJcsRsaSignature2022"; pub const PROOF_TYPE_JCS_RSA_LEGACY: &str = "JcsRsaSignature2022"; +pub const CRYPTOSUITE_JCS_RSA: &str = "mitra-jcs-rsa-2022"; // Similar to EthereumPersonalSignature2021 but with JCS pub const PROOF_TYPE_JCS_EIP191: &str = "MitraJcsEip191Signature2022"; @@ -49,3 +53,13 @@ impl FromStr for ProofType { Ok(proof_type) } } + +impl ProofType { + pub fn from_cryptosuite(value: &str) -> Result { + let proof_type = match value { + CRYPTOSUITE_JCS_RSA => Self::JcsRsaSignature, + _ => return Err(ConversionError), + }; + Ok(proof_type) + } +} diff --git a/src/json_signatures/verify.rs b/src/json_signatures/verify.rs index 49e4f0b..443f3a9 100644 --- a/src/json_signatures/verify.rs +++ b/src/json_signatures/verify.rs @@ -9,7 +9,7 @@ use mitra_utils::{ canonicalize_object, CanonicalizationError, }, - crypto_rsa::verify_rsa_signature, + crypto_rsa::verify_rsa_sha256_signature, did::Did, did_key::DidKey, did_pkh::DidPkh, @@ -24,7 +24,7 @@ use super::create::{ PROOF_KEY, PROOF_PURPOSE, }; -use super::proofs::ProofType; +use super::proofs::{ProofType, DATA_INTEGRITY_PROOF}; #[derive(Debug, PartialEq)] pub enum JsonSigner { @@ -75,8 +75,15 @@ pub fn get_json_signature( if proof.proof_purpose != PROOF_PURPOSE { return Err(VerificationError::InvalidProof("invalid proof purpose")); }; - let signature_type = proof.proof_type.parse() - .map_err(|_| VerificationError::InvalidProof("unsupported proof type"))?; + let proof_type = if proof.proof_type == DATA_INTEGRITY_PROOF { + let cryptosuite = proof.cryptosuite.as_ref() + .ok_or(VerificationError::InvalidProof("cryptosuite is not specified"))?; + ProofType::from_cryptosuite(cryptosuite) + .map_err(|_| VerificationError::InvalidProof("unsupported proof type"))? + } else { + proof.proof_type.parse() + .map_err(|_| VerificationError::InvalidProof("unsupported proof type"))? + }; let signer = if let Ok(did) = Did::from_str(&proof.verification_method) { JsonSigner::Did(did) } else if Url::parse(&proof.verification_method).is_ok() { @@ -84,12 +91,12 @@ pub fn get_json_signature( } else { return Err(VerificationError::InvalidProof("unsupported verification method")); }; - let message = canonicalize_object(&object)?; + let transformed_object = canonicalize_object(&object)?; let signature = decode_multibase_base58btc(&proof.proof_value)?; let signature_data = SignatureData { - signature_type, + signature_type: proof_type, signer, - message, + message: transformed_object, signature, }; Ok(signature_data) @@ -99,7 +106,7 @@ pub fn verify_rsa_json_signature( signature_data: &SignatureData, signer_key: &RsaPublicKey, ) -> Result<(), VerificationError> { - let is_valid_signature = verify_rsa_signature( + let is_valid_signature = verify_rsa_sha256_signature( signer_key, &signature_data.message, &signature_data.signature,