use rsa::BoxedUint;
use rsa::traits::PrivateKeyParts;
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroizing;
use super::jwk_build;
use super::pkcs1::{self, Pkcs1Key, RsaPrivateFields, RsaPublicParts};
use super::pkcs8;
use super::spki;
use super::types::{AlgId, Pkcs8Parts, SpkiParts};
use crate::error::{Error, Result};
use crate::jwk::{Jwk, RsaKey, RsaPrivateParts};
const PRIVATE_MISMATCH: &str = "RSA private key does not match n and e";
const CRT_MISMATCH: &str = "RSA private key dp, dq or qi does not match d, p and q";
const MEMBER_SIZE: &str = "RSA key member is empty or too long";
const EXPONENT_TOO_LARGE: &str = "RSA exponent is larger than 2^33 - 1";
pub(crate) fn from_spki(parts: SpkiParts<'_>) -> Result<Jwk> {
if parts.alg != AlgId::RsaEncryption {
return Err(Error::InvalidKey("the key is not an RSA key"));
}
public_jwk(pkcs1::decode_public(parts.subject_public_key)?)
}
pub(crate) fn from_pkcs1(der: &[u8]) -> Result<Jwk> {
match pkcs1::decode(der)? {
Pkcs1Key::Public(key) => public_jwk(key),
Pkcs1Key::Private(key) => private_jwk(&key),
}
}
pub(crate) fn from_pkcs1_public(der: &[u8]) -> Result<Jwk> {
public_jwk(pkcs1::decode_public(der)?)
}
pub(crate) fn from_pkcs1_private(der: &[u8]) -> Result<Jwk> {
private_jwk(&pkcs1::decode_private(der)?)
}
pub(crate) fn from_pkcs8(parts: Pkcs8Parts<'_>) -> Result<Jwk> {
if parts.alg != AlgId::RsaEncryption {
return Err(Error::InvalidKey("the key is not an RSA key"));
}
if parts.public_key.is_some() {
return Err(Error::UnsupportedAlgorithm);
}
private_jwk(&pkcs1::decode_private(parts.private_key)?)
}
fn public_jwk(key: RsaPublicParts<'_>) -> Result<Jwk> {
jwk_build::rsa_public_jwk(key.n, key.e, None)
}
fn private_jwk(key: &RsaPrivateFields<'_>) -> Result<Jwk> {
jwk_build::rsa_private_jwk(key, None)
}
pub(crate) fn to_spki(key: &RsaKey) -> Result<Zeroizing<Vec<u8>>> {
let rsa_public_key = to_pkcs1(key)?;
spki::encode(&SpkiParts {
alg: AlgId::RsaEncryption,
subject_public_key: &rsa_public_key,
})
}
pub(crate) fn to_pkcs1(key: &RsaKey) -> Result<Zeroizing<Vec<u8>>> {
pkcs1::encode_public(key.n(), key.e())
}
pub(crate) fn to_pkcs8(key: &RsaKey) -> Result<Zeroizing<Vec<u8>>> {
let private = key
.private()
.ok_or(Error::InvalidKey("private key required"))?;
let inner = pkcs1::encode_private(&RsaPrivateFields {
n: key.n(),
e: key.e(),
d: &private.d,
p: &private.p,
q: &private.q,
dp: &private.dp,
dq: &private.dq,
qi: &private.qi,
})?;
pkcs8::encode(&Pkcs8Parts {
version: 0,
alg: AlgId::RsaEncryption,
private_key: &inner,
public_key: None,
})
}
pub(crate) fn private_key(key: &RsaKey) -> Result<rsa::PrivateKey> {
let private = key.private().ok_or(Error::KeyAlgorithmMismatch)?;
from_components(key.n(), key.e(), private)
}
pub(crate) fn check_private(n: &[u8], e: &[u8], private: &RsaPrivateParts) -> Result<()> {
let key = from_components(n, e, private)?;
let (Some(dp), Some(dq), Some(qi)) = (key.dp(), key.dq(), key.crt_coefficient()) else {
return Err(Error::InvalidKey(PRIVATE_MISMATCH));
};
let qi = Zeroizing::new(qi);
let equal = same_value(&private.dp, &Zeroizing::new(dp.to_be_bytes()))
& same_value(&private.dq, &Zeroizing::new(dq.to_be_bytes()))
& same_value(&private.qi, &Zeroizing::new(qi.to_be_bytes()));
if bool::from(equal) {
Ok(())
} else {
Err(Error::InvalidKey(CRT_MISMATCH))
}
}
fn from_components(n: &[u8], e: &[u8], private: &RsaPrivateParts) -> Result<rsa::PrivateKey> {
let n = uint(n)?;
let e = uint(e)?;
let d = uint(&private.d)?;
let p = uint(&private.p)?;
let q = uint(&private.q)?;
rsa::PrivateKey::from_components(n, e, d, vec![p, q]).map_err(|err| {
Error::InvalidKey(match err {
rsa::errors::Error::PublicExponentTooLarge => EXPONENT_TOO_LARGE,
_ => PRIVATE_MISMATCH,
})
})
}
fn same_value(minimal: &[u8], fixed: &[u8]) -> Choice {
let Some(pad) = fixed.len().checked_sub(minimal.len()) else {
return Choice::from(0);
};
let (zeros, digits) = fixed.split_at(pad);
let zeros_ok = zeros
.iter()
.fold(Choice::from(1), |acc, b| acc & b.ct_eq(&0));
zeros_ok & digits.ct_eq(minimal)
}
fn uint(bytes: &[u8]) -> Result<BoxedUint> {
let bits = u32::try_from(bytes.len())
.ok()
.and_then(|len| len.checked_mul(8))
.filter(|&bits| bits > 0)
.ok_or(Error::InvalidKey(MEMBER_SIZE))?;
BoxedUint::from_be_slice(bytes, bits).map_err(|_| Error::InvalidKey(MEMBER_SIZE))
}
#[cfg(test)]
mod tests {
use super::super::der::fixtures;
use super::super::types::{AlgId, Label};
use super::super::{oid, pem, pkcs8};
use super::*;
use crate::jwk::KeyMaterial;
fn jwk_fixture(dir: &str, file: &str) -> Jwk {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/data/keys")
.join(dir)
.join(file);
let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
Jwk::from_json(&bytes).unwrap_or_else(|e| panic!("{file}: {e:?}"))
}
fn pem_text(dir: &str, file: &str) -> String {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/data/keys")
.join(dir)
.join(file);
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()))
}
fn rsa(jwk: &Jwk) -> &RsaKey {
let KeyMaterial::Rsa(key) = jwk.material() else {
panic!("an RSA key")
};
key
}
fn assert_same_key(actual: &Jwk, expected: &Jwk, what: &str) {
assert_eq!(*actual.to_json(), *expected.to_json(), "{what}");
}
fn import_spki(der: &[u8]) -> Result<Jwk> {
from_spki(spki::decode(der)?)
}
fn positive() -> Vec<(&'static str, String, bool, String)> {
let mut out = Vec::new();
for (dir, prefix) in [("openssl", "rsa"), ("rfc", "rfc7517-a1-rsa")] {
for file in fixtures::files(dir, |name| name.starts_with(prefix)) {
let stem = file.rsplit_once('.').unwrap().0.to_owned();
let spki = stem.ends_with("-spki");
assert!(spki || stem.ends_with("-pkcs1-pub"), "{file}");
let expected = if dir == "openssl" {
format!("expected/{stem}.jwk.json")
} else {
format!("{stem}.jwk.json")
};
out.push((dir, file, spki, expected));
}
}
assert_eq!(out.len(), 11);
out
}
fn import(der: &[u8], spki: bool) -> Result<Jwk> {
if spki {
import_spki(der)
} else {
from_pkcs1(der)
}
}
#[test]
fn rfc7517_a1_imports_to_the_rfc_jwk_and_exports_back() {
let expected = jwk_fixture("rfc", "rfc7517-a1-rsa-spki.jwk.json");
assert_eq!(rsa(&expected).e(), &[0x01, 0x00, 0x01]);
let spki_der = fixtures::load("rfc", "rfc7517-a1-rsa-spki.der");
let pkcs1_der = fixtures::load("rfc", "rfc7517-a1-rsa-pkcs1-pub.der");
let from_spki_der = import_spki(&spki_der).unwrap();
let from_pkcs1_der = from_pkcs1(&pkcs1_der).unwrap();
assert_same_key(&from_spki_der, &expected, "SPKI");
assert_same_key(&from_pkcs1_der, &expected, "PKCS#1");
assert_eq!(rsa(&from_spki_der).modulus_bits(), 2048);
for jwk in [&from_spki_der, &from_pkcs1_der] {
assert_eq!(&to_spki(rsa(jwk)).unwrap()[..], &spki_der[..]);
assert_eq!(&to_pkcs1(rsa(jwk)).unwrap()[..], &pkcs1_der[..]);
}
}
#[test]
fn every_positive_fixture_imports_and_exports_byte_for_byte() {
for (dir, file, spki, expected) in positive() {
let der = fixtures::load(dir, &file);
let jwk = import(&der, spki).unwrap_or_else(|e| panic!("{file}: {e:?}"));
assert_same_key(&jwk, &jwk_fixture(dir, &expected), &file);
assert!(jwk.is_public() && jwk.alg().is_none(), "{file}");
let (exported, label) = if spki {
(to_spki(rsa(&jwk)).unwrap(), Label::PublicKey)
} else {
(to_pkcs1(rsa(&jwk)).unwrap(), Label::RsaPublic)
};
assert_eq!(&exported[..], &der[..], "{file}");
if file.ends_with(".pem") {
let text = pem::encode(label, &exported).unwrap();
assert_eq!(text.as_str(), pem_text(dir, &file), "{file}");
}
}
}
#[test]
fn spki_and_pkcs1_exports_carry_the_same_rsa_public_key() {
for (dir, file, spki, _) in positive() {
let der = fixtures::load(dir, &file);
let jwk = import(&der, spki).unwrap();
let as_spki = to_spki(rsa(&jwk)).unwrap();
let as_pkcs1 = to_pkcs1(rsa(&jwk)).unwrap();
let parts = spki::decode(&as_spki).unwrap();
assert_eq!(parts.alg, AlgId::RsaEncryption, "{file}");
assert_eq!(parts.subject_public_key, &as_pkcs1[..], "{file}");
let other = if spki {
file.replace("-spki", "-pkcs1-pub")
} else {
file.replace("-pkcs1-pub", "-spki")
};
if fixtures::files(dir, |name| name == other).len() == 1 {
let other_der = fixtures::load(dir, &other);
let exported = if spki { &as_pkcs1 } else { &as_spki };
assert_eq!(&exported[..], &other_der[..], "{file} as {other}");
}
}
}
#[test]
fn the_crate_pem_decoder_feeds_the_same_keys() {
for (dir, file, spki, expected) in positive() {
if !file.ends_with(".pem") {
continue;
}
let (label, der) = pem::decode(&pem_text(dir, &file)).unwrap();
assert_eq!(
label,
if spki {
Label::PublicKey
} else {
Label::RsaPublic
}
);
let jwk = import(&der, spki).unwrap();
assert_same_key(&jwk, &jwk_fixture(dir, &expected), &file);
}
}
fn is_invalid<T>(result: &Result<T>) -> bool {
matches!(result, Err(Error::InvalidKey(_)))
}
fn is_unsupported<T>(result: &Result<T>) -> bool {
matches!(result, Err(Error::UnsupportedAlgorithm))
}
fn detail<T>(result: Result<T>) -> &'static str {
match result {
Err(Error::InvalidKey(detail)) => detail,
Err(other) => panic!("expected InvalidKey, got {other:?}"),
Ok(_) => panic!("expected InvalidKey, got a key"),
}
}
#[test]
fn malformed_rsa_public_key_vectors_are_invalid() {
for id in ["N60", "N61", "N62", "N66"] {
assert!(is_invalid(&import_spki(&fixtures::negative(id))), "{id}");
}
assert_eq!(
detail(import_spki(&fixtures::negative("N63"))),
"RSA exponent must be odd and greater than 1"
);
assert_eq!(
detail(import_spki(&fixtures::negative("N64"))),
"RSA modulus is shorter than 2048 bits"
);
assert!(is_invalid(&from_pkcs1(&fixtures::negative("N65"))));
}
#[test]
fn rsa1024_public_keys_are_refused_by_the_jwk_validation() {
for (file, spki) in [
("reject-rsa1024-spki.pem", true),
("reject-rsa1024-pkcs1-pub.pem", false),
] {
let oracle = fixtures::load("openssl", file);
let (_, ours) = pem::decode(&pem_text("openssl", file)).unwrap();
assert_eq!(&ours[..], &oracle[..], "{file}");
assert_eq!(
detail(import(&ours, spki)),
"RSA modulus is shorter than 2048 bits",
"{file}"
);
}
}
#[test]
fn from_spki_refuses_a_key_of_another_type() {
for file in ["ec-p256-spki.der", "ed25519-spki.pem", "x25519-spki.pem"] {
let der = fixtures::load("openssl", file);
let parts = spki::decode(&der).unwrap();
assert_eq!(
detail(from_spki(parts)),
"the key is not an RSA key",
"{file}"
);
}
}
fn import_pem(text: &str) -> Result<Jwk> {
let (label, der) = pem::decode(text)?;
match label {
Label::PrivateKey => from_pkcs8(pkcs8::decode(&der)?),
Label::PublicKey => import_spki(&der),
Label::RsaPublic => from_pkcs1_public(&der),
Label::RsaPrivate => from_pkcs1_private(&der),
other => panic!("pem::decode returned {other:?}"),
}
}
fn rsa_private_fixtures() -> Vec<(&'static str, String, bool, bool)> {
let mut out = Vec::new();
for bits in [1024, 2048, 3072, 4096] {
for ext in ["pem", "der"] {
let file = format!("reject-rsa{bits}-private-pkcs8.{ext}");
if fixtures::files("openssl", |name| name == file).len() == 1 {
out.push(("openssl", file, true, true));
}
}
}
for (file, pkcs8, oracle) in [
("reject-rsa2048-private-pkcs1.pem", false, true),
("reject-rsa2048-private-pkcs1.der", false, true),
("reject-rsa2048-3prime-private-pkcs8.pem", true, true),
("reject-rsa2048-3prime-private-pkcs1.pem", false, true),
("reject-rsa2048-pkcs8-encrypted.pem", true, true),
("reject-rsa2048-pkcs1-legacy-encrypted.pem", false, false),
] {
out.push(("openssl", file.to_owned(), pkcs8, oracle));
}
for bits in [1024, 2048, 4096] {
let file = format!("reject-rfc9500-testRSA{bits}-private-pkcs1.pem");
out.push(("rfc", file, false, true));
}
assert_eq!(out.len(), 14);
out
}
fn adr_0067_outcome(file: &str) -> std::result::Result<(), Error> {
if file.contains("encrypted") || file.contains("3prime") {
Err(Error::UnsupportedAlgorithm)
} else if file.contains("1024") {
Err(Error::InvalidKey("RSA modulus is shorter than 2048 bits"))
} else {
Ok(())
}
}
#[test]
fn every_rsa_private_key_fixture_has_its_adr_0067_outcome() {
for (dir, file, pkcs8, oracle) in rsa_private_fixtures() {
let expected = adr_0067_outcome(&file);
let mut imported = Vec::new();
if file.ends_with(".pem") {
let result = import_pem(&pem_text(dir, &file));
assert_eq!(
result.as_ref().map(drop).map_err(Clone::clone),
expected,
"{file}"
);
imported.extend(result.ok());
}
if oracle {
let der = fixtures::load(dir, &file);
let result = if pkcs8 {
pkcs8::decode(&der).and_then(from_pkcs8)
} else {
from_pkcs1(&der)
};
assert_eq!(
result.as_ref().map(drop).map_err(Clone::clone),
expected,
"{file} as DER"
);
imported.extend(result.ok());
}
for jwk in &imported {
assert!(!jwk.is_public() && jwk.alg().is_none(), "{file}");
assert_eq!(*jwk.to_json(), *imported[0].to_json(), "{file}");
}
}
}
#[test]
fn pkcs8_and_pkcs1_private_keys_round_trip_byte_for_byte() {
let pkcs8_der = fixtures::load("openssl", "reject-rsa2048-private-pkcs8.der");
let pkcs1_der = fixtures::load("openssl", "reject-rsa2048-private-pkcs1.der");
let from_8 = from_pkcs8(pkcs8::decode(&pkcs8_der).unwrap()).unwrap();
let from_1 = from_pkcs1(&pkcs1_der).unwrap();
assert_eq!(*from_8.to_json(), *from_1.to_json());
let exported = to_pkcs8(rsa(&from_1)).unwrap();
assert_eq!(&exported[..], &pkcs8_der[..]);
let inner = pkcs8::decode(&exported).unwrap();
assert_eq!(inner.private_key, &pkcs1_der[..]);
let public = rsa(&from_1);
assert!(from_pkcs1(&to_pkcs1(public).unwrap()).unwrap().is_public());
assert!(import_spki(&to_spki(public).unwrap()).unwrap().is_public());
let public_jwk = from_1.to_public().unwrap();
assert_eq!(
to_pkcs8(rsa(&public_jwk)).err(),
Some(Error::InvalidKey("private key required"))
);
}
#[test]
fn a_pkcs8_rsa_key_with_a_malformed_private_key_is_invalid() {
let mut keys = vec![fixtures::negative("N67")];
for (dir, file, pkcs8, oracle) in rsa_private_fixtures() {
if pkcs8 && oracle && !file.contains("encrypted") {
keys.push(fixtures::load(dir, &file));
}
}
assert_eq!(keys.len(), 7);
for (index, mut der) in keys.into_iter().enumerate() {
if index > 0 {
let at = der
.windows(oid::ALG_RSA_ENCRYPTION.len())
.position(|window| window == oid::ALG_RSA_ENCRYPTION)
.unwrap()
+ oid::ALG_RSA_ENCRYPTION.len();
assert_eq!(der[at], 0x04, "privateKey is an OCTET STRING");
der[at] = 0x1F;
}
assert!(
is_invalid(&Jwk::from_der(&der, super::super::KeyFormat::Pkcs8)),
"key {index}"
);
}
}
#[test]
fn rsa_pss_keys_are_unsupported() {
let result = import_pem(&pem_text("openssl", "reject-rsapss2048-spki.pem"));
assert!(is_unsupported(&result), "{:?}", result.err());
let result = import_pem(&pem_text("openssl", "reject-rsapss2048-private-pkcs8.pem"));
assert!(is_unsupported(&result), "{:?}", result.err());
}
#[test]
fn a_private_key_with_an_exponent_over_2_pow_33_gives_the_exponent_detail() {
let text = String::from_utf8(
std::fs::read(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/data/keys/rfc/rfc7515-a2-rsa-private.jwk.json"),
)
.unwrap(),
)
.unwrap();
assert!(text.contains(r#""e":"AQAB""#));
let large = text.replace(r#""e":"AQAB""#, r#""e":"BAAAAAE""#);
assert_eq!(
Jwk::from_json(large.as_bytes()).err(),
Some(Error::InvalidKey(EXPONENT_TOO_LARGE))
);
assert_eq!(EXPONENT_TOO_LARGE, "RSA exponent is larger than 2^33 - 1");
let wrong = text.replace(r#""e":"AQAB""#, r#""e":"Aw""#);
assert_eq!(
Jwk::from_json(wrong.as_bytes()).err(),
Some(Error::InvalidKey(PRIVATE_MISMATCH))
);
}
#[test]
fn errors_carry_no_key_bytes() {
let marker = [0xA5u8; 128];
let mut even = [0xA5u8; 256];
even[255] = 0xA4;
let mut private = vec![0x30, 0x81, 0x00, 0x02, 0x01, 0x00];
for _ in 0..8 {
private.extend_from_slice(&[0x02, 0x10]);
private.extend_from_slice(&[0xA5; 16]);
}
private[2] = u8::try_from(private.len() - 3).unwrap();
let spki_of = |n: &[u8]| {
let pkcs1 = pkcs1::encode_public(n, &[0x01, 0x00, 0x01]).unwrap();
spki::encode(&SpkiParts {
alg: AlgId::RsaEncryption,
subject_public_key: &pkcs1,
})
.unwrap()
};
let mut positive = vec![0x30, 0x81, 0x00, 0x02, 0x01, 0x00];
for _ in 0..8 {
positive.extend_from_slice(&[0x02, 0x11, 0x00]);
positive.extend_from_slice(&[0xA5; 16]);
}
positive[2] = u8::try_from(positive.len() - 3).unwrap();
let errors = [
import_spki(&spki_of(&marker)).unwrap_err(),
import_spki(&spki_of(&even)).unwrap_err(),
from_pkcs1(&pkcs1::encode_public(&marker, &[0xA5]).unwrap()).unwrap_err(),
from_pkcs1(&private).unwrap_err(),
from_pkcs1(&positive).unwrap_err(),
];
assert_eq!(errors[3], Error::InvalidKey("DER INTEGER negative"));
assert!(matches!(errors[4], Error::InvalidKey(_)));
for error in errors {
for text in [
format!("{error:?}"),
format!("{error}"),
format!("{error:#?}"),
] {
for needle in ["a5", "A5", "165", "paWl"] {
assert!(!text.contains(needle), "{text}");
}
}
}
}
}
#[cfg(test)]
mod adversarial {
use super::super::types::{AlgId, SpkiParts};
use super::super::{pem, spki};
use super::*;
fn int(contents: &[u8]) -> Vec<u8> {
let mut out = vec![0x02];
push_len(&mut out, contents.len());
out.extend_from_slice(contents);
out
}
fn push_len(out: &mut Vec<u8>, len: usize) {
match len {
0..=0x7F => out.push(u8::try_from(len).unwrap()),
0x80..=0xFF => out.extend_from_slice(&[0x81, u8::try_from(len).unwrap()]),
_ => {
let [high, low] = u16::try_from(len).unwrap().to_be_bytes();
out.extend_from_slice(&[0x82, high, low]);
}
}
}
fn raw_public(n: &[u8], e: &[u8]) -> Vec<u8> {
let mut body = int(n);
body.extend_from_slice(&int(e));
let mut out = vec![0x30];
push_len(&mut out, body.len());
out.extend_from_slice(&body);
out
}
fn detail<T>(result: Result<T>) -> &'static str {
match result {
Err(Error::InvalidKey(detail)) => detail,
Err(other) => panic!("expected InvalidKey, got {other:?}"),
Ok(_) => panic!("expected InvalidKey, got a key"),
}
}
fn n2048() -> Vec<u8> {
let mut n = vec![0xC3; 256];
n[255] = 0xC1;
n
}
fn with_sign_octet(n: &[u8]) -> Vec<u8> {
let mut out = vec![0x00];
out.extend_from_slice(n);
out
}
#[test]
fn modulus_sign_octet_is_required_exactly_when_the_top_bit_is_set() {
let n = n2048();
let key = from_pkcs1(&raw_public(&with_sign_octet(&n), &[0x01, 0x00, 0x01])).unwrap();
assert_eq!(rsa(&key).n(), &n[..], "the sign octet is not part of n");
assert_eq!(rsa(&key).modulus_bits(), 2048);
assert!(matches!(
from_pkcs1(&raw_public(&n, &[0x01, 0x00, 0x01])),
Err(Error::InvalidKey(_))
));
let mut small_top = vec![0x7F; 256];
small_top[255] = 0x01;
assert!(matches!(
from_pkcs1(&raw_public(&with_sign_octet(&small_top), &[0x03])),
Err(Error::InvalidKey(_))
));
}
fn rsa(jwk: &Jwk) -> &RsaKey {
let crate::jwk::KeyMaterial::Rsa(key) = jwk.material() else {
panic!("an RSA key")
};
key
}
#[test]
fn exponent_with_a_leading_zero_is_non_minimal() {
let n = with_sign_octet(&n2048());
for e in [
&[0x00, 0x03][..],
&[0x00, 0x01, 0x00, 0x01][..],
&[0x00, 0x00, 0x03][..],
] {
assert!(
matches!(from_pkcs1(&raw_public(&n, e)), Err(Error::InvalidKey(_))),
"{e:?}"
);
}
let key = from_pkcs1(&raw_public(&n, &[0x00, 0x81])).unwrap();
assert_eq!(rsa(&key).e(), &[0x81]);
assert_eq!(
&to_pkcs1(rsa(&key)).unwrap()[..],
&raw_public(&n, &[0x00, 0x81])[..]
);
}
#[test]
fn a_2047_bit_modulus_is_refused_and_2048_is_accepted() {
let mut n = vec![0x7F; 256];
n[255] = 0xFF;
assert_eq!(
detail(from_pkcs1(&raw_public(&n, &[0x01, 0x00, 0x01]))),
"RSA modulus is shorter than 2048 bits"
);
let mut n = vec![0x80; 256];
n[255] = 0x01;
let key = from_pkcs1(&raw_public(&with_sign_octet(&n), &[0x01, 0x00, 0x01])).unwrap();
assert_eq!(rsa(&key).modulus_bits(), 2048);
}
#[test]
fn odd_and_even_n_and_e() {
let mut n = n2048();
let e = [0x01, 0x00, 0x01];
assert!(from_pkcs1(&raw_public(&with_sign_octet(&n), &e)).is_ok());
n[255] = 0xC0;
assert_eq!(
detail(from_pkcs1(&raw_public(&with_sign_octet(&n), &e))),
"RSA modulus must be odd"
);
let n = with_sign_octet(&n2048());
for even in [&[0x02][..], &[0x01, 0x00][..], &[0x01, 0x00, 0x00][..]] {
assert_eq!(
detail(from_pkcs1(&raw_public(&n, even))),
"RSA exponent must be odd and greater than 1",
"{even:?}"
);
}
for bad in [&[0x01][..], &[0x00][..]] {
assert!(matches!(
from_pkcs1(&raw_public(&n, bad)),
Err(Error::InvalidKey(_))
));
}
assert!(from_pkcs1(&raw_public(&n, &[0x03])).is_ok());
}
#[test]
fn exponent_not_less_than_the_modulus_is_refused() {
let n = n2048();
let signed = with_sign_octet(&n);
assert_eq!(
detail(from_pkcs1(&raw_public(&signed, &signed))),
"RSA exponent must be less than the modulus"
);
let mut longer = vec![0x01];
longer.extend_from_slice(&n);
assert_eq!(
detail(from_pkcs1(&raw_public(&signed, &longer))),
"RSA exponent must be less than the modulus"
);
let mut below = n.clone();
below[255] -= 2;
assert!(from_pkcs1(&raw_public(&signed, &with_sign_octet(&below))).is_ok());
}
#[test]
fn modulus_above_8192_bits_is_refused_at_import() {
let e = [0x01, 0x00, 0x01];
for bytes in [1025usize, 1100] {
let mut n = vec![0x80; bytes];
n[bytes - 1] = 0x01;
let der = raw_public(&with_sign_octet(&n), &e);
assert_eq!(
detail(from_pkcs1(&der)),
"RSA modulus is longer than 8192 bits",
"{bytes}"
);
let spki_der = spki::encode(&SpkiParts {
alg: AlgId::RsaEncryption,
subject_public_key: &der,
})
.unwrap();
assert_eq!(
detail(import_spki_der(&spki_der)),
"RSA modulus is longer than 8192 bits",
"{bytes}"
);
}
let mut n = vec![0x55; 1025];
n[0] = 0x01;
n[1024] = 0x01;
assert_eq!(
detail(from_pkcs1(&raw_public(&n, &e))),
"RSA modulus is longer than 8192 bits"
);
let mut n = vec![0x80; 1024];
n[1023] = 0x01;
let der = raw_public(&with_sign_octet(&n), &e);
let key = from_pkcs1(&der).unwrap();
assert_eq!(rsa(&key).modulus_bits(), 8192);
assert_eq!(&to_pkcs1(rsa(&key)).unwrap()[..], &der[..]);
let spki_der = to_spki(rsa(&key)).unwrap();
assert!(import_spki_der(&spki_der).is_ok());
}
fn import_spki_der(der: &[u8]) -> Result<Jwk> {
from_spki(spki::decode(der)?)
}
#[test]
fn spki_with_a_trailing_byte_or_wrong_bit_string_is_invalid() {
let pkcs1 = pkcs1::encode_public(&n2048(), &[0x01, 0x00, 0x01]).unwrap();
let der = spki::encode(&SpkiParts {
alg: AlgId::RsaEncryption,
subject_public_key: &pkcs1,
})
.unwrap();
assert!(import_spki_der(&der).is_ok());
let mut trailing = der.to_vec();
trailing.push(0);
assert!(import_spki_der(&trailing).is_err());
let mut truncated = der.to_vec();
truncated.pop();
assert!(import_spki_der(&truncated).is_err());
}
#[test]
fn encrypted_pem_is_refused_before_its_body_and_rsa_private_pem_is_read() {
let text = "-----BEGIN RSA PRIVATE KEY-----\n!!!not base64 at all!!!\n-----END RSA PRIVATE KEY-----\n";
assert!(matches!(pem::decode(text), Err(Error::InvalidKey(_))));
{
let label = "ENCRYPTED PRIVATE KEY";
let text = format!(
"-----BEGIN {label}-----\n!!!not base64 at all!!!\n-----END {label}-----\n"
);
assert_eq!(pem::decode(&text).unwrap_err(), Error::UnsupportedAlgorithm);
let text = format!("-----BEGIN {label}-----\nAAAA\n");
assert_eq!(pem::decode(&text).unwrap_err(), Error::UnsupportedAlgorithm);
}
}
#[test]
fn pkcs8_rsa_encryption_with_a_bad_or_missing_private_key_is_invalid() {
let alg = [
0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05,
0x00,
];
let mut with_garbage = vec![0x02, 0x01, 0x00];
with_garbage.extend_from_slice(&alg);
with_garbage.extend_from_slice(&[0x04, 0x04, 0xA5, 0xA5, 0xA5, 0xA5]);
let mut without = vec![0x02, 0x01, 0x00];
without.extend_from_slice(&alg);
for body in [with_garbage, without] {
let mut der = vec![0x30];
push_len(&mut der, body.len());
der.extend_from_slice(&body);
let error = Jwk::from_der(&der, super::super::KeyFormat::Pkcs8).unwrap_err();
assert!(matches!(error, Error::InvalidKey(_)), "{error:?}");
assert!(!format!("{error:?}").to_ascii_lowercase().contains("a5"));
}
}
}