Skip to content

Commit

Permalink
feat(crypto): Add rsa crate support to rust_native_crypto feature (
Browse files Browse the repository at this point in the history
…#853)

Also makes RSA-PSS signatures available in WASM.
  • Loading branch information
scouten-adobe authored Jan 16, 2025
1 parent 630330f commit 2c534f9
Show file tree
Hide file tree
Showing 25 changed files with 381 additions and 1,015 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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

[features]
json_schema = ["dep:schemars"]
rust_native_crypto = ["dep:ed25519-dalek"]
rust_native_crypto = [
"dep:const-oid",
"dep:der",
"dep:ed25519-dalek",
"dep:num-bigint-dig",
"dep:pkcs1",
"dep:rsa",
"dep:spki",
]

[dependencies]
asn1-rs = "0.6.2"
Expand All @@ -43,20 +51,26 @@ bytes = "1.7.2"
c2pa-status-tracker = { path = "../status-tracker", version = "0.2.0" }
ciborium = "0.2.2"
const-hex = "1.14"
const-oid = { version = "0.9.6", optional = true }
coset = "0.3.1"
der = { version = "0.7.9", optional = true }
ed25519-dalek = { version = "2.1.1", features = ["alloc", "digest", "pem", "pkcs8"], optional = true }
getrandom = { version = "0.2.7", features = ["js"] }
hex = "0.4.3"
nom = "7.1.3"
num-bigint-dig = { version = "0.8.4", optional = true }
pkcs1 = { version = "0.7.5", optional = true }
rand = "0.8.5"
rasn = "0.22.0"
rasn-ocsp = "0.22.0"
rasn-pkix = "0.22.0"
rsa = { version = "0.9.7", features = ["pem", "sha2", "std"], optional = true }
schemars = { version = "0.8.21", optional = true }
serde = { version = "1.0.197", features = ["derive"] }
serde_bytes = "0.11.5"
sha1 = "0.10.6"
sha2 = "0.10.6"
spki = { version = "0.7.3", optional = true }
thiserror = "2.0.8"
web-time = "1.1"
x509-certificate = "0.21.0"
Expand All @@ -81,12 +95,16 @@ default-features = false
features = ["now", "wasmbind"]

[target.'cfg(target_arch = "wasm32")'.dependencies]
async-trait = { version = "0.1.77" }
async-trait = "0.1.77"
const-oid = "0.9.6"
der = "0.7.9"
ecdsa = "0.16.9"
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"] }
num-bigint-dig = "0.8.4"
pkcs1 = "0.7.5"
rsa = { version = "0.9.7", features = ["pem", "sha2"] }
spki = "0.7.3"
wasm-bindgen = "0.2.83"
wasm-bindgen-futures = "0.4.31"
Expand All @@ -104,11 +122,16 @@ getrandom = { version = "0.2.7", features = ["js"] }
js-sys = "0.3.58"

[dev-dependencies]
const-oid = "0.9.6"
der = "0.7.9"
ed25519-dalek = { version = "2.1.1", features = ["alloc", "digest", "pem", "pkcs8"] }
num-bigint-dig = "0.8.4"
pkcs1 = "0.7.5"
rsa = { version = "0.9.7", features = ["pem", "sha2"] }
spki = "0.7.3"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
actix = "0.13.1"
ed25519-dalek = { version = "2.1.1", features = ["alloc", "digest", "pem", "pkcs8"] }

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.31"
18 changes: 9 additions & 9 deletions internal/crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ This crate has two features, neither of which are enabled by default:
| `es384` | OpenSSL | OpenSSL ||
| `es512` | OpenSSL | OpenSSL ||
| `ed25519` | OpenSSL | `ed25519-dalek` | `ed25519-dalek` |
| `ps256` | OpenSSL | OpenSSL | |
| `ps384` | OpenSSL | OpenSSL | |
| `ps512` | OpenSSL | OpenSSL | |
| `ps256` | OpenSSL | `rsa` | `rsa` |
| `ps384` | OpenSSL | `rsa` | `rsa` |
| `ps512` | OpenSSL | `rsa` | `rsa` |

(*) Applies to all supported platforms except WASM <br />
❌ = not supported
Expand All @@ -38,9 +38,9 @@ This crate has two features, neither of which are enabled by default:
| `es384` | OpenSSL | OpenSSL | `p384` |
| `es512` | OpenSSL | OpenSSL ||
| `ed25519` | OpenSSL | `ed25519-dalek` | `ed25519-dalek` |
| `ps256` | OpenSSL | OpenSSL | `rsa` |
| `ps384` | OpenSSL | OpenSSL | `rsa` |
| `ps512` | OpenSSL | OpenSSL | `rsa` |
| `ps256` | OpenSSL | `rsa` | `rsa` |
| `ps384` | OpenSSL | `rsa` | `rsa` |
| `ps512` | OpenSSL | `rsa` | `rsa` |

(*) Applies to all supported platforms except WASM <br />
❌ = not supported
Expand All @@ -53,9 +53,9 @@ This crate has two features, neither of which are enabled by default:
| `es384` | OpenSSL | OpenSSL | WebCrypto |
| `es512` | OpenSSL | OpenSSL | WebCrypto |
| `ed25519` | OpenSSL | `ed25519-dalek` | `ed25519-dalek` |
| `ps256` | OpenSSL | OpenSSL | `rsa` |
| `ps384` | OpenSSL | OpenSSL | `rsa` |
| `ps512` | OpenSSL | OpenSSL | `rsa` |
| `ps256` | OpenSSL | `rsa` | `rsa` |
| `ps384` | OpenSSL | `rsa` | `rsa` |
| `ps512` | OpenSSL | `rsa` | `rsa` |

(*) Applies to all supported platforms except WASM

Expand Down
3 changes: 3 additions & 0 deletions internal/crypto/src/raw_signature/oids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
use x509_parser::{der_parser::oid, oid_registry::Oid};

pub(crate) const RSA_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .1);
pub(crate) const RSA_PSS_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .10);

pub(crate) const SHA256_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .11);
pub(crate) const SHA384_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .12);
pub(crate) const SHA512_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .13);

pub(crate) const SHA1_OID: Oid<'static> = oid!(1.3.14 .3 .2 .26);
pub(crate) const SHA256_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .1);
pub(crate) const SHA384_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .2);
Expand Down
10 changes: 10 additions & 0 deletions internal/crypto/src/raw_signature/rust_native/signers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use crate::raw_signature::{RawSigner, RawSignerError, SigningAlg};

mod ed25519_signer;
mod rsa_signer;

/// Return a built-in [`RawSigner`] instance using the provided signing
/// certificate and private key.
Expand All @@ -41,6 +42,15 @@ pub(crate) fn signer_from_cert_chain_and_private_key(
)?,
)),

SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => Ok(Box::new(
rsa_signer::RsaSigner::from_cert_chain_and_private_key(
cert_chain,
private_key,
alg,
time_stamp_service_url,
)?,
)),

_ => Err(RawSignerError::InternalError(format!(
"unsupported signing algorithm {alg}"
))),
Expand Down
181 changes: 181 additions & 0 deletions internal/crypto/src/raw_signature/rust_native/signers/rsa_signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// 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 const_oid::ObjectIdentifier;
use der::{pem::PemLabel, SecretDocument};
use num_bigint_dig::BigUint;
use rsa::{
pkcs8::PrivateKeyInfo,
pss::SigningKey,
sha2::{Sha256, Sha384, Sha512},
signature::{RandomizedSigner, SignatureEncoding},
RsaPrivateKey,
};
use x509_parser::{error::PEMError, pem::Pem};

use crate::{
raw_signature::{RawSigner, RawSignerError, SigningAlg},
time_stamp::TimeStampProvider,
};

enum RsaSigningAlg {
Ps256,
Ps384,
Ps512,
}

/// Implements [`RawSigner`] trait using `rsa` crate's implementation of SHA256
/// + RSA encryption.
pub(crate) struct RsaSigner {
alg: RsaSigningAlg,

cert_chain: Vec<Vec<u8>>,
cert_chain_len: usize,

private_key: RsaPrivateKey,

time_stamp_service_url: Option<String>,
time_stamp_size: usize,
}

// Can't use the OIDs defined at certificate_profile.rs because they're
// different underlying types. (Sigh.)
const RSA_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1");
const RSA_PSS_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10");

impl RsaSigner {
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 cert_chain = Pem::iter_from_buffer(cert_chain)
.map(|r| match r {
Ok(pem) => Ok(pem.contents),
Err(e) => Err(e),
})
.collect::<Result<Vec<Vec<u8>>, PEMError>>()
.map_err(|e| RawSignerError::InvalidSigningCredentials(e.to_string()))?;

// TO DO: check_chain_order(&cert_chain).await?;

let cert_chain_len = cert_chain.len();

let pem_str = std::str::from_utf8(private_key)
.map_err(|e| RawSignerError::InvalidSigningCredentials(e.to_string()))?;

let (label, private_key_der) = SecretDocument::from_pem(pem_str)
.map_err(|e| RawSignerError::InvalidSigningCredentials(e.to_string()))?;

PrivateKeyInfo::validate_pem_label(label)
.map_err(|e| RawSignerError::InvalidSigningCredentials(e.to_string()))?;

let pki = PrivateKeyInfo::try_from(private_key_der.as_bytes())
.map_err(|e| RawSignerError::InvalidSigningCredentials(e.to_string()))?;

let oid = &pki.algorithm.oid;
if !(oid == &RSA_OID || oid == &RSA_PSS_OID) {
return Err(RawSignerError::InvalidSigningCredentials(format!(
"unsupported private key algorithm ({oid})"
)));
}

let pkcs1_key = pkcs1::RsaPrivateKey::try_from(pki.private_key)
.map_err(|e| RawSignerError::InvalidSigningCredentials(e.to_string()))?;

if pkcs1_key.version() != pkcs1::Version::TwoPrime {
return Err(RawSignerError::InvalidSigningCredentials(
"multi-prime RSA keys not supported".to_string(),
));
}

let n = BigUint::from_bytes_be(pkcs1_key.modulus.as_bytes());
let e = BigUint::from_bytes_be(pkcs1_key.public_exponent.as_bytes());
let d = BigUint::from_bytes_be(pkcs1_key.private_exponent.as_bytes());
let prime1 = BigUint::from_bytes_be(pkcs1_key.prime1.as_bytes());
let prime2 = BigUint::from_bytes_be(pkcs1_key.prime2.as_bytes());
let primes = vec![prime1, prime2];
let private_key = RsaPrivateKey::from_components(n, e, d, primes)
.map_err(|e| RawSignerError::InvalidSigningCredentials(e.to_string()))?;

let alg: RsaSigningAlg = match alg {
SigningAlg::Ps256 => RsaSigningAlg::Ps256,
SigningAlg::Ps384 => RsaSigningAlg::Ps384,
SigningAlg::Ps512 => RsaSigningAlg::Ps512,
_ => {
return Err(RawSignerError::InternalError(
"RsaSigner should be used only for SigningAlg::Ps***".to_string(),
));
}
};

Ok(RsaSigner {
alg,
cert_chain,
private_key,
cert_chain_len,
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 RsaSigner {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>, RawSignerError> {
let mut rng = rand::thread_rng();

match self.alg {
RsaSigningAlg::Ps256 => {
let s = SigningKey::<Sha256>::new(self.private_key.clone());
let sig = s.sign_with_rng(&mut rng, data);
Ok(sig.to_bytes().to_vec())
}

RsaSigningAlg::Ps384 => {
let s = SigningKey::<Sha384>::new(self.private_key.clone());
let sig = s.sign_with_rng(&mut rng, data);
Ok(sig.to_bytes().to_vec())
}

RsaSigningAlg::Ps512 => {
let s = SigningKey::<Sha512>::new(self.private_key.clone());
let sig = s.sign_with_rng(&mut rng, data);
Ok(sig.to_bytes().to_vec())
}
}
}

fn reserve_size(&self) -> usize {
1024 + self.cert_chain_len + self.time_stamp_size
}

fn cert_chain(&self) -> Result<Vec<Vec<u8>>, RawSignerError> {
Ok(self.cert_chain.clone())
}

fn alg(&self) -> SigningAlg {
match self.alg {
RsaSigningAlg::Ps256 => SigningAlg::Ps256,
RsaSigningAlg::Ps384 => SigningAlg::Ps384,
RsaSigningAlg::Ps512 => SigningAlg::Ps512,
}
}
}

impl TimeStampProvider for RsaSigner {
fn time_stamp_service_url(&self) -> Option<String> {
self.time_stamp_service_url.clone()
}
}
Loading

0 comments on commit 2c534f9

Please sign in to comment.