typed-rust: AES-256-GCM seal/open as crypto-prims (FFI to aes-gcm)

ober

eccee32bf0d725ce43907cf417221fb80aaaa2e5

diff --git a/lib/jerboa/typed/checker.ss b/lib/jerboa/typed/checker.ss
index a60463c..54d507d 100644
--- a/lib/jerboa/typed/checker.ss
+++ b/lib/jerboa/typed/checker.ss
@@ -392,6 +392,15 @@
       (cons 'sha256
             (make-typed-call-sig (list 'Bytes) 'Bytes '()
               'crypto-prim (list (cons 'prim 'sha256))))
+      ;; AES-256-GCM AEAD (NIST SP 800-38D). (key nonce data aad): 32-byte key,
+      ;; 12-byte nonce, aad may be empty. seal → ciphertext‖tag; open →
+      ;; (Option Bytes), None on authentication failure (wrong key/tamper).
+      (cons 'aes-256-gcm-seal
+            (make-typed-call-sig (list 'Bytes 'Bytes 'Bytes 'Bytes) 'Bytes '()
+              'crypto-prim (list (cons 'prim 'aes-256-gcm-seal))))
+      (cons 'aes-256-gcm-open
+            (make-typed-call-sig (list 'Bytes 'Bytes 'Bytes 'Bytes) (list 'Option 'Bytes) '()
+              'crypto-prim (list (cons 'prim 'aes-256-gcm-open))))
       (cons 'hmac-sha256
             (make-typed-call-sig (list 'Bytes 'Bytes) 'Bytes '()
               'crypto-prim (list (cons 'prim 'hmac-sha256))))
diff --git a/lib/jerboa/typed/rust.ss b/lib/jerboa/typed/rust.ss
index a99ab5d..a48fe4a 100644
--- a/lib/jerboa/typed/rust.ss
+++ b/lib/jerboa/typed/rust.ss
@@ -1422,6 +1422,40 @@
       "{ let __k: [u8; 32] = " (x25519-key32 (car args))
       "; x25519_dalek::x25519(__k, x25519_dalek::X25519_BASEPOINT_BYTES).to_vec() }"))
 
+  ;; (aes-256-gcm-seal key nonce plaintext aad) : (Bytes*4) -> Bytes.
+  ;; AES-256-GCM AEAD encrypt; the returned buffer is ciphertext with the
+  ;; 16-byte authentication tag appended (RustCrypto convention). 32-byte key,
+  ;; 12-byte nonce, aad may be empty. A wrong key/nonce length panics and is
+  ;; caught by the C-ABI wrapper's catch_unwind.
+  (def (emit-aes-256-gcm-seal args)
+    (unless (= (length args) 4)
+      (error 'typed-rust
+        "aes-256-gcm-seal expects key, nonce, plaintext and aad Bytes" args))
+    (string-append
+      "{ use aes_gcm::aead::{Aead, KeyInit, Payload}; "
+      "let __c = aes_gcm::Aes256Gcm::new_from_slice(" (bytes-slice (car args))
+      ").expect(\"AES-256-GCM key is 32 bytes\"); "
+      "let __n = aes_gcm::Nonce::from_slice(" (bytes-slice (cadr args))
+      "); __c.encrypt(__n, Payload { msg: " (bytes-slice (caddr args))
+      ", aad: " (bytes-slice (cadddr args))
+      " }).expect(\"AES-256-GCM encryption within size limits\") }"))
+
+  ;; (aes-256-gcm-open key nonce ciphertext aad) : (Bytes*4) -> (Option Bytes).
+  ;; Decrypt and verify; None on authentication failure (wrong key, tampered
+  ;; ciphertext/aad, or bad tag). `ciphertext` is the ct‖tag buffer seal made.
+  (def (emit-aes-256-gcm-open args)
+    (unless (= (length args) 4)
+      (error 'typed-rust
+        "aes-256-gcm-open expects key, nonce, ciphertext and aad Bytes" args))
+    (string-append
+      "{ use aes_gcm::aead::{Aead, KeyInit, Payload}; "
+      "let __c = aes_gcm::Aes256Gcm::new_from_slice(" (bytes-slice (car args))
+      ").expect(\"AES-256-GCM key is 32 bytes\"); "
+      "let __n = aes_gcm::Nonce::from_slice(" (bytes-slice (cadr args))
+      "); match __c.decrypt(__n, Payload { msg: " (bytes-slice (caddr args))
+      ", aad: " (bytes-slice (cadddr args))
+      " }) { Ok(__pt) => Some(__pt), Err(_) => None } }"))
+
   (def (emit-crypto-prim prim args)
     (case prim
       [(sha256) (emit-sha256 args)]
@@ -1429,6 +1463,8 @@
       [(hkdf-sha256) (emit-hkdf-sha256 args)]
       [(x25519-dh) (emit-x25519-dh args)]
       [(x25519-base) (emit-x25519-base args)]
+      [(aes-256-gcm-seal) (emit-aes-256-gcm-seal args)]
+      [(aes-256-gcm-open) (emit-aes-256-gcm-open args)]
       [else (error 'typed-rust "unknown crypto primitive" prim)]))
 
   ;; (bytevector-append a b) -> a fresh owned buffer a ++ b. Clone the first so