use super::funejson::Value; use super::auth_jwt_decode_jwt::{check_jwt_secret, split_jwt}; use super::crypto_constant_time_equal::constant_time_equal; use super::crypto_hmac_sha256::hmac_sha256; fn fail(error: &str, message: &str) -> JwtVerification { JwtVerification { valid: false, claims: None, error: Some(error.to_string()), message: Some(message.to_string()), } } /// A present claim, as distinct from an absent one: `Value::get` answers /// Null for both. fn field<'v>(object: &'v Value, key: &str) -> Option<&'v Value> { match object { Value::Obj(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v), _ => None, } } /// Check an HS256 token: shape, algorithm (so "none" and algorithm-confusion /// tokens are refused before any key is used), signature in constant time, /// then exp, nbf and iat against `now` with leeway. A bad token is an /// answer, never a panic; only a bad secret or leeway panics. /// /// # Panics /// Panics if the secret is not at least 32 bytes or the leeway is negative. pub fn verify_jwt(token: &str, secret: &[i64], now: i64, leeway_seconds: i64) -> JwtVerification { check_jwt_secret(secret); if leeway_seconds < 0 { panic!("leewaySeconds must be a whole number, 0 or more"); } let (header, claims, signing_input, signature) = match split_jwt(token) { Some(parts) => parts, None => return fail("malformed_token", "the token is not a well-formed JWT"), }; if field(&header, "alg") != Some(&Value::str("HS256")) { return fail("unsupported_algorithm", "only HS256 tokens are accepted"); } if field(&header, "crit").is_some() { return fail( "unsupported_algorithm", "the token requires header extensions (crit) this verifier does not support", ); } let input: Vec = signing_input.bytes().map(|b| b as i64).collect(); if !constant_time_equal(&hmac_sha256(secret, &input), &signature) { return fail("invalid_signature", "the token's signature does not match"); } let mut times: [Option; 3] = [None, None, None]; for (slot, name) in ["exp", "nbf", "iat"].iter().enumerate() { match field(&claims, name) { None => {} Some(Value::Int(i)) => times[slot] = Some(*i as f64), Some(Value::Float(f)) => times[slot] = Some(*f), Some(_) => { return fail("invalid_claims", &format!("the token's {} claim is not a number", name)); } } } let now = now as f64; let leeway = leeway_seconds as f64; // RFC 7519 4.1.4: the current time MUST be before exp. if let Some(exp) = times[0] { if now >= exp + leeway { return fail("token_expired", "the token has expired"); } } // RFC 7519 4.1.5: the current time MUST be at or after nbf. if let Some(nbf) = times[1] { if now + leeway < nbf { return fail("token_not_yet_valid", "the token is not valid yet"); } } if let Some(iat) = times[2] { if iat > now + leeway { return fail("token_issued_in_future", "the token was issued in the future"); } } JwtVerification { valid: true, claims: Some(claims), error: None, message: None } } pub fn jwt_verification_to_value(result: &JwtVerification) -> Value { let text = |v: &Option| match v { Some(s) => Value::str(s), None => Value::Null, }; Value::obj(vec![ ("valid", Value::Bool(result.valid)), ("claims", result.claims.clone().unwrap_or(Value::Null)), ("error", text(&result.error)), ("message", text(&result.message)), ]) } fn whole(value: &Value, message: &str) -> i64 { match value { Value::Int(i) => *i, _ => panic!("{}", message), } } pub fn fune_vector(args: &[Value]) -> Value { let secret: Vec = match &args[1] { Value::Arr(items) => items .iter() .map(|item| match item { Value::Int(i) => *i, _ => panic!("secret must be a list of integers from 0 to 255"), }) .collect(), _ => panic!("secret must be a list of integers from 0 to 255"), }; let now = whole(&args[2], "now must be a whole number of Unix seconds"); let leeway = whole(&args[3], "leewaySeconds must be a whole number, 0 or more"); jwt_verification_to_value(&verify_jwt(args[0].as_str(), &secret, now, leeway)) }