Skip to main content

seq_runtime/crypto/
hash.rs

1//! SHA-256, HMAC-SHA256, and timing-safe string comparison.
2
3use crate::seqstring::global_string;
4use crate::stack::{Stack, pop, push};
5use crate::value::Value;
6
7use hmac::digest::KeyInit;
8use hmac::{Hmac, Mac};
9use sha2::{Digest, Sha256};
10use subtle::ConstantTimeEq;
11
12type HmacSha256 = Hmac<Sha256>;
13
14/// Compute SHA-256 hash of a string
15///
16/// Stack effect: ( String -- String )
17///
18/// Returns the hash as a lowercase hex string (64 characters).
19///
20/// # Safety
21/// Stack must have a String value on top
22#[unsafe(no_mangle)]
23pub unsafe extern "C" fn patch_seq_sha256(stack: Stack) -> Stack {
24    assert!(!stack.is_null(), "sha256: stack is empty");
25
26    let (stack, value) = unsafe { pop(stack) };
27
28    match value {
29        Value::String(s) => {
30            let mut hasher = Sha256::new();
31            hasher.update(s.as_bytes());
32            let result = hasher.finalize();
33            let hex_digest = hex::encode(result);
34            unsafe { push(stack, Value::String(global_string(hex_digest))) }
35        }
36        _ => panic!("sha256: expected String on stack, got {:?}", value),
37    }
38}
39
40/// Compute HMAC-SHA256 of a message with a key
41///
42/// Stack effect: ( message key -- String )
43///
44/// Returns the signature as a lowercase hex string (64 characters).
45/// Used for webhook verification, JWT signing, API authentication.
46///
47/// # Safety
48/// Stack must have two String values on top (message, then key)
49#[unsafe(no_mangle)]
50pub unsafe extern "C" fn patch_seq_hmac_sha256(stack: Stack) -> Stack {
51    assert!(!stack.is_null(), "hmac-sha256: stack is empty");
52
53    let (stack, key_value) = unsafe { pop(stack) };
54    let (stack, msg_value) = unsafe { pop(stack) };
55
56    match (msg_value, key_value) {
57        (Value::String(msg), Value::String(key)) => {
58            let mut mac = <HmacSha256 as KeyInit>::new_from_slice(key.as_bytes())
59                .expect("HMAC can take any key");
60            mac.update(msg.as_bytes());
61            let result = mac.finalize();
62            let hex_sig = hex::encode(result.into_bytes());
63            unsafe { push(stack, Value::String(global_string(hex_sig))) }
64        }
65        (msg, key) => panic!(
66            "hmac-sha256: expected (String, String) on stack, got ({:?}, {:?})",
67            msg, key
68        ),
69    }
70}
71
72/// Timing-safe string comparison
73///
74/// Stack effect: ( String String -- Bool )
75///
76/// Compares two strings in constant time to prevent timing attacks.
77/// Essential for comparing signatures, hashes, tokens, etc.
78///
79/// Uses the `subtle` crate for cryptographically secure constant-time comparison.
80/// This prevents timing side-channel attacks where an attacker could deduce
81/// secret values by measuring comparison duration.
82///
83/// # Safety
84/// Stack must have two String values on top
85#[unsafe(no_mangle)]
86pub unsafe extern "C" fn patch_seq_constant_time_eq(stack: Stack) -> Stack {
87    assert!(!stack.is_null(), "constant-time-eq: stack is empty");
88
89    let (stack, b_value) = unsafe { pop(stack) };
90    let (stack, a_value) = unsafe { pop(stack) };
91
92    match (a_value, b_value) {
93        (Value::String(a), Value::String(b)) => {
94            let a_bytes = a.as_bytes();
95            let b_bytes = b.as_bytes();
96
97            // Use subtle crate for truly constant-time comparison
98            // This handles different-length strings correctly without timing leaks
99            let eq = a_bytes.ct_eq(b_bytes);
100
101            unsafe { push(stack, Value::Bool(bool::from(eq))) }
102        }
103        (a, b) => panic!(
104            "constant-time-eq: expected (String, String) on stack, got ({:?}, {:?})",
105            a, b
106        ),
107    }
108}