use crypto_bigint::{BoxedUint, CtAssign, CtLt};
use rsa::{RsaPrivateKey, traits::PublicKeyParts};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use zeroize::Zeroizing;
use super::{CryptoProvider, ProviderError, ProviderInputError, ProviderRng, RecoveredContentKey};
pub(super) fn recover(
provider: &dyn CryptoProvider,
key: &RsaPrivateKey,
ciphertext: &[u8],
key_len: usize,
) -> Result<RecoveredContentKey, ProviderError> {
let width = key.size();
if !matches!(key_len, 16 | 24 | 32) || width < key_len + 11 {
return Err(ProviderError::InvalidInput(
ProviderInputError::PrimitiveInitialization("XMLEnc content-key width"),
));
}
if ciphertext.len() != width {
return Err(ProviderError::InvalidInput(
ProviderInputError::PrimitiveInitialization("RSA ciphertext width"),
));
}
let mut output = Zeroizing::new(vec![0; key_len]);
provider.fill_random(&mut output)?;
let mut representative = BoxedUint::from_be_slice(ciphertext, key.n_bits_precision())
.map_err(|error| super::rustcrypto::map_rsa_error(error.into()))?;
let in_range = representative.ct_lt(key.n());
representative.ct_assign(
&BoxedUint::zero_with_precision(key.n_bits_precision()),
!in_range,
);
let decoded = Zeroizing::new(
rsa::hazmat::rsa_decrypt_and_check(key, Some(&mut ProviderRng(provider)), &representative)
.map_err(super::rustcrypto::map_rsa_error)?,
);
let encoded = Zeroizing::new(decoded.to_be_bytes());
let encoded = &encoded[encoded.len() - width..];
let delimiter = width - key_len - 1;
let mut valid = encoded[0].ct_eq(&0)
& encoded[1].ct_eq(&2)
& encoded[delimiter].ct_eq(&0)
& Choice::from(in_range.to_u8());
for byte in &encoded[2..delimiter] {
valid &= !byte.ct_eq(&0);
}
for (output, recovered) in output.iter_mut().zip(&encoded[delimiter + 1..]) {
*output = u8::conditional_select(output, recovered, valid);
}
Ok(RecoveredContentKey::recovery(output, valid))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rsa_encoding::RsaPrivateKeyEncoding as _;
fn private_key() -> RsaPrivateKey {
RsaPrivateKey::from_pkcs8_pem(include_str!(
"../../tests/fixtures/keys/rsa/rsa-2048-key.pem"
))
.expect("valid RSA fixture")
}
fn encrypt_encoded(key: &RsaPrivateKey, encoded: &[u8]) -> Vec<u8> {
let value =
BoxedUint::from_be_slice(encoded, key.n_bits_precision()).expect("modulus-width block");
let ciphertext = rsa::hazmat::rsa_encrypt(&key.to_public_key(), &value)
.expect("encoded representative below modulus")
.to_be_bytes();
ciphertext[ciphertext.len() - key.size()..].to_vec()
}
#[test]
fn fixed_width_recovery_checks_every_padding_boundary() {
let key = private_key();
let provider = super::super::RustCryptoProvider;
for key_len in [16, 24, 32] {
let delimiter = key.size() - key_len - 1;
let mut encoded = vec![0x31; key.size()];
encoded[0] = 0;
encoded[1] = 2;
encoded[delimiter] = 0;
let candidate = recover(&provider, &key, &encrypt_encoded(&key, &encoded), key_len)
.expect("valid recovery");
assert!(candidate.valid());
assert_eq!(candidate.bytes(), &encoded[delimiter + 1..]);
for (position, replacement) in
[(0, 1), (1, 1), (2, 0), (delimiter - 1, 0), (delimiter, 1)]
{
let previous = encoded[position];
encoded[position] = replacement;
let candidate = recover(&provider, &key, &encrypt_encoded(&key, &encoded), key_len)
.expect("fixed-width invalid candidate");
assert!(!candidate.valid(), "position {position}");
assert_eq!(candidate.bytes().len(), key_len);
encoded[position] = previous;
}
}
}
#[test]
fn out_of_range_representative_retains_invalidity() {
let key = private_key();
let bytes = key.n().to_be_bytes();
let bytes = &bytes[bytes.len() - key.size()..];
let candidate = recover(&super::super::RustCryptoProvider, &key, bytes, 16)
.expect("normalized invalid candidate");
assert!(!candidate.valid());
assert_eq!(candidate.bytes().len(), 16);
assert!(recover(&super::super::RustCryptoProvider, &key, &bytes[1..], 16).is_err());
assert!(recover(&super::super::RustCryptoProvider, &key, bytes, 15).is_err());
}
}