use super::error::{Error, Result};
use super::provider::{
CryptoProvider, Hasher, SignatureVerifier, Signer, SigningKeyMaterial, SymmetricCipher,
};
use super::types::{AesKeySize, EcCurve, HashAlgorithm, Padding, RsaPublicKey};
#[cfg(feature = "signatures")]
use super::types::{AsymmetricAlgorithm, RsaScheme, SigningAlgorithm};
#[derive(Debug, Default, Clone, Copy)]
pub struct RustCryptoProvider;
impl RustCryptoProvider {
pub const fn new() -> Self {
Self
}
}
impl CryptoProvider for RustCryptoProvider {
fn name(&self) -> &'static str {
"rust-crypto"
}
fn is_legacy_allowed(&self) -> bool {
cfg!(feature = "legacy-crypto")
}
fn hasher(&self, algo: HashAlgorithm) -> Result<Box<dyn Hasher>> {
Ok(match algo {
HashAlgorithm::Md5 => {
#[cfg(feature = "legacy-crypto")]
{
Box::new(Md5Hasher::new())
}
#[cfg(not(feature = "legacy-crypto"))]
{
return Err(Error::AlgorithmNotPermitted {
kind: crate::crypto::error::AlgorithmKind::Hash,
name: "MD5",
reason: "legacy-crypto feature disabled at compile time",
});
}
},
HashAlgorithm::Sha1 => {
#[cfg(feature = "signatures")]
{
Box::new(Sha1Hasher::new())
}
#[cfg(not(feature = "signatures"))]
{
return Err(Error::Backend("SHA-1 requires the 'signatures' cargo feature"));
}
},
HashAlgorithm::Sha256 => Box::new(Sha256Hasher::new()),
HashAlgorithm::Sha384 => Box::new(Sha384Hasher::new()),
HashAlgorithm::Sha512 => Box::new(Sha512Hasher::new()),
})
}
fn symmetric(&self) -> &dyn SymmetricCipher {
&RustSymmetric
}
fn verifier(&self) -> &dyn SignatureVerifier {
&RustVerifier
}
fn random_bytes(&self, out: &mut [u8]) -> Result<()> {
getrandom::fill(out).map_err(|_| Error::Backend("getrandom failed"))
}
fn signer(&self, _key: &SigningKeyMaterial<'_>) -> Result<Box<dyn Signer>> {
#[cfg(feature = "signatures")]
{
signing::build_signer(_key)
}
#[cfg(not(feature = "signatures"))]
{
Err(Error::Backend("signing requires the 'signatures' cargo feature"))
}
}
}
#[cfg(feature = "legacy-crypto")]
struct Md5Hasher(md5::Md5);
#[cfg(feature = "legacy-crypto")]
impl Md5Hasher {
fn new() -> Self {
use md5::Digest;
Self(md5::Md5::new())
}
}
#[cfg(feature = "legacy-crypto")]
impl Hasher for Md5Hasher {
fn update(&mut self, data: &[u8]) {
use md5::Digest;
self.0.update(data);
}
fn finalize(self: Box<Self>) -> Vec<u8> {
use md5::Digest;
self.0.finalize().to_vec()
}
fn algorithm(&self) -> HashAlgorithm {
HashAlgorithm::Md5
}
}
#[cfg(feature = "signatures")]
struct Sha1Hasher(sha1::Sha1);
#[cfg(feature = "signatures")]
impl Sha1Hasher {
fn new() -> Self {
use sha1::Digest;
Self(sha1::Sha1::new())
}
}
#[cfg(feature = "signatures")]
impl Hasher for Sha1Hasher {
fn update(&mut self, data: &[u8]) {
use sha1::Digest;
self.0.update(data);
}
fn finalize(self: Box<Self>) -> Vec<u8> {
use sha1::Digest;
self.0.finalize().to_vec()
}
fn algorithm(&self) -> HashAlgorithm {
HashAlgorithm::Sha1
}
}
macro_rules! sha2_hasher {
($name:ident, $inner:ty, $algo:expr) => {
struct $name($inner);
impl $name {
fn new() -> Self {
use sha2::Digest;
Self(<$inner>::new())
}
}
impl Hasher for $name {
fn update(&mut self, data: &[u8]) {
use sha2::Digest;
self.0.update(data);
}
fn finalize(self: Box<Self>) -> Vec<u8> {
use sha2::Digest;
self.0.finalize().to_vec()
}
fn algorithm(&self) -> HashAlgorithm {
$algo
}
}
};
}
sha2_hasher!(Sha256Hasher, sha2::Sha256, HashAlgorithm::Sha256);
sha2_hasher!(Sha384Hasher, sha2::Sha384, HashAlgorithm::Sha384);
sha2_hasher!(Sha512Hasher, sha2::Sha512, HashAlgorithm::Sha512);
struct RustSymmetric;
impl SymmetricCipher for RustSymmetric {
fn aes_cbc_encrypt(
&self,
key_size: AesKeySize,
key: &[u8],
iv: &[u8],
data: &[u8],
padding: Padding,
) -> Result<Vec<u8>> {
check_key_iv(key_size, key, iv)?;
if matches!(padding, Padding::None) && !data.len().is_multiple_of(16) {
return Err(Error::InvalidInput(
"no-padding AES-CBC requires data length to be a 16-byte multiple",
));
}
match (key_size, padding) {
(AesKeySize::Aes128, Padding::Pkcs7) => {
aes_cbc_encrypt_pkcs7::<aes::Aes128>(key, iv, data)
},
(AesKeySize::Aes128, Padding::None) => {
aes_cbc_encrypt_no_pad::<aes::Aes128>(key, iv, data)
},
(AesKeySize::Aes256, Padding::Pkcs7) => {
aes_cbc_encrypt_pkcs7::<aes::Aes256>(key, iv, data)
},
(AesKeySize::Aes256, Padding::None) => {
aes_cbc_encrypt_no_pad::<aes::Aes256>(key, iv, data)
},
}
}
fn aes_cbc_decrypt(
&self,
key_size: AesKeySize,
key: &[u8],
iv: &[u8],
data: &[u8],
padding: Padding,
) -> Result<Vec<u8>> {
check_key_iv(key_size, key, iv)?;
if !data.len().is_multiple_of(16) {
return Err(Error::InvalidInput("AES-CBC ciphertext must be a 16-byte multiple"));
}
match (key_size, padding) {
(AesKeySize::Aes128, Padding::Pkcs7) => {
aes_cbc_decrypt_pkcs7::<aes::Aes128>(key, iv, data)
},
(AesKeySize::Aes128, Padding::None) => {
aes_cbc_decrypt_no_pad::<aes::Aes128>(key, iv, data)
},
(AesKeySize::Aes256, Padding::Pkcs7) => {
aes_cbc_decrypt_pkcs7::<aes::Aes256>(key, iv, data)
},
(AesKeySize::Aes256, Padding::None) => {
aes_cbc_decrypt_no_pad::<aes::Aes256>(key, iv, data)
},
}
}
#[cfg_attr(not(feature = "legacy-crypto"), allow(unused_variables))]
fn rc4(&self, key: &[u8], data: &[u8]) -> Result<Vec<u8>> {
#[cfg(not(feature = "legacy-crypto"))]
{
Err(Error::AlgorithmNotPermitted {
kind: crate::crypto::error::AlgorithmKind::SymmetricCipher,
name: "RC4",
reason: "legacy-crypto feature disabled at compile time",
})
}
#[cfg(feature = "legacy-crypto")]
{
if key.is_empty() || key.len() > 256 {
return Err(Error::InvalidInput("RC4 key must be 1..=256 bytes"));
}
Ok(crate::encryption::rc4::rc4_crypt_impl(key, data))
}
}
}
fn check_key_iv(key_size: AesKeySize, key: &[u8], iv: &[u8]) -> Result<()> {
if key.len() != key_size.key_bytes() {
return Err(Error::InvalidInput(match key_size {
AesKeySize::Aes128 => "AES-128 requires a 16-byte key",
AesKeySize::Aes256 => "AES-256 requires a 32-byte key",
}));
}
if iv.len() != 16 {
return Err(Error::InvalidInput("AES-CBC requires a 16-byte IV"));
}
Ok(())
}
fn aes_cbc_encrypt_pkcs7<C>(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>>
where
C: aes::cipher::BlockCipherEncrypt + aes::cipher::KeyInit,
{
use aes::cipher::{block_padding::Pkcs7, BlockModeEncrypt, KeyIvInit};
type Enc<C> = cbc::Encryptor<C>;
let cipher = <Enc<C> as KeyIvInit>::new_from_slices(key, iv)
.map_err(|_| Error::InvalidInput("AES-CBC key/iv length mismatch"))?;
let mut buf = vec![0u8; data.len() + 16];
buf[..data.len()].copy_from_slice(data);
let n = cipher
.encrypt_padded::<Pkcs7>(&mut buf, data.len())
.map_err(|_| Error::Backend("AES-CBC PKCS#7 encryption failed"))?
.len();
buf.truncate(n);
Ok(buf)
}
fn aes_cbc_decrypt_pkcs7<C>(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>>
where
C: aes::cipher::BlockCipherDecrypt + aes::cipher::KeyInit,
{
use aes::cipher::{block_padding::Pkcs7, BlockModeDecrypt, KeyIvInit};
type Dec<C> = cbc::Decryptor<C>;
let cipher = <Dec<C> as KeyIvInit>::new_from_slices(key, iv)
.map_err(|_| Error::InvalidInput("AES-CBC key/iv length mismatch"))?;
let mut buf = data.to_vec();
let n = cipher
.decrypt_padded::<Pkcs7>(&mut buf)
.map_err(|_| Error::Verification("AES-CBC PKCS#7 padding invalid"))?
.len();
buf.truncate(n);
Ok(buf)
}
fn aes_cbc_encrypt_no_pad<C>(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>>
where
C: aes::cipher::BlockCipherEncrypt + aes::cipher::KeyInit,
{
use aes::cipher::{block_padding::NoPadding, BlockModeEncrypt, KeyIvInit};
type Enc<C> = cbc::Encryptor<C>;
let cipher = <Enc<C> as KeyIvInit>::new_from_slices(key, iv)
.map_err(|_| Error::InvalidInput("AES-CBC key/iv length mismatch"))?;
let mut buf = data.to_vec();
let len = data.len();
cipher
.encrypt_padded::<NoPadding>(&mut buf, len)
.map_err(|_| Error::Backend("AES-CBC no-padding encryption failed"))?;
Ok(buf)
}
fn aes_cbc_decrypt_no_pad<C>(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>>
where
C: aes::cipher::BlockCipherDecrypt + aes::cipher::KeyInit,
{
use aes::cipher::{block_padding::NoPadding, BlockModeDecrypt, KeyIvInit};
type Dec<C> = cbc::Decryptor<C>;
let cipher = <Dec<C> as KeyIvInit>::new_from_slices(key, iv)
.map_err(|_| Error::InvalidInput("AES-CBC key/iv length mismatch"))?;
let mut buf = data.to_vec();
cipher
.decrypt_padded::<NoPadding>(&mut buf)
.map_err(|_| Error::Backend("AES-CBC no-padding decryption failed"))?;
Ok(buf)
}
struct RustVerifier;
#[cfg(feature = "signatures")]
impl SignatureVerifier for RustVerifier {
fn verify_rsa_pkcs1v15(
&self,
pubkey: &RsaPublicKey<'_>,
hash: HashAlgorithm,
message: &[u8],
signature: &[u8],
) -> Result<()> {
use crate::signatures::crypto::digest_info_prefix;
use rsa::pkcs1v15::Pkcs1v15Sign;
use rsa::RsaPublicKey as RcRsa;
let (oid, digest) = match hash {
HashAlgorithm::Sha1 => {
use sha1::Digest as _;
(der::oid::db::rfc5912::ID_SHA_1, sha1::Sha1::digest(message).to_vec())
},
HashAlgorithm::Sha256 => {
use sha2_v10::Digest as _;
(der::oid::db::rfc5912::ID_SHA_256, sha2_v10::Sha256::digest(message).to_vec())
},
HashAlgorithm::Sha384 => {
use sha2_v10::Digest as _;
(der::oid::db::rfc5912::ID_SHA_384, sha2_v10::Sha384::digest(message).to_vec())
},
HashAlgorithm::Sha512 => {
use sha2_v10::Digest as _;
(der::oid::db::rfc5912::ID_SHA_512, sha2_v10::Sha512::digest(message).to_vec())
},
HashAlgorithm::Md5 => {
return Err(Error::Verification(
"MD5 not supported for RSA-PKCS#1-v1.5 signature verification",
));
},
};
let prefix = digest_info_prefix(oid)
.ok_or(Error::Backend("no DigestInfo prefix table entry for selected hash"))?;
let mut digest_info = Vec::with_capacity(prefix.len() + digest.len());
digest_info.extend_from_slice(prefix);
digest_info.extend_from_slice(&digest);
let n = rsa::BigUint::from_bytes_be(pubkey.modulus_be);
let e = rsa::BigUint::from_bytes_be(pubkey.exponent_be);
let key =
RcRsa::new(n, e).map_err(|_| Error::InvalidInput("invalid RSA modulus/exponent"))?;
key.verify(Pkcs1v15Sign::new_unprefixed(), &digest_info, signature)
.map_err(|_| Error::Verification("RSA-PKCS#1-v1.5 signature did not verify"))
}
fn verify_rsa_pss(
&self,
pubkey: &RsaPublicKey<'_>,
hash: HashAlgorithm,
message: &[u8],
signature: &[u8],
) -> Result<()> {
use rsa::pss::{Signature as PssSignature, VerifyingKey};
use rsa::signature::Verifier;
use rsa::RsaPublicKey as RcRsa;
let n = rsa::BigUint::from_bytes_be(pubkey.modulus_be);
let e = rsa::BigUint::from_bytes_be(pubkey.exponent_be);
let key =
RcRsa::new(n, e).map_err(|_| Error::InvalidInput("invalid RSA modulus/exponent"))?;
let sig = PssSignature::try_from(signature)
.map_err(|_| Error::InvalidInput("malformed RSA-PSS signature bytes"))?;
let ok = match hash {
HashAlgorithm::Sha256 => VerifyingKey::<sha2_v10::Sha256>::new(key)
.verify(message, &sig)
.is_ok(),
HashAlgorithm::Sha384 => VerifyingKey::<sha2_v10::Sha384>::new(key)
.verify(message, &sig)
.is_ok(),
HashAlgorithm::Sha512 => VerifyingKey::<sha2_v10::Sha512>::new(key)
.verify(message, &sig)
.is_ok(),
HashAlgorithm::Sha1 | HashAlgorithm::Md5 => {
return Err(Error::Backend(
"SHA-1 / MD5 RSA-PSS not supported by RustCryptoProvider \
(rsa 0.9 ↔ sha1 0.11 / md-5 0.11 digest-trait mismatch)",
));
},
};
if ok {
Ok(())
} else {
Err(Error::Verification("RSA-PSS signature did not verify"))
}
}
fn verify_ecdsa(
&self,
curve: EcCurve,
pubkey_sec1: &[u8],
message: &[u8],
signature_der: &[u8],
) -> Result<()> {
match curve {
EcCurve::P256 => {
use p256::ecdsa::{signature::Verifier, Signature, VerifyingKey};
let vk = VerifyingKey::from_sec1_bytes(pubkey_sec1)
.map_err(|_| Error::InvalidInput("invalid SEC1 P-256 public key"))?;
let sig = Signature::from_der(signature_der)
.map_err(|_| Error::InvalidInput("malformed P-256 ECDSA signature DER"))?;
vk.verify(message, &sig)
.map_err(|_| Error::Verification("P-256 ECDSA signature did not verify"))
},
EcCurve::P384 => {
use p384::ecdsa::{signature::Verifier, Signature, VerifyingKey};
let vk = VerifyingKey::from_sec1_bytes(pubkey_sec1)
.map_err(|_| Error::InvalidInput("invalid SEC1 P-384 public key"))?;
let sig = Signature::from_der(signature_der)
.map_err(|_| Error::InvalidInput("malformed P-384 ECDSA signature DER"))?;
vk.verify(message, &sig)
.map_err(|_| Error::Verification("P-384 ECDSA signature did not verify"))
},
}
}
}
#[cfg(not(feature = "signatures"))]
impl SignatureVerifier for RustVerifier {
fn verify_rsa_pkcs1v15(
&self,
_pubkey: &RsaPublicKey<'_>,
_hash: HashAlgorithm,
_message: &[u8],
_signature: &[u8],
) -> Result<()> {
Err(Error::Backend("RSA verification requires the 'signatures' cargo feature"))
}
fn verify_rsa_pss(
&self,
_pubkey: &RsaPublicKey<'_>,
_hash: HashAlgorithm,
_message: &[u8],
_signature: &[u8],
) -> Result<()> {
Err(Error::Backend("RSA-PSS verification requires the 'signatures' cargo feature"))
}
fn verify_ecdsa(
&self,
_curve: EcCurve,
_pubkey_sec1: &[u8],
_digest: &[u8],
_signature_der: &[u8],
) -> Result<()> {
Err(Error::Backend("ECDSA verification requires the 'signatures' cargo feature"))
}
}
#[cfg(feature = "signatures")]
mod signing {
use super::*;
use rsa::pkcs1v15::Pkcs1v15Sign;
use rsa::pkcs8::DecodePrivateKey;
use rsa::RsaPrivateKey;
pub(super) fn build_signer(key: &SigningKeyMaterial<'_>) -> Result<Box<dyn Signer>> {
let (priv_key, algo, scheme) = match key {
SigningKeyMaterial::Pkcs8Der { algo, bytes } => match algo.asym {
AsymmetricAlgorithm::Rsa(scheme) => {
let pk = RsaPrivateKey::from_pkcs8_der(bytes)
.map_err(|_| Error::InvalidInput("invalid PKCS#8 RSA private key"))?;
(pk, *algo, scheme)
},
AsymmetricAlgorithm::Ecdsa(_) => {
return Err(Error::Backend(
"ECDSA signing not yet implemented in RustCryptoProvider",
));
},
},
SigningKeyMaterial::Pkcs1Der {
scheme,
hash,
bytes,
} => {
use rsa::pkcs1::DecodeRsaPrivateKey;
let pk = RsaPrivateKey::from_pkcs1_der(bytes)
.map_err(|_| Error::InvalidInput("invalid PKCS#1 RSA private key"))?;
let algo = SigningAlgorithm {
asym: AsymmetricAlgorithm::Rsa(*scheme),
hash: *hash,
};
(pk, algo, *scheme)
},
};
if !matches!(scheme, RsaScheme::Pkcs1v15) {
return Err(Error::Backend(
"RustCryptoProvider only signs RSA-PKCS#1-v1.5; use AwsLcProvider for PSS sign",
));
}
Ok(Box::new(RsaSigner {
key: priv_key,
algo,
}))
}
struct RsaSigner {
key: RsaPrivateKey,
algo: SigningAlgorithm,
}
impl Signer for RsaSigner {
fn algorithm(&self) -> SigningAlgorithm {
self.algo
}
fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
self.key
.sign(Pkcs1v15Sign::new_unprefixed(), message)
.map_err(|_| Error::Backend("RSA-PKCS#1-v1.5 signing failed"))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn provider() -> RustCryptoProvider {
RustCryptoProvider::new()
}
#[test]
fn name_and_legacy_policy() {
let p = provider();
assert_eq!(p.name(), "rust-crypto");
#[cfg(feature = "legacy-crypto")]
assert!(p.is_legacy_allowed());
#[cfg(not(feature = "legacy-crypto"))]
assert!(!p.is_legacy_allowed());
}
use hex_literal::hex;
#[cfg(feature = "legacy-crypto")]
#[test]
fn md5_empty() {
let p = provider();
let mut h = p.hasher(HashAlgorithm::Md5).unwrap();
h.update(b"");
assert_eq!(h.finalize(), hex!("d41d8cd98f00b204e9800998ecf8427e"));
}
#[cfg(feature = "legacy-crypto")]
#[test]
fn md5_abc() {
let p = provider();
let mut h = p.hasher(HashAlgorithm::Md5).unwrap();
h.update(b"abc");
assert_eq!(h.finalize(), hex!("900150983cd24fb0d6963f7d28e17f72"));
}
#[test]
fn sha256_abc() {
let p = provider();
let mut h = p.hasher(HashAlgorithm::Sha256).unwrap();
h.update(b"abc");
assert_eq!(
h.finalize(),
hex!("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
);
}
#[test]
fn sha384_abc() {
let p = provider();
let mut h = p.hasher(HashAlgorithm::Sha384).unwrap();
h.update(b"abc");
assert_eq!(
h.finalize(),
hex!(
"cb00753f45a35e8bb5a03d699ac65007"
"272c32ab0eded1631a8b605a43ff5bed"
"8086072ba1e7cc2358baeca134c825a7"
)
);
}
#[test]
fn sha512_abc() {
let p = provider();
let mut h = p.hasher(HashAlgorithm::Sha512).unwrap();
h.update(b"abc");
assert_eq!(
h.finalize(),
hex!(
"ddaf35a193617abacc417349ae204131"
"12e6fa4e89a97ea20a9eeee64b55d39a"
"2192992a274fc1a836ba3c23a3feebbd"
"454d4423643ce80e2a9ac94fa54ca49f"
)
);
}
#[cfg(feature = "signatures")]
#[test]
fn sha1_abc() {
let p = provider();
let mut h = p.hasher(HashAlgorithm::Sha1).unwrap();
h.update(b"abc");
assert_eq!(h.finalize(), hex!("a9993e364706816aba3e25717850c26c9cd0d89d"));
}
#[test]
fn aes128_cbc_pkcs7_round_trip() {
let p = provider();
let key = [0x42u8; 16];
let iv = [0x13u8; 16];
let plaintext = b"PDF Standard Security Handler V=4 stream content.";
let ct = p
.symmetric()
.aes_cbc_encrypt(AesKeySize::Aes128, &key, &iv, plaintext, Padding::Pkcs7)
.unwrap();
let pt = p
.symmetric()
.aes_cbc_decrypt(AesKeySize::Aes128, &key, &iv, &ct, Padding::Pkcs7)
.unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn aes256_cbc_no_pad_round_trip() {
let p = provider();
let key = [0x07u8; 32];
let iv = [0u8; 16]; let plaintext = [0xa5u8; 32]; let ct = p
.symmetric()
.aes_cbc_encrypt(AesKeySize::Aes256, &key, &iv, &plaintext, Padding::None)
.unwrap();
let pt = p
.symmetric()
.aes_cbc_decrypt(AesKeySize::Aes256, &key, &iv, &ct, Padding::None)
.unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn aes_cbc_no_pad_rejects_unaligned_input() {
let p = provider();
let key = [0u8; 16];
let iv = [0u8; 16];
let result = p.symmetric().aes_cbc_encrypt(
AesKeySize::Aes128,
&key,
&iv,
b"15 bytes only..",
Padding::None,
);
assert!(matches!(result, Err(Error::InvalidInput(_))));
}
#[test]
fn aes_rejects_wrong_key_length() {
let p = provider();
let result = p.symmetric().aes_cbc_encrypt(
AesKeySize::Aes128,
&[0u8; 8], &[0u8; 16],
b"data1234data1234",
Padding::None,
);
assert!(matches!(result, Err(Error::InvalidInput(_))));
}
#[cfg(feature = "legacy-crypto")]
#[test]
fn rc4_round_trip() {
let p = provider();
let key = b"PDF-encrypt-key";
let plaintext = b"Sensitive PDF content stream.";
let ct = p.symmetric().rc4(key, plaintext).unwrap();
let pt = p.symmetric().rc4(key, &ct).unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn random_bytes_are_non_constant() {
let p = provider();
let mut a = [0u8; 32];
let mut b = [0u8; 32];
p.random_bytes(&mut a).unwrap();
p.random_bytes(&mut b).unwrap();
assert_ne!(a, b, "random_bytes must not return constant data");
}
}