From 2df2f1de95a4b768d5c9abdbcce84ba7f4aa6cee Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Thu, 21 Nov 2024 14:55:29 -0800 Subject: [PATCH] chore: Move WASM asynchronous raw signature mechanism into c2pa-crypto (#694) --- internal/crypto/Cargo.toml | 1 + .../crypto/src/raw_signature/validator.rs | 23 + .../webcrypto/validators/async_validators.rs | 303 ++++++++++ .../src/tests/webcrypto/validators/mod.rs | 1 + .../async_validators/ecdsa_validator.rs | 150 +++++ .../async_validators/ed25519_validator.rs | 38 ++ .../src/webcrypto/async_validators/mod.rs | 114 ++++ .../async_validators/rsa_legacy_validator.rs | 48 ++ .../async_validators/rsa_validator.rs | 53 ++ internal/crypto/src/webcrypto/mod.rs | 6 + .../validators/rsa_legacy_validator.rs | 2 +- sdk/src/cose_validator.rs | 25 +- sdk/src/time_stamp.rs | 91 +-- sdk/src/wasm/mod.rs | 6 - sdk/src/wasm/webcrypto_validator.rs | 556 ------------------ sdk/src/wasm/webpki_trust_handler.rs | 71 ++- 16 files changed, 816 insertions(+), 672 deletions(-) create mode 100644 internal/crypto/src/tests/webcrypto/validators/async_validators.rs create mode 100644 internal/crypto/src/webcrypto/async_validators/ecdsa_validator.rs create mode 100644 internal/crypto/src/webcrypto/async_validators/ed25519_validator.rs create mode 100644 internal/crypto/src/webcrypto/async_validators/mod.rs create mode 100644 internal/crypto/src/webcrypto/async_validators/rsa_legacy_validator.rs create mode 100644 internal/crypto/src/webcrypto/async_validators/rsa_validator.rs delete mode 100644 sdk/src/wasm/webcrypto_validator.rs diff --git a/internal/crypto/Cargo.toml b/internal/crypto/Cargo.toml index 8381c8fc9..c74b6d582 100644 --- a/internal/crypto/Cargo.toml +++ b/internal/crypto/Cargo.toml @@ -63,6 +63,7 @@ default-features = false features = ["now", "wasmbind"] [target.'cfg(target_arch = "wasm32")'.dependencies] +async-trait = { version = "0.1.77" } ecdsa = "0.16.9" ed25519-dalek = "2.1.1" p256 = "0.13.2" diff --git a/internal/crypto/src/raw_signature/validator.rs b/internal/crypto/src/raw_signature/validator.rs index b1e1db09b..089f2de57 100644 --- a/internal/crypto/src/raw_signature/validator.rs +++ b/internal/crypto/src/raw_signature/validator.rs @@ -133,6 +133,15 @@ pub enum RawSignatureValidationError { /// An invalid signature value was provided. #[error("invalid signature value")] InvalidSignature, + + /// The time stamp uses an unsupported signing or hash algorithm. + #[error("signature uses an unsupported algorithm")] + UnsupportedAlgorithm, + + /// An unexpected internal error occured while requesting the time stamp + /// response. + #[error("internal error ({0})")] + InternalError(&'static str), } #[cfg(feature = "openssl")] @@ -141,3 +150,17 @@ impl From for RawSignatureValidationError { Self::OpenSslError(err.to_string()) } } + +#[cfg(target_arch = "wasm32")] +impl From for RawSignatureValidationError { + fn from(err: crate::webcrypto::WasmCryptoError) -> Self { + match err { + crate::webcrypto::WasmCryptoError::UnknownContext => { + Self::InternalError("unknown WASM context") + } + crate::webcrypto::WasmCryptoError::NoCryptoAvailable => { + Self::InternalError("WASM crypto unavailable") + } + } + } +} diff --git a/internal/crypto/src/tests/webcrypto/validators/async_validators.rs b/internal/crypto/src/tests/webcrypto/validators/async_validators.rs new file mode 100644 index 000000000..ec5ff9e92 --- /dev/null +++ b/internal/crypto/src/tests/webcrypto/validators/async_validators.rs @@ -0,0 +1,303 @@ +// 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 rasn::types::OctetString; +use wasm_bindgen_test::wasm_bindgen_test; + +use crate::{ + raw_signature::RawSignatureValidationError, + webcrypto::{ + async_validator_for_sig_and_hash_algs, async_validators::async_validator_for_signing_alg, + }, + SigningAlg, +}; + +const SAMPLE_DATA: &[u8] = b"some sample content to sign"; + +#[wasm_bindgen_test] +async fn es256() { + let signature = include_bytes!("../../fixtures/raw_signature/es256.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/es256.pub_key"); + + let validator = async_validator_for_signing_alg(SigningAlg::Es256).unwrap(); + + validator + .validate_async(signature, SAMPLE_DATA, pub_key) + .await + .unwrap(); +} + +#[wasm_bindgen_test] +async fn es256_bad_signature() { + let mut signature = include_bytes!("../../fixtures/raw_signature/es256.raw_sig").to_vec(); + assert_ne!(signature[10], 10); + signature[10] = 10; + + let pub_key = include_bytes!("../../fixtures/raw_signature/es256.pub_key"); + + let validator = async_validator_for_signing_alg(SigningAlg::Es256).unwrap(); + + assert_eq!( + validator + .validate_async(&signature, SAMPLE_DATA, pub_key) + .await + .unwrap_err(), + RawSignatureValidationError::SignatureMismatch + ); +} + +#[wasm_bindgen_test] +async fn es256_bad_data() { + let signature = include_bytes!("../../fixtures/raw_signature/es256.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/es256.pub_key"); + + let mut data = SAMPLE_DATA.to_vec(); + data[10] = 0; + + let validator = async_validator_for_signing_alg(SigningAlg::Es256).unwrap(); + + assert_eq!( + validator + .validate_async(signature, &data, pub_key) + .await + .unwrap_err(), + RawSignatureValidationError::SignatureMismatch + ); +} + +#[wasm_bindgen_test] +async fn es384() { + let signature = include_bytes!("../../fixtures/raw_signature/es384.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/es384.pub_key"); + + let validator = async_validator_for_signing_alg(SigningAlg::Es384).unwrap(); + + validator + .validate_async(signature, SAMPLE_DATA, pub_key) + .await + .unwrap(); +} + +#[wasm_bindgen_test] +async fn es512() { + let signature = include_bytes!("../../fixtures/raw_signature/es512.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/es512.pub_key"); + + let validator = async_validator_for_signing_alg(SigningAlg::Es512).unwrap(); + + validator + .validate_async(signature, SAMPLE_DATA, pub_key) + .await + .unwrap(); +} + +#[wasm_bindgen_test] +async fn ed25519() { + let signature = include_bytes!("../../fixtures/raw_signature/ed25519.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/ed25519.pub_key"); + + let validator = async_validator_for_signing_alg(SigningAlg::Ed25519).unwrap(); + + validator + .validate_async(signature, SAMPLE_DATA, pub_key) + .await + .unwrap(); +} + +#[wasm_bindgen_test] +async fn ed25519_bad_data() { + let signature = include_bytes!("../../fixtures/raw_signature/ed25519.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/ed25519.pub_key"); + + let mut data = SAMPLE_DATA.to_vec(); + data[5] = 10; + data[6] = 11; + + let validator = async_validator_for_signing_alg(SigningAlg::Ed25519).unwrap(); + + assert_eq!( + validator + .validate_async(signature, &data, pub_key) + .await + .unwrap_err(), + RawSignatureValidationError::SignatureMismatch + ); +} + +#[wasm_bindgen_test] +async fn ps256() { + let signature = include_bytes!("../../fixtures/raw_signature/ps256.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/ps256.pub_key"); + + let validator = async_validator_for_signing_alg(SigningAlg::Ps256).unwrap(); + + validator + .validate_async(signature, SAMPLE_DATA, pub_key) + .await + .unwrap(); +} + +#[wasm_bindgen_test] +async fn ps256_bad_signature() { + let mut signature = include_bytes!("../../fixtures/raw_signature/ps256.raw_sig").to_vec(); + assert_ne!(signature[10], 10); + signature[10] = 10; + + let pub_key = include_bytes!("../../fixtures/raw_signature/ps256.pub_key"); + + let validator = async_validator_for_signing_alg(SigningAlg::Ps256).unwrap(); + + assert_eq!( + validator + .validate_async(&signature, SAMPLE_DATA, pub_key) + .await + .unwrap_err(), + RawSignatureValidationError::SignatureMismatch + ); +} + +#[wasm_bindgen_test] +async fn ps256_bad_data() { + let signature = include_bytes!("../../fixtures/raw_signature/ps256.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/ps256.pub_key"); + + let mut data = SAMPLE_DATA.to_vec(); + data[10] = 0; + + let validator = async_validator_for_signing_alg(SigningAlg::Ps256).unwrap(); + + assert_eq!( + validator + .validate_async(signature, &data, pub_key) + .await + .unwrap_err(), + RawSignatureValidationError::SignatureMismatch + ); +} + +#[wasm_bindgen_test] +async fn ps384() { + let signature = include_bytes!("../../fixtures/raw_signature/ps384.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/ps384.pub_key"); + + let validator = async_validator_for_signing_alg(SigningAlg::Ps384).unwrap(); + + validator + .validate_async(signature, SAMPLE_DATA, pub_key) + .await + .unwrap(); +} + +#[wasm_bindgen_test] +async fn ps512() { + let signature = include_bytes!("../../fixtures/raw_signature/ps512.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/ps512.pub_key"); + + let validator = async_validator_for_signing_alg(SigningAlg::Ps512).unwrap(); + + validator + .validate_async(signature, SAMPLE_DATA, pub_key) + .await + .unwrap(); +} + +// Argh. Different Oid types across different crates, so we have to construct +// our own constants here. +const RSA_OID: Oid = bcder::Oid(OctetString::from_static(&[ + 42, 134, 72, 134, 247, 13, 1, 1, 1, +])); + +const SHA256_OID: Oid = bcder::Oid(OctetString::from_static(&[96, 134, 72, 1, 101, 3, 4, 2, 1])); + +const SHA384_OID: Oid = bcder::Oid(OctetString::from_static(&[96, 134, 72, 1, 101, 3, 4, 2, 2])); + +const SHA512_OID: Oid = bcder::Oid(OctetString::from_static(&[96, 134, 72, 1, 101, 3, 4, 2, 3])); + +#[wasm_bindgen_test] +async fn legacy_rs256() { + let signature = include_bytes!("../../fixtures/raw_signature/legacy/rs256.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/legacy/rs256.pub_key"); + + let validator = async_validator_for_sig_and_hash_algs(&RSA_OID, &SHA256_OID).unwrap(); + + validator + .validate_async(signature, SAMPLE_DATA, pub_key) + .await + .unwrap(); +} + +#[wasm_bindgen_test] +async fn legacy_rs256_bad_signature() { + let mut signature = + include_bytes!("../../fixtures/raw_signature/legacy/rs256.raw_sig").to_vec(); + assert_ne!(signature[10], 10); + signature[10] = 10; + + let pub_key = include_bytes!("../../fixtures/raw_signature/legacy/rs256.pub_key"); + + let validator = async_validator_for_sig_and_hash_algs(&RSA_OID, &SHA256_OID).unwrap(); + + assert_eq!( + validator + .validate_async(&signature, SAMPLE_DATA, pub_key) + .await + .unwrap_err(), + RawSignatureValidationError::SignatureMismatch + ); +} + +#[wasm_bindgen_test] +async fn legacy_rs256_bad_data() { + let signature = include_bytes!("../../fixtures/raw_signature/legacy/rs256.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/legacy/rs256.pub_key"); + + let mut data = SAMPLE_DATA.to_vec(); + data[10] = 0; + + let validator = async_validator_for_sig_and_hash_algs(&RSA_OID, &SHA256_OID).unwrap(); + + assert_eq!( + validator + .validate_async(signature, &data, pub_key) + .await + .unwrap_err(), + RawSignatureValidationError::SignatureMismatch + ); +} + +#[wasm_bindgen_test] +async fn legacy_rs384() { + let signature = include_bytes!("../../fixtures/raw_signature/legacy/rs384.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/legacy/rs384.pub_key"); + + let validator = async_validator_for_sig_and_hash_algs(&RSA_OID, &SHA384_OID).unwrap(); + + validator + .validate_async(signature, SAMPLE_DATA, pub_key) + .await + .unwrap(); +} + +#[wasm_bindgen_test] +async fn legacy_rs512() { + let signature = include_bytes!("../../fixtures/raw_signature/legacy/rs512.raw_sig"); + let pub_key = include_bytes!("../../fixtures/raw_signature/legacy/rs512.pub_key"); + + let validator = async_validator_for_sig_and_hash_algs(&RSA_OID, &SHA512_OID).unwrap(); + + validator + .validate_async(signature, SAMPLE_DATA, pub_key) + .await + .unwrap(); +} diff --git a/internal/crypto/src/tests/webcrypto/validators/mod.rs b/internal/crypto/src/tests/webcrypto/validators/mod.rs index 6f24c1252..241ab21ef 100644 --- a/internal/crypto/src/tests/webcrypto/validators/mod.rs +++ b/internal/crypto/src/tests/webcrypto/validators/mod.rs @@ -11,6 +11,7 @@ // specific language governing permissions and limitations under // each license. +mod async_validators; mod ecdsa_validator; mod ed25519_validator; mod rsa_legacy_validator; diff --git a/internal/crypto/src/webcrypto/async_validators/ecdsa_validator.rs b/internal/crypto/src/webcrypto/async_validators/ecdsa_validator.rs new file mode 100644 index 000000000..88a0cba72 --- /dev/null +++ b/internal/crypto/src/webcrypto/async_validators/ecdsa_validator.rs @@ -0,0 +1,150 @@ +// 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::RawSignatureValidationError, + webcrypto::{AsyncRawSignatureValidator, WindowOrWorker}, +}; + +/// 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", + ) + })?; + + 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"))?; + + let crypto_key: CryptoKey = JsFuture::from(promise) + .await + .map_err(|_err| { + RawSignatureValidationError::InternalError("unable to create CryptoKey promise") + })? + .into(); + + let algorithm = EcdsaParams(hash).as_js_object().map_err(|_err| { + RawSignatureValidationError::InternalError("error creating JS object for EcdsaParams") + })?; + + 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") + })?; + + let verified: JsValue = JsFuture::from(promise) + .await + .map_err(|_err| { + RawSignatureValidationError::InternalError( + "error creating JS future from SubtleCrypto promise", + ) + })? + .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/webcrypto/async_validators/ed25519_validator.rs b/internal/crypto/src/webcrypto/async_validators/ed25519_validator.rs new file mode 100644 index 000000000..fbf0b3530 --- /dev/null +++ b/internal/crypto/src/webcrypto/async_validators/ed25519_validator.rs @@ -0,0 +1,38 @@ +// 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. + +use async_trait::async_trait; + +use crate::{ + raw_signature::{RawSignatureValidationError, RawSignatureValidator}, + webcrypto::AsyncRawSignatureValidator, +}; + +/// An `Ed25519Validator` can validate raw signatures with the Ed25519 signature +/// algorithm. +pub struct Ed25519Validator {} + +#[async_trait(?Send)] +impl AsyncRawSignatureValidator for Ed25519Validator { + async fn validate_async( + &self, + sig: &[u8], + data: &[u8], + public_key: &[u8], + ) -> Result<(), RawSignatureValidationError> { + // Sync and async cases are identical for Ed25519. + + let sync_validator = crate::webcrypto::validators::Ed25519Validator {}; + sync_validator.validate(sig, data, public_key) + } +} diff --git a/internal/crypto/src/webcrypto/async_validators/mod.rs b/internal/crypto/src/webcrypto/async_validators/mod.rs new file mode 100644 index 000000000..2acaf4f22 --- /dev/null +++ b/internal/crypto/src/webcrypto/async_validators/mod.rs @@ -0,0 +1,114 @@ +// 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 bcder::Oid; + +use crate::{ + raw_signature::{oids::*, RawSignatureValidationError}, + SigningAlg, +}; + +/// An `AsyncRawSignatureValidator` implementation checks a signature encoded +/// using a specific signature algorithm and a private/public key pair. +/// +/// IMPORTANT: This signature is typically embedded in a wrapper provided by +/// another signature mechanism. In the C2PA ecosystem, this wrapper is +/// typically COSE, but `AsyncRawSignatureValidator` does not implement COSE. +/// +/// The WASM implementation of `c2pa-crypto` also implements +/// [`RawSignatureValidator`] (the synchronous version), but some encryption +/// algorithms are not fully supported. When possible, it's preferable to use +/// this implementation. +/// +/// [`RawSignatureValidator`]: crate::raw_signature::RawSignatureValidator +#[async_trait(?Send)] +pub trait AsyncRawSignatureValidator { + /// Return `true` if the signature `sig` is valid for the raw content `data` + /// and the public key `public_key`. + async fn validate_async( + &self, + sig: &[u8], + data: &[u8], + public_key: &[u8], + ) -> Result<(), RawSignatureValidationError>; +} + +/// 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)), + SigningAlg::Ed25519 => Some(Box::new(Ed25519Validator {})), + SigningAlg::Ps256 => Some(Box::new(RsaValidator::Ps256)), + SigningAlg::Ps384 => Some(Box::new(RsaValidator::Ps384)), + SigningAlg::Ps512 => Some(Box::new(RsaValidator::Ps512)), + } +} + +/// Return a built-in async signature validator for the requested signature +/// algorithm as identified by OID. +/// +/// TEMPORARILY PUBLIC: This will become `pub(crate)` once time stamp code moves +/// into c2pa-crypto. +pub fn async_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 hash_alg.as_ref() == SHA1_OID.as_bytes() { + return None; // not supported + } else 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() { + return Some(Box::new(RsaLegacyValidator::Rsa384)); + } else if hash_alg.as_ref() == SHA512_OID.as_bytes() { + return Some(Box::new(RsaLegacyValidator::Rsa512)); + } + } 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() + { + 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); + } + } else if sig_alg.as_ref() == ED25519_OID.as_bytes() { + return async_validator_for_signing_alg(SigningAlg::Ed25519); + } + + None +} + +pub(crate) mod ecdsa_validator; +use ecdsa_validator::EcdsaValidator; + +pub(crate) mod ed25519_validator; +use ed25519_validator::Ed25519Validator; + +pub(crate) mod rsa_legacy_validator; +use rsa_legacy_validator::RsaLegacyValidator; + +pub(crate) mod rsa_validator; +use rsa_validator::RsaValidator; diff --git a/internal/crypto/src/webcrypto/async_validators/rsa_legacy_validator.rs b/internal/crypto/src/webcrypto/async_validators/rsa_legacy_validator.rs new file mode 100644 index 000000000..2f3bcf297 --- /dev/null +++ b/internal/crypto/src/webcrypto/async_validators/rsa_legacy_validator.rs @@ -0,0 +1,48 @@ +// 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. + +use async_trait::async_trait; + +use crate::{ + raw_signature::{RawSignatureValidationError, RawSignatureValidator}, + webcrypto::AsyncRawSignatureValidator, +}; + +/// An `RsaLegacyValidator` can validate raw signatures with an RSA signature +/// algorithm that is not supported directly by C2PA. (Some RFC 3161 time stamp +/// providers issue these signatures, which is why it's supported here.) +pub(crate) enum RsaLegacyValidator { + Rsa256, + Rsa384, + Rsa512, +} + +#[async_trait(?Send)] +impl AsyncRawSignatureValidator for RsaLegacyValidator { + async fn validate_async( + &self, + sig: &[u8], + data: &[u8], + public_key: &[u8], + ) -> Result<(), RawSignatureValidationError> { + // Sync and async cases are identical for RSA. + + let sync_validator = match self { + Self::Rsa256 => crate::webcrypto::validators::RsaLegacyValidator::Rsa256, + Self::Rsa384 => crate::webcrypto::validators::RsaLegacyValidator::Rsa384, + Self::Rsa512 => crate::webcrypto::validators::RsaLegacyValidator::Rsa512, + }; + + sync_validator.validate(sig, data, public_key) + } +} diff --git a/internal/crypto/src/webcrypto/async_validators/rsa_validator.rs b/internal/crypto/src/webcrypto/async_validators/rsa_validator.rs new file mode 100644 index 000000000..2e71d8934 --- /dev/null +++ b/internal/crypto/src/webcrypto/async_validators/rsa_validator.rs @@ -0,0 +1,53 @@ +// 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 crate::{ + raw_signature::{RawSignatureValidationError, RawSignatureValidator}, + webcrypto::AsyncRawSignatureValidator, +}; + +/// An `RsaValidator` can validate raw signatures with one of the RSA-PSS +/// signature algorithms. +#[non_exhaustive] +pub enum RsaValidator { + /// RSASSA-PSS using SHA-256 and MGF1 with SHA-256 + Ps256, + + /// RSASSA-PSS using SHA-384 and MGF1 with SHA-384 + Ps384, + + /// RSASSA-PSS using SHA-512 and MGF1 with SHA-512 + Ps512, +} + +#[async_trait(?Send)] +impl AsyncRawSignatureValidator for RsaValidator { + async fn validate_async( + &self, + sig: &[u8], + data: &[u8], + public_key: &[u8], + ) -> Result<(), RawSignatureValidationError> { + // Sync and async cases are identical for RSA. + + let sync_validator = match self { + Self::Ps256 => crate::webcrypto::validators::RsaValidator::Ps256, + Self::Ps384 => crate::webcrypto::validators::RsaValidator::Ps384, + Self::Ps512 => crate::webcrypto::validators::RsaValidator::Ps512, + }; + + sync_validator.validate(sig, data, public_key) + } +} diff --git a/internal/crypto/src/webcrypto/mod.rs b/internal/crypto/src/webcrypto/mod.rs index e301271b4..fb51e2d32 100644 --- a/internal/crypto/src/webcrypto/mod.rs +++ b/internal/crypto/src/webcrypto/mod.rs @@ -19,6 +19,12 @@ //! //! [`SubtleCrypto`]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.SubtleCrypto.html +pub(crate) mod async_validators; +pub use async_validators::{ + async_validator_for_sig_and_hash_algs, async_validator_for_signing_alg, + AsyncRawSignatureValidator, +}; + pub mod validators; mod window_or_worker; diff --git a/internal/crypto/src/webcrypto/validators/rsa_legacy_validator.rs b/internal/crypto/src/webcrypto/validators/rsa_legacy_validator.rs index 574f8b82e..ce3667741 100644 --- a/internal/crypto/src/webcrypto/validators/rsa_legacy_validator.rs +++ b/internal/crypto/src/webcrypto/validators/rsa_legacy_validator.rs @@ -18,7 +18,7 @@ use rsa::{ RsaPublicKey, }; use spki::SubjectPublicKeyInfoRef; -use x509_parser::der_parser::ber::{parse_ber_sequence /* BerObject */}; +use x509_parser::der_parser::ber::parse_ber_sequence; use super::rsa_validator::biguint_val; use crate::raw_signature::{RawSignatureValidationError, RawSignatureValidator}; diff --git a/sdk/src/cose_validator.rs b/sdk/src/cose_validator.rs index 990e8b89c..6edfe20f2 100644 --- a/sdk/src/cose_validator.rs +++ b/sdk/src/cose_validator.rs @@ -37,6 +37,8 @@ use x509_parser::{ #[cfg(feature = "openssl")] use crate::openssl::verify_trust; +#[cfg(target_arch = "wasm32")] +use crate::wasm::webpki_trust_handler::verify_trust_async; use crate::{ error::{Error, Result}, settings::get_settings_value, @@ -46,10 +48,6 @@ use crate::{ validation_status, validator::ValidationInfo, }; -#[cfg(target_arch = "wasm32")] -use crate::{ - wasm::webcrypto_validator::validate_async, wasm::webpki_trust_handler::verify_trust_async, -}; pub(crate) const RSA_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .1); pub(crate) const EC_PUBLICKEY_OID: Oid<'static> = oid!(1.2.840 .10045 .2 .1); @@ -1319,14 +1317,17 @@ async fn validate_with_cert_async( let pk = signcert.public_key(); let pk_der = pk.raw; - if validate_async(signing_alg, sig, data, pk_der).await? { - Ok(CertInfo { - subject: extract_subject_from_cert(&signcert).unwrap_or_default(), - serial_number: extract_serial_from_cert(&signcert), - }) - } else { - Err(Error::CoseSignature) - } + let Some(validator) = c2pa_crypto::webcrypto::async_validator_for_signing_alg(signing_alg) + else { + return Err(Error::UnknownAlgorithm); + }; + + validator.validate_async(sig, data, pk_der).await?; + + Ok(CertInfo { + subject: extract_subject_from_cert(&signcert).unwrap_or_default(), + serial_number: extract_serial_from_cert(&signcert), + }) } #[cfg(not(target_arch = "wasm32"))] diff --git a/sdk/src/time_stamp.rs b/sdk/src/time_stamp.rs index 924e7cb7c..9a2c47e0c 100644 --- a/sdk/src/time_stamp.rs +++ b/sdk/src/time_stamp.rs @@ -32,12 +32,6 @@ use coset::{sig_structure_data, ProtectedHeader}; use serde::{Deserialize, Serialize}; use x509_certificate::DigestAlgorithm::{self}; -#[cfg(target_arch = "wasm32")] -use crate::cose_validator::{ - ECDSA_WITH_SHA256_OID, ECDSA_WITH_SHA384_OID, ECDSA_WITH_SHA512_OID, EC_PUBLICKEY_OID, - ED25519_OID, RSA_OID, SHA1_OID, SHA256_OID, SHA256_WITH_RSAENCRYPTION_OID, SHA384_OID, - SHA384_WITH_RSAENCRYPTION_OID, SHA512_OID, SHA512_WITH_RSAENCRYPTION_OID, -}; use crate::{ error::{Error, Result}, hash_utils::vec_compare, @@ -361,45 +355,6 @@ fn time_to_datetime(t: x509_certificate::asn1time::Time) -> chrono::DateTime 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 hash_alg.as_ref() == SHA1_OID.as_bytes() { - Some("sha1".to_string()) - } else if hash_alg.as_ref() == SHA256_OID.as_bytes() { - Some("rsa256".to_string()) - } else if hash_alg.as_ref() == SHA384_OID.as_bytes() { - Some("rsa384".to_string()) - } else if hash_alg.as_ref() == SHA512_OID.as_bytes() { - Some("rsa512".to_string()) - } else { - None - } - } 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() - { - if hash_alg.as_ref() == SHA256_OID.as_bytes() { - Some(c2pa_crypto::SigningAlg::Es256.to_string()) - } else if hash_alg.as_ref() == SHA384_OID.as_bytes() { - Some(c2pa_crypto::SigningAlg::Es384.to_string()) - } else if hash_alg.as_ref() == SHA512_OID.as_bytes() { - Some(c2pa_crypto::SigningAlg::Es512.to_string()) - } else { - None - } - } else if sig_alg.as_ref() == ED25519_OID.as_bytes() { - Some(c2pa_crypto::SigningAlg::Ed25519.to_string()) - } else { - None - } -} - // Returns TimeStamp token info if ts verifies against supplied data #[allow(unused_variables)] #[async_generic] @@ -592,23 +547,19 @@ pub(crate) fn verify_timestamp(ts: &[u8], data: &[u8]) -> Result { #[cfg(target_arch = "wasm32")] { if _sync { + // IMPORTANT: The synchronous implementation of validate_timestamp_sync + // on WASM is unable to support _some_ signature algorithms. The async path + // should be used whenever possible. validate_timestamp_sig(sig_alg, hash_alg, sig_val, &tbs, &signing_key_der)?; } else { - // TO REVIEW: Worth keeping this WASM-specific async path alive, or can we fully switch over to the synchronous path? (I'd prefer the latter.) - let mut certificate_der = Vec::::new(); - cert.encode_ref() - .write_encoded(bcder::Mode::Der, &mut certificate_der)?; - - if !crate::wasm::verify_data( - certificate_der, - get_validator_type(sig_alg, hash_alg), - sig_val.to_bytes().to_vec(), - tbs, + validate_timestamp_sig_async( + sig_alg, + hash_alg, + sig_val, + &tbs, + &signing_key_der, ) - .await? - { - return Err(Error::CoseTimeStampMismatch); - } + .await?; } } @@ -675,6 +626,28 @@ fn validate_timestamp_sig( Ok(()) } +#[cfg(target_arch = "wasm32")] +async fn validate_timestamp_sig_async( + sig_alg: &bcder::Oid, + hash_alg: &bcder::Oid, + sig_val: &OctetString, + tbs: &[u8], + signing_key_der: &[u8], +) -> Result<()> { + let Some(validator) = + c2pa_crypto::webcrypto::async_validator_for_sig_and_hash_algs(sig_alg, hash_alg) + else { + return Err(Error::CoseSignatureAlgorithmNotSupported); + }; + + validator + .validate_async(&sig_val.to_bytes(), tbs, signing_key_der) + .await + .map_err(|_| Error::CoseTimeStampMismatch)?; + + Ok(()) +} + // Get TimeStampResponse from DER TimeStampResp bytes pub(crate) fn get_timestamp_response(tsresp: &[u8]) -> Result { let ts = TimeStampResponse( diff --git a/sdk/src/wasm/mod.rs b/sdk/src/wasm/mod.rs index 8119f1471..dc929a189 100644 --- a/sdk/src/wasm/mod.rs +++ b/sdk/src/wasm/mod.rs @@ -19,12 +19,6 @@ pub(crate) use rsa_wasm_signer::RsaWasmSignerAsync; #[cfg(target_arch = "wasm32")] pub(crate) mod util; #[cfg(target_arch = "wasm32")] -pub(crate) mod webcrypto_validator; -#[cfg(target_arch = "wasm32")] -pub use webcrypto_validator::validate_async; -#[cfg(target_arch = "wasm32")] pub(crate) mod webpki_trust_handler; #[cfg(target_arch = "wasm32")] -pub(crate) use webpki_trust_handler::verify_data; -#[cfg(target_arch = "wasm32")] pub(crate) use webpki_trust_handler::WebTrustHandlerConfig; diff --git a/sdk/src/wasm/webcrypto_validator.rs b/sdk/src/wasm/webcrypto_validator.rs deleted file mode 100644 index 50f56ef5d..000000000 --- a/sdk/src/wasm/webcrypto_validator.rs +++ /dev/null @@ -1,556 +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. - -use std::convert::TryFrom; - -use c2pa_crypto::{webcrypto::WindowOrWorker, SigningAlg}; -use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array}; -use spki::SubjectPublicKeyInfoRef; -use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::JsFuture; -use web_sys::{CryptoKey, SubtleCrypto}; -use x509_parser::der_parser::ber::{parse_ber_sequence, BerObject}; - -use crate::{Error, Result}; - -pub struct EcKeyImportParams { - name: String, - named_curve: String, - hash: String, -} - -impl EcKeyImportParams { - pub fn new(name: &str, hash: &str, named_curve: &str) -> Self { - EcKeyImportParams { - name: name.to_owned(), - named_curve: named_curve.to_owned(), - hash: hash.to_owned(), - } - } - - pub fn as_js_object(&self) -> Object { - let obj = Object::new(); - Reflect::set(&obj, &"name".into(), &self.name.clone().into()).expect("not valid name"); - Reflect::set(&obj, &"namedCurve".into(), &self.named_curve.clone().into()) - .expect("not valid name"); - - let inner_obj = Object::new(); - Reflect::set(&inner_obj, &"name".into(), &self.hash.clone().into()) - .expect("not valid name"); - - Reflect::set(&obj, &"hash".into(), &inner_obj).expect("not valid name"); - - obj - } -} - -pub struct EcdsaParams { - name: String, - hash: String, -} - -impl EcdsaParams { - pub fn new(name: &str, hash: &str) -> Self { - EcdsaParams { - name: name.to_owned(), - hash: hash.to_owned(), - } - } - - pub fn as_js_object(&self) -> Object { - let obj = Object::new(); - Reflect::set(&obj, &"name".into(), &self.name.clone().into()).expect("not valid name"); - - let inner_obj = Object::new(); - Reflect::set(&inner_obj, &"name".into(), &self.hash.clone().into()) - .expect("not valid name"); - - Reflect::set(&obj, &"hash".into(), &inner_obj).expect("not valid name"); - - 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() -} - -async fn crypto_is_verified( - subtle_crypto: &SubtleCrypto, - alg: &Object, - key: &CryptoKey, - sig: &Object, - data: &Object, -) -> Result { - let promise = subtle_crypto - .verify_with_object_and_buffer_source_and_buffer_source(alg, key, sig, data) - .map_err(|_err| Error::WasmVerifier)?; - let verified: JsValue = JsFuture::from(promise) - .await - .map_err(|_err| Error::WasmVerifier)? - .into(); - let result = verified.is_truthy(); - web_sys::console::debug_2(&"verified".into(), &result.into()); - Ok(result) -} - -// Conversion utility from num-bigint::BigUint (used by x509_parser) -// to num-bigint-dig::BigUint (used by rsa) -fn biguint_val(ber_object: &BerObject) -> rsa::BigUint { - ber_object - .as_biguint() - .map(|x| x.to_u32_digits()) - .map(rsa::BigUint::new) - .unwrap_or_default() -} - -// Validate an Ed25519 signature for the provided data. The pkey must -// be the raw bytes representing CompressedEdwardsY. The length must 32 bytes. -fn ed25519_validate(sig: Vec, data: Vec, pkey: Vec) -> Result { - use ed25519_dalek::{Signature, Verifier, VerifyingKey, PUBLIC_KEY_LENGTH}; - - if pkey.len() == PUBLIC_KEY_LENGTH { - let ed_sig = Signature::from_slice(&sig).map_err(|_| Error::CoseInvalidCert)?; - - // convert to VerifyingKey - let mut cert_slice: [u8; 32] = Default::default(); - cert_slice.copy_from_slice(&pkey[0..PUBLIC_KEY_LENGTH]); - - let vk = VerifyingKey::from_bytes(&cert_slice).map_err(|_| Error::CoseInvalidCert)?; - - match vk.verify(&data, &ed_sig) { - Ok(_) => Ok(true), - Err(_) => Ok(false), - } - } else { - web_sys::console::debug_2( - &"Ed25519 public key incorrect length: ".into(), - &pkey.len().to_string().into(), - ); - Err(Error::CoseInvalidCert) - } -} - -pub(crate) async fn async_validate( - algo: String, - hash: String, - _salt_len: u32, - pkey: Vec, - sig: Vec, - data: Vec, -) -> Result { - use rsa::{ - sha2::{Sha256, Sha384, Sha512}, - RsaPublicKey, - }; - - let context = WindowOrWorker::new(); - let subtle_crypto = context?.subtle_crypto()?; - let sig_array_buf = data_as_array_buffer(&sig); - let data_array_buf = data_as_array_buffer(&data); - - match algo.as_ref() { - "RSASSA-PKCS1-v1_5" => { - use rsa::{pkcs1v15::Signature, signature::Verifier}; - - // used for certificate validation - let spki = SubjectPublicKeyInfoRef::try_from(pkey.as_ref()) - .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; - - let (_, seq) = parse_ber_sequence(&spki.subject_public_key.raw_bytes()) - .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; - - let modulus = biguint_val(&seq[0]); - let exp = biguint_val(&seq[1]); - let public_key = RsaPublicKey::new(modulus, exp) - .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; - let normalized_hash = hash.clone().replace("-", "").to_lowercase(); - - let result = match normalized_hash.as_ref() { - "sha256" => { - let vk = rsa::pkcs1v15::VerifyingKey::::new(public_key); - let signature: Signature = sig.as_slice().try_into().map_err(|_e| { - Error::WasmRsaKeyImport("could no process RSA signature".to_string()) - })?; - vk.verify(&data, &signature) - } - "sha384" => { - let vk = rsa::pkcs1v15::VerifyingKey::::new(public_key); - let signature: Signature = sig.as_slice().try_into().map_err(|_e| { - Error::WasmRsaKeyImport("could no process RSA signature".to_string()) - })?; - vk.verify(&data, &signature) - } - "sha512" => { - let vk = rsa::pkcs1v15::VerifyingKey::::new(public_key); - let signature: Signature = sig.as_slice().try_into().map_err(|_e| { - Error::WasmRsaKeyImport("could no process RSA signature".to_string()) - })?; - vk.verify(&data, &signature) - } - _ => return Err(Error::UnknownAlgorithm), - }; - - match result { - Ok(()) => { - web_sys::console::debug_1(&"RSA validation success:".into()); - Ok(true) - } - Err(err) => { - web_sys::console::debug_2( - &"RSA validation failed:".into(), - &err.to_string().into(), - ); - Ok(false) - } - } - } - "RSA-PSS" => { - use rsa::{pss::Signature, signature::Verifier}; - - let spki = SubjectPublicKeyInfoRef::try_from(pkey.as_ref()) - .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; - - let (_, seq) = parse_ber_sequence(&spki.subject_public_key.raw_bytes()) - .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; - - // We need to normalize this from SHA-256 (the format WebCrypto uses) to sha256 - // (the format the util function expects) so that it maps correctly - let normalized_hash = hash.clone().replace("-", "").to_lowercase(); - let modulus = biguint_val(&seq[0]); - let exp = biguint_val(&seq[1]); - let public_key = RsaPublicKey::new(modulus, exp) - .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; - - let result = match normalized_hash.as_ref() { - "sha256" => { - let vk = rsa::pss::VerifyingKey::::new(public_key); - let signature: Signature = sig.as_slice().try_into().map_err(|_e| { - Error::WasmRsaKeyImport("could no process RSA signature".to_string()) - })?; - vk.verify(&data, &signature) - } - "sha384" => { - let vk = rsa::pss::VerifyingKey::::new(public_key); - let signature: Signature = sig.as_slice().try_into().map_err(|_e| { - Error::WasmRsaKeyImport("could no process RSA signature".to_string()) - })?; - vk.verify(&data, &signature) - } - "sha512" => { - let vk = rsa::pss::VerifyingKey::::new(public_key); - let signature: Signature = sig.as_slice().try_into().map_err(|_e| { - Error::WasmRsaKeyImport("could no process RSA signature".to_string()) - })?; - vk.verify(&data, &signature) - } - _ => return Err(Error::UnknownAlgorithm), - }; - - match result { - Ok(()) => { - web_sys::console::debug_1(&"RSA-PSS validation success:".into()); - Ok(true) - } - Err(err) => { - web_sys::console::debug_2( - &"RSA-PSS validation failed:".into(), - &err.to_string().into(), - ); - Ok(false) - } - } - } - "ECDSA" => { - // Create Key - let named_curve = match hash.as_ref() { - "SHA-256" => "P-256".to_string(), - "SHA-384" => "P-384".to_string(), - "SHA-512" => "P-521".to_string(), - _ => return Err(Error::UnsupportedType), - }; - let mut algorithm = EcKeyImportParams::new(&algo, &hash, &named_curve).as_js_object(); - let key_array_buf = data_as_array_buffer(&pkey); - 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| Error::WasmKey)?; - let crypto_key: CryptoKey = JsFuture::from(promise) - .await - .map_err(|_| Error::CoseInvalidCert)? - .into(); - web_sys::console::debug_2(&"CryptoKey".into(), &crypto_key); - - // Create verifier - algorithm = EcdsaParams::new(&algo, &hash).as_js_object(); - crypto_is_verified( - &subtle_crypto, - &algorithm, - &crypto_key, - &sig_array_buf, - &data_array_buf, - ) - .await - } - "ED25519" => { - use x509_parser::{prelude::*, public_key::PublicKey}; - - // pull out raw Ed code points - if let Ok((_, certificate_public_key)) = SubjectPublicKeyInfo::from_der(&pkey) { - match certificate_public_key.parsed() { - Ok(key) => match key { - PublicKey::Unknown(raw_key) => { - ed25519_validate(sig, data, raw_key.to_vec()) - } - _ => Err(Error::OtherError( - "could not unwrap Ed25519 public key".into(), - )), - }, - Err(_) => Err(Error::OtherError( - "could not recognize Ed25519 public key".into(), - )), - } - } else { - Err(Error::OtherError( - "could not parse Ed25519 public key".into(), - )) - } - } - _ => Err(Error::UnsupportedType), - } -} - -// This interface is called from CoseValidator. RSA validation not supported here. -pub async fn validate_async(alg: SigningAlg, sig: &[u8], data: &[u8], pkey: &[u8]) -> Result { - web_sys::console::debug_2(&"Validating with algorithm".into(), &alg.to_string().into()); - - match alg { - SigningAlg::Ps256 => { - async_validate( - "RSA-PSS".to_string(), - "SHA-256".to_string(), - 32, - pkey.to_vec(), - sig.to_vec(), - data.to_vec(), - ) - .await - } - SigningAlg::Ps384 => { - async_validate( - "RSA-PSS".to_string(), - "SHA-384".to_string(), - 48, - pkey.to_vec(), - sig.to_vec(), - data.to_vec(), - ) - .await - } - SigningAlg::Ps512 => { - async_validate( - "RSA-PSS".to_string(), - "SHA-512".to_string(), - 64, - pkey.to_vec(), - sig.to_vec(), - data.to_vec(), - ) - .await - } - // "rs256" => { - // async_validate( - // "RSASSA-PKCS1-v1_5".to_string(), - // "SHA-256".to_string(), - // 0, - // pkey.to_vec(), - // sig.to_vec(), - // data.to_vec(), - // ) - // .await - // } - // "rs384" => { - // async_validate( - // "RSASSA-PKCS1-v1_5".to_string(), - // "SHA-384".to_string(), - // 0, - // pkey.to_vec(), - // sig.to_vec(), - // data.to_vec(), - // ) - // .await - // } - // "rs512" => { - // async_validate( - // "RSASSA-PKCS1-v1_5".to_string(), - // "SHA-512".to_string(), - // 0, - // pkey.to_vec(), - // sig.to_vec(), - // data.to_vec(), - // ) - // .await - // } - SigningAlg::Es256 => { - async_validate( - "ECDSA".to_string(), - "SHA-256".to_string(), - 0, - pkey.to_vec(), - sig.to_vec(), - data.to_vec(), - ) - .await - } - SigningAlg::Es384 => { - async_validate( - "ECDSA".to_string(), - "SHA-384".to_string(), - 0, - pkey.to_vec(), - sig.to_vec(), - data.to_vec(), - ) - .await - } - SigningAlg::Es512 => { - async_validate( - "ECDSA".to_string(), - "SHA-512".to_string(), - 0, - pkey.to_vec(), - sig.to_vec(), - data.to_vec(), - ) - .await - } - SigningAlg::Ed25519 => { - async_validate( - "ED25519".to_string(), - "SHA-512".to_string(), - 0, - pkey.to_vec(), - sig.to_vec(), - data.to_vec(), - ) - .await - } - } -} - -#[cfg(test)] -pub mod tests { - #![allow(clippy::unwrap_used)] - - use c2pa_crypto::SigningAlg; - #[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(not(target_arch = "wasm32"), test)] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[wasm_bindgen_test] - async fn test_async_verify_rsa_pss() { - // PS signatures - let sig_bytes = include_bytes!("../../tests/fixtures/sig_ps256.data"); - let data_bytes = include_bytes!("../../tests/fixtures/data_ps256.data"); - let key_bytes = include_bytes!("../../tests/fixtures/key_ps256.data"); - - let validated = validate_async(SigningAlg::Ps256, sig_bytes, data_bytes, key_bytes) - .await - .unwrap(); - - assert_eq!(validated, true); - } - - #[cfg_attr(not(target_arch = "wasm32"), test)] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[wasm_bindgen_test] - async fn test_async_verify_ecdsa() { - // EC signatures - let sig_es384_bytes = include_bytes!("../../tests/fixtures/sig_es384.data"); - let data_es384_bytes = include_bytes!("../../tests/fixtures/data_es384.data"); - let key_es384_bytes = include_bytes!("../../tests/fixtures/key_es384.data"); - - let mut validated = validate_async( - SigningAlg::Es384, - sig_es384_bytes, - data_es384_bytes, - key_es384_bytes, - ) - .await - .unwrap(); - - assert_eq!(validated, true); - - let sig_es512_bytes = include_bytes!("../../tests/fixtures/sig_es512.data"); - let data_es512_bytes = include_bytes!("../../tests/fixtures/data_es512.data"); - let key_es512_bytes = include_bytes!("../../tests/fixtures/key_es512.data"); - - validated = validate_async( - SigningAlg::Es512, - sig_es512_bytes, - data_es512_bytes, - key_es512_bytes, - ) - .await - .unwrap(); - - assert_eq!(validated, true); - - let sig_es256_bytes = include_bytes!("../../tests/fixtures/sig_es256.data"); - let data_es256_bytes = include_bytes!("../../tests/fixtures/data_es256.data"); - let key_es256_bytes = include_bytes!("../../tests/fixtures/key_es256.data"); - - let validated = validate_async( - SigningAlg::Es256, - sig_es256_bytes, - data_es256_bytes, - key_es256_bytes, - ) - .await - .unwrap(); - - assert_eq!(validated, true); - } - - #[cfg_attr(not(target_arch = "wasm32"), test)] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - #[wasm_bindgen_test] - #[ignore] - async fn test_async_verify_bad() { - let sig_bytes = include_bytes!("../../tests/fixtures/sig_ps256.data"); - let data_bytes = include_bytes!("../../tests/fixtures/data_ps256.data"); - let key_bytes = include_bytes!("../../tests/fixtures/key_ps256.data"); - - let mut bad_bytes = data_bytes.to_vec(); - bad_bytes[0] = b'c'; - bad_bytes[1] = b'2'; - bad_bytes[2] = b'p'; - bad_bytes[3] = b'a'; - - let validated = validate_async(SigningAlg::Ps256, sig_bytes, &bad_bytes, key_bytes) - .await - .unwrap(); - - assert_eq!(validated, false); - } -} diff --git a/sdk/src/wasm/webpki_trust_handler.rs b/sdk/src/wasm/webpki_trust_handler.rs index 5c763001f..d8c3d8eeb 100644 --- a/sdk/src/wasm/webpki_trust_handler.rs +++ b/sdk/src/wasm/webpki_trust_handler.rs @@ -18,7 +18,10 @@ use std::{ }; use asn1_rs::{nom::AsBytes, Any, Class, Header, Tag}; -use c2pa_crypto::{base64, SigningAlg}; +use c2pa_crypto::{ + base64, raw_signature::RawSignatureValidationError, webcrypto::async_validator_for_signing_alg, + SigningAlg, +}; use x509_parser::{ der_parser::der::{parse_der_integer, parse_der_sequence_of}, prelude::*, @@ -31,7 +34,6 @@ use crate::{ trust_handler::{ has_allowed_oid, load_eku_configuration, load_trust_from_data, TrustHandlerConfig, }, - wasm::webcrypto_validator::async_validate, }; // Struct to handle verification of trust chains using WebPki @@ -292,7 +294,7 @@ fn cert_signing_alg(cert: &x509_parser::certificate::X509Certificate) -> Option< Some(signing_alg) } -pub(crate) async fn verify_data( +async fn verify_data( cert_der: Vec, sig_alg: Option, sig: Vec, @@ -305,46 +307,39 @@ pub(crate) async fn verify_data( let certificate_public_key = cert.public_key(); - if let Some(cert_alg_string) = sig_alg { - let (algo, hash, salt_len) = match cert_alg_string.as_str() { - "rsa256" => ("RSASSA-PKCS1-v1_5".to_string(), "SHA-256".to_string(), 0), - "rsa384" => ("RSASSA-PKCS1-v1_5".to_string(), "SHA-384".to_string(), 0), - "rsa512" => ("RSASSA-PKCS1-v1_5".to_string(), "SHA-512".to_string(), 0), - "es256" => ("ECDSA".to_string(), "SHA-256".to_string().to_string(), 0), - "es384" => ("ECDSA".to_string(), "SHA-384".to_string(), 0), - "es512" => ("ECDSA".to_string(), "SHA-512".to_string(), 0), - "ps256" => ("RSA-PSS".to_string(), "SHA-256".to_string(), 32), - "ps384" => ("RSA-PSS".to_string(), "SHA-384".to_string(), 48), - "ps512" => ("RSA-PSS".to_string(), "SHA-512".to_string(), 64), - "ed25519" => ("ED25519".to_string(), "SHA-512".to_string(), 0), - _ => return Err(Error::UnsupportedType), - }; + let Some(cert_alg_string) = sig_alg else { + return Err(Error::BadParam("unknown alg processing cert".to_string())); + }; - let adjusted_sig = if cert_alg_string.starts_with("es") { - let parsed_alg_string: SigningAlg = cert_alg_string - .parse() - .map_err(|_| Error::UnknownAlgorithm)?; - match der_to_p1363(&sig, parsed_alg_string) { - Some(p1363) => p1363, - None => sig, - } - } else { - sig - }; + let signing_alg: SigningAlg = cert_alg_string + .parse() + .map_err(|_| Error::UnknownAlgorithm)?; - async_validate( - algo, - hash, - salt_len, - certificate_public_key.raw.to_vec(), - adjusted_sig, - data, - ) - .await + // 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. + let adjusted_sig = if cert_alg_string.starts_with("es") { + match der_to_p1363(&sig, signing_alg) { + Some(p1363) => p1363, + None => sig, + } } else { - return Err(Error::BadParam("unknown alg processing cert".to_string())); + sig + }; + + let Some(validator) = async_validator_for_signing_alg(signing_alg) else { + return Err(Error::UnknownAlgorithm); + }; + + let result = validator + .validate_async(&adjusted_sig, &data, certificate_public_key.raw.as_ref()) + .await; + + match result { + Ok(()) => Ok(true), + Err(RawSignatureValidationError::SignatureMismatch) => Ok(false), + Err(err) => Err(err.into()), } } + // convert der signatures to P1363 format: r | s fn der_to_p1363(data: &[u8], alg: SigningAlg) -> Option> { // handle if this is a der sequence