feat(security): harden gateway boundaries and usage policies

Consolidate subscription usage policy enforcement, privacy-safe persistence, and gateway security hardening into one reviewable change.

Includes bounded HTTP and execution envelopes, header and protocol guards, DNS and relay validation, authentication and secret projection hardening, secure backup/install paths, and regression coverage.
This commit is contained in:
elky
2026-09-04 03:45:52 +08:00
parent ddcbeb3ae9
commit 579f2c7cc1
1019 changed files with 190437 additions and 26080 deletions
+2
View File
@@ -1,7 +1,9 @@
mod python_fernet;
mod rsa_pkcs1_sha256;
pub use python_fernet::{
decrypt_python_fernet_ciphertext, derive_python_fernet_key, encrypt_python_fernet_plaintext,
looks_like_python_fernet_ciphertext, warm_python_fernet_secret, PythonFernetCompat,
PythonFernetError, APP_SALT_HEX, APP_SALT_SEED, DEVELOPMENT_ENCRYPTION_KEY,
};
pub use rsa_pkcs1_sha256::{rsa_pkcs1_sha256_sign, rsa_pkcs1_sha256_verify, RsaPkcs1Sha256Error};
+154 -20
View File
@@ -19,8 +19,17 @@ const SIGNING_KEY_SIZE: usize = 16;
const ENCRYPTION_KEY_SIZE: usize = 16;
const MIN_CIPHERTEXT_SIZE: usize = 16;
const MIN_TOKEN_SIZE: usize = 1 + 8 + IV_SIZE + MIN_CIPHERTEXT_SIZE + HMAC_SIZE;
const STANDARD_FERNET_TOKEN_PREFIX: &str = "gAAAA";
const WRAPPED_FERNET_TOKEN_PREFIX: &str = "Z0FBQUFB";
const PBKDF2_ITERATIONS: u32 = 100_000;
const MAX_CACHED_DERIVED_KEYS: usize = 16;
const MAX_FERNET_PLAINTEXT_BYTES: usize = 16 * 1024 * 1024;
// A Fernet token contains fixed metadata, PKCS#7 padded ciphertext and an HMAC.
// Aether's compatibility format then base64-encodes that token twice.
const MAX_FERNET_TOKEN_BYTES: usize =
1 + 8 + IV_SIZE + MAX_FERNET_PLAINTEXT_BYTES + AES_BLOCK_SIZE + HMAC_SIZE;
const AES_BLOCK_SIZE: usize = 16;
pub const APP_SALT_SEED: &[u8] = b"aether-v1";
pub const APP_SALT_HEX: &str = "8797080a7a4b45b4810e934d1af36261";
@@ -55,7 +64,7 @@ fn minimum_wrapped_token_len() -> usize {
base64_unpadded_len(base64_unpadded_len(MIN_TOKEN_SIZE))
}
#[derive(Debug, Default)]
#[derive(Default)]
struct RawFernetKeyCache {
entries: HashMap<Arc<str>, [u8; 32]>,
insertion_order: VecDeque<Arc<str>>,
@@ -99,14 +108,28 @@ pub enum PythonFernetError {
InvalidPadding,
#[error("invalid Python Fernet plaintext utf-8")]
InvalidUtf8(#[from] std::string::FromUtf8Error),
#[error("Python Fernet plaintext exceeds {limit_bytes} bytes")]
PlaintextTooLarge { limit_bytes: usize },
#[error("Python Fernet ciphertext exceeds the supported size")]
CiphertextTooLarge,
}
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct PythonFernetCompat {
signing_key: [u8; SIGNING_KEY_SIZE],
encryption_key: [u8; ENCRYPTION_KEY_SIZE],
}
impl std::fmt::Debug for PythonFernetCompat {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("PythonFernetCompat")
.field("signing_key", &"[REDACTED]")
.field("encryption_key", &"[REDACTED]")
.finish()
}
}
impl PythonFernetCompat {
pub fn from_secret(secret: &str) -> Self {
let raw_key = raw_fernet_key(secret);
@@ -115,13 +138,10 @@ impl PythonFernetCompat {
pub fn decrypt_ciphertext(&self, ciphertext: &str) -> Result<String, PythonFernetError> {
if ciphertext.is_empty() {
return Ok(String::new());
return Err(PythonFernetError::InvalidTokenStructure);
}
let outer =
decode_urlsafe(ciphertext).map_err(|_| PythonFernetError::InvalidOuterBase64)?;
let token =
decode_urlsafe_bytes(&outer).map_err(|_| PythonFernetError::InvalidInnerBase64)?;
let token = decode_wrapped_fernet_token_with_limit(ciphertext, MAX_FERNET_TOKEN_BYTES)?;
let plaintext = self.decrypt_token_bytes(token)?;
String::from_utf8(plaintext).map_err(PythonFernetError::InvalidUtf8)
}
@@ -146,6 +166,9 @@ impl PythonFernetCompat {
}
fn decrypt_token_bytes(&self, mut token: Vec<u8>) -> Result<Vec<u8>, PythonFernetError> {
if token.len() > MAX_FERNET_TOKEN_BYTES {
return Err(PythonFernetError::CiphertextTooLarge);
}
if token.len() < MIN_TOKEN_SIZE {
return Err(PythonFernetError::InvalidTokenStructure);
}
@@ -174,6 +197,11 @@ impl PythonFernetCompat {
.map_err(|_| PythonFernetError::InvalidPadding)?
.len()
};
if plaintext_len > MAX_FERNET_PLAINTEXT_BYTES {
return Err(PythonFernetError::PlaintextTooLarge {
limit_bytes: MAX_FERNET_PLAINTEXT_BYTES,
});
}
token.copy_within(ciphertext_offset..ciphertext_offset + plaintext_len, 0);
token.truncate(plaintext_len);
@@ -187,6 +215,11 @@ impl PythonFernetCompat {
iv: [u8; IV_SIZE],
) -> Result<String, PythonFernetError> {
let plaintext = plaintext.as_bytes();
if plaintext.len() > MAX_FERNET_PLAINTEXT_BYTES {
return Err(PythonFernetError::PlaintextTooLarge {
limit_bytes: MAX_FERNET_PLAINTEXT_BYTES,
});
}
let mut padded = vec![0u8; plaintext.len() + IV_SIZE];
padded[..plaintext.len()].copy_from_slice(plaintext);
let ciphertext = Aes128CbcEnc::new((&self.encryption_key).into(), (&iv).into())
@@ -227,14 +260,28 @@ pub fn decrypt_python_fernet_ciphertext(
pub fn looks_like_python_fernet_ciphertext(ciphertext: &str) -> bool {
let ciphertext = ciphertext.trim();
if ciphertext.is_empty() || ciphertext.len() < minimum_wrapped_token_len() {
if ciphertext.is_empty() {
return false;
}
let max_inner_encoded_bytes = maximum_base64_len_for_decoded_limit(MAX_FERNET_TOKEN_BYTES);
if ciphertext.len() > maximum_base64_len_for_decoded_limit(max_inner_encoded_bytes) {
return false;
}
if (ciphertext.len() >= minimum_wrapped_token_len()
&& ciphertext.starts_with(WRAPPED_FERNET_TOKEN_PREFIX))
|| (ciphertext.len() >= base64_unpadded_len(MIN_TOKEN_SIZE)
&& ciphertext.starts_with(STANDARD_FERNET_TOKEN_PREFIX))
{
return true;
}
if ciphertext.len() < minimum_wrapped_token_len() {
return false;
}
let Ok(outer) = decode_urlsafe(ciphertext) else {
let Ok(outer) = decode_urlsafe_with_limit(ciphertext, max_inner_encoded_bytes) else {
return false;
};
let Ok(inner) = decode_urlsafe_bytes(&outer) else {
let Ok(inner) = decode_urlsafe_bytes_with_limit(&outer, MAX_FERNET_TOKEN_BYTES) else {
return false;
};
@@ -287,6 +334,9 @@ fn raw_fernet_key(secret: &str) -> [u8; 32] {
}
fn decode_direct_fernet_key(secret: &str) -> Result<[u8; 32], PythonFernetError> {
if secret.len() > base64_encoded_len(32) {
return Err(PythonFernetError::InvalidTokenStructure);
}
let decoded = URL_SAFE
.decode(secret)
.or_else(|_| STANDARD.decode(secret))
@@ -298,32 +348,75 @@ fn decode_direct_fernet_key(secret: &str) -> Result<[u8; 32], PythonFernetError>
Ok(raw_key)
}
fn decode_urlsafe(value: &str) -> Result<Vec<u8>, base64::DecodeError> {
decode_with_engine_fallback(value.as_bytes())
#[derive(Debug)]
enum BoundedBase64Error {
TooLarge,
Invalid,
}
fn decode_urlsafe_bytes(value: &[u8]) -> Result<Vec<u8>, base64::DecodeError> {
decode_with_engine_fallback(value)
fn maximum_base64_len_for_decoded_limit(decoded_limit: usize) -> usize {
decoded_limit
.checked_add(2)
.and_then(|value| value.checked_div(3))
.and_then(|value| value.checked_mul(4))
.unwrap_or(usize::MAX)
}
fn decode_with_engine_fallback(value: &[u8]) -> Result<Vec<u8>, base64::DecodeError> {
fn decode_urlsafe_with_limit(
value: &str,
decoded_limit: usize,
) -> Result<Vec<u8>, BoundedBase64Error> {
decode_urlsafe_bytes_with_limit(value.as_bytes(), decoded_limit)
}
fn decode_urlsafe_bytes_with_limit(
value: &[u8],
decoded_limit: usize,
) -> Result<Vec<u8>, BoundedBase64Error> {
if value.len() > maximum_base64_len_for_decoded_limit(decoded_limit) {
return Err(BoundedBase64Error::TooLarge);
}
let mut decoded = Vec::with_capacity(decoded_len_estimate(value.len()));
match URL_SAFE.decode_vec(value, &mut decoded) {
Ok(()) => Ok(decoded),
Ok(()) if decoded.len() <= decoded_limit => Ok(decoded),
Ok(()) => Err(BoundedBase64Error::TooLarge),
Err(_) => {
decoded.clear();
URL_SAFE_NO_PAD.decode_vec(value, &mut decoded)?;
URL_SAFE_NO_PAD
.decode_vec(value, &mut decoded)
.map_err(|_| BoundedBase64Error::Invalid)?;
if decoded.len() > decoded_limit {
return Err(BoundedBase64Error::TooLarge);
}
Ok(decoded)
}
}
}
fn decode_wrapped_fernet_token_with_limit(
ciphertext: &str,
max_token_bytes: usize,
) -> Result<Vec<u8>, PythonFernetError> {
let max_inner_encoded_bytes = maximum_base64_len_for_decoded_limit(max_token_bytes);
let outer = decode_urlsafe_with_limit(ciphertext, max_inner_encoded_bytes).map_err(
|error| match error {
BoundedBase64Error::TooLarge => PythonFernetError::CiphertextTooLarge,
BoundedBase64Error::Invalid => PythonFernetError::InvalidOuterBase64,
},
)?;
decode_urlsafe_bytes_with_limit(&outer, max_token_bytes).map_err(|error| match error {
BoundedBase64Error::TooLarge => PythonFernetError::CiphertextTooLarge,
BoundedBase64Error::Invalid => PythonFernetError::InvalidInnerBase64,
})
}
#[cfg(test)]
mod tests {
use super::{
decrypt_python_fernet_ciphertext, derive_python_fernet_key,
encrypt_python_fernet_plaintext, looks_like_python_fernet_ciphertext, PythonFernetCompat,
PythonFernetError, APP_SALT_HEX, DEVELOPMENT_ENCRYPTION_KEY,
decode_wrapped_fernet_token_with_limit, decrypt_python_fernet_ciphertext,
derive_python_fernet_key, encrypt_python_fernet_plaintext,
looks_like_python_fernet_ciphertext, maximum_base64_len_for_decoded_limit,
PythonFernetCompat, PythonFernetError, APP_SALT_HEX, DEVELOPMENT_ENCRYPTION_KEY,
};
#[test]
@@ -341,6 +434,16 @@ mod tests {
assert_eq!(derive_python_fernet_key(direct_key), direct_key);
}
#[test]
fn debug_output_never_exposes_fernet_key_material() {
let crypto = PythonFernetCompat::from_secret(DEVELOPMENT_ENCRYPTION_KEY);
assert_eq!(
format!("{crypto:?}"),
"PythonFernetCompat { signing_key: \"[REDACTED]\", encryption_key: \"[REDACTED]\" }"
);
}
#[test]
fn decrypts_legacy_python_ciphertext_with_direct_fernet_key() {
let direct_key = "h0Wzfv1ieDOgsmGELpKEV7qH8QpFP+lRnPW4pI0g4/M=";
@@ -400,6 +503,8 @@ mod tests {
.expect("ciphertext should build");
ciphertext.replace_range(ciphertext.len() - 2.., "AA");
assert!(looks_like_python_fernet_ciphertext(&ciphertext));
let err = decrypt_python_fernet_ciphertext(DEVELOPMENT_ENCRYPTION_KEY, &ciphertext)
.expect_err("tampered ciphertext should fail");
assert!(matches!(
@@ -410,6 +515,35 @@ mod tests {
));
}
#[test]
fn rejects_unauthenticated_empty_ciphertext() {
assert!(matches!(
decrypt_python_fernet_ciphertext(DEVELOPMENT_ENCRYPTION_KEY, ""),
Err(PythonFernetError::InvalidTokenStructure)
));
let ciphertext = encrypt_python_fernet_plaintext(DEVELOPMENT_ENCRYPTION_KEY, "")
.expect("empty plaintext should still have an authenticated token");
assert_eq!(
decrypt_python_fernet_ciphertext(DEVELOPMENT_ENCRYPTION_KEY, &ciphertext)
.expect("authenticated empty plaintext should decrypt"),
""
);
}
#[test]
fn wrapped_fernet_decode_rejects_oversized_outer_base64_before_allocation() {
let token_limit = 3;
let inner_encoded_limit = maximum_base64_len_for_decoded_limit(token_limit);
let outer_encoded_limit = maximum_base64_len_for_decoded_limit(inner_encoded_limit);
let oversized = "A".repeat(outer_encoded_limit + 1);
assert!(matches!(
decode_wrapped_fernet_token_with_limit(&oversized, token_limit),
Err(PythonFernetError::CiphertextTooLarge)
));
}
#[test]
fn encrypt_and_decrypt_round_trip() {
let ciphertext =
@@ -0,0 +1,257 @@
use aws_lc_rs::rand::SystemRandom;
use aws_lc_rs::rsa::{KeyPair, PublicKey, RsaParameters};
use aws_lc_rs::signature::{UnparsedPublicKey, RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_SHA256};
use base64::engine::general_purpose::STANDARD;
use base64::Engine as _;
use thiserror::Error;
const MAX_RSA_KEY_INPUT_BYTES: usize = 64 * 1024;
const PRIVATE_KEY_PEM_LABELS: &[&str] = &["PRIVATE KEY", "RSA PRIVATE KEY"];
const PUBLIC_KEY_PEM_LABELS: &[&str] = &["PUBLIC KEY", "RSA PUBLIC KEY"];
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
pub enum RsaPkcs1Sha256Error {
#[error("invalid RSA private key")]
InvalidPrivateKey,
#[error("invalid RSA public key")]
InvalidPublicKey,
#[error("RSA signing failed")]
SigningFailed,
#[error("invalid RSA signature encoding")]
InvalidSignature,
}
fn decode_text_key_material(input: &[u8], labels: &[&str]) -> Option<Vec<u8>> {
let text = std::str::from_utf8(input).ok()?.trim();
if text.is_empty() {
return None;
}
let encoded = if text.starts_with("-----BEGIN ") {
labels.iter().find_map(|label| {
let header = format!("-----BEGIN {label}-----");
let footer = format!("-----END {label}-----");
text.strip_prefix(&header)?.strip_suffix(&footer)
})?
} else {
text
};
let mut compact = encoded
.bytes()
.filter(|byte| !byte.is_ascii_whitespace())
.collect::<Vec<_>>();
let decoded = (!compact.is_empty())
.then(|| STANDARD.decode(&compact).ok())
.flatten();
compact.fill(0);
decoded
}
fn parse_private_key_der(input: &[u8]) -> Result<KeyPair, RsaPkcs1Sha256Error> {
KeyPair::from_pkcs8(input)
.or_else(|_| KeyPair::from_der(input))
.map_err(|_| RsaPkcs1Sha256Error::InvalidPrivateKey)
}
fn parse_private_key(input: &[u8]) -> Result<KeyPair, RsaPkcs1Sha256Error> {
if input.is_empty() || input.len() > MAX_RSA_KEY_INPUT_BYTES {
return Err(RsaPkcs1Sha256Error::InvalidPrivateKey);
}
if let Ok(key_pair) = parse_private_key_der(input) {
return Ok(key_pair);
}
let mut der = decode_text_key_material(input, PRIVATE_KEY_PEM_LABELS)
.ok_or(RsaPkcs1Sha256Error::InvalidPrivateKey)?;
let result = parse_private_key_der(&der);
der.fill(0);
result
}
fn parse_public_key_der(input: &[u8]) -> Result<PublicKey, RsaPkcs1Sha256Error> {
let public_key =
PublicKey::from_der(input).map_err(|_| RsaPkcs1Sha256Error::InvalidPublicKey)?;
let bits = RsaParameters::public_modulus_len(public_key.as_ref())
.map_err(|_| RsaPkcs1Sha256Error::InvalidPublicKey)?;
if !(2048..=8192).contains(&bits) {
return Err(RsaPkcs1Sha256Error::InvalidPublicKey);
}
Ok(public_key)
}
fn parse_public_key(input: &[u8]) -> Result<PublicKey, RsaPkcs1Sha256Error> {
if input.is_empty() || input.len() > MAX_RSA_KEY_INPUT_BYTES {
return Err(RsaPkcs1Sha256Error::InvalidPublicKey);
}
if let Ok(public_key) = parse_public_key_der(input) {
return Ok(public_key);
}
let der = decode_text_key_material(input, PUBLIC_KEY_PEM_LABELS)
.ok_or(RsaPkcs1Sha256Error::InvalidPublicKey)?;
parse_public_key_der(&der)
}
/// Signs `message` with RSASSA-PKCS1-v1_5 and SHA-256 using AWS-LC.
///
/// The private key may be PKCS#8 or PKCS#1 DER, either PEM encoded or supplied
/// as bare standard-base64 DER. Raw DER bytes are accepted as well.
pub fn rsa_pkcs1_sha256_sign(
private_key: &[u8],
message: &[u8],
) -> Result<Vec<u8>, RsaPkcs1Sha256Error> {
let key_pair = parse_private_key(private_key)?;
let mut signature = vec![0; key_pair.public_modulus_len()];
key_pair
.sign(
&RSA_PKCS1_SHA256,
&SystemRandom::new(),
message,
&mut signature,
)
.map_err(|_| RsaPkcs1Sha256Error::SigningFailed)?;
Ok(signature)
}
/// Verifies an RSASSA-PKCS1-v1_5 SHA-256 signature using AWS-LC.
///
/// The public key may be PKCS#1 or X.509 SubjectPublicKeyInfo DER, either PEM
/// encoded or supplied as bare standard-base64 DER. Raw DER bytes are accepted
/// as well.
pub fn rsa_pkcs1_sha256_verify(
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<bool, RsaPkcs1Sha256Error> {
let public_key = parse_public_key(public_key)?;
let modulus_bits = RsaParameters::public_modulus_len(public_key.as_ref())
.map_err(|_| RsaPkcs1Sha256Error::InvalidPublicKey)?;
let signature_len = (modulus_bits as usize).div_ceil(8);
if signature.len() != signature_len {
return Err(RsaPkcs1Sha256Error::InvalidSignature);
}
Ok(
UnparsedPublicKey::new(&RSA_PKCS1_2048_8192_SHA256, public_key.as_ref())
.verify(message, signature)
.is_ok(),
)
}
#[cfg(test)]
mod tests {
use aws_lc_rs::encoding::{AsDer, Pkcs8V1Der, PublicKeyX509Der};
use aws_lc_rs::rsa::{KeyPair, KeySize};
use aws_lc_rs::signature::KeyPair as _;
use base64::engine::general_purpose::STANDARD;
use base64::Engine as _;
use super::{rsa_pkcs1_sha256_sign, rsa_pkcs1_sha256_verify, RsaPkcs1Sha256Error};
fn read_der_tlv<'a>(input: &mut &'a [u8], expected_tag: u8) -> &'a [u8] {
assert_eq!(input.first().copied(), Some(expected_tag));
let length_byte = input[1];
let (header_len, value_len) = if length_byte & 0x80 == 0 {
(2, usize::from(length_byte))
} else {
let length_bytes = usize::from(length_byte & 0x7f);
assert!((1..=4).contains(&length_bytes));
let value_len = input[2..2 + length_bytes]
.iter()
.fold(0usize, |value, byte| (value << 8) | usize::from(*byte));
(2 + length_bytes, value_len)
};
let end = header_len + value_len;
assert!(end <= input.len());
let value = &input[header_len..end];
*input = &input[end..];
value
}
fn pkcs1_private_key_from_pkcs8(pkcs8: &[u8]) -> Vec<u8> {
let mut input = pkcs8;
let mut sequence = read_der_tlv(&mut input, 0x30);
assert!(input.is_empty());
let _version = read_der_tlv(&mut sequence, 0x02);
let _algorithm = read_der_tlv(&mut sequence, 0x30);
read_der_tlv(&mut sequence, 0x04).to_vec()
}
fn pem(label: &str, der: &[u8]) -> String {
format!(
"-----BEGIN {label}-----\n{}\n-----END {label}-----",
STANDARD.encode(der)
)
}
#[test]
fn signs_and_verifies_all_supported_rsa_key_encodings() {
let key_pair = KeyPair::generate(KeySize::Rsa2048).expect("RSA key should generate");
let pkcs8 = AsDer::<Pkcs8V1Der<'static>>::as_der(&key_pair)
.expect("PKCS#8 should encode")
.as_ref()
.to_vec();
let pkcs1_private = pkcs1_private_key_from_pkcs8(&pkcs8);
let pkcs1_public = key_pair.public_key().as_ref().to_vec();
let spki_public = AsDer::<PublicKeyX509Der<'static>>::as_der(key_pair.public_key())
.expect("SPKI should encode")
.as_ref()
.to_vec();
let private_inputs = [
pkcs8.clone(),
pkcs1_private.clone(),
pem("PRIVATE KEY", &pkcs8).into_bytes(),
pem("RSA PRIVATE KEY", &pkcs1_private).into_bytes(),
STANDARD.encode(&pkcs8).into_bytes(),
STANDARD.encode(&pkcs1_private).into_bytes(),
];
let public_inputs = [
pkcs1_public.clone(),
spki_public.clone(),
pem("RSA PUBLIC KEY", &pkcs1_public).into_bytes(),
pem("PUBLIC KEY", &spki_public).into_bytes(),
STANDARD.encode(&pkcs1_public).into_bytes(),
STANDARD.encode(&spki_public).into_bytes(),
];
let message = b"Aether RSA-SHA256 compatibility vector";
let expected =
rsa_pkcs1_sha256_sign(&private_inputs[0], message).expect("PKCS#8 DER should sign");
assert_eq!(expected.len(), 256);
for private_key in private_inputs {
assert_eq!(
rsa_pkcs1_sha256_sign(&private_key, message).expect("key format should sign"),
expected,
"PKCS#1 v1.5 output must remain deterministic across encodings"
);
}
for public_key in public_inputs {
assert!(rsa_pkcs1_sha256_verify(&public_key, message, &expected)
.expect("key format should verify"));
}
assert!(
!rsa_pkcs1_sha256_verify(&pkcs1_public, b"tampered", &expected)
.expect("valid key with invalid signature should return false")
);
assert_eq!(
rsa_pkcs1_sha256_verify(&pkcs1_public, message, &expected[..255]),
Err(RsaPkcs1Sha256Error::InvalidSignature)
);
}
#[test]
fn rejects_malformed_or_unsupported_rsa_keys() {
assert_eq!(
rsa_pkcs1_sha256_sign(b"not-a-key", b"message"),
Err(RsaPkcs1Sha256Error::InvalidPrivateKey)
);
assert_eq!(
rsa_pkcs1_sha256_verify(b"not-a-key", b"message", b"signature"),
Err(RsaPkcs1Sha256Error::InvalidPublicKey)
);
assert_eq!(
rsa_pkcs1_sha256_sign(&vec![b'A'; 64 * 1024 + 1], b"message"),
Err(RsaPkcs1Sha256Error::InvalidPrivateKey)
);
}
}