use crate::error::{Error, Result};
use crate::jwa::SigningAlg;
use crate::jwk::{Jwk, KeyAlg, KeyMaterial, KeyOp, RSA_MODULUS_BITS};
use super::{ecdsa, eddsa, hmac, rsa};
pub(crate) type SignFn = fn(&SigningAlg, &Jwk, &[u8]) -> Result<Vec<u8>>;
pub(crate) type VerifyFn = fn(&SigningAlg, &Jwk, &[u8], &[u8]) -> Result<()>;
#[derive(Clone, Copy)]
pub(crate) struct Families {
pub(crate) sign: SignFn,
pub(crate) verify: VerifyFn,
}
pub(crate) const FAMILIES: Families = Families {
sign: dispatch_sign,
verify: dispatch_verify,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Family {
Hmac,
Rsa,
Ecdsa,
EdDsa,
}
pub(crate) fn family(alg: &SigningAlg) -> Option<Family> {
use SigningAlg as A;
match alg {
A::HS256 | A::HS384 | A::HS512 => Some(Family::Hmac),
A::RS256 | A::RS384 | A::RS512 | A::PS256 | A::PS384 | A::PS512 => Some(Family::Rsa),
A::ES256 | A::ES384 | A::ES512 | A::ES256K => Some(Family::Ecdsa),
A::EdDSA | A::Ed25519 => Some(Family::EdDsa),
A::None | A::Unknown(_) => None,
}
}
pub(crate) fn unserved(alg: &SigningAlg) -> Error {
if *alg == SigningAlg::None {
Error::AlgorithmNotAllowed
} else {
Error::UnsupportedAlgorithm
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Operation {
Sign,
Verify,
}
pub(crate) fn bind_key(alg: &SigningAlg, key: &Jwk, op: Operation) -> Result<()> {
family(alg).ok_or_else(|| unserved(alg))?;
let jwk_alg_ok = key
.alg()
.is_none_or(|key_alg| matches!(key_alg, KeyAlg::Signing(a) if a == alg));
let key_op = match op {
Operation::Sign => KeyOp::Sign,
Operation::Verify => KeyOp::Verify,
};
let bound = key.material().serves_signing_alg(alg)
&& jwk_alg_ok
&& key.permits(&key_op)
&& !(op == Operation::Sign && key.is_public());
if !bound {
return Err(Error::KeyAlgorithmMismatch);
}
check_strength(alg, key.material())
}
fn check_strength(alg: &SigningAlg, material: &KeyMaterial) -> Result<()> {
match material {
KeyMaterial::Oct(k) => {
let min = match alg {
SigningAlg::HS384 => 48,
SigningAlg::HS512 => 64,
_ => 32,
};
if k.len() < min {
return Err(Error::InvalidKey(
"HMAC key is shorter than the hash output",
));
}
Ok(())
}
KeyMaterial::Rsa(k) => {
if RSA_MODULUS_BITS.contains(&k.modulus_bits()) {
Ok(())
} else {
Err(Error::InvalidKey("RSA modulus must be 2048 to 8192 bits"))
}
}
KeyMaterial::Ec(_) | KeyMaterial::Okp(_) => Ok(()),
}
}
fn dispatch_sign(alg: &SigningAlg, key: &Jwk, signing_input: &[u8]) -> Result<Vec<u8>> {
match family(alg) {
Some(Family::Hmac) => hmac::sign(alg, key, signing_input),
Some(Family::Rsa) => rsa::sign(alg, key, signing_input),
Some(Family::Ecdsa) => ecdsa::sign(alg, key, signing_input),
Some(Family::EdDsa) => eddsa::sign(alg, key, signing_input),
None => Err(unserved(alg)),
}
}
fn dispatch_verify(
alg: &SigningAlg,
key: &Jwk,
signing_input: &[u8],
signature: &[u8],
) -> Result<()> {
match family(alg) {
Some(Family::Hmac) => hmac::verify(alg, key, signing_input, signature),
Some(Family::Rsa) => rsa::verify(alg, key, signing_input, signature),
Some(Family::Ecdsa) => ecdsa::verify(alg, key, signing_input, signature),
Some(Family::EdDsa) => eddsa::verify(alg, key, signing_input, signature),
None => Err(unserved(alg)),
}
}
#[cfg(test)]
pub(crate) mod fake {
use sha2::{Digest, Sha256};
use super::Families;
use crate::error::{Error, Result};
use crate::jwa::SigningAlg;
use crate::jwk::Jwk;
pub(crate) const FAKE: Families = Families {
sign: fake_sign,
verify: fake_verify,
};
pub(crate) const REJECTING: Families = Families {
sign: fake_sign,
verify: |_, _, _, _| Err(Error::InvalidSignature),
};
pub(crate) fn fake_signature(alg: &SigningAlg, signing_input: &[u8]) -> Vec<u8> {
let mut h = Sha256::new();
h.update(alg.as_str().as_bytes());
h.update([0]);
h.update(signing_input);
h.finalize().to_vec()
}
fn fake_sign(alg: &SigningAlg, _key: &Jwk, signing_input: &[u8]) -> Result<Vec<u8>> {
Ok(fake_signature(alg, signing_input))
}
fn fake_verify(
alg: &SigningAlg,
_key: &Jwk,
signing_input: &[u8],
signature: &[u8],
) -> Result<()> {
if fake_signature(alg, signing_input) == signature {
Ok(())
} else {
Err(Error::InvalidSignature)
}
}
}
#[cfg(test)]
pub(crate) mod test_keys {
use crate::jwk::Jwk;
use crate::util::b64url_encode;
pub(crate) const P256_X: &str = "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4";
pub(crate) const P256_Y: &str = "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM";
pub(crate) const P256_D: &str = "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE";
pub(crate) const ED25519_X: &str = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo";
pub(crate) const ED25519_D: &str = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A";
pub(crate) fn jwk(json: &str) -> Jwk {
Jwk::from_json(json.as_bytes()).unwrap()
}
pub(crate) fn oct(len: usize, extra: &str) -> Jwk {
let k = b64url_encode(&vec![0x5a; len]);
jwk(&format!(r#"{{"kty":"oct","k":"{k}"{extra}}}"#))
}
pub(crate) fn rsa(bits: usize) -> Jwk {
let mut n = vec![0xff; bits.div_ceil(8)];
n[0] >>= (8 - bits % 8) % 8;
let n = b64url_encode(&n);
jwk(&format!(r#"{{"kty":"RSA","n":"{n}","e":"AQAB"}}"#))
}
pub(crate) fn rsa_private(extra: &str) -> Jwk {
let json = include_str!("../../tests/data/keys/rfc/rfc7515-a2-rsa-private.jwk.json");
let json = json.trim_end().strip_suffix('}').unwrap_or(json);
jwk(&format!("{json}{extra}}}"))
}
pub(crate) fn p256(extra: &str) -> Jwk {
jwk(&format!(
r#"{{"kty":"EC","crv":"P-256","x":"{P256_X}","y":"{P256_Y}"{extra}}}"#
))
}
pub(crate) fn p256_private(extra: &str) -> Jwk {
p256(&format!(r#","d":"{P256_D}"{extra}"#))
}
pub(crate) fn p384() -> Jwk {
jwk(
r#"{"kty":"EC","crv":"P-384","x":"qofKIr6LBTeOscce8yCtdG4dO2KLp5uYWfdB4IJUKjhVAvJdv1UpbDpUXjhydgq3","y":"NhfeSpYmLG9dnpi_kpLcKfj0Hb0omhR86doxE7XwuMAKYLHOHX6BnXpDHXyQ6g5f"}"#,
)
}
pub(crate) fn secp256k1() -> Jwk {
jwk(
r#"{"kty":"EC","crv":"secp256k1","x":"eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g","y":"SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg"}"#,
)
}
pub(crate) fn ed25519(extra: &str) -> Jwk {
jwk(&format!(
r#"{{"kty":"OKP","crv":"Ed25519","x":"{ED25519_X}"{extra}}}"#
))
}
pub(crate) fn ed25519_private(extra: &str) -> Jwk {
ed25519(&format!(r#","d":"{ED25519_D}"{extra}"#))
}
pub(crate) fn x25519() -> Jwk {
jwk(r#"{"kty":"OKP","crv":"X25519","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"}"#)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn oct() -> Jwk {
Jwk::from_json(br#"{"kty":"oct","k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"}"#).unwrap()
}
use super::test_keys as keys;
use crate::util::b64url_encode;
use Operation::{Sign, Verify};
use SigningAlg as A;
const MISMATCH: Result<()> = Err(Error::KeyAlgorithmMismatch);
#[test]
fn binding_accepts_each_family_with_its_key() {
let cases = [
(A::HS256, keys::oct(32, "")),
(A::HS384, keys::oct(48, "")),
(A::HS512, keys::oct(64, "")),
(A::ES256, keys::p256_private("")),
(A::EdDSA, keys::ed25519_private("")),
(A::Ed25519, keys::ed25519_private("")),
];
for (alg, key) in &cases {
assert_eq!(bind_key(alg, key, Sign), Ok(()), "sign {alg}");
assert_eq!(bind_key(alg, key, Verify), Ok(()), "verify {alg}");
}
let verify_only = [
(A::RS256, keys::rsa(2048)),
(A::PS512, keys::rsa(8192)),
(A::ES256, keys::p256("")),
(A::ES384, keys::p384()),
(A::ES256K, keys::secp256k1()),
(A::Ed25519, keys::ed25519("")),
];
for (alg, key) in &verify_only {
assert_eq!(bind_key(alg, key, Verify), Ok(()), "verify {alg}");
}
}
#[test]
fn binding_rejects_the_wrong_family() {
assert_eq!(bind_key(&A::HS256, &keys::rsa(2048), Verify), MISMATCH);
assert_eq!(bind_key(&A::HS256, &keys::rsa(2048), Sign), MISMATCH);
assert_eq!(bind_key(&A::RS256, &keys::oct(64, ""), Verify), MISMATCH);
assert_eq!(bind_key(&A::ES256, &keys::p384(), Verify), MISMATCH);
assert_eq!(bind_key(&A::ES256K, &keys::p256(""), Verify), MISMATCH);
assert_eq!(bind_key(&A::ES256, &keys::secp256k1(), Verify), MISMATCH);
assert_eq!(bind_key(&A::EdDSA, &keys::x25519(), Verify), MISMATCH);
assert_eq!(bind_key(&A::Ed25519, &keys::x25519(), Verify), MISMATCH);
assert_eq!(bind_key(&A::ES256, &keys::ed25519(""), Verify), MISMATCH);
}
#[test]
fn binding_requires_the_jwk_alg_to_match_exactly() {
let eddsa = keys::ed25519_private(r#","alg":"EdDSA""#);
assert_eq!(bind_key(&A::Ed25519, &eddsa, Verify), MISMATCH);
assert_eq!(bind_key(&A::Ed25519, &eddsa, Sign), MISMATCH);
assert_eq!(bind_key(&A::EdDSA, &eddsa, Verify), Ok(()));
let ed25519 = keys::ed25519(r#","alg":"Ed25519""#);
assert_eq!(bind_key(&A::EdDSA, &ed25519, Verify), MISMATCH);
assert_eq!(bind_key(&A::Ed25519, &ed25519, Verify), Ok(()));
let hs256 = keys::oct(64, r#","alg":"HS256""#);
assert_eq!(bind_key(&A::HS512, &hs256, Verify), MISMATCH);
assert_eq!(bind_key(&A::HS256, &hs256, Verify), Ok(()));
let a128kw = keys::oct(64, r#","alg":"A128KW""#);
assert_eq!(bind_key(&A::HS256, &a128kw, Verify), MISMATCH);
let unknown = keys::oct(64, r#","alg":"HS1""#);
assert_eq!(bind_key(&A::HS256, &unknown, Verify), MISMATCH);
}
#[test]
fn binding_honours_use_and_key_ops() {
let enc = keys::oct(64, r#","use":"enc""#);
assert_eq!(bind_key(&A::HS256, &enc, Verify), MISMATCH);
assert_eq!(bind_key(&A::HS256, &enc, Sign), MISMATCH);
let other = keys::p256(r#","use":"other""#);
assert_eq!(bind_key(&A::ES256, &other, Verify), MISMATCH);
let sig = keys::p256(r#","use":"sig""#);
assert_eq!(bind_key(&A::ES256, &sig, Verify), Ok(()));
let sign_only = keys::p256_private(r#","key_ops":["sign"]"#);
assert_eq!(bind_key(&A::ES256, &sign_only, Verify), MISMATCH);
assert_eq!(bind_key(&A::ES256, &sign_only, Sign), Ok(()));
let verify_only = keys::oct(64, r#","key_ops":["verify"]"#);
assert_eq!(bind_key(&A::HS256, &verify_only, Sign), MISMATCH);
assert_eq!(bind_key(&A::HS256, &verify_only, Verify), Ok(()));
}
#[test]
fn binding_refuses_a_public_key_for_signing() {
assert_eq!(bind_key(&A::ES256, &keys::p256(""), Sign), MISMATCH);
assert_eq!(bind_key(&A::Ed25519, &keys::ed25519(""), Sign), MISMATCH);
let public = keys::p256_private("").to_public().unwrap();
assert_eq!(bind_key(&A::ES256, &public, Sign), MISMATCH);
}
#[test]
fn binding_enforces_the_hmac_key_minimum_both_ways() {
for (alg, min) in [(A::HS256, 32), (A::HS384, 48), (A::HS512, 64)] {
let short = keys::oct(min - 1, "");
for op in [Sign, Verify] {
assert_eq!(
bind_key(&alg, &short, op).map_err(|e| e.code()),
Err("InvalidKey"),
"{alg} {op:?}"
);
assert_eq!(bind_key(&alg, &keys::oct(min, ""), op), Ok(()));
}
}
}
#[test]
fn binding_enforces_the_rsa_modulus_range() {
let mut n = vec![0xff; 8200 / 8];
n[0] = 0x7f;
let n = b64url_encode(&n);
assert_eq!(
Jwk::from_json(format!(r#"{{"kty":"RSA","n":"{n}","e":"AQAB"}}"#).as_bytes()).err(),
Some(Error::InvalidKey("RSA modulus is longer than 8192 bits"))
);
assert_eq!(bind_key(&A::PS256, &keys::rsa(8192), Verify), Ok(()));
}
#[test]
fn rsa_signing_binds_like_every_other_family() {
let private = keys::rsa_private("");
let encrypt_only = keys::rsa_private(r#","key_ops":["encrypt"]"#);
assert_eq!(
bind_key(&A::RS256, &encrypt_only, Verify),
Err(Error::KeyAlgorithmMismatch)
);
for alg in [A::RS256, A::RS384, A::RS512, A::PS256, A::PS384, A::PS512] {
assert_eq!(bind_key(&alg, &private, Sign), Ok(()), "{alg}");
assert_eq!(bind_key(&alg, &private, Verify), Ok(()), "{alg}");
for key in [keys::rsa(2048), keys::oct(64, ""), encrypt_only.clone()] {
assert_eq!(
bind_key(&alg, &key, Sign),
Err(Error::KeyAlgorithmMismatch),
"{alg}"
);
}
}
assert_eq!(
bind_key(&A::PS256, &keys::rsa_private(r#","alg":"RS256""#), Sign),
Err(Error::KeyAlgorithmMismatch)
);
}
#[test]
fn binding_refuses_none_and_unknown() {
for op in [Sign, Verify] {
assert_eq!(
bind_key(&A::None, &keys::oct(64, ""), op),
Err(Error::AlgorithmNotAllowed)
);
assert_eq!(
bind_key(&A::Unknown("HS1".into()), &keys::oct(64, ""), op),
Err(Error::UnsupportedAlgorithm)
);
}
}
#[test]
fn every_known_alg_but_none_has_a_family() {
use Family as F;
use SigningAlg as A;
let expected = [
(A::HS256, F::Hmac),
(A::HS384, F::Hmac),
(A::HS512, F::Hmac),
(A::RS256, F::Rsa),
(A::RS384, F::Rsa),
(A::RS512, F::Rsa),
(A::PS256, F::Rsa),
(A::PS384, F::Rsa),
(A::PS512, F::Rsa),
(A::ES256, F::Ecdsa),
(A::ES384, F::Ecdsa),
(A::ES512, F::Ecdsa),
(A::ES256K, F::Ecdsa),
(A::EdDSA, F::EdDsa),
(A::Ed25519, F::EdDsa),
];
for (alg, fam) in &expected {
assert_eq!(family(alg), Some(*fam), "{alg}");
}
assert_eq!(expected.len() + 1, SigningAlg::KNOWN.len());
assert_eq!(family(&A::None), None);
assert_eq!(family(&A::Unknown("HS1".into())), None);
}
#[test]
fn dispatch_refuses_none_and_unknown() {
let key = oct();
let unknown = SigningAlg::Unknown("HS1".into());
assert_eq!(
(FAMILIES.sign)(&SigningAlg::None, &key, b"x"),
Err(Error::AlgorithmNotAllowed)
);
assert_eq!(
(FAMILIES.verify)(&SigningAlg::None, &key, b"x", b""),
Err(Error::AlgorithmNotAllowed)
);
assert_eq!(
(FAMILIES.sign)(&unknown, &key, b"x"),
Err(Error::UnsupportedAlgorithm)
);
assert_eq!(
(FAMILIES.verify)(&unknown, &key, b"x", b""),
Err(Error::UnsupportedAlgorithm)
);
}
#[test]
fn rsa_sign_dispatches_to_the_rsa_family() {
for alg in [SigningAlg::RS256, SigningAlg::PS512] {
assert_eq!(
(FAMILIES.sign)(&alg, &oct(), b"x"),
Err(Error::KeyAlgorithmMismatch)
);
let sig = (FAMILIES.sign)(&alg, &test_keys::rsa_private(""), b"x").unwrap();
assert_eq!(sig.len(), 256);
}
}
#[test]
fn fake_families_round_trip_and_reject_tampering() {
let key = oct();
let alg = SigningAlg::HS256;
let sig = (fake::FAKE.sign)(&alg, &key, b"input").unwrap();
assert_eq!((fake::FAKE.verify)(&alg, &key, b"input", &sig), Ok(()));
assert_eq!(
(fake::FAKE.verify)(&alg, &key, b"inpuT", &sig),
Err(Error::InvalidSignature)
);
assert_eq!(
(fake::FAKE.verify)(&SigningAlg::HS384, &key, b"input", &sig),
Err(Error::InvalidSignature)
);
assert_eq!(
(fake::REJECTING.verify)(&alg, &key, b"input", &sig),
Err(Error::InvalidSignature)
);
}
}