Skip to content

Commit

Permalink
feat: Add RawSigner trait to c2pa-crypto (derived from `c2pa::Sig…
Browse files Browse the repository at this point in the history
…ner`) (#716)
  • Loading branch information
scouten-adobe authored Dec 11, 2024
1 parent 2a65756 commit 52124e5
Show file tree
Hide file tree
Showing 53 changed files with 2,117 additions and 1,344 deletions.
1 change: 0 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion internal/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"] }
Expand All @@ -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"
48 changes: 48 additions & 0 deletions internal/crypto/src/openssl/cert_chain.rs
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 3 additions & 0 deletions internal/crypto/src/openssl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
140 changes: 140 additions & 0 deletions internal/crypto/src/openssl/signers/ecdsa_signer.rs
Original file line number Diff line number Diff line change
@@ -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<X509>,
cert_chain_len: usize,

private_key: EcKey<Private>,

time_stamp_service_url: Option<String>,
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<String>,
) -> Result<Self, RawSignerError> {
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<Vec<u8>, 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<Vec<Vec<u8>>, 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<String> {
self.time_stamp_service_url.clone()
}
}
105 changes: 105 additions & 0 deletions internal/crypto/src/openssl/signers/ed25519_signer.rs
Original file line number Diff line number Diff line change
@@ -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<X509>,
cert_chain_len: usize,

private_key: PKey<Private>,

time_stamp_service_url: Option<String>,
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<String>,
) -> Result<Self, RawSignerError> {
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<Vec<u8>, 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<Vec<Vec<u8>>, 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<String> {
self.time_stamp_service_url.clone()
}
}
Loading

0 comments on commit 52124e5

Please sign in to comment.