#[cfg(doc)]
use crate::error::Error;
use crate::error::Result;
use crate::header::Header;
use crate::jwa::SigningAlg;
use crate::jwk::{Jwk, JwkSet};
use crate::jws::{FAMILIES, finish_compact, precheck_compact};
use super::keyset::select_and_verify;
use super::typ::{Layer, check_jwt_header};
use super::validate::validate_claims;
use super::{Claims, Policy};
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum Keys<'a> {
One(&'a Jwk),
Set(&'a JwkSet),
}
impl<'a> From<&'a Jwk> for Keys<'a> {
fn from(key: &'a Jwk) -> Self {
Keys::One(key)
}
}
impl<'a> From<&'a JwkSet> for Keys<'a> {
fn from(set: &'a JwkSet) -> Self {
Keys::Set(set)
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct VerifiedJwt {
pub claims: Claims,
pub header: Header,
pub alg: SigningAlg,
pub key_index: Option<usize>,
pub kid: Option<String>,
}
pub fn verify(token: &str, keys: Keys<'_>, policy: &Policy<'_>) -> Result<VerifiedJwt> {
verify_inner(token, keys, policy)
}
pub(crate) fn verify_inner(token: &str, keys: Keys<'_>, p: &Policy<'_>) -> Result<VerifiedJwt> {
let pre = precheck_compact(token, &p.verify_options())?;
check_jwt_header(pre.header(), None, p, Layer::Signed)?;
let (verified, key_index, kid) = match keys {
Keys::One(key) => (finish_compact(&FAMILIES, &pre, key)?, None, key.kid()),
Keys::Set(set) => {
let (verified, index) = select_and_verify(&pre, set, p)?;
let kid = set.keys().get(index).and_then(Jwk::kid);
(verified, Some(index), kid)
}
};
let claims = Claims::from_json(&verified.payload)?;
validate_claims(&claims, p)?;
Ok(VerifiedJwt {
claims,
header: pre.header().clone(),
alg: verified.alg.clone(),
key_index,
kid: kid.map(str::to_owned),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error;
use crate::jws::{ProtectedHeader, SignOptions, Signer, sign_compact, sign_flattened};
use crate::jws::{VerifyOptions, verify_json};
use crate::jwt::FixedClock;
const OCT_64: &str = r#"{"kty":"oct","k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"}"#;
const RFC7519_3_1: &str = concat!(
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9",
".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
);
const CLAIMS: &[u8] =
b"{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}";
const HS256: [SigningAlg; 1] = [SigningAlg::HS256];
const NOW: i64 = 1_300_819_320;
fn key() -> Jwk {
Jwk::from_json(OCT_64.as_bytes()).expect("RFC 7515 A.1.1 key")
}
fn protected(alg: &str) -> ProtectedHeader {
let mut header = Header::new();
header.alg = Some(alg.into());
ProtectedHeader::new(header).expect("protected header")
}
fn flattened(unprotected: Header) -> String {
let key = key();
let signer = Signer::new(&key)
.protected(protected("HS256"))
.unprotected(unprotected);
sign_flattened(&signer, CLAIMS, &SignOptions::new()).expect("flattened JWS")
}
#[test]
fn an_unprotected_typ_or_cty_is_invalid_header_on_the_internal_path() {
let key = key();
let clock = FixedClock(NOW);
let policies = [
Policy::new(&clock, &HS256).expect("policy"),
Policy::new(&clock, &HS256)
.expect("policy")
.expect_typ("at+jwt"),
Policy::new(&clock, &HS256).expect("policy").any_typ(),
];
let mut typ = Header::new();
typ.typ = Some("at+jwt".into());
let mut cty = Header::new();
cty.cty = Some("example".into());
let mut both = Header::new();
both.typ = Some("JWT".into());
both.cty = Some("JWT".into());
for unprotected in [typ, cty, both] {
let jws = flattened(unprotected);
let opts = VerifyOptions::new(&HS256).expect("options");
let verified = verify_json(&jws, &key, &opts).expect("a valid flattened JWS");
let protected = verified.protected.as_ref().expect("protected header");
assert!(verified.unprotected.is_some());
for policy in &policies {
assert!(matches!(
check_jwt_header(
protected,
verified.unprotected.as_ref(),
policy,
Layer::Signed
),
Err(Error::InvalidHeader(_))
));
if !matches!(policy.typ_mode(), crate::jwt::TypMode::Exact(_)) {
assert_eq!(
check_jwt_header(protected, None, policy, Layer::Signed),
Ok(())
);
}
assert!(matches!(
verify_inner(&jws, Keys::One(&key), policy),
Err(Error::InvalidFormat(_))
));
}
}
let mut other = Header::new();
other.kid = Some("k1".into());
let jws = flattened(other);
let opts = VerifyOptions::new(&HS256).expect("options");
let verified = verify_json(&jws, &key, &opts).expect("a valid flattened JWS");
let protected = verified.protected.as_ref().expect("protected header");
assert_eq!(
check_jwt_header(
protected,
verified.unprotected.as_ref(),
&policies[0],
Layer::Signed
),
Ok(())
);
}
#[test]
fn verify_inner_is_verify() {
let key = key();
for now in [NOW, 1_300_819_440] {
let clock = FixedClock(now);
let policy = Policy::new(&clock, &HS256).expect("policy");
for token in [RFC7519_3_1, "a.b", ""] {
assert_eq!(
verify_inner(token, Keys::One(&key), &policy),
verify(token, Keys::One(&key), &policy)
);
}
}
}
#[test]
fn a_verified_payload_that_is_not_a_json_object_is_invalid_json() {
let key = key();
let clock = FixedClock(NOW);
let policy = Policy::new(&clock, &HS256).expect("policy");
for payload in [&b"\xff\xfe"[..], b"", b"[]", b"\"joe\"", b"{\"iss\":"] {
let token =
sign_compact(&protected("HS256"), payload, &key, &SignOptions::new()).expect("JWS");
assert!(
matches!(
verify_inner(&token, Keys::One(&key), &policy),
Err(Error::InvalidJson(_))
),
"{payload:?}"
);
}
}
#[test]
fn the_result_carries_the_protected_header_and_alg() {
let key = key();
let clock = FixedClock(NOW);
let policy = Policy::new(&clock, &HS256).expect("policy");
let v = verify_inner(RFC7519_3_1, Keys::One(&key), &policy).expect("§3.1 verifies");
let mut header = Header::new();
header.typ = Some("JWT".into());
header.alg = Some("HS256".into());
assert_eq!(v.header, header);
assert_eq!(v.alg, SigningAlg::HS256);
assert_eq!(v.claims, Claims::from_json(CLAIMS).expect("claims"));
}
}