Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::auth_jwt_decode_jwt::{check_jwt_secret, split_jwt}; ← decodeJwt, another function of this group · built into the same file, even by a slim install
use super::crypto_constant_time_equal::constant_time_equal; ← from crypto.constant-time-equal ^1.0.0 · built alongside by fune
use super::crypto_hmac_sha256::hmac_sha256; ← from crypto.hmac-sha256 ^1.0.0 · built alongside by fune
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<i64> = 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<f64>; 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<String>| 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<i64> = 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))
}