seq_runtime/crypto/
aes.rs1use crate::seqstring::{global_bytes, global_string};
4use crate::stack::{Stack, pop, push};
5use crate::value::Value;
6
7use aes_gcm::{
8 Aes256Gcm, Nonce,
9 aead::{Aead, KeyInit as AesKeyInit},
10};
11use base64::{Engine, engine::general_purpose::STANDARD};
12use rand_core::{OsRng, RngCore};
13
14use super::{AES_GCM_TAG_SIZE, AES_KEY_SIZE, AES_NONCE_SIZE};
15
16#[unsafe(no_mangle)]
31pub unsafe extern "C" fn patch_seq_crypto_aes_gcm_encrypt(stack: Stack) -> Stack {
32 assert!(!stack.is_null(), "crypto.aes-gcm-encrypt: stack is null");
33
34 let (stack, key_val) = unsafe { pop(stack) };
35 let (stack, plaintext_val) = unsafe { pop(stack) };
36
37 match (plaintext_val, key_val) {
38 (Value::String(plaintext), Value::String(key_hex)) => {
39 match aes_gcm_encrypt(plaintext.as_bytes(), key_hex.as_str_or_empty()) {
43 Some(ciphertext) => {
44 let stack = unsafe { push(stack, Value::String(global_string(ciphertext))) };
45 unsafe { push(stack, Value::Bool(true)) }
46 }
47 None => {
48 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
49 unsafe { push(stack, Value::Bool(false)) }
50 }
51 }
52 }
53 _ => panic!("crypto.aes-gcm-encrypt: expected two Strings on stack"),
54 }
55}
56
57#[unsafe(no_mangle)]
72pub unsafe extern "C" fn patch_seq_crypto_aes_gcm_decrypt(stack: Stack) -> Stack {
73 assert!(!stack.is_null(), "crypto.aes-gcm-decrypt: stack is null");
74
75 let (stack, key_val) = unsafe { pop(stack) };
76 let (stack, ciphertext_val) = unsafe { pop(stack) };
77
78 match (ciphertext_val, key_val) {
79 (Value::String(ciphertext), Value::String(key_hex)) => {
80 match aes_gcm_decrypt(ciphertext.as_str_or_empty(), key_hex.as_str_or_empty()) {
84 Some(plaintext_bytes) => {
85 let stack =
86 unsafe { push(stack, Value::String(global_bytes(plaintext_bytes))) };
87 unsafe { push(stack, Value::Bool(true)) }
88 }
89 None => {
90 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
91 unsafe { push(stack, Value::Bool(false)) }
92 }
93 }
94 }
95 _ => panic!("crypto.aes-gcm-decrypt: expected two Strings on stack"),
96 }
97}
98
99pub(super) fn aes_gcm_encrypt(plaintext: &[u8], key_hex: &str) -> Option<String> {
104 let key_bytes = hex::decode(key_hex).ok()?;
106 if key_bytes.len() != AES_KEY_SIZE {
107 return None;
108 }
109
110 let cipher = Aes256Gcm::new_from_slice(&key_bytes).ok()?;
112
113 let mut nonce_bytes = [0u8; AES_NONCE_SIZE];
115 OsRng.fill_bytes(&mut nonce_bytes);
116 let nonce = Nonce::try_from(&nonce_bytes[..]).expect("nonce is fixed-length");
117
118 let ciphertext = cipher.encrypt(&nonce, plaintext).ok()?;
120
121 let mut combined = Vec::with_capacity(AES_NONCE_SIZE + ciphertext.len());
123 combined.extend_from_slice(&nonce_bytes);
124 combined.extend_from_slice(&ciphertext);
125
126 Some(STANDARD.encode(&combined))
127}
128
129pub(super) fn aes_gcm_decrypt(ciphertext_b64: &str, key_hex: &str) -> Option<Vec<u8>> {
134 let combined = STANDARD.decode(ciphertext_b64).ok()?;
136 if combined.len() < AES_NONCE_SIZE + AES_GCM_TAG_SIZE {
137 return None;
139 }
140
141 let key_bytes = hex::decode(key_hex).ok()?;
143 if key_bytes.len() != AES_KEY_SIZE {
144 return None;
145 }
146
147 let (nonce_bytes, ciphertext) = combined.split_at(AES_NONCE_SIZE);
149 let nonce = Nonce::try_from(nonce_bytes).expect("nonce is fixed-length");
150
151 let cipher = Aes256Gcm::new_from_slice(&key_bytes).ok()?;
153 cipher.decrypt(&nonce, ciphertext).ok()
154}