#![cfg(feature = "fips")]
use aws_lc_rs::cipher::{
DecryptingKey, EncryptingKey, EncryptionContext, PaddedBlockDecryptingKey,
PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256,
};
use aws_lc_rs::digest::{Context, SHA1_FOR_LEGACY_USE_ONLY, SHA256, SHA384, SHA512};
use aws_lc_rs::iv::FixedLength;
use aws_lc_rs::rand::{SecureRandom, SystemRandom};
use aws_lc_rs::signature::{
self, UnparsedPublicKey, ECDSA_P256_SHA256_ASN1, ECDSA_P384_SHA384_ASN1,
RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY, RSA_PKCS1_2048_8192_SHA256,
RSA_PKCS1_2048_8192_SHA384, RSA_PKCS1_2048_8192_SHA512, RSA_PSS_2048_8192_SHA256,
RSA_PSS_2048_8192_SHA384, RSA_PSS_2048_8192_SHA512,
};
use super::error::{not_permitted, AlgorithmKind, Error, Result};
use super::provider::{
CryptoProvider, Hasher, SignatureVerifier, Signer, SigningKeyMaterial, SymmetricCipher,
};
use super::types::{AesKeySize, EcCurve, HashAlgorithm, Padding, RsaPublicKey};
#[derive(Debug, Default, Clone, Copy)]
pub struct AwsLcProvider;
impl AwsLcProvider {
pub const fn new() -> Self {
Self
}
}
impl CryptoProvider for AwsLcProvider {
fn name(&self) -> &'static str {
"aws-lc-rs"
}
fn is_legacy_allowed(&self) -> bool {
false
}
fn hasher(&self, algo: HashAlgorithm) -> Result<Box<dyn Hasher>> {
let alg = match algo {
HashAlgorithm::Md5 => {
return Err(not_permitted(
AlgorithmKind::Hash,
"MD5",
"FIPS 140-3 forbids MD5 for any use (NIST SP 800-131A)",
));
},
HashAlgorithm::Sha1 => &SHA1_FOR_LEGACY_USE_ONLY,
HashAlgorithm::Sha256 => &SHA256,
HashAlgorithm::Sha384 => &SHA384,
HashAlgorithm::Sha512 => &SHA512,
};
Ok(Box::new(AwsLcHasher {
ctx: Context::new(alg),
algo,
}))
}
fn symmetric(&self) -> &dyn SymmetricCipher {
&AwsLcSymmetric
}
fn verifier(&self) -> &dyn SignatureVerifier {
&AwsLcVerifier
}
fn random_bytes(&self, out: &mut [u8]) -> Result<()> {
SystemRandom::new()
.fill(out)
.map_err(|_| Error::Backend("aws_lc_rs SystemRandom failed"))
}
fn signer(&self, _key: &SigningKeyMaterial<'_>) -> Result<Box<dyn Signer>> {
Err(Error::Backend(
"AwsLcProvider signing not yet implemented — \
use RustCryptoProvider for sign or pre-sign with an HSM",
))
}
}
struct AwsLcHasher {
ctx: Context,
algo: HashAlgorithm,
}
impl Hasher for AwsLcHasher {
fn update(&mut self, data: &[u8]) {
self.ctx.update(data);
}
fn finalize(self: Box<Self>) -> Vec<u8> {
self.ctx.finish().as_ref().to_vec()
}
fn algorithm(&self) -> HashAlgorithm {
self.algo
}
}
fn aes_alg(key_size: AesKeySize) -> &'static aws_lc_rs::cipher::Algorithm {
match key_size {
AesKeySize::Aes128 => &AES_128,
AesKeySize::Aes256 => &AES_256,
}
}
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(())
}
struct AwsLcSymmetric;
impl SymmetricCipher for AwsLcSymmetric {
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)?;
let iv_array: [u8; 16] = iv.try_into().expect("checked above");
let ec = EncryptionContext::Iv128(FixedLength::from(iv_array));
let alg = aes_alg(key_size);
match padding {
Padding::Pkcs7 => {
let unbound = UnboundCipherKey::new(alg, key)
.map_err(|_| Error::Backend("aws_lc_rs UnboundCipherKey::new failed"))?;
let ek = PaddedBlockEncryptingKey::cbc_pkcs7(unbound).map_err(|_| {
Error::Backend("aws_lc_rs PaddedBlockEncryptingKey::cbc_pkcs7 failed")
})?;
let mut buf: Vec<u8> = Vec::with_capacity(data.len() + 16);
buf.extend_from_slice(data);
ek.less_safe_encrypt(&mut buf, ec)
.map_err(|_| Error::Backend("aws_lc_rs AES-CBC PKCS#7 encrypt failed"))?;
Ok(buf)
},
Padding::None => {
if !data.len().is_multiple_of(16) {
return Err(Error::InvalidInput(
"no-padding AES-CBC requires data length to be a 16-byte multiple",
));
}
let unbound = UnboundCipherKey::new(alg, key)
.map_err(|_| Error::Backend("aws_lc_rs UnboundCipherKey::new failed"))?;
let ek = EncryptingKey::cbc(unbound)
.map_err(|_| Error::Backend("aws_lc_rs EncryptingKey::cbc failed"))?;
let mut buf = data.to_vec();
ek.less_safe_encrypt(&mut buf, ec)
.map_err(|_| Error::Backend("aws_lc_rs AES-CBC encrypt failed"))?;
Ok(buf)
},
}
}
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"));
}
let iv_array: [u8; 16] = iv.try_into().expect("checked above");
let dc = aws_lc_rs::cipher::DecryptionContext::Iv128(FixedLength::from(iv_array));
let alg = aes_alg(key_size);
match padding {
Padding::Pkcs7 => {
let unbound = UnboundCipherKey::new(alg, key)
.map_err(|_| Error::Backend("aws_lc_rs UnboundCipherKey::new failed"))?;
let dk = PaddedBlockDecryptingKey::cbc_pkcs7(unbound).map_err(|_| {
Error::Backend("aws_lc_rs PaddedBlockDecryptingKey::cbc_pkcs7 failed")
})?;
let mut buf = data.to_vec();
let pt = dk
.decrypt(&mut buf, dc)
.map_err(|_| Error::Backend("aws_lc_rs AES-CBC PKCS#7 decrypt failed"))?;
Ok(pt.to_vec())
},
Padding::None => {
let unbound = UnboundCipherKey::new(alg, key)
.map_err(|_| Error::Backend("aws_lc_rs UnboundCipherKey::new failed"))?;
let dk = DecryptingKey::cbc(unbound)
.map_err(|_| Error::Backend("aws_lc_rs DecryptingKey::cbc failed"))?;
let mut buf = data.to_vec();
let pt = dk
.decrypt(&mut buf, dc)
.map_err(|_| Error::Backend("aws_lc_rs AES-CBC decrypt failed"))?;
Ok(pt.to_vec())
},
}
}
fn rc4(&self, _key: &[u8], _data: &[u8]) -> Result<Vec<u8>> {
Err(not_permitted(
AlgorithmKind::SymmetricCipher,
"RC4",
"FIPS 140-3 forbids RC4 — re-encrypt PDF Standard Security R≤4 \
documents with R=6 (AES-256) under a non-FIPS provider before opening",
))
}
}
struct AwsLcVerifier;
impl SignatureVerifier for AwsLcVerifier {
fn verify_rsa_pkcs1v15(
&self,
pubkey: &RsaPublicKey<'_>,
hash: HashAlgorithm,
message: &[u8],
signature: &[u8],
) -> Result<()> {
let scheme: &dyn signature::VerificationAlgorithm = match hash {
HashAlgorithm::Sha1 => &RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
HashAlgorithm::Sha256 => &RSA_PKCS1_2048_8192_SHA256,
HashAlgorithm::Sha384 => &RSA_PKCS1_2048_8192_SHA384,
HashAlgorithm::Sha512 => &RSA_PKCS1_2048_8192_SHA512,
HashAlgorithm::Md5 => {
return Err(not_permitted(
AlgorithmKind::SignatureVerify,
"RSA-PKCS#1-v1.5+MD5",
"FIPS 140-3 disallows MD5 for signature verification",
));
},
};
let spki = encode_rsa_public_key_der(pubkey)?;
let key = UnparsedPublicKey::new(scheme, &spki);
key.verify(message, 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<()> {
let scheme: &dyn signature::VerificationAlgorithm = match hash {
HashAlgorithm::Sha256 => &RSA_PSS_2048_8192_SHA256,
HashAlgorithm::Sha384 => &RSA_PSS_2048_8192_SHA384,
HashAlgorithm::Sha512 => &RSA_PSS_2048_8192_SHA512,
HashAlgorithm::Sha1 | HashAlgorithm::Md5 => {
return Err(not_permitted(
AlgorithmKind::SignatureVerify,
"RSA-PSS-SHA1/MD5",
"FIPS 140-3 disallows PSS with SHA-1 or MD5",
));
},
};
let spki = encode_rsa_public_key_der(pubkey)?;
let key = UnparsedPublicKey::new(scheme, &spki);
key.verify(message, signature)
.map_err(|_| Error::Verification("RSA-PSS signature did not verify"))
}
fn verify_ecdsa(
&self,
curve: EcCurve,
pubkey_sec1: &[u8],
message: &[u8],
signature_der: &[u8],
) -> Result<()> {
let scheme: &dyn signature::VerificationAlgorithm = match curve {
EcCurve::P256 => &ECDSA_P256_SHA256_ASN1,
EcCurve::P384 => &ECDSA_P384_SHA384_ASN1,
};
let key = UnparsedPublicKey::new(scheme, pubkey_sec1);
key.verify(message, signature_der)
.map_err(|_| Error::Verification("ECDSA signature did not verify"))
}
}
fn encode_rsa_public_key_der(pubkey: &RsaPublicKey<'_>) -> Result<Vec<u8>> {
let mut rsapubkey = Vec::new();
der_seq(&mut rsapubkey, |inner| {
der_unsigned_int(inner, pubkey.modulus_be);
der_unsigned_int(inner, pubkey.exponent_be);
});
const RSA_ENCRYPTION_OID_DER: &[u8] = &[
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
];
const NULL_DER: &[u8] = &[0x05, 0x00];
let mut algo_id = Vec::new();
algo_id.extend_from_slice(RSA_ENCRYPTION_OID_DER);
algo_id.extend_from_slice(NULL_DER);
let mut subject_pk_bitstring = Vec::with_capacity(1 + rsapubkey.len());
subject_pk_bitstring.push(0x00); subject_pk_bitstring.extend_from_slice(&rsapubkey);
let mut spki = Vec::new();
der_seq(&mut spki, |inner| {
der_seq(inner, |a| a.extend_from_slice(&algo_id));
der_tag_value(inner, 0x03, &subject_pk_bitstring); });
Ok(spki)
}
fn der_seq<F: FnOnce(&mut Vec<u8>)>(out: &mut Vec<u8>, content: F) {
let mut inner = Vec::new();
content(&mut inner);
der_tag_value(out, 0x30, &inner);
}
fn der_unsigned_int(out: &mut Vec<u8>, be_bytes: &[u8]) {
let mut view = be_bytes;
while view.len() > 1 && view[0] == 0 {
view = &view[1..];
}
let needs_leading_zero = !view.is_empty() && view[0] & 0x80 != 0;
let total_len = view.len() + if needs_leading_zero { 1 } else { 0 };
out.push(0x02);
der_length(out, total_len);
if needs_leading_zero {
out.push(0x00);
}
out.extend_from_slice(view);
}
fn der_tag_value(out: &mut Vec<u8>, tag: u8, value: &[u8]) {
out.push(tag);
der_length(out, value.len());
out.extend_from_slice(value);
}
fn der_length(out: &mut Vec<u8>, len: usize) {
if len < 0x80 {
out.push(len as u8);
} else {
let mut bytes = Vec::with_capacity(4);
let mut n = len;
while n > 0 {
bytes.insert(0, (n & 0xff) as u8);
n >>= 8;
}
out.push(0x80 | bytes.len() as u8);
out.extend_from_slice(&bytes);
}
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
fn provider() -> AwsLcProvider {
AwsLcProvider::new()
}
#[test]
fn name_and_legacy_policy() {
let p = provider();
assert_eq!(p.name(), "aws-lc-rs");
assert!(!p.is_legacy_allowed());
}
#[test]
fn md5_rejected() {
let p = provider();
let result = p.hasher(HashAlgorithm::Md5);
assert!(matches!(result, Err(Error::AlgorithmNotPermitted { .. })));
}
#[test]
fn rc4_rejected() {
let p = provider();
let result = p.symmetric().rc4(b"key", b"data");
assert!(matches!(result, Err(Error::AlgorithmNotPermitted { .. })));
}
#[test]
fn pss_sha1_rejected() {
let p = provider();
let pubkey = RsaPublicKey {
modulus_be: &[0u8; 256],
exponent_be: &[0x01, 0x00, 0x01],
};
let result =
p.verifier()
.verify_rsa_pss(&pubkey, HashAlgorithm::Sha1, b"message", &[0u8; 256]);
assert!(matches!(result, Err(Error::AlgorithmNotPermitted { .. })));
}
#[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"
)
);
}
#[test]
fn aes128_cbc_pkcs7_round_trip() {
let p = provider();
let key = [0x42u8; 16];
let iv = [0x13u8; 16];
let plaintext = b"PDF Standard Security 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 random_bytes_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);
}
#[test]
fn cross_provider_aes_compat() {
use super::super::RustCryptoProvider;
let key = [0x42u8; 16];
let iv = [0x13u8; 16];
let plaintext = b"FIPS-vs-rust-crypto byte-equality check.";
let aws_lc = AwsLcProvider::new();
let rust = RustCryptoProvider::new();
let aws_ct = aws_lc
.symmetric()
.aes_cbc_encrypt(AesKeySize::Aes128, &key, &iv, plaintext, Padding::Pkcs7)
.unwrap();
let rust_ct = rust
.symmetric()
.aes_cbc_encrypt(AesKeySize::Aes128, &key, &iv, plaintext, Padding::Pkcs7)
.unwrap();
assert_eq!(aws_ct, rust_ct, "FIPS and rust-crypto AES-CBC must match");
}
}