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