Skip to content

Commit

Permalink
chore: Move OpenSslMutex into c2pa-crypto (#684)
Browse files Browse the repository at this point in the history
  • Loading branch information
scouten-adobe authored Nov 16, 2024
1 parent fffdd64 commit d2e56f2
Show file tree
Hide file tree
Showing 19 changed files with 194 additions and 94 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ The Rust library crate provides:
* `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` this is an demonstration feature that will attempt 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.

## Example code

Expand Down
5 changes: 5 additions & 0 deletions internal/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ rustdoc-args = ["--cfg", "docsrs"]

[features]
json_schema = ["dep:schemars"]
openssl = ["dep:openssl"]

[dependencies]
base64 = "0.22.1"
Expand All @@ -43,10 +44,14 @@ thiserror = "1.0.61"
x509-certificate = "0.21.0"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
openssl = { version = "0.10.61", features = ["vendored"], optional = true }
ureq = "2.4.0"
url = "=2.5.2" # Can't advance to 2.5.3 until Unicode-3.0 license is reviewed.
x509-parser = "0.16.0"

[package.metadata.cargo-udeps.ignore]
normal = ["openssl"] # TEMPORARY: Remove after openssl transition complete.

[dependencies.chrono]
version = "0.4.38"
default-features = false
Expand Down
7 changes: 7 additions & 0 deletions internal/crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ pub mod hash;
pub(crate) mod internal;

pub mod ocsp;

#[cfg(all(feature = "openssl", not(target_arch = "wasm32")))]
pub mod openssl;

#[cfg(all(feature = "openssl", target_arch = "wasm32"))]
compile_error!("OpenSSL feature is not compatible with WASM platform");

pub mod validation_codes;

mod signing_alg;
Expand Down
79 changes: 79 additions & 0 deletions internal/crypto/src/openssl/ffi_mutex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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 std::{
error::Error,
fmt,
sync::{Mutex, MutexGuard},
};

static FFI_MUTEX: Mutex<()> = Mutex::new(());

/// This mutex must be used by all code that accesses OpenSSL native code since
/// the OpenSSL native code library is not re-entrant.
///
/// Failure to do so has been observed to lead to unexpected behavior including
/// process crashes.
pub struct OpenSslMutex<'a> {
// The dead code bypass is intentional. We don't need to read the () contents of this guard. We
// only need to ensure that the guard is dropped when this struct is dropped.
#[allow(dead_code)]
guard: MutexGuard<'a, ()>,
}

impl OpenSslMutex<'_> {
/// Acquire a mutex on OpenSSL FFI code.
///
/// WARNING: Calling code MUST NOT PANIC inside this function or
/// anything called by it, even in test code. This will poison the FFI mutex
/// and leave OpenSSL unusable for the remainder of the process lifetime.
pub fn acquire() -> Result<Self, OpenSslMutexUnavailable> {
// Useful for debugging.
// eprintln!(
// "ACQUIRING FFI MUTEX at\n{}",
// std::backtrace::Backtrace::force_capture()
// );

match FFI_MUTEX.lock() {
Ok(guard) => Ok(Self { guard }),
Err(_) => Err(OpenSslMutexUnavailable {}),
}
}
}

// Useful for debugging.
// impl<'a> Drop for OpenSslMutex<'a> {
// fn drop(&mut self) {
// eprintln!("Releasing FFI mutex\n\n\n");
// }
// }

/// Error returned when unable to acquire the OpenSSL native code mutex.
///
/// If this occurs, it's likely that a prior invocation of OpenSSL code panicked
/// while holding the mutex. When this happens, the OpenSSL native code mutex is
/// considered poisoned for the remainder of the process lifetime.
///
/// See [Rustnomicon: Poisoning] for more information.
///
/// [Rustnomicon: Poisoning]: https://doc.rust-lang.org/nomicon/poisoning.html
#[derive(Debug, Eq, PartialEq)]
pub struct OpenSslMutexUnavailable;

impl fmt::Display for OpenSslMutexUnavailable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unable to acquire OpenSSL native code mutex")
}
}

impl Error for OpenSslMutexUnavailable {}
22 changes: 22 additions & 0 deletions internal/crypto/src/openssl/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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.

//! This module provides functions for working with the [`openssl` native code
//! library].
//!
//! It is only available if the `openssl` feature is enabled.
//!
//! [`openssl` native code library]: https://crates.io/crates/openssl

mod ffi_mutex;
pub use ffi_mutex::{OpenSslMutex, OpenSslMutexUnavailable};
4 changes: 4 additions & 0 deletions internal/crypto/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ mod base64;
mod hash;
mod internal;
mod ocsp;

#[cfg(all(feature = "openssl", not(target_arch = "wasm32")))]
mod openssl;

mod signing_alg;
29 changes: 29 additions & 0 deletions internal/crypto/src/tests/openssl/ffi_mutex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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::openssl::OpenSslMutexUnavailable;

#[test]
fn impl_display() {
let err = OpenSslMutexUnavailable {};
assert_eq!(
err.to_string(),
"Unable to acquire OpenSSL native code mutex"
);
}

#[test]
fn impl_debug() {
let err = OpenSslMutexUnavailable {};
assert_eq!(format!("{err:?}"), "OpenSslMutexUnavailable");
}
14 changes: 14 additions & 0 deletions internal/crypto/src/tests/openssl/mod.rs
Original file line number Diff line number Diff line change
@@ -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 ffi_mutex;
6 changes: 2 additions & 4 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ file_io = ["openssl_sign"]
serialize_thumbnails = []
no_interleaved_io = ["file_io"]
fetch_remote_manifests = []
openssl_sign = ["openssl"]
openssl = ["dep:openssl", "c2pa-crypto/openssl"]
openssl_sign = ["openssl", "c2pa-crypto/openssl"]
json_schema = ["dep:schemars"]
pdf = ["dep:lopdf"]
v1_api = []
unstable_api = []
openssl_ffi_mutex = []

# The diagnostics feature is unsupported and might be removed.
# It enables some low-overhead timing features used in our development cycle.
Expand All @@ -60,7 +60,6 @@ required-features = ["unstable_api"]
name = "v2api"
required-features = ["unstable_api"]


[lib]
crate-type = ["lib"]

Expand Down Expand Up @@ -171,7 +170,6 @@ c2pa = { path = ".", features = [
glob = "0.3.1"
jumbf = "0.4.0"


[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.31"

Expand Down
10 changes: 9 additions & 1 deletion sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,12 @@ pub enum Error {
#[error(transparent)]
CborError(#[from] serde_cbor::Error),

#[cfg(feature = "openssl")]
#[error("could not acquire OpenSSL FFI mutex")]
OpenSslMutexError,

#[error(transparent)]
#[cfg(feature = "openssl")]
#[error(transparent)]
OpenSslError(#[from] openssl::error::ErrorStack),

#[error(transparent)]
Expand All @@ -303,3 +304,10 @@ pub enum Error {

/// A specialized `Result` type for C2PA toolkit operations.
pub type Result<T> = std::result::Result<T, Error>;

#[cfg(feature = "openssl")]
impl From<c2pa_crypto::openssl::OpenSslMutexUnavailable> for Error {
fn from(_err: c2pa_crypto::openssl::OpenSslMutexUnavailable) -> Self {
Self::OpenSslMutexError
}
}
8 changes: 4 additions & 4 deletions sdk/src/openssl/ec_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// specific language governing permissions and limitations under
// each license.

use c2pa_crypto::SigningAlg;
use c2pa_crypto::{openssl::OpenSslMutex, SigningAlg};
use openssl::{
ec::EcKey,
hash::MessageDigest,
Expand Down Expand Up @@ -47,7 +47,7 @@ impl ConfigurableSigner for EcSigner {
alg: SigningAlg,
tsa_url: Option<String>,
) -> Result<Self> {
let _openssl = super::OpenSslMutex::acquire()?;
let _openssl = OpenSslMutex::acquire()?;

let certs_size = signcert.len();
let pkey = EcKey::private_key_from_pem(pkey).map_err(Error::OpenSslError)?;
Expand All @@ -73,7 +73,7 @@ impl ConfigurableSigner for EcSigner {

impl Signer for EcSigner {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
let _openssl = super::OpenSslMutex::acquire()?;
let _openssl = OpenSslMutex::acquire()?;

let key = PKey::from_ec_key(self.pkey.clone()).map_err(Error::OpenSslError)?;

Expand All @@ -95,7 +95,7 @@ impl Signer for EcSigner {
}

fn certs(&self) -> Result<Vec<Vec<u8>>> {
let _openssl = super::OpenSslMutex::acquire()?;
let _openssl = OpenSslMutex::acquire()?;

let mut certs: Vec<Vec<u8>> = Vec::new();

Expand Down
4 changes: 2 additions & 2 deletions sdk/src/openssl/ec_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// specific language governing permissions and limitations under
// each license.

use c2pa_crypto::SigningAlg;
use c2pa_crypto::{openssl::OpenSslMutex, SigningAlg};
use openssl::{ec::EcKey, hash::MessageDigest, pkey::PKey};

use crate::{validator::CoseValidator, Error, Result};
Expand All @@ -28,7 +28,7 @@ impl EcValidator {

impl CoseValidator for EcValidator {
fn validate(&self, sig: &[u8], data: &[u8], pkey: &[u8]) -> Result<bool> {
let _openssl = super::OpenSslMutex::acquire()?;
let _openssl = OpenSslMutex::acquire()?;

let public_key = EcKey::public_key_from_der(pkey).map_err(|_err| Error::CoseSignature)?;
let key = PKey::from_ec_key(public_key).map_err(wrap_openssl_err)?;
Expand Down
8 changes: 4 additions & 4 deletions sdk/src/openssl/ed_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// specific language governing permissions and limitations under
// each license.

use c2pa_crypto::SigningAlg;
use c2pa_crypto::{openssl::OpenSslMutex, SigningAlg};
use openssl::{
pkey::{PKey, Private},
x509::X509,
Expand Down Expand Up @@ -40,7 +40,7 @@ impl ConfigurableSigner for EdSigner {
alg: SigningAlg,
tsa_url: Option<String>,
) -> Result<Self> {
let _openssl = super::OpenSslMutex::acquire()?;
let _openssl = OpenSslMutex::acquire()?;

let certs_size = signcert.len();
let signcerts = X509::stack_from_pem(signcert).map_err(Error::OpenSslError)?;
Expand Down Expand Up @@ -70,7 +70,7 @@ impl ConfigurableSigner for EdSigner {

impl Signer for EdSigner {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
let _openssl = super::OpenSslMutex::acquire()?;
let _openssl = OpenSslMutex::acquire()?;

let mut signer =
openssl::sign::Signer::new_without_digest(&self.pkey).map_err(Error::OpenSslError)?;
Expand All @@ -85,7 +85,7 @@ impl Signer for EdSigner {
}

fn certs(&self) -> Result<Vec<Vec<u8>>> {
let _openssl = super::OpenSslMutex::acquire()?;
let _openssl = OpenSslMutex::acquire()?;

let mut certs: Vec<Vec<u8>> = Vec::new();

Expand Down
4 changes: 2 additions & 2 deletions sdk/src/openssl/ed_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// specific language governing permissions and limitations under
// each license.

use c2pa_crypto::SigningAlg;
use c2pa_crypto::{openssl::OpenSslMutex, SigningAlg};
use openssl::pkey::PKey;

use crate::{validator::CoseValidator, Error, Result};
Expand All @@ -28,7 +28,7 @@ impl EdValidator {

impl CoseValidator for EdValidator {
fn validate(&self, sig: &[u8], data: &[u8], pkey: &[u8]) -> Result<bool> {
let _openssl = super::OpenSslMutex::acquire()?;
let _openssl = OpenSslMutex::acquire()?;

let public_key = PKey::public_key_from_der(pkey).map_err(|_err| Error::CoseSignature)?;

Expand Down
Loading

0 comments on commit d2e56f2

Please sign in to comment.