use aws_lc_rs::{digest, hmac, signature::Ed25519KeyPair};
use proptest::collection::vec;
use proptest::prelude::*;
use rind::signature;
#[test]
fn ed25519_key_generation() {
for _ in 0..100 {
let key = signature::Ed25519KeyPair::generate().unwrap();
let key_enc = key.to_pkcs8();
let key_enc = key_enc.as_ref();
let aws = Ed25519KeyPair::from_pkcs8(key_enc)
.expect("aws-lc-rs rejected an ed25519 key we generated");
assert_eq!(
aws.to_pkcs8().unwrap().as_ref(),
key_enc,
"aws-lc-rs varied our ed25519 key encoding"
);
}
}
#[test]
fn hashes_every_length() {
let message: Vec<u8> = (0..3 * 128 + 1).map(|i| (i * 7 + 3) as u8).collect();
for (ours, theirs, block) in [
(
&rind::digest::legacy::SHA1,
&digest::SHA1_FOR_LEGACY_USE_ONLY,
64,
),
(&rind::digest::SHA256, &digest::SHA256, 64),
(&rind::digest::SHA384, &digest::SHA384, 128),
(&rind::digest::SHA512, &digest::SHA512, 128),
(&rind::digest::SHA512_256, &digest::SHA512_256, 128),
] {
for len in 0..=message.len() {
let message = &message[..len];
let wanted = digest::digest(theirs, message);
let got = rind::digest::digest(ours, message);
assert_eq!(got.as_ref(), wanted.as_ref(), "{ours:?} one-shot {len}");
let mut ctx = rind::digest::Context::new(ours);
for part in message.chunks(block) {
ctx.update(part);
}
assert_eq!(
ctx.finish().as_ref(),
wanted.as_ref(),
"{ours:?} blockwise {len}"
);
}
}
}
proptest! {
#[test]
fn hashes_match_aws_lc_rs(
message in vec(any::<u8>(), 0..1024),
splits in vec(any::<prop::sample::Index>(), 0..4),
) {
let mut splits: Vec<_> = splits.iter().map(|s| s.index(message.len() + 1)).collect();
splits.sort();
for (ours, theirs) in [
(&rind::digest::legacy::SHA1, &digest::SHA1_FOR_LEGACY_USE_ONLY),
(&rind::digest::SHA256, &digest::SHA256),
(&rind::digest::SHA384, &digest::SHA384),
(&rind::digest::SHA512, &digest::SHA512),
(&rind::digest::SHA512_256, &digest::SHA512_256),
] {
let mut ctx = rind::digest::Context::new(ours);
let mut start = 0;
for &end in splits.iter().chain([&message.len()]) {
ctx.update(&message[start..end]);
start = end;
}
let (got, wanted) = (ctx.finish(), digest::digest(theirs, &message));
prop_assert_eq!(got.as_ref(), wanted.as_ref());
}
}
#[test]
fn hmac_matches_aws_lc_rs(
key in vec(any::<u8>(), 0..300),
message in vec(any::<u8>(), 0..1024),
split in any::<prop::sample::Index>(),
) {
let split = split.index(message.len() + 1);
for (ours, theirs) in [
(&rind::hmac::legacy::HMAC_SHA1, hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY),
(&rind::hmac::HMAC_SHA256, hmac::HMAC_SHA256),
(&rind::hmac::HMAC_SHA384, hmac::HMAC_SHA384),
(&rind::hmac::HMAC_SHA512, hmac::HMAC_SHA512),
] {
let ours = rind::hmac::Key::new(ours, &key);
let mut ctx = rind::hmac::Context::with_key(&ours);
ctx.update(&message[..split]);
ctx.update(&message[split..]);
let wanted = hmac::sign(&hmac::Key::new(theirs, &key), &message);
let got = ctx.sign();
prop_assert_eq!(got.as_ref(), wanted.as_ref());
prop_assert_eq!(rind::hmac::verify(&ours, &message, wanted.as_ref()), Ok(()));
}
}
#[test]
fn slice_comparison_matches_aws_lc_rs(
a in vec(any::<u8>(), 0..64),
other in vec(any::<u8>(), 0..64),
flip in any::<prop::sample::Index>(),
) {
let mut flipped = a.clone();
if !flipped.is_empty() {
flipped[flip.index(a.len())] ^= 1;
}
for b in [&a, &flipped, &other] {
let ours = rind::constant_time::verify(&a, b);
let theirs = aws_lc_rs::constant_time::verify_slices_are_equal(&a, b);
prop_assert_eq!(ours.is_ok(), theirs.is_ok());
let wanted = if a.len() != b.len() {
Err(rind::Error::WrongLength)
} else if a != *b {
Err(rind::Error::NotEqual)
} else {
Ok(())
};
prop_assert_eq!(ours, wanted);
}
}
}
mod cipher {
use aws_lc_rs::aead::{Aad, CHACHA20_POLY1305, LessSafeKey, Nonce, UnboundKey, quic};
use aws_lc_rs::cipher::{AES_128, AES_256, EncryptingKey, EncryptionContext, UnboundCipherKey};
use proptest::prelude::*;
use rind::Error;
use rind::cipher::{self, Aes, AesCtr, ChaCha20};
fn aes_key() -> impl Strategy<Value = Vec<u8>> {
prop_oneof![
prop::collection::vec(any::<u8>(), 16),
prop::collection::vec(any::<u8>(), 32)
]
}
fn rind_aes_algorithm(key: &[u8]) -> &'static cipher::Algorithm {
if key.len() == 16 {
&cipher::AES_128
} else {
&cipher::AES_256
}
}
fn aws_aes_key(key: &[u8]) -> UnboundCipherKey {
let alg = if key.len() == 16 { &AES_128 } else { &AES_256 };
UnboundCipherKey::new(alg, key).unwrap()
}
fn counter() -> impl Strategy<Value = u32> {
prop_oneof![any::<u32>(), (u32::MAX - 80)..=u32::MAX]
}
proptest! {
#[test]
fn aes_block(key in aes_key(), block in any::<[u8; 16]>()) {
let mut ours = block;
Aes::new(rind_aes_algorithm(&key), &key).unwrap().encrypt_block(&mut ours);
let mut theirs = block;
EncryptingKey::ecb(aws_aes_key(&key))
.unwrap()
.less_safe_encrypt(&mut theirs, EncryptionContext::None)
.unwrap();
prop_assert_eq!(ours, theirs);
}
#[test]
fn aes_ctr(
key in aes_key(),
nonce in any::<[u8; 12]>(),
counter in counter(),
msg in prop::collection::vec(any::<u8>(), 0..1100),
) {
let mut ours = msg.clone();
let result = AesCtr::new(rind_aes_algorithm(&key), &key)
.unwrap()
.apply_keystream(&nonce, counter, &mut ours);
let blocks = msg.len().div_ceil(16) as u64;
if u64::from(counter) + blocks > 1 << 32 {
prop_assert_eq!(result, Err(Error::WrongLength));
prop_assert_eq!(ours, msg);
return Ok(());
}
result.unwrap();
let mut iv = [0u8; 16];
iv[..12].copy_from_slice(&nonce);
iv[12..].copy_from_slice(&counter.to_be_bytes());
let mut theirs = msg.clone();
if !theirs.is_empty() {
EncryptingKey::ctr(aws_aes_key(&key))
.unwrap()
.less_safe_encrypt(&mut theirs, EncryptionContext::Iv128(iv.into()))
.unwrap();
}
prop_assert_eq!(ours, theirs);
}
#[test]
fn quic_header_protection_mask(
algorithm in 0..3usize,
key in any::<[u8; 32]>(),
sample in any::<[u8; 16]>(),
) {
use rind::aead::quic as ours;
let (algorithm, theirs) = [
(&ours::AES_128, &quic::AES_128),
(&ours::AES_256, &quic::AES_256),
(&ours::CHACHA20, &quic::CHACHA20),
][algorithm];
let key = &key[..algorithm.key_len()];
let mask = ours::HeaderProtectionKey::new(algorithm, key).unwrap().mask(&sample);
let wanted = quic::HeaderProtectionKey::new(theirs, key)
.unwrap()
.new_mask(&sample)
.unwrap();
prop_assert_eq!(mask, wanted);
}
#[test]
fn chacha20_keystream(
key in any::<[u8; 32]>(),
nonce in any::<[u8; 12]>(),
msg in prop::collection::vec(any::<u8>(), 0..1100),
) {
let mut ours = msg.clone();
ChaCha20::new(key).apply_keystream(&nonce, 1, &mut ours).unwrap();
let mut theirs = msg.clone();
let _tag = LessSafeKey::new(UnboundKey::new(&CHACHA20_POLY1305, &key).unwrap())
.seal_in_place_separate_tag(
Nonce::assume_unique_for_key(nonce),
Aad::empty(),
&mut theirs,
)
.unwrap();
prop_assert_eq!(ours, theirs);
}
}
}
mod hkdf {
use aws_lc_rs::{aead as their_aead, hkdf as theirs, hmac as their_hmac};
use proptest::collection::vec;
use proptest::prelude::*;
use rind::{Error, aead, hkdf, hmac};
struct Len(usize);
impl theirs::KeyType for Len {
fn len(&self) -> usize {
self.0
}
}
fn algorithms() -> [(&'static hkdf::Algorithm, theirs::Algorithm, usize); 4] {
[
(
&hkdf::legacy::HKDF_SHA1,
theirs::HKDF_SHA1_FOR_LEGACY_USE_ONLY,
20,
),
(&hkdf::HKDF_SHA256, theirs::HKDF_SHA256, 32),
(&hkdf::HKDF_SHA384, theirs::HKDF_SHA384, 48),
(&hkdf::HKDF_SHA512, theirs::HKDF_SHA512, 64),
]
}
proptest! {
#[test]
fn matches_aws_lc_rs(
algorithm in 0..4usize,
salt in vec(any::<u8>(), 0..200),
ikm in vec(any::<u8>(), 0..200),
info in vec(vec(any::<u8>(), 0..100), 0..4),
len in 0..200usize,
at_limit in any::<bool>(),
) {
let (ours, their_alg, hash_len) = algorithms()[algorithm];
let len = match at_limit {
true => 255 * hash_len + len - 100,
false => len,
};
let info: Vec<&[u8]> = info.iter().map(Vec::as_slice).collect();
let mut ours_out = vec![0u8; len];
let prk = hkdf::Salt::new(ours, &salt).extract(&ikm);
let result = prk.expand(&info, len).fill(&mut ours_out);
let prk = theirs::Salt::new(their_alg, &salt).extract(&ikm);
match prk.expand(&info, Len(len)) {
Ok(okm) => {
result.unwrap();
let mut theirs_out = vec![0u8; len];
okm.fill(&mut theirs_out).unwrap();
prop_assert_eq!(ours_out, theirs_out);
}
Err(_) => {
prop_assert_eq!(result, Err(Error::WrongLength));
prop_assert!(ours_out.iter().all(|b| *b == 0));
}
}
}
#[test]
fn keys(
algorithm in 0..4usize,
ikm in vec(any::<u8>(), 0..100),
info in vec(vec(any::<u8>(), 0..50), 0..3),
msg in vec(any::<u8>(), 0..100),
) {
let (ours_prk_alg, theirs_prk_alg, _) = algorithms()[algorithm];
let info: Vec<&[u8]> = info.iter().map(Vec::as_slice).collect();
let ours = hkdf::Salt::new(ours_prk_alg, b"salt").extract(&ikm);
let theirs = theirs::Salt::new(theirs_prk_alg, b"salt").extract(&ikm);
for (ours_alg, theirs_alg) in [
(&aead::AES_128_GCM, &their_aead::AES_128_GCM),
(&aead::AES_256_GCM, &their_aead::AES_256_GCM),
(&aead::CHACHA20_POLY1305, &their_aead::CHACHA20_POLY1305),
] {
let ours_key = aead::Key::from(ours.expand(&info, ours_alg));
let mut ours_sealed = msg.clone();
ours_key
.seal_in_place_append_tag(
aead::Nonce::assume_unique_for_key([0; 12]),
&[],
&mut ours_sealed,
)
.unwrap();
let theirs_key = their_aead::LessSafeKey::new(their_aead::UnboundKey::from(
theirs.expand(&info, theirs_alg).unwrap(),
));
let mut theirs_sealed = msg.clone();
theirs_key
.seal_in_place_append_tag(
their_aead::Nonce::assume_unique_for_key([0; 12]),
their_aead::Aad::empty(),
&mut theirs_sealed,
)
.unwrap();
prop_assert_eq!(ours_sealed, theirs_sealed);
}
for (ours_alg, theirs_alg) in [
(&hmac::legacy::HMAC_SHA1, their_hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY),
(&hmac::HMAC_SHA256, their_hmac::HMAC_SHA256),
(&hmac::HMAC_SHA384, their_hmac::HMAC_SHA384),
(&hmac::HMAC_SHA512, their_hmac::HMAC_SHA512),
] {
let ours_key = hmac::Key::from(ours.expand(&info, ours_alg));
let theirs_key = their_hmac::Key::from(theirs.expand(&info, theirs_alg).unwrap());
let ours_tag = hmac::sign(&ours_key, &msg);
let theirs_tag = their_hmac::sign(&theirs_key, &msg);
prop_assert_eq!(ours_tag.as_ref(), theirs_tag.as_ref());
}
}
}
}
mod pbkdf2 {
use core::num::NonZeroU32;
use aws_lc_rs::pbkdf2 as theirs;
use proptest::collection::vec;
use proptest::prelude::*;
use rind::{Error, pbkdf2};
proptest! {
#[test]
fn matches_aws_lc_rs(
password in vec(any::<u8>(), 0..300),
salt in vec(any::<u8>(), 0..200),
iterations in 1..20u32,
len in 1..200usize,
) {
let iterations = NonZeroU32::new(iterations).unwrap();
for (ours, theirs, hash_len) in [
(&pbkdf2::legacy::PBKDF2_HMAC_SHA1, theirs::PBKDF2_HMAC_SHA1, 20),
(&pbkdf2::PBKDF2_HMAC_SHA256, theirs::PBKDF2_HMAC_SHA256, 32),
(&pbkdf2::PBKDF2_HMAC_SHA384, theirs::PBKDF2_HMAC_SHA384, 48),
(&pbkdf2::PBKDF2_HMAC_SHA512, theirs::PBKDF2_HMAC_SHA512, 64),
] {
let mut wanted = vec![0u8; len];
theirs::derive(theirs, iterations, &salt, &password, &mut wanted);
let secret = pbkdf2::Secret::new(ours, &password);
let mut got = vec![0u8; len];
secret.derive(iterations, &salt, &mut got).unwrap();
prop_assert_eq!(&got, &wanted);
let verified = match len < hash_len {
true => Err(Error::WrongLength),
false => Ok(()),
};
prop_assert_eq!(secret.verify(iterations, &salt, &wanted), verified);
}
}
}
}
mod aead {
use aws_lc_rs::aead as theirs;
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::sample::Index;
use rind::Error;
use rind::aead::{
AES_128_GCM, AES_256_GCM, Algorithm, CHACHA20_POLY1305, Key, Nonce, RandomizedNonceKey,
};
fn check(
ours: &'static Algorithm,
theirs: &'static theirs::Algorithm,
key: &[u8],
nonce: [u8; 12],
aad: &[u8],
msg: &[u8],
flip: Option<Index>,
) -> Result<(), TestCaseError> {
let ours = Key::new(ours, key).unwrap();
let mut ours_sealed = msg.to_vec();
ours.seal_in_place_append_tag(Nonce::assume_unique_for_key(nonce), aad, &mut ours_sealed)
.unwrap();
let mut theirs_sealed = msg.to_vec();
theirs::LessSafeKey::new(theirs::UnboundKey::new(theirs, key).unwrap())
.seal_in_place_append_tag(
theirs::Nonce::assume_unique_for_key(nonce),
theirs::Aad::from(aad),
&mut theirs_sealed,
)
.unwrap();
prop_assert_eq!(&ours_sealed, &theirs_sealed);
if let Some(flip) = flip {
let index = flip.index(theirs_sealed.len());
theirs_sealed[index] ^= 1;
}
let result = ours
.open_in_place(Nonce::assume_unique_for_key(nonce), aad, &mut theirs_sealed)
.map(|plain| plain.to_vec());
if flip.is_some() {
prop_assert_eq!(result, Err(Error::DecryptFailed));
} else {
prop_assert_eq!(result, Ok(msg.to_vec()));
}
Ok(())
}
#[test]
fn aes_gcm_every_length() {
let msg: Vec<u8> = (0..2048 + 256 + 15).map(|i| (i * 7 + 3) as u8).collect();
for (ours, theirs, key) in [
(&AES_128_GCM, &theirs::AES_128_GCM, &[1u8; 16][..]),
(&AES_256_GCM, &theirs::AES_256_GCM, &[2u8; 32][..]),
] {
for len in 0..=32 * 16 + 15 {
check(ours, theirs, key, [3; 12], b"aad", &msg[..len], None).unwrap();
check(ours, theirs, key, [3; 12], &msg[..len], b"msg", None).unwrap();
}
for len in 2048 - 16..=msg.len() {
check(ours, theirs, key, [3; 12], b"aad", &msg[..len], None).unwrap();
}
}
}
proptest! {
#[test]
fn aes_gcm(
key in prop_oneof![vec(any::<u8>(), 16), vec(any::<u8>(), 32)],
nonce in any::<[u8; 12]>(),
aad in vec(any::<u8>(), 0..100),
msg in vec(any::<u8>(), 0..1100),
flip in any::<Option<Index>>(),
) {
let (ours, theirs) = if key.len() == 16 {
(&AES_128_GCM, &theirs::AES_128_GCM)
} else {
(&AES_256_GCM, &theirs::AES_256_GCM)
};
check(ours, theirs, &key, nonce, &aad, &msg, flip)?;
}
#[test]
fn chacha20_poly1305(
key in any::<[u8; 32]>(),
nonce in any::<[u8; 12]>(),
aad in vec(any::<u8>(), 0..100),
msg in vec(any::<u8>(), 0..1100),
flip in any::<Option<Index>>(),
) {
check(&CHACHA20_POLY1305, &theirs::CHACHA20_POLY1305, &key, nonce, &aad, &msg, flip)?;
}
#[test]
fn xchacha20_poly1305(
key in prop_oneof![Just([0; 32]), Just([0xff; 32]), any::<[u8; 32]>()],
aad in vec(any::<u8>(), 0..100),
msg in vec(any::<u8>(), 0..1100),
flip in any::<Option<Index>>(),
) {
let ours = RandomizedNonceKey::new(&key);
let mut ours_sealed = msg.clone();
let nonce = ours.seal_in_place_append_tag(&aad, &mut ours_sealed).unwrap();
let mut theirs_sealed = msg.clone();
theirs_sealed.extend([0; 16]);
let (out, tag) = theirs_sealed.split_at_mut(msg.len());
let mut tag_len = 0;
let sealed = unsafe {
let ctx = aws_lc_sys::EVP_AEAD_CTX_new(
aws_lc_sys::EVP_aead_xchacha20_poly1305(),
key.as_ptr(),
key.len(),
tag.len(),
);
assert!(!ctx.is_null());
let sealed = aws_lc_sys::EVP_AEAD_CTX_seal_scatter(
ctx,
out.as_mut_ptr(),
tag.as_mut_ptr(),
&mut tag_len,
tag.len(),
nonce.as_ref().as_ptr(),
nonce.as_ref().len(),
msg.as_ptr(),
msg.len(),
core::ptr::null(),
0,
aad.as_ptr(),
aad.len(),
);
aws_lc_sys::EVP_AEAD_CTX_free(ctx);
sealed
};
prop_assert_eq!((sealed, tag_len), (1, 16));
prop_assert_eq!(&ours_sealed, &theirs_sealed);
if let Some(flip) = flip {
let index = flip.index(theirs_sealed.len());
theirs_sealed[index] ^= 1;
}
let result = ours
.open_in_place(nonce, &aad, &mut theirs_sealed)
.map(|plain| plain.to_vec());
if flip.is_some() {
prop_assert_eq!(result, Err(Error::DecryptFailed));
} else {
prop_assert_eq!(result, Ok(msg));
}
}
}
}
macro_rules! ec_crosschecks {
($curve:ident, $oid:literal, $ecdh:ident, $fixed_signing:ident, $fixed:ident, $asn1_signing:ident, $asn1:ident) => {
mod $curve {
use aws_lc_rs::agreement::{self, $ecdh, EphemeralPrivateKey};
use aws_lc_rs::encoding::{AsBigEndian, AsDer};
use aws_lc_rs::rand::SystemRandom;
use aws_lc_rs::signature::{
$asn1, $asn1_signing, $fixed, $fixed_signing, EcdsaKeyPair, KeyPair,
UnparsedPublicKey,
};
use proptest::collection::vec;
use proptest::prelude::*;
use rind::signature::{self, sign, verify};
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn ecdsa(message in vec(any::<u8>(), 0..300)) {
let ours = signature::EcdsaKeyPair::generate(&sign::$fixed).unwrap();
let encoded = ours.public_key();
let public = encoded.as_ref();
let theirs_from_ours = EcdsaKeyPair::from_pkcs8(
&$fixed_signing,
ours.to_pkcs8().as_ref(),
)
.expect("aws-lc-rs rejected a key we generated");
prop_assert_eq!(theirs_from_ours.public_key().as_ref(), public);
let spki = theirs_from_ours.public_key().as_der().unwrap();
let ours_spki = ours.to_spki();
prop_assert_eq!(spki.as_ref(), ours_spki.as_ref());
let scalar = theirs_from_ours.private_key().as_be_bytes().unwrap();
let ours_scalar = ours.to_scalar();
prop_assert_eq!(ours_scalar.as_ref(), scalar.as_ref());
let reloaded = signature::EcdsaKeyPair::from_private_key_and_public_key(
&sign::$asn1,
scalar.as_ref(),
public,
)
.unwrap();
let (reloaded_pkcs8, ours_pkcs8) = (reloaded.to_pkcs8(), ours.to_pkcs8());
prop_assert_eq!(reloaded_pkcs8.as_ref(), ours_pkcs8.as_ref());
let other = EcdsaKeyPair::generate(&$fixed_signing).unwrap();
prop_assert!(signature::EcdsaKeyPair::from_private_key_and_public_key(
&sign::$asn1,
scalar.as_ref(),
other.public_key().as_ref(),
)
.is_err());
let theirs_verifier = |alg| UnparsedPublicKey::new(alg, public);
let signature = ours.sign(&message).unwrap();
theirs_verifier(&$fixed).verify(&message, signature.as_ref()).unwrap();
let signature = reloaded.sign(&message).unwrap();
theirs_verifier(&$asn1).verify(&message, signature.as_ref()).unwrap();
for (signing, ours_verification) in [
(&$fixed_signing, &verify::$fixed),
(&$asn1_signing, &verify::$asn1),
] {
let theirs = EcdsaKeyPair::generate(signing).unwrap();
let signature = theirs.sign(&SystemRandom::new(), &message).unwrap();
let verifier =
signature::PublicKey::new(ours_verification, theirs.public_key().as_ref())
.unwrap();
verifier.verify(&message, signature.as_ref()).unwrap();
prop_assert!(
verifier.verify(&[&message[..], b"!"].concat(), signature.as_ref()).is_err()
);
}
}
#[test]
fn ecdh(_case in any::<u8>()) {
let ours = rind::agreement::PrivateKey::generate(&rind::agreement::$ecdh)
.unwrap();
let ours_public = ours.public_key();
let theirs = EphemeralPrivateKey::generate(&$ecdh, &SystemRandom::new()).unwrap();
let theirs_public = theirs.compute_public_key().unwrap();
let ours_secret = ours.agree(theirs_public.as_ref()).unwrap();
let theirs_secret = agreement::agree_ephemeral(
theirs,
agreement::UnparsedPublicKey::new(&$ecdh, ours_public.as_ref()),
(),
|secret| Ok(secret.to_vec()),
)
.unwrap();
prop_assert_eq!(ours_secret.as_ref(), &theirs_secret[..]);
}
#[test]
fn public_key_forms(_case in any::<u8>()) {
let key = EcdsaKeyPair::generate(&$asn1_signing).unwrap();
let scalar = key.private_key().as_be_bytes().unwrap();
let scalar = scalar.as_ref();
let public = key.public_key();
let point = public.as_ref();
let (x, y) = point[1..].split_at(point.len() / 2);
let parity = y[y.len() - 1] & 1;
for (encoded, ours_takes, theirs_takes) in [
([&[0x02 | parity][..], x].concat(), true, true),
([&[0x03 ^ parity][..], x].concat(), false, false),
([&[0x06 | parity][..], x, y].concat(), false, true),
] {
let sec1 = crate::der(0x30, &[
&[0x02, 0x01, 0x01][..],
&crate::der(0x04, scalar),
&crate::der(0xa0, &crate::der(0x06, &hex::decode($oid).unwrap())),
&crate::der(0xa1, &crate::der(0x03, &[&[0][..], &encoded].concat())),
].concat());
let pkcs8 = crate::der(0x30, &[
&[0x02, 0x01, 0x00][..],
&crate::der(0x30, &[
&crate::der(0x06, &hex::decode("2a8648ce3d0201").unwrap())[..],
&crate::der(0x06, &hex::decode($oid).unwrap()),
].concat()),
&crate::der(0x04, &sec1),
].concat());
let ours = [
signature::EcdsaKeyPair::from_private_key_and_public_key(
&sign::$asn1, scalar, &encoded,
),
signature::EcdsaKeyPair::from_sec1(&sign::$asn1, &sec1),
signature::EcdsaKeyPair::from_pkcs8(&sign::$asn1, &pkcs8),
];
for ours in ours {
prop_assert_eq!(ours.is_ok(), ours_takes, "{:02x?}", encoded);
if let Ok(ours) = ours {
let ours = ours.public_key();
prop_assert_eq!(ours.as_ref(), point);
}
}
let theirs = [
EcdsaKeyPair::from_private_key_and_public_key(
&$asn1_signing, scalar, &encoded,
),
EcdsaKeyPair::from_private_key_der(&$asn1_signing, &sec1),
EcdsaKeyPair::from_pkcs8(&$asn1_signing, &pkcs8),
];
for theirs in theirs {
prop_assert_eq!(theirs.is_ok(), theirs_takes, "{:02x?}", encoded);
}
}
}
}
}
};
}
fn der(tag: u8, body: &[u8]) -> Vec<u8> {
let len = match body.len() {
len @ 0..=0x7f => vec![len as u8],
len @ 0x80..=0xff => vec![0x81, len as u8],
len => [&[0x82][..], &u16::try_from(len).unwrap().to_be_bytes()].concat(),
};
[&[tag][..], &len, body].concat()
}
ec_crosschecks!(
p256,
"2a8648ce3d030107",
ECDH_P256,
ECDSA_P256_SHA256_FIXED_SIGNING,
ECDSA_P256_SHA256_FIXED,
ECDSA_P256_SHA256_ASN1_SIGNING,
ECDSA_P256_SHA256_ASN1
);
ec_crosschecks!(
p384,
"2b81040022",
ECDH_P384,
ECDSA_P384_SHA384_FIXED_SIGNING,
ECDSA_P384_SHA384_FIXED,
ECDSA_P384_SHA384_ASN1_SIGNING,
ECDSA_P384_SHA384_ASN1
);
ec_crosschecks!(
p521,
"2b81040023",
ECDH_P521,
ECDSA_P521_SHA512_FIXED_SIGNING,
ECDSA_P521_SHA512_FIXED,
ECDSA_P521_SHA512_ASN1_SIGNING,
ECDSA_P521_SHA512_ASN1
);
mod ed25519 {
use aws_lc_rs::encoding::{AsBigEndian, AsDer};
use aws_lc_rs::signature::{ED25519, Ed25519KeyPair, KeyPair, UnparsedPublicKey};
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::sample::Index;
use rind::signature;
proptest! {
#[test]
fn sign_verify(
seed in any::<[u8; 32]>(),
message in vec(any::<u8>(), 0..300),
flip in any::<Option<Index>>(),
) {
let theirs = Ed25519KeyPair::from_seed_unchecked(&seed).unwrap();
let public = theirs.public_key().as_ref();
let ours = signature::Ed25519KeyPair::from_seed_and_public_key(&seed, public).unwrap();
let spki = theirs.public_key().as_der().unwrap();
let ours_spki = ours.to_spki();
prop_assert_eq!(ours_spki.as_ref(), spki.as_ref());
let (ours_pkcs8, theirs_pkcs8) = (ours.to_pkcs8(), theirs.to_pkcs8().unwrap());
prop_assert_eq!(ours_pkcs8.as_ref(), theirs_pkcs8.as_ref());
let ours_seed = ours.to_seed();
let theirs_seed = theirs.seed().unwrap().as_be_bytes().unwrap();
prop_assert_eq!(ours_seed.as_ref(), theirs_seed.as_ref());
let signature = ours.sign(&message);
let theirs_signature = theirs.sign(&message);
prop_assert_eq!(signature.as_ref(), theirs_signature.as_ref());
let mut signature = signature.as_ref().to_vec();
if let Some(flip) = flip {
let bit = flip.index(signature.len() * 8);
signature[bit / 8] ^= 1 << (bit % 8);
}
let ours_result = signature::PublicKey::new(&signature::verify::ED25519, public)
.unwrap()
.verify(&message, &signature);
let theirs_result = UnparsedPublicKey::new(&ED25519, public).verify(&message, &signature);
prop_assert_eq!(ours_result.is_ok(), flip.is_none());
prop_assert_eq!(theirs_result.is_ok(), flip.is_none());
}
#[test]
fn seed_and_public_key(seed in any::<[u8; 32]>(), other in any::<[u8; 32]>()) {
let public = Ed25519KeyPair::from_seed_unchecked(&seed).unwrap();
let public = public.public_key().as_ref();
let wrong = Ed25519KeyPair::from_seed_unchecked(&other).unwrap();
let wrong = wrong.public_key().as_ref();
signature::Ed25519KeyPair::from_seed_and_public_key(&seed, public).unwrap();
Ed25519KeyPair::from_seed_and_public_key(&seed, public).unwrap();
prop_assert_eq!(
signature::Ed25519KeyPair::from_seed_and_public_key(&seed, wrong).is_ok(),
seed == other
);
prop_assert_eq!(
Ed25519KeyPair::from_seed_and_public_key(&seed, wrong).is_ok(),
seed == other
);
}
}
}
mod rsa {
use std::sync::LazyLock;
use aws_lc_rs::encoding::AsDer;
use aws_lc_rs::rand::SystemRandom;
use aws_lc_rs::rsa::{KeyPair, KeySize};
use aws_lc_rs::signature::{self, KeyPair as _, UnparsedPublicKey};
use proptest::collection::vec;
use proptest::prelude::*;
use rind::Error;
use rind::signature::{RsaKeyPair, RsaKeySize, sign, verify};
struct Scheme {
ours_sign: &'static rind::signature::RsaSigningAlgorithm,
ours_verify: &'static rind::signature::RsaVerificationAlgorithm,
theirs_sign: &'static signature::RsaSignatureEncoding,
theirs_verify: &'static signature::RsaParameters,
deterministic: bool,
}
const SCHEMES: [Scheme; 6] = [
Scheme {
ours_sign: &sign::RSA_PKCS1_SHA256,
ours_verify: &verify::RSA_PKCS1_SHA256,
theirs_sign: &signature::RSA_PKCS1_SHA256,
theirs_verify: &signature::RSA_PKCS1_2048_8192_SHA256,
deterministic: true,
},
Scheme {
ours_sign: &sign::RSA_PKCS1_SHA384,
ours_verify: &verify::RSA_PKCS1_SHA384,
theirs_sign: &signature::RSA_PKCS1_SHA384,
theirs_verify: &signature::RSA_PKCS1_2048_8192_SHA384,
deterministic: true,
},
Scheme {
ours_sign: &sign::RSA_PKCS1_SHA512,
ours_verify: &verify::RSA_PKCS1_SHA512,
theirs_sign: &signature::RSA_PKCS1_SHA512,
theirs_verify: &signature::RSA_PKCS1_2048_8192_SHA512,
deterministic: true,
},
Scheme {
ours_sign: &sign::RSA_PSS_SHA256,
ours_verify: &verify::RSA_PSS_SHA256,
theirs_sign: &signature::RSA_PSS_SHA256,
theirs_verify: &signature::RSA_PSS_2048_8192_SHA256,
deterministic: false,
},
Scheme {
ours_sign: &sign::RSA_PSS_SHA384,
ours_verify: &verify::RSA_PSS_SHA384,
theirs_sign: &signature::RSA_PSS_SHA384,
theirs_verify: &signature::RSA_PSS_2048_8192_SHA384,
deterministic: false,
},
Scheme {
ours_sign: &sign::RSA_PSS_SHA512,
ours_verify: &verify::RSA_PSS_SHA512,
theirs_sign: &signature::RSA_PSS_SHA512,
theirs_verify: &signature::RSA_PSS_2048_8192_SHA512,
deterministic: false,
},
];
#[test]
fn key_generation_2048() {
check_key_generation(RsaKeySize::Rsa2048);
}
#[test]
fn key_generation_2048_soak() {
if std::env::var_os("SLOW_TESTS").is_some() {
for _ in 0..512 {
check_key_generation(RsaKeySize::Rsa2048);
}
}
}
#[test]
fn key_generation_3072() {
check_key_generation(RsaKeySize::Rsa3072);
}
#[test]
fn key_generation_4096() {
if std::env::var_os("SLOW_TESTS").is_some() {
check_key_generation(RsaKeySize::Rsa4096);
}
}
#[test]
fn key_generation_6144() {
if std::env::var_os("SLOW_TESTS").is_some() {
check_key_generation(RsaKeySize::Rsa6144);
}
}
#[test]
fn key_generation_8192() {
if std::env::var_os("SLOW_TESTS").is_some() {
check_key_generation(RsaKeySize::Rsa8192);
}
}
fn check_key_generation(size: RsaKeySize) {
let ours = RsaKeyPair::generate(size).unwrap();
let theirs = KeyPair::from_pkcs8(ours.to_pkcs8().as_ref())
.expect("aws-lc-rs rejected a key we generated");
let public = theirs.public_key().as_ref();
for scheme in &SCHEMES {
let signature = ours.sign(scheme.ours_sign, b"hello").unwrap();
UnparsedPublicKey::new(scheme.theirs_verify, public)
.verify(b"hello", signature.as_ref())
.unwrap();
rind::signature::PublicKey::new(scheme.ours_verify, public)
.unwrap()
.verify(b"hello", signature.as_ref())
.unwrap();
}
}
static THEIRS: LazyLock<[KeyPair; 5]> = LazyLock::new(|| {
[
KeyPair::generate(KeySize::Rsa2048).unwrap(),
KeyPair::from_pkcs8(include_bytes!("../src/high/rsa/rsa2049.pkcs8.der")).unwrap(),
KeyPair::from_pkcs8(include_bytes!("../src/high/rsa/rsa2050.pkcs8.der")).unwrap(),
KeyPair::from_pkcs8(include_bytes!("../src/high/rsa/rsa2056.pkcs8.der")).unwrap(),
KeyPair::from_pkcs8(include_bytes!("../src/high/rsa/rsa2048-e33.pkcs8.der")).unwrap(),
]
});
proptest! {
#![proptest_config(ProptestConfig::with_cases(8))]
#[test]
fn sign_verify(message in vec(any::<u8>(), 0..300)) {
for theirs in &*THEIRS {
let ours = RsaKeyPair::from_pkcs8(theirs.as_der().unwrap().as_ref()).unwrap();
let public = theirs.public_key().as_ref();
let spki = theirs.public_key().as_der().unwrap();
let ours_spki = ours.to_spki();
prop_assert_eq!(ours_spki.as_ref(), spki.as_ref());
let rng = SystemRandom::new();
let len = theirs.public_modulus_len();
for scheme in &SCHEMES {
let ours_public = ours.public_key(scheme.ours_sign);
prop_assert_eq!(ours_public.as_ref(), public);
let ours_signature = ours.sign(scheme.ours_sign, &message).unwrap();
let ours_signature = ours_signature.as_ref();
prop_assert_eq!(ours_signature.len(), len);
UnparsedPublicKey::new(scheme.theirs_verify, public)
.verify(&message, ours_signature)
.unwrap();
let mut theirs_signature = vec![0u8; len];
theirs
.sign(scheme.theirs_sign, &rng, &message, &mut theirs_signature)
.unwrap();
if scheme.deterministic {
prop_assert_eq!(ours_signature, &theirs_signature[..]);
}
let verifier = rind::signature::PublicKey::new(scheme.ours_verify, public).unwrap();
verifier.verify(&message, &theirs_signature).unwrap();
prop_assert_eq!(
verifier.verify(&[&message[..], b"!"].concat(), &theirs_signature),
Err(Error::BadSignature)
);
}
}
}
}
const VERIFICATION: [(
&rind::signature::RsaVerificationAlgorithm,
&signature::RsaParameters,
); 11] = [
(
&verify::legacy::RSA_PKCS1_1024_SHA1,
&signature::RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY,
),
(
&verify::legacy::RSA_PKCS1_1024_SHA256,
&signature::RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY,
),
(
&verify::legacy::RSA_PKCS1_1024_SHA512,
&signature::RSA_PKCS1_1024_8192_SHA512_FOR_LEGACY_USE_ONLY,
),
(
&verify::legacy::RSA_PKCS1_SHA1,
&signature::RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
),
(
&verify::RSA_PKCS1_SHA256,
&signature::RSA_PKCS1_2048_8192_SHA256,
),
(
&verify::RSA_PKCS1_SHA384,
&signature::RSA_PKCS1_2048_8192_SHA384,
),
(
&verify::RSA_PKCS1_SHA512,
&signature::RSA_PKCS1_2048_8192_SHA512,
),
(
&verify::legacy::RSA_PKCS1_3072_SHA384,
&signature::RSA_PKCS1_3072_8192_SHA384,
),
(
&verify::RSA_PSS_SHA256,
&signature::RSA_PSS_2048_8192_SHA256,
),
(
&verify::RSA_PSS_SHA384,
&signature::RSA_PSS_2048_8192_SHA384,
),
(
&verify::RSA_PSS_SHA512,
&signature::RSA_PSS_2048_8192_SHA512,
),
];
#[test]
fn verification_agrees() {
for file in [
"rsa_pkcs1_1024_sig_gen_test.json",
"rsa_pkcs1_2048_sig_gen_test.json",
] {
let path = format!("../thirdparty/wycheproof/testvectors_v1/{file}");
let tests: serde_json::Value =
serde_json::from_reader(std::fs::File::open(path).unwrap()).unwrap();
for group in tests["testGroups"].as_array().unwrap() {
let hex = |value: &serde_json::Value| hex::decode(value.as_str().unwrap()).unwrap();
let der = hex(&group["keyAsn"]);
let modulus = hex(&group["privateKey"]["modulus"]);
let e = hex(&group["privateKey"]["publicExponent"]);
let n = modulus.strip_prefix(&[0]).unwrap();
for (i, test) in group["tests"].as_array().unwrap().iter().enumerate() {
let message = hex(&test["msg"]);
let signature = hex(&test["sig"]);
let other = [&message[..], b"!"].concat();
let messages = match i {
0 => &[&message[..], &other][..],
_ => &[&message[..]],
};
for message in messages {
for (ours, theirs) in VERIFICATION {
let case = format!("{file} {i} {ours:?}");
for n in [n, &modulus] {
let ours = rind::signature::RsaPublicKeyComponents { n, e: &e }
.to_public_key(ours)
.and_then(|key| key.verify(message, &signature));
let theirs = signature::RsaPublicKeyComponents { n, e: &e }
.verify(theirs, message, &signature);
assert_eq!(ours.is_ok(), theirs.is_ok(), "{case} {ours:?}");
}
let ours = rind::signature::PublicKey::new(ours, &der)
.and_then(|key| key.verify(message, &signature));
let theirs =
UnparsedPublicKey::new(theirs, &der).verify(message, &signature);
assert_eq!(ours.is_ok(), theirs.is_ok(), "{case} {ours:?}");
}
}
}
}
}
}
#[test]
fn refuses_modulus_below_2048_bits() {
let private = include_bytes!("../src/high/rsa/rsa2047.pkcs8.der");
assert!(KeyPair::from_pkcs8(private).is_err());
assert!(matches!(
RsaKeyPair::from_pkcs8(private),
Err(Error::KeyRejected(_))
));
let public = include_bytes!("../src/high/rsa/rsa2047.pub.der");
assert!(matches!(
rind::signature::PublicKey::new(&verify::RSA_PKCS1_SHA256, public),
Err(Error::KeyRejected(_))
));
}
#[test]
fn refuses_exponent_over_33_bits() {
let private = include_bytes!("../src/high/rsa/rsa2048-e34.pkcs8.der");
assert!(KeyPair::from_pkcs8(private).is_err());
assert!(matches!(
RsaKeyPair::from_pkcs8(private),
Err(Error::KeyRejected(_))
));
let public = include_bytes!("../src/high/rsa/rsa2048-e34.pub.der");
let signature = include_bytes!("../src/high/rsa/rsa2048-e34.sig");
assert!(
UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256, public)
.verify(b"message", signature)
.is_err()
);
assert!(matches!(
rind::signature::PublicKey::new(&verify::RSA_PKCS1_SHA256, public),
Err(Error::KeyRejected(_))
));
}
}
mod mlkem768 {
use aws_lc_rs::kem::{Ciphertext, DecapsulationKey, ML_KEM_768};
use proptest::prelude::*;
use rind::kem;
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn encapsulated_by_ours(_case in any::<u8>()) {
let theirs = DecapsulationKey::generate(&ML_KEM_768).unwrap();
let theirs_public = theirs.encapsulation_key().unwrap().key_bytes().unwrap();
let (ciphertext, secret) =
kem::EncapsulationKey::new(&kem::ML_KEM_768, theirs_public.as_ref())
.unwrap()
.encapsulate()
.unwrap();
let theirs_secret = theirs
.decapsulate(Ciphertext::from(ciphertext.as_ref()))
.unwrap();
prop_assert_eq!(secret.as_ref(), theirs_secret.as_ref());
}
}
}
mod pkcs8_attributes {
use aws_lc_rs::rsa::KeyPair as TheirsRsaKeyPair;
use aws_lc_rs::signature::{self as theirs, KeyPair as _};
use rind::signature::{self, sign};
struct Key {
pkcs8: Vec<u8>,
v2: bool,
ours: fn(&[u8]) -> Option<Vec<u8>>,
theirs: fn(&[u8]) -> Option<Vec<u8>>,
}
macro_rules! ecdsa {
($ours:ident, $theirs:ident) => {
Key {
pkcs8: signature::EcdsaKeyPair::generate(&sign::$ours)
.unwrap()
.to_pkcs8()
.as_ref()
.to_vec(),
v2: false,
ours: |der| {
let key = signature::EcdsaKeyPair::from_pkcs8(&sign::$ours, der);
key.ok().map(|key| key.public_key().as_ref().to_vec())
},
theirs: |der| {
let key = theirs::EcdsaKeyPair::from_pkcs8(&theirs::$theirs, der);
key.ok().map(|key| key.public_key().as_ref().to_vec())
},
}
};
}
fn keys() -> [Key; 5] {
[
ecdsa!(ECDSA_P256_SHA256_ASN1, ECDSA_P256_SHA256_ASN1_SIGNING),
ecdsa!(ECDSA_P384_SHA384_ASN1, ECDSA_P384_SHA384_ASN1_SIGNING),
ecdsa!(ECDSA_P521_SHA512_ASN1, ECDSA_P521_SHA512_ASN1_SIGNING),
Key {
pkcs8: include_bytes!("../src/high/rsa/rsa2056.pkcs8.der").to_vec(),
v2: false,
ours: |der| {
let key = signature::RsaKeyPair::from_pkcs8(der);
key.ok()
.map(|key| key.public_key(&sign::RSA_PKCS1_SHA256).as_ref().to_vec())
},
theirs: |der| {
let key = TheirsRsaKeyPair::from_pkcs8(der);
key.ok().map(|key| key.public_key().as_ref().to_vec())
},
},
Key {
pkcs8: signature::Ed25519KeyPair::generate()
.unwrap()
.to_pkcs8()
.as_ref()
.to_vec(),
v2: true,
ours: |der| {
let key = signature::Ed25519KeyPair::from_pkcs8(der);
key.ok().map(|key| key.public_key().as_ref().to_vec())
},
theirs: |der| {
let key = theirs::Ed25519KeyPair::from_pkcs8(der);
key.ok().map(|key| key.public_key().as_ref().to_vec())
},
},
]
}
fn element(der: &[u8]) -> (usize, usize) {
match der[1] {
len @ 0..=0x7f => (2, usize::from(len)),
0x81 => (3, usize::from(der[2])),
0x82 => (4, usize::from(u16::from_be_bytes([der[2], der[3]]))),
_ => unreachable!("no test document is that long"),
}
}
fn insert(pkcs8: &[u8], after: usize, fields: &[u8]) -> Vec<u8> {
let (header, len) = element(pkcs8);
let mut body = pkcs8[header..header + len].to_vec();
let mut at = 0;
for _ in 0..after {
let (header, len) = element(&body[at..]);
at += header + len;
}
body.splice(at..at, fields.iter().copied());
let header = match body.len() {
len @ 0..=0x7f => vec![0x30, len as u8],
len @ 0x80..=0xff => vec![0x30, 0x81, len as u8],
len => [
&[0x30, 0x82][..],
&u16::try_from(len).unwrap().to_be_bytes(),
]
.concat(),
};
[header, body].concat()
}
#[test]
fn ecdsa_and_rsa_refuse_v2() {
for key in keys() {
let public = (key.ours)(&key.pkcs8).unwrap();
let v2 = match key.v2 {
true => key.pkcs8.clone(),
false => {
let field = crate::der(0x81, &[&[0][..], &public].concat());
let mut v2 = insert(&key.pkcs8, 3, &field);
let (header, _) = element(&v2);
assert_eq!(v2[header..header + 3], [0x02, 0x01, 0x00]);
v2[header + 2] = 0x01;
v2
}
};
let expected = key.v2.then_some(&public[..]);
assert_eq!((key.ours)(&v2).as_deref(), expected, "{v2:02x?}");
assert_eq!((key.theirs)(&v2).as_deref(), expected, "{v2:02x?}");
}
}
#[test]
fn attributes() {
let rfc_8410 =
hex::decode("a01f301d060a2a864886f70d01090914310f0c0d437572646c6520436861697273")
.unwrap();
let skipped: [&[u8]; 3] = [&[0xa0, 0x00], &rfc_8410, &[0xa0, 0x03, 0xff, 0xff, 0xff]];
let refused: [&[u8]; 5] = [
&[0xa0, 0x7f, 0x31, 0x00], &[0x80, 0x00], &[0xa2, 0x00], &[0xa0, 0x00, 0x05, 0x00], &[0xa0, 0x00, 0xa0, 0x00], ];
for key in keys() {
let plain = (key.ours)(&key.pkcs8).unwrap();
let check = |pkcs8: &[u8], expected: Option<&[u8]>| {
let (ours, theirs) = ((key.ours)(pkcs8), (key.theirs)(pkcs8));
assert_eq!(ours.as_deref(), expected, "{pkcs8:02x?}");
assert_eq!(theirs.as_deref(), expected, "{pkcs8:02x?}");
};
for fields in skipped {
check(&insert(&key.pkcs8, 3, fields), Some(&plain));
}
for fields in refused {
check(&insert(&key.pkcs8, 3, fields), None);
}
if key.v2 {
check(&insert(&key.pkcs8, 4, &[0xa0, 0x00]), None);
}
}
}
}