// hmac_sha256.mbt — HMAC-SHA256 provider (RFC 2104) built on a mature
// SHA-256 dependency (gmlewis/sha256, Apache-2.0).
//
// The SHA-256 primitive is provided by the gmlewis/sha256 package; HMAC
// construction follows RFC 2104 exactly (64-byte block, ipad 0x36, opad
// 0x5c, key hashing when longer than the block). MAC comparisons use
// `constant_time_equal`.
///|
/// The HMAC-SHA256 algorithm provider.
pub enum HmacSha256 {
HmacSha256
}
///|
/// Constructs the HMAC-SHA256 provider.
pub fn HmacSha256::new() -> HmacSha256 {
HmacSha256
}
///|
/// Returns the registered algorithm name.
pub fn HmacSha256::algorithm_name(_self : HmacSha256) -> String {
"hmac-sha256"
}
///|
/// The SHA-256 block size in bytes.
let hmac_block_size = 64
///|
/// Computes the raw 32-byte SHA-256 digest of `data`.
///
/// The `gmlewis/sha256` package exposes a streaming digest whose `check_sum`
/// returns lowercase hex; we decode it back to raw bytes.
pub fn sha256_raw(data : Bytes) -> Bytes {
let d = @sha256.Digest::new()
for b in data {
d.write(b)
}
match hex_decode_bytes(d.check_sum()) {
Ok(b) => b
Err(_) => abort("sha256 hex decode failed")
}
}
///|
/// Computes HMAC-SHA256 of `message` under `key` per RFC 2104.
pub fn hmac_sha256(key : Bytes, message : Bytes) -> Bytes {
// If key is longer than the block size, hash it first.
let mut effective = key
if key.length() > hmac_block_size {
effective = sha256_raw(key)
}
// Pad the key to the block size with trailing zero bytes.
let padded = FixedArray::make(hmac_block_size, b'\x00')
for i = 0; i < effective.length(); i = i + 1 {
padded[i] = effective[i]
}
let ipad = FixedArray::make(hmac_block_size, b'\x00')
let opad = FixedArray::make(hmac_block_size, b'\x00')
for i = 0; i < hmac_block_size; i = i + 1 {
ipad[i] = (padded[i].to_int() ^ 0x36).to_byte()
opad[i] = (padded[i].to_int() ^ 0x5C).to_byte()
}
// inner = SHA256( (key XOR ipad) || message )
let inner_buf = Buffer::Buffer(size_hint=hmac_block_size + message.length())
inner_buf.write_bytes(Bytes::from_array(ipad))
inner_buf.write_bytes(message)
let inner = sha256_raw(inner_buf.to_bytes())
// outer = SHA256( (key XOR opad) || inner )
let outer_buf = Buffer::Buffer(size_hint=hmac_block_size + 32)
outer_buf.write_bytes(Bytes::from_array(opad))
outer_buf.write_bytes(inner)
sha256_raw(outer_buf.to_bytes())
}
///|
/// Signs a message with a shared secret.
pub fn HmacSha256::sign_hmac(
_self : HmacSha256,
message : Bytes,
key : Bytes,
) -> Bytes {
hmac_sha256(key, message)
}
///|
/// Verifies a MAC with a shared secret in constant time.
pub fn HmacSha256::verify_hmac(
_self : HmacSha256,
message : Bytes,
signature : Bytes,
key : Bytes,
) -> Bool {
let expected = hmac_sha256(key, message)
constant_time_equal(expected, signature)
}
///|
/// Implements `SignatureAlgorithm` for `HmacSha256`.
pub impl SignatureAlgorithm for HmacSha256 with fn name(_self) {
"hmac-sha256"
}
///|
/// Implements `sign` for `HmacSha256`.
pub impl SignatureAlgorithm for HmacSha256 with fn sign(_self, message, key) {
match key {
SharedSecret(secret) => Ok(hmac_sha256(secret, message))
ExternalKey(_) =>
Err(
hs_error(
KeyResolution,
InvalidKeyMaterial,
"hmac-sha256 requires a shared secret",
),
)
}
}
///|
/// Implements `verify` for `HmacSha256`.
pub impl SignatureAlgorithm for HmacSha256 with fn verify(
_self,
message,
signature,
key,
) {
match key {
SharedSecret(secret) =>
Ok(constant_time_equal(hmac_sha256(secret, message), signature))
ExternalKey(_) =>
Err(
hs_error(
KeyResolution,
InvalidKeyMaterial,
"hmac-sha256 requires a shared secret",
),
)
}
}