use rsa::signature::Verifier;
use rsa::{BoxedUint, pkcs1v15, pss};
use sha2::{Sha256, Sha384, Sha512};
use crate::error::{Error, Result};
use crate::jwa::SigningAlg;
use crate::jwk::{Jwk, KeyMaterial, RSA_MODULUS_BITS};
use crate::rng::CryptoRngAdapter;
use super::alg::{self, Family};
pub(crate) fn sign(alg: &SigningAlg, key: &Jwk, signing_input: &[u8]) -> Result<Vec<u8>> {
sign_with(alg, key, signing_input, &mut CryptoRngAdapter::os())
}
fn sign_with(
alg: &SigningAlg,
key: &Jwk,
signing_input: &[u8],
rng: &mut CryptoRngAdapter<'_>,
) -> Result<Vec<u8>> {
use SigningAlg as A;
let KeyMaterial::Rsa(jwk_key) = key.material() else {
return Err(Error::KeyAlgorithmMismatch);
};
if alg::family(alg) != Some(Family::Rsa) {
return Err(Error::KeyAlgorithmMismatch);
}
let private = crate::keys::rsa::private_key(jwk_key)?;
let signed = match alg {
A::RS256 => private.pkcs1v15_sign_with_rng::<Sha256, _>(rng, signing_input),
A::RS384 => private.pkcs1v15_sign_with_rng::<Sha384, _>(rng, signing_input),
A::RS512 => private.pkcs1v15_sign_with_rng::<Sha512, _>(rng, signing_input),
A::PS256 => private.pss_sign_with_rng::<Sha256, _>(rng, signing_input),
A::PS384 => private.pss_sign_with_rng::<Sha384, _>(rng, signing_input),
A::PS512 => private.pss_sign_with_rng::<Sha512, _>(rng, signing_input),
_ => return Err(Error::KeyAlgorithmMismatch),
};
signed.map_err(|err| match err {
rsa::errors::Error::Rng => Error::RandomnessUnavailable,
_ => Error::InvalidKey("RSA key cannot make this signature"),
})
}
pub(crate) fn verify(
alg: &SigningAlg,
key: &Jwk,
signing_input: &[u8],
signature: &[u8],
) -> Result<()> {
use SigningAlg as A;
let KeyMaterial::Rsa(jwk_key) = key.material() else {
return Err(Error::KeyAlgorithmMismatch);
};
if alg::family(alg) != Some(Family::Rsa) {
return Err(Error::KeyAlgorithmMismatch);
}
let public = public_key(jwk_key.n(), jwk_key.e())?;
let verified = match alg {
A::RS256 => pkcs1v15::VerifyingKey::<Sha256>::new(public)
.verify(signing_input, &pkcs1v15_signature(signature)?),
A::RS384 => pkcs1v15::VerifyingKey::<Sha384>::new(public)
.verify(signing_input, &pkcs1v15_signature(signature)?),
A::RS512 => pkcs1v15::VerifyingKey::<Sha512>::new(public)
.verify(signing_input, &pkcs1v15_signature(signature)?),
A::PS256 => pss::VerifyingKey::<Sha256>::new(public)
.verify(signing_input, &pss_signature(signature)?),
A::PS384 => pss::VerifyingKey::<Sha384>::new(public)
.verify(signing_input, &pss_signature(signature)?),
A::PS512 => pss::VerifyingKey::<Sha512>::new(public)
.verify(signing_input, &pss_signature(signature)?),
_ => return Err(Error::KeyAlgorithmMismatch),
};
verified.map_err(|_| Error::InvalidSignature)
}
fn pkcs1v15_signature(signature: &[u8]) -> Result<pkcs1v15::Signature> {
pkcs1v15::Signature::try_from(signature).map_err(|_| Error::InvalidSignature)
}
fn pss_signature(signature: &[u8]) -> Result<pss::Signature> {
pss::Signature::try_from(signature).map_err(|_| Error::InvalidSignature)
}
pub(crate) fn public_key(n: &[u8], e: &[u8]) -> Result<rsa::PublicKey> {
use rsa::errors::Error as E;
let (n, e) = (uint(n)?, uint(e)?);
rsa::PublicKey::new_with_size_limits(n, e, RSA_MODULUS_BITS).map_err(|err| {
Error::InvalidKey(match err {
E::ModulusTooSmall | E::ModulusTooLarge => "RSA modulus must be 2048 to 8192 bits",
E::InvalidExponent | E::PublicExponentTooSmall => {
"RSA exponent must be odd and greater than 1"
}
E::PublicExponentTooLarge => "RSA exponent is larger than 2^33 - 1",
_ => "RSA modulus must be odd and greater than the exponent",
})
})
}
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("RSA key member is empty or too long"))?;
BoxedUint::from_be_slice(bytes, bits)
.map_err(|_| Error::InvalidKey("RSA key member is empty or too long"))
}
#[cfg(test)]
mod tests {
use rsa::traits::PublicKeyParts;
use super::super::alg::{Operation, bind_key};
use super::*;
use crate::header::Header;
use crate::jws::{self, ProtectedHeader, SignOptions, VerifyOptions};
use crate::test_support::wycheproof::{
self, Deviation, Group, Outcome, Schema, Suite, Summary,
};
use crate::util::b64url_decode;
use SigningAlg as A;
const F4: &[u8] = &[0x01, 0x00, 0x01];
const RFC7515_A2_N: &str = concat!(
"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx",
"HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs",
"D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH",
"SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV",
"MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8",
"NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ",
);
const RFC7515_A2_INPUT: &str = concat!(
"eyJhbGciOiJSUzI1NiJ9",
".",
"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt",
"cGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
);
const RFC7515_A2_SIG: &str = concat!(
"cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7",
"AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4",
"BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K",
"0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv",
"hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB",
"p0igcN_IoypGlUPQGe77Rw",
);
const RFC7520_N: &str = concat!(
"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT",
"-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV",
"wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-",
"oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde",
"3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC",
"LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g",
"HdrNP5zw",
);
const RFC7520_PAYLOAD: &str = concat!(
"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH",
"lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk",
"b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm",
"UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4",
);
const RFC7520_4_1_PROTECTED: &str = concat!(
"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX",
"hhbXBsZSJ9",
);
const RFC7520_4_1_SIG: &str = concat!(
"MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmK",
"ZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4J",
"IwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8w",
"W1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluP",
"xUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_f",
"cIe8u9ipH84ogoree7vjbU5y18kDquDg",
);
const RFC7520_4_2_PROTECTED: &str = concat!(
"eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX",
"hhbXBsZSJ9",
);
const RFC7520_4_2_SIG: &str = concat!(
"cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2I",
"pN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXU",
"vdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRX",
"e8P_ijQ7p8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT",
"0qI0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a",
"6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw",
);
fn rsa_jwk(n: &str) -> Jwk {
Jwk::from_json(format!(r#"{{"kty":"RSA","n":"{n}","e":"AQAB"}}"#).as_bytes()).unwrap()
}
fn signing_input(protected: &str, payload: &str) -> Vec<u8> {
format!("{protected}.{payload}").into_bytes()
}
struct Vector {
at: &'static str,
alg: SigningAlg,
key: Jwk,
input: Vec<u8>,
sig: Vec<u8>,
}
fn rfc_vectors() -> Vec<Vector> {
let sig = |s: &str| b64url_decode(s).unwrap();
vec![
Vector {
at: "RFC 7515 A.2",
alg: A::RS256,
key: rsa_jwk(RFC7515_A2_N),
input: RFC7515_A2_INPUT.as_bytes().to_vec(),
sig: sig(RFC7515_A2_SIG),
},
Vector {
at: "RFC 7520 4.1",
alg: A::RS256,
key: rsa_jwk(RFC7520_N),
input: signing_input(RFC7520_4_1_PROTECTED, RFC7520_PAYLOAD),
sig: sig(RFC7520_4_1_SIG),
},
Vector {
at: "RFC 7520 4.2",
alg: A::PS384,
key: rsa_jwk(RFC7520_N),
input: signing_input(RFC7520_4_2_PROTECTED, RFC7520_PAYLOAD),
sig: sig(RFC7520_4_2_SIG),
},
]
}
fn flipped(bytes: &[u8], at: usize, bit: u8) -> Vec<u8> {
let mut out = bytes.to_vec();
out[at] ^= 1 << bit;
out
}
#[test]
fn rfc_vectors_verify_with_the_public_key() {
for Vector {
at,
alg,
key,
input,
sig,
} in rfc_vectors()
{
assert!(key.is_public(), "{at}");
assert_eq!(verify(&alg, &key, &input, &sig), Ok(()), "{at}");
}
}
#[test]
fn rfc_vectors_with_a_flipped_bit_are_invalid() {
for Vector {
at,
alg,
key,
input,
sig,
} in rfc_vectors()
{
for pos in [0, sig.len() / 2, sig.len() - 1] {
for bit in [0, 7] {
let bad = flipped(&sig, pos, bit);
assert_eq!(
verify(&alg, &key, &input, &bad),
Err(Error::InvalidSignature),
"{at}: signature octet {pos} bit {bit}"
);
}
}
for pos in [0, input.len() / 2, input.len() - 1] {
for bit in [0, 7] {
let bad = flipped(&input, pos, bit);
assert_eq!(
verify(&alg, &key, &bad, &sig),
Err(Error::InvalidSignature),
"{at}: signing input octet {pos} bit {bit}"
);
}
}
}
}
#[test]
fn rfc_vectors_fail_under_every_other_rsa_alg() {
let all = [A::RS256, A::RS384, A::RS512, A::PS256, A::PS384, A::PS512];
for Vector {
at,
alg,
key,
input,
sig,
} in rfc_vectors()
{
for other in all.iter().filter(|other| **other != alg) {
assert_eq!(
verify(other, &key, &input, &sig),
Err(Error::InvalidSignature),
"{at} under {other}"
);
}
}
}
#[test]
fn verify_refuses_what_key_binding_would_have_stopped() {
let Vector {
key: rsa_key,
input,
sig,
..
} = rfc_vectors().remove(0);
let oct = Jwk::from_json(br#"{"kty":"oct","k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"}"#).unwrap();
assert_eq!(
verify(&A::RS256, &oct, &input, &sig),
Err(Error::KeyAlgorithmMismatch)
);
for alg in [A::HS256, A::ES256, A::EdDSA, A::None] {
assert_eq!(
verify(&alg, &rsa_key, &input, &sig),
Err(Error::KeyAlgorithmMismatch),
"{alg}"
);
}
}
#[test]
fn verify_rejects_a_key_the_rsa_crate_refuses() {
let Vector { input, sig, .. } = rfc_vectors().remove(0);
let key = Jwk::from_json(
format!(r#"{{"kty":"RSA","n":"{RFC7515_A2_N}","e":"AgAAAAE"}}"#).as_bytes(),
)
.unwrap();
assert!(matches!(
verify(&A::RS256, &key, &input, &sig),
Err(Error::InvalidKey(_))
));
}
fn modulus(bits: usize) -> Vec<u8> {
let mut n = vec![0xff; bits.div_ceil(8)];
n[0] >>= (8 - bits % 8) % 8;
n
}
fn invalid_key(result: Result<rsa::PublicKey>) -> bool {
matches!(result, Err(Error::InvalidKey(_)))
}
#[test]
fn public_key_imports_2048_to_8192_bits() {
for bits in [2048, 3072, 4096, 8191, 8192] {
let key = public_key(&modulus(bits), F4).unwrap();
assert_eq!(key.n().bit_width() as usize, bits);
assert_eq!(key.size(), bits.div_ceil(8));
}
assert!(public_key(&modulus(2048), &[3]).is_ok());
}
#[test]
fn public_key_rejects_a_modulus_outside_2048_to_8192_bits() {
for bits in [1024, 2047, 8193, 8200] {
assert!(invalid_key(public_key(&modulus(bits), F4)), "{bits}");
}
}
#[test]
fn public_key_rejects_a_bad_exponent() {
let n = modulus(2048);
for e in [&[0x01, 0x00, 0x00][..], &[1], &[0], &[2], &[]] {
assert!(invalid_key(public_key(&n, e)), "{e:?}");
}
assert!(invalid_key(public_key(&n, &n)), "e = n");
assert!(invalid_key(public_key(&n, &[0x02, 0x00, 0x00, 0x00, 0x01])));
assert!(public_key(&n, &[0x01, 0xff, 0xff, 0xff, 0xff]).is_ok());
}
#[test]
fn public_key_rejects_an_even_or_empty_modulus() {
let mut even = modulus(2048);
if let Some(last) = even.last_mut() {
*last = 0xfe;
}
assert!(invalid_key(public_key(&even, F4)));
assert!(invalid_key(public_key(&[], F4)));
}
const PS384_BY_SALT_LEN: [(usize, &str); 4] = [
(
0,
concat!(
"bwOrJVMBUJZNZngSHWOOjFVnnjqrCv2_Er81PVCpbNMqr6lo29plwjD8Wm0lS6wN",
"3VzO72xj-PzEC4sD06E4_SiPTW-RF6DqqEUyF1sy-x2iGwew4hzunBxyFp8dXBGS",
"sZy2GbDaGnPyB2En4y7GCuFVpe1EiTjQvs4ULbHmzbyvgDjY6Xwr9ZdN268ASVIt",
"Sw7dCI0i8bf3A2tOf203ZHNR6Xd-bptVbn-IM7KzWUWR0mBU-JwRM9a3yV6DwzfD",
"D6wEfdTEVjFcll1bXtOJLa9ZOCRzk2MtbuJgCIHKog0uVoQShqeIuwNOGvgaLEn5",
"N-BWXJsxr0ZQIn-6uLK0Og",
),
),
(
32,
concat!(
"JoCU0sMfLp6T4nyDkpJsmSgCMNJKW-WHazl8i2vt0x7vcIDIuawU9ou-drJ7qKt-",
"sbTqPL-96TwA1pSGnwvU0lww3COp1vEOh5KuZ877HXdniuCEYozbW-f1ECGroqd9",
"A648TgNXTAtkLkiKBzQEs7LJI6dSPLX3Wlz7_VrZuUWX4VI0WJop6oFIdS9YGepe",
"0OnfndWz06wW6ovP3hwtoS_zsGm03LOHPkAIr9seG4KOOOliyDFMnqqYYr6fw2Vk",
"E5fQLIYp_wQTqX-Tt0DzvpQbGA-YsnyehiFDJ7gxHDT-qkAEPMTBBmLPEoICFMqC",
"ELJDhIrknJnrgD-OHGTpmQ",
),
),
(
48,
concat!(
"L7eXQx4TvNcA9eCsTaNGtvw724PAlPWU82288GS8RKCJ3_LaEq3Hg_qBLAGkBFUC",
"yL3ayaURuZ336a3jrEK5W_TrVplZ-cYTfwsAzKk1FtiQ4K6hHKeBXWSOeQfEExdL",
"sW6KBecWWoSegaLpByWR0ZdhvtIBFe6vJosjIkleVrM5pHDe8V8YQ3Rdfkxe73XX",
"1Y6dx9GYKReHN1BdJuoUneNUqEVU-rUSSLfHjLxVAnAO3ODVeE_y7fAwN8huguoa",
"TVcdpHFY94mBpKdcb5VK0GHbkPpcDGnL8YogoQJ8Up4_ShhhD7LUX01289bbZ0yw",
"uYWJy4d1Ez1y7Bh-qIpYAw",
),
),
(
64,
concat!(
"A5Ahut2wt26zbNYt3DG2pAVoOyAzMpDX3JRiG_U6SsuvB_IzW1jZLEfzJPmFUnrW",
"VBOziLxYquDapdsChviOVJFNsRMiGscLWKS3B1K18hr0-dP1NZ9R5F8kr7b7wZP7",
"paKEafmn-8hM3TqtvfrYyYCNeZAhKM4dK8rES02qnyHIjGmZ3evqQ-DkFylP0rju",
"Kko6JswxTT_ABW3V3uphvuNouGmyxMVWJvOAnj-ukukoDy42DjtJsIKZ7ENSLd4H",
"vqFeht4FbOwU6gCzs7SecUi5OMdlVa0Jj9mOcYOduO0xg9EPmQEcnMPVI-6NXQIR",
"ZWAI_cmMUppgSPURVU0YTA",
),
),
];
#[test]
fn pss_accepts_only_a_salt_as_long_as_the_hash() {
let key = rsa_jwk(RFC7520_N);
let input = signing_input(RFC7520_4_2_PROTECTED, RFC7520_PAYLOAD);
for (salt_len, sig) in PS384_BY_SALT_LEN {
let sig = b64url_decode(sig).unwrap();
let expected = if salt_len == 48 {
Ok(())
} else {
Err(Error::InvalidSignature)
};
assert_eq!(
verify(&A::PS384, &key, &input, &sig),
expected,
"salt {salt_len}"
);
}
}
#[test]
fn a_signature_must_be_exactly_the_modulus_length() {
for (name, tc_id) in [
("rsa_signature_2048_sha256", 258),
("rsa_pss_2048_sha384_mgf1_48", 64),
] {
let suite = wycheproof::load(name);
let (group, test) = suite.tests().find(|(_, test)| test.tc_id == tc_id).unwrap();
let alg = group_alg(&suite, group);
let key = Jwk::from_json(group.verification_jwk().as_bytes()).unwrap();
let (msg, sig) = test.signature();
assert_eq!(sig.len(), 256, "{name} {tc_id}");
assert_eq!(sig[0], 0, "{name} {tc_id}");
assert_eq!(verify(&alg, &key, msg, sig), Ok(()), "{name} {tc_id}");
assert_eq!(
verify(&alg, &key, msg, &sig[1..]),
Err(Error::InvalidSignature),
"{name} {tc_id}: leading zero stripped"
);
}
let Vector {
alg,
key,
input,
sig,
..
} = rfc_vectors().remove(0);
let mut padded = vec![0];
padded.extend_from_slice(&sig);
let mut long = sig.clone();
long.resize(1025, 0);
for bad in [&padded[..], &sig[..255], &long[..], &[]] {
assert_eq!(
verify(&alg, &key, &input, bad),
Err(Error::InvalidSignature),
"{} octets",
bad.len()
);
}
}
fn rfc7515_a2_private() -> Jwk {
Jwk::from_json(include_bytes!(
"../../tests/data/keys/rfc/rfc7515-a2-rsa-private.jwk.json"
))
.unwrap()
}
fn rfc7520_private() -> Jwk {
Jwk::from_json(include_bytes!(
"../../tests/data/keys/rfc/rfc7520-3.4-rsa-private.jwk.json"
))
.unwrap()
}
#[test]
fn rs256_signing_reproduces_rfc_7515_a2_and_rfc_7520_4_1_exactly() {
let a2 = rfc7515_a2_private();
assert!(!a2.is_public());
assert_eq!(
sign(&A::RS256, &a2, RFC7515_A2_INPUT.as_bytes()).unwrap(),
b64url_decode(RFC7515_A2_SIG).unwrap()
);
let input = signing_input(RFC7520_4_1_PROTECTED, RFC7520_PAYLOAD);
assert_eq!(
sign(&A::RS256, &rfc7520_private(), &input).unwrap(),
b64url_decode(RFC7520_4_1_SIG).unwrap()
);
let KeyMaterial::Rsa(public) = a2.material() else {
panic!("RSA")
};
assert_eq!(public.n(), b64url_decode(RFC7515_A2_N).unwrap());
}
#[test]
fn every_rsa_alg_signs_and_its_own_verify_accepts_it() {
let all = [A::RS256, A::RS384, A::RS512, A::PS256, A::PS384, A::PS512];
let input = signing_input(RFC7520_4_2_PROTECTED, RFC7520_PAYLOAD);
for key in [rfc7515_a2_private(), rfc7520_private()] {
let public = key.to_public().unwrap();
for alg in &all {
let sig = sign(alg, &key, &input).unwrap();
assert_eq!(sig.len(), 256, "{alg}");
assert_eq!(verify(alg, &public, &input, &sig), Ok(()), "{alg}");
assert_eq!(verify(alg, &key, &input, &sig), Ok(()), "{alg}");
for other in all.iter().filter(|other| *other != alg) {
assert_eq!(
verify(other, &public, &input, &sig),
Err(Error::InvalidSignature),
"{alg} as {other}"
);
}
}
}
}
#[test]
fn pss_signatures_are_randomized_and_rs_signatures_are_not() {
let key = rfc7520_private();
let input = signing_input(RFC7520_4_2_PROTECTED, RFC7520_PAYLOAD);
for alg in [A::PS256, A::PS384, A::PS512] {
assert_ne!(sign(&alg, &key, &input), sign(&alg, &key, &input), "{alg}");
}
for alg in [A::RS256, A::RS384, A::RS512] {
assert_eq!(sign(&alg, &key, &input), sign(&alg, &key, &input), "{alg}");
}
}
#[test]
fn sign_refuses_public_and_non_rsa_keys_and_non_rsa_algs() {
use super::super::alg::test_keys as keys;
let input = RFC7515_A2_INPUT.as_bytes();
for key in [
rsa_jwk(RFC7515_A2_N),
keys::oct(64, ""),
keys::p256_private(""),
keys::ed25519_private(""),
] {
for alg in [A::RS256, A::PS512] {
assert_eq!(
sign(&alg, &key, input),
Err(Error::KeyAlgorithmMismatch),
"{alg}"
);
}
}
for alg in [A::HS256, A::ES256, A::EdDSA, A::None] {
assert_eq!(
sign(&alg, &rfc7515_a2_private(), input),
Err(Error::KeyAlgorithmMismatch),
"{alg}"
);
}
let protected =
ProtectedHeader::new(Header::from_json(br#"{"alg":"RS256"}"#).unwrap()).unwrap();
assert_eq!(
jws::sign_compact(
&protected,
b"x",
&rsa_jwk(RFC7515_A2_N),
&SignOptions::new()
),
Err(Error::KeyAlgorithmMismatch)
);
let jws = jws::sign_compact(&protected, b"x", &rfc7515_a2_private(), &SignOptions::new())
.unwrap();
let allowed = [A::RS256];
let opts = VerifyOptions::new(&allowed).unwrap();
let verified = jws::verify_compact(&jws, &rsa_jwk(RFC7515_A2_N), &opts).unwrap();
assert_eq!(*verified.payload, b"x");
}
#[test]
fn a_failing_rng_fails_closed_and_never_signs_unblinded() {
let key = rfc7515_a2_private();
let error = getrandom::Error::UNSUPPORTED;
for alg in [A::RS256, A::RS384, A::RS512, A::PS256, A::PS384, A::PS512] {
assert_eq!(
sign_with(&alg, &key, b"input", &mut CryptoRngAdapter::failing(error)),
Err(Error::RandomnessUnavailable),
"{alg}"
);
}
}
#[test]
fn wycheproof_rsa_pkcs1_signature_generation() {
let (mut checked, mut unbalanced) = (0, 0);
for name in wycheproof::names("rsa_pkcs1_") {
let suite = wycheproof::load(name);
for group in &suite.groups {
let alg = match group.sha.as_deref() {
Some("SHA-256") => A::RS256,
Some("SHA-384") => A::RS384,
Some("SHA-512") => A::RS512,
_ => continue,
};
let jwk = group.private_key_jwk.as_deref().unwrap();
let key = match Jwk::from_json(jwk.as_bytes()) {
Ok(key) => key,
Err(error) => {
assert!(matches!(error, Error::InvalidKey(_)), "{name}: {error:?}");
assert!(
group
.tests
.iter()
.all(|t| t.result == wycheproof::Expected::Acceptable),
"{name}"
);
unbalanced += 1;
continue;
}
};
assert!(!key.is_public());
for test in &group.tests {
let (msg, sig) = test.signature();
assert_ne!(
test.result,
wycheproof::Expected::Invalid,
"{name} {}",
test.tc_id
);
assert_eq!(
sign(&alg, &key, msg).as_deref(),
Ok(sig),
"{name} tcId {}",
test.tc_id
);
assert_eq!(
verify(&alg, &key, msg, sig),
Ok(()),
"{name} {}",
test.tc_id
);
checked += 1;
}
}
}
assert_eq!((checked, unbalanced), (72, 5));
}
fn rfc7520_figure3() -> Jwk {
Jwk::from_json(
format!(
r#"{{"kty":"RSA","kid":"bilbo.baggins@hobbiton.example","use":"sig","n":"{RFC7520_N}","e":"AQAB"}}"#
)
.as_bytes(),
)
.unwrap()
}
#[test]
fn verify_compact_end_to_end() {
let rfc7515 = format!("{RFC7515_A2_INPUT}.{RFC7515_A2_SIG}");
let rfc7520_4_1 = format!("{RFC7520_4_1_PROTECTED}.{RFC7520_PAYLOAD}.{RFC7520_4_1_SIG}");
let rfc7520_4_2 = format!("{RFC7520_4_2_PROTECTED}.{RFC7520_PAYLOAD}.{RFC7520_4_2_SIG}");
let figure7 = "It\u{2019}s a dangerous business, Frodo, going out your door. You step onto the road, and if you don't keep your feet, there\u{2019}s no knowing where you might be swept off to.";
let cases = [
(
&rfc7515,
rsa_jwk(RFC7515_A2_N),
A::RS256,
&b"{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}"[..],
),
(&rfc7520_4_1, rfc7520_figure3(), A::RS256, figure7.as_bytes()),
(&rfc7520_4_2, rfc7520_figure3(), A::PS384, figure7.as_bytes()),
];
let allowed = [A::RS256, A::PS384];
let opts = VerifyOptions::new(&allowed).unwrap();
for (jws, key, alg, payload) in &cases {
let verified = jws::verify_compact(jws, key, &opts).unwrap();
assert_eq!(verified.alg, *alg);
assert_eq!(*verified.payload, *payload);
assert_eq!(verified.signature_index, 0);
let mut tampered = (*jws).clone();
let last = tampered.pop().unwrap();
tampered.push(if last == 'A' { 'Q' } else { 'A' });
assert_eq!(
jws::verify_compact(&tampered, key, &opts),
Err(Error::InvalidSignature),
"{alg}"
);
let other = [A::ES256];
let strict = VerifyOptions::new(&other).unwrap();
assert_eq!(
jws::verify_compact(jws, key, &strict),
Err(Error::AlgorithmNotAllowed)
);
}
}
const PKCS1V15_DEVIATIONS: &[Deviation] = &[Deviation::tests(
&[8],
Outcome::Reject,
"RFC 8017 §8.2.2 step 4, §9.2 note 1: EM' carries the DigestInfo \
with NULL hash parameters, so one without them (MissingNull) is \
an invalid signature",
)];
const PSS_DEVIATIONS: &[Deviation] = &[];
fn group_alg(suite: &Suite, group: &Group) -> SigningAlg {
let sha = group.sha.as_deref().unwrap_or_default();
let (alg, hash_len) = match (suite.schema, sha) {
(Schema::RsassaPkcs1Verify, "SHA-256") => (A::RS256, 32),
(Schema::RsassaPkcs1Verify, "SHA-384") => (A::RS384, 48),
(Schema::RsassaPkcs1Verify, "SHA-512") => (A::RS512, 64),
(Schema::RsassaPssVerify, "SHA-256") => (A::PS256, 32),
(Schema::RsassaPssVerify, "SHA-384") => (A::PS384, 48),
(Schema::RsassaPssVerify, "SHA-512") => (A::PS512, 64),
other => panic!("{}: no JWS alg for {other:?}", suite.name),
};
if suite.schema == Schema::RsassaPssVerify {
assert_eq!(group.mgf_sha.as_deref(), Some(sha), "{}", suite.name);
assert_eq!(group.salt_len, Some(hash_len), "{}", suite.name);
}
alg
}
fn run_wycheproof(suite: &Suite, deviations: &[Deviation]) -> Summary {
wycheproof::assert_conforms(suite, deviations, |group, test| {
let alg = group_alg(suite, group);
let key = Jwk::from_json(group.verification_jwk().as_bytes())
.unwrap_or_else(|e| panic!("{}: tcId {}: {e:?}", suite.name, test.tc_id));
assert_eq!(bind_key(&alg, &key, Operation::Verify), Ok(()));
let (msg, sig) = test.signature();
let result = verify(&alg, &key, msg, sig);
if let Err(err) = &result {
assert_eq!(
*err,
Error::InvalidSignature,
"{}: tcId {}",
suite.name,
test.tc_id
);
}
Outcome::of(&result)
})
}
#[test]
fn wycheproof_rsa_signature_suites() {
for name in wycheproof::names("rsa_signature_") {
let suite = wycheproof::load(name);
let summary = run_wycheproof(&suite, PKCS1V15_DEVIATIONS);
println!("{name}: {summary:?}");
}
}
#[test]
fn wycheproof_rsa_pss_suites() {
for name in wycheproof::names("rsa_pss_") {
let suite = wycheproof::load(name);
let summary = run_wycheproof(&suite, PSS_DEVIATIONS);
println!("{name}: {summary:?}");
}
}
}