Add AEAD encrypt/decrypt and constant-time compare
ober
69f036f257cc7816a8258dc26fa5d08770765338
--- a/chez_crypto_shim.c +++ b/chez_crypto_shim.c @@ -7,6 +7,7 @@ #include <openssl/rand.h> #include <openssl/err.h> #include <openssl/kdf.h> +#include <openssl/crypto.h> #include <stdlib.h> #include <string.h> @@ -243,6 +244,111 @@ int chez_ed25519_verify(const unsigned char *pubkey, int publen, return rc; } +/* ---- AEAD Encrypt/Decrypt (ChaCha20-Poly1305, AES-GCM, etc.) ---- */ + +/* + * chez_aead_encrypt — AEAD encrypt with authentication tag. + * + * algo: cipher name (e.g. "chacha20-poly1305", "aes-256-gcm") + * key: encryption key + * nonce: nonce/IV + * noncelen: nonce length (12 for chacha20-poly1305) + * aad: additional authenticated data (can be NULL) + * aadlen: AAD length (0 if no AAD) + * in: plaintext + * inlen: plaintext length + * out: ciphertext output (must be >= inlen bytes) + * outlen: receives ciphertext length + * tag: authentication tag output (must be >= taglen bytes) + * taglen: desired tag length (16 for chacha20-poly1305) + * + * Returns: 0 on success, negative on error. + */ +int chez_aead_encrypt(const char *algo, + const unsigned char *key, + const unsigned char *nonce, int noncelen, + const unsigned char *aad, int aadlen, + const unsigned char *in, int inlen, + unsigned char *out, int *outlen, + unsigned char *tag, int taglen) { + const EVP_CIPHER *c = EVP_get_cipherbyname(algo); + if (!c) return -1; + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) return -2; + int rc = -3; + int len1 = 0, len2 = 0; + + if (EVP_EncryptInit_ex(ctx, c, NULL, NULL, NULL) != 1) goto done; + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, NULL) != 1) goto done; + if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce) != 1) goto done; + + if (aad && aadlen > 0) { + if (EVP_EncryptUpdate(ctx, NULL, &len1, aad, aadlen) != 1) goto done; + } + if (EVP_EncryptUpdate(ctx, out, &len1, in, inlen) != 1) goto done; + if (EVP_EncryptFinal_ex(ctx, out + len1, &len2) != 1) goto done; + *outlen = len1 + len2; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, taglen, tag) != 1) goto done; + rc = 0; + +done: + EVP_CIPHER_CTX_free(ctx); + return rc; +} + +/* + * chez_aead_decrypt — AEAD decrypt and verify authentication tag. + * + * Returns: 0 on success (tag verified), -1 on auth failure, negative on error. + */ +int chez_aead_decrypt(const char *algo, + const unsigned char *key, + const unsigned char *nonce, int noncelen, + const unsigned char *aad, int aadlen, + const unsigned char *in, int inlen, + unsigned char *out, int *outlen, + const unsigned char *tag, int taglen) { + const EVP_CIPHER *c = EVP_get_cipherbyname(algo); + if (!c) return -1; + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) return -2; + int rc = -3; + int len1 = 0, len2 = 0; + + if (EVP_DecryptInit_ex(ctx, c, NULL, NULL, NULL) != 1) goto done; + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, NULL) != 1) goto done; + if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce) != 1) goto done; + + if (aad && aadlen > 0) { + if (EVP_DecryptUpdate(ctx, NULL, &len1, aad, aadlen) != 1) goto done; + } + if (EVP_DecryptUpdate(ctx, out, &len1, in, inlen) != 1) goto done; + + /* Set expected tag before finalize */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, taglen, + (void *)(unsigned char *)tag) != 1) goto done; + + /* DecryptFinal returns 0 on tag mismatch */ + if (EVP_DecryptFinal_ex(ctx, out + len1, &len2) != 1) { + rc = -1; /* authentication failure */ + goto done; + } + *outlen = len1 + len2; + rc = 0; + +done: + EVP_CIPHER_CTX_free(ctx); + return rc; +} + +/* ---- Constant-time comparison ---- */ + +int chez_constant_time_compare(const unsigned char *a, + const unsigned char *b, int len) { + return CRYPTO_memcmp(a, b, len) == 0 ? 1 : 0; +} + /* ---- BN (Big Number) ---- */ int chez_bn_bytes(const unsigned char *bin, int binlen) { --- a/src/chez-crypto.sls +++ b/src/chez-crypto.sls @@ -20,6 +20,10 @@ decrypt-init! decrypt-update! decrypt-final! ;; Public Key — Ed25519 ed25519-keygen ed25519-sign ed25519-verify + ;; AEAD (Authenticated Encryption) + aead-encrypt aead-decrypt + ;; Constant-time comparison + constant-time-compare? ;; KDF scrypt ;; Error @@ -59,6 +63,12 @@ (define c-ed25519-sign (foreign-procedure "chez_ed25519_sign" (u8* int u8* int u8* u8*) int)) (define c-ed25519-verify (foreign-procedure "chez_ed25519_verify" (u8* int u8* int u8* int) int)) (define c-scrypt (foreign-procedure "chez_scrypt" (u8* int u8* int unsigned-64 int int u8* int) int)) + (define c-aead-encrypt (foreign-procedure "chez_aead_encrypt" + (string u8* u8* int u8* int u8* int u8* u8* u8* int) int)) + (define c-aead-decrypt (foreign-procedure "chez_aead_decrypt" + (string u8* u8* int u8* int u8* int u8* u8* u8* int) int)) + (define c-constant-time-compare (foreign-procedure "chez_constant_time_compare" + (u8* u8* int) int)) ;; ---- Helpers ---- (define (check-rc who rc) @@ -283,4 +293,60 @@ (check-rc 'scrypt rc) out)])) + ;; ---- AEAD (Authenticated Encryption with Associated Data) ---- + + ;; aead-encrypt: encrypt plaintext with AEAD cipher. + ;; algo: cipher name (e.g. "chacha20-poly1305", "aes-256-gcm") + ;; key: bytevector (32 bytes for chacha20-poly1305) + ;; nonce: bytevector (12 bytes for chacha20-poly1305) + ;; plaintext: bytevector + ;; Optional: aad (additional authenticated data bytevector) + ;; Returns: (values ciphertext tag) — both bytevectors + (define aead-encrypt + (case-lambda + [(algo key nonce plaintext) (aead-encrypt algo key nonce plaintext (make-bytevector 0))] + [(algo key nonce plaintext aad) + (let* ([inlen (bytevector-length plaintext)] + [out (make-bytevector (+ inlen 64) 0)] + [outlen (make-int-buf)] + [tag (make-bytevector 16 0)] + [rc (c-aead-encrypt algo key nonce (bytevector-length nonce) + aad (bytevector-length aad) + plaintext inlen out outlen tag 16)]) + (check-rc 'aead-encrypt rc) + (let* ([n (int-ref outlen)] + [ct (make-bytevector n)]) + (bytevector-copy! out 0 ct 0 n) + (values ct tag)))])) + + ;; aead-decrypt: decrypt and verify AEAD ciphertext. + ;; Returns plaintext bytevector. + ;; Raises error on authentication failure. + (define aead-decrypt + (case-lambda + [(algo key nonce ciphertext tag) + (aead-decrypt algo key nonce ciphertext tag (make-bytevector 0))] + [(algo key nonce ciphertext tag aad) + (let* ([inlen (bytevector-length ciphertext)] + [out (make-bytevector (+ inlen 64) 0)] + [outlen (make-int-buf)] + [rc (c-aead-decrypt algo key nonce (bytevector-length nonce) + aad (bytevector-length aad) + ciphertext inlen out outlen tag (bytevector-length tag))]) + (when (= rc -1) + (error 'aead-decrypt "authentication failed" algo)) + (check-rc 'aead-decrypt rc) + (let* ([n (int-ref outlen)] + [pt (make-bytevector n)]) + (bytevector-copy! out 0 pt 0 n) + pt))])) + + ;; ---- Constant-time comparison ---- + + ;; constant-time-compare?: compare two bytevectors in constant time. + ;; Returns #t if equal, #f otherwise. Lengths must match. + (define (constant-time-compare? a b) + (and (= (bytevector-length a) (bytevector-length b)) + (= 1 (c-constant-time-compare a b (bytevector-length a))))) + ) ;; end library --- a/tests/crypto-test.ss +++ b/tests/crypto-test.ss @@ -110,6 +110,43 @@ (chk (ed25519-verify pub "test message" sig) => #t) (chk (ed25519-verify pub "wrong message" sig) => #f))) +;;; ---- AEAD (ChaCha20-Poly1305) ---- +(let* ([key (random-bytes 32)] + [nonce (random-bytes 12)] + [plain (string->utf8 "AEAD test message")]) + ;; Encrypt + (let-values ([(ct tag) (aead-encrypt "chacha20-poly1305" key nonce plain)]) + (chk (> (bytevector-length ct) 0) => #t) + (chk (= (bytevector-length tag) 16) => #t) + ;; Decrypt + (let ([pt (aead-decrypt "chacha20-poly1305" key nonce ct tag)]) + (chk (equal? plain pt) => #t)) + ;; Tampered ciphertext should fail + (let ([bad-ct (bytevector-copy ct)]) + (bytevector-u8-set! bad-ct 0 (bitwise-xor (bytevector-u8-ref bad-ct 0) #xff)) + (chk (guard (e [#t #t]) (aead-decrypt "chacha20-poly1305" key nonce bad-ct tag) #f) => #t)))) + +;; AEAD with AAD +(let* ([key (random-bytes 32)] + [nonce (random-bytes 12)] + [plain (string->utf8 "secret")] + [aad (string->utf8 "authenticated header")]) + (let-values ([(ct tag) (aead-encrypt "chacha20-poly1305" key nonce plain aad)]) + ;; Decrypt with correct AAD + (let ([pt (aead-decrypt "chacha20-poly1305" key nonce ct tag aad)]) + (chk (equal? plain pt) => #t)) + ;; Decrypt with wrong AAD should fail + (chk (guard (e [#t #t]) + (aead-decrypt "chacha20-poly1305" key nonce ct tag (string->utf8 "wrong")) #f) => #t))) + +;;; ---- Constant-time comparison ---- +(let ([a (string->utf8 "hello")] + [b (string->utf8 "hello")] + [c (string->utf8 "world")]) + (chk (constant-time-compare? a b) => #t) + (chk (constant-time-compare? a c) => #f) + (chk (constant-time-compare? a (string->utf8 "hi")) => #f)) ;; different lengths + ;;; ---- Scrypt ---- (let ([key (scrypt "password" "salt" 32)]) (chk (= (bytevector-length key) 32) => #t)