use crate::error::{Error, Result};
use crate::jwa::SigningAlg;
use crate::jwk::{EcCurve, EcKey, Jwk, KeyMaterial};
use crate::rng::CryptoRngAdapter;
pub(crate) fn sign(alg: &SigningAlg, key: &Jwk, signing_input: &[u8]) -> Result<Vec<u8>> {
sign_with_rng(alg, key, signing_input, &mut CryptoRngAdapter::os())
}
fn sign_with_rng(
alg: &SigningAlg,
key: &Jwk,
signing_input: &[u8],
rng: &mut CryptoRngAdapter<'_>,
) -> Result<Vec<u8>> {
let ec = bound_ec_key(alg, key)?;
let d = ec.d().ok_or(Error::KeyAlgorithmMismatch)?;
match ec.crv() {
EcCurve::P256 => p256_ops::sign(d, signing_input, rng),
EcCurve::P384 => p384_ops::sign(d, signing_input, rng),
EcCurve::P521 => p521_ops::sign(d, signing_input, rng),
EcCurve::Secp256k1 => k256_ops::sign(d, signing_input, rng),
}
}
pub(crate) fn verify(
alg: &SigningAlg,
key: &Jwk,
signing_input: &[u8],
signature: &[u8],
) -> Result<()> {
let ec = bound_ec_key(alg, key)?;
let (x, y) = (ec.x(), ec.y());
match ec.crv() {
EcCurve::P256 => p256_ops::verify(x, y, signing_input, signature),
EcCurve::P384 => p384_ops::verify(x, y, signing_input, signature),
EcCurve::P521 => p521_ops::verify(x, y, signing_input, signature),
EcCurve::Secp256k1 => k256_ops::verify(x, y, signing_input, signature),
}
}
fn bound_ec_key<'k>(alg: &SigningAlg, key: &'k Jwk) -> Result<&'k EcKey> {
let curve = match alg {
SigningAlg::ES256 => EcCurve::P256,
SigningAlg::ES384 => EcCurve::P384,
SigningAlg::ES512 => EcCurve::P521,
SigningAlg::ES256K => EcCurve::Secp256k1,
_ => return Err(Error::KeyAlgorithmMismatch),
};
match key.material() {
KeyMaterial::Ec(ec) if ec.crv() == curve => Ok(ec),
_ => Err(Error::KeyAlgorithmMismatch),
}
}
const COORDINATE_LENGTH: &str = "EC coordinate has the wrong length for crv";
const POINT_INVALID: &str = "EC point is not on the curve";
const SCALAR_LENGTH: &str = "EC private key d has the wrong length for crv";
const SCALAR_INVALID: &str = "EC private key d is out of range";
macro_rules! curve_ops {
($(#[$doc:meta])* $module:ident, $krate:ident) => {
$(#[$doc])*
pub(super) mod $module {
use $krate::ecdsa::signature::{RandomizedSigner, Verifier};
use $krate::ecdsa::{Signature, SigningKey, VerifyingKey};
use $krate::{FieldBytes, Sec1Point};
use super::{COORDINATE_LENGTH, POINT_INVALID, SCALAR_INVALID, SCALAR_LENGTH};
use crate::error::{Error, Result};
use crate::rng::CryptoRngAdapter;
pub(crate) fn signing_key(d: &[u8]) -> Result<SigningKey> {
let d = <&FieldBytes>::try_from(d).map_err(|_| Error::InvalidKey(SCALAR_LENGTH))?;
SigningKey::from_bytes(d).map_err(|_| Error::InvalidKey(SCALAR_INVALID))
}
pub(crate) fn sign(
d: &[u8],
signing_input: &[u8],
rng: &mut CryptoRngAdapter<'_>,
) -> Result<Vec<u8>> {
let key = signing_key(d)?;
let signature: Signature = key
.try_sign_with_rng(rng, signing_input)
.map_err(|_| Error::RandomnessUnavailable)?;
Ok(signature.normalize_s().to_bytes().to_vec())
}
pub(crate) fn verifying_key(x: &[u8], y: &[u8]) -> Result<VerifyingKey> {
let (Ok(x), Ok(y)) = (<&FieldBytes>::try_from(x), <&FieldBytes>::try_from(y))
else {
return Err(Error::InvalidKey(COORDINATE_LENGTH));
};
let point = Sec1Point::from_affine_coordinates(x, y, false);
VerifyingKey::from_sec1_point(&point).map_err(|_| Error::InvalidKey(POINT_INVALID))
}
pub(crate) fn verify(
x: &[u8],
y: &[u8],
signing_input: &[u8],
signature: &[u8],
) -> Result<()> {
let key = verifying_key(x, y)?;
let signature =
Signature::from_slice(signature).map_err(|_| Error::InvalidSignature)?;
key.verify(signing_input, &signature.normalize_s())
.map_err(|_| Error::InvalidSignature)
}
}
};
}
curve_ops!(
p256_ops,
p256
);
curve_ops!(
p384_ops,
p384
);
curve_ops!(
p521_ops,
p521
);
curve_ops!(
k256_ops,
k256
);
#[cfg(test)]
mod tests {
use super::*;
use crate::util::b64url_decode;
use SigningAlg as A;
fn jwk(json: &str) -> Jwk {
Jwk::from_json(json.as_bytes()).unwrap()
}
fn b64(s: &str) -> Vec<u8> {
b64url_decode(s).unwrap()
}
fn ec_jwk(crv: &str, x: &str, y: &str, d: Option<&str>) -> Jwk {
let d = d.map(|d| format!(r#","d":"{d}""#)).unwrap_or_default();
jwk(&format!(
r#"{{"kty":"EC","crv":"{crv}","x":"{x}","y":"{y}"{d}}}"#
))
}
const A3_X: &str = "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU";
const A3_Y: &str = "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0";
const A3_D: &str = "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI";
const A3_INPUT: &str = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ";
const A3_SIG: &str =
"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q";
const A4_X: &str =
"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk";
const A4_Y: &str =
"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2";
const A4_D: &str =
"AY5pb7A0UFiB3RELSD64fTLOSV_jazdF7fLYyuTw8lOfRhWg6Y6rUrPAxerEzgdRhajnu0ferB0d53vM9mE15j2C";
const A4_INPUT: &str = "eyJhbGciOiJFUzUxMiJ9.UGF5bG9hZA";
const A4_SIG: &str = "AdwMgeerwtHoh-l192l60hp9wAHZFVJbLfD_UxMi70cwnZOYaRI1bKPWROc-mZZqwqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8KpEHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn";
const RFC7520_KEY: &str = r#"{"kty":"EC","kid":"bilbo.baggins@hobbiton.example","use":"sig","crv":"P-521","x":"AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt","y":"AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1","d":"AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt"}"#;
const RFC7520_INPUT: &str = "eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4";
const RFC7520_SIG: &str = "AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvbu9Plon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kvAD890jl8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2";
fn a3_public() -> Jwk {
ec_jwk("P-256", A3_X, A3_Y, None)
}
fn a4_public() -> Jwk {
ec_jwk("P-521", A4_X, A4_Y, None)
}
fn rfc_vectors() -> Vec<(SigningAlg, Jwk, &'static str, Vec<u8>)> {
vec![
(A::ES256, a3_public(), A3_INPUT, b64(A3_SIG)),
(A::ES512, a4_public(), A4_INPUT, b64(A4_SIG)),
(A::ES512, jwk(RFC7520_KEY), RFC7520_INPUT, b64(RFC7520_SIG)),
]
}
#[test]
fn rfc7515_a3_es256_verifies() {
let sig = b64(A3_SIG);
assert_eq!(sig.len(), 64);
assert_eq!(
verify(&A::ES256, &a3_public(), A3_INPUT.as_bytes(), &sig),
Ok(())
);
}
#[test]
fn rfc7515_a3_is_a_high_s_signature_and_its_twin_verifies_too() {
let sig = p256::ecdsa::Signature::from_slice(&b64(A3_SIG)).unwrap();
let low = sig.normalize_s();
assert_ne!(low, sig, "A.3 is expected to be high-S");
let low = low.to_bytes().to_vec();
assert_eq!(
verify(&A::ES256, &a3_public(), A3_INPUT.as_bytes(), &low),
Ok(())
);
}
#[test]
fn rfc7515_a4_es512_verifies() {
let sig = b64(A4_SIG);
assert_eq!(sig.len(), 132);
assert_eq!(
verify(&A::ES512, &a4_public(), A4_INPUT.as_bytes(), &sig),
Ok(())
);
}
#[test]
fn rfc7520_4_3_es512_verifies_with_the_3_2_key() {
let sig = b64(RFC7520_SIG);
assert_eq!(sig.len(), 132);
let key = jwk(RFC7520_KEY);
assert_eq!(
verify(&A::ES512, &key, RFC7520_INPUT.as_bytes(), &sig),
Ok(())
);
let public = key.to_public().unwrap();
assert_eq!(
verify(&A::ES512, &public, RFC7520_INPUT.as_bytes(), &sig),
Ok(())
);
}
#[test]
fn a3_any_one_bit_flip_of_the_signature_fails() {
let key = a3_public();
let sig = b64(A3_SIG);
for bit in 0..sig.len() * 8 {
let mut flipped = sig.clone();
flipped[bit / 8] ^= 1 << (bit % 8);
assert_eq!(
verify(&A::ES256, &key, A3_INPUT.as_bytes(), &flipped),
Err(Error::InvalidSignature),
"bit {bit}"
);
}
}
#[test]
fn rfc_vectors_reject_a_flipped_bit_in_signature_or_input() {
for (alg, key, input, sig) in rfc_vectors() {
let n = sig.len();
for bit in [0, 7, n * 2, n * 4 - 1, n * 4, n * 6, n * 8 - 1] {
let mut flipped = sig.clone();
flipped[bit / 8] ^= 1 << (bit % 8);
assert_eq!(
verify(&alg, &key, input.as_bytes(), &flipped),
Err(Error::InvalidSignature),
"{alg} signature bit {bit}"
);
}
for at in [0, input.len() / 2, input.len() - 1] {
let mut tampered = input.as_bytes().to_vec();
tampered[at] ^= 1;
assert_eq!(
verify(&alg, &key, &tampered, &sig),
Err(Error::InvalidSignature),
"{alg} input byte {at}"
);
}
}
}
fn der(rs: &[u8]) -> Vec<u8> {
let (r, s) = rs.split_at(rs.len() / 2);
let int = |v: &[u8]| {
let v: Vec<u8> = v.iter().copied().skip_while(|&b| b == 0).collect();
let mut out = vec![0x02];
let pad = v.first().is_some_and(|&b| b >= 0x80);
out.push(u8::try_from(v.len() + usize::from(pad)).unwrap());
if pad {
out.push(0);
}
out.extend(v);
out
};
let body = [int(r), int(s)].concat();
[vec![0x30, u8::try_from(body.len()).unwrap()], body].concat()
}
#[test]
fn verify_requires_the_exact_r_s_length_and_never_accepts_der() {
for (alg, key, input, sig) in rfc_vectors() {
let n = sig.len();
let mut long = sig.clone();
long.push(0);
let mut zero_prefixed = vec![0];
zero_prefixed.extend(&sig);
let wrong = [
Vec::new(),
sig[..n - 1].to_vec(),
long,
zero_prefixed,
[sig.clone(), sig.clone()].concat(),
sig[..n / 2].to_vec(),
];
for bad in &wrong {
assert_eq!(
verify(&alg, &key, input.as_bytes(), bad),
Err(Error::InvalidSignature),
"{alg}: {} octets",
bad.len()
);
}
}
let der = der(&b64(A3_SIG));
assert_eq!(der.len(), 71);
assert_eq!(
verify(&A::ES256, &a3_public(), A3_INPUT.as_bytes(), &der),
Err(Error::InvalidSignature)
);
}
#[test]
fn verify_rejects_zero_r_or_s() {
for (alg, key, input, sig) in rfc_vectors() {
let half = sig.len() / 2;
let zero_r = [vec![0; half], sig[half..].to_vec()].concat();
let zero_s = [sig[..half].to_vec(), vec![0; half]].concat();
for bad in [vec![0; sig.len()], zero_r, zero_s] {
assert_eq!(
verify(&alg, &key, input.as_bytes(), &bad),
Err(Error::InvalidSignature),
"{alg}"
);
}
}
}
#[test]
fn verify_rejects_r_or_s_not_below_the_group_order() {
let n = hex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551");
let sig = b64(A3_SIG);
let all_ones = vec![0xff; 32];
for bad in [
[n.clone(), sig[32..].to_vec()].concat(),
[sig[..32].to_vec(), n.clone()].concat(),
[all_ones.clone(), all_ones].concat(),
] {
assert_eq!(
verify(&A::ES256, &a3_public(), A3_INPUT.as_bytes(), &bad),
Err(Error::InvalidSignature)
);
}
}
fn hex(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn public_point_is_checked_at_use() {
let (x, y) = (b64(A3_X), b64(A3_Y));
let mut y_off = y.clone();
y_off[31] ^= 1;
let point_error =
|r: Result<p256::ecdsa::VerifyingKey>| r.map(|_| ()).map_err(|e| e.code());
assert_eq!(point_error(p256_ops::verifying_key(&x, &y)), Ok(()));
assert_eq!(
point_error(p256_ops::verifying_key(&x, &y_off)),
Err("InvalidKey")
);
assert_eq!(
point_error(p256_ops::verifying_key(&[0; 32], &[0; 32])),
Err("InvalidKey")
);
assert_eq!(
point_error(p256_ops::verifying_key(&x[1..], &y)),
Err("InvalidKey")
);
assert!(p384_ops::verifying_key(&[0; 48], &[0; 48]).is_err());
assert!(p521_ops::verifying_key(&[0; 66], &[0; 66]).is_err());
assert!(k256_ops::verifying_key(&[0; 32], &[0; 32]).is_err());
let (x, mut y) = (b64(A4_X), b64(A4_Y));
y[65] ^= 1;
assert!(p521_ops::verifying_key(&x, &y).is_err());
assert_eq!(
p256_ops::verify(&b64(A3_X), &y_off, A3_INPUT.as_bytes(), b"not a signature")
.map_err(|e| e.code()),
Err("InvalidKey")
);
}
#[test]
fn a_key_of_another_curve_or_family_is_refused_not_used() {
let sig = b64(A3_SIG);
let input = A3_INPUT.as_bytes();
let oct = crate::jws::alg::test_keys::oct(32, "");
let cases = [
(A::ES384, a3_public()),
(A::ES256K, a3_public()),
(A::ES256, a4_public()),
(A::ES256, oct),
(A::HS256, a3_public()),
];
for (alg, key) in &cases {
assert_eq!(
verify(alg, key, input, &sig),
Err(Error::KeyAlgorithmMismatch),
"{alg}"
);
}
}
use crate::rng::insecure_test_vectors::FixedRng;
const ALL: [SigningAlg; 4] = [A::ES256, A::ES384, A::ES512, A::ES256K];
fn signature_len(alg: &SigningAlg) -> usize {
match alg {
A::ES256 | A::ES256K => 64,
A::ES384 => 96,
_ => 132,
}
}
fn derived_jwk(crv: &str, d: &[u8], xy: (Vec<u8>, Vec<u8>)) -> Jwk {
let e = crate::util::b64url_encode;
ec_jwk(crv, &e(&xy.0), &e(&xy.1), Some(&e(d)))
}
fn private_key(alg: &SigningAlg) -> Jwk {
macro_rules! derived {
($krate:ident, $crv:literal, $d:expr) => {{
use $krate::elliptic_curve::sec1::ToSec1Point;
let d: &[u8] = $d;
let public = $krate::SecretKey::from_slice(d).unwrap().public_key();
let point = public.to_sec1_point(false);
let xy = (point.x().unwrap().to_vec(), point.y().unwrap().to_vec());
derived_jwk($crv, d, xy)
}};
}
match alg {
A::ES256 => ec_jwk("P-256", A3_X, A3_Y, Some(A3_D)),
A::ES512 => ec_jwk("P-521", A4_X, A4_Y, Some(A4_D)),
A::ES384 => derived!(p384, "P-384", &[0x38; 48]),
_ => derived!(k256, "secp256k1", &[0x6b; 32]),
}
}
fn low_s_and_twin(alg: &SigningAlg, sig: &[u8]) -> (bool, Vec<u8>) {
macro_rules! twin {
($krate:ident) => {{
use $krate::elliptic_curve::ff::PrimeField;
let sig = $krate::ecdsa::Signature::from_slice(sig).unwrap();
let (r, s) = sig.split_scalars();
let twin =
$krate::ecdsa::Signature::from_scalars(r.to_repr(), (-*s).to_repr()).unwrap();
(sig.normalize_s() == sig, twin.to_bytes().to_vec())
}};
}
match alg {
A::ES256 => twin!(p256),
A::ES384 => twin!(p384),
A::ES512 => twin!(p521),
_ => twin!(k256),
}
}
#[test]
fn rfc_private_keys_sign_what_their_public_keys_verify() {
for (alg, key) in [
(A::ES256, private_key(&A::ES256)),
(A::ES512, private_key(&A::ES512)),
(A::ES512, jwk(RFC7520_KEY)),
] {
let sig = sign(&alg, &key, b"RFC 7515 \xc2\xa75.1").unwrap();
assert_eq!(sig.len(), signature_len(&alg));
let public = key.to_public().unwrap();
assert_eq!(verify(&alg, &public, b"RFC 7515 \xc2\xa75.1", &sig), Ok(()));
assert_eq!(
verify(&alg, &public, b"RFC 7515 \xc2\xa75.2", &sig),
Err(Error::InvalidSignature)
);
}
}
#[test]
fn two_hundred_signatures_per_curve_are_low_s_and_verify_in_both_forms() {
for alg in &ALL {
let key = private_key(alg);
let public = key.to_public().unwrap();
let mut seen = std::collections::BTreeSet::new();
for i in 0u32..200 {
let input = format!("eyJhbGciOiJFUzI1NiJ9.{i}");
let sig = sign(alg, &key, input.as_bytes()).unwrap();
assert_eq!(sig.len(), signature_len(alg), "{alg} #{i}");
let (low, twin) = low_s_and_twin(alg, &sig);
assert!(low, "{alg} #{i}: high-S emitted");
assert_ne!(twin, sig);
assert_eq!(
verify(alg, &public, input.as_bytes(), &sig),
Ok(()),
"{alg} #{i}"
);
assert_eq!(
verify(alg, &public, input.as_bytes(), &twin),
Ok(()),
"{alg} #{i} twin"
);
assert!(seen.insert(sig), "{alg} #{i}: repeated signature");
}
}
}
#[test]
fn signing_is_hedged_not_deterministic() {
for alg in &ALL {
let key = private_key(alg);
let a = sign(alg, &key, b"same input").unwrap();
let b = sign(alg, &key, b"same input").unwrap();
assert_ne!(a, b, "{alg}");
}
}
#[test]
fn signing_draws_exactly_one_field_element_of_entropy() {
for alg in &ALL {
let key = private_key(alg);
let len = signature_len(alg) / 2;
let sign_fixed = |bytes: Vec<u8>| {
let mut fixed = FixedRng::new(bytes);
let sig = sign_with_rng(
alg,
&key,
b"input",
&mut CryptoRngAdapter::fixed(&mut fixed),
);
(sig, fixed.remaining())
};
let (a, left) = sign_fixed(vec![1; len]);
assert_eq!(left, 0, "{alg}");
let a = a.unwrap();
assert_eq!(sign_fixed(vec![1; len]).0.as_ref(), Ok(&a), "{alg}");
assert_ne!(sign_fixed(vec![2; len]).0.as_ref(), Ok(&a), "{alg}");
assert_eq!(
sign_fixed(vec![1; len - 1]).0,
Err(Error::RandomnessUnavailable),
"{alg}"
);
let public = key.to_public().unwrap();
assert_eq!(verify(alg, &public, b"input", &a), Ok(()));
}
}
#[test]
fn a_getrandom_failure_is_randomness_unavailable_on_every_curve() {
let injected = [
getrandom::Error::UNSUPPORTED,
getrandom::Error::UNEXPECTED,
getrandom::Error::new_custom(7),
];
for alg in &ALL {
let key = private_key(alg);
for e in injected {
assert_eq!(
sign_with_rng(alg, &key, b"input", &mut CryptoRngAdapter::failing(e)),
Err(Error::RandomnessUnavailable),
"{alg} {e:?}"
);
}
}
}
#[test]
fn private_scalar_is_checked_at_use() {
let code = |r: Result<()>| r.map_err(|e| e.code());
let d = b64(A3_D);
let n = hex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551");
assert_eq!(code(p256_ops::signing_key(&d).map(|_| ())), Ok(()));
for bad in [
d[1..].to_vec(),
[vec![0], d.clone()].concat(),
vec![0; 32],
n,
] {
assert_eq!(
code(p256_ops::signing_key(&bad).map(|_| ())),
Err("InvalidKey"),
"{} octets",
bad.len()
);
}
let d = b64(A4_D);
assert!(p521_ops::signing_key(&d).is_ok());
assert!(p521_ops::signing_key(&d[1..]).is_err());
assert!(p384_ops::signing_key(&[0x38; 47]).is_err());
assert!(k256_ops::signing_key(&[0x6b; 31]).is_err());
assert!(k256_ops::signing_key(&[0; 32]).is_err());
}
#[test]
fn signing_refuses_a_key_of_another_curve_family_or_without_d() {
let cases = [
(A::ES384, private_key(&A::ES256)),
(A::ES256K, private_key(&A::ES256)),
(A::ES256, private_key(&A::ES256).to_public().unwrap()),
(A::ES256, crate::jws::alg::test_keys::oct(32, "")),
(A::HS256, private_key(&A::ES256)),
];
for (alg, key) in &cases {
assert_eq!(
sign(alg, key, b"x"),
Err(Error::KeyAlgorithmMismatch),
"{alg}"
);
}
}
#[test]
fn sign_compact_then_verify_compact_for_every_alg() {
use crate::jws::{
ProtectedHeader, SignOptions, VerifyOptions, sign_compact, verify_compact,
};
use crate::util::b64url_encode;
for alg in &ALL {
let key = private_key(alg);
let header = format!(r#"{{"alg":"{alg}"}}"#);
let protected =
ProtectedHeader::from_encoded(&b64url_encode(header.as_bytes())).unwrap();
let jws = sign_compact(&protected, b"payload", &key, &SignOptions::new()).unwrap();
let (head, sig) = jws.rsplit_once('.').unwrap();
let sig = b64(sig);
assert_eq!(sig.len(), signature_len(alg), "{alg}");
let algs = [alg.clone()];
let opts = VerifyOptions::new(&algs).unwrap();
let public = key.to_public().unwrap();
let verified = verify_compact(&jws, &public, &opts).unwrap();
assert_eq!(
(&verified.alg, verified.payload.as_slice()),
(alg, &b"payload"[..])
);
let with_sig = |sig: &[u8]| format!("{head}.{}", b64url_encode(sig));
let (_, twin) = low_s_and_twin(alg, &sig);
assert!(
verify_compact(&with_sig(&twin), &public, &opts).is_ok(),
"{alg} twin"
);
if *alg != A::ES512 {
assert_eq!(
verify_compact(&with_sig(&der(&sig)), &public, &opts).err(),
Some(Error::InvalidSignature),
"{alg} DER"
);
}
let (first, _) = head.split_once('.').unwrap();
let other = format!(
"{first}.{}.{}",
b64url_encode(b"payloaD"),
b64url_encode(&sig)
);
assert_eq!(
verify_compact(&other, &public, &opts).err(),
Some(Error::InvalidSignature),
"{alg} payload"
);
}
}
mod all_curves {
use super::*;
#[test]
fn malformed_signatures_are_invalid_signature_on_every_curve() {
for alg in &ALL {
let private = private_key(alg);
let public = private.to_public().unwrap();
let sig = sign(alg, &private, b"input").unwrap();
let n = sig.len();
assert_eq!(n, signature_len(alg));
assert_eq!(verify(alg, &public, b"input", &sig), Ok(()));
let half = n / 2;
let mut long = sig.clone();
long.push(0);
let mut flipped = sig.clone();
flipped[n - 1] ^= 1;
let mut flipped_r = sig.clone();
flipped_r[0] ^= 0x80;
let bad = [
Vec::new(),
sig[..n - 1].to_vec(),
long,
vec![0; n],
[vec![0; half], sig[half..].to_vec()].concat(),
[sig[..half].to_vec(), vec![0; half]].concat(),
vec![0xff; n],
der(&sig),
flipped,
flipped_r,
];
for b in &bad {
assert_eq!(
verify(alg, &public, b"input", b),
Err(Error::InvalidSignature),
"{alg}: {} octets",
b.len()
);
}
assert_eq!(
verify(alg, &public, b"inpuu", &sig),
Err(Error::InvalidSignature),
"{alg}"
);
}
}
#[test]
fn an_off_curve_key_beats_a_bad_signature_on_every_curve() {
macro_rules! check {
($ops:ident, $alg:expr) => {{
let key = private_key(&$alg);
let public = key.to_public().unwrap();
let KeyMaterial::Ec(ec) = public.material() else {
unreachable!()
};
let mut y = ec.y().to_vec();
*y.last_mut().unwrap() ^= 1;
for sig in [&b"junk"[..], &[]] {
assert_eq!(
$ops::verify(ec.x(), &y, b"input", sig).map_err(|e| e.code()),
Err("InvalidKey")
);
}
assert_eq!(
$ops::verify(ec.x(), &y[1..], b"input", b"junk").map_err(|e| e.code()),
Err("InvalidKey")
);
}};
}
check!(p256_ops, A::ES256);
check!(p384_ops, A::ES384);
check!(p521_ops, A::ES512);
check!(k256_ops, A::ES256K);
}
}
mod properties {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn sign_then_verify_round_trips_and_detects_changes(
input in proptest::collection::vec(any::<u8>(), 0..512),
bit in any::<usize>(),
) {
for alg in &ALL {
let key = private_key(alg);
let public = key.to_public().unwrap();
let sig = sign(alg, &key, &input).unwrap();
prop_assert_eq!(sig.len(), signature_len(alg));
prop_assert!(low_s_and_twin(alg, &sig).0);
prop_assert_eq!(verify(alg, &public, &input, &sig), Ok(()));
let mut flipped = sig.clone();
let bit = bit % (sig.len() * 8);
flipped[bit / 8] ^= 1 << (bit % 8);
prop_assert_eq!(verify(alg, &public, &input, &flipped), Err(Error::InvalidSignature));
let mut changed = input.clone();
changed.push(0);
prop_assert_eq!(verify(alg, &public, &changed, &sig), Err(Error::InvalidSignature));
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn arbitrary_signature_bytes_are_invalid_signature(
sig in proptest::collection::vec(any::<u8>(), 0..200),
) {
for alg in &ALL {
let public = private_key(alg).to_public().unwrap();
prop_assert_eq!(verify(alg, &public, b"input", &sig), Err(Error::InvalidSignature));
}
}
}
}
mod wycheproof {
use super::*;
use crate::test_support::wycheproof::{self, Deviation, Expected, Outcome, Summary};
const P256: &[Deviation] = &[];
const P384: &[Deviation] = &[];
const P521: &[Deviation] = &[];
const SECP256K1: &[Deviation] = &[];
fn run(suite: &str, alg: SigningAlg, deviations: &[Deviation]) -> (Summary, usize) {
let suite = wycheproof::load(suite);
let mut high_s = 0;
let summary = wycheproof::assert_conforms(&suite, deviations, |group, test| {
let (msg, sig) = test.signature();
let result = Jwk::from_json(group.verification_jwk().as_bytes())
.and_then(|key| verify(&alg, &key, msg, sig));
match &result {
Ok(()) if test.result == Expected::Valid => {
high_s += usize::from(!low_s_and_twin(&alg, sig).0);
}
Ok(()) => {}
Err(e) => assert!(
matches!(e, Error::InvalidSignature | Error::InvalidKey(_)),
"tcId {}: {e:?}",
test.tc_id
),
}
Outcome::of(&result)
});
(summary, high_s)
}
fn summary(tests: usize, accepted: usize) -> Summary {
Summary {
tests,
accepted,
rejected: tests - accepted,
deviations: 0,
}
}
#[test]
fn p256() {
let (s, high_s) = run("ecdsa_secp256r1_sha256_p1363", A::ES256, P256);
assert_eq!(s, summary(262, 173));
assert!(high_s > 0, "no high-S valid test exercised");
}
#[test]
fn p384() {
let (s, high_s) = run("ecdsa_secp384r1_sha384_p1363", A::ES384, P384);
assert_eq!(s, summary(280, 193));
assert!(high_s > 0, "no high-S valid test exercised");
}
#[test]
fn p521() {
let (s, high_s) = run("ecdsa_secp521r1_sha512_p1363", A::ES512, P521);
assert_eq!(s, summary(318, 231));
assert!(high_s > 0, "no high-S valid test exercised");
}
#[test]
fn secp256k1() {
let (s, high_s) = run("ecdsa_secp256k1_sha256_p1363", A::ES256K, SECP256K1);
assert_eq!(s, summary(252, 167));
assert!(high_s > 0, "no high-S valid test exercised");
}
}
}