Skip to content
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
b11b39a
chore: Create new `RawSignatureValidator` trait in c2pa_crypto and mo…
scouten-adobe Nov 17, 2024
9c2fdd1
Add WASM (not WASI) implementation of ES256 and ES384 signatures from…
scouten-adobe Nov 17, 2024
3640cce
Clippy fix
scouten-adobe Nov 17, 2024
095c75c
Fix a few WASM build issues
scouten-adobe Nov 17, 2024
0e29d38
Hook in webcrypto impl when available
scouten-adobe Nov 17, 2024
e32612c
cargo fmt
scouten-adobe Nov 17, 2024
53f0a1f
Add coverage for unified RawSignatureValidator interface
scouten-adobe Nov 18, 2024
dce71ee
Bring over Ed25519 validator
scouten-adobe Nov 18, 2024
e7a0f24
Rename EcValidator to EcdsaValidator
scouten-adobe Nov 18, 2024
e8f51a4
WASM implementation of Ed25519
scouten-adobe Nov 18, 2024
ed095cc
Mark `EcdsaValidator` as non-exhaustive
scouten-adobe Nov 18, 2024
cb2af66
Bring over RSA-PSS validators
scouten-adobe Nov 18, 2024
e121f4b
Clean up `EcdsaValidator` code a bit
scouten-adobe Nov 18, 2024
3410c19
Add tests for impl-generic API of RsaValidator
scouten-adobe Nov 18, 2024
2688f3d
Bring over RSA-PSS validators for WASM from #653
scouten-adobe Nov 18, 2024
fbed3bd
Simplify
scouten-adobe Nov 18, 2024
3cb30d0
Enable OpenSSL version of RsaLegacyValidator
scouten-adobe Nov 18, 2024
825dab1
Add support for legacy raw-sig validator (used in time stamp validation)
scouten-adobe Nov 18, 2024
608227e
Clippy
scouten-adobe Nov 18, 2024
ec7b255
Fix WASM build issues
scouten-adobe Nov 18, 2024
949fdc2
Remove c2pa's `CoseValidator` trait and implementations
scouten-adobe Nov 18, 2024
450b8d3
Fix WASM build issues
scouten-adobe Nov 18, 2024
478fdbd
Enable sync code path for verify_cose on WASM
scouten-adobe Nov 18, 2024
6852392
Add WASM impl of RSALegacyValidator
scouten-adobe Nov 18, 2024
a078d0f
Test coverage for `RsaLegacyValidator::Rsa256`
scouten-adobe Nov 18, 2024
30bb623
Test coverage for `RsaLegacyValidator::Rs384`
scouten-adobe Nov 18, 2024
5cae35d
Add test coverage for `RsaLegacyValidator::Rs512`
scouten-adobe Nov 18, 2024
df5f04c
Add test coverage for `RsaLegacyValidator::Sha1`
scouten-adobe Nov 19, 2024
a267e5c
Add coverage for `RsaLegacyValixdator` edge cases
scouten-adobe Nov 19, 2024
c50e7b1
Test coverage for WASM impl of `RsaLegacyValidator`
scouten-adobe Nov 19, 2024
341a2d5
Remove redundant comparisons
scouten-adobe Nov 19, 2024
f213ab9
Add test coverage for validator_for_sig_and_hash_algs
scouten-adobe Nov 19, 2024
9ed67b7
Add README for test signature material
scouten-adobe Nov 19, 2024
7adf8c2
Merge branch 'main' into move-cose-validator
scouten-adobe Nov 19, 2024
df219df
Clippy
scouten-adobe Nov 19, 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
19 changes: 18 additions & 1 deletion internal/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ serde = { version = "1.0.197", features = ["derive"] }
sha1 = "0.10.6"
thiserror = "1.0.61"
x509-certificate = "0.21.0"
x509-parser = "0.16.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.
Expand All @@ -63,9 +63,26 @@ default-features = false
features = ["now", "wasmbind"]

[target.'cfg(target_arch = "wasm32")'.dependencies]
ecdsa = "0.16.9"
ed25519-dalek = "2.1.1"
p256 = "0.13.2"
p384 = "0.13.0"
rsa = { version = "0.9.6", features = ["sha2"] }
spki = "0.7.3"
wasm-bindgen = "0.2.83"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wasm-bindgen, web-sys, web-time can not be included in the wasi target. I know this PR isn't really about adding WASI support, but there are other wasi targets in the Cargo.toml

wasm-bindgen-futures = "0.4.31"
web-sys = { version = "0.3.58", features = [
"console",
"Crypto",
"SubtleCrypto",
"CryptoKey",
"Window",
"WorkerGlobalScope",
] }
web-time = "1.1"

[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies]
getrandom = { version = "0.2.7", features = ["js"] }

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.31"
7 changes: 6 additions & 1 deletion internal/crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,15 @@ pub mod openssl;
#[cfg(all(feature = "openssl", target_arch = "wasm32"))]
compile_error!("OpenSSL feature is not compatible with WASM platform");

pub mod validation_codes;
pub mod raw_signature;

mod signing_alg;
pub use signing_alg::{SigningAlg, UnknownAlgorithmError};

pub mod validation_codes;

#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
pub mod webcrypto;

#[cfg(test)]
pub(crate) mod tests;
2 changes: 2 additions & 0 deletions internal/crypto/src/openssl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@

mod ffi_mutex;
pub use ffi_mutex::{OpenSslMutex, OpenSslMutexUnavailable};

pub mod validators;
82 changes: 82 additions & 0 deletions internal/crypto/src/openssl/validators/ecdsa_validator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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::{
bn::BigNum, ec::EcKey, ecdsa::EcdsaSig, hash::MessageDigest, pkey::PKey, sign::Verifier,
};

use crate::{
openssl::OpenSslMutex,
raw_signature::{RawSignatureValidationError, RawSignatureValidator},
};

/// An `EcdsaValidator` can validate raw signatures with one of the ECDSA
/// signature algorithms.
#[non_exhaustive]
pub enum EcdsaValidator {
/// ECDSA with SHA-256
Es256,

/// ECDSA with SHA-384
Es384,

/// ECDSA with SHA-512
Es512,
}

impl RawSignatureValidator for EcdsaValidator {
fn validate(
&self,
sig: &[u8],
data: &[u8],
public_key: &[u8],
) -> Result<(), RawSignatureValidationError> {
let _openssl = OpenSslMutex::acquire()?;

let public_key = EcKey::public_key_from_der(public_key)?;
let key = PKey::from_ec_key(public_key)?;

let mut verifier = match self {
Self::Es256 => Verifier::new(MessageDigest::sha256(), &key)?,
Self::Es384 => Verifier::new(MessageDigest::sha384(), &key)?,
Self::Es512 => Verifier::new(MessageDigest::sha512(), &key)?,
};

// We may need to convert a P1363 signature to a DER signature if the signature
// matches one of the expected P1363 signature sizes.
let is_p1363 = match self {
Self::Es256 => sig.len() == 64,
Self::Es384 => sig.len() == 96,
Self::Es512 => sig.len() == 132,
};

let sig_der = if is_p1363 {
// Convert P1363 signature to DER signature.
let sig_len = sig.len() / 2;

let r = BigNum::from_slice(&sig[0..sig_len])?;
let s = BigNum::from_slice(&sig[sig_len..])?;
EcdsaSig::from_private_components(r, s)?.to_der()?
} else {
sig.to_vec()

Check warning on line 71 in internal/crypto/src/openssl/validators/ecdsa_validator.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/openssl/validators/ecdsa_validator.rs#L71

Added line #L71 was not covered by tests
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mauricefisher64 do you know of a case where the signature is not encoded as P1363?

Copy link
Collaborator

@mauricefisher64 mauricefisher64 Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenSSL/AWS KMS/X509 certs use DER. Web standards and Cose/C2PA use P1363

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So when we sign with OpenSSL or we are checking certificates the values are DER.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mauricefisher64 reason I'm asking is that the unit test framework isn't generating coverage for line 70 (the DER case) so I'm looking for sample data that would exercise that case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is you sign something with straight OpenSSL it will be a DER signature. You can't use a signature that came from Cose because it will be in the other format.

};

verifier.update(data)?;

if verifier.verify(&sig_der)? {
Ok(())
} else {
Err(RawSignatureValidationError::SignatureMismatch)
}
}
}
42 changes: 42 additions & 0 deletions internal/crypto/src/openssl/validators/ed25519_validator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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, sign::Verifier};

use crate::{
openssl::OpenSslMutex,
raw_signature::{RawSignatureValidationError, RawSignatureValidator},
};

/// An `Ed25519Validator` can validate raw signatures with the Ed25519 signature
/// algorithm.
pub struct Ed25519Validator {}

impl RawSignatureValidator for Ed25519Validator {
fn validate(
&self,
sig: &[u8],
data: &[u8],
public_key: &[u8],
) -> Result<(), RawSignatureValidationError> {
let _openssl = OpenSslMutex::acquire()?;

let public_key = PKey::public_key_from_der(public_key)?;

if Verifier::new_without_digest(&public_key)?.verify_oneshot(sig, data)? {
Ok(())
} else {
Err(RawSignatureValidationError::SignatureMismatch)
}
}
}
70 changes: 70 additions & 0 deletions internal/crypto/src/openssl/validators/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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 validating raw signatures to this
//! crate's [`RawSignatureValidator`] trait.

use bcder::Oid;

use crate::{
raw_signature::{oids::*, RawSignatureValidator},
SigningAlg,
};

mod ecdsa_validator;
pub use ecdsa_validator::EcdsaValidator;

mod ed25519_validator;
pub use ed25519_validator::Ed25519Validator;

mod rsa_legacy_validator;
pub(crate) use rsa_legacy_validator::RsaLegacyValidator;

mod rsa_validator;
pub use rsa_validator::RsaValidator;

/// Return a validator for the given signing algorithm.
pub fn validator_for_signing_alg(alg: SigningAlg) -> Option<Box<dyn RawSignatureValidator>> {
match alg {
SigningAlg::Es256 => Some(Box::new(EcdsaValidator::Es256)),
SigningAlg::Es384 => Some(Box::new(EcdsaValidator::Es384)),
SigningAlg::Es512 => Some(Box::new(EcdsaValidator::Es512)),
SigningAlg::Ed25519 => Some(Box::new(Ed25519Validator {})),
SigningAlg::Ps256 => Some(Box::new(RsaValidator::Ps256)),
SigningAlg::Ps384 => Some(Box::new(RsaValidator::Ps384)),
SigningAlg::Ps512 => Some(Box::new(RsaValidator::Ps512)),
}
}

pub(crate) fn validator_for_sig_and_hash_algs(
sig_alg: &Oid,
hash_alg: &Oid,
) -> Option<Box<dyn RawSignatureValidator>> {
if sig_alg.as_ref() == RSA_OID.as_bytes()
|| sig_alg.as_ref() == SHA256_WITH_RSAENCRYPTION_OID.as_bytes()
|| sig_alg.as_ref() == SHA384_WITH_RSAENCRYPTION_OID.as_bytes()
|| sig_alg.as_ref() == SHA512_WITH_RSAENCRYPTION_OID.as_bytes()

Check warning on line 56 in internal/crypto/src/openssl/validators/mod.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/openssl/validators/mod.rs#L54-L56

Added lines #L54 - L56 were not covered by tests
{
if hash_alg.as_ref() == SHA1_OID.as_bytes() {
return Some(Box::new(RsaLegacyValidator::Sha1));
} else if hash_alg.as_ref() == SHA256_OID.as_bytes() {
return Some(Box::new(RsaLegacyValidator::Rsa256));
} else if hash_alg.as_ref() == SHA384_OID.as_bytes() {
return Some(Box::new(RsaLegacyValidator::Rsa384));
} else if hash_alg.as_ref() == SHA512_OID.as_bytes() {
return Some(Box::new(RsaLegacyValidator::Rsa512));
}
}

Check warning on line 67 in internal/crypto/src/openssl/validators/mod.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/openssl/validators/mod.rs#L66-L67

Added lines #L66 - L67 were not covered by tests

None

Check warning on line 69 in internal/crypto/src/openssl/validators/mod.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/openssl/validators/mod.rs#L69

Added line #L69 was not covered by tests
}
66 changes: 66 additions & 0 deletions internal/crypto/src/openssl/validators/rsa_legacy_validator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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(missing_docs)] // REMOVE once this becomes `pub(crate)`

use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa, sign::Verifier};

use crate::{
openssl::OpenSslMutex,
raw_signature::{RawSignatureValidationError, RawSignatureValidator},
};

/// An `RsaLegacyValidator` can validate raw signatures with an RSA signature
/// algorithm that is not supported directly by C2PA. (Some RFC 3161 time stamp
/// providers issue these signatures, which is why it's supported here.)
///
/// TEMPORARILY public; will move to `pub(crate)` visibility later in the
/// refactoring.
pub enum RsaLegacyValidator {
Sha1,
Rsa256,
Rsa384,
Rsa512,
}

impl RawSignatureValidator for RsaLegacyValidator {
fn validate(
&self,
sig: &[u8],
data: &[u8],
pkey: &[u8],
) -> Result<(), RawSignatureValidationError> {
let _openssl = OpenSslMutex::acquire()?;
let rsa = Rsa::public_key_from_der(pkey)?;

// Rebuild RSA keys to eliminate incompatible values.
let n = rsa.n().to_owned()?;
let e = rsa.e().to_owned()?;

let new_rsa = Rsa::from_public_components(n, e)?;
let public_key = PKey::from_rsa(new_rsa)?;

let mut verifier = match self {
Self::Sha1 => Verifier::new(MessageDigest::sha1(), &public_key)?,
Self::Rsa256 => Verifier::new(MessageDigest::sha256(), &public_key)?,
Self::Rsa384 => Verifier::new(MessageDigest::sha384(), &public_key)?,
Self::Rsa512 => Verifier::new(MessageDigest::sha512(), &public_key)?,
};

if verifier.verify_oneshot(sig, data)? {
Ok(())
} else {
Err(RawSignatureValidationError::SignatureMismatch)
}
}
}
Loading
Loading