diff --git a/Cargo.lock b/Cargo.lock
index a6c755447..82e49d06f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -818,7 +818,9 @@ dependencies = [
"openssl",
"p256",
"p384",
+ "p521",
"pkcs1",
+ "pkcs8",
"rand",
"rasn",
"rasn-ocsp",
@@ -1458,6 +1460,7 @@ dependencies = [
"digest",
"elliptic-curve",
"rfc6979",
+ "sha2",
"signature",
"spki",
]
@@ -3011,6 +3014,20 @@ dependencies = [
"sha2",
]
+[[package]]
+name = "p521"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
+dependencies = [
+ "base16ct",
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "rand_core 0.6.4",
+ "sha2",
+]
+
[[package]]
name = "parking"
version = "2.2.1"
diff --git a/internal/crypto/Cargo.toml b/internal/crypto/Cargo.toml
index 9b61b7ba0..d1e2da592 100644
--- a/internal/crypto/Cargo.toml
+++ b/internal/crypto/Cargo.toml
@@ -54,12 +54,17 @@ const-hex = "1.14"
const-oid = { version = "0.9.6", optional = true }
coset = "0.3.8"
der = { version = "0.7.9", optional = true }
+ecdsa = { version = "0.16.9", features = ["digest", "sha2"] }
ed25519-dalek = { version = "2.1.1", features = ["alloc", "digest", "pem", "pkcs8"], optional = true }
getrandom = { version = "0.2.7", features = ["js"] }
hex = "0.4.3"
nom = "7.1.3"
num-bigint-dig = { version = "0.8.4", optional = true }
+p256 = "0.13.2"
+p384 = "0.13.0"
+p521 = { version = "0.13.3", features = ["pkcs8", "digest", "ecdsa"] }
pkcs1 = { version = "0.7.5", optional = true }
+pkcs8 = "0.10.2"
rand = "0.8.5"
rasn = "0.22.0"
rasn-ocsp = "0.22.0"
@@ -98,10 +103,7 @@ features = ["now", "wasmbind"]
async-trait = "0.1.77"
const-oid = "0.9.6"
der = "0.7.9"
-ecdsa = "0.16.9"
ed25519-dalek = { version = "2.1.1", features = ["alloc", "digest", "pem", "pkcs8"] }
-p256 = "0.13.2"
-p384 = "0.13.0"
num-bigint-dig = "0.8.4"
pkcs1 = "0.7.5"
rsa = { version = "0.9.7", features = ["pem", "sha2"] }
diff --git a/internal/crypto/README.md b/internal/crypto/README.md
index 219ab2e2d..86520c568 100644
--- a/internal/crypto/README.md
+++ b/internal/crypto/README.md
@@ -15,7 +15,7 @@ This crate has two features, neither of which are enabled by default:
`c2pa-crypto` will use different cryptography libraries depending on which platform and feature flags are used:
-### Signing (synchronous or asynchronous)
+### Signing
| C2PA `SigningAlg` | Default (*) | `feature = "rust_native_crypto"` (*) | WASM |
| --- | --- | --- | --- |
@@ -30,32 +30,17 @@ This crate has two features, neither of which are enabled by default:
(*) Applies to all supported platforms except WASM
❌ = not supported
-### Validation (synchronous)
-
-| C2PA `SigningAlg` | Default (*) | `feature = "rust_native_crypto"` (*) | WASM |
-| --- | --- | --- | --- |
-| `es256` | OpenSSL | OpenSSL | `p256` |
-| `es384` | OpenSSL | OpenSSL | `p384` |
-| `es512` | OpenSSL | OpenSSL | ❌ |
-| `ed25519` | OpenSSL | `ed25519-dalek` | `ed25519-dalek` |
-| `ps256` | OpenSSL | `rsa` | `rsa` |
-| `ps384` | OpenSSL | `rsa` | `rsa` |
-| `ps512` | OpenSSL | `rsa` | `rsa` |
-
-(*) Applies to all supported platforms except WASM
-❌ = not supported
-
-### Validation (asynchronous)
-
-| C2PA `SigningAlg` | Default (*) | `feature = "rust_native_crypto"` (*) | WASM |
-| --- | --- | --- | --- |
-| `es256` | OpenSSL | OpenSSL | WebCrypto |
-| `es384` | OpenSSL | OpenSSL | WebCrypto |
-| `es512` | OpenSSL | OpenSSL | WebCrypto |
-| `ed25519` | OpenSSL | `ed25519-dalek` | `ed25519-dalek` |
-| `ps256` | OpenSSL | `rsa` | `rsa` |
-| `ps384` | OpenSSL | `rsa` | `rsa` |
-| `ps512` | OpenSSL | `rsa` | `rsa` |
+### Validation
+
+| 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
diff --git a/internal/crypto/src/cose/certificate_profile.rs b/internal/crypto/src/cose/certificate_profile.rs
index fa1cabbe7..a72d9363b 100644
--- a/internal/crypto/src/cose/certificate_profile.rs
+++ b/internal/crypto/src/cose/certificate_profile.rs
@@ -312,7 +312,6 @@ pub fn check_certificate_profile(
let mut aki_good = false;
let mut ski_good = false;
let mut key_usage_good = false;
- let mut handled_all_critical = true;
let extended_key_usage_good = match tbscert
.extended_key_usage()
.map_err(|_| CertificateProfileError::InvalidCertificate)?
@@ -417,20 +416,7 @@ pub fn check_certificate_profile(
ParsedExtension::CRLNumber(_) => (),
ParsedExtension::ReasonCode(_) => (),
ParsedExtension::InvalidityDate(_) => (),
-
- ParsedExtension::Unparsed => {
- if e.critical {
- // Unhandled critical extension.
- handled_all_critical = false;
- }
- }
-
- _ => {
- if e.critical {
- // Unhandled critical extension.
- handled_all_critical = false;
- }
- }
+ _ => (),
}
}
@@ -438,7 +424,7 @@ pub fn check_certificate_profile(
ski_good = if tbscert.is_ca() { ski_good } else { true };
// Check all flags.
- if aki_good && ski_good && key_usage_good && extended_key_usage_good && handled_all_critical {
+ if aki_good && ski_good && key_usage_good && extended_key_usage_good {
Ok(())
} else {
log_item!(
diff --git a/internal/crypto/src/cose/certificate_trust_policy.rs b/internal/crypto/src/cose/certificate_trust_policy.rs
index c6ed27072..0fb5cccb8 100644
--- a/internal/crypto/src/cose/certificate_trust_policy.rs
+++ b/internal/crypto/src/cose/certificate_trust_policy.rs
@@ -100,17 +100,14 @@ impl CertificateTrustPolicy {
return Ok(());
}
- if _async {
- #[cfg(target_arch = "wasm32")]
- {
- return crate::raw_signature::webcrypto::check_certificate_trust::check_certificate_trust(
- self,
- chain_der,
- end_entity_cert_der,
- signing_time_epoch,
- )
- .await;
- }
+ #[cfg(any(target_arch = "wasm32", feature = "rust_native_crypto", test))]
+ {
+ return crate::raw_signature::rust_native::check_certificate_trust::check_certificate_trust(
+ self,
+ chain_der,
+ end_entity_cert_der,
+ signing_time_epoch,
+ );
}
#[cfg(not(target_arch = "wasm32"))]
@@ -372,20 +369,6 @@ impl From for Certificat
}
}
-#[cfg(target_arch = "wasm32")]
-impl From for CertificateTrustError {
- fn from(err: crate::raw_signature::webcrypto::WasmCryptoError) -> Self {
- match err {
- crate::raw_signature::webcrypto::WasmCryptoError::UnknownContext => {
- Self::InternalError("unknown WASM context".to_string())
- }
- crate::raw_signature::webcrypto::WasmCryptoError::NoCryptoAvailable => {
- Self::InternalError("WASM crypto unavailable".to_string())
- }
- }
- }
-}
-
/// This error can occur when adding certificates to a
/// [`CertificateTrustPolicy`].
#[derive(Debug, Eq, PartialEq)]
diff --git a/internal/crypto/src/cose/sign.rs b/internal/crypto/src/cose/sign.rs
index a68b84bf3..6433d606d 100644
--- a/internal/crypto/src/cose/sign.rs
+++ b/internal/crypto/src/cose/sign.rs
@@ -20,9 +20,10 @@ use coset::{
};
use serde_bytes::ByteBuf;
+use super::cert_chain_from_sign1;
use crate::{
cose::{add_sigtst_header, add_sigtst_header_async, CoseError, TimeStampStorage},
- p1363::{der_to_p1363, parse_ec_der_sig},
+ ec_utils::{der_to_p1363, ec_curve_from_public_key_der, parse_ec_der_sig},
raw_signature::{AsyncRawSigner, RawSigner, SigningAlg},
};
@@ -154,7 +155,17 @@ pub fn sign_v1(
SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => {
if parse_ec_der_sig(&signature).is_ok() {
// Fix up DER signature to be in P1363 format.
- der_to_p1363(&signature, alg)?
+ let certs = cert_chain_from_sign1(&sign1)?;
+
+ let signing_cert = certs.first().ok_or(CoseError::CborGenerationError(
+ "bad certificate chain".to_string(),
+ ))?;
+
+ let curve = ec_curve_from_public_key_der(signing_cert).ok_or(
+ CoseError::CborGenerationError("incorrect EC signature format".to_string()),
+ )?;
+
+ der_to_p1363(&signature, curve.p1363_sig_len())?
} else {
signature
}
@@ -165,6 +176,7 @@ pub fn sign_v1(
// The payload is provided elsewhere, so we don't need to repeat it in the
// `Cose_Sign1` structure.
sign1.payload = None;
+
pad_cose_sig(&mut sign1, box_size)
}
@@ -217,7 +229,17 @@ pub fn sign_v2(
SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => {
if parse_ec_der_sig(&signature).is_ok() {
// Fix up DER signature to be in P1363 format.
- der_to_p1363(&signature, alg)?
+ let certs = cert_chain_from_sign1(&sign1)?;
+
+ let signing_cert = certs.first().ok_or(CoseError::CborGenerationError(
+ "bad certificate chain".to_string(),
+ ))?;
+
+ let curve = ec_curve_from_public_key_der(signing_cert).ok_or(
+ CoseError::CborGenerationError("incorrect EC signature format".to_string()),
+ )?;
+
+ der_to_p1363(&signature, curve.p1363_sig_len())?
} else {
signature
}
@@ -348,7 +370,7 @@ fn pad_cose_sig(sign1: &mut CoseSign1, end_size: Option) -> Result end_size {
return Err(CoseError::BoxSizeTooSmall);
}
diff --git a/internal/crypto/src/cose/verifier.rs b/internal/crypto/src/cose/verifier.rs
index b347334d2..bebcae953 100644
--- a/internal/crypto/src/cose/verifier.rs
+++ b/internal/crypto/src/cose/verifier.rs
@@ -11,7 +11,7 @@
// specific language governing permissions and limitations under
// each license.
-use std::io::Cursor;
+use std::io::Write;
use asn1_rs::FromDer;
use async_generic::async_generic;
@@ -28,12 +28,13 @@ use x509_parser::prelude::X509Certificate;
use crate::{
asn1::rfc3161::TstInfo,
+ base64::encode,
cose::{
cert_chain_from_sign1, check_certificate_profile, parse_cose_sign1, signing_alg_from_sign1,
validate_cose_tst_info, validate_cose_tst_info_async, CertificateInfo,
CertificateTrustError, CertificateTrustPolicy, CoseError,
},
- p1363::parse_ec_der_sig,
+ ec_utils::parse_ec_der_sig,
raw_signature::{validator_for_signing_alg, SigningAlg},
time_stamp::TimeStampError,
};
@@ -93,9 +94,13 @@ impl Verifier<'_> {
SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => {
if parse_ec_der_sig(&sign1.signature).is_ok() {
// Should have been in P1363 format, not DER.
- log_item!("Cose_Sign1", "unsupported signature format", "verify_cose")
- .validation_status(SIGNING_CREDENTIAL_INVALID)
- .failure_no_throw(validation_log, CoseError::InvalidEcdsaSignature);
+ log_item!(
+ "Cose_Sign1",
+ "unsupported signature format (EC signature should be in P1363 r|s format)",
+ "verify_cose"
+ )
+ .validation_status(SIGNING_CREDENTIAL_INVALID)
+ .failure_no_throw(validation_log, CoseError::InvalidEcdsaSignature);
// validation_log.log(log_item, CoseError::InvalidEcdsaSignature)?;
return Err(CoseError::InvalidEcdsaSignature);
@@ -313,18 +318,37 @@ impl Verifier<'_> {
}
fn dump_cert_chain(certs: &[Vec]) -> Result, CoseError> {
- let mut out_buf: Vec = Vec::new();
- let mut writer = Cursor::new(out_buf);
+ let mut writer = Vec::new();
+
+ let line_len = 64;
+ let cert_begin = "-----BEGIN CERTIFICATE-----";
+ let cert_end = "-----END CERTIFICATE-----";
for der_bytes in certs {
- let c = x509_certificate::X509Certificate::from_der(der_bytes)
- .map_err(|_e| CoseError::CborParsingError("invalid X509 certificate".to_string()))?;
+ let cert_base_str = encode(der_bytes);
+
+ // Break line into fixed-length lines.
+ let cert_lines = cert_base_str
+ .chars()
+ .collect::>()
+ .chunks(line_len)
+ .map(|chunk| chunk.iter().collect::())
+ .collect::>();
+
+ writer
+ .write_fmt(format_args!("{}\n", cert_begin))
+ .map_err(|_e| CoseError::InternalError("could not write PEM".to_string()))?;
+
+ for l in cert_lines {
+ writer
+ .write_fmt(format_args!("{}\n", l))
+ .map_err(|_e| CoseError::InternalError("could not write PEM".to_string()))?;
+ }
- c.write_pem(&mut writer).map_err(|_| {
- CoseError::InternalError("I/O error constructing cert_chain dump".to_string())
- })?;
+ writer
+ .write_fmt(format_args!("{}\n", cert_end))
+ .map_err(|_e| CoseError::InternalError("could not write PEM".to_string()))?;
}
- out_buf = writer.into_inner();
- Ok(out_buf)
+ Ok(writer)
}
diff --git a/internal/crypto/src/ec_utils.rs b/internal/crypto/src/ec_utils.rs
new file mode 100644
index 000000000..b15ae305f
--- /dev/null
+++ b/internal/crypto/src/ec_utils.rs
@@ -0,0 +1,172 @@
+// Copyright 2025 Adobe. All rights reserved.
+// This file is licensed to you under the Apache License,
+// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+// or the MIT license (http://opensource.org/licenses/MIT),
+// at your option.
+
+// Unless required by applicable law or agreed to in writing,
+// this software is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
+// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
+// specific language governing permissions and limitations under
+// each license.
+
+//! Utilities for working with the EC signatures used by C2PA in ECDSA
+//! signatures.
+
+use asn1_rs::{FromDer, ToDer};
+use pkcs8::PrivateKeyInfo;
+use x509_parser::{
+ der_parser::{
+ der::{parse_der_integer, parse_der_sequence_defined_g},
+ error::BerResult,
+ },
+ x509::SubjectPublicKeyInfo,
+};
+
+use crate::raw_signature::{
+ oids::{EC_PUBLICKEY_OID, PRIME256V1_OID, SECP384R1_OID, SECP521R1_OID},
+ RawSignerError,
+};
+
+/// NIST curves supported by `EcdsaValidator`.
+pub(crate) enum EcdsaCurve {
+ /// NIST curve P-256
+ P256,
+
+ /// NIST curve P-384
+ P384,
+
+ /// NIST curve P-521
+ P521,
+}
+
+impl EcdsaCurve {
+ // Returns the P1363 r|s signature size for a given curve.
+ pub fn p1363_sig_len(&self) -> usize {
+ match self {
+ EcdsaCurve::P256 => 64,
+ EcdsaCurve::P384 => 96,
+ EcdsaCurve::P521 => 132,
+ }
+ }
+}
+
+/// Parse an ASN.1 DER object that contains a P1363 format into its components.
+///
+/// This format is used by C2PA to describe ECDSA signature keys.
+pub(crate) fn parse_ec_der_sig(data: &[u8]) -> BerResult {
+ parse_der_sequence_defined_g(|content: &[u8], _| {
+ let (rem1, r) = parse_der_integer(content)?;
+ let (_rem2, s) = parse_der_integer(rem1)?;
+
+ Ok((
+ data,
+ EcSigComps {
+ r: r.as_slice()?,
+ s: s.as_slice()?,
+ },
+ ))
+ })(data)
+}
+
+pub(crate) struct EcSigComps<'a> {
+ pub r: &'a [u8],
+ pub s: &'a [u8],
+}
+
+pub(crate) fn der_to_p1363(data: &[u8], sig_len: usize) -> Result, RawSignerError> {
+ // P1363 format: r | s
+
+ let (_, p) = parse_ec_der_sig(data)
+ .map_err(|err| RawSignerError::InternalError(format!("invalid DER signature: {err}")))?;
+
+ let mut r = const_hex::encode(p.r);
+ let mut s = const_hex::encode(p.s);
+
+ // Check against the supported signature sizes.
+ if ![64usize, 96, 132].contains(&sig_len) {
+ return Err(RawSignerError::InternalError(
+ "unsupported algorithm for der_to_p1363".to_string(),
+ ));
+ }
+
+ // Pad or truncate as needed.
+ let rp = if r.len() > sig_len {
+ let offset = r.len() - sig_len;
+ &r[offset..r.len()]
+ } else {
+ while r.len() != sig_len {
+ r.insert(0, '0');
+ }
+ r.as_ref()
+ };
+
+ let sp = if s.len() > sig_len {
+ let offset = s.len() - sig_len;
+ &s[offset..s.len()]
+ } else {
+ while s.len() != sig_len {
+ s.insert(0, '0');
+ }
+ s.as_ref()
+ };
+
+ if rp.len() != sig_len || rp.len() != sp.len() {
+ return Err(RawSignerError::InternalError(
+ "invalid signature components".to_string(),
+ ));
+ }
+
+ // Merge r and s strings.
+ let new_sig = format!("{rp}{sp}");
+
+ // Convert back from hex string to byte array.
+ const_hex::decode(&new_sig)
+ .map_err(|e| RawSignerError::InternalError(format!("invalid signature components {e}")))
+}
+
+// Returns supported EcdsaCurve for given public key.
+pub(crate) fn ec_curve_from_public_key_der(public_key: &[u8]) -> Option {
+ let (_, pk) = SubjectPublicKeyInfo::from_der(public_key).ok()?;
+
+ let public_key_alg = &pk.algorithm;
+
+ if public_key_alg.algorithm == EC_PUBLICKEY_OID {
+ if let Some(parameters) = &public_key_alg.parameters {
+ let named_curve_oid = parameters.as_oid().ok()?;
+
+ // Find supported curve.
+ if named_curve_oid == PRIME256V1_OID {
+ return Some(EcdsaCurve::P256);
+ } else if named_curve_oid == SECP384R1_OID {
+ return Some(EcdsaCurve::P384);
+ } else if named_curve_oid == SECP521R1_OID {
+ return Some(EcdsaCurve::P521);
+ }
+ }
+ }
+
+ None
+}
+
+// Returns supported EcdsaCurve for given private key.
+#[allow(dead_code)] // not used on WASM builds
+pub(crate) fn ec_curve_from_private_key_der(private_key: &[u8]) -> Option {
+ use pkcs8::der::Decode;
+ let ec_key = PrivateKeyInfo::from_der(private_key).ok()?;
+
+ let p256_oid = pkcs8::ObjectIdentifier::from_der(&PRIME256V1_OID.to_der_vec().ok()?).ok()?;
+ let p384_oid = pkcs8::ObjectIdentifier::from_der(&SECP384R1_OID.to_der_vec().ok()?).ok()?;
+ let p521_oid = pkcs8::ObjectIdentifier::from_der(&SECP521R1_OID.to_der_vec().ok()?).ok()?;
+
+ if ec_key.algorithm.assert_parameters_oid(p256_oid).is_ok() {
+ return Some(EcdsaCurve::P256);
+ } else if ec_key.algorithm.assert_parameters_oid(p384_oid).is_ok() {
+ return Some(EcdsaCurve::P384);
+ } else if ec_key.algorithm.assert_parameters_oid(p521_oid).is_ok() {
+ return Some(EcdsaCurve::P521);
+ }
+
+ None
+}
diff --git a/internal/crypto/src/lib.rs b/internal/crypto/src/lib.rs
index 70ed1e460..9734808e1 100644
--- a/internal/crypto/src/lib.rs
+++ b/internal/crypto/src/lib.rs
@@ -22,11 +22,10 @@
pub(crate) mod asn1;
pub mod base64;
pub mod cose;
+pub(crate) mod ec_utils;
pub mod hash;
pub(crate) mod internal;
pub mod ocsp;
-
-pub(crate) mod p1363;
pub mod raw_signature;
pub mod time_stamp;
diff --git a/internal/crypto/src/p1363.rs b/internal/crypto/src/p1363.rs
deleted file mode 100644
index 0baf5a848..000000000
--- a/internal/crypto/src/p1363.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2024 Adobe. All rights reserved.
-// This file is licensed to you under the Apache License,
-// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
-// or the MIT license (http://opensource.org/licenses/MIT),
-// at your option.
-
-// Unless required by applicable law or agreed to in writing,
-// this software is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
-// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
-// specific language governing permissions and limitations under
-// each license.
-
-//! Utilities for working with the P1363 signature format used by C2PA in ECDSA
-//! signatures.
-
-use x509_parser::der_parser::{
- der::{parse_der_integer, parse_der_sequence_defined_g},
- error::BerResult,
-};
-
-use crate::raw_signature::{RawSignerError, SigningAlg};
-
-/// Parse an ASN.1 DER object that contains a P1363 format into its components.
-///
-/// This format is used by C2PA to describe ECDSA signature keys.
-pub fn parse_ec_der_sig(data: &[u8]) -> BerResult {
- parse_der_sequence_defined_g(|content: &[u8], _| {
- let (rem1, r) = parse_der_integer(content)?;
- let (_rem2, s) = parse_der_integer(rem1)?;
-
- Ok((
- data,
- EcSigComps {
- r: r.as_slice()?,
- s: s.as_slice()?,
- },
- ))
- })(data)
-}
-
-/// Component data for ECDSA signature components.
-#[allow(missing_docs)]
-pub struct EcSigComps<'a> {
- pub r: &'a [u8],
- pub s: &'a [u8],
-}
-
-pub(crate) fn der_to_p1363(data: &[u8], alg: SigningAlg) -> Result, RawSignerError> {
- // P1363 format: r | s
-
- let (_, p) = parse_ec_der_sig(data)
- .map_err(|err| RawSignerError::InternalError(format!("invalid DER signature: {err}")))?;
-
- let mut r = const_hex::encode(p.r);
- let mut s = const_hex::encode(p.s);
-
- let sig_len: usize = match alg {
- SigningAlg::Es256 => 64,
- SigningAlg::Es384 => 96,
- SigningAlg::Es512 => 132,
- _ => {
- return Err(RawSignerError::InternalError(
- "unsupported algorithm for der_to_p1363".to_string(),
- ))
- }
- };
-
- // Pad or truncate as needed.
- let rp = if r.len() > sig_len {
- let offset = r.len() - sig_len;
- &r[offset..r.len()]
- } else {
- while r.len() != sig_len {
- r.insert(0, '0');
- }
- r.as_ref()
- };
-
- let sp = if s.len() > sig_len {
- let offset = s.len() - sig_len;
- &s[offset..s.len()]
- } else {
- while s.len() != sig_len {
- s.insert(0, '0');
- }
- s.as_ref()
- };
-
- if rp.len() != sig_len || rp.len() != sp.len() {
- return Err(RawSignerError::InternalError(
- "invalid signature components".to_string(),
- ));
- }
-
- // Merge r and s strings.
- let new_sig = format!("{rp}{sp}");
-
- // Convert back from hex string to byte array.
- const_hex::decode(&new_sig)
- .map_err(|e| RawSignerError::InternalError(format!("invalid signature components {e}")))
-}
diff --git a/internal/crypto/src/raw_signature/mod.rs b/internal/crypto/src/raw_signature/mod.rs
index f3dbb93dd..1755eb1ba 100644
--- a/internal/crypto/src/raw_signature/mod.rs
+++ b/internal/crypto/src/raw_signature/mod.rs
@@ -32,12 +32,11 @@ pub use signing_alg::{SigningAlg, UnknownAlgorithmError};
mod validator;
#[cfg(target_arch = "wasm32")]
+pub(crate) use validator::async_validator_for_sig_and_hash_algs;
+#[cfg(target_arch = "wasm32")]
pub use validator::async_validator_for_signing_alg;
pub(crate) use validator::validator_for_sig_and_hash_algs;
pub use validator::{
validator_for_signing_alg, AsyncRawSignatureValidator, RawSignatureValidationError,
RawSignatureValidator,
};
-
-#[cfg(target_arch = "wasm32")]
-pub mod webcrypto;
diff --git a/internal/crypto/src/raw_signature/oids.rs b/internal/crypto/src/raw_signature/oids.rs
index e50068c96..03a5e5b52 100644
--- a/internal/crypto/src/raw_signature/oids.rs
+++ b/internal/crypto/src/raw_signature/oids.rs
@@ -32,4 +32,8 @@ pub(crate) const ECDSA_WITH_SHA256_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3
pub(crate) const ECDSA_WITH_SHA384_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .3);
pub(crate) const ECDSA_WITH_SHA512_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .4);
+pub(crate) const SECP521R1_OID: Oid<'static> = oid!(1.3.132 .0 .35);
+pub(crate) const SECP384R1_OID: Oid<'static> = oid!(1.3.132 .0 .34);
+pub(crate) const PRIME256V1_OID: Oid<'static> = oid!(1.2.840 .10045 .3 .1 .7);
+
pub(crate) const ED25519_OID: Oid<'static> = oid!(1.3.101 .112);
diff --git a/internal/crypto/src/raw_signature/openssl/signers/ecdsa_signer.rs b/internal/crypto/src/raw_signature/openssl/signers/ecdsa_signer.rs
index 81013c747..6431762b2 100644
--- a/internal/crypto/src/raw_signature/openssl/signers/ecdsa_signer.rs
+++ b/internal/crypto/src/raw_signature/openssl/signers/ecdsa_signer.rs
@@ -20,7 +20,7 @@ use openssl::{
};
use crate::{
- p1363::der_to_p1363,
+ ec_utils::{der_to_p1363, ec_curve_from_private_key_der},
raw_signature::{
openssl::{cert_chain::check_chain_order, OpenSslMutex},
RawSigner, RawSignerError, SigningAlg,
@@ -97,6 +97,16 @@ impl RawSigner for EcdsaSigner {
let private_key = PKey::from_ec_key(self.private_key.clone())?;
+ let pkcs8_private_key = private_key.private_key_to_pkcs8().map_err(|_| {
+ RawSignerError::InvalidSigningCredentials("unsupported EC curve".to_string())
+ })?;
+
+ let curve = ec_curve_from_private_key_der(&pkcs8_private_key).ok_or(
+ RawSignerError::InvalidSigningCredentials("unsupported EC curve".to_string()),
+ )?;
+
+ let sig_len = curve.p1363_sig_len();
+
let mut signer = match self.alg {
EcdsaSigningAlg::Es256 => Signer::new(MessageDigest::sha256(), &private_key)?,
EcdsaSigningAlg::Es384 => Signer::new(MessageDigest::sha384(), &private_key)?,
@@ -106,7 +116,7 @@ impl RawSigner for EcdsaSigner {
signer.update(data)?;
let der_sig = signer.sign_to_vec()?;
- der_to_p1363(&der_sig, self.alg())
+ der_to_p1363(&der_sig, sig_len)
}
fn alg(&self) -> SigningAlg {
diff --git a/internal/crypto/src/raw_signature/openssl/validators/mod.rs b/internal/crypto/src/raw_signature/openssl/validators/mod.rs
index 815a9ec2a..c128066af 100644
--- a/internal/crypto/src/raw_signature/openssl/validators/mod.rs
+++ b/internal/crypto/src/raw_signature/openssl/validators/mod.rs
@@ -47,11 +47,7 @@ pub(crate) fn validator_for_sig_and_hash_algs(
sig_alg: &Oid,
hash_alg: &Oid,
) -> Option> {
- if sig_alg.as_ref() == RSA_OID.as_bytes()
- || sig_alg.as_ref() == SHA256_WITH_RSAENCRYPTION_OID.as_bytes()
- || sig_alg.as_ref() == SHA384_WITH_RSAENCRYPTION_OID.as_bytes()
- || sig_alg.as_ref() == SHA512_WITH_RSAENCRYPTION_OID.as_bytes()
- {
+ if sig_alg.as_ref() == RSA_OID.as_bytes() {
if hash_alg.as_ref() == SHA1_OID.as_bytes() {
return Some(Box::new(RsaLegacyValidator::Sha1));
} else if hash_alg.as_ref() == SHA256_OID.as_bytes() {
@@ -63,5 +59,34 @@ pub(crate) fn validator_for_sig_and_hash_algs(
}
}
+ // Handle RSS-PSS.
+ if sig_alg.as_ref() == RSA_PSS_OID.as_bytes() {
+ if hash_alg.as_ref() == SHA256_OID.as_bytes() {
+ return Some(Box::new(RsaValidator::Ps256));
+ } else if hash_alg.as_ref() == SHA384_OID.as_bytes() {
+ return Some(Box::new(RsaValidator::Ps384));
+ } else if hash_alg.as_ref() == SHA512_OID.as_bytes() {
+ return Some(Box::new(RsaValidator::Ps512));
+ }
+ }
+
+ // Handle elliptical curve and hash combinations.
+ // Handle elliptical curve and hash combinations.
+ // Handle elliptical curve and hash combinations.
+ if sig_alg.as_ref() == EC_PUBLICKEY_OID.as_bytes() {
+ if hash_alg.as_ref() == SHA256_OID.as_bytes() {
+ return Some(Box::new(EcdsaValidator::Es256));
+ } else if hash_alg.as_ref() == SHA384_OID.as_bytes() {
+ return Some(Box::new(EcdsaValidator::Es384));
+ } else if hash_alg.as_ref() == SHA512_OID.as_bytes() {
+ return Some(Box::new(EcdsaValidator::Es512));
+ }
+ }
+
+ // Handle ED25519.
+ if sig_alg.as_ref() == ED25519_OID.as_bytes() {
+ return Some(Box::new(Ed25519Validator {}));
+ }
+
None
}
diff --git a/internal/crypto/src/raw_signature/webcrypto/check_certificate_trust.rs b/internal/crypto/src/raw_signature/rust_native/check_certificate_trust.rs
similarity index 68%
rename from internal/crypto/src/raw_signature/webcrypto/check_certificate_trust.rs
rename to internal/crypto/src/raw_signature/rust_native/check_certificate_trust.rs
index eff1e40cd..ee3e5d6f8 100644
--- a/internal/crypto/src/raw_signature/webcrypto/check_certificate_trust.rs
+++ b/internal/crypto/src/raw_signature/rust_native/check_certificate_trust.rs
@@ -11,26 +11,25 @@
// specific language governing permissions and limitations under
// each license.
+use std::str::FromStr;
+
use asn1_rs::{Any, Class, FromDer, Header, Tag};
use nom::AsBytes;
use x509_parser::{
certificate::X509Certificate, der_parser::oid, oid_registry::Oid, x509::AlgorithmIdentifier,
};
+use super::validators::validator_for_sig_and_hash_algs;
use crate::{
cose::{CertificateTrustError, CertificateTrustPolicy},
- p1363::der_to_p1363,
- raw_signature::{
- validator_for_signing_alg, webcrypto::async_validator_for_signing_alg,
- RawSignatureValidationError, SigningAlg,
- },
+ raw_signature::{oids::*, RawSignatureValidationError, SigningAlg},
};
-pub(crate) async fn check_certificate_trust(
+pub(crate) fn check_certificate_trust(
ctp: &CertificateTrustPolicy,
chain_der: &[Vec],
cert_der: &[u8],
- _signing_time_epoch: Option,
+ signing_time_epoch: Option,
) -> Result<(), CertificateTrustError> {
let Ok((_rem, cert)) = X509Certificate::from_der(cert_der) else {
return Err(CertificateTrustError::InvalidCertificate);
@@ -40,12 +39,12 @@ pub(crate) async fn check_certificate_trust(
return Err(CertificateTrustError::InvalidEku);
};
- let Some(_approved_oid) = ctp.has_allowed_eku(&eku.value) else {
+ let Some(_approved_oid) = ctp.has_allowed_eku(eku.value) else {
return Err(CertificateTrustError::InvalidEku);
};
// Add end-entity cert to the chain if not already there.
- let full_chain = if !chain_der.is_empty() && cert_der == &chain_der[0] {
+ let full_chain = if !chain_der.is_empty() && cert_der == chain_der[0] {
chain_der.to_vec()
} else {
let mut full_chain: Vec> = Vec::new();
@@ -56,7 +55,7 @@ pub(crate) async fn check_certificate_trust(
};
// Make sure chain is in the correct order and valid.
- check_chain_order(&full_chain).await?;
+ check_chain_order(&full_chain)?;
// Build anchors and check against trust anchors.
let anchors = ctp
@@ -77,6 +76,16 @@ pub(crate) async fn check_certificate_trust(
let (_, chain_cert) = X509Certificate::from_der(cert)
.map_err(|_e| CertificateTrustError::CertificateNotTrusted)?;
+ // Make sure the certificate was not expired.
+ if let Some(signing_time) = signing_time_epoch {
+ if !chain_cert.validity().is_valid_at(
+ x509_parser::time::ASN1Time::from_timestamp(signing_time)
+ .map_err(|_| CertificateTrustError::CertificateNotTrusted)?,
+ ) {
+ return Err(CertificateTrustError::CertificateNotTrusted);
+ }
+ }
+
for anchor in ctp.trust_anchor_ders() {
let data = chain_cert.tbs_certificate.as_ref();
let sig = chain_cert.signature_value.as_ref();
@@ -87,8 +96,7 @@ pub(crate) async fn check_certificate_trust(
.map_err(|_e| CertificateTrustError::CertificateNotTrusted)?;
if chain_cert.issuer() == anchor_cert.subject() {
- let result =
- verify_data(anchor.clone(), sig_alg, sig.to_vec(), data.to_vec()).await;
+ let result = verify_data(anchor.clone(), sig_alg, sig.to_vec(), data.to_vec());
match result {
Ok(b) => {
@@ -102,11 +110,10 @@ pub(crate) async fn check_certificate_trust(
}
}
- // TO DO: Consider path check and names restrictions.
- return Err(CertificateTrustError::CertificateNotTrusted);
+ Err(CertificateTrustError::CertificateNotTrusted)
}
-async fn check_chain_order(certs: &[Vec]) -> Result<(), CertificateTrustError> {
+fn check_chain_order(certs: &[Vec]) -> Result<(), CertificateTrustError> {
let chain_length = certs.len();
if chain_length < 2 {
return Ok(());
@@ -121,7 +128,7 @@ async fn check_chain_order(certs: &[Vec]) -> Result<(), CertificateTrustErro
let sig = current_cert.signature_value.as_ref();
let sig_alg = cert_signing_alg(¤t_cert);
- if !verify_data(issuer_der, sig_alg, sig.to_vec(), data.to_vec()).await? {
+ if !verify_data(issuer_der, sig_alg, sig.to_vec(), data.to_vec())? {
return Err(CertificateTrustError::CertificateNotTrusted);
}
}
@@ -129,6 +136,67 @@ async fn check_chain_order(certs: &[Vec]) -> Result<(), CertificateTrustErro
Ok(())
}
+fn ans1_oid_bcder_oid(asn1_oid: &asn1_rs::Oid) -> Option {
+ let asn1_oid_str = asn1_oid.to_id_string();
+ bcder::Oid::from_str(&asn1_oid_str).ok()
+}
+
+fn signing_alg_to_sig_and_hash_oid(alg: &str) -> Option<(bcder::Oid, bcder::Oid)> {
+ if alg == "rsa256" {
+ Some((
+ ans1_oid_bcder_oid(&RSA_OID)?,
+ ans1_oid_bcder_oid(&SHA256_OID)?,
+ ))
+ } else if alg == "rsa384" {
+ Some((
+ ans1_oid_bcder_oid(&RSA_OID)?,
+ ans1_oid_bcder_oid(&SHA384_OID)?,
+ ))
+ } else if alg == "rsa512" {
+ Some((
+ ans1_oid_bcder_oid(&RSA_OID)?,
+ ans1_oid_bcder_oid(&SHA512_OID)?,
+ ))
+ } else if alg == "es256" {
+ Some((
+ ans1_oid_bcder_oid(&EC_PUBLICKEY_OID)?,
+ ans1_oid_bcder_oid(&SHA256_OID)?,
+ ))
+ } else if alg == "es384" {
+ Some((
+ ans1_oid_bcder_oid(&EC_PUBLICKEY_OID)?,
+ ans1_oid_bcder_oid(&SHA384_OID)?,
+ ))
+ } else if alg == "es512" {
+ Some((
+ ans1_oid_bcder_oid(&EC_PUBLICKEY_OID)?,
+ ans1_oid_bcder_oid(&SHA512_OID)?,
+ ))
+ } else if alg == "ps256" {
+ Some((
+ ans1_oid_bcder_oid(&RSA_PSS_OID)?,
+ ans1_oid_bcder_oid(&SHA256_OID)?,
+ ))
+ } else if alg == "ps384" {
+ Some((
+ ans1_oid_bcder_oid(&RSA_PSS_OID)?,
+ ans1_oid_bcder_oid(&SHA384_OID)?,
+ ))
+ } else if alg == "ps512" {
+ Some((
+ ans1_oid_bcder_oid(&RSA_PSS_OID)?,
+ ans1_oid_bcder_oid(&SHA512_OID)?,
+ ))
+ } else if alg == "ed25519" {
+ Some((
+ ans1_oid_bcder_oid(&ED25519_OID)?,
+ ans1_oid_bcder_oid(&SHA512_OID)?,
+ ))
+ } else {
+ None
+ }
+}
+
fn cert_signing_alg(cert: &X509Certificate) -> Option {
let cert_alg = &cert.signature_algorithm.algorithm;
@@ -144,7 +212,7 @@ fn cert_signing_alg(cert: &X509Certificate) -> Option {
Some(SigningAlg::Es384.to_string())
} else if *cert_alg == ECDSA_WITH_SHA512_OID {
Some(SigningAlg::Es512.to_string())
- } else if *cert_alg == RSASSA_PSS_OID {
+ } else if *cert_alg == RSA_PSS_OID {
signing_alg_from_rsapss_alg(&cert.signature_algorithm)
} else if *cert_alg == ED25519_OID {
Some(SigningAlg::Ed25519.to_string())
@@ -186,9 +254,7 @@ fn signing_alg_from_rsapss_alg(alg: &AlgorithmIdentifier) -> Option {
return None;
};
- let Some(mgf_ai_parameters) = mgf_ai.parameters else {
- return None;
- };
+ let mgf_ai_parameters = mgf_ai.parameters?;
let Ok(mgf_ai_parameters) = mgf_ai_parameters.as_sequence() else {
return None;
@@ -221,7 +287,7 @@ fn signing_alg_from_rsapss_alg(alg: &AlgorithmIdentifier) -> Option {
}
}
-async fn verify_data(
+fn verify_data(
cert_der: Vec,
sig_alg: Option,
sig: Vec,
@@ -236,35 +302,13 @@ async fn verify_data(
return Err(CertificateTrustError::InvalidCertificate);
};
- let signing_alg: SigningAlg = cert_alg_string
- .parse()
- .map_err(|_| CertificateTrustError::InvalidCertificate)?;
-
- // Not sure this is needed any more. Leaving this for now, but I think this
- // should be handled in c2pa_crypto's raw signature code.
-
- // TO REVIEW: For now, this is needed because this function could validate C2PA
- // signatures (P1363) or those from certificates which are ASN.1 DER. I don't
- // know if the new code is only used for DER now.
- let adjusted_sig = if cert_alg_string.starts_with("es") {
- match der_to_p1363(&sig, signing_alg) {
- Ok(p1363) => p1363,
- Err(_) => sig,
- }
- } else {
- sig
- };
+ let (sig_alg, hash_alg) = signing_alg_to_sig_and_hash_oid(&cert_alg_string)
+ .ok_or(CertificateTrustError::InvalidCertificate)?;
- let result = if let Some(validator) = async_validator_for_signing_alg(signing_alg) {
- validator
- .validate_async(&adjusted_sig, &data, certificate_public_key.raw.as_ref())
- .await
+ let result = if let Some(validator) = validator_for_sig_and_hash_algs(&sig_alg, &hash_alg) {
+ validator.validate(&sig, &data, certificate_public_key.raw.as_ref())
} else {
- if let Some(validator) = validator_for_signing_alg(signing_alg) {
- validator.validate(&adjusted_sig, &data, certificate_public_key.raw.as_ref())
- } else {
- return Err(CertificateTrustError::InvalidCertificate);
- }
+ return Err(CertificateTrustError::InvalidCertificate);
};
match result {
@@ -273,15 +317,3 @@ async fn verify_data(
Err(_err) => Err(CertificateTrustError::InvalidCertificate),
}
}
-
-const RSASSA_PSS_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .10);
-const ECDSA_WITH_SHA256_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .2);
-const ECDSA_WITH_SHA384_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .3);
-const ECDSA_WITH_SHA512_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .4);
-const SHA256_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .11);
-const SHA384_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .12);
-const SHA512_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .13);
-const ED25519_OID: Oid<'static> = oid!(1.3.101 .112);
-const SHA256_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .1);
-const SHA384_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .2);
-const SHA512_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .3);
diff --git a/internal/crypto/src/raw_signature/rust_native/mod.rs b/internal/crypto/src/raw_signature/rust_native/mod.rs
index dce099230..e9ead76d9 100644
--- a/internal/crypto/src/raw_signature/rust_native/mod.rs
+++ b/internal/crypto/src/raw_signature/rust_native/mod.rs
@@ -20,5 +20,6 @@
//! At the moment, does not offer support for all C2PA-supported cryptography
//! algorithms.
+pub(crate) mod check_certificate_trust;
pub(crate) mod signers;
pub(crate) mod validators;
diff --git a/internal/crypto/src/raw_signature/rust_native/validators/ecdsa_validator.rs b/internal/crypto/src/raw_signature/rust_native/validators/ecdsa_validator.rs
new file mode 100644
index 000000000..82c8420d7
--- /dev/null
+++ b/internal/crypto/src/raw_signature/rust_native/validators/ecdsa_validator.rs
@@ -0,0 +1,129 @@
+// Copyright 2025 Adobe. All rights reserved.
+// This file is licensed to you under the Apache License,
+// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+// or the MIT license (http://opensource.org/licenses/MIT),
+// at your option.
+
+// Unless required by applicable law or agreed to in writing,
+// this software is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
+// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
+// specific language governing permissions and limitations under
+// each license.
+
+use std::str::FromStr;
+
+use bcder::Oid;
+use der::Decode;
+use ecdsa::{
+ signature::{hazmat::PrehashVerifier, Verifier as EcdsaVerifier},
+ Signature as EcdsaSignature, SignatureBytes, SignatureWithOid, ECDSA_SHA256_OID,
+};
+use p256::{ecdsa::VerifyingKey as P256VerifyingKey, NistP256, PublicKey as P256PublicKey};
+use p384::{ecdsa::VerifyingKey as P384VerifyingKey, NistP384, PublicKey as P384PublicKey};
+use p521::{ecdsa::VerifyingKey as P521VerifyingKey, NistP521, PublicKey as P521PublicKey};
+use sha2::{Digest, Sha256, Sha384, Sha512};
+
+use crate::{
+ ec_utils::{der_to_p1363, ec_curve_from_public_key_der, EcdsaCurve},
+ raw_signature::{oids::*, RawSignatureValidationError, RawSignatureValidator, SigningAlg},
+};
+
+/// An `EcdsaValidator` can validate raw signatures with one of the ECDSA
+/// signature algorithms.
+pub enum EcdsaValidator {
+ /// ECDSA with SHA-256
+ Es256,
+
+ /// ECDSA with SHA-384
+ Es384,
+
+ /// ECDSA with SHA-512
+ Es512,
+}
+
+impl RawSignatureValidator for EcdsaValidator {
+ fn validate(
+ &self,
+ sig: &[u8],
+ data: &[u8],
+ public_key: &[u8],
+ ) -> Result<(), RawSignatureValidationError> {
+ let digest = match self {
+ EcdsaValidator::Es256 => {
+ let mut hasher = Sha256::new();
+ hasher.update(data);
+ hasher.finalize().to_vec()
+ }
+
+ EcdsaValidator::Es384 => {
+ let mut hasher = Sha384::new();
+ hasher.update(data);
+ hasher.finalize().to_vec()
+ }
+
+ EcdsaValidator::Es512 => {
+ let mut hasher = Sha512::new();
+ hasher.update(data);
+ hasher.finalize().to_vec()
+ }
+ };
+
+ // Determine curve from public key.
+ let curve = ec_curve_from_public_key_der(public_key)
+ .ok_or(RawSignatureValidationError::InvalidPublicKey)?;
+
+ // Requires fixed-size P1363 signature.
+ let adjusted_sig = match der_to_p1363(sig, curve.p1363_sig_len()) {
+ Ok(p1363) => p1363,
+ Err(_) => sig.to_vec(),
+ };
+
+ let result = match curve {
+ EcdsaCurve::P256 => {
+ use p256::pkcs8::DecodePublicKey;
+ let signature = EcdsaSignature::from_slice(&adjusted_sig)
+ .map_err(|_| RawSignatureValidationError::InvalidSignature)?;
+
+ let vk = P256VerifyingKey::from_public_key_der(public_key)
+ .map_err(|_| RawSignatureValidationError::InvalidPublicKey)?;
+
+ vk.verify_prehash(&digest, &signature)
+ }
+
+ EcdsaCurve::P384 => {
+ use p384::pkcs8::DecodePublicKey;
+ let signature = EcdsaSignature::from_slice(&adjusted_sig)
+ .map_err(|_| RawSignatureValidationError::InvalidSignature)?;
+
+ let vk = P384VerifyingKey::from_public_key_der(public_key)
+ .map_err(|_| RawSignatureValidationError::InvalidPublicKey)?;
+
+ vk.verify_prehash(&digest, &signature)
+ }
+
+ EcdsaCurve::P521 => {
+ use p521::pkcs8::DecodePublicKey;
+ let signature = EcdsaSignature::from_slice(&adjusted_sig)
+ .map_err(|_| RawSignatureValidationError::InvalidSignature)?;
+
+ // P521VerifyingKey does't have an implementation of `from_public_key` so we
+ // load it manually.
+ let pk = P521PublicKey::from_public_key_der(public_key)
+ .map_err(|_| RawSignatureValidationError::InvalidPublicKey)?;
+
+ let pk_bytes = pk.to_sec1_bytes();
+
+ let vk = P521VerifyingKey::from_sec1_bytes(&pk_bytes)
+ .map_err(|_| RawSignatureValidationError::InvalidPublicKey)?;
+
+ vk.verify_prehash(&digest, &signature)
+ }
+ };
+
+ match result {
+ Ok(_) => Ok(()),
+ Err(err) => Err(RawSignatureValidationError::SignatureMismatch),
+ }
+ }
+}
diff --git a/internal/crypto/src/raw_signature/rust_native/validators/mod.rs b/internal/crypto/src/raw_signature/rust_native/validators/mod.rs
index 92fee1e4e..4810a2e7c 100644
--- a/internal/crypto/src/raw_signature/rust_native/validators/mod.rs
+++ b/internal/crypto/src/raw_signature/rust_native/validators/mod.rs
@@ -14,9 +14,16 @@
//! This module binds Rust native logic for generating raw signatures to this
//! crate's [`RawSignatureValidator`] trait.
+use async_trait::async_trait;
use bcder::Oid;
-use crate::raw_signature::{oids::*, RawSignatureValidator, SigningAlg};
+use crate::raw_signature::{
+ oids::*, AsyncRawSignatureValidator, RawSignatureValidationError, RawSignatureValidator,
+ SigningAlg,
+};
+
+mod ecdsa_validator;
+pub(crate) use ecdsa_validator::EcdsaValidator;
mod ed25519_validator;
pub(crate) use ed25519_validator::Ed25519Validator;
@@ -27,6 +34,48 @@ pub(crate) use rsa_legacy_validator::RsaLegacyValidator;
mod rsa_validator;
pub(crate) use rsa_validator::RsaValidator;
+struct AsyncRawSignatureValidatorAdapter {
+ validator: Box,
+}
+
+impl AsyncRawSignatureValidatorAdapter {
+ fn new(validator: Box) -> Self {
+ Self { validator }
+ }
+}
+
+#[async_trait(?Send)]
+impl AsyncRawSignatureValidator for AsyncRawSignatureValidatorAdapter {
+ async fn validate_async(
+ &self,
+ sig: &[u8],
+ data: &[u8],
+ public_key: &[u8],
+ ) -> Result<(), RawSignatureValidationError> {
+ self.validator.validate(sig, data, public_key)
+ }
+}
+
+/// Return an async validator for the given signing algorithm.
+pub(crate) fn async_validator_for_signing_alg(
+ alg: SigningAlg,
+) -> Option> {
+ let validator = validator_for_signing_alg(alg)?;
+
+ Some(Box::new(AsyncRawSignatureValidatorAdapter::new(validator)))
+}
+
+/// Return a built-in async signature validator for the requested signature
+/// algorithm as identified by OID.
+pub(crate) fn async_validator_for_sig_and_hash_algs(
+ sig_alg: &Oid,
+ hash_alg: &Oid,
+) -> Option> {
+ let validator = validator_for_sig_and_hash_algs(sig_alg, hash_alg)?;
+
+ Some(Box::new(AsyncRawSignatureValidatorAdapter::new(validator)))
+}
+
/// Return a validator for the given signing algorithm.
pub fn validator_for_signing_alg(alg: SigningAlg) -> Option> {
match alg {
@@ -34,19 +83,20 @@ pub fn validator_for_signing_alg(alg: SigningAlg) -> Option Some(Box::new(RsaValidator::Ps256)),
SigningAlg::Ps384 => Some(Box::new(RsaValidator::Ps384)),
SigningAlg::Ps512 => Some(Box::new(RsaValidator::Ps512)),
+ SigningAlg::Es256 => Some(Box::new(EcdsaValidator::Es256)),
+ SigningAlg::Es384 => Some(Box::new(EcdsaValidator::Es384)),
+ SigningAlg::Es512 => Some(Box::new(EcdsaValidator::Es512)),
_ => None,
}
}
+/// Select validator based on signing algorithm and hash type or EC curve.
pub(crate) fn validator_for_sig_and_hash_algs(
sig_alg: &Oid,
hash_alg: &Oid,
) -> Option> {
- if sig_alg.as_ref() == RSA_OID.as_bytes()
- || sig_alg.as_ref() == SHA256_WITH_RSAENCRYPTION_OID.as_bytes()
- || sig_alg.as_ref() == SHA384_WITH_RSAENCRYPTION_OID.as_bytes()
- || sig_alg.as_ref() == SHA512_WITH_RSAENCRYPTION_OID.as_bytes()
- {
+ // Handle legacy RSA.
+ if sig_alg.as_ref() == RSA_OID.as_bytes() {
if hash_alg.as_ref() == SHA256_OID.as_bytes() {
return Some(Box::new(RsaLegacyValidator::Rsa256));
} else if hash_alg.as_ref() == SHA384_OID.as_bytes() {
@@ -56,5 +106,32 @@ pub(crate) fn validator_for_sig_and_hash_algs(
}
}
+ // Handle RSS-PSS.
+ if sig_alg.as_ref() == RSA_PSS_OID.as_bytes() {
+ if hash_alg.as_ref() == SHA256_OID.as_bytes() {
+ return Some(Box::new(RsaValidator::Ps256));
+ } else if hash_alg.as_ref() == SHA384_OID.as_bytes() {
+ return Some(Box::new(RsaValidator::Ps384));
+ } else if hash_alg.as_ref() == SHA512_OID.as_bytes() {
+ return Some(Box::new(RsaValidator::Ps512));
+ }
+ }
+
+ // Handle elliptical curve and hash combinations.
+ if sig_alg.as_ref() == EC_PUBLICKEY_OID.as_bytes() {
+ if hash_alg.as_ref() == SHA256_OID.as_bytes() {
+ return Some(Box::new(EcdsaValidator::Es256));
+ } else if hash_alg.as_ref() == SHA384_OID.as_bytes() {
+ return Some(Box::new(EcdsaValidator::Es384));
+ } else if hash_alg.as_ref() == SHA512_OID.as_bytes() {
+ return Some(Box::new(EcdsaValidator::Es512));
+ }
+ }
+
+ // Handle ED25519.
+ if sig_alg.as_ref() == ED25519_OID.as_bytes() {
+ return Some(Box::new(Ed25519Validator {}));
+ }
+
None
}
diff --git a/internal/crypto/src/raw_signature/signer.rs b/internal/crypto/src/raw_signature/signer.rs
index cc9c84d4d..c1a7b44c6 100644
--- a/internal/crypto/src/raw_signature/signer.rs
+++ b/internal/crypto/src/raw_signature/signer.rs
@@ -171,20 +171,6 @@ impl From for RawSignerE
}
}
-#[cfg(target_arch = "wasm32")]
-impl From for RawSignerError {
- fn from(err: crate::raw_signature::webcrypto::WasmCryptoError) -> Self {
- match err {
- crate::raw_signature::webcrypto::WasmCryptoError::UnknownContext => {
- Self::InternalError("unknown WASM context".to_string())
- }
- crate::raw_signature::webcrypto::WasmCryptoError::NoCryptoAvailable => {
- Self::InternalError("WASM crypto unavailable".to_string())
- }
- }
- }
-}
-
/// Return a built-in [`RawSigner`] instance using the provided signing
/// certificate and private key.
///
diff --git a/internal/crypto/src/raw_signature/validator.rs b/internal/crypto/src/raw_signature/validator.rs
index 3e622aec9..beecf6cdf 100644
--- a/internal/crypto/src/raw_signature/validator.rs
+++ b/internal/crypto/src/raw_signature/validator.rs
@@ -15,7 +15,7 @@ use async_trait::async_trait;
use bcder::Oid;
use thiserror::Error;
-use crate::raw_signature::{oids::*, SigningAlg};
+use crate::raw_signature::SigningAlg;
/// A `RawSignatureValidator` implementation checks a signature encoded using a
/// specific signature algorithm and a private/public key pair.
@@ -81,13 +81,6 @@ pub fn validator_for_signing_alg(alg: SigningAlg) -> Option Option> {
- if sig_alg.as_ref() == RSA_OID.as_bytes()
- || sig_alg.as_ref() == SHA256_WITH_RSAENCRYPTION_OID.as_bytes()
- || sig_alg.as_ref() == SHA384_WITH_RSAENCRYPTION_OID.as_bytes()
- || sig_alg.as_ref() == SHA512_WITH_RSAENCRYPTION_OID.as_bytes()
+ // TO REVIEW: Do we need any of the RSA-PSS algorithms for this use case?
+ #[cfg(any(target_arch = "wasm32", feature = "rust_native_crypto"))]
{
- // TO REVIEW: Do we need any of the RSA-PSS algorithms for this use case?
- #[cfg(any(target_arch = "wasm32", feature = "rust_native_crypto"))]
- {
- if let Some(validator) =
- crate::raw_signature::rust_native::validators::validator_for_sig_and_hash_algs(
- sig_alg, hash_alg,
- )
- {
- return Some(validator);
- }
- }
-
- #[cfg(not(target_arch = "wasm32"))]
if let Some(validator) =
- crate::raw_signature::openssl::validators::validator_for_sig_and_hash_algs(
+ crate::raw_signature::rust_native::validators::validator_for_sig_and_hash_algs(
sig_alg, hash_alg,
)
{
return Some(validator);
}
- } else if sig_alg.as_ref() == EC_PUBLICKEY_OID.as_bytes()
- || sig_alg.as_ref() == ECDSA_WITH_SHA256_OID.as_bytes()
- || sig_alg.as_ref() == ECDSA_WITH_SHA384_OID.as_bytes()
- || sig_alg.as_ref() == ECDSA_WITH_SHA512_OID.as_bytes()
+ }
+
+ #[cfg(not(target_arch = "wasm32"))]
+ if let Some(validator) =
+ crate::raw_signature::openssl::validators::validator_for_sig_and_hash_algs(
+ sig_alg, hash_alg,
+ )
{
- if hash_alg.as_ref() == SHA256_OID.as_bytes() {
- return validator_for_signing_alg(SigningAlg::Es256);
- } else if hash_alg.as_ref() == SHA384_OID.as_bytes() {
- return validator_for_signing_alg(SigningAlg::Es384);
- } else if hash_alg.as_ref() == SHA512_OID.as_bytes() {
- return validator_for_signing_alg(SigningAlg::Es512);
- }
- } else if sig_alg.as_ref() == ED25519_OID.as_bytes() {
- return validator_for_signing_alg(SigningAlg::Ed25519);
+ return Some(validator);
}
+ let _ = sig_alg; // this value will be unused in this case
+ let _ = hash_alg; // this value will be unused in this case
+
None
}
@@ -157,7 +133,16 @@ pub(crate) fn validator_for_sig_and_hash_algs(
pub fn async_validator_for_signing_alg(
alg: SigningAlg,
) -> Option> {
- crate::raw_signature::webcrypto::async_validator_for_signing_alg(alg)
+ crate::raw_signature::rust_native::validators::async_validator_for_signing_alg(alg)
+}
+#[cfg(target_arch = "wasm32")]
+pub(crate) fn async_validator_for_sig_and_hash_algs(
+ sig_alg: &Oid,
+ hash_alg: &Oid,
+) -> Option> {
+ crate::raw_signature::rust_native::validators::async_validator_for_sig_and_hash_algs(
+ sig_alg, hash_alg,
+ )
}
/// Describes errors that can be identified when validating a raw signature.
@@ -203,17 +188,3 @@ impl From for RawSignatu
Self::InternalError(err.to_string())
}
}
-
-#[cfg(target_arch = "wasm32")]
-impl From for RawSignatureValidationError {
- fn from(err: crate::raw_signature::webcrypto::WasmCryptoError) -> Self {
- match err {
- crate::raw_signature::webcrypto::WasmCryptoError::UnknownContext => {
- Self::InternalError("unknown WASM context".to_string())
- }
- crate::raw_signature::webcrypto::WasmCryptoError::NoCryptoAvailable => {
- Self::InternalError("WASM crypto unavailable".to_string())
- }
- }
- }
-}
diff --git a/internal/crypto/src/raw_signature/webcrypto/async_validators/ecdsa_validator.rs b/internal/crypto/src/raw_signature/webcrypto/async_validators/ecdsa_validator.rs
deleted file mode 100644
index b5e2212e2..000000000
--- a/internal/crypto/src/raw_signature/webcrypto/async_validators/ecdsa_validator.rs
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2024 Adobe. All rights reserved.
-// This file is licensed to you under the Apache License,
-// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
-// or the MIT license (http://opensource.org/licenses/MIT),
-// at your option.
-
-// Unless required by applicable law or agreed to in writing,
-// this software is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
-// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
-// specific language governing permissions and limitations under
-// each license.
-
-use async_trait::async_trait;
-use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array};
-use wasm_bindgen::prelude::*;
-use wasm_bindgen_futures::JsFuture;
-use web_sys::CryptoKey;
-
-use crate::raw_signature::{
- webcrypto::WindowOrWorker, AsyncRawSignatureValidator, RawSignatureValidationError,
-};
-
-/// An `EcdsaValidator` can validate raw signatures with one of the ECDSA
-/// signature algorithms.
-pub enum EcdsaValidator {
- /// ECDSA with SHA-256
- Es256,
-
- /// ECDSA with SHA-384
- Es384,
-
- // ECDSA with SHA-512
- Es512,
-}
-
-#[async_trait(?Send)]
-impl AsyncRawSignatureValidator for EcdsaValidator {
- async fn validate_async(
- &self,
- sig: &[u8],
- data: &[u8],
- public_key: &[u8],
- ) -> Result<(), RawSignatureValidationError> {
- let context = WindowOrWorker::new();
- let subtle_crypto = context?.subtle_crypto()?;
-
- let (hash, named_curve) = match self {
- Self::Es256 => ("SHA-256", "P-256"),
- Self::Es384 => ("SHA-384", "P-384"),
- Self::Es512 => ("SHA-512", "P-521"),
- };
-
- let algorithm = EcKeyImportParams { hash, named_curve }
- .as_js_object()
- .map_err(|_err| {
- RawSignatureValidationError::InternalError(
- "error creating JS object for EcKeyImportParams".to_string(),
- )
- })?;
-
- let key_array_buf = data_as_array_buffer(public_key);
-
- let usages = Array::new();
- usages.push(&"verify".into());
-
- let promise = subtle_crypto
- .import_key_with_object("spki", &key_array_buf, &algorithm, true, &usages)
- .map_err(|_err| {
- RawSignatureValidationError::InternalError("SPKI unavailable".to_string())
- })?;
-
- let crypto_key: CryptoKey = JsFuture::from(promise)
- .await
- .map_err(|_err| {
- RawSignatureValidationError::InternalError("invalid ECDSA key".to_string())
- })?
- .into();
-
- let algorithm = EcdsaParams(hash).as_js_object().map_err(|_err| {
- RawSignatureValidationError::InternalError(
- "error creating JS object for EcdsaParams".to_string(),
- )
- })?;
-
- let promise = subtle_crypto
- .verify_with_object_and_buffer_source_and_buffer_source(
- &algorithm,
- &crypto_key,
- &data_as_array_buffer(&sig),
- &data_as_array_buffer(&data),
- )
- .map_err(|_err| {
- RawSignatureValidationError::InternalError(
- "unable to invoke SubtleCrypto verifier".to_string(),
- )
- })?;
-
- let verified: JsValue = JsFuture::from(promise)
- .await
- .map_err(|_err| {
- RawSignatureValidationError::InternalError(
- "error creating JS future from SubtleCrypto promise".to_string(),
- )
- })?
- .into();
-
- if verified.is_truthy() {
- Ok(())
- } else {
- Err(RawSignatureValidationError::SignatureMismatch)
- }
- }
-}
-
-struct EcKeyImportParams {
- named_curve: &'static str,
- hash: &'static str,
-}
-
-impl EcKeyImportParams {
- pub fn as_js_object(&self) -> Result