diff --git a/sdk/src/crypto/README.md b/sdk/src/crypto/README.md index 5eed8530a..ab21b7964 100644 --- a/sdk/src/crypto/README.md +++ b/sdk/src/crypto/README.md @@ -4,15 +4,15 @@ ## Signing -| C2PA `SigningAlg` | Default (*) | `feature = "rust_native_crypto"` (*) | WASM | -| --- | --- | --- | --- | -| `es256` | OpenSSL | `p256` | `p256` | -| `es384` | OpenSSL | `p384` | `p384` | -| `es512` | OpenSSL | OpenSSL | ❌ | -| `ed25519` | OpenSSL | `ed25519-dalek` | `ed25519-dalek` | -| `ps256` | OpenSSL | `rsa` | `rsa` | -| `ps384` | OpenSSL | `rsa` | `rsa` | -| `ps512` | OpenSSL | `rsa` | `rsa` | +| C2PA `SigningAlg` | Default (*) | `feature = "rust_native_crypto"` (*) or WASM | +| --- | --- | --- | +| `es256` | OpenSSL | `p256` | +| `es384` | OpenSSL | `p384` | +| `es512` | OpenSSL | `p521` | +| `ed25519` | OpenSSL | `ed25519-dalek` | +| `ps256` | OpenSSL | `rsa` | +| `ps384` | OpenSSL | `rsa` | +| `ps512` | OpenSSL | `rsa` | (*) Applies to all supported platforms except WASM
❌ = not supported diff --git a/sdk/src/crypto/cose/certificate_trust_policy.rs b/sdk/src/crypto/cose/certificate_trust_policy.rs index 54e5edb76..da133a0d6 100644 --- a/sdk/src/crypto/cose/certificate_trust_policy.rs +++ b/sdk/src/crypto/cose/certificate_trust_policy.rs @@ -649,7 +649,6 @@ zGxQnM2hCA== let ps512 = test_signer(SigningAlg::Ps512); let es256 = test_signer(SigningAlg::Es256); let es384 = test_signer(SigningAlg::Es384); - #[cfg(feature = "openssl")] let es512 = test_signer(SigningAlg::Es512); let ed25519 = test_signer(SigningAlg::Ed25519); @@ -658,7 +657,6 @@ zGxQnM2hCA== let ps512_certs = ps512.cert_chain().unwrap(); let es256_certs = es256.cert_chain().unwrap(); let es384_certs = es384.cert_chain().unwrap(); - #[cfg(feature = "openssl")] let es512_certs = es512.cert_chain().unwrap(); let ed25519_certs = ed25519.cert_chain().unwrap(); @@ -672,7 +670,6 @@ zGxQnM2hCA== .unwrap(); ctp.check_certificate_trust(&es384_certs[1..], &es384_certs[0], None) .unwrap(); - #[cfg(feature = "openssl")] ctp.check_certificate_trust(&es512_certs[1..], &es512_certs[0], None) .unwrap(); ctp.check_certificate_trust(&ed25519_certs[1..], &ed25519_certs[0], None) @@ -742,7 +739,6 @@ zGxQnM2hCA== let ps512 = test_signer(SigningAlg::Ps512); let es256 = test_signer(SigningAlg::Es256); let es384 = test_signer(SigningAlg::Es384); - #[cfg(feature = "openssl")] let es512 = test_signer(SigningAlg::Es512); let ed25519 = test_signer(SigningAlg::Ed25519); @@ -751,7 +747,6 @@ zGxQnM2hCA== let ps512_certs = ps512.cert_chain().unwrap(); let es256_certs = es256.cert_chain().unwrap(); let es384_certs = es384.cert_chain().unwrap(); - #[cfg(feature = "openssl")] let es512_certs = es512.cert_chain().unwrap(); let ed25519_certs = ed25519.cert_chain().unwrap(); @@ -792,7 +787,6 @@ zGxQnM2hCA== CertificateTrustError::CertificateNotTrusted ); - #[cfg(feature = "openssl")] assert_eq!( ctp.check_certificate_trust(&es512_certs[2..], &es512_certs[0], None) .unwrap_err(), @@ -933,7 +927,6 @@ zGxQnM2hCA== let ps512 = test_signer(SigningAlg::Ps512); let es256 = test_signer(SigningAlg::Es256); let es384 = test_signer(SigningAlg::Es384); - #[cfg(feature = "openssl")] let es512 = test_signer(SigningAlg::Es512); let ed25519 = test_signer(SigningAlg::Ed25519); @@ -942,7 +935,6 @@ zGxQnM2hCA== assert_eq!(ps512.alg(), SigningAlg::Ps512); assert_eq!(es256.alg(), SigningAlg::Es256); assert_eq!(es384.alg(), SigningAlg::Es384); - #[cfg(feature = "openssl")] assert_eq!(es512.alg(), SigningAlg::Es512); assert_eq!(ed25519.alg(), SigningAlg::Ed25519); @@ -951,7 +943,6 @@ zGxQnM2hCA== let ps512_certs = ps512.cert_chain().unwrap(); let es256_certs = es256.cert_chain().unwrap(); let es384_certs = es384.cert_chain().unwrap(); - #[cfg(feature = "openssl")] let es512_certs = es512.cert_chain().unwrap(); let ed25519_certs = ed25519.cert_chain().unwrap(); @@ -965,7 +956,6 @@ zGxQnM2hCA== .unwrap(); ctp.check_certificate_trust(&es384_certs[1..], &es384_certs[0], None) .unwrap(); - #[cfg(feature = "openssl")] ctp.check_certificate_trust(&es512_certs[1..], &es512_certs[0], None) .unwrap(); ctp.check_certificate_trust(&ed25519_certs[1..], &ed25519_certs[0], None) diff --git a/sdk/src/crypto/raw_signature/rust_native/signers/ecdsa_signer.rs b/sdk/src/crypto/raw_signature/rust_native/signers/ecdsa_signer.rs index faa745736..3f45c4cb4 100644 --- a/sdk/src/crypto/raw_signature/rust_native/signers/ecdsa_signer.rs +++ b/sdk/src/crypto/raw_signature/rust_native/signers/ecdsa_signer.rs @@ -11,27 +11,36 @@ // specific language governing permissions and limitations under // each license. +use std::str::FromStr; + +use asn1_rs::{Any, BitString, DerSequence, FromDer, Sequence}; +use der::{Decode, Encode}; use ecdsa::signature::Signer; use p256::ecdsa::{Signature as P256Signature, SigningKey as P256SigningKey}; use p384::ecdsa::{Signature as P384Signature, SigningKey as P384SigningKey}; -use pkcs8::DecodePrivateKey; +use p521::ecdsa::{Signature as P512Signature, SigningKey as P512SigningKey}; +use pkcs8::{DecodePrivateKey, ObjectIdentifier, PrivateKeyInfo}; use x509_parser::{error::PEMError, pem::Pem}; use crate::crypto::{ - raw_signature::{RawSigner, RawSignerError, SigningAlg}, + raw_signature::{ + oids::{EC_PUBLICKEY_OID, SECP521R1_OID}, + RawSigner, RawSignerError, SigningAlg, + }, time_stamp::TimeStampProvider, }; -// Rust native does not support Es512 enum EcdsaSigningAlg { Es256, Es384, + Es512, } -// Signing keys for ES256 and ES384 are different types +// Signing keys for ES256, ES384, and ES512 are different types pub enum EcdsaSigningKey { Es256(P256SigningKey), Es384(P384SigningKey), + Es512(P512SigningKey), } pub struct EcdsaSigner { @@ -85,9 +94,8 @@ impl EcdsaSigner { (EcdsaSigningKey::Es384(key), EcdsaSigningAlg::Es384) } SigningAlg::Es512 => { - return Err(RawSignerError::InvalidSigningCredentials( - "Rust Crypto does not support Es512, only OpenSSL".to_string(), - )) + let key = es512_from_pkcs8_pem(private_key_pem)?; + (EcdsaSigningKey::Es512(key), EcdsaSigningAlg::Es512) } _ => { return Err(RawSignerError::InvalidSigningCredentials( @@ -118,6 +126,10 @@ impl RawSigner for EcdsaSigner { let signature: P384Signature = key.sign(data); Ok(signature.to_vec()) } + EcdsaSigningKey::Es512(ref key) => { + let signature: P512Signature = key.sign(data); + Ok(signature.to_vec()) + } } } @@ -125,6 +137,7 @@ impl RawSigner for EcdsaSigner { match self.alg { EcdsaSigningAlg::Es256 => SigningAlg::Es256, EcdsaSigningAlg::Es384 => SigningAlg::Es384, + EcdsaSigningAlg::Es512 => SigningAlg::Es512, } } @@ -143,6 +156,68 @@ impl TimeStampProvider for EcdsaSigner { } } +#[derive(DerSequence)] +struct ECPrivateKey<'a> { + version: u32, + private_key: &'a [u8], // OCTET STRING content + parameters: Option>, + public_key: Option>, +} + +fn es512_from_pkcs8_pem(private_key_pem: &str) -> Result { + let pem = pem::parse(private_key_pem).map_err(|e| { + RawSignerError::InvalidSigningCredentials(format!("invalid ES512 private key PEM: {e}")) + })?; + let pk_info = PrivateKeyInfo::try_from(pem.contents()).map_err(|e| { + RawSignerError::InvalidSigningCredentials(format!("invalid ES512 PKCS#8 structure: {e}")) + })?; + + // Check OID is id-ecPublicKey (1.2.840.10045.2.1) + if pk_info.algorithm.oid.as_bytes() != EC_PUBLICKEY_OID.as_bytes() { + return Err(RawSignerError::InvalidSigningCredentials(format!( + "Unexpected OID: {}, expected id-ecPublicKey {}", + pk_info.algorithm.oid, EC_PUBLICKEY_OID + ))); + } + + let params = pk_info + .algorithm + .parameters + .as_ref() + .ok_or_else(|| { + RawSignerError::InvalidSigningCredentials("Missing algorithm parameters".to_string()) + })? + .to_der() + .map_err(|_| { + RawSignerError::InvalidSigningCredentials( + "Algorithm parameters are not a valid OID".to_string(), + ) + })?; + // Parse the parameters as an ASN.1 OID + let curve_oid = ObjectIdentifier::from_der(¶ms).map_err(|_| { + RawSignerError::InvalidSigningCredentials( + "Algorithm parameters are not a valid OID".to_string(), + ) + })?; + + // Parse ECPrivateKey ASN.1 structure using asn1-rs + let ec_private_key = ECPrivateKey::from_der(pk_info.private_key).map_err(|e| { + RawSignerError::InvalidSigningCredentials(format!("invalid ES512 ECPrivateKey ASN.1: {e}")) + })?; + + // Check version is 1 + if ec_private_key.1.version != 1 { + return Err(RawSignerError::InvalidSigningCredentials(format!( + "ECPrivateKey ASN.1 version is {}, expected 1", + ec_private_key.1.version + ))); + } + + P512SigningKey::from_slice(ec_private_key.1.private_key).map_err(|e| { + RawSignerError::InvalidSigningCredentials(format!("invalid ES512 private key: {e}")) + }) +} + #[cfg(test)] mod tests { #![allow(clippy::expect_used)] @@ -153,7 +228,7 @@ mod tests { use crate::crypto::raw_signature::SigningAlg; #[test] - fn test_es512_not_supported() { + fn test_es512_supported() { let cert_chain = include_bytes!("../../../../../tests/fixtures/crypto/raw_signature/es512.pub"); let private_key = @@ -168,9 +243,9 @@ mod tests { time_stamp_service_url, ); - assert!(result.is_err()); - if let Err(RawSignerError::InvalidSigningCredentials(err_msg)) = result { - assert_eq!(err_msg, "Rust Crypto does not support Es512, only OpenSSL"); + assert!(result.is_ok()); + if let Ok(ecdsa_signer) = result { + assert_eq!(ecdsa_signer.alg(), SigningAlg::Es512); } else { unreachable!("Expected InvalidSigningCredentials error"); } diff --git a/sdk/src/crypto/raw_signature/rust_native/signers/mod.rs b/sdk/src/crypto/raw_signature/rust_native/signers/mod.rs index ac9f8b388..94277b8bb 100644 --- a/sdk/src/crypto/raw_signature/rust_native/signers/mod.rs +++ b/sdk/src/crypto/raw_signature/rust_native/signers/mod.rs @@ -52,7 +52,7 @@ pub(crate) fn signer_from_cert_chain_and_private_key( )?, )), - SigningAlg::Es256 | SigningAlg::Es384 => Ok(Box::new( + SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => Ok(Box::new( ecdsa_signer::EcdsaSigner::from_cert_chain_and_private_key( cert_chain, private_key, diff --git a/sdk/src/crypto/raw_signature/tests/async_signers.rs b/sdk/src/crypto/raw_signature/tests/async_signers.rs index f2694ee8b..3ba55c521 100644 --- a/sdk/src/crypto/raw_signature/tests/async_signers.rs +++ b/sdk/src/crypto/raw_signature/tests/async_signers.rs @@ -80,7 +80,12 @@ async fn es384() { validator.validate(&signature, data, pub_key).unwrap(); } -#[cfg_attr(feature = "openssl", actix::test)] +#[cfg_attr(not(target_arch = "wasm32"), actix::test)] +#[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test +)] +#[cfg_attr(target_os = "wasi", wstd::test)] async fn es512() { let cert_chain = include_bytes!("../../../../tests/fixtures/crypto/raw_signature/es512.pub"); let private_key = include_bytes!("../../../../tests/fixtures/crypto/raw_signature/es512.priv");