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(crypto): Add rsa crate support to rust_native_crypto feature #853

Merged
merged 13 commits into from
Jan 16, 2025
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
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
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should probably include OCSP size if part of this signer

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 we have a way to estimate OCSP size ahead of time?

}

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
Loading