From 52124e52dc80d6ba33acae39cfd133402c3f1fb6 Mon Sep 17 00:00:00 2001 From: Eric Scouten Date: Tue, 10 Dec 2024 16:45:43 -0800 Subject: [PATCH] feat: Add `RawSigner` trait to `c2pa-crypto` (derived from `c2pa::Signer`) (#716) --- docs/usage.md | 1 - internal/crypto/Cargo.toml | 6 +- internal/crypto/src/openssl/cert_chain.rs | 48 +++ internal/crypto/src/openssl/mod.rs | 3 + .../src/openssl/signers/ecdsa_signer.rs | 140 ++++++++ .../src/openssl/signers/ed25519_signer.rs | 105 ++++++ internal/crypto/src/openssl/signers/mod.rs | 67 ++++ .../crypto/src/openssl/signers/rsa_signer.rs | 186 +++++++++++ internal/crypto/src/p1363.rs | 59 ++++ internal/crypto/src/raw_signature/mod.rs | 6 + internal/crypto/src/raw_signature/signer.rs | 269 ++++++++++++++++ .../tests/fixtures/raw_signature/ed25519.priv | 3 + .../tests/fixtures/raw_signature/es256.priv | 5 + .../tests/fixtures/raw_signature/es384.priv | 6 + .../tests/fixtures/raw_signature/es512.priv | 8 + internal/crypto/src/tests/openssl/mod.rs | 1 + .../src/tests/openssl/signers/ecdsa_signer.rs | 176 ++++++++++ .../tests/openssl/signers/ed25519_signer.rs | 72 +++++ .../crypto/src/tests/openssl/signers/mod.rs | 16 + .../src/tests/openssl/signers/rsa_signer.rs | 176 ++++++++++ internal/crypto/src/tests/webcrypto/mod.rs | 1 + .../tests/webcrypto/signers/ed25519_signer.rs | 42 +++ .../crypto/src/tests/webcrypto/signers/mod.rs | 14 + internal/crypto/src/webcrypto/mod.rs | 1 + .../src/webcrypto/signers/ed25519_signer.rs | 101 ++++++ internal/crypto/src/webcrypto/signers/mod.rs | 48 +++ sdk/Cargo.toml | 1 - sdk/src/asset_handlers/c2pa_io.rs | 8 +- sdk/src/builder.rs | 19 +- sdk/src/callback_signer.rs | 14 +- sdk/src/cose_sign.rs | 79 ++++- sdk/src/cose_validator.rs | 45 ++- sdk/src/create_signer.rs | 41 +-- sdk/src/error.rs | 3 + sdk/src/jumbf_io.rs | 6 +- sdk/src/manifest.rs | 56 ++-- sdk/src/openssl/ec_signer.rs | 171 ---------- sdk/src/openssl/ed_signer.rs | 130 -------- sdk/src/openssl/mod.rs | 84 ----- sdk/src/openssl/openssl_trust_handler.rs | 68 ++-- sdk/src/openssl/rsa_signer.rs | 303 ------------------ sdk/src/openssl/temp_signer.rs | 184 ----------- sdk/src/openssl/temp_signer_async.rs | 101 ------ sdk/src/resource_store.rs | 7 +- sdk/src/signer.rs | 167 +++++++++- sdk/src/store.rs | 80 ++--- sdk/src/utils/mod.rs | 4 +- sdk/src/utils/sig_utils.rs | 73 ----- sdk/src/utils/test.rs | 93 +----- sdk/src/utils/test_signer.rs | 146 +++++++++ sdk/src/wasm/rsa_wasm_signer.rs | 5 +- sdk/tests/common/test_signer.rs | 9 +- sdk/tests/v2_api_integration.rs | 4 +- 53 files changed, 2117 insertions(+), 1344 deletions(-) create mode 100644 internal/crypto/src/openssl/cert_chain.rs create mode 100644 internal/crypto/src/openssl/signers/ecdsa_signer.rs create mode 100644 internal/crypto/src/openssl/signers/ed25519_signer.rs create mode 100644 internal/crypto/src/openssl/signers/mod.rs create mode 100644 internal/crypto/src/openssl/signers/rsa_signer.rs create mode 100644 internal/crypto/src/raw_signature/signer.rs create mode 100644 internal/crypto/src/tests/fixtures/raw_signature/ed25519.priv create mode 100644 internal/crypto/src/tests/fixtures/raw_signature/es256.priv create mode 100644 internal/crypto/src/tests/fixtures/raw_signature/es384.priv create mode 100644 internal/crypto/src/tests/fixtures/raw_signature/es512.priv create mode 100644 internal/crypto/src/tests/openssl/signers/ecdsa_signer.rs create mode 100644 internal/crypto/src/tests/openssl/signers/ed25519_signer.rs create mode 100644 internal/crypto/src/tests/openssl/signers/mod.rs create mode 100644 internal/crypto/src/tests/openssl/signers/rsa_signer.rs create mode 100644 internal/crypto/src/tests/webcrypto/signers/ed25519_signer.rs create mode 100644 internal/crypto/src/tests/webcrypto/signers/mod.rs create mode 100644 internal/crypto/src/webcrypto/signers/ed25519_signer.rs create mode 100644 internal/crypto/src/webcrypto/signers/mod.rs delete mode 100644 sdk/src/openssl/ec_signer.rs delete mode 100644 sdk/src/openssl/ed_signer.rs delete mode 100644 sdk/src/openssl/rsa_signer.rs delete mode 100644 sdk/src/openssl/temp_signer.rs delete mode 100644 sdk/src/openssl/temp_signer_async.rs delete mode 100644 sdk/src/utils/sig_utils.rs create mode 100644 sdk/src/utils/test_signer.rs diff --git a/docs/usage.md b/docs/usage.md index 417857ed5..54ce6bfc0 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -42,7 +42,6 @@ The Rust library crate provides the following capabilities: * `no_interleaved_io` forces fully-synchronous I/O; otherwise, the library uses threaded I/O for some operations to improve performance. * `fetch_remote_manifests` enables the verification step to retrieve externally referenced manifest stores. External manifests are only fetched if there is no embedded manifest store and no locally adjacent .c2pa manifest store file of the same name. * `json_schema` is used by `make schema` to produce a JSON schema document that represents the `ManifestStore` data structures. -* `psxxx_ocsp_stapling_experimental` enables a demonstration feature that attempts to fetch the OCSP data from the OCSP responders listed in the manifest signing certificate. The response becomes part of the manifest and is used to prove the certificate was not revoked at the time of signing. This is only implemented for PS256, PS384 and PS512 signatures and is intended as a demonstration. * `openssl_ffi_mutex` prevents multiple threads from accessing the C OpenSSL library simultaneously. (This library is not re-entrant.) In a multi-threaded process (such as Cargo's test runner), this can lead to unpredictable behavior. ### New API diff --git a/internal/crypto/Cargo.toml b/internal/crypto/Cargo.toml index 06b3712f6..3137466a3 100644 --- a/internal/crypto/Cargo.toml +++ b/internal/crypto/Cargo.toml @@ -37,6 +37,7 @@ bcder = "0.7.3" bytes = "1.7.2" c2pa-status-tracker = { path = "../status-tracker", version = "0.1.0" } ciborium = "0.2.2" +const-hex = "1.14" coset = "0.3.1" getrandom = { version = "0.2.7", features = ["js"] } hex = "0.4.3" @@ -74,7 +75,7 @@ features = ["now", "wasmbind"] [target.'cfg(target_arch = "wasm32")'.dependencies] async-trait = { version = "0.1.77" } ecdsa = "0.16.9" -ed25519-dalek = "2.1.1" +ed25519-dalek = { version = "2.1.1", features = ["alloc", "digest", "pem", "pkcs8"] } p256 = "0.13.2" p384 = "0.13.0" rsa = { version = "0.9.6", features = ["sha2"] } @@ -95,5 +96,8 @@ web-time = "1.1" getrandom = { version = "0.2.7", features = ["js"] } js-sys = "0.3.58" +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +actix = "0.13.1" + [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3.31" diff --git a/internal/crypto/src/openssl/cert_chain.rs b/internal/crypto/src/openssl/cert_chain.rs new file mode 100644 index 000000000..f8442bfb3 --- /dev/null +++ b/internal/crypto/src/openssl/cert_chain.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. + +#![allow(dead_code)] // TEMPORARY while refactoring + +use openssl::x509::X509; + +// Verify the certificate chain order. +// +// Return `true` if each cert in the chain can be verified as issued by the next +// issuer. +pub(crate) fn check_chain_order(certs: &[X509]) -> bool { + // IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please + // don't make this pub or pub(crate) without finding a way to ensure that + // precondition. + + let mut iter = certs.iter().peekable(); + + while let Some(cert) = iter.next() { + let Some(next) = iter.peek() else { + break; + }; + + let Ok(pkey) = next.public_key() else { + return false; + }; + + let Ok(verified) = cert.verify(&pkey) else { + return false; + }; + + if !verified { + return false; + } + } + + true +} diff --git a/internal/crypto/src/openssl/mod.rs b/internal/crypto/src/openssl/mod.rs index 2b0ad9437..78fda28d4 100644 --- a/internal/crypto/src/openssl/mod.rs +++ b/internal/crypto/src/openssl/mod.rs @@ -18,7 +18,10 @@ //! //! [`openssl` native code library]: https://crates.io/crates/openssl +mod cert_chain; + mod ffi_mutex; pub use ffi_mutex::{OpenSslMutex, OpenSslMutexUnavailable}; +pub(crate) mod signers; pub mod validators; diff --git a/internal/crypto/src/openssl/signers/ecdsa_signer.rs b/internal/crypto/src/openssl/signers/ecdsa_signer.rs new file mode 100644 index 000000000..aa35635e4 --- /dev/null +++ b/internal/crypto/src/openssl/signers/ecdsa_signer.rs @@ -0,0 +1,140 @@ +// 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 openssl::{ + ec::EcKey, + hash::MessageDigest, + pkey::{PKey, Private}, + sign::Signer, + x509::X509, +}; + +use crate::{ + openssl::{cert_chain::check_chain_order, OpenSslMutex}, + p1363::der_to_p1363, + raw_signature::{RawSigner, RawSignerError}, + time_stamp::TimeStampProvider, + SigningAlg, +}; + +enum EcdsaSigningAlg { + Es256, + Es384, + Es512, +} + +/// Implements `Signer` trait using OpenSSL's implementation of +/// ECDSA encryption. +pub struct EcdsaSigner { + alg: EcdsaSigningAlg, + + cert_chain: Vec, + cert_chain_len: usize, + + private_key: EcKey, + + time_stamp_service_url: Option, + time_stamp_size: usize, +} + +impl EcdsaSigner { + pub(crate) fn from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, + ) -> Result { + let alg = match alg { + SigningAlg::Es256 => EcdsaSigningAlg::Es256, + SigningAlg::Es384 => EcdsaSigningAlg::Es384, + SigningAlg::Es512 => EcdsaSigningAlg::Es512, + _ => { + return Err(RawSignerError::InternalError( + "EcdsaSigner should be used only for SigningAlg::Es***".to_string(), + )); + } + }; + + let _openssl = OpenSslMutex::acquire()?; + + let cert_chain = X509::stack_from_pem(cert_chain)?; + let cert_chain_len = cert_chain.len(); + + if !check_chain_order(&cert_chain) { + return Err(RawSignerError::InvalidSigningCredentials( + "certificate chain in incorrect order".to_string(), + )); + } + + let private_key = EcKey::private_key_from_pem(private_key)?; + + Ok(EcdsaSigner { + alg, + cert_chain, + cert_chain_len, + private_key, + time_stamp_service_url, + time_stamp_size: 10000, + // TO DO: Call out to time stamp service to get actual time stamp and use that size? + }) + } +} + +impl RawSigner for EcdsaSigner { + fn sign(&self, data: &[u8]) -> Result, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + let private_key = PKey::from_ec_key(self.private_key.clone())?; + + let mut signer = match self.alg { + EcdsaSigningAlg::Es256 => Signer::new(MessageDigest::sha256(), &private_key)?, + EcdsaSigningAlg::Es384 => Signer::new(MessageDigest::sha384(), &private_key)?, + EcdsaSigningAlg::Es512 => Signer::new(MessageDigest::sha512(), &private_key)?, + }; + + signer.update(data)?; + + let der_sig = signer.sign_to_vec()?; + der_to_p1363(&der_sig, self.alg()) + } + + fn alg(&self) -> SigningAlg { + match self.alg { + EcdsaSigningAlg::Es256 => SigningAlg::Es256, + EcdsaSigningAlg::Es384 => SigningAlg::Es384, + EcdsaSigningAlg::Es512 => SigningAlg::Es512, + } + } + + fn reserve_size(&self) -> usize { + 1024 + self.cert_chain_len + self.time_stamp_size + } + + fn cert_chain(&self) -> Result>, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + self.cert_chain + .iter() + .map(|cert| { + cert.to_der() + .map_err(|e| RawSignerError::OpenSslError(e.to_string())) + }) + .collect() + } +} + +impl TimeStampProvider for EcdsaSigner { + fn time_stamp_service_url(&self) -> Option { + self.time_stamp_service_url.clone() + } +} diff --git a/internal/crypto/src/openssl/signers/ed25519_signer.rs b/internal/crypto/src/openssl/signers/ed25519_signer.rs new file mode 100644 index 000000000..63e6f574f --- /dev/null +++ b/internal/crypto/src/openssl/signers/ed25519_signer.rs @@ -0,0 +1,105 @@ +// 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 openssl::{ + pkey::{PKey, Private}, + sign::Signer, + x509::X509, +}; + +use crate::{ + openssl::{cert_chain::check_chain_order, OpenSslMutex}, + raw_signature::{RawSigner, RawSignerError}, + time_stamp::TimeStampProvider, + SigningAlg, +}; + +/// Implements `RawSigner` trait using OpenSSL's implementation of +/// Edwards Curve encryption. +pub struct Ed25519Signer { + cert_chain: Vec, + cert_chain_len: usize, + + private_key: PKey, + + time_stamp_service_url: Option, + time_stamp_size: usize, +} + +impl Ed25519Signer { + pub(crate) fn from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + time_stamp_service_url: Option, + ) -> Result { + let _openssl = OpenSslMutex::acquire()?; + + let cert_chain = X509::stack_from_pem(cert_chain)?; + let cert_chain_len = cert_chain.len(); + + if !check_chain_order(&cert_chain) { + return Err(RawSignerError::InvalidSigningCredentials( + "certificate chain in incorrect order".to_string(), + )); + } + + let private_key = PKey::private_key_from_pem(private_key)?; + + Ok(Ed25519Signer { + cert_chain, + cert_chain_len, + + private_key, + + time_stamp_service_url, + time_stamp_size: 10000, + // TO DO: Call out to time stamp service to get actual time stamp and use that size? + }) + } +} + +impl RawSigner for Ed25519Signer { + fn sign(&self, data: &[u8]) -> Result, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + let mut signer = Signer::new_without_digest(&self.private_key)?; + + Ok(signer.sign_oneshot_to_vec(data)?) + } + + fn alg(&self) -> SigningAlg { + SigningAlg::Ed25519 + } + + fn reserve_size(&self) -> usize { + 1024 + self.cert_chain_len + self.time_stamp_size + } + + fn cert_chain(&self) -> Result>, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + self.cert_chain + .iter() + .map(|cert| { + cert.to_der() + .map_err(|e| RawSignerError::OpenSslError(e.to_string())) + }) + .collect() + } +} + +impl TimeStampProvider for Ed25519Signer { + fn time_stamp_service_url(&self) -> Option { + self.time_stamp_service_url.clone() + } +} diff --git a/internal/crypto/src/openssl/signers/mod.rs b/internal/crypto/src/openssl/signers/mod.rs new file mode 100644 index 000000000..639d0a1c5 --- /dev/null +++ b/internal/crypto/src/openssl/signers/mod.rs @@ -0,0 +1,67 @@ +// 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 OpenSSL logic for generating raw signatures to this +//! crate's [`RawSigner`] trait. + +use crate::{ + raw_signature::{RawSigner, RawSignerError}, + SigningAlg, +}; + +mod ecdsa_signer; +mod ed25519_signer; +mod rsa_signer; + +/// Return a built-in [`RawSigner`] instance using the provided signing +/// certificate and private key. +/// +/// Which signers are available may vary depending on the platform and which +/// crate features were enabled. +/// +/// Returns `None` if the signing algorithm is unsupported. May return an `Err` +/// response if the certificate chain or private key are invalid. +pub(crate) fn signer_from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, +) -> Result, RawSignerError> { + match alg { + SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => Ok(Box::new( + ecdsa_signer::EcdsaSigner::from_cert_chain_and_private_key( + cert_chain, + private_key, + alg, + time_stamp_service_url, + )?, + )), + + SigningAlg::Ed25519 => Ok(Box::new( + ed25519_signer::Ed25519Signer::from_cert_chain_and_private_key( + cert_chain, + private_key, + time_stamp_service_url, + )?, + )), + + SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => Ok(Box::new( + rsa_signer::RsaSigner::from_cert_chain_and_private_key( + cert_chain, + private_key, + alg, + time_stamp_service_url, + )?, + )), + } +} diff --git a/internal/crypto/src/openssl/signers/rsa_signer.rs b/internal/crypto/src/openssl/signers/rsa_signer.rs new file mode 100644 index 000000000..98e58cd26 --- /dev/null +++ b/internal/crypto/src/openssl/signers/rsa_signer.rs @@ -0,0 +1,186 @@ +// 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 openssl::{ + hash::MessageDigest, + pkey::{PKey, Private}, + rsa::{Rsa, RsaPrivateKeyBuilder}, + sign::Signer, + x509::X509, +}; + +use crate::{ + openssl::{cert_chain::check_chain_order, OpenSslMutex}, + raw_signature::{RawSigner, RawSignerError}, + time_stamp::TimeStampProvider, + SigningAlg, +}; + +enum RsaSigningAlg { + Ps256, + Ps384, + Ps512, +} + +/// Implements [`RawSigner`] trait using OpenSSL's implementation of SHA256 + +/// RSA encryption. +pub(crate) struct RsaSigner { + alg: RsaSigningAlg, + + cert_chain: Vec, + cert_chain_len: usize, + + private_key: PKey, + + time_stamp_service_url: Option, + time_stamp_size: usize, +} + +impl RsaSigner { + pub(crate) fn from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, + ) -> Result { + let _openssl = OpenSslMutex::acquire()?; + + let cert_chain = X509::stack_from_pem(cert_chain)?; + let cert_chain_len = cert_chain.len(); + + if !check_chain_order(&cert_chain) { + return Err(RawSignerError::InvalidSigningCredentials( + "certificate chain in incorrect order".to_string(), + )); + } + + // Rebuild RSA keys to eliminate incompatible values. + let private_key = Rsa::private_key_from_pem(private_key)?; + + let n = private_key.n().to_owned()?; + let e = private_key.e().to_owned()?; + let d = private_key.d().to_owned()?; + let po = private_key.p(); + let qo = private_key.q(); + let dmp1o = private_key.dmp1(); + let dmq1o = private_key.dmq1(); + let iqmpo = private_key.iqmp(); + + let mut pk_builder = RsaPrivateKeyBuilder::new(n, e, d)?; + + if let Some(p) = po { + if let Some(q) = qo { + pk_builder = pk_builder.set_factors(p.to_owned()?, q.to_owned()?)?; + } + } + + if let Some(dmp1) = dmp1o { + if let Some(dmq1) = dmq1o { + if let Some(iqmp) = iqmpo { + pk_builder = pk_builder.set_crt_params( + dmp1.to_owned()?, + dmq1.to_owned()?, + iqmp.to_owned()?, + )?; + } + } + } + + let private_key = PKey::from_rsa(pk_builder.build())?; + + let alg: RsaSigningAlg = match alg { + SigningAlg::Ps256 => RsaSigningAlg::Ps256, + SigningAlg::Ps384 => RsaSigningAlg::Ps384, + SigningAlg::Ps512 => RsaSigningAlg::Ps512, + _ => { + return Err(RawSignerError::InternalError( + "RsaSigner should be used only for SigningAlg::Ps***".to_string(), + )); + } + }; + + Ok(RsaSigner { + alg, + cert_chain, + private_key, + cert_chain_len, + time_stamp_service_url, + time_stamp_size: 10000, + // TO DO: Call out to time stamp service to get actual time stamp and use that size? + }) + } +} + +impl RawSigner for RsaSigner { + fn sign(&self, data: &[u8]) -> Result, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + let mut signer = match self.alg { + RsaSigningAlg::Ps256 => { + let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?; + signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; + signer.set_rsa_mgf1_md(MessageDigest::sha256())?; + signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; + signer + } + + RsaSigningAlg::Ps384 => { + let mut signer = Signer::new(MessageDigest::sha384(), &self.private_key)?; + signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; + signer.set_rsa_mgf1_md(MessageDigest::sha384())?; + signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; + signer + } + + RsaSigningAlg::Ps512 => { + let mut signer = Signer::new(MessageDigest::sha512(), &self.private_key)?; + signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; + signer.set_rsa_mgf1_md(MessageDigest::sha512())?; + signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; + signer + } + }; + + Ok(signer.sign_oneshot_to_vec(data)?) + } + + fn reserve_size(&self) -> usize { + 1024 + self.cert_chain_len + self.time_stamp_size + } + + fn cert_chain(&self) -> Result>, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + self.cert_chain + .iter() + .map(|cert| { + cert.to_der() + .map_err(|e| RawSignerError::OpenSslError(e.to_string())) + }) + .collect() + } + + fn alg(&self) -> SigningAlg { + match self.alg { + RsaSigningAlg::Ps256 => SigningAlg::Ps256, + RsaSigningAlg::Ps384 => SigningAlg::Ps384, + RsaSigningAlg::Ps512 => SigningAlg::Ps512, + } + } +} + +impl TimeStampProvider for RsaSigner { + fn time_stamp_service_url(&self) -> Option { + self.time_stamp_service_url.clone() + } +} diff --git a/internal/crypto/src/p1363.rs b/internal/crypto/src/p1363.rs index 4f806d10a..473f24e56 100644 --- a/internal/crypto/src/p1363.rs +++ b/internal/crypto/src/p1363.rs @@ -43,3 +43,62 @@ pub struct EcSigComps<'a> { pub r: &'a [u8], pub s: &'a [u8], } + +#[cfg(not(target_arch = "wasm32"))] // Maye will be used later? +use crate::{raw_signature::RawSignerError, SigningAlg}; + +#[cfg(not(target_arch = "wasm32"))] // Maye will be used later? +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 67e0510b5..b5a54e86e 100644 --- a/internal/crypto/src/raw_signature/mod.rs +++ b/internal/crypto/src/raw_signature/mod.rs @@ -13,6 +13,12 @@ //! Tools for working with raw signature algorithms. +pub(crate) mod signer; +pub use signer::{ + async_signer_from_cert_chain_and_private_key, signer_from_cert_chain_and_private_key, + AsyncRawSigner, RawSigner, RawSignerError, +}; + pub(crate) mod oids; mod validator; diff --git a/internal/crypto/src/raw_signature/signer.rs b/internal/crypto/src/raw_signature/signer.rs new file mode 100644 index 000000000..cca4f607d --- /dev/null +++ b/internal/crypto/src/raw_signature/signer.rs @@ -0,0 +1,269 @@ +// 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 thiserror::Error; + +use crate::{ + time_stamp::{AsyncTimeStampProvider, TimeStampError, TimeStampProvider}, + SigningAlg, +}; + +/// Implementations of the `RawSigner` trait generate a cryptographic signature +/// over an arbitrary byte array. +/// +/// If an implementation _can_ be asynchronous, that is preferred. +pub trait RawSigner: TimeStampProvider { + /// Return a raw signature over the original byte slice. + fn sign(&self, data: &[u8]) -> Result, RawSignerError>; + + /// Return the algorithm implemented by this signer. + fn alg(&self) -> SigningAlg; + + /// Return the signing certificate chain. + /// + /// Each certificate should be encoded in DER format and sequenced from + /// end-entity certificate to the outermost certificate authority. + fn cert_chain(&self) -> Result>, RawSignerError>; + + /// Return the size in bytes of the largest possible expected signature. + /// Signing will fail if the result of the [`sign`] function is larger + /// than this value. + /// + /// [`sign`]: Self::sign + fn reserve_size(&self) -> usize; + + /// Return an OCSP response for the signing certificate if available. + /// + /// By pre-querying the value for the signing certificate, the value can be + /// cached which will reduce load on the certificate authority, as + /// recommended by the C2PA spec. + fn ocsp_response(&self) -> Option> { + None + } +} + +/// Implementations of the `AsyncRawSigner` trait generate a cryptographic +/// signature over an arbitrary byte array. +/// +/// Use this trait only when the implementation must be asynchronous. +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait AsyncRawSigner: Sync + AsyncTimeStampProvider { + /// Return a raw signature over the original byte slice. + async fn sign(&self, data: Vec) -> Result, RawSignerError>; + + /// Return the algorithm implemented by this signer. + fn alg(&self) -> SigningAlg; + + /// Return the signing certificate chain. + /// + /// Each certificate should be encoded in DER format and sequenced from + /// end-entity certificate to the outermost certificate authority. + fn cert_chain(&self) -> Result>, RawSignerError>; + + /// Return the size in bytes of the largest possible expected signature. + /// Signing will fail if the result of the [`sign`] function is larger + /// than this value. + /// + /// [`sign`]: Self::sign + fn reserve_size(&self) -> usize; + + /// Return an OCSP response for the signing certificate if available. + /// + /// By pre-querying the value for the signing certificate, the value can be + /// cached which will reduce load on the certificate authority, as + /// recommended by the C2PA spec. + async fn ocsp_response(&self) -> Option> { + None + } +} + +/// Describes errors that can be identified when generating a raw signature. +#[derive(Debug, Eq, Error, PartialEq)] +#[non_exhaustive] +pub enum RawSignerError { + /// The signing credentials are invalid. + #[error("invalid signing credentials ({0})")] + InvalidSigningCredentials(String), + + /// An I/O error occurred. This typically happens when loading + /// public/private key material from files. + /// + /// NOTE: We do not directly capture the I/O error itself because it + /// lacks an `Eq` implementation. Instead we capture the error description. + #[error("I/O error ({0})")] + IoError(String), + + /// An error was reported by the OpenSSL native code. + /// + /// NOTE: We do not directly capture the OpenSSL error itself because it + /// lacks an `Eq` implementation. Instead we capture the error description. + #[cfg(feature = "openssl")] + #[error("an error was reported by OpenSSL native code: {0}")] + OpenSslError(String), + + /// The OpenSSL native code mutex could not be acquired. + #[cfg(feature = "openssl")] + #[error(transparent)] + OpenSslMutexUnavailable(#[from] crate::openssl::OpenSslMutexUnavailable), + + /// An unexpected internal error occured while requesting the time stamp + /// response. + #[error("internal error ({0})")] + InternalError(String), +} + +impl From for RawSignerError { + fn from(err: std::io::Error) -> Self { + Self::IoError(err.to_string()) + } +} + +#[cfg(feature = "openssl")] +impl From for RawSignerError { + fn from(err: openssl::error::ErrorStack) -> Self { + Self::OpenSslError(err.to_string()) + } +} + +#[cfg(target_arch = "wasm32")] +impl From for RawSignerError { + fn from(err: crate::webcrypto::WasmCryptoError) -> Self { + match err { + crate::webcrypto::WasmCryptoError::UnknownContext => { + Self::InternalError("unknown WASM context".to_string()) + } + crate::webcrypto::WasmCryptoError::NoCryptoAvailable => { + Self::InternalError("WASM crypto unavailable".to_string()) + } + } + } +} + +/// Return a built-in [`RawSigner`] instance using the provided signing +/// certificate and private key. +/// +/// Which signers are available may vary depending on the platform and which +/// crate features were enabled. +/// +/// Returns `None` if the signing algorithm is unsupported. May return an `Err` +/// response if the certificate chain or private key are invalid. +#[allow(unused)] // arguments may or may not be used depending on crate features +pub fn signer_from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, +) -> Result, RawSignerError> { + #[cfg(feature = "openssl")] + { + return crate::openssl::signers::signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + alg, + time_stamp_service_url, + ); + } + + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] + { + return crate::webcrypto::signers::signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + alg, + time_stamp_service_url, + ); + } + + Err(RawSignerError::InternalError(format!( + "unsupported algorithm: {alg}" + ))) +} + +/// Return a built-in [`AsyncRawSigner`] instance using the provided signing +/// certificate and private key. +/// +/// Which signers are available may vary depending on the platform and which +/// crate features were enabled. +/// +/// Returns `None` if the signing algorithm is unsupported. May return an `Err` +/// response if the certificate chain or private key are invalid. +#[allow(unused)] // arguments may or may not be used depending on crate features +pub fn async_signer_from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, +) -> Result, RawSignerError> { + // TO DO: Preferentially use WASM-based signers, some of which are necessarily + // async. + + let sync_signer = signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + alg, + time_stamp_service_url, + )?; + + Ok(Box::new(AsyncRawSignerWrapper(sync_signer))) +} + +struct AsyncRawSignerWrapper(Box); + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl AsyncRawSigner for AsyncRawSignerWrapper { + async fn sign(&self, data: Vec) -> Result, RawSignerError> { + self.0.sign(&data) + } + + fn alg(&self) -> SigningAlg { + self.0.alg() + } + + fn cert_chain(&self) -> Result>, RawSignerError> { + self.0.cert_chain() + } + + fn reserve_size(&self) -> usize { + self.0.reserve_size() + } + + async fn ocsp_response(&self) -> Option> { + self.0.ocsp_response() + } +} + +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +impl AsyncTimeStampProvider for AsyncRawSignerWrapper { + fn time_stamp_service_url(&self) -> Option { + self.0.time_stamp_service_url() + } + + fn time_stamp_request_headers(&self) -> Option> { + self.0.time_stamp_request_headers() + } + + fn time_stamp_request_body(&self, message: &[u8]) -> Result, TimeStampError> { + self.0.time_stamp_request_body(message) + } + + async fn send_time_stamp_request( + &self, + message: &[u8], + ) -> Option, TimeStampError>> { + self.0.send_time_stamp_request(message) + } +} diff --git a/internal/crypto/src/tests/fixtures/raw_signature/ed25519.priv b/internal/crypto/src/tests/fixtures/raw_signature/ed25519.priv new file mode 100644 index 000000000..fda14a840 --- /dev/null +++ b/internal/crypto/src/tests/fixtures/raw_signature/ed25519.priv @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIL2+9INLPNSLH3STzKQJ3Wen9R6uPbIYOIKA2574YQ4O +-----END PRIVATE KEY----- diff --git a/internal/crypto/src/tests/fixtures/raw_signature/es256.priv b/internal/crypto/src/tests/fixtures/raw_signature/es256.priv new file mode 100644 index 000000000..5e59fcc5e --- /dev/null +++ b/internal/crypto/src/tests/fixtures/raw_signature/es256.priv @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfNJBsaRLSeHizv0m +GL+gcn78QmtfLSm+n+qG9veC2W2hRANCAAQPaL6RkAkYkKU4+IryBSYxJM3h77sF +iMrbvbI8fG7w2Bbl9otNG/cch3DAw5rGAPV7NWkyl3QGuV/wt0MrAPDo +-----END PRIVATE KEY----- diff --git a/internal/crypto/src/tests/fixtures/raw_signature/es384.priv b/internal/crypto/src/tests/fixtures/raw_signature/es384.priv new file mode 100644 index 000000000..9dcc3af1c --- /dev/null +++ b/internal/crypto/src/tests/fixtures/raw_signature/es384.priv @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDClh93nMdKl9Ujg71e3 +95QK1Z7OnmHrAYRv9B4ujlgWBoLQVtkoamQdizWQI/GvpAuhZANiAATY7kzZpbiB +azyKpJ3gb/6TZPMaNfJ+P5YeyhCynvzQtQT5l3HnJosPxUWYKTkdwqBWwe9P7QKl +KyX4PpQXVkJXYHffOGYOOKhLkJbc3JpYZzrxlZzY2FiVa1NQ/wOPXiU= +-----END PRIVATE KEY----- diff --git a/internal/crypto/src/tests/fixtures/raw_signature/es512.priv b/internal/crypto/src/tests/fixtures/raw_signature/es512.priv new file mode 100644 index 000000000..33cb2ad84 --- /dev/null +++ b/internal/crypto/src/tests/fixtures/raw_signature/es512.priv @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBDCrjJaQ9kS2QVZHr +AoJMiCyk3YtvecNK913LsZ5alTlq01W13cEWVl3nprbCrCVzANiHWLjqUzfVW2oN +wT0rW5qhgYkDgYYABAHbhWCA1ywh5K74+Al75qoC2FLcqEmIJQoU4lNpy5lsPU59 +3LNjzmVUY8VTK8bK9SLnqhZC0sdijlqitmSOJg/VLwB9uYVTnwBmize3WEQmlEyz +nYJ0XdmCZYJcB4ke1aQKpHw1jNhHGSxEO9xn7+50v1EzEwRa0yM934mF3UxCV2c5 +nw== +-----END PRIVATE KEY----- diff --git a/internal/crypto/src/tests/openssl/mod.rs b/internal/crypto/src/tests/openssl/mod.rs index 27cb16350..1ce823fa2 100644 --- a/internal/crypto/src/tests/openssl/mod.rs +++ b/internal/crypto/src/tests/openssl/mod.rs @@ -12,4 +12,5 @@ // each license. mod ffi_mutex; +mod signers; mod validators; diff --git a/internal/crypto/src/tests/openssl/signers/ecdsa_signer.rs b/internal/crypto/src/tests/openssl/signers/ecdsa_signer.rs new file mode 100644 index 000000000..858418deb --- /dev/null +++ b/internal/crypto/src/tests/openssl/signers/ecdsa_signer.rs @@ -0,0 +1,176 @@ +// 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 openssl::x509::X509; + +use crate::{ + openssl::{signers::signer_from_cert_chain_and_private_key, validators::EcdsaValidator}, + raw_signature::{async_signer_from_cert_chain_and_private_key, RawSignatureValidator}, + SigningAlg, +}; + +#[test] +fn es256() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/es256.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/es256.priv"); + + let signer = + signer_from_cert_chain_and_private_key(cert_chain, private_key, SigningAlg::Es256, None) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data).unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + EcdsaValidator::Es256 + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[actix::test] +async fn es256_async() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/es256.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/es256.priv"); + + let signer = async_signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + SigningAlg::Es256, + None, + ) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data.to_vec()).await.unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + EcdsaValidator::Es256 + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[test] +fn es384() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/es384.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/es384.priv"); + + let signer = + signer_from_cert_chain_and_private_key(cert_chain, private_key, SigningAlg::Es384, None) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data).unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + EcdsaValidator::Es384 + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[actix::test] +async fn es384_async() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/es384.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/es384.priv"); + + let signer = async_signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + SigningAlg::Es384, + None, + ) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data.to_vec()).await.unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + EcdsaValidator::Es384 + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[test] +fn es512() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/es512.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/es512.priv"); + + let signer = + signer_from_cert_chain_and_private_key(cert_chain, private_key, SigningAlg::Es512, None) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data).unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + EcdsaValidator::Es512 + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[actix::test] +async fn es512_async() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/es512.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/es512.priv"); + + let signer = async_signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + SigningAlg::Es512, + None, + ) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data.to_vec()).await.unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + EcdsaValidator::Es512 + .validate(&signature, data, &pub_key) + .unwrap(); +} diff --git a/internal/crypto/src/tests/openssl/signers/ed25519_signer.rs b/internal/crypto/src/tests/openssl/signers/ed25519_signer.rs new file mode 100644 index 000000000..d7a2de19f --- /dev/null +++ b/internal/crypto/src/tests/openssl/signers/ed25519_signer.rs @@ -0,0 +1,72 @@ +// 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 openssl::x509::X509; + +use crate::{ + openssl::{signers::signer_from_cert_chain_and_private_key, validators::Ed25519Validator}, + raw_signature::{async_signer_from_cert_chain_and_private_key, RawSignatureValidator}, + SigningAlg, +}; + +#[test] +fn ed25519() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/ed25519.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/ed25519.priv"); + + let signer = + signer_from_cert_chain_and_private_key(cert_chain, private_key, SigningAlg::Ed25519, None) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data).unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + Ed25519Validator {} + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[actix::test] +async fn ed25519_async() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/ed25519.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/ed25519.priv"); + + let signer = async_signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + SigningAlg::Ed25519, + None, + ) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data.to_vec()).await.unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + Ed25519Validator {} + .validate(&signature, data, &pub_key) + .unwrap(); +} diff --git a/internal/crypto/src/tests/openssl/signers/mod.rs b/internal/crypto/src/tests/openssl/signers/mod.rs new file mode 100644 index 000000000..c6d556b09 --- /dev/null +++ b/internal/crypto/src/tests/openssl/signers/mod.rs @@ -0,0 +1,16 @@ +// 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. + +mod ecdsa_signer; +mod ed25519_signer; +mod rsa_signer; diff --git a/internal/crypto/src/tests/openssl/signers/rsa_signer.rs b/internal/crypto/src/tests/openssl/signers/rsa_signer.rs new file mode 100644 index 000000000..74ae59f35 --- /dev/null +++ b/internal/crypto/src/tests/openssl/signers/rsa_signer.rs @@ -0,0 +1,176 @@ +// 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 openssl::x509::X509; + +use crate::{ + openssl::{signers::signer_from_cert_chain_and_private_key, validators::RsaValidator}, + raw_signature::{async_signer_from_cert_chain_and_private_key, RawSignatureValidator}, + SigningAlg, +}; + +#[test] +fn ps256() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/ps256.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/ps256.priv"); + + let signer = + signer_from_cert_chain_and_private_key(cert_chain, private_key, SigningAlg::Ps256, None) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data).unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + RsaValidator::Ps256 + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[actix::test] +async fn ps256_async() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/ps256.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/ps256.priv"); + + let signer = async_signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + SigningAlg::Ps256, + None, + ) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data.to_vec()).await.unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + RsaValidator::Ps256 + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[test] +fn ps384() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/ps384.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/ps384.priv"); + + let signer = + signer_from_cert_chain_and_private_key(cert_chain, private_key, SigningAlg::Ps384, None) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data).unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + RsaValidator::Ps384 + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[actix::test] +async fn ps384_async() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/ps384.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/ps384.priv"); + + let signer = async_signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + SigningAlg::Ps384, + None, + ) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data.to_vec()).await.unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + RsaValidator::Ps384 + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[test] +fn ps512() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/ps512.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/ps512.priv"); + + let signer = + signer_from_cert_chain_and_private_key(cert_chain, private_key, SigningAlg::Ps512, None) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data).unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + RsaValidator::Ps512 + .validate(&signature, data, &pub_key) + .unwrap(); +} + +#[actix::test] +async fn ps512_async() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/ps512.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/ps512.priv"); + + let signer = async_signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + SigningAlg::Ps512, + None, + ) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data.to_vec()).await.unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let cert = X509::from_pem(cert_chain).unwrap(); + let pub_key = cert.public_key().unwrap(); + let pub_key = pub_key.public_key_to_der().unwrap(); + + RsaValidator::Ps512 + .validate(&signature, data, &pub_key) + .unwrap(); +} diff --git a/internal/crypto/src/tests/webcrypto/mod.rs b/internal/crypto/src/tests/webcrypto/mod.rs index c1bbf2757..a2a612f0f 100644 --- a/internal/crypto/src/tests/webcrypto/mod.rs +++ b/internal/crypto/src/tests/webcrypto/mod.rs @@ -11,5 +11,6 @@ // specific language governing permissions and limitations under // each license. +mod signers; mod validators; mod window_or_worker; diff --git a/internal/crypto/src/tests/webcrypto/signers/ed25519_signer.rs b/internal/crypto/src/tests/webcrypto/signers/ed25519_signer.rs new file mode 100644 index 000000000..9770f6673 --- /dev/null +++ b/internal/crypto/src/tests/webcrypto/signers/ed25519_signer.rs @@ -0,0 +1,42 @@ +// 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::{signer_from_cert_chain_and_private_key, RawSignatureValidator}, + webcrypto::validators::Ed25519Validator, + SigningAlg, +}; + +#[wasm_bindgen_test] +fn ed25519() { + let cert_chain = include_bytes!("../../fixtures/raw_signature/ed25519.pub"); + let private_key = include_bytes!("../../fixtures/raw_signature/ed25519.priv"); + + let signer = + signer_from_cert_chain_and_private_key(cert_chain, private_key, SigningAlg::Ed25519, None) + .unwrap(); + + let data = b"some sample content to sign"; + let signature = signer.sign(data).unwrap(); + + println!("signature len = {}", signature.len()); + assert!(signature.len() <= signer.reserve_size()); + + let pub_key = include_bytes!("../../fixtures/raw_signature/ed25519.pub_key"); + + Ed25519Validator {} + .validate(&signature, data, pub_key) + .unwrap(); +} diff --git a/internal/crypto/src/tests/webcrypto/signers/mod.rs b/internal/crypto/src/tests/webcrypto/signers/mod.rs new file mode 100644 index 000000000..577cd72b2 --- /dev/null +++ b/internal/crypto/src/tests/webcrypto/signers/mod.rs @@ -0,0 +1,14 @@ +// 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. + +mod ed25519_signer; diff --git a/internal/crypto/src/webcrypto/mod.rs b/internal/crypto/src/webcrypto/mod.rs index fb51e2d32..de9ae2b80 100644 --- a/internal/crypto/src/webcrypto/mod.rs +++ b/internal/crypto/src/webcrypto/mod.rs @@ -25,6 +25,7 @@ pub use async_validators::{ AsyncRawSignatureValidator, }; +pub(crate) mod signers; pub mod validators; mod window_or_worker; diff --git a/internal/crypto/src/webcrypto/signers/ed25519_signer.rs b/internal/crypto/src/webcrypto/signers/ed25519_signer.rs new file mode 100644 index 000000000..a7d3871a4 --- /dev/null +++ b/internal/crypto/src/webcrypto/signers/ed25519_signer.rs @@ -0,0 +1,101 @@ +// 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 ed25519_dalek::{pkcs8::DecodePrivateKey, SigningKey}; +use x509_parser::{error::PEMError, pem::Pem}; + +use crate::{ + raw_signature::{RawSigner, RawSignerError}, + time_stamp::TimeStampProvider, + SigningAlg, +}; + +/// Implements `RawSigner` trait using `ed25519_dalek` crate's implementation of +/// Edwards Curve encryption. +pub struct Ed25519Signer { + #[allow(dead_code)] + cert_chain: Vec>, + cert_chain_len: usize, + + signing_key: SigningKey, + + time_stamp_service_url: Option, + time_stamp_size: usize, +} + +impl Ed25519Signer { + pub(crate) fn from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + time_stamp_service_url: Option, + ) -> Result { + let cert_chain = Pem::iter_from_buffer(cert_chain) + .map(|r| match r { + Ok(pem) => Ok(pem.contents), + Err(e) => Err(e), + }) + .collect::>, PEMError>>() + .map_err(|e| RawSignerError::InvalidSigningCredentials(e.to_string()))?; + + let cert_chain_len = cert_chain.len(); + + let private_key_pem = std::str::from_utf8(private_key).map_err(|e| { + RawSignerError::InvalidSigningCredentials(format!("invalid private key: {e}")) + })?; + + let signing_key = SigningKey::from_pkcs8_pem(private_key_pem).map_err(|e| { + RawSignerError::InvalidSigningCredentials(format!("invalid private key: {e}")) + })?; + + Ok(Ed25519Signer { + cert_chain, + cert_chain_len, + + signing_key, + + time_stamp_service_url, + time_stamp_size: 10000, + // TO DO: Call out to time stamp service to get actual time stamp and use that size? + }) + } +} + +impl RawSigner for Ed25519Signer { + fn sign(&self, data: &[u8]) -> Result, RawSignerError> { + use ed25519_dalek::Signer; + + Ok(self + .signing_key + .try_sign(data) + .map_err(|e| RawSignerError::InternalError(format!("signature error: {e}")))? + .to_vec()) + } + + fn alg(&self) -> SigningAlg { + SigningAlg::Ed25519 + } + + fn reserve_size(&self) -> usize { + 1024 + self.cert_chain_len + self.time_stamp_size + } + + fn cert_chain(&self) -> Result>, RawSignerError> { + Ok(self.cert_chain.clone()) + } +} + +impl TimeStampProvider for Ed25519Signer { + fn time_stamp_service_url(&self) -> Option { + self.time_stamp_service_url.clone() + } +} diff --git a/internal/crypto/src/webcrypto/signers/mod.rs b/internal/crypto/src/webcrypto/signers/mod.rs new file mode 100644 index 000000000..134f3d664 --- /dev/null +++ b/internal/crypto/src/webcrypto/signers/mod.rs @@ -0,0 +1,48 @@ +// 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 crate::{ + raw_signature::{RawSigner, RawSignerError}, + SigningAlg, +}; + +mod ed25519_signer; + +/// Return a built-in [`RawSigner`] instance using the provided signing +/// certificate and private key. +/// +/// Which signers are available may vary depending on the platform and which +/// crate features were enabled. +/// +/// Returns `None` if the signing algorithm is unsupported. May return an `Err` +/// response if the certificate chain or private key are invalid. +pub(crate) fn signer_from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, +) -> Result, RawSignerError> { + match alg { + SigningAlg::Ed25519 => Ok(Box::new( + ed25519_signer::Ed25519Signer::from_cert_chain_and_private_key( + cert_chain, + private_key, + time_stamp_service_url, + )?, + )), + + _ => Err(RawSignerError::InternalError(format!( + "unsupported algorithm: {alg}" + ))), + } +} diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index a5f2f490a..324ea93e2 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -28,7 +28,6 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["v1_api"] add_thumbnails = ["image"] -psxxx_ocsp_stapling_experimental = [] file_io = ["openssl_sign"] serialize_thumbnails = [] no_interleaved_io = ["file_io"] diff --git a/sdk/src/asset_handlers/c2pa_io.rs b/sdk/src/asset_handlers/c2pa_io.rs index 400122525..4dca3475e 100644 --- a/sdk/src/asset_handlers/c2pa_io.rs +++ b/sdk/src/asset_handlers/c2pa_io.rs @@ -146,13 +146,17 @@ pub mod tests { #![allow(clippy::expect_used)] #![allow(clippy::unwrap_used)] + use c2pa_crypto::SigningAlg; use c2pa_status_tracker::OneShotStatusTracker; use tempfile::tempdir; use super::{AssetIO, C2paIO, CAIReader, CAIWriter}; use crate::{ store::Store, - utils::test::{fixture_path, temp_dir_path, temp_signer}, + utils::{ + test::{fixture_path, temp_dir_path}, + test_signer::test_signer, + }, }; #[test] @@ -171,7 +175,7 @@ pub mod tests { let store = Store::load_from_asset(&temp_path, false, &mut OneShotStatusTracker::default()) .expect("loading store"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let manifest2 = store.to_jumbf(signer.as_ref()).expect("to_jumbf"); assert_eq!(&manifest, &manifest2); diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index beff52879..8ac8aa072 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -1083,6 +1083,7 @@ mod tests { #![allow(clippy::unwrap_used)] use std::io::Cursor; + use c2pa_crypto::SigningAlg; use serde_json::json; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -1092,7 +1093,7 @@ mod tests { assertions::BoxHash, asset_handlers::jpeg_io::JpegIO, hash_stream_by_alg, - utils::test::{temp_signer, write_jpeg_placeholder_stream}, + utils::{test::write_jpeg_placeholder_stream, test_signer::test_signer}, Reader, }; @@ -1305,7 +1306,7 @@ mod tests { let mut _builder = Builder::from_archive(&mut zipped).unwrap(); // sign and write to the output stream - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); builder .sign(signer.as_ref(), format, &mut source, &mut dest) .unwrap(); @@ -1337,7 +1338,7 @@ mod tests { .unwrap(); // sign and write to the output stream - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); builder.sign_file(signer.as_ref(), source, &dest).unwrap(); // read and validate the signed manifest store @@ -1389,7 +1390,7 @@ mod tests { .unwrap(); // sign and write to the output stream - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); builder .sign(signer.as_ref(), format, &mut source, &mut dest) .unwrap(); @@ -1472,7 +1473,7 @@ mod tests { .unwrap(); // sign the ManifestStoreBuilder and write it to the output stream - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let manifest_data = builder .sign(signer.as_ref(), "image/jpeg", &mut source, &mut dest) .unwrap(); @@ -1496,7 +1497,7 @@ mod tests { const CLOUD_IMAGE: &[u8] = include_bytes!("../tests/fixtures/cloud.jpg"); let mut input_stream = Cursor::new(CLOUD_IMAGE); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut builder = Builder::from_json(&simple_manifest()).unwrap(); @@ -1569,7 +1570,7 @@ mod tests { builder.add_assertion(labels::BOX_HASH, &box_hash).unwrap(); - let signer = crate::utils::test::temp_async_signer(); + let signer = crate::utils::test_signer::async_test_signer(SigningAlg::Ed25519); let manifest_bytes = builder .sign_box_hashed_embeddable_async(signer.as_ref(), "image/jpeg") @@ -1633,7 +1634,7 @@ mod tests { let mut builder = Builder::from_archive(&mut zipped).unwrap(); // sign the ManifestStoreBuilder and write it to the output stream - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let _manifest_data = builder .sign(signer.as_ref(), "image/jpeg", &mut source, &mut dest) .unwrap(); @@ -1809,7 +1810,7 @@ mod tests { // convert buffer to cursor with Read/Write/Seek capability let mut input = Cursor::new(image.to_vec()); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Embed a manifest using the signer. let mut output = Cursor::new(Vec::new()); builder diff --git a/sdk/src/callback_signer.rs b/sdk/src/callback_signer.rs index 494e0851a..52f952949 100644 --- a/sdk/src/callback_signer.rs +++ b/sdk/src/callback_signer.rs @@ -16,12 +16,10 @@ //! The `callback_signer` module provides a way to obtain a [`Signer`] or [`AsyncSigner`] //! using a callback and public signing certificates. +use async_trait::async_trait; use c2pa_crypto::SigningAlg; -use crate::{ - error::{Error, Result}, - AsyncSigner, Signer, -}; +use crate::{AsyncSigner, Error, Result, Signer}; /// Defines a callback function interface for a [`CallbackSigner`]. /// @@ -54,7 +52,6 @@ pub struct CallbackSigner { } unsafe impl Send for CallbackSigner {} - unsafe impl Sync for CallbackSigner {} impl CallbackSigner { @@ -66,6 +63,7 @@ impl CallbackSigner { { let certs = certs.into(); let reserve_size = 10000 + certs.len(); + Self { context: std::ptr::null(), callback: Box::new(callback), @@ -114,13 +112,14 @@ impl CallbackSigner { // Parse the PEM data to get the private key let pem = parse(private_key).map_err(|e| Error::OtherError(Box::new(e)))?; + // For Ed25519, the key is 32 bytes long, so we skip the first 16 bytes of the PEM data let key_bytes = &pem.contents()[16..]; let signing_key = SigningKey::try_from(key_bytes).map_err(|e| Error::OtherError(Box::new(e)))?; + // Sign the data let signature: Signature = signing_key.sign(data); - Ok(signature.to_bytes().to_vec()) } } @@ -162,11 +161,8 @@ impl Signer for CallbackSigner { } } -use async_trait::async_trait; - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -// I'm not sure if this is useful since the callback is still synchronous. impl AsyncSigner for CallbackSigner { async fn sign(&self, data: Vec) -> Result> { (self.callback)(self.context, &data) diff --git a/sdk/src/cose_sign.rs b/sdk/src/cose_sign.rs index 81dccd2f3..ddb4d15b5 100644 --- a/sdk/src/cose_sign.rs +++ b/sdk/src/cose_sign.rs @@ -35,7 +35,6 @@ use crate::{ cose_timestamp_countersign, cose_timestamp_countersign_async, make_cose_timestamp, }, trust_handler::TrustHandlerConfig, - utils::sig_utils::der_to_p1363, AsyncSigner, Error, Result, Signer, }; @@ -206,6 +205,63 @@ pub(crate) fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Re Ok(c2pa_sig_data) } +fn der_to_p1363(data: &[u8], alg: SigningAlg) -> Result> { + // P1363 format: r | s + + let (_, p) = parse_ec_der_sig(data).map_err(|_err| Error::InvalidEcdsaSignature)?; + + let mut r = extfmt::Hexlify(p.r).to_string(); + let mut s = extfmt::Hexlify(p.s).to_string(); + + let sig_len: usize = match alg { + SigningAlg::Es256 => 64, + SigningAlg::Es384 => 96, + SigningAlg::Es512 => 132, + _ => return Err(Error::UnsupportedType), + }; + + // pad or truncate as needed + let rp = if r.len() > sig_len { + // truncate + let offset = r.len() - sig_len; + &r[offset..r.len()] + } else { + // pad + while r.len() != sig_len { + r.insert(0, '0'); + } + r.as_ref() + }; + + let sp = if s.len() > sig_len { + // truncate + let offset = s.len() - sig_len; + &s[offset..s.len()] + } else { + // pad + while s.len() != sig_len { + s.insert(0, '0'); + } + s.as_ref() + }; + + if rp.len() != sig_len || rp.len() != sp.len() { + return Err(Error::InvalidEcdsaSignature); + } + + // merge r and s strings + let mut new_sig = rp.to_string(); + new_sig.push_str(sp); + + // convert back from hex string to byte array + (0..new_sig.len()) + .step_by(2) + .map(|i| { + u8::from_str_radix(&new_sig[i..i + 2], 16).map_err(|_err| Error::InvalidEcdsaSignature) + }) + .collect() +} + #[async_generic(async_signature(signer: &dyn AsyncSigner, data: &[u8], alg: SigningAlg))] fn build_headers(signer: &dyn Signer, data: &[u8], alg: SigningAlg) -> Result<(Header, Header)> { let mut protected_h = match alg { @@ -357,9 +413,12 @@ fn pad_cose_sig(sign1: &mut CoseSign1, end_size: usize) -> Result> { #[cfg(test)] mod tests { #![allow(clippy::unwrap_used)] + use c2pa_crypto::SigningAlg; use super::sign_claim; - use crate::{claim::Claim, utils::test::temp_signer}; + #[cfg(not(target_arch = "wasm32"))] + use crate::utils::test_signer::async_test_signer; + use crate::{claim::Claim, utils::test_signer::test_signer, Result, Signer}; #[test] fn test_sign_claim() { @@ -368,7 +427,7 @@ mod tests { let claim_bytes = claim.data().unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let box_size = signer.reserve_size(); let cose_sign1 = sign_claim(&claim_bytes, signer.as_ref(), box_size).unwrap(); @@ -380,16 +439,16 @@ mod tests { #[cfg(feature = "openssl")] #[actix::test] async fn test_sign_claim_async() { - use crate::{ - cose_sign::sign_claim_async, openssl::AsyncSignerAdapter, AsyncSigner, SigningAlg, - }; + use c2pa_crypto::SigningAlg; + + use crate::{cose_sign::sign_claim_async, AsyncSigner}; let mut claim = Claim::new("extern_sign_test", Some("contentauth")); claim.build().unwrap(); let claim_bytes = claim.data().unwrap(); - let signer = AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = async_test_signer(SigningAlg::Ps256); let box_size = signer.reserve_size(); let cose_sign1 = sign_claim_async(&claim_bytes, &signer, box_size) @@ -407,8 +466,8 @@ mod tests { } } - impl crate::Signer for BogusSigner { - fn sign(&self, _data: &[u8]) -> crate::error::Result> { + impl Signer for BogusSigner { + fn sign(&self, _data: &[u8]) -> Result> { eprintln!("Canary, canary, please cause this deploy to fail!"); Ok(b"totally bogus signature".to_vec()) } @@ -417,7 +476,7 @@ mod tests { c2pa_crypto::SigningAlg::Ps256 } - fn certs(&self) -> crate::error::Result>> { + fn certs(&self) -> Result>> { let cert_vec: Vec = Vec::new(); let certs = vec![cert_vec]; Ok(certs) diff --git a/sdk/src/cose_validator.rs b/sdk/src/cose_validator.rs index 30f5767a1..19f01a6a7 100644 --- a/sdk/src/cose_validator.rs +++ b/sdk/src/cose_validator.rs @@ -1360,11 +1360,12 @@ fn gt_to_datetime( #[cfg(feature = "openssl_sign")] #[cfg(test)] pub mod tests { + use c2pa_crypto::SigningAlg; use c2pa_status_tracker::DetailedStatusTracker; use sha2::digest::generic_array::sequence::Shorten; use super::*; - use crate::{openssl::temp_signer, signer::ConfigurableSigner, Signer, SigningAlg}; + use crate::{utils::test_signer::test_signer, Signer}; #[test] #[cfg(feature = "file_io")] @@ -1393,39 +1394,31 @@ pub mod tests { #[test] #[cfg(feature = "openssl_sign")] fn test_cert_algorithms() { - let cert_dir = crate::utils::test::fixture_path("certs"); let th = crate::openssl::OpenSSLTrustHandlerConfig::new(); let mut validation_log = DetailedStatusTracker::default(); - let (_, cert_path) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); - let es256_cert = std::fs::read(cert_path).unwrap(); + let es256_cert = include_bytes!("../tests/fixtures/certs/es256.pub"); + let es384_cert = include_bytes!("../tests/fixtures/certs/es384.pub"); + let es512_cert = include_bytes!("../tests/fixtures/certs/es512.pub"); + let rsa_pss256_cert = include_bytes!("../tests/fixtures/certs/ps256.pub"); - let (_, cert_path) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); - let es384_cert = std::fs::read(cert_path).unwrap(); - - let (_, cert_path) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); - let es512_cert = std::fs::read(cert_path).unwrap(); - - let (_, cert_path) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); - let rsa_pss256_cert = std::fs::read(cert_path).unwrap(); - - if let Ok(signcert) = openssl::x509::X509::from_pem(&es256_cert) { + if let Ok(signcert) = openssl::x509::X509::from_pem(es256_cert) { let der_bytes = signcert.to_der().unwrap(); assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } - if let Ok(signcert) = openssl::x509::X509::from_pem(&es384_cert) { + if let Ok(signcert) = openssl::x509::X509::from_pem(es384_cert) { let der_bytes = signcert.to_der().unwrap(); assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } - if let Ok(signcert) = openssl::x509::X509::from_pem(&es512_cert) { + if let Ok(signcert) = openssl::x509::X509::from_pem(es512_cert) { let der_bytes = signcert.to_der().unwrap(); assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } - if let Ok(signcert) = openssl::x509::X509::from_pem(&rsa_pss256_cert) { + if let Ok(signcert) = openssl::x509::X509::from_pem(rsa_pss256_cert) { let der_bytes = signcert.to_der().unwrap(); assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } @@ -1442,7 +1435,7 @@ pub mod tests { let box_size = 10000; - let signer = crate::utils::test::temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let cose_bytes = crate::cose_sign::sign_claim(&claim_bytes, signer.as_ref(), box_size).unwrap(); @@ -1456,6 +1449,10 @@ pub mod tests { #[test] #[cfg(feature = "openssl_sign")] fn test_stapled_ocsp() { + use c2pa_crypto::raw_signature::{ + signer_from_cert_chain_and_private_key, RawSigner, RawSignerError, + }; + let mut validation_log = DetailedStatusTracker::default(); let mut claim = crate::claim::Claim::new("ocsp_sign_test", Some("contentauth")); @@ -1467,13 +1464,9 @@ pub mod tests { let pem_key = include_bytes!("../tests/fixtures/certs/ps256.pem").to_vec(); let ocsp_rsp_data = include_bytes!("../tests/fixtures/ocsp_good.data"); - let signer = crate::openssl::RsaSigner::from_signcert_and_pkey( - &sign_cert, - &pem_key, - SigningAlg::Ps256, - None, - ) - .unwrap(); + let signer = + signer_from_cert_chain_and_private_key(&sign_cert, &pem_key, SigningAlg::Ps256, None) + .unwrap(); // create a test signer that supports stapling struct OcspSigner { @@ -1504,7 +1497,7 @@ pub mod tests { } let ocsp_signer = OcspSigner { - signer: Box::new(signer), + signer: Box::new(crate::signer::RawSignerWrapper(signer)), ocsp_rsp: ocsp_rsp_data.to_vec(), }; diff --git a/sdk/src/create_signer.rs b/sdk/src/create_signer.rs index 176cfdf07..67fb1887f 100644 --- a/sdk/src/create_signer.rs +++ b/sdk/src/create_signer.rs @@ -18,14 +18,9 @@ #[cfg(feature = "file_io")] use std::path::Path; -use c2pa_crypto::SigningAlg; +use c2pa_crypto::{raw_signature::signer_from_cert_chain_and_private_key, SigningAlg}; -use crate::{ - error::Result, - openssl::{EcSigner, EdSigner, RsaSigner}, - signer::ConfigurableSigner, - Signer, -}; +use crate::{error::Result, signer::RawSignerWrapper, Signer}; /// Creates a [`Signer`] instance using signing certificate and private key /// as byte slices. @@ -45,17 +40,9 @@ pub fn from_keys( alg: SigningAlg, tsa_url: Option, ) -> Result> { - Ok(match alg { - SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => Box::new( - RsaSigner::from_signcert_and_pkey(signcert, pkey, alg, tsa_url)?, - ), - SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => Box::new( - EcSigner::from_signcert_and_pkey(signcert, pkey, alg, tsa_url)?, - ), - SigningAlg::Ed25519 => Box::new(EdSigner::from_signcert_and_pkey( - signcert, pkey, alg, tsa_url, - )?), - }) + Ok(Box::new(RawSignerWrapper( + signer_from_cert_chain_and_private_key(signcert, pkey, alg, tsa_url)?, + ))) } /// Creates a [`Signer`] instance using signing certificate and @@ -74,18 +61,8 @@ pub fn from_files>( alg: SigningAlg, tsa_url: Option, ) -> Result> { - Ok(match alg { - SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => Box::new( - RsaSigner::from_files(&signcert_path, &pkey_path, alg, tsa_url)?, - ), - SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => Box::new( - EcSigner::from_files(&signcert_path, &pkey_path, alg, tsa_url)?, - ), - SigningAlg::Ed25519 => Box::new(EdSigner::from_files( - &signcert_path, - &pkey_path, - alg, - tsa_url, - )?), - }) + let cert_chain = std::fs::read(signcert_path)?; + let private_key = std::fs::read(pkey_path)?; + + from_keys(&cert_chain, &private_key, alg, tsa_url) } diff --git a/sdk/src/error.rs b/sdk/src/error.rs index d4ee87532..2e2f54b89 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -306,6 +306,9 @@ pub enum Error { #[error(transparent)] RawSignatureValidationError(#[from] c2pa_crypto::raw_signature::RawSignatureValidationError), + + #[error(transparent)] + RawSignerError(#[from] c2pa_crypto::raw_signature::RawSignerError), } /// A specialized `Result` type for C2PA toolkit operations. diff --git a/sdk/src/jumbf_io.rs b/sdk/src/jumbf_io.rs index 677ec9be7..7e11ae36e 100644 --- a/sdk/src/jumbf_io.rs +++ b/sdk/src/jumbf_io.rs @@ -349,10 +349,12 @@ pub mod tests { use std::io::Seek; + use c2pa_crypto::SigningAlg; + use super::*; use crate::{ asset_io::RemoteRefEmbedType, - utils::test::{create_test_store, temp_signer}, + utils::{test::create_test_store, test_signer::test_signer}, }; #[test] @@ -447,7 +449,7 @@ pub mod tests { fn test_jumbf(asset_type: &str, reader: &mut dyn CAIRead) { let mut writer = Cursor::new(Vec::new()); let store = create_test_store().unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let jumbf = store.to_jumbf(&*signer).unwrap(); save_jumbf_to_stream(asset_type, reader, &mut writer, &jumbf).unwrap(); writer.set_position(0); diff --git a/sdk/src/manifest.rs b/sdk/src/manifest.rs index 774798e6f..ebfa67153 100644 --- a/sdk/src/manifest.rs +++ b/sdk/src/manifest.rs @@ -1504,6 +1504,8 @@ pub(crate) mod tests { use std::io::Cursor; + #[cfg(any(feature = "file_io", target_arch = "wasm32"))] + use c2pa_crypto::SigningAlg; #[cfg(feature = "file_io")] use c2pa_status_tracker::{DetailedStatusTracker, StatusTracker}; #[cfg(feature = "file_io")] @@ -1520,7 +1522,8 @@ pub(crate) mod tests { ingredient::Ingredient, reader::Reader, store::Store, - utils::test::{temp_remote_signer, temp_signer, TEST_VC}, + utils::test::{temp_remote_signer, TEST_VC}, + utils::test_signer::{async_test_signer, test_signer}, Manifest, Result, }; #[cfg(feature = "file_io")] @@ -1601,7 +1604,7 @@ pub(crate) mod tests { let test_output = dir.path().join("wc_embed_test.jpg"); //embed a claim generated from this manifest - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let _store = manifest .embed(&source_path, &test_output, signer.as_ref()) @@ -1772,7 +1775,7 @@ pub(crate) mod tests { ) .expect("add_assertion"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let c2pa_data = manifest .embed(&output, &output, signer.as_ref()) @@ -1797,7 +1800,7 @@ pub(crate) mod tests { .expect("add_redaction"); //embed a claim in output2 - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let _store2 = manifest2 .embed(&output2, &output2, signer.as_ref()) .expect("embed"); @@ -1838,7 +1841,7 @@ pub(crate) mod tests { .add_assertion(&actions) .expect("add_assertion"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); parent_manifest .embed(&parent_output, &parent_output, signer.as_ref()) .expect("embed"); @@ -1895,8 +1898,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); - let async_signer = - crate::openssl::temp_signer_async::AsyncSignerAdapter::new(crate::SigningAlg::Ps256); + let async_signer = async_test_signer(SigningAlg::Ps256); let mut manifest = test_manifest(); manifest @@ -1938,7 +1940,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = test_manifest(); manifest.set_label("MyLabel"); @@ -1963,7 +1965,7 @@ pub(crate) mod tests { let fp = format!("file:/{}", sidecar.to_str().unwrap()); let url = url::Url::parse(&fp).unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = test_manifest(); manifest.set_label("MyLabel"); @@ -2104,7 +2106,8 @@ pub(crate) mod tests { )) .unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); + let mut output = Cursor::new(Vec::new()); // Embed a manifest using the signer. manifest @@ -2126,7 +2129,7 @@ pub(crate) mod tests { #[cfg_attr(feature = "openssl_sign", actix::test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] async fn test_embed_from_memory_async() { - use crate::{assertions::User, utils::test::temp_async_signer}; + use crate::assertions::User; let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg"); // convert buffer to cursor with Read/Write/Seek capability let mut stream = std::io::Cursor::new(image.to_vec()); @@ -2142,8 +2145,9 @@ pub(crate) mod tests { )) .unwrap(); - let signer = temp_async_signer(); + let signer = async_test_signer(SigningAlg::Ed25519); let mut output = Cursor::new(Vec::new()); + // Embed a manifest using the signer. manifest .embed_to_stream_async("jpeg", &mut stream, &mut output, signer.as_ref()) @@ -2171,7 +2175,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = test_manifest(); let ingredient = @@ -2208,7 +2212,7 @@ pub(crate) mod tests { let fp = format!("file:/{}", sidecar.to_str().unwrap()); let url = url::Url::parse(&fp).unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let parent = Ingredient::from_file(fixture_path("XCA.jpg")).expect("getting parent"); let mut manifest = test_manifest(); @@ -2235,7 +2239,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = test_manifest(); let thumb_data = vec![1, 2, 3]; @@ -2397,7 +2401,8 @@ pub(crate) mod tests { // convert buffer to cursor with Read/Write/Seek capability let mut input = std::io::Cursor::new(image.to_vec()); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); + // Embed a manifest using the signer. let mut output = Cursor::new(Vec::new()); manifest @@ -2464,7 +2469,8 @@ pub(crate) mod tests { let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); + // Embed a manifest using the signer. let output_image = manifest .embed_from_memory("jpeg", image, signer.as_ref()) @@ -2529,7 +2535,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = Manifest::from_json(MANIFEST_JSON).expect("from_json"); manifest.with_base_path(fixtures).expect("with_base"); @@ -2556,7 +2562,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_WEBP); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = Manifest::from_json(MANIFEST_JSON).expect("from_json"); manifest.with_base_path(fixtures).expect("with_base"); @@ -2594,7 +2600,7 @@ pub(crate) mod tests { .is_ok()); assert!(manifest.thumbnail_ref().is_some()); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); manifest .embed(&output, &output, signer.as_ref()) .expect("embed"); @@ -2621,7 +2627,7 @@ pub(crate) mod tests { // verify there is no thumbnail assert!(manifest.thumbnail().is_none()); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); manifest .embed(&output, &output, signer.as_ref()) .expect("embed"); @@ -2650,9 +2656,11 @@ pub(crate) mod tests { let mut source = std::io::Cursor::new(vec![1, 2, 3]); let mut dest = std::io::Cursor::new(Vec::new()); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); + let result = manifest.embed_to_stream("image/jpeg", &mut source, &mut dest, signer.as_ref()); + assert!(result.is_err()); assert!(result .unwrap_err() @@ -2666,7 +2674,7 @@ pub(crate) mod tests { fn test_data_hash_embeddable_manifest() { let ap = fixture_path("cloud.jpg"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = Manifest::new("claim_generator"); @@ -2792,7 +2800,7 @@ pub(crate) mod tests { .add_labeled_assertion(crate::assertions::labels::BOX_HASH, &box_hash) .unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let embeddable = manifest .box_hash_embeddable_manifest(signer.as_ref(), None) diff --git a/sdk/src/openssl/ec_signer.rs b/sdk/src/openssl/ec_signer.rs deleted file mode 100644 index 136cf328e..000000000 --- a/sdk/src/openssl/ec_signer.rs +++ /dev/null @@ -1,171 +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 c2pa_crypto::{openssl::OpenSslMutex, SigningAlg}; -use openssl::{ - ec::EcKey, - hash::MessageDigest, - pkey::{PKey, Private}, - x509::X509, -}; - -use super::check_chain_order; -use crate::{ - error::{Error, Result}, - signer::ConfigurableSigner, - utils::sig_utils::der_to_p1363, - Signer, -}; - -/// Implements `Signer` trait using OpenSSL's implementation of -/// ECDSA encryption. -pub struct EcSigner { - signcerts: Vec, - pkey: EcKey, - - certs_size: usize, - timestamp_size: usize, - - alg: SigningAlg, - tsa_url: Option, -} - -impl ConfigurableSigner for EcSigner { - fn from_signcert_and_pkey( - signcert: &[u8], - pkey: &[u8], - alg: SigningAlg, - tsa_url: Option, - ) -> Result { - let _openssl = OpenSslMutex::acquire()?; - - let certs_size = signcert.len(); - let pkey = EcKey::private_key_from_pem(pkey).map_err(Error::OpenSslError)?; - let signcerts = X509::stack_from_pem(signcert).map_err(Error::OpenSslError)?; - - // make sure cert chains are in order - if !check_chain_order(&signcerts) { - return Err(Error::BadParam( - "certificate chain is not in correct order".to_string(), - )); - } - - Ok(EcSigner { - signcerts, - pkey, - certs_size, - timestamp_size: 10000, /* todo: call out to TSA to get actual timestamp and use that size */ - alg, - tsa_url, - }) - } -} - -impl Signer for EcSigner { - fn sign(&self, data: &[u8]) -> Result> { - let _openssl = OpenSslMutex::acquire()?; - - let key = PKey::from_ec_key(self.pkey.clone()).map_err(Error::OpenSslError)?; - - let mut signer = match self.alg { - SigningAlg::Es256 => openssl::sign::Signer::new(MessageDigest::sha256(), &key)?, - SigningAlg::Es384 => openssl::sign::Signer::new(MessageDigest::sha384(), &key)?, - SigningAlg::Es512 => openssl::sign::Signer::new(MessageDigest::sha512(), &key)?, - _ => return Err(Error::UnsupportedType), - }; - - signer.update(data).map_err(Error::OpenSslError)?; - let der_sig = signer.sign_to_vec().map_err(Error::OpenSslError)?; - - der_to_p1363(&der_sig, self.alg) - } - - fn alg(&self) -> SigningAlg { - self.alg - } - - fn certs(&self) -> Result>> { - let _openssl = OpenSslMutex::acquire()?; - - let mut certs: Vec> = Vec::new(); - - for c in &self.signcerts { - let cert = c.to_der().map_err(Error::OpenSslError)?; - certs.push(cert); - } - - Ok(certs) - } - - fn time_authority_url(&self) -> Option { - self.tsa_url.clone() - } - - fn reserve_size(&self) -> usize { - 1024 + self.certs_size + self.timestamp_size // the Cose_Sign1 contains complete certs and timestamps so account for size - } -} - -#[cfg(test)] -#[cfg(feature = "file_io")] -mod tests { - #![allow(clippy::unwrap_used)] - - use super::*; - use crate::{openssl::temp_signer, utils::test::fixture_path}; - - #[test] - fn es256_signer() { - let cert_dir = fixture_path("certs"); - - let (signer, _) = temp_signer::get_ec_signer(cert_dir, SigningAlg::Es256, None); - - let data = b"some sample content to sign"; - println!("data len = {}", data.len()); - - let signature = signer.sign(data).unwrap(); - println!("signature.len = {}", signature.len()); - assert!(signature.len() >= 64); - assert!(signature.len() <= signer.reserve_size()); - } - - #[test] - fn es384_signer() { - let cert_dir = fixture_path("certs"); - - let (signer, _) = temp_signer::get_ec_signer(cert_dir, SigningAlg::Es384, None); - - let data = b"some sample content to sign"; - println!("data len = {}", data.len()); - - let signature = signer.sign(data).unwrap(); - println!("signature.len = {}", signature.len()); - assert!(signature.len() >= 64); - assert!(signature.len() <= signer.reserve_size()); - } - - #[test] - fn es512_signer() { - let cert_dir = fixture_path("certs"); - - let (signer, _) = temp_signer::get_ec_signer(cert_dir, SigningAlg::Es512, None); - - let data = b"some sample content to sign"; - println!("data len = {}", data.len()); - - let signature = signer.sign(data).unwrap(); - println!("signature.len = {}", signature.len()); - assert!(signature.len() >= 64); - assert!(signature.len() <= signer.reserve_size()); - } -} diff --git a/sdk/src/openssl/ed_signer.rs b/sdk/src/openssl/ed_signer.rs deleted file mode 100644 index 21202eebf..000000000 --- a/sdk/src/openssl/ed_signer.rs +++ /dev/null @@ -1,130 +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 c2pa_crypto::{openssl::OpenSslMutex, SigningAlg}; -use openssl::{ - pkey::{PKey, Private}, - x509::X509, -}; - -use super::check_chain_order; -use crate::{signer::ConfigurableSigner, Error, Result, Signer}; - -/// Implements `Signer` trait using OpenSSL's implementation of -/// Edwards Curve encryption. -pub struct EdSigner { - signcerts: Vec, - pkey: PKey, - - certs_size: usize, - timestamp_size: usize, - - alg: SigningAlg, - tsa_url: Option, -} - -impl ConfigurableSigner for EdSigner { - fn from_signcert_and_pkey( - signcert: &[u8], - pkey: &[u8], - alg: SigningAlg, - tsa_url: Option, - ) -> Result { - let _openssl = OpenSslMutex::acquire()?; - - let certs_size = signcert.len(); - let signcerts = X509::stack_from_pem(signcert).map_err(Error::OpenSslError)?; - let pkey = PKey::private_key_from_pem(pkey).map_err(Error::OpenSslError)?; - - if alg != SigningAlg::Ed25519 { - return Err(Error::UnsupportedType); // only ed25519 is supported by C2PA - } - - // make sure cert chains are in order - if !check_chain_order(&signcerts) { - return Err(Error::BadParam( - "certificate chain is not in correct order".to_string(), - )); - } - - Ok(EdSigner { - signcerts, - pkey, - certs_size, - timestamp_size: 10000, /* todo: call out to TSA to get actual timestamp and use that size */ - alg, - tsa_url, - }) - } -} - -impl Signer for EdSigner { - fn sign(&self, data: &[u8]) -> Result> { - let _openssl = OpenSslMutex::acquire()?; - - let mut signer = - openssl::sign::Signer::new_without_digest(&self.pkey).map_err(Error::OpenSslError)?; - - let signed_data = signer.sign_oneshot_to_vec(data)?; - - Ok(signed_data) - } - - fn alg(&self) -> SigningAlg { - self.alg - } - - fn certs(&self) -> Result>> { - let _openssl = OpenSslMutex::acquire()?; - - let mut certs: Vec> = Vec::new(); - - for c in &self.signcerts { - let cert = c.to_der().map_err(Error::OpenSslError)?; - certs.push(cert); - } - - Ok(certs) - } - - fn time_authority_url(&self) -> Option { - self.tsa_url.clone() - } - - fn reserve_size(&self) -> usize { - 1024 + self.certs_size + self.timestamp_size // the Cose_Sign1 contains complete certs and timestamps so account for size - } -} - -#[cfg(test)] -#[cfg(feature = "file_io")] -mod tests { - #![allow(clippy::unwrap_used)] - use super::*; - use crate::{openssl::temp_signer, utils::test::fixture_path}; - - #[test] - fn ed25519_signer() { - let cert_dir = fixture_path("certs"); - - let (signer, _) = temp_signer::get_ed_signer(cert_dir, SigningAlg::Ed25519, None); - - let data = b"some sample content to sign"; - println!("data len = {}", data.len()); - - let signature = signer.sign(data).unwrap(); - println!("signature.len = {}", signature.len()); - assert!(signature.len() >= 64); - assert!(signature.len() <= signer.reserve_size()); - } -} diff --git a/sdk/src/openssl/mod.rs b/sdk/src/openssl/mod.rs index 13dbaeb83..43c7b4a8f 100644 --- a/sdk/src/openssl/mod.rs +++ b/sdk/src/openssl/mod.rs @@ -11,94 +11,10 @@ // specific language governing permissions and limitations under // each license. -#[cfg(feature = "openssl_sign")] -mod rsa_signer; -#[cfg(feature = "openssl_sign")] -pub(crate) use rsa_signer::RsaSigner; - -#[cfg(feature = "openssl_sign")] -mod ec_signer; -#[cfg(feature = "openssl_sign")] -pub(crate) use ec_signer::EcSigner; - -#[cfg(feature = "openssl_sign")] -mod ed_signer; -#[cfg(feature = "openssl_sign")] -pub(crate) use ed_signer::EdSigner; - #[cfg(feature = "openssl")] mod openssl_trust_handler; -#[cfg(test)] -pub(crate) mod temp_signer; #[cfg(feature = "openssl")] pub(crate) use openssl_trust_handler::verify_trust; #[cfg(feature = "openssl")] pub(crate) use openssl_trust_handler::OpenSSLTrustHandlerConfig; - -#[cfg(test)] -pub(crate) mod temp_signer_async; - -#[cfg(feature = "openssl")] -use openssl::x509::X509; -#[cfg(test)] -#[allow(unused_imports)] -#[cfg(feature = "openssl")] -pub(crate) use temp_signer_async::AsyncSignerAdapter; - -#[cfg(feature = "openssl")] -fn check_chain_order(certs: &[X509]) -> bool { - // IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please - // don't make this pub or pub(crate) without finding a way to ensure that - // precondition. - - { - if certs.len() > 1 { - for (i, c) in certs.iter().enumerate() { - if let Some(next_c) = certs.get(i + 1) { - if let Ok(pkey) = next_c.public_key() { - if let Ok(verified) = c.verify(&pkey) { - if !verified { - return false; - } - } else { - return false; - } - } else { - return false; - } - } - } - } - true - } -} - -#[cfg(not(feature = "openssl"))] -fn check_chain_order(certs: &[X509]) -> bool { - true -} - -#[cfg(feature = "openssl")] -#[allow(dead_code)] -fn check_chain_order_der(cert_ders: &[Vec]) -> bool { - // IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please - // don't make this pub or pub(crate) without finding a way to ensure that - // precondition. - - let mut certs: Vec = Vec::new(); - for cert_der in cert_ders { - if let Ok(cert) = X509::from_der(cert_der) { - certs.push(cert); - } else { - return false; - } - } - - check_chain_order(&certs) -} - -#[cfg(not(feature = "openssl"))] -fn check_chain_order_der(cert_ders: &[Vec]) -> bool { - true -} diff --git a/sdk/src/openssl/openssl_trust_handler.rs b/sdk/src/openssl/openssl_trust_handler.rs index 9d5ae56b9..bfaabae35 100644 --- a/sdk/src/openssl/openssl_trust_handler.rs +++ b/sdk/src/openssl/openssl_trust_handler.rs @@ -302,28 +302,23 @@ pub mod tests { use c2pa_crypto::SigningAlg; use super::*; - use crate::{ - openssl::temp_signer::{self}, - Signer, - }; + use crate::{utils::test_signer::test_signer, Signer}; #[test] fn test_trust_store() { - let cert_dir = crate::utils::test::fixture_path("certs"); - let mut th = OpenSSLTrustHandlerConfig::new(); th.clear(); th.load_default_trust().unwrap(); // test all the certs - let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); - let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); - let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); - let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); - let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); - let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); - let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + let ps256 = test_signer(SigningAlg::Ps256); + let ps384 = test_signer(SigningAlg::Ps384); + let ps512 = test_signer(SigningAlg::Ps512); + let es256 = test_signer(SigningAlg::Es256); + let es384 = test_signer(SigningAlg::Es384); + let es512 = test_signer(SigningAlg::Es512); + let ed25519 = test_signer(SigningAlg::Ed25519); let ps256_certs = ps256.certs().unwrap(); let ps384_certs = ps384.certs().unwrap(); @@ -344,7 +339,6 @@ pub mod tests { #[test] fn test_broken_trust_chain() { - let cert_dir = crate::utils::test::fixture_path("certs"); let ta = include_bytes!("../../tests/fixtures/certs/trust/test_cert_root_bundle.pem"); let mut th = OpenSSLTrustHandlerConfig::new(); @@ -355,13 +349,13 @@ pub mod tests { th.load_trust_anchors_from_data(&mut reader).unwrap(); // test all the certs - let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); - let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); - let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); - let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); - let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); - let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); - let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + let ps256 = test_signer(SigningAlg::Ps256); + let ps384 = test_signer(SigningAlg::Ps384); + let ps512 = test_signer(SigningAlg::Ps512); + let es256 = test_signer(SigningAlg::Es256); + let es384 = test_signer(SigningAlg::Es384); + let es512 = test_signer(SigningAlg::Es512); + let ed25519 = test_signer(SigningAlg::Ed25519); let ps256_certs = ps256.certs().unwrap(); let ps384_certs = ps384.certs().unwrap(); @@ -383,8 +377,6 @@ pub mod tests { #[test] fn test_allowed_list() { - let cert_dir = crate::utils::test::fixture_path("certs"); - let mut th = OpenSSLTrustHandlerConfig::new(); th.clear(); @@ -397,13 +389,13 @@ pub mod tests { th.load_allowed_list(&mut allowed_list).unwrap(); // test all the certs - let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); - let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); - let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); - let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); - let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); - let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); - let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + let ps256 = test_signer(SigningAlg::Ps256); + let ps384 = test_signer(SigningAlg::Ps384); + let ps512 = test_signer(SigningAlg::Ps512); + let es256 = test_signer(SigningAlg::Es256); + let es384 = test_signer(SigningAlg::Es384); + let es512 = test_signer(SigningAlg::Es512); + let ed25519 = test_signer(SigningAlg::Ed25519); let ps256_certs = ps256.certs().unwrap(); let ps384_certs = ps384.certs().unwrap(); @@ -424,8 +416,6 @@ pub mod tests { #[test] fn test_allowed_list_hashes() { - let cert_dir = crate::utils::test::fixture_path("certs"); - let mut th = OpenSSLTrustHandlerConfig::new(); th.clear(); @@ -438,13 +428,13 @@ pub mod tests { th.load_allowed_list(&mut allowed_list).unwrap(); // test all the certs - let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); - let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); - let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); - let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); - let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); - let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); - let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + let ps256 = test_signer(SigningAlg::Ps256); + let ps384 = test_signer(SigningAlg::Ps384); + let ps512 = test_signer(SigningAlg::Ps512); + let es256 = test_signer(SigningAlg::Es256); + let es384 = test_signer(SigningAlg::Es384); + let es512 = test_signer(SigningAlg::Es512); + let ed25519 = test_signer(SigningAlg::Ed25519); let ps256_certs = ps256.certs().unwrap(); let ps384_certs = ps384.certs().unwrap(); diff --git a/sdk/src/openssl/rsa_signer.rs b/sdk/src/openssl/rsa_signer.rs deleted file mode 100644 index ce19a5004..000000000 --- a/sdk/src/openssl/rsa_signer.rs +++ /dev/null @@ -1,303 +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::cell::Cell; - -use c2pa_crypto::{ - ocsp::OcspResponse, openssl::OpenSslMutex, time_stamp::TimeStampProvider, SigningAlg, -}; -use openssl::{ - hash::MessageDigest, - pkey::{PKey, Private}, - rsa::{Rsa, RsaPrivateKeyBuilder}, - x509::X509, -}; - -use super::check_chain_order; -use crate::{signer::ConfigurableSigner, Error, Result, Signer}; - -/// Implements `Signer` trait using OpenSSL's implementation of -/// SHA256 + RSA encryption. -pub struct RsaSigner { - signcerts: Vec, - pkey: PKey, - - certs_size: usize, - timestamp_size: usize, - ocsp_size: Cell, - - alg: SigningAlg, - tsa_url: Option, - ocsp_rsp: Cell, -} - -impl RsaSigner { - // Sample of OCSP stapling while signing. This code is only for demo purposes and not for - // production use since there is no caching in the SDK and fetching is expensive. This is behind the - // feature flag 'psxxx_ocsp_stapling_experimental' - fn update_ocsp(&self) { - // IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please - // don't make this pub or pub(crate) without finding a way to ensure that - // precondition. - - // do we need an update - let now = chrono::offset::Utc::now(); - - // is it time for an OCSP update - let ocsp_data = self.ocsp_rsp.take(); - let next_update = ocsp_data.next_update; - self.ocsp_rsp.set(ocsp_data); - if now > next_update { - #[cfg(feature = "psxxx_ocsp_stapling_experimental")] - { - if let Ok(certs) = self.certs_internal() { - if let Some(ocsp_rsp) = c2pa_crypto::ocsp::fetch_ocsp_response(&certs) { - self.ocsp_size.set(ocsp_rsp.len()); - let mut validation_log = - c2pa_status_tracker::DetailedStatusTracker::default(); - if let Ok(ocsp_response) = - OcspResponse::from_der_checked(&ocsp_rsp, None, &mut validation_log) - { - self.ocsp_rsp.set(ocsp_response); - } - } - } - } - } - } - - fn certs_internal(&self) -> Result>> { - // IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please - // don't make this pub or pub(crate) without finding a way to ensure that - // precondition. - - let mut certs: Vec> = Vec::new(); - - for c in &self.signcerts { - let cert = c.to_der().map_err(wrap_openssl_err)?; - certs.push(cert); - } - - Ok(certs) - } -} - -impl ConfigurableSigner for RsaSigner { - fn from_signcert_and_pkey( - signcert: &[u8], - pkey: &[u8], - alg: SigningAlg, - tsa_url: Option, - ) -> Result { - let _openssl = OpenSslMutex::acquire()?; - - let signcerts = X509::stack_from_pem(signcert).map_err(wrap_openssl_err)?; - let rsa = Rsa::private_key_from_pem(pkey).map_err(wrap_openssl_err)?; - - // make sure cert chains are in order - if !check_chain_order(&signcerts) { - return Err(Error::BadParam( - "certificate chain is not in correct order".to_string(), - )); - } - - // rebuild RSA keys to eliminate incompatible values - let n = rsa.n().to_owned().map_err(wrap_openssl_err)?; - let e = rsa.e().to_owned().map_err(wrap_openssl_err)?; - let d = rsa.d().to_owned().map_err(wrap_openssl_err)?; - let po = rsa.p(); - let qo = rsa.q(); - let dmp1o = rsa.dmp1(); - let dmq1o = rsa.dmq1(); - let iqmpo = rsa.iqmp(); - let mut builder = RsaPrivateKeyBuilder::new(n, e, d).map_err(wrap_openssl_err)?; - - if let Some(p) = po { - if let Some(q) = qo { - builder = builder - .set_factors(p.to_owned()?, q.to_owned()?) - .map_err(wrap_openssl_err)?; - } - } - - if let Some(dmp1) = dmp1o { - if let Some(dmq1) = dmq1o { - if let Some(iqmp) = iqmpo { - builder = builder - .set_crt_params(dmp1.to_owned()?, dmq1.to_owned()?, iqmp.to_owned()?) - .map_err(wrap_openssl_err)?; - } - } - } - - let new_rsa = builder.build(); - - let pkey = PKey::from_rsa(new_rsa).map_err(wrap_openssl_err)?; - - let signer = RsaSigner { - signcerts, - pkey, - certs_size: signcert.len(), - timestamp_size: 10000, /* todo: call out to TSA to get actual timestamp and use that size */ - ocsp_size: Cell::new(0), - alg, - tsa_url, - ocsp_rsp: Cell::new(OcspResponse::default()), - }; - - // get OCSP if possible - signer.update_ocsp(); - - Ok(signer) - } -} - -impl Signer for RsaSigner { - fn sign(&self, data: &[u8]) -> Result> { - let mut signer = match self.alg { - SigningAlg::Ps256 => { - let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &self.pkey) - .map_err(wrap_openssl_err)?; - - signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; // use C2PA recommended padding - signer.set_rsa_mgf1_md(MessageDigest::sha256())?; - signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; - signer - } - SigningAlg::Ps384 => { - let mut signer = openssl::sign::Signer::new(MessageDigest::sha384(), &self.pkey) - .map_err(wrap_openssl_err)?; - - signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; // use C2PA recommended padding - signer.set_rsa_mgf1_md(MessageDigest::sha384())?; - signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; - signer - } - SigningAlg::Ps512 => { - let mut signer = openssl::sign::Signer::new(MessageDigest::sha512(), &self.pkey) - .map_err(wrap_openssl_err)?; - - signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; // use C2PA recommended padding - signer.set_rsa_mgf1_md(MessageDigest::sha512())?; - signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; - signer - } - // "rs256" => openssl::sign::Signer::new(MessageDigest::sha256(), &self.pkey) - // .map_err(wrap_openssl_err)?, - // "rs384" => openssl::sign::Signer::new(MessageDigest::sha384(), &self.pkey) - // .map_err(wrap_openssl_err)?, - // "rs512" => openssl::sign::Signer::new(MessageDigest::sha512(), &self.pkey) - // .map_err(wrap_openssl_err)?, - _ => return Err(Error::UnsupportedType), - }; - - let signed_data = signer.sign_oneshot_to_vec(data)?; - - // println!("sig: {}", Hexlify(&signed_data)); - - Ok(signed_data) - } - - fn reserve_size(&self) -> usize { - 1024 + self.certs_size + self.timestamp_size + self.ocsp_size.get() // the Cose_Sign1 contains complete certs, timestamps and ocsp so account for size - } - - fn certs(&self) -> Result>> { - let _openssl = OpenSslMutex::acquire()?; - self.certs_internal() - } - - fn alg(&self) -> SigningAlg { - self.alg - } - - fn ocsp_val(&self) -> Option> { - let _openssl = OpenSslMutex::acquire().ok()?; - - // update OCSP if needed - self.update_ocsp(); - - let ocsp_data = self.ocsp_rsp.take(); - let ocsp_rsp = ocsp_data.ocsp_der.clone(); - self.ocsp_rsp.set(ocsp_data); - if !ocsp_rsp.is_empty() { - Some(ocsp_rsp) - } else { - None - } - } -} - -impl TimeStampProvider for RsaSigner { - fn time_stamp_service_url(&self) -> Option { - self.tsa_url.clone() - } -} - -fn wrap_openssl_err(err: openssl::error::ErrorStack) -> Error { - Error::OpenSslError(err) -} - -#[allow(unused_imports)] -#[allow(clippy::unwrap_used)] -#[cfg(test)] -mod tests { - - use super::*; - use crate::{ - utils::test::{fixture_path, temp_signer}, - Signer, SigningAlg, - }; - - #[test] - fn signer_from_files() { - let signer = temp_signer(); - let data = b"some sample content to sign"; - - let signature = signer.sign(data).unwrap(); - println!("signature len = {}", signature.len()); - assert!(signature.len() <= signer.reserve_size()); - } - - #[test] - fn sign_ps256() { - let cert_bytes = include_bytes!("../../tests/fixtures/temp_cert.data"); - let key_bytes = include_bytes!("../../tests/fixtures/temp_priv_key.data"); - - let signer = - RsaSigner::from_signcert_and_pkey(cert_bytes, key_bytes, SigningAlg::Ps256, None) - .unwrap(); - - let data = b"some sample content to sign"; - - let signature = signer.sign(data).unwrap(); - println!("signature len = {}", signature.len()); - assert!(signature.len() <= signer.reserve_size()); - } - - // #[test] - // fn sign_rs256() { - // let cert_bytes = include_bytes!("../../tests/fixtures/temp_cert.data"); - // let key_bytes = include_bytes!("../../tests/fixtures/temp_priv_key.data"); - - // let signer = - // RsaSigner::from_signcert_and_pkey(cert_bytes, key_bytes, "rs256".to_string(), None) - // .unwrap(); - - // let data = b"some sample content to sign"; - - // let signature = signer.sign(data).unwrap(); - // println!("signature len = {}", signature.len()); - // assert!(signature.len() <= signer.reserve_size()); - // } -} diff --git a/sdk/src/openssl/temp_signer.rs b/sdk/src/openssl/temp_signer.rs deleted file mode 100644 index 411615cdf..000000000 --- a/sdk/src/openssl/temp_signer.rs +++ /dev/null @@ -1,184 +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. - -#![deny(missing_docs)] - -//! Temporary signing instances for testing purposes. -//! -//! This module contains functions to create self-signed certificates -//! and provision [`Signer`] instances for each of the supported signature -//! formats. -//! -//! Private-key and signing certificate pairs are created in a directory -//! provided by the caller. It is recommended to use a temporary directory -//! that is deleted upon completion of the test. (We recommend using -//! the [tempfile](https://crates.io/crates/tempfile) crate.) -//! -//! This module should be used only for testing purposes. - -// Since this module is intended for testing purposes, all of -// its functions are allowed to panic. -#![allow(clippy::panic)] -#![allow(clippy::unwrap_used)] - -#[cfg(feature = "file_io")] -use std::path::{Path, PathBuf}; - -#[cfg(feature = "file_io")] -use c2pa_crypto::SigningAlg; - -#[cfg(feature = "file_io")] -use crate::{ - openssl::{EcSigner, EdSigner, RsaSigner}, - signer::ConfigurableSigner, -}; - -/// Create an OpenSSL ES256 signer that can be used for testing purposes. -/// -/// # Arguments -/// -/// * `path` - A directory (which must already exist) to receive the temporary -/// private key / certificate pair. -/// * `alg` - A format for signing. Must be one of the `SigningAlg::Es*` variants. -/// * `tsa_url` - Optional URL for a timestamp authority. -/// -/// # Returns -/// -/// Returns a tuple of `(signer, sign_cert_path)` where `signer` is -/// the [`Signer`] instance and `sign_cert_path` is the path to the -/// signing certificate. -/// -/// # Panics -/// -/// Can panic if unable to invoke OpenSSL executable properly. -#[cfg(feature = "file_io")] -pub fn get_ec_signer>( - path: P, - alg: SigningAlg, - tsa_url: Option, -) -> (EcSigner, PathBuf) { - match alg { - SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => (), - _ => { - panic!("Unknown EC signer alg {alg:#?}"); - } - } - - let mut sign_cert_path = path.as_ref().to_path_buf(); - sign_cert_path.push(alg.to_string()); - sign_cert_path.set_extension("pub"); - - let mut pem_key_path = path.as_ref().to_path_buf(); - pem_key_path.push(alg.to_string()); - pem_key_path.set_extension("pem"); - - ( - EcSigner::from_files(&sign_cert_path, &pem_key_path, alg, tsa_url).unwrap(), - sign_cert_path, - ) -} - -/// Create an OpenSSL ES256 signer that can be used for testing purposes. -/// -/// # Arguments -/// -/// * `path` - A directory (which must already exist) to look for -/// private key / certificate pair. -/// * `alg` - A format for signing. Must be `ed25519`. -/// * `tsa_url` - Optional URL for a timestamp authority. -/// -/// # Returns -/// -/// Returns a tuple of `(signer, sign_cert_path)` where `signer` is -/// the [`Signer`] instance and `sign_cert_path` is the path to the -/// signing certificate. -/// -/// # Panics -/// -/// Can panic if unable to invoke OpenSSL executable properly. -#[cfg(feature = "file_io")] -pub fn get_ed_signer>( - path: P, - alg: SigningAlg, - tsa_url: Option, -) -> (EdSigner, PathBuf) { - if alg != SigningAlg::Ed25519 { - panic!("Unknown ED signer alg {alg:#?}"); - } - - let mut sign_cert_path = path.as_ref().to_path_buf(); - sign_cert_path.push(alg.to_string()); - sign_cert_path.set_extension("pub"); - - let mut pem_key_path = path.as_ref().to_path_buf(); - pem_key_path.push(alg.to_string()); - pem_key_path.set_extension("pem"); - - ( - EdSigner::from_files(&sign_cert_path, &pem_key_path, alg, tsa_url).unwrap(), - sign_cert_path, - ) -} - -/// Create an OpenSSL SHA+RSA signer that can be used for testing purposes. -/// -/// # Arguments -/// -/// * `path` - A directory (which must already exist) to receive the temporary -/// private key / certificate pair. -/// * `alg` - A format for signing. Must be one of the `SignerAlg::Ps*` options. -/// * `tsa_url` - Optional URL for a timestamp authority. -/// -/// # Returns -/// -/// Returns a tuple of `(signer, sign_cert_path)` where `signer` is -/// the [`Signer`] instance and `sign_cert_path` is the path to the -/// signing certificate. -/// -/// # Panics -/// -/// Can panic if unable to invoke OpenSSL executable properly. -#[cfg(feature = "file_io")] -pub fn get_rsa_signer>( - path: P, - alg: SigningAlg, - tsa_url: Option, -) -> (RsaSigner, PathBuf) { - match alg { - SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => (), - _ => { - panic!("Unknown RSA signer alg {alg:#?}"); - } - } - - let mut sign_cert_path = path.as_ref().to_path_buf(); - sign_cert_path.push(alg.to_string()); - sign_cert_path.set_extension("pub"); - - let mut pem_key_path = path.as_ref().to_path_buf(); - pem_key_path.push(alg.to_string()); - pem_key_path.set_extension("pem"); - - if !sign_cert_path.exists() || !pem_key_path.exists() { - panic!( - "path found: {}, {}", - sign_cert_path.display(), - pem_key_path.display() - ); - } - - ( - RsaSigner::from_files(&sign_cert_path, &pem_key_path, alg, tsa_url).unwrap(), - sign_cert_path, - ) -} diff --git a/sdk/src/openssl/temp_signer_async.rs b/sdk/src/openssl/temp_signer_async.rs deleted file mode 100644 index 4dc81fa2a..000000000 --- a/sdk/src/openssl/temp_signer_async.rs +++ /dev/null @@ -1,101 +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. - -#![deny(missing_docs)] - -//! Temporary async signing instances for testing purposes. -//! -//! This is only a demonstration async Signer that is used to test -//! the asynchronous signing of claims. -//! This module should be used only for testing purposes. - -#[cfg(feature = "openssl_sign")] -use c2pa_crypto::SigningAlg; - -#[cfg(feature = "openssl_sign")] -fn get_local_signer(alg: SigningAlg) -> Box { - let cert_dir = crate::utils::test::fixture_path("certs"); - - match alg { - SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => { - let (s, _k) = super::temp_signer::get_rsa_signer(&cert_dir, alg, None); - Box::new(s) - } - SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => { - let (s, _k) = super::temp_signer::get_ec_signer(&cert_dir, alg, None); - Box::new(s) - } - SigningAlg::Ed25519 => { - let (s, _k) = super::temp_signer::get_ed_signer(&cert_dir, alg, None); - Box::new(s) - } - } -} - -#[cfg(feature = "openssl_sign")] -pub struct AsyncSignerAdapter { - alg: SigningAlg, - certs: Vec>, - reserve_size: usize, - tsa_url: Option, - ocsp_val: Option>, -} - -#[cfg(feature = "openssl_sign")] -impl AsyncSignerAdapter { - pub fn new(alg: SigningAlg) -> Self { - let signer = get_local_signer(alg); - - AsyncSignerAdapter { - alg, - certs: signer.certs().unwrap_or_default(), - reserve_size: signer.reserve_size(), - tsa_url: signer.time_authority_url(), - ocsp_val: signer.ocsp_val(), - } - } -} - -#[cfg(test)] -#[cfg(feature = "openssl_sign")] -#[async_trait::async_trait] -impl crate::AsyncSigner for AsyncSignerAdapter { - async fn sign(&self, data: Vec) -> crate::error::Result> { - let signer = get_local_signer(self.alg); - signer.sign(&data) - } - - fn alg(&self) -> SigningAlg { - self.alg - } - - fn certs(&self) -> crate::Result>> { - let mut output: Vec> = Vec::new(); - for v in &self.certs { - output.push(v.clone()); - } - Ok(output) - } - - fn reserve_size(&self) -> usize { - self.reserve_size - } - - fn time_authority_url(&self) -> Option { - self.tsa_url.clone() - } - - async fn ocsp_val(&self) -> Option> { - self.ocsp_val.clone() - } -} diff --git a/sdk/src/resource_store.rs b/sdk/src/resource_store.rs index 6d6eff8f4..c5229d23b 100644 --- a/sdk/src/resource_store.rs +++ b/sdk/src/resource_store.rs @@ -404,8 +404,10 @@ mod tests { use std::io::Cursor; + use c2pa_crypto::SigningAlg; + use super::*; - use crate::{utils::test::temp_signer, Builder, Reader}; + use crate::{utils::test_signer::test_signer, Builder, Reader}; #[test] #[cfg(feature = "openssl_sign")] @@ -451,7 +453,8 @@ mod tests { let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); + // Embed a manifest using the signer. let mut output_image = Cursor::new(Vec::new()); builder diff --git a/sdk/src/signer.rs b/sdk/src/signer.rs index cc42fa37a..72c37b436 100644 --- a/sdk/src/signer.rs +++ b/sdk/src/signer.rs @@ -12,7 +12,7 @@ // each license. use async_trait::async_trait; -use c2pa_crypto::SigningAlg; +use c2pa_crypto::{raw_signature::RawSigner, SigningAlg}; use crate::{DynamicAssertion, Result}; @@ -107,10 +107,8 @@ pub(crate) trait ConfigurableSigner: Signer + Sized { alg: SigningAlg, tsa_url: Option, ) -> Result { - use crate::Error; - - let signcert = std::fs::read(signcert_path).map_err(Error::IoError)?; - let pkey = std::fs::read(pkey_path).map_err(Error::IoError)?; + let signcert = std::fs::read(signcert_path).map_err(crate::Error::IoError)?; + let pkey = std::fs::read(pkey_path).map_err(crate::Error::IoError)?; Self::from_signcert_and_pkey(&signcert, &pkey, alg, tsa_url) } @@ -300,7 +298,7 @@ pub trait RemoteSigner: Sync { fn reserve_size(&self) -> usize; } -impl Signer for Box { +impl Signer for Box { fn sign(&self, data: &[u8]) -> Result> { (**self).sign(data) } @@ -328,4 +326,161 @@ impl Signer for Box { fn dynamic_assertions(&self) -> Vec> { (**self).dynamic_assertions() } + + fn time_authority_url(&self) -> Option { + (**self).time_authority_url() + } + + fn timestamp_request_headers(&self) -> Option> { + (**self).timestamp_request_headers() + } + + fn timestamp_request_body(&self, message: &[u8]) -> Result> { + (**self).timestamp_request_body(message) + } + + fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + (**self).send_timestamp_request(message) + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[async_trait] +impl AsyncSigner for Box { + async fn sign(&self, data: Vec) -> Result> { + (**self).sign(data).await + } + + fn alg(&self) -> SigningAlg { + (**self).alg() + } + + fn certs(&self) -> Result>> { + (**self).certs() + } + + fn reserve_size(&self) -> usize { + (**self).reserve_size() + } + + fn time_authority_url(&self) -> Option { + (**self).time_authority_url() + } + + fn timestamp_request_headers(&self) -> Option> { + (**self).timestamp_request_headers() + } + + fn timestamp_request_body(&self, message: &[u8]) -> Result> { + (**self).timestamp_request_body(message) + } + + async fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + (**self).send_timestamp_request(message).await + } + + async fn ocsp_val(&self) -> Option> { + (**self).ocsp_val().await + } + + fn direct_cose_handling(&self) -> bool { + (**self).direct_cose_handling() + } + + fn dynamic_assertions(&self) -> Vec> { + (**self).dynamic_assertions() + } +} + +#[cfg(target_arch = "wasm32")] +#[async_trait(?Send)] +impl AsyncSigner for Box { + async fn sign(&self, data: Vec) -> Result> { + (**self).sign(data).await + } + + fn alg(&self) -> SigningAlg { + (**self).alg() + } + + fn certs(&self) -> Result>> { + (**self).certs() + } + + fn reserve_size(&self) -> usize { + (**self).reserve_size() + } + + fn time_authority_url(&self) -> Option { + (**self).time_authority_url() + } + + fn timestamp_request_headers(&self) -> Option> { + (**self).timestamp_request_headers() + } + + fn timestamp_request_body(&self, message: &[u8]) -> Result> { + (**self).timestamp_request_body(message) + } + + async fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + (**self).send_timestamp_request(message).await + } + + async fn ocsp_val(&self) -> Option> { + (**self).ocsp_val().await + } + + fn direct_cose_handling(&self) -> bool { + (**self).direct_cose_handling() + } + + fn dynamic_assertions(&self) -> Vec> { + (**self).dynamic_assertions() + } +} + +#[cfg_attr(target_arch = "wasm32", allow(dead_code))] +pub(crate) struct RawSignerWrapper(pub(crate) Box); + +impl Signer for RawSignerWrapper { + fn sign(&self, data: &[u8]) -> Result> { + self.0.sign(data).map_err(|e| e.into()) + } + + fn alg(&self) -> SigningAlg { + self.0.alg() + } + + fn certs(&self) -> Result>> { + self.0.cert_chain().map_err(|e| e.into()) + } + + fn reserve_size(&self) -> usize { + self.0.reserve_size() + } + + fn ocsp_val(&self) -> Option> { + self.0.ocsp_response() + } + + fn time_authority_url(&self) -> Option { + self.0.time_stamp_service_url() + } + + fn timestamp_request_headers(&self) -> Option> { + self.0.time_stamp_request_headers() + } + + fn timestamp_request_body(&self, message: &[u8]) -> Result> { + self.0 + .time_stamp_request_body(message) + .map_err(|e| e.into()) + } + + fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + self.0 + .send_time_stamp_request(message) + .map(|r| r.map_err(|e| e.into())) + } } diff --git a/sdk/src/store.rs b/sdk/src/store.rs index a560a0970..31d56a5e0 100644 --- a/sdk/src/store.rs +++ b/sdk/src/store.rs @@ -510,7 +510,8 @@ impl Store { } else { if signer.direct_cose_handling() { // Let the signer do all the COSE processing and return the structured COSE data. - return signer.sign(claim_bytes.clone()).await; // do not verify remote signers (we never did) + return signer.sign(claim_bytes.clone()).await; + // do not verify remote signers (we never did) } else { cose_sign_async(signer, &claim_bytes, box_size).await } @@ -3644,9 +3645,10 @@ pub mod tests { hash_utils::Hasher, patch::patch_file, test::{ - create_test_claim, fixture_path, temp_dir_path, temp_fixture_path, temp_signer, + create_test_claim, fixture_path, temp_dir_path, temp_fixture_path, write_jpeg_placeholder_file, }, + test_signer::{async_test_signer, test_signer}, }, }; @@ -3697,7 +3699,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Test generate JUMBF // Get labels for label test @@ -3810,7 +3812,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -3850,7 +3852,7 @@ pub mod tests { struct BadSigner {} - impl crate::Signer for BadSigner { + impl Signer for BadSigner { fn sign(&self, _data: &[u8]) -> Result> { Ok(b"not a valid signature".to_vec()) } @@ -3895,7 +3897,9 @@ pub mod tests { #[test] #[cfg(feature = "file_io")] fn test_sign_with_expired_cert() { - use crate::{openssl::RsaSigner, signer::ConfigurableSigner, SigningAlg}; + use c2pa_crypto::SigningAlg; + + use crate::create_signer; // test adding to actual image let ap = fixture_path("earth_apollo17.jpg"); @@ -3909,7 +3913,7 @@ pub mod tests { let signcert_path = fixture_path("rsa-pss256_key-expired.pub"); let pkey_path = fixture_path("rsa-pss256-expired.pem"); let signer = - RsaSigner::from_files(signcert_path, pkey_path, SigningAlg::Ps256, None).unwrap(); + create_signer::from_files(signcert_path, pkey_path, SigningAlg::Ps256, None).unwrap(); store.commit_claim(claim).unwrap(); @@ -3955,7 +3959,7 @@ pub mod tests { #[actix::test] async fn test_jumbf_generation_async() { - let signer = crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = async_test_signer(SigningAlg::Ps256); // test adding to actual image let ap = fixture_path("earth_apollo17.jpg"); @@ -4080,7 +4084,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4176,7 +4180,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commmits store.commit_claim(claim1).unwrap(); @@ -4249,7 +4253,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commmits store.commit_claim(claim1).unwrap(); @@ -4323,7 +4327,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4397,7 +4401,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4471,7 +4475,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4537,7 +4541,7 @@ pub mod tests { let claim1 = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4581,7 +4585,7 @@ pub mod tests { let claim1 = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4625,7 +4629,7 @@ pub mod tests { let claim1 = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4751,7 +4755,7 @@ pub mod tests { fn test_verifiable_credentials() { use crate::utils::test::create_test_store; - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // test adding to actual image let ap = fixture_path("earth_apollo17.jpg"); @@ -4789,7 +4793,7 @@ pub mod tests { fn test_data_box_creation() { use crate::utils::test::create_test_store; - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // test adding to actual image let ap = fixture_path("earth_apollo17.jpg"); @@ -4844,7 +4848,7 @@ pub mod tests { fn test_update_manifest() { use crate::{hashed_uri::HashedUri, utils::test::create_test_store}; - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // test adding to actual image let ap = fixture_path("earth_apollo17.jpg"); @@ -5019,7 +5023,7 @@ pub mod tests { // Create a new claim. let claim1 = create_test_claim().unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. store.commit_claim(claim1).unwrap(); @@ -5049,7 +5053,7 @@ pub mod tests { // Create a new claim. let claim1 = create_test_claim().unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let result: Vec = Vec::new(); let mut output_stream = Cursor::new(result); @@ -5111,7 +5115,7 @@ pub mod tests { claim.set_external_manifest(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); store.commit_claim(claim).unwrap(); @@ -5148,7 +5152,7 @@ pub mod tests { let mut claim = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // start with base url let fp = format!("file:/{}", sidecar.to_str().unwrap()); @@ -5216,7 +5220,7 @@ pub mod tests { let mut claim = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // start with base url let fp = format!("file:/{}", sidecar.to_str().unwrap()); @@ -5268,7 +5272,7 @@ pub mod tests { let mut claim = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // start with base url let fp = format!("file:/{}", sidecar.to_str().unwrap()); @@ -5324,7 +5328,7 @@ pub mod tests { // Create a new claim. let claim1 = create_test_claim().unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); store.commit_claim(claim1).unwrap(); @@ -5378,7 +5382,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -5448,7 +5452,7 @@ pub mod tests { store.commit_claim(claim).unwrap(); // Do we generate JUMBF? - let signer = crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = async_test_signer(SigningAlg::Ps256); // get the embeddable manifest let em = store @@ -5534,7 +5538,7 @@ pub mod tests { store.commit_claim(claim).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // get the embeddable manifest let em = store @@ -5597,12 +5601,12 @@ pub mod tests { #[cfg(feature = "file_io")] async fn test_datahash_embeddable_manifest_async() { // test adding to actual image - use std::io::SeekFrom; + let ap = fixture_path("cloud.jpg"); // Do we generate JUMBF? - let signer = crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = async_test_signer(SigningAlg::Ps256); // Create claims store. let mut store = Store::new(); @@ -5671,7 +5675,7 @@ pub mod tests { let ap = fixture_path("cloud.jpg"); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Create claims store. let mut store = Store::new(); @@ -5742,7 +5746,7 @@ pub mod tests { let mut hasher = Hasher::SHA256(Sha256::new()); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Create claims store. let mut store = Store::new(); @@ -5848,7 +5852,7 @@ pub mod tests { fn test_placed_manifest() { use crate::jumbf::labels::to_normalized_uri; - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // test adding to actual image let ap = fixture_path("C.jpg"); @@ -5968,7 +5972,7 @@ pub mod tests { impl DynamicSigner { fn new() -> Self { - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); DynamicSigner { alg: signer.alg(), certs: signer.certs().unwrap_or_default(), @@ -5982,7 +5986,7 @@ pub mod tests { #[async_trait::async_trait] impl crate::AsyncSigner for DynamicSigner { async fn sign(&self, data: Vec) -> crate::error::Result> { - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); signer.sign(&data) } @@ -6095,7 +6099,7 @@ pub mod tests { store.commit_claim(claim).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // add manifest based on let new_output_path = output_path.join(init_dir.file_name().unwrap()); diff --git a/sdk/src/utils/mod.rs b/sdk/src/utils/mod.rs index e63b3a037..5c2887a31 100644 --- a/sdk/src/utils/mod.rs +++ b/sdk/src/utils/mod.rs @@ -19,7 +19,6 @@ pub(crate) mod merkle; pub(crate) mod mime; #[allow(dead_code)] // for wasm build pub(crate) mod patch; -pub(crate) mod sig_utils; #[cfg(feature = "add_thumbnails")] pub(crate) mod thumbnail; pub(crate) mod time_it; @@ -29,3 +28,6 @@ pub(crate) mod xmp_inmemory_utils; #[cfg(test)] #[allow(dead_code)] // for wasm build pub mod test; + +#[cfg(test)] +pub(crate) mod test_signer; diff --git a/sdk/src/utils/sig_utils.rs b/sdk/src/utils/sig_utils.rs deleted file mode 100644 index c5ef223a4..000000000 --- a/sdk/src/utils/sig_utils.rs +++ /dev/null @@ -1,73 +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 c2pa_crypto::{p1363::parse_ec_der_sig, SigningAlg}; - -use crate::{Error, Result}; - -pub(crate) fn der_to_p1363(data: &[u8], alg: SigningAlg) -> Result> { - // P1363 format: r | s - - let (_, p) = parse_ec_der_sig(data).map_err(|_err| Error::InvalidEcdsaSignature)?; - - let mut r = extfmt::Hexlify(p.r).to_string(); - let mut s = extfmt::Hexlify(p.s).to_string(); - - let sig_len: usize = match alg { - SigningAlg::Es256 => 64, - SigningAlg::Es384 => 96, - SigningAlg::Es512 => 132, - _ => return Err(Error::UnsupportedType), - }; - - // pad or truncate as needed - let rp = if r.len() > sig_len { - // truncate - let offset = r.len() - sig_len; - &r[offset..r.len()] - } else { - // pad - while r.len() != sig_len { - r.insert(0, '0'); - } - r.as_ref() - }; - - let sp = if s.len() > sig_len { - // truncate - let offset = s.len() - sig_len; - &s[offset..s.len()] - } else { - // pad - while s.len() != sig_len { - s.insert(0, '0'); - } - s.as_ref() - }; - - if rp.len() != sig_len || rp.len() != sp.len() { - return Err(Error::InvalidEcdsaSignature); - } - - // merge r and s strings - let mut new_sig = rp.to_string(); - new_sig.push_str(sp); - - // convert back from hex string to byte array - (0..new_sig.len()) - .step_by(2) - .map(|i| { - u8::from_str_radix(&new_sig[i..i + 2], 16).map_err(|_err| Error::InvalidEcdsaSignature) - }) - .collect() -} diff --git a/sdk/src/utils/test.rs b/sdk/src/utils/test.rs index 485dfec82..bfa6f9176 100644 --- a/sdk/src/utils/test.rs +++ b/sdk/src/utils/test.rs @@ -23,8 +23,6 @@ use std::{ use c2pa_crypto::SigningAlg; use tempfile::TempDir; -#[cfg(feature = "file_io")] -use crate::create_signer; use crate::{ assertions::{labels, Action, Actions, Ingredient, ReviewRating, SchemaDotOrg, Thumbnail}, asset_io::CAIReadWrite, @@ -33,12 +31,7 @@ use crate::{ jumbf_io::get_assetio_handler, salt::DefaultSalt, store::Store, - RemoteSigner, Result, Signer, -}; -#[cfg(feature = "openssl_sign")] -use crate::{ - openssl::{AsyncSignerAdapter, RsaSigner}, - signer::ConfigurableSigner, + AsyncSigner, RemoteSigner, Result, }; pub const TEST_SMALL_JPEG: &str = "earth_apollo17.jpg"; @@ -212,7 +205,7 @@ pub fn temp_fixture_path(temp_dir: &TempDir, file_name: &str) -> PathBuf { /// Can panic if the certs cannot be read. (This function should only /// be used as part of testing infrastructure.) #[cfg(feature = "file_io")] -pub fn temp_signer_file() -> RsaSigner { +pub fn temp_signer_file() -> Box { #![allow(clippy::expect_used)] let mut sign_cert_path = fixture_path("certs"); sign_cert_path.push("ps256"); @@ -222,7 +215,7 @@ pub fn temp_signer_file() -> RsaSigner { pem_key_path.push("ps256"); pem_key_path.set_extension("pem"); - RsaSigner::from_files(&sign_cert_path, &pem_key_path, SigningAlg::Ps256, None) + crate::create_signer::from_files(&sign_cert_path, &pem_key_path, SigningAlg::Ps256, None) .expect("get_temp_signer") } @@ -311,7 +304,7 @@ pub(crate) struct AsyncTestGoodSigner {} #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] -impl crate::AsyncSigner for AsyncTestGoodSigner { +impl AsyncSigner for AsyncTestGoodSigner { async fn sign(&self, _data: Vec) -> Result> { Ok(b"not a valid signature".to_vec()) } @@ -336,75 +329,6 @@ impl crate::AsyncSigner for AsyncTestGoodSigner { } } -/// Create a [`Signer`] instance that can be used for testing purposes using ps256 alg. -/// -/// # Returns -/// -/// Returns a boxed [`Signer`] instance. -#[cfg(test)] -pub(crate) fn temp_signer() -> Box { - #[cfg(feature = "openssl_sign")] - { - #![allow(clippy::expect_used)] - let sign_cert = include_bytes!("../../tests/fixtures/certs/ps256.pub").to_vec(); - let pem_key = include_bytes!("../../tests/fixtures/certs/ps256.pem").to_vec(); - - let signer = - RsaSigner::from_signcert_and_pkey(&sign_cert, &pem_key, SigningAlg::Ps256, None) - .expect("get_temp_signer"); - - Box::new(signer) - } - - // todo: the will be a RustTLS signer shortly - #[cfg(not(feature = "openssl_sign"))] - { - Box::new(TestGoodSigner {}) - } -} - -#[cfg(any(target_arch = "wasm32", feature = "openssl_sign"))] -pub fn temp_async_signer() -> Box { - #[cfg(feature = "openssl_sign")] - { - Box::new(AsyncSignerAdapter::new(SigningAlg::Es256)) - } - - #[cfg(target_arch = "wasm32")] - { - let sign_cert = include_str!("../../tests/fixtures/certs/es256.pub"); - let pem_key = include_str!("../../tests/fixtures/certs/es256.pem"); - let signer = WebCryptoSigner::new("es256", sign_cert, pem_key); - Box::new(signer) - } -} - -/// Create a [`Signer`] instance for a specific algorithm that can be used for testing purposes. -/// -/// # Returns -/// -/// Returns a boxed [`Signer`] instance. -/// -/// # Panics -/// -/// Can panic if the certs cannot be read. (This function should only -/// be used as part of testing infrastructure.) -#[cfg(feature = "file_io")] -pub fn temp_signer_with_alg(alg: SigningAlg) -> Box { - #![allow(clippy::expect_used)] - // sign and embed into the target file - let mut sign_cert_path = fixture_path("certs"); - sign_cert_path.push(alg.to_string()); - sign_cert_path.set_extension("pub"); - - let mut pem_key_path = fixture_path("certs"); - pem_key_path.push(alg.to_string()); - pem_key_path.set_extension("pem"); - - create_signer::from_files(sign_cert_path.clone(), pem_key_path, alg, None) - .expect("get_temp_signer_with_alg") -} - struct TempRemoteSigner {} #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] @@ -413,8 +337,7 @@ impl crate::signer::RemoteSigner for TempRemoteSigner { async fn sign_remote(&self, claim_bytes: &[u8]) -> crate::error::Result> { #[cfg(feature = "openssl_sign")] { - let signer = - crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = crate::utils::test_signer::async_test_signer(SigningAlg::Ps256); // this would happen on some remote server crate::cose_sign::cose_sign_async(&signer, claim_bytes, self.reserve_size()).await @@ -559,17 +482,17 @@ struct TempAsyncRemoteSigner { #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] -impl crate::signer::AsyncSigner for TempAsyncRemoteSigner { +impl AsyncSigner for TempAsyncRemoteSigner { // this will not be called but requires an implementation async fn sign(&self, claim_bytes: Vec) -> Result> { #[cfg(feature = "openssl_sign")] { - let signer = - crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = crate::utils::test_signer::async_test_signer(SigningAlg::Ps256); // this would happen on some remote server crate::cose_sign::cose_sign_async(&signer, &claim_bytes, self.reserve_size()).await } + #[cfg(target_arch = "wasm32")] { let signer = crate::wasm::rsa_wasm_signer::RsaWasmSignerAsync::new(); diff --git a/sdk/src/utils/test_signer.rs b/sdk/src/utils/test_signer.rs new file mode 100644 index 000000000..c9d32a99d --- /dev/null +++ b/sdk/src/utils/test_signer.rs @@ -0,0 +1,146 @@ +// 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(clippy::unwrap_used)] // This mod is only used in test code. + +use async_trait::async_trait; +use c2pa_crypto::{ + raw_signature::{ + async_signer_from_cert_chain_and_private_key, signer_from_cert_chain_and_private_key, + AsyncRawSigner, + }, + SigningAlg, +}; + +use crate::{signer::RawSignerWrapper, AsyncSigner, Result, Signer}; + +/// Creates a [`Signer`] instance for testing purposes using test credentials. +pub(crate) fn test_signer(alg: SigningAlg) -> Box { + let (cert_chain, private_key) = cert_chain_and_private_key_for_alg(alg); + + Box::new(RawSignerWrapper( + signer_from_cert_chain_and_private_key(&cert_chain, &private_key, alg, None).unwrap(), + )) +} + +/// Creates an [`AsyncSigner`] instance for testing purposes using test credentials. +#[cfg(not(target_arch = "wasm32"))] +pub(crate) fn async_test_signer(alg: SigningAlg) -> Box { + let (cert_chain, private_key) = cert_chain_and_private_key_for_alg(alg); + + Box::new(AsyncRawSignerWrapper( + async_signer_from_cert_chain_and_private_key(&cert_chain, &private_key, alg, None).unwrap(), + )) +} + +/// Creates an [`AsyncSigner`] instance for testing purposes using test credentials. +#[cfg(target_arch = "wasm32")] +pub(crate) fn async_test_signer(alg: SigningAlg) -> Box { + let (cert_chain, private_key) = cert_chain_and_private_key_for_alg(alg); + + Box::new(AsyncRawSignerWrapper( + async_signer_from_cert_chain_and_private_key(&cert_chain, &private_key, alg, None).unwrap(), + )) +} + +fn cert_chain_and_private_key_for_alg(alg: SigningAlg) -> (Vec, Vec) { + match alg { + SigningAlg::Ps256 => ( + include_bytes!("../../tests/fixtures/certs/ps256.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/ps256.pem").to_vec(), + ), + + SigningAlg::Ps384 => ( + include_bytes!("../../tests/fixtures/certs/ps384.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/ps384.pem").to_vec(), + ), + + SigningAlg::Ps512 => ( + include_bytes!("../../tests/fixtures/certs/ps512.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/ps512.pem").to_vec(), + ), + + SigningAlg::Es256 => ( + include_bytes!("../../tests/fixtures/certs/es256.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/es256.pem").to_vec(), + ), + + SigningAlg::Es384 => ( + include_bytes!("../../tests/fixtures/certs/es384.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/es384.pem").to_vec(), + ), + + SigningAlg::Es512 => ( + include_bytes!("../../tests/fixtures/certs/es512.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/es512.pem").to_vec(), + ), + + SigningAlg::Ed25519 => ( + include_bytes!("../../tests/fixtures/certs/ed25519.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/ed25519.pem").to_vec(), + ), + } +} + +#[cfg(not(target_arch = "wasm32"))] +struct AsyncRawSignerWrapper(Box); + +#[allow(dead_code)] // TEMPORARY: Not used on WASM +#[cfg(target_arch = "wasm32")] +struct AsyncRawSignerWrapper(Box); + +#[allow(dead_code)] // TEMPORARY: Not used on WASM +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl AsyncSigner for AsyncRawSignerWrapper { + async fn sign(&self, data: Vec) -> Result> { + self.0.sign(data).await.map_err(|e| e.into()) + } + + fn alg(&self) -> SigningAlg { + self.0.alg() + } + + fn certs(&self) -> Result>> { + self.0.cert_chain().map_err(|e| e.into()) + } + + fn reserve_size(&self) -> usize { + self.0.reserve_size() + } + + async fn ocsp_val(&self) -> Option> { + self.0.ocsp_response().await + } + + fn time_authority_url(&self) -> Option { + self.0.time_stamp_service_url() + } + + fn timestamp_request_headers(&self) -> Option> { + self.0.time_stamp_request_headers() + } + + fn timestamp_request_body(&self, message: &[u8]) -> Result> { + self.0 + .time_stamp_request_body(message) + .map_err(|e| e.into()) + } + + async fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + self.0 + .send_time_stamp_request(message) + .await + .map(|r| r.map_err(|e| e.into())) + } +} diff --git a/sdk/src/wasm/rsa_wasm_signer.rs b/sdk/src/wasm/rsa_wasm_signer.rs index cb1cda0fa..4e3320539 100644 --- a/sdk/src/wasm/rsa_wasm_signer.rs +++ b/sdk/src/wasm/rsa_wasm_signer.rs @@ -311,10 +311,7 @@ mod tests { }; use super::*; - use crate::{ - utils::test::{fixture_path, temp_signer}, - Signer, - }; + use crate::{utils::test::fixture_path, Signer}; #[test] fn sign_ps256() { diff --git a/sdk/tests/common/test_signer.rs b/sdk/tests/common/test_signer.rs index c3bd1a2c1..f2ee408f6 100644 --- a/sdk/tests/common/test_signer.rs +++ b/sdk/tests/common/test_signer.rs @@ -12,7 +12,7 @@ // each license. use c2pa::CallbackSigner; -use c2pa_crypto::SigningAlg; +use c2pa_crypto::{raw_signature::RawSignerError, SigningAlg}; const CERTS: &[u8] = include_bytes!("../../tests/fixtures/certs/ed25519.pub"); const PRIVATE_KEY: &[u8] = include_bytes!("../../tests/fixtures/certs/ed25519.pem"); @@ -29,12 +29,13 @@ fn ed_sign(data: &[u8], private_key: &[u8]) -> c2pa::Result> { // Parse the PEM data to get the private key let pem = parse(private_key).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?; + // For Ed25519, the key is 32 bytes long, so we skip the first 16 bytes of the PEM data let key_bytes = &pem.contents()[16..]; - let signing_key = - SigningKey::try_from(key_bytes).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?; + let signing_key = SigningKey::try_from(key_bytes) + .map_err(|e| RawSignerError::InternalError(e.to_string()))?; + // Sign the data let signature: Signature = signing_key.sign(data); - Ok(signature.to_bytes().to_vec()) } diff --git a/sdk/tests/v2_api_integration.rs b/sdk/tests/v2_api_integration.rs index cbeebd0cc..09d6e1b19 100644 --- a/sdk/tests/v2_api_integration.rs +++ b/sdk/tests/v2_api_integration.rs @@ -15,7 +15,6 @@ // Isolate from wasm by wrapping in module. #[cfg(not(target_arch = "wasm32"))] // wasm doesn't support ed25519 yet mod integration_v2 { - use std::io::{Cursor, Seek}; use anyhow::Result; @@ -181,13 +180,14 @@ mod integration_v2 { // Parse the PEM data to get the private key let pem = parse(private_key).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?; + // For Ed25519, the key is 32 bytes long, so we skip the first 16 bytes of the PEM data let key_bytes = &pem.contents()[16..]; let signing_key = SigningKey::try_from(key_bytes).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?; + // Sign the data let signature: Signature = signing_key.sign(data); - Ok(signature.to_bytes().to_vec()) } }