use std::collections::BTreeSet;
use zeroize::Zeroizing;
use crate::error::{Error, Result};
use crate::header::Header;
use crate::json::{Object, Value};
use crate::jwa::SigningAlg;
use crate::jwk::Jwk;
use crate::util::b64url_encode;
use super::VerifyOptions;
use super::alg::{Families, Operation, bind_key};
pub(crate) const UNDERSTOOD_CRIT: &[&str] = &["b64"];
#[derive(Debug, Clone)]
pub(crate) struct JoseHeader {
protected: Option<Header>,
unprotected: Option<Header>,
union: Header,
}
impl JoseHeader {
pub(crate) fn new(protected: Option<Header>, unprotected: Option<Header>) -> Result<Self> {
let union = union(protected.as_ref(), unprotected.as_ref())?;
if let Some(u) = &unprotected {
if u.crit.is_some() {
return Err(Error::InvalidHeader("crit must be in the protected header"));
}
if u.b64.is_some() {
return Err(Error::InvalidHeader("b64 must be in the protected header"));
}
}
union.validate_crit()?;
if let Some(p) = &protected {
let b64_critical = p.crit.iter().flatten().any(|name| name == "b64");
if p.b64.is_some() && !b64_critical {
return Err(Error::InvalidHeader("b64 must be listed in crit"));
}
}
if union.alg.is_none() {
return Err(Error::InvalidHeader("missing alg"));
}
Ok(Self {
protected,
unprotected,
union,
})
}
#[cfg(test)]
pub(crate) fn protected(&self) -> Option<&Header> {
self.protected.as_ref()
}
#[cfg(test)]
pub(crate) fn unprotected(&self) -> Option<&Header> {
self.unprotected.as_ref()
}
#[cfg(test)]
pub(crate) fn union(&self) -> &Header {
&self.union
}
pub(crate) fn b64(&self) -> bool {
self.union.b64.unwrap_or(true)
}
pub(crate) fn into_parts(self) -> (Option<Header>, Option<Header>) {
(self.protected, self.unprotected)
}
pub(crate) fn into_union(self) -> Header {
self.union
}
}
fn union(protected: Option<&Header>, unprotected: Option<&Header>) -> Result<Header> {
let (p, u) = match (protected, unprotected) {
(Some(p), Some(u)) => (p, u),
(Some(only), None) | (None, Some(only)) => return Ok(only.clone()),
(None, None) => return Ok(Header::new()),
};
let p = p.to_object()?;
let u = u.to_object()?;
let names: BTreeSet<&str> = p.iter().map(|(name, _)| name).collect();
if u.iter().any(|(name, _)| names.contains(name)) {
return Err(Error::InvalidHeader(
"protected and unprotected headers share a parameter",
));
}
drop(names);
let members: Vec<(Zeroizing<String>, Value)> = p.into_iter().chain(u).collect();
Header::from_object(Object::from_unique_members(members))
}
pub(crate) fn common_b64(headers: &[JoseHeader]) -> Result<bool> {
let first = headers.first().is_none_or(JoseHeader::b64);
if headers.iter().all(|h| h.b64() == first) {
Ok(first)
} else {
Err(Error::InvalidHeader("b64 differs between signatures"))
}
}
pub(crate) fn signing_input(protected_b64: &str, payload: &[u8], b64: bool) -> Zeroizing<Vec<u8>> {
if b64 {
let encoded = Zeroizing::new(b64url_encode(payload));
signing_input_from_part(protected_b64, encoded.as_bytes())
} else {
signing_input_from_part(protected_b64, payload)
}
}
pub(crate) fn signing_input_from_part(
protected_b64: &str,
payload_part: &[u8],
) -> Zeroizing<Vec<u8>> {
let mut input = Zeroizing::new(Vec::with_capacity(
protected_b64.len() + 1 + payload_part.len(),
));
input.extend_from_slice(protected_b64.as_bytes());
input.push(b'.');
input.extend_from_slice(payload_part);
input
}
fn understood_alg(header: &JoseHeader) -> Result<SigningAlg> {
header.union.validate_crit_understood(UNDERSTOOD_CRIT)?;
header.union.signing_alg()
}
pub(crate) fn allowed_alg(header: &JoseHeader, opts: &VerifyOptions<'_>) -> Result<SigningAlg> {
let alg = understood_alg(header)?;
if alg == SigningAlg::None || !opts.allowed_algs.contains(&alg) {
return Err(Error::AlgorithmNotAllowed);
}
Ok(alg)
}
#[cfg(test)]
pub(crate) fn check_verify(
header: &JoseHeader,
key: &Jwk,
opts: &VerifyOptions<'_>,
) -> Result<SigningAlg> {
let alg = allowed_alg(header, opts)?;
bind_key(&alg, key, Operation::Verify)?;
Ok(alg)
}
pub(crate) fn verify_with_key(
families: &Families,
alg: &SigningAlg,
key: &Jwk,
signing_input: &[u8],
signature: &[u8],
) -> Result<()> {
bind_key(alg, key, Operation::Verify)?;
(families.verify)(alg, key, signing_input, signature)
}
pub(crate) fn verify_signature(
families: &Families,
header: &JoseHeader,
key: &Jwk,
opts: &VerifyOptions<'_>,
signing_input: &[u8],
signature: &[u8],
) -> Result<SigningAlg> {
let alg = allowed_alg(header, opts)?;
verify_with_key(families, &alg, key, signing_input, signature)?;
Ok(alg)
}
pub(crate) struct SignReady<'k> {
alg: SigningAlg,
key: &'k Jwk,
}
impl SignReady<'_> {
#[cfg(test)]
pub(crate) fn alg(&self) -> &SigningAlg {
&self.alg
}
pub(crate) fn sign(&self, families: &Families, signing_input: &[u8]) -> Result<Vec<u8>> {
(families.sign)(&self.alg, self.key, signing_input)
}
}
pub(crate) fn prepare_sign<'k>(header: &JoseHeader, key: &'k Jwk) -> Result<SignReady<'k>> {
let alg = understood_alg(header)?;
if alg == SigningAlg::None {
return Err(Error::AlgorithmNotAllowed);
}
bind_key(&alg, key, Operation::Sign)?;
Ok(SignReady { alg, key })
}
pub(crate) fn progress_rank(error: &Error) -> u8 {
match error {
Error::InvalidSignature => 6,
Error::InvalidKey(_) => 5,
Error::KeyAlgorithmMismatch => 4,
Error::AlgorithmNotAllowed => 3,
Error::UnsupportedAlgorithm => 2,
Error::UnknownCritical => 1,
_ => 0,
}
}
pub(crate) fn furthest(errors: impl IntoIterator<Item = Error>) -> Option<Error> {
errors.into_iter().fold(None, |best, e| match best {
Some(b) if progress_rank(&b) >= progress_rank(&e) => Some(b),
_ => Some(e),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jwa::SigningAlg;
use crate::jwk::Jwk;
use crate::jws::alg::fake::{FAKE, REJECTING, fake_signature};
use crate::jws::alg::test_keys as keys;
use crate::jws::{ProtectedHeader, VerifyOptions};
use crate::util::b64url_encode;
fn h(json: &str) -> Header {
Header::from_json(json.as_bytes()).unwrap()
}
fn jose(protected: Option<&str>, unprotected: Option<&str>) -> Result<JoseHeader> {
JoseHeader::new(protected.map(h), unprotected.map(h))
}
fn code(r: Result<JoseHeader>) -> &'static str {
r.err().map_or("ok", |e| e.code())
}
#[test]
fn signing_input_rfc7515_a1() {
let payload =
b"{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}";
let expected = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ";
let input = signing_input("eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9", payload, true);
assert_eq!(*input, expected.as_bytes());
assert_eq!(&input[..8], &[101, 121, 74, 48, 101, 88, 65, 105]);
assert_eq!(&input[input.len() - 4..], &[86, 108, 102, 81]);
}
#[test]
fn signing_input_rfc7797_section_4() {
assert_eq!(
*signing_input("eyJhbGciOiJIUzI1NiJ9", b"$.02", true),
b"eyJhbGciOiJIUzI1NiJ9.JC4wMg"
);
let protected = "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19";
let input = signing_input(protected, b"$.02", false);
assert_eq!(
*input,
b"eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19.$.02"
);
assert_eq!(&input[input.len() - 5..], &[46, 36, 46, 48, 50]);
}
#[test]
fn signing_input_is_wiped_on_drop() {
let input: Zeroizing<Vec<u8>> = signing_input("e30", b"secret", true);
assert_eq!(*input, b"e30.c2VjcmV0");
let part: Zeroizing<Vec<u8>> = signing_input_from_part("e30", b"c2VjcmV0");
assert_eq!(part, input);
}
#[test]
fn signing_input_from_part_matches_signing_input() {
for payload in [&b""[..], b"$.02", b"\x00\xff.", b"hello world"] {
let encoded = b64url_encode(payload);
assert_eq!(
signing_input_from_part("e30", encoded.as_bytes()),
signing_input("e30", payload, true)
);
assert_eq!(
signing_input_from_part("e30", payload),
signing_input("e30", payload, false)
);
}
}
#[test]
fn signing_input_edge_cases() {
assert_eq!(*signing_input("", b"$.02", true), b".JC4wMg");
assert_eq!(*signing_input("e30", b"", true), b"e30.");
assert_eq!(*signing_input("e30", b"", false), b"e30.");
assert_eq!(
*signing_input("e30", &[0, 0xff, b'.'], false),
b"e30.\x00\xff."
);
}
#[test]
fn union_of_disjoint_headers() {
let j = jose(
Some(r#"{"alg":"HS256","x-a":1}"#),
Some(r#"{"kid":"k1","x-b":2}"#),
)
.unwrap();
let u = j.union();
assert_eq!(u.alg.as_deref(), Some("HS256"));
assert_eq!(u.kid.as_deref(), Some("k1"));
assert!(u.contains("x-a") && u.contains("x-b"));
assert_eq!(j.protected(), Some(&h(r#"{"alg":"HS256","x-a":1}"#)));
assert_eq!(j.unprotected(), Some(&h(r#"{"kid":"k1","x-b":2}"#)));
assert!(j.b64());
let (p, up) = j.into_parts();
assert!(p.is_some() && up.is_some());
let only_p = jose(Some(r#"{"alg":"ES256"}"#), None).unwrap();
assert_eq!(only_p.union(), &h(r#"{"alg":"ES256"}"#));
let only_u = jose(None, Some(r#"{"alg":"HS256","kid":"k"}"#)).unwrap();
assert_eq!(only_u.union(), &h(r#"{"alg":"HS256","kid":"k"}"#));
assert!(only_u.protected().is_none());
}
#[test]
fn into_union_is_the_protected_header_for_compact() {
let json = r#"{"alg":"HS256","typ":"JWT","kid":"k","b64":false,"crit":["b64"]}"#;
let compact = jose(Some(json), None).unwrap();
assert_eq!(compact.clone().into_union(), h(json));
assert_eq!(compact.into_parts(), (Some(h(json)), None));
let both = jose(Some(r#"{"alg":"HS256"}"#), Some(r#"{"kid":"k"}"#)).unwrap();
assert_eq!(both.into_union(), h(r#"{"alg":"HS256","kid":"k"}"#));
}
#[test]
fn overlapping_headers_are_invalid() {
for (p, u) in [
(r#"{"alg":"HS256"}"#, r#"{"alg":"HS256"}"#),
(r#"{"alg":"HS256","kid":"a"}"#, r#"{"kid":"b"}"#),
(r#"{"alg":"HS256","x-a":1}"#, r#"{"x-a":1}"#),
] {
assert_eq!(code(jose(Some(p), Some(u))), "InvalidHeader", "{p} {u}");
}
}
#[test]
fn crit_must_be_protected_and_well_formed() {
assert_eq!(
code(jose(
Some(r#"{"alg":"HS256","x-a":1}"#),
Some(r#"{"crit":["x-a"]}"#)
)),
"InvalidHeader"
);
assert_eq!(
code(jose(
None,
Some(r#"{"alg":"HS256","crit":["x-a"],"x-a":1}"#)
)),
"InvalidHeader"
);
assert_eq!(
code(jose(Some(r#"{"alg":"HS256","crit":["x-a"]}"#), None)),
"InvalidHeader"
);
assert_eq!(
code(jose(
Some(r#"{"alg":"HS256","crit":["x-a"]}"#),
Some(r#"{"x-a":1}"#)
)),
"ok"
);
for crit in [r#"[]"#, r#"["alg"]"#, r#"["b64","b64"]"#, r#""b64""#] {
let json = format!(r#"{{"alg":"HS256","b64":false,"crit":{crit}}}"#);
assert_eq!(
Header::from_json(json.as_bytes())
.map(drop)
.unwrap_err()
.code(),
"InvalidHeader",
"{crit}"
);
}
}
#[test]
fn b64_must_be_protected_and_critical() {
assert_eq!(
code(jose(Some(r#"{"alg":"HS256"}"#), Some(r#"{"b64":false}"#))),
"InvalidHeader"
);
for p in [
r#"{"alg":"HS256","b64":false}"#,
r#"{"alg":"HS256","b64":true}"#,
r#"{"alg":"HS256","b64":false,"crit":["x-a"],"x-a":1}"#,
] {
assert_eq!(code(jose(Some(p), None)), "InvalidHeader", "{p}");
}
let f = jose(Some(r#"{"alg":"HS256","b64":false,"crit":["b64"]}"#), None).unwrap();
assert!(!f.b64());
let t = jose(Some(r#"{"alg":"HS256","b64":true,"crit":["b64"]}"#), None).unwrap();
assert!(t.b64());
assert_eq!(
code(jose(Some(r#"{"alg":"HS256","crit":["b64"]}"#), None)),
"InvalidHeader"
);
}
#[test]
fn alg_must_be_present() {
assert_eq!(code(jose(Some(r#"{"typ":"JWT"}"#), None)), "InvalidHeader");
assert_eq!(
code(jose(Some(r#"{"typ":"JWT"}"#), Some(r#"{"kid":"k"}"#))),
"InvalidHeader"
);
assert_eq!(code(jose(None, None)), "InvalidHeader");
assert_eq!(code(jose(Some(r#"{"alg":"HS1"}"#), None)), "ok");
}
#[test]
fn duplicate_header_member_is_invalid_json() {
for json in [
r#"{"alg":"HS256","alg":"none"}"#,
r#"{"alg":"HS256","alg":"none"}"#,
r#"{"alg":"HS256","b64":false,"crit":["b64"],"b64":true}"#,
] {
let parsed = Header::from_json(json.as_bytes()).map(drop);
assert_eq!(parsed.unwrap_err().code(), "InvalidJson", "{json}");
let encoded = b64url_encode(json.as_bytes());
let protected = ProtectedHeader::from_encoded(&encoded).map(drop);
assert_eq!(protected.unwrap_err().code(), "InvalidJson", "{json}");
}
}
#[test]
fn b64_must_agree_across_signatures() {
let f = || jose(Some(r#"{"alg":"HS256","b64":false,"crit":["b64"]}"#), None).unwrap();
let t = || jose(Some(r#"{"alg":"HS256","b64":true,"crit":["b64"]}"#), None).unwrap();
let absent = || jose(Some(r#"{"alg":"HS256"}"#), None).unwrap();
let unprotected_only = || jose(None, Some(r#"{"alg":"HS256"}"#)).unwrap();
assert_eq!(common_b64(&[f(), f()]), Ok(false));
assert_eq!(common_b64(&[t(), absent(), unprotected_only()]), Ok(true));
assert_eq!(common_b64(&[absent()]), Ok(true));
assert_eq!(common_b64(&[]), Ok(true));
for headers in [[f(), absent()], [absent(), f()], [t(), f()]] {
assert_eq!(
common_b64(&headers).map_err(|e| e.code()),
Err("InvalidHeader")
);
}
}
use SigningAlg as A;
fn hj(protected: &str) -> JoseHeader {
jose(Some(protected), None).unwrap()
}
fn opts(allowed: &[SigningAlg]) -> VerifyOptions<'_> {
VerifyOptions::new(allowed).unwrap()
}
fn verify_code(protected: &str, key: &Jwk, allowed: &[SigningAlg]) -> &'static str {
check_verify(&hj(protected), key, &opts(allowed)).map_or_else(|e| e.code(), |_| "ok")
}
#[test]
fn verify_policy_each_step() {
let hs = keys::oct(32, "");
assert_eq!(
verify_code(
r#"{"alg":"HS256","crit":["x-a"],"x-a":1}"#,
&hs,
&[A::HS256]
),
"UnknownCritical"
);
assert_eq!(
verify_code(
r#"{"alg":"HS256","b64":false,"crit":["b64"]}"#,
&hs,
&[A::HS256]
),
"ok"
);
assert_eq!(
verify_code(r#"{"alg":"HS1"}"#, &hs, &[A::HS256]),
"UnsupportedAlgorithm"
);
assert_eq!(
verify_code(r#"{"alg":"RSA1_5"}"#, &hs, &[A::HS256]),
"UnsupportedAlgorithm"
);
assert_eq!(
verify_code(r#"{"alg":"HS384"}"#, &hs, &[A::HS256]),
"AlgorithmNotAllowed"
);
assert_eq!(
verify_code(r#"{"alg":"none"}"#, &hs, &[A::HS256]),
"AlgorithmNotAllowed"
);
assert_eq!(
verify_code(r#"{"alg":"ES256"}"#, &hs, &[A::ES256]),
"KeyAlgorithmMismatch"
);
assert_eq!(
verify_code(r#"{"alg":"HS512"}"#, &hs, &[A::HS512]),
"InvalidKey"
);
assert_eq!(
check_verify(&hj(r#"{"alg":"HS256"}"#), &hs, &opts(&[A::HS256])),
Ok(A::HS256)
);
}
fn allowed_code(protected: &str, allowed: &[SigningAlg]) -> &'static str {
allowed_alg(&hj(protected), &opts(allowed)).map_or_else(|e| e.code(), |_| "ok")
}
#[test]
fn allowed_alg_runs_steps_5_to_7_without_a_key() {
assert_eq!(
allowed_code(r#"{"alg":"HS256","crit":["x-a"],"x-a":1}"#, &[A::HS256]),
"UnknownCritical"
);
assert_eq!(
allowed_code(r#"{"alg":"HS1","crit":["x-a"],"x-a":1}"#, &[A::HS256]),
"UnknownCritical"
);
assert_eq!(
allowed_code(r#"{"alg":"HS1"}"#, &[A::HS256]),
"UnsupportedAlgorithm"
);
assert_eq!(
allowed_code(r#"{"alg":"HS384"}"#, &[A::HS256]),
"AlgorithmNotAllowed"
);
assert_eq!(
allowed_code(r#"{"alg":"none"}"#, &[A::HS256, A::ES256]),
"AlgorithmNotAllowed"
);
assert_eq!(
allowed_alg(
&hj(r#"{"alg":"ES256","b64":false,"crit":["b64"]}"#),
&opts(&[A::HS256, A::ES256])
),
Ok(A::ES256)
);
}
#[test]
fn check_verify_is_allowed_alg_then_key_binding() {
let headers = [
r#"{"alg":"HS256","crit":["x-a"],"x-a":1}"#,
r#"{"alg":"HS1"}"#,
r#"{"alg":"HS384"}"#,
r#"{"alg":"none"}"#,
r#"{"alg":"HS256"}"#,
r#"{"alg":"HS512"}"#,
r#"{"alg":"ES256"}"#,
r#"{"alg":"RS256"}"#,
];
let keys = [
keys::oct(32, ""),
keys::oct(64, ""),
keys::p256(""),
keys::ed25519(""),
keys::oct(32, r#","alg":"HS512""#),
];
let allowed = [A::HS256, A::HS512, A::ES256, A::RS256];
for header in headers {
for key in &keys {
let split = allowed_alg(&hj(header), &opts(&allowed))
.and_then(|alg| bind_key(&alg, key, Operation::Verify).map(|()| alg));
assert_eq!(check_verify(&hj(header), key, &opts(&allowed)), split);
}
}
}
#[test]
fn verify_with_key_runs_steps_8_and_9() {
let input = signing_input("eyJhbGciOiJIUzI1NiJ9", b"$.02", true);
let sig = fake_signature(&A::HS256, &input);
let hs = keys::oct(32, "");
let check = |families: &Families, alg: &SigningAlg, key: &Jwk, sig: &[u8]| {
verify_with_key(families, alg, key, &input, sig)
};
assert_eq!(check(&FAKE, &A::HS256, &hs, &sig), Ok(()));
assert_eq!(
check(&FAKE, &A::HS256, &hs, &sig[1..]),
Err(Error::InvalidSignature)
);
assert_eq!(
check(&REJECTING, &A::HS256, &hs, &sig),
Err(Error::InvalidSignature)
);
assert_eq!(
check(&FAKE, &A::HS256, &keys::p256(""), &sig),
Err(Error::KeyAlgorithmMismatch)
);
assert_eq!(
check(&FAKE, &A::HS256, &keys::oct(32, r#","use":"enc""#), &sig),
Err(Error::KeyAlgorithmMismatch)
);
assert_eq!(
check(&FAKE, &A::HS256, &keys::oct(31, ""), &sig).map_err(|e| e.code()),
Err("InvalidKey")
);
}
#[test]
fn verify_signature_is_allowed_alg_then_verify_with_key() {
let input = signing_input("eyJhbGciOiJIUzI1NiJ9", b"p", true);
let good = fake_signature(&A::HS256, &input);
let allowed = [A::HS256, A::ES256];
for header in [
r#"{"alg":"HS256"}"#,
r#"{"alg":"HS384"}"#,
r#"{"alg":"none"}"#,
r#"{"alg":"ES256"}"#,
r#"{"alg":"HS256","crit":["x-a"],"x-a":1}"#,
] {
for key in [keys::oct(32, ""), keys::oct(31, ""), keys::p256("")] {
for sig in [&good[..], b"x"] {
let hdr = hj(header);
let o = opts(&allowed);
let split = allowed_alg(&hdr, &o).and_then(|alg| {
verify_with_key(&FAKE, &alg, &key, &input, sig).map(|()| alg)
});
assert_eq!(
verify_signature(&FAKE, &hdr, &key, &o, &input, sig),
split,
"{header}"
);
}
}
}
}
#[test]
fn precedence_follows_the_adr_0026_order() {
let rsa = keys::rsa(2048);
let hs = keys::oct(32, "");
assert_eq!(
verify_code(
r#"{"alg":"HS512","crit":["x-a"],"x-a":1}"#,
&hs,
&[A::HS256]
),
"UnknownCritical"
);
assert_eq!(
verify_code(r#"{"alg":"HS1","crit":["x-a"],"x-a":1}"#, &hs, &[A::HS256]),
"UnknownCritical"
);
assert_eq!(
verify_code(r#"{"alg":"XS256"}"#, &rsa, &[A::RS256]),
"UnsupportedAlgorithm"
);
assert_eq!(
verify_code(r#"{"alg":"HS256"}"#, &rsa, &[A::RS256]),
"AlgorithmNotAllowed"
);
assert_eq!(
verify_code(r#"{"alg":"none"}"#, &rsa, &[A::RS256]),
"AlgorithmNotAllowed"
);
assert_eq!(
code(jose(Some(r#"{"alg":"none","crit":["x-a"]}"#), None)),
"InvalidHeader"
);
let hdr = hj(r#"{"alg":"HS256"}"#);
assert_eq!(
verify_signature(&FAKE, &hdr, &rsa, &opts(&[A::HS256]), b"x", b""),
Err(Error::KeyAlgorithmMismatch)
);
}
#[test]
fn verify_signature_runs_step_9_through_the_families_table() {
let key = keys::oct(32, "");
let hdr = hj(r#"{"alg":"HS256"}"#);
let o = opts(&[A::HS256]);
let input = signing_input("eyJhbGciOiJIUzI1NiJ9", b"$.02", hdr.b64());
let sig = fake_signature(&A::HS256, &input);
assert_eq!(
verify_signature(&FAKE, &hdr, &key, &o, &input, &sig),
Ok(A::HS256)
);
assert_eq!(
verify_signature(&FAKE, &hdr, &key, &o, &input, &sig[1..]),
Err(Error::InvalidSignature)
);
assert_eq!(
verify_signature(&REJECTING, &hdr, &key, &o, &input, &sig),
Err(Error::InvalidSignature)
);
}
#[test]
fn sign_policy_each_step() {
let hs = keys::oct(32, "");
let sign_code = |protected: &str, key: &Jwk| {
prepare_sign(&hj(protected), key).map_or_else(|e| e.code(), |_| "ok")
};
assert_eq!(
sign_code(r#"{"alg":"HS256","crit":["x-a"],"x-a":1}"#, &hs),
"UnknownCritical"
);
assert_eq!(sign_code(r#"{"alg":"HS1"}"#, &hs), "UnsupportedAlgorithm");
assert_eq!(sign_code(r#"{"alg":"none"}"#, &hs), "AlgorithmNotAllowed");
assert_eq!(
sign_code(r#"{"alg":"RS256"}"#, &keys::rsa_private("")),
"ok"
);
assert_eq!(
sign_code(r#"{"alg":"RS256"}"#, &keys::rsa(2048)),
"KeyAlgorithmMismatch"
);
assert_eq!(sign_code(r#"{"alg":"PS256"}"#, &hs), "KeyAlgorithmMismatch");
assert_eq!(
sign_code(r#"{"alg":"ES256"}"#, &keys::p256("")),
"KeyAlgorithmMismatch"
);
assert_eq!(sign_code(r#"{"alg":"HS512"}"#, &hs), "InvalidKey");
assert_eq!(
sign_code(r#"{"alg":"HS256","b64":false,"crit":["b64"]}"#, &hs),
"ok"
);
}
#[test]
fn prepared_signature_round_trips() {
let key = keys::oct(32, "");
let hdr = jose(Some(r#"{"alg":"HS256"}"#), Some(r#"{"kid":"k"}"#)).unwrap();
let ready = prepare_sign(&hdr, &key).unwrap();
assert_eq!(ready.alg(), &A::HS256);
let input = signing_input("eyJhbGciOiJIUzI1NiJ9", b"payload", hdr.b64());
let sig = ready.sign(&FAKE, &input).unwrap();
assert_eq!(
verify_signature(&FAKE, &hdr, &key, &opts(&[A::HS256]), &input, &sig),
Ok(A::HS256)
);
}
#[test]
fn progress_ranking_matches_adr_0026_section_3() {
let ranked = [
Error::InvalidSignature,
Error::InvalidKey("k"),
Error::KeyAlgorithmMismatch,
Error::AlgorithmNotAllowed,
Error::UnsupportedAlgorithm,
Error::UnknownCritical,
];
for pair in ranked.windows(2) {
assert!(
progress_rank(&pair[0]) > progress_rank(&pair[1]),
"{pair:?}"
);
}
assert!(progress_rank(&Error::UnknownCritical) > progress_rank(&Error::InvalidHeader("h")));
}
#[test]
fn furthest_error_wins_with_ties_to_the_lowest_index() {
use Error as E;
assert_eq!(
furthest([E::InvalidSignature, E::AlgorithmNotAllowed]),
Some(E::InvalidSignature)
);
assert_eq!(
furthest([E::AlgorithmNotAllowed, E::InvalidSignature]),
Some(E::InvalidSignature)
);
assert_eq!(
furthest([E::UnknownCritical, E::UnknownCritical]),
Some(E::UnknownCritical)
);
assert_eq!(
furthest([
E::UnknownCritical,
E::InvalidKey("first"),
E::InvalidKey("second")
]),
Some(E::InvalidKey("first"))
);
assert_eq!(furthest([]), None);
}
#[test]
fn multi_signature_flow_with_the_shared_helpers() {
let key = keys::oct(32, "");
let headers = [
hj(r#"{"alg":"HS256","crit":["x-a"],"x-a":1}"#), hj(r#"{"alg":"HS384"}"#), hj(r#"{"alg":"HS256"}"#), hj(r#"{"alg":"HS256","kid":"good"}"#), ];
let b64 = common_b64(&headers).unwrap();
let o = opts(&[A::HS256]);
let run = |count: usize| {
let mut errors = Vec::new();
for (index, hdr) in headers.iter().take(count).enumerate() {
let input = signing_input("", b"p", b64);
let sig = if index == 3 {
fake_signature(&A::HS256, &input)
} else {
vec![0]
};
match verify_signature(&FAKE, hdr, &key, &o, &input, &sig) {
Ok(_) => return Ok(index),
Err(e) => errors.push(e),
}
}
Err(furthest(errors))
};
assert_eq!(run(4), Ok(3));
assert_eq!(run(3), Err(Some(Error::InvalidSignature)));
assert_eq!(run(2), Err(Some(Error::AlgorithmNotAllowed)));
assert_eq!(run(1), Err(Some(Error::UnknownCritical)));
}
}