Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add RawSigner trait to c2pa-crypto (derived from c2pa::Signer) #716

Merged
merged 35 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4a6e94d
feat: Add `RawSigner` trait to `c2pa-crypto` (derived from `c2pa::Sig…
scouten-adobe Dec 6, 2024
e2a8cd6
Move `ConfigurableSigner` to end of file
scouten-adobe Dec 6, 2024
9dd1741
Adjust function names a bit
scouten-adobe Dec 6, 2024
1497f9d
Update `CallbackSigner` for new trait interface
scouten-adobe Dec 6, 2024
795516d
Redefine `Signer` and `AsyncSigner` in terms of the new raw signer tr…
scouten-adobe Dec 6, 2024
34ff5f4
Start to build in the create_signer interface
scouten-adobe Dec 6, 2024
8cdb210
Update all the call sites for Signer -> RawSigner transition
scouten-adobe Dec 6, 2024
4540f56
Clippy hack
scouten-adobe Dec 6, 2024
1c03e97
Move check_chain_order into c2pa_crypto
scouten-adobe Dec 6, 2024
53788bb
Move RsaSigner into c2pa_crypto
scouten-adobe Dec 6, 2024
5b2605a
Remove RsaSigner from c2pa-rs
scouten-adobe Dec 6, 2024
427db9e
Merge branch 'main' into move-raw-signer
scouten-adobe Dec 6, 2024
36a5bbd
Clippy
scouten-adobe Dec 6, 2024
c72ce8e
Sort a few more build issues
scouten-adobe Dec 6, 2024
27dee33
Revert error type changes
scouten-adobe Dec 6, 2024
00ad08e
Remove ConfigurableSigner from c2pa_crypto
scouten-adobe Dec 7, 2024
f07ff4d
Move EcDSA signer into c2pa_crypto
scouten-adobe Dec 7, 2024
8031df0
Fix WASM build issues
scouten-adobe Dec 7, 2024
4da05ed
Move Ed25519 signer into c2pa_crypto
scouten-adobe Dec 7, 2024
5da65eb
Remove one use of temp_signer
scouten-adobe Dec 7, 2024
2032e43
Simplify temp_signer API now that we don't need path any more
scouten-adobe Dec 7, 2024
1cd4d5d
Remove psxxx_ocsp_stapling_experimental; conflicts with upcoming Asyn…
scouten-adobe Dec 7, 2024
0210d17
Add test coverage for ed25519 signer
scouten-adobe Dec 7, 2024
e9b8ecd
Add ability to request async signers
scouten-adobe Dec 7, 2024
86b5901
Start replacing temp_signer with test_signer
scouten-adobe Dec 7, 2024
5fe275a
Remove remaining instances of temp_signer
scouten-adobe Dec 8, 2024
87c42d9
Fix WASM build issues
scouten-adobe Dec 8, 2024
278839a
Clippy
scouten-adobe Dec 8, 2024
43d3140
mod openssl::signers doesn't need to be fully public
scouten-adobe Dec 9, 2024
0950c8a
Merge branch 'main' into move-raw-signer
scouten-adobe Dec 9, 2024
1ca94c6
Add WASM implementation for signing via Ed25519
scouten-adobe Dec 10, 2024
6cc1c0f
Use Ed25519 for test signatures since it's available on WASM
scouten-adobe Dec 10, 2024
577f405
Remove TO REVIEW comment that's no longer relevant
scouten-adobe Dec 10, 2024
6241275
Use `crate::utils::test_signer::async_test_signer`
scouten-adobe Dec 10, 2024
1d9e7a8
Merge branch 'main' into move-raw-signer
scouten-adobe Dec 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

Check warning on line 35 in internal/crypto/src/openssl/cert_chain.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/openssl/cert_chain.rs#L35

Added line #L35 was not covered by tests
};

let Ok(verified) = cert.verify(&pkey) else {
return false;

Check warning on line 39 in internal/crypto/src/openssl/cert_chain.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/openssl/cert_chain.rs#L39

Added line #L39 was not covered by tests
};

if !verified {
return false;

Check warning on line 43 in internal/crypto/src/openssl/cert_chain.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/openssl/cert_chain.rs#L43

Added line #L43 was not covered by tests
}
}

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(),
));

Check warning on line 64 in internal/crypto/src/openssl/signers/ecdsa_signer.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/openssl/signers/ecdsa_signer.rs#L62-L64

Added lines #L62 - L64 were not covered by tests
}
};

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(),
));

Check warning on line 76 in internal/crypto/src/openssl/signers/ecdsa_signer.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/openssl/signers/ecdsa_signer.rs#L74-L76

Added lines #L74 - L76 were not covered by tests
}

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(),
));

Check warning on line 53 in internal/crypto/src/openssl/signers/ed25519_signer.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/openssl/signers/ed25519_signer.rs#L51-L53

Added lines #L51 - L53 were not covered by tests
}

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
Loading