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");