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 { - let obj = Object::new(); - Reflect::set(&obj, &"name".into(), &"ECDSA".into())?; - Reflect::set(&obj, &"namedCurve".into(), &self.named_curve.into())?; - - let inner_obj = Object::new(); - Reflect::set(&inner_obj, &"name".into(), &self.hash.into())?; - Reflect::set(&obj, &"hash".into(), &inner_obj)?; - - Ok(obj) - } -} - -struct EcdsaParams(&'static str); - -impl EcdsaParams { - fn as_js_object(&self) -> Result { - let obj = Object::new(); - Reflect::set(&obj, &"name".into(), &"ECDSA".into())?; - - let inner_obj = Object::new(); - Reflect::set(&inner_obj, &"name".into(), &self.0.into())?; - - Reflect::set(&obj, &"hash".into(), &inner_obj)?; - - Ok(obj) - } -} - -fn data_as_array_buffer(data: &[u8]) -> ArrayBuffer { - let typed_array = Uint8Array::new_with_length(data.len() as u32); - typed_array.copy_from(data); - typed_array.buffer() -} diff --git a/internal/crypto/src/raw_signature/webcrypto/async_validators/mod.rs b/internal/crypto/src/raw_signature/webcrypto/async_validators/mod.rs deleted file mode 100644 index 64e415d18..000000000 --- a/internal/crypto/src/raw_signature/webcrypto/async_validators/mod.rs +++ /dev/null @@ -1,54 +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 bcder::Oid; - -use crate::raw_signature::{oids::*, AsyncRawSignatureValidator, SigningAlg}; - -/// Return an async validator for the given signing algorithm. -pub fn async_validator_for_signing_alg( - alg: SigningAlg, -) -> Option> { - match alg { - SigningAlg::Es256 => Some(Box::new(EcdsaValidator::Es256)), - SigningAlg::Es384 => Some(Box::new(EcdsaValidator::Es384)), - SigningAlg::Es512 => Some(Box::new(EcdsaValidator::Es512)), - _ => None, - } -} - -/// 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> { - 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() - { - if hash_alg.as_ref() == SHA256_OID.as_bytes() { - return async_validator_for_signing_alg(SigningAlg::Es256); - } else if hash_alg.as_ref() == SHA384_OID.as_bytes() { - return async_validator_for_signing_alg(SigningAlg::Es384); - } else if hash_alg.as_ref() == SHA512_OID.as_bytes() { - return async_validator_for_signing_alg(SigningAlg::Es512); - } - } - - None -} - -pub(crate) mod ecdsa_validator; -use ecdsa_validator::EcdsaValidator; diff --git a/internal/crypto/src/raw_signature/webcrypto/mod.rs b/internal/crypto/src/raw_signature/webcrypto/mod.rs deleted file mode 100644 index 874f80e40..000000000 --- a/internal/crypto/src/raw_signature/webcrypto/mod.rs +++ /dev/null @@ -1,31 +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. - -//! This module provides functions for working with the [`SubtleCrypto`] library -//! typically available in web browser environments. -//! -//! It is only available when this crate is compiled for `wasm` architecture and -//! not `wasi` target. -//! -//! [`SubtleCrypto`]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.SubtleCrypto.html - -pub(crate) mod async_validators; -pub(crate) use async_validators::{ - async_validator_for_sig_and_hash_algs, async_validator_for_signing_alg, -}; - -pub(crate) mod check_certificate_trust; -pub(crate) mod validators; - -mod window_or_worker; -pub use window_or_worker::{WasmCryptoError, WindowOrWorker}; diff --git a/internal/crypto/src/raw_signature/webcrypto/validators/ecdsa_validator.rs b/internal/crypto/src/raw_signature/webcrypto/validators/ecdsa_validator.rs deleted file mode 100644 index 0fac96091..000000000 --- a/internal/crypto/src/raw_signature/webcrypto/validators/ecdsa_validator.rs +++ /dev/null @@ -1,74 +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 ecdsa::{signature::Verifier as EcdsaVerifier, Signature as EcdsaSignature}; -use p256::ecdsa::VerifyingKey as P256VerifyingKey; -use p384::ecdsa::VerifyingKey as P384VerifyingKey; -use spki::DecodePublicKey; - -use crate::raw_signature::{RawSignatureValidationError, RawSignatureValidator}; - -/// 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, // not yet implemented (check with Colin) -} - -impl RawSignatureValidator for EcdsaValidator { - fn validate( - &self, - sig: &[u8], - data: &[u8], - public_key: &[u8], - ) -> Result<(), RawSignatureValidationError> { - let result = match self { - Self::Es256 => { - let signature = EcdsaSignature::from_slice(sig) - .map_err(|_| RawSignatureValidationError::InvalidSignature)?; - - let vk = P256VerifyingKey::from_public_key_der(public_key) - .map_err(|_| RawSignatureValidationError::InvalidPublicKey)?; - - vk.verify(&data, &signature) - } - - Self::Es384 => { - let signature = EcdsaSignature::from_slice(sig) - .map_err(|_| RawSignatureValidationError::InvalidSignature)?; - - let vk = P384VerifyingKey::from_public_key_der(public_key) - .map_err(|_| RawSignatureValidationError::InvalidPublicKey)?; - - vk.verify(&data, &signature) - } - }; - - match result { - Ok(_) => Ok(()), - Err(err) => { - web_sys::console::debug_2( - &"ECDSA validation failed:".into(), - &err.to_string().into(), - ); - - Err(RawSignatureValidationError::SignatureMismatch) - } - } - } -} diff --git a/internal/crypto/src/raw_signature/webcrypto/validators/mod.rs b/internal/crypto/src/raw_signature/webcrypto/validators/mod.rs deleted file mode 100644 index bcd0a56c6..000000000 --- a/internal/crypto/src/raw_signature/webcrypto/validators/mod.rs +++ /dev/null @@ -1,30 +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. - -//! This module binds [`SubtleCrypto`] logic for validating raw signatures to -//! this crate's [`RawSignatureValidator`] trait. - -use crate::raw_signature::{RawSignatureValidator, SigningAlg}; - -mod ecdsa_validator; -pub use ecdsa_validator::EcdsaValidator; - -/// Return a validator for the given signing algorithm. -pub fn validator_for_signing_alg(alg: SigningAlg) -> Option> { - match alg { - SigningAlg::Es256 => Some(Box::new(EcdsaValidator::Es256)), - SigningAlg::Es384 => Some(Box::new(EcdsaValidator::Es384)), - SigningAlg::Es512 => None, /* why is this unimplemented? */ - _ => None, - } -} diff --git a/internal/crypto/src/raw_signature/webcrypto/window_or_worker.rs b/internal/crypto/src/raw_signature/webcrypto/window_or_worker.rs deleted file mode 100644 index da0523920..000000000 --- a/internal/crypto/src/raw_signature/webcrypto/window_or_worker.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2022 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. - -#![allow(missing_docs)] - -use thiserror::Error; -use wasm_bindgen::{prelude::*, JsCast, JsValue}; -use web_sys::{Crypto, SubtleCrypto, Window, WorkerGlobalScope}; - -/// Adapted from gloo's implementation, since there doesn't seem to be a great -/// way to do context checking using `wasm-bindgen/web-sys` without using -/// something like `js_sys::eval`. -/// -/// References: -/// -/// - Issue: https://github.com/rustwasm/wasm-bindgen/issues/1046 -/// - Issue: https://github.com/rustwasm/wasm-bindgen/issues/2148#issuecomment-638606446 -/// - Code reference: https://git.io/J9crn -pub enum WindowOrWorker { - Window(Window), - Worker(WorkerGlobalScope), -} - -impl WindowOrWorker { - pub fn new() -> Result { - #[wasm_bindgen] - extern "C" { - type Global; - - #[wasm_bindgen(method, getter, js_name = Window)] - fn window(this: &Global) -> JsValue; - - #[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)] - fn worker(this: &Global) -> JsValue; - } - - let global: Global = js_sys::global().unchecked_into(); - - if !global.window().is_undefined() { - Ok(Self::Window(global.unchecked_into())) - } else if !global.worker().is_undefined() { - Ok(Self::Worker(global.unchecked_into())) - } else { - Err(WasmCryptoError::UnknownContext) - } - } - - pub fn crypto(&self) -> Result { - match self { - Self::Window(window) => window.crypto(), - Self::Worker(worker) => worker.crypto(), - } - .map_err(|_err| WasmCryptoError::NoCryptoAvailable) - } - - pub fn subtle_crypto(&self) -> Result { - Ok(self.crypto()?.subtle()) - } -} - -/// Error returned when cryptography libraries are unavailable. -#[derive(Debug, Error, Eq, PartialEq)] -pub enum WasmCryptoError { - /// Unknown context. - #[error("could not find window or worker in global environment")] - UnknownContext, - - /// Crypto library unavailable. - #[error("window or worker's .crypto() method failed")] - NoCryptoAvailable, -} diff --git a/internal/crypto/src/tests/mod.rs b/internal/crypto/src/tests/mod.rs index d1a7cb5d8..4b3430e67 100644 --- a/internal/crypto/src/tests/mod.rs +++ b/internal/crypto/src/tests/mod.rs @@ -32,6 +32,3 @@ mod openssl; mod raw_signature; mod signing_alg; - -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] -mod webcrypto; diff --git a/internal/crypto/src/tests/raw_signature/rust_native/validators.rs b/internal/crypto/src/tests/raw_signature/rust_native/validators.rs index 939a664ca..f9ac4be4c 100644 --- a/internal/crypto/src/tests/raw_signature/rust_native/validators.rs +++ b/internal/crypto/src/tests/raw_signature/rust_native/validators.rs @@ -20,7 +20,6 @@ use crate::raw_signature::{rust_native, RawSignatureValidationError, SigningAlg} const SAMPLE_DATA: &[u8] = b"some sample content to sign"; -/* Not implemented in rust_native yet. #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn es256() { @@ -80,8 +79,7 @@ fn es384() { } #[test] -// #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] // ES512 not -// implemented +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn es512() { let signature = include_bytes!("../../fixtures/raw_signature/es512.raw_sig"); let pub_key = include_bytes!("../../fixtures/raw_signature/es512.pub_key"); @@ -90,7 +88,6 @@ fn es512() { validator.validate(signature, SAMPLE_DATA, pub_key).unwrap(); } -*/ #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] @@ -283,7 +280,7 @@ fn rs512() { const SHA1_OID: Oid = bcder::Oid(OctetString::from_static(&[43, 14, 3, 2, 26])); #[test] -// #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn sha1() { let signature = include_bytes!("../../fixtures/raw_signature/legacy/sha1.raw_sig"); let pub_key = include_bytes!("../../fixtures/raw_signature/legacy/sha1.pub_key"); diff --git a/internal/crypto/src/tests/raw_signature/validators.rs b/internal/crypto/src/tests/raw_signature/validators.rs index fa9060cc0..50df94130 100644 --- a/internal/crypto/src/tests/raw_signature/validators.rs +++ b/internal/crypto/src/tests/raw_signature/validators.rs @@ -11,6 +11,8 @@ // specific language governing permissions and limitations under // each license. +use std::str::FromStr; + use bcder::Oid; use rasn::types::OctetString; #[cfg(target_arch = "wasm32")] @@ -285,3 +287,43 @@ fn sha1() { validator.validate(signature, SAMPLE_DATA, pub_key).unwrap(); } + +fn ans1_oid_bcder_oid(asn1_oid: &asn1_rs::Oid) -> bcder::Oid { + const TEST_FAIL: Oid = bcder::Oid(OctetString::from_static(&[0, 0, 0, 0])); + + let asn1_oid_str = asn1_oid.to_id_string(); + + bcder::Oid::from_str(&asn1_oid_str).unwrap_or(TEST_FAIL.to_owned()) +} + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn test_get_by_sig_and_alg() { + use crate::raw_signature::oids::*; + + let rsa_oid = ans1_oid_bcder_oid(&RSA_OID); + let rsa_pss_oid = ans1_oid_bcder_oid(&RSA_PSS_OID); + let ec_pubic_key_oid = ans1_oid_bcder_oid(&EC_PUBLICKEY_OID); + let ed25519_oid = ans1_oid_bcder_oid(&ED25519_OID); + let sha256 = ans1_oid_bcder_oid(&SHA256_OID); + let sha384 = ans1_oid_bcder_oid(&SHA384_OID); + let sha512 = ans1_oid_bcder_oid(&SHA512_OID); + + assert!(validator_for_sig_and_hash_algs(&rsa_oid, &sha256).is_some()); + assert!(validator_for_sig_and_hash_algs(&rsa_oid, &sha384).is_some()); + assert!(validator_for_sig_and_hash_algs(&rsa_oid, &sha512).is_some()); + + assert!(validator_for_sig_and_hash_algs(&rsa_pss_oid, &sha256).is_some()); + assert!(validator_for_sig_and_hash_algs(&rsa_pss_oid, &sha384).is_some()); + assert!(validator_for_sig_and_hash_algs(&rsa_pss_oid, &sha512).is_some()); + + assert!(validator_for_sig_and_hash_algs(&ec_pubic_key_oid, &sha256).is_some()); + assert!(validator_for_sig_and_hash_algs(&ec_pubic_key_oid, &sha384).is_some()); + assert!(validator_for_sig_and_hash_algs(&ec_pubic_key_oid, &sha512).is_some()); + + assert!(validator_for_sig_and_hash_algs(&ed25519_oid, &sha512).is_some()); + + // test negative case + const TEST_FAIL: Oid = bcder::Oid(OctetString::from_static(&[0, 0, 0, 0])); + assert!(validator_for_sig_and_hash_algs(&TEST_FAIL, &sha512).is_none()); +} diff --git a/internal/crypto/src/tests/webcrypto.rs b/internal/crypto/src/tests/webcrypto.rs deleted file mode 100644 index 69aa65e1f..000000000 --- a/internal/crypto/src/tests/webcrypto.rs +++ /dev/null @@ -1,21 +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 wasm_bindgen_test::wasm_bindgen_test; - -use crate::raw_signature::webcrypto::WindowOrWorker; - -#[wasm_bindgen_test] -fn has_crypto_library() { - WindowOrWorker::new().unwrap().crypto().unwrap(); -} diff --git a/internal/crypto/src/time_stamp/verify.rs b/internal/crypto/src/time_stamp/verify.rs index 46e47bac4..f7b9b39b0 100644 --- a/internal/crypto/src/time_stamp/verify.rs +++ b/internal/crypto/src/time_stamp/verify.rs @@ -224,8 +224,8 @@ pub(crate) fn verify_time_stamp(ts: &[u8], data: &[u8]) -> Result Result Result<(), TimeStampError> { if let Some(validator) = - crate::raw_signature::webcrypto::async_validator_for_sig_and_hash_algs(sig_alg, hash_alg) + crate::raw_signature::async_validator_for_sig_and_hash_algs(sig_alg, hash_alg) { validator .validate_async(&sig_val.to_bytes(), tbs, signing_key_der) diff --git a/make_test_images/src/make_test_images.rs b/make_test_images/src/make_test_images.rs index 2303cb741..d2e1c0ee9 100644 --- a/make_test_images/src/make_test_images.rs +++ b/make_test_images/src/make_test_images.rs @@ -167,7 +167,7 @@ impl MakeTestImages { let mut path_buf = PathBuf::from(s); // parent() tends to return an empty string instead of None let has_path = match path_buf.parent() { - Some(p) => p.to_string_lossy().len() > 0, + Some(p) => !p.to_string_lossy().is_empty(), None => false, }; // if we just have a filename, then assume it is in the output folder diff --git a/sdk/src/assertions/actions.rs b/sdk/src/assertions/actions.rs index cc9d2fc5b..2c23236d7 100644 --- a/sdk/src/assertions/actions.rs +++ b/sdk/src/assertions/actions.rs @@ -130,8 +130,9 @@ pub struct Action { /// This is NOT the instanceID in the spec /// It is now deprecated but was previously used to map the action to an ingredient - #[serde(rename = "instanceId", skip_serializing)] // this should never be written to CBOR #[deprecated(since = "0.37.0", note = "Use `org.cai.ingredientIds` instead")] + #[serde(skip_serializing)] + #[serde(alias = "instanceId", alias = "instanceID")] instance_id: Option, /// Additional parameters of the action. These vary by the type of action. diff --git a/sdk/src/asset_handlers/mp3_io.rs b/sdk/src/asset_handlers/mp3_io.rs index 147aadd72..f9c6d66bb 100644 --- a/sdk/src/asset_handlers/mp3_io.rs +++ b/sdk/src/asset_handlers/mp3_io.rs @@ -94,7 +94,7 @@ impl ID3V2Header { } fn decode_tag_size(n: u32) -> u32 { - n & 0xff | (n & 0xff00) >> 1 | (n & 0xff0000) >> 2 | (n & 0xff000000) >> 3 + (n & 0xff) | ((n & 0xff00) >> 1) | ((n & 0xff0000) >> 2) | ((n & 0xff000000) >> 3) } fn is_mp3_frame_sync(header: &[u8]) -> bool { diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 157845a91..b71a200c6 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1403,7 +1403,7 @@ mod tests { "cloud_manifest.c2pa", ]; for file_name in TESTFILES { - let extension = file_name.split('.').last().unwrap(); + let extension = file_name.split('.').next_back().unwrap(); let format = extension; let path = format!("tests/fixtures/{}", file_name); diff --git a/sdk/src/cose_validator.rs b/sdk/src/cose_validator.rs index 044d5334b..5a433270f 100644 --- a/sdk/src/cose_validator.rs +++ b/sdk/src/cose_validator.rs @@ -11,10 +11,11 @@ // specific language governing permissions and limitations under // each license. -use std::io::Cursor; +use std::io::Write; use async_generic::async_generic; use c2pa_crypto::{ + base64, cose::{ cert_chain_from_sign1, parse_cose_sign1, signing_alg_from_sign1, signing_time_from_sign1, signing_time_from_sign1_async, CertificateInfo, CertificateTrustPolicy, Verifier, @@ -69,16 +70,38 @@ pub(crate) fn verify_cose( // internal util function to dump the cert chain in PEM format fn dump_cert_chain(certs: &[Vec]) -> Result> { - 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) + let cert_base_str = base64::encode(der_bytes); + + // break line into fixed len lines + let cert_lines = cert_base_str + .chars() + .collect::>() + .chunks(line_len) + .map(|chunk| chunk.iter().collect::()) + .collect::>(); + + // write lines + writer + .write_fmt(format_args!("{}\n", cert_begin)) + .map_err(|_e| Error::UnsupportedType)?; + for l in cert_lines { + writer + .write_fmt(format_args!("{}\n", l)) + .map_err(|_e| Error::UnsupportedType)?; + } + writer + .write_fmt(format_args!("{}\n", cert_end)) .map_err(|_e| Error::UnsupportedType)?; - c.write_pem(&mut writer)?; } - out_buf = writer.into_inner(); - Ok(out_buf) + + Ok(writer) } fn extract_subject_from_cert(cert: &X509Certificate) -> Result { diff --git a/sdk/src/error.rs b/sdk/src/error.rs index 0541df46a..0dc95dfa4 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -330,20 +330,6 @@ pub enum Error { /// A specialized `Result` type for C2PA toolkit operations. pub type Result = std::result::Result; -#[cfg(target_arch = "wasm32")] -impl From for Error { - fn from(err: c2pa_crypto::raw_signature::webcrypto::WasmCryptoError) -> Self { - match err { - c2pa_crypto::raw_signature::webcrypto::WasmCryptoError::UnknownContext => { - Self::WasmInvalidContext - } - c2pa_crypto::raw_signature::webcrypto::WasmCryptoError::NoCryptoAvailable => { - Self::WasmNoCrypto - } - } - } -} - impl From for Error { fn from(err: CoseError) -> Self { match err { diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 862b1cab3..7bc2a88ed 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -98,8 +98,6 @@ pub mod jumbf_io; pub mod settings; pub mod validation_results; pub mod validation_status; -#[cfg(target_arch = "wasm32")] -pub mod wasm; // Public exports pub use assertions::Relationship; diff --git a/sdk/src/salt.rs b/sdk/src/salt.rs index 09aa1e3c8..1509077da 100644 --- a/sdk/src/salt.rs +++ b/sdk/src/salt.rs @@ -55,19 +55,12 @@ impl Default for DefaultSalt { impl SaltGenerator for DefaultSalt { fn generate_salt(&self) -> Option> { - #[cfg(target_arch = "wasm32")] - { - Some(crate::wasm::util::get_random_values(self.salt_len).ok()?) - } - #[cfg(not(target_arch = "wasm32"))] - { - use rand::prelude::*; + use rand::prelude::*; - let mut salt = vec![0u8; self.salt_len]; - let mut rng = rand_chacha::ChaCha20Rng::from_entropy(); - rng.fill_bytes(&mut salt); + let mut salt = vec![0u8; self.salt_len]; + let mut rng = rand_chacha::ChaCha20Rng::from_entropy(); + rng.fill_bytes(&mut salt); - Some(salt) - } + Some(salt) } } diff --git a/sdk/src/utils/test.rs b/sdk/src/utils/test.rs index 37c5f13d5..80a95e02c 100644 --- a/sdk/src/utils/test.rs +++ b/sdk/src/utils/test.rs @@ -386,6 +386,7 @@ impl crate::signer::RemoteSigner for TempRemoteSigner { } } +/* todo: This test should be replaced by a rust_native signer if desired to sign from wasm #[cfg(target_arch = "wasm32")] struct WebCryptoSigner { signing_alg: SigningAlg, @@ -484,6 +485,7 @@ impl AsyncSigner for WebCryptoSigner { None } } +*/ /// Create a [`RemoteSigner`] instance that can be used for testing purposes. /// diff --git a/sdk/src/wasm/mod.rs b/sdk/src/wasm/mod.rs deleted file mode 100644 index 2b54e20cd..000000000 --- a/sdk/src/wasm/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 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. - -#[cfg(target_arch = "wasm32")] -pub(crate) mod util; diff --git a/sdk/src/wasm/util.rs b/sdk/src/wasm/util.rs deleted file mode 100644 index 8e9c3bb5c..000000000 --- a/sdk/src/wasm/util.rs +++ /dev/null @@ -1,44 +0,0 @@ -use c2pa_crypto::raw_signature::webcrypto::WindowOrWorker; -use web_sys::Crypto; - -use crate::{Error, Result}; - -pub fn get_random_values(len: usize) -> Result> { - let context = WindowOrWorker::new(); - let crypto: Crypto = context?.crypto()?; - let mut values = vec![0u8; len]; - crypto - .get_random_values_with_u8_array(&mut values) - .map_err(|_err| Error::WasmNoCrypto)?; - - Ok(values) -} - -#[cfg(test)] -pub mod tests { - #![allow(clippy::unwrap_used)] - - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::*; - - use super::*; - - #[cfg(target_arch = "wasm32")] - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn test_get_random_values() { - let len: usize = 32; - let random1 = get_random_values(len).unwrap(); - let random2 = get_random_values(len).unwrap(); - let sum_fn = |sum: u32, i: &u8| sum + (*i as u32); - let sum1 = random1.iter().fold(0u32, sum_fn); - let sum2 = random2.iter().fold(0u32, sum_fn); - - assert_eq!(random1.len(), len); - assert_eq!(random2.len(), len); - assert_ne!(sum1, sum2); - assert!(sum1 > 0); - assert!(sum2 > 0); - } -}