Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 9 additions & 9 deletions sdk/src/crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

## Signing

| C2PA `SigningAlg` | Default (*) | `feature = "rust_native_crypto"` (*) | WASM |
| --- | --- | --- | --- |
| `es256` | OpenSSL | `p256` | `p256` |
| `es384` | OpenSSL | `p384` | `p384` |
| `es512` | OpenSSL | OpenSSL | ❌ |
| `ed25519` | OpenSSL | `ed25519-dalek` | `ed25519-dalek` |
| `ps256` | OpenSSL | `rsa` | `rsa` |
| `ps384` | OpenSSL | `rsa` | `rsa` |
| `ps512` | OpenSSL | `rsa` | `rsa` |
| C2PA `SigningAlg` | Default (*) | `feature = "rust_native_crypto"` (*) or WASM |
| --- | --- | --- |
| `es256` | OpenSSL | `p256` |
| `es384` | OpenSSL | `p384` |
| `es512` | OpenSSL | `p521` |
| `ed25519` | OpenSSL | `ed25519-dalek` |
| `ps256` | OpenSSL | `rsa` |
| `ps384` | OpenSSL | `rsa` |
| `ps512` | OpenSSL | `rsa` |

(*) Applies to all supported platforms except WASM <br />
❌ = not supported
Expand Down
10 changes: 0 additions & 10 deletions sdk/src/crypto/cose/certificate_trust_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,6 @@ zGxQnM2hCA==
let ps512 = test_signer(SigningAlg::Ps512);
let es256 = test_signer(SigningAlg::Es256);
let es384 = test_signer(SigningAlg::Es384);
#[cfg(feature = "openssl")]
let es512 = test_signer(SigningAlg::Es512);
let ed25519 = test_signer(SigningAlg::Ed25519);

Expand All @@ -658,7 +657,6 @@ zGxQnM2hCA==
let ps512_certs = ps512.cert_chain().unwrap();
let es256_certs = es256.cert_chain().unwrap();
let es384_certs = es384.cert_chain().unwrap();
#[cfg(feature = "openssl")]
let es512_certs = es512.cert_chain().unwrap();
let ed25519_certs = ed25519.cert_chain().unwrap();

Expand All @@ -672,7 +670,6 @@ zGxQnM2hCA==
.unwrap();
ctp.check_certificate_trust(&es384_certs[1..], &es384_certs[0], None)
.unwrap();
#[cfg(feature = "openssl")]
ctp.check_certificate_trust(&es512_certs[1..], &es512_certs[0], None)
.unwrap();
ctp.check_certificate_trust(&ed25519_certs[1..], &ed25519_certs[0], None)
Expand Down Expand Up @@ -742,7 +739,6 @@ zGxQnM2hCA==
let ps512 = test_signer(SigningAlg::Ps512);
let es256 = test_signer(SigningAlg::Es256);
let es384 = test_signer(SigningAlg::Es384);
#[cfg(feature = "openssl")]
let es512 = test_signer(SigningAlg::Es512);
let ed25519 = test_signer(SigningAlg::Ed25519);

Expand All @@ -751,7 +747,6 @@ zGxQnM2hCA==
let ps512_certs = ps512.cert_chain().unwrap();
let es256_certs = es256.cert_chain().unwrap();
let es384_certs = es384.cert_chain().unwrap();
#[cfg(feature = "openssl")]
let es512_certs = es512.cert_chain().unwrap();
let ed25519_certs = ed25519.cert_chain().unwrap();

Expand Down Expand Up @@ -792,7 +787,6 @@ zGxQnM2hCA==
CertificateTrustError::CertificateNotTrusted
);

#[cfg(feature = "openssl")]
assert_eq!(
ctp.check_certificate_trust(&es512_certs[2..], &es512_certs[0], None)
.unwrap_err(),
Expand Down Expand Up @@ -933,7 +927,6 @@ zGxQnM2hCA==
let ps512 = test_signer(SigningAlg::Ps512);
let es256 = test_signer(SigningAlg::Es256);
let es384 = test_signer(SigningAlg::Es384);
#[cfg(feature = "openssl")]
let es512 = test_signer(SigningAlg::Es512);
let ed25519 = test_signer(SigningAlg::Ed25519);

Expand All @@ -942,7 +935,6 @@ zGxQnM2hCA==
assert_eq!(ps512.alg(), SigningAlg::Ps512);
assert_eq!(es256.alg(), SigningAlg::Es256);
assert_eq!(es384.alg(), SigningAlg::Es384);
#[cfg(feature = "openssl")]
assert_eq!(es512.alg(), SigningAlg::Es512);
assert_eq!(ed25519.alg(), SigningAlg::Ed25519);

Expand All @@ -951,7 +943,6 @@ zGxQnM2hCA==
let ps512_certs = ps512.cert_chain().unwrap();
let es256_certs = es256.cert_chain().unwrap();
let es384_certs = es384.cert_chain().unwrap();
#[cfg(feature = "openssl")]
let es512_certs = es512.cert_chain().unwrap();
let ed25519_certs = ed25519.cert_chain().unwrap();

Expand All @@ -965,7 +956,6 @@ zGxQnM2hCA==
.unwrap();
ctp.check_certificate_trust(&es384_certs[1..], &es384_certs[0], None)
.unwrap();
#[cfg(feature = "openssl")]
ctp.check_certificate_trust(&es512_certs[1..], &es512_certs[0], None)
.unwrap();
ctp.check_certificate_trust(&ed25519_certs[1..], &ed25519_certs[0], None)
Expand Down
97 changes: 86 additions & 11 deletions sdk/src/crypto/raw_signature/rust_native/signers/ecdsa_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,36 @@
// specific language governing permissions and limitations under
// each license.

use std::str::FromStr;

use asn1_rs::{Any, BitString, DerSequence, FromDer, Sequence};
use der::{Decode, Encode};
use ecdsa::signature::Signer;
use p256::ecdsa::{Signature as P256Signature, SigningKey as P256SigningKey};
use p384::ecdsa::{Signature as P384Signature, SigningKey as P384SigningKey};
use pkcs8::DecodePrivateKey;
use p521::ecdsa::{Signature as P512Signature, SigningKey as P512SigningKey};
use pkcs8::{DecodePrivateKey, ObjectIdentifier, PrivateKeyInfo};
use x509_parser::{error::PEMError, pem::Pem};

use crate::crypto::{
raw_signature::{RawSigner, RawSignerError, SigningAlg},
raw_signature::{
oids::{EC_PUBLICKEY_OID, SECP521R1_OID},
RawSigner, RawSignerError, SigningAlg,
},
time_stamp::TimeStampProvider,
};

// Rust native does not support Es512
enum EcdsaSigningAlg {
Es256,
Es384,
Es512,
}

// Signing keys for ES256 and ES384 are different types
// Signing keys for ES256, ES384, and ES512 are different types
pub enum EcdsaSigningKey {
Es256(P256SigningKey),
Es384(P384SigningKey),
Es512(P512SigningKey),
}

pub struct EcdsaSigner {
Expand Down Expand Up @@ -85,9 +94,8 @@ impl EcdsaSigner {
(EcdsaSigningKey::Es384(key), EcdsaSigningAlg::Es384)
}
SigningAlg::Es512 => {
return Err(RawSignerError::InvalidSigningCredentials(
"Rust Crypto does not support Es512, only OpenSSL".to_string(),
))
let key = es512_from_pkcs8_pem(private_key_pem)?;
(EcdsaSigningKey::Es512(key), EcdsaSigningAlg::Es512)
}
_ => {
return Err(RawSignerError::InvalidSigningCredentials(
Expand Down Expand Up @@ -118,13 +126,18 @@ impl RawSigner for EcdsaSigner {
let signature: P384Signature = key.sign(data);
Ok(signature.to_vec())
}
EcdsaSigningKey::Es512(ref key) => {
let signature: P512Signature = key.sign(data);
Ok(signature.to_vec())
}
}
}

fn alg(&self) -> SigningAlg {
match self.alg {
EcdsaSigningAlg::Es256 => SigningAlg::Es256,
EcdsaSigningAlg::Es384 => SigningAlg::Es384,
EcdsaSigningAlg::Es512 => SigningAlg::Es512,
}
}

Expand All @@ -143,6 +156,68 @@ impl TimeStampProvider for EcdsaSigner {
}
}

#[derive(DerSequence)]
struct ECPrivateKey<'a> {
version: u32,
private_key: &'a [u8], // OCTET STRING content
parameters: Option<Any<'a>>,
public_key: Option<BitString<'a>>,
}

fn es512_from_pkcs8_pem(private_key_pem: &str) -> Result<P512SigningKey, RawSignerError> {
let pem = pem::parse(private_key_pem).map_err(|e| {
RawSignerError::InvalidSigningCredentials(format!("invalid ES512 private key PEM: {e}"))
})?;
let pk_info = PrivateKeyInfo::try_from(pem.contents()).map_err(|e| {
RawSignerError::InvalidSigningCredentials(format!("invalid ES512 PKCS#8 structure: {e}"))
})?;

// Check OID is id-ecPublicKey (1.2.840.10045.2.1)
if pk_info.algorithm.oid.as_bytes() != EC_PUBLICKEY_OID.as_bytes() {
return Err(RawSignerError::InvalidSigningCredentials(format!(
"Unexpected OID: {}, expected id-ecPublicKey {}",
pk_info.algorithm.oid, EC_PUBLICKEY_OID
)));
}

let params = pk_info
.algorithm
.parameters
.as_ref()
.ok_or_else(|| {
RawSignerError::InvalidSigningCredentials("Missing algorithm parameters".to_string())
})?
.to_der()
.map_err(|_| {
RawSignerError::InvalidSigningCredentials(
"Algorithm parameters are not a valid OID".to_string(),
)
})?;
// Parse the parameters as an ASN.1 OID
let curve_oid = ObjectIdentifier::from_der(&params).map_err(|_| {
RawSignerError::InvalidSigningCredentials(
"Algorithm parameters are not a valid OID".to_string(),
)
})?;

// Parse ECPrivateKey ASN.1 structure using asn1-rs
let ec_private_key = ECPrivateKey::from_der(pk_info.private_key).map_err(|e| {
RawSignerError::InvalidSigningCredentials(format!("invalid ES512 ECPrivateKey ASN.1: {e}"))
})?;

// Check version is 1
if ec_private_key.1.version != 1 {
return Err(RawSignerError::InvalidSigningCredentials(format!(
"ECPrivateKey ASN.1 version is {}, expected 1",
ec_private_key.1.version
)));
}

P512SigningKey::from_slice(ec_private_key.1.private_key).map_err(|e| {
RawSignerError::InvalidSigningCredentials(format!("invalid ES512 private key: {e}"))
})
}

#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
Expand All @@ -153,7 +228,7 @@ mod tests {
use crate::crypto::raw_signature::SigningAlg;

#[test]
fn test_es512_not_supported() {
fn test_es512_supported() {
let cert_chain =
include_bytes!("../../../../../tests/fixtures/crypto/raw_signature/es512.pub");
let private_key =
Expand All @@ -168,9 +243,9 @@ mod tests {
time_stamp_service_url,
);

assert!(result.is_err());
if let Err(RawSignerError::InvalidSigningCredentials(err_msg)) = result {
assert_eq!(err_msg, "Rust Crypto does not support Es512, only OpenSSL");
assert!(result.is_ok());
if let Ok(ecdsa_signer) = result {
assert_eq!(ecdsa_signer.alg(), SigningAlg::Es512);
} else {
unreachable!("Expected InvalidSigningCredentials error");
}
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/crypto/raw_signature/rust_native/signers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub(crate) fn signer_from_cert_chain_and_private_key(
)?,
)),

SigningAlg::Es256 | SigningAlg::Es384 => Ok(Box::new(
SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => Ok(Box::new(
ecdsa_signer::EcdsaSigner::from_cert_chain_and_private_key(
cert_chain,
private_key,
Expand Down
7 changes: 6 additions & 1 deletion sdk/src/crypto/raw_signature/tests/async_signers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@ async fn es384() {
validator.validate(&signature, data, pub_key).unwrap();
}

#[cfg_attr(feature = "openssl", actix::test)]
#[cfg_attr(not(target_arch = "wasm32"), actix::test)]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "wasi")),
wasm_bindgen_test
)]
#[cfg_attr(target_os = "wasi", wstd::test)]
async fn es512() {
let cert_chain = include_bytes!("../../../../tests/fixtures/crypto/raw_signature/es512.pub");
let private_key = include_bytes!("../../../../tests/fixtures/crypto/raw_signature/es512.priv");
Expand Down