///|
/// HMAC-based signer & verifier for JWT.
///
/// Uses MoonBit's `@crypto.hmac` internally. HS384 and HS512 use the
/// self-implemented `Sha384` / `Sha512` hashers (see `mjwt_hash_sha512.mbt`)
/// which are verified with NIST known-answer tests and RFC 4231 vectors.
///
/// # Supported algorithms
///
/// | Algorithm | Hash | Test vectors |
/// |-----------|---------|-----------------------|
/// | HS256 | SHA-256 | ✅ RFC 4231 Test Case 2 |
/// | HS384 | SHA-384 | ✅ RFC 4231 Test Case 2 |
/// | HS512 | SHA-512 | ✅ RFC 4231 Test Case 2 |
///
/// # Panics
/// None — errors are returned via `raise JwtError`.
///
/// # Example
///
/// ```moonbit
/// let s = HmacSigner::new("HS256", "my-secret")?
/// let v = HmacVerifier::new("HS256", "my-secret")?
/// let token = @mjwt.encode_with(s, my_claims)?
/// let decoded = @mjwt.decode_with(v, token)?
/// ```
// =============================================================================
// HmacSigner
// =============================================================================
///|
/// Symmetric HMAC signer. The same secret is used for both signing and
/// verification.
pub struct HmacSigner {
alg_name : String
key : Bytes
}
///|
/// Create an `HmacSigner`.
///
/// # Parameters
/// - `alg` — `"HS256"`, `"HS384"`, or `"HS512"`
/// - `key` — shared secret
///
/// # Errors
/// `UnsupportedAlgorithm` when `alg` is not recognised.
pub fn HmacSigner::new(
alg : String,
key : StringView,
) -> HmacSigner raise JwtError {
let name = match alg {
"HS256" | "HS384" | "HS512" => alg
_ => raise UnsupportedAlgorithm(alg)
}
{ alg_name: name, key: string_to_bytes(key.to_owned()) }
}
///|
pub impl JwtSigner for HmacSigner with fn alg_name(self : HmacSigner) -> String {
self.alg_name
}
///|
pub impl JwtSigner for HmacSigner with fn sign(
self : HmacSigner,
message : BytesView,
) -> FixedArray[Byte] raise JwtError {
match self.alg_name {
"HS256" => @crypto.hmac(@crypto.SHA256::new(), self.key.view(), message)
"HS384" => @crypto.hmac(Sha384::new(), self.key.view(), message)
"HS512" => @crypto.hmac(Sha512::new(), self.key.view(), message)
_ => raise UnsupportedAlgorithm(self.alg_name)
}
}
///|
pub extend HmacSigner with JwtSigner::{alg_name, sign}
// =============================================================================
// HmacVerifier
// =============================================================================
///|
/// Symmetric HMAC verifier.
pub struct HmacVerifier {
alg_name : String
key : Bytes
}
///|
/// Create an `HmacVerifier`.
pub fn HmacVerifier::new(
alg : String,
key : StringView,
) -> HmacVerifier raise JwtError {
let name = match alg {
"HS256" | "HS384" | "HS512" => alg
_ => raise UnsupportedAlgorithm(alg)
}
{ alg_name: name, key: string_to_bytes(key.to_owned()) }
}
///|
pub impl JwtVerifier for HmacVerifier with fn alg_name(self : HmacVerifier) -> String {
self.alg_name
}
///|
pub impl JwtVerifier for HmacVerifier with fn verify(
self : HmacVerifier,
message : BytesView,
signature : BytesView,
) -> Bool {
let expected = match self.alg_name {
"HS256" => @crypto.hmac(@crypto.SHA256::new(), self.key.view(), message)
"HS384" => @crypto.hmac(Sha384::new(), self.key.view(), message)
"HS512" => @crypto.hmac(Sha512::new(), self.key.view(), message)
_ => return false
}
if signature.length() != expected.length() {
return false
}
for i = 0; i < signature.length(); i = i + 1 {
if signature[i] != expected[i] {
return false
}
}
true
}
///|
pub extend HmacVerifier with JwtVerifier::{alg_name, verify}