Migrate chez-crypto to jerboa-crypto: source -> src/jerboa-crypto.ss (Jerboa .ss, prelude import, def). C shim renamed jerboa_crypto_shim.c with jerboa_* symbol prefix; CHEZ_CRYPTO_LIB -> JERBOA_CRYPTO_LIB. Makefile uses jerbuild + brew openssl@3 on darwin. Tests: 34/34 pass.
ober
a8bceb0203a7b6ecd8cea2218bf1f374d897d0ef
new file mode 100644 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +lib/ +*.so +*.dylib +*.wpo +*.o +.jerbuild-hashes --- a/Makefile +++ b/Makefile @@ -1,17 +1,48 @@ -CC = gcc -CFLAGS = -shared -fPIC -O2 -LIBS = -lcrypto -SCHEME = scheme +JERBOA_HOME ?= $(HOME)/mine/jerboa +SCHEME ?= $(JERBOA_HOME)/.chez/bin/scheme +JERBUILD ?= $(JERBOA_HOME)/jerbuild +LIBDIRS = lib:$(JERBOA_HOME)/lib -.PHONY: all clean test +CC ?= cc -all: chez_crypto_shim.so +# OpenSSL — Linux has it in standard paths; macOS needs brew's openssl@3 +UNAME := $(shell uname -s) +ifeq ($(UNAME),Darwin) +OPENSSL_PREFIX ?= $(shell brew --prefix openssl@3 2>/dev/null) +ifneq ($(OPENSSL_PREFIX),) +OPENSSL_CFLAGS := -I$(OPENSSL_PREFIX)/include +OPENSSL_LIBS := -L$(OPENSSL_PREFIX)/lib -lcrypto +else +OPENSSL_LIBS := -lcrypto +endif +else +OPENSSL_LIBS := -lcrypto +endif -chez_crypto_shim.so: chez_crypto_shim.c +CFLAGS ?= -shared -fPIC -O2 $(OPENSSL_CFLAGS) +LIBS ?= $(OPENSSL_LIBS) +SHIM = jerboa_crypto_shim.so + +.PHONY: all build transpile test clean shim + +all: build + +shim: $(SHIM) + +$(SHIM): jerboa_crypto_shim.c $(CC) $(CFLAGS) -o $@ $< $(LIBS) -test: chez_crypto_shim.so - LD_LIBRARY_PATH=. $(SCHEME) --libdirs src --script tests/crypto-test.ss +transpile: + $(JERBUILD) transpile src lib --force + +build: shim transpile + +test: build + JERBOA_CRYPTO_LIB=$(CURDIR) \ + DYLD_LIBRARY_PATH=$(CURDIR) \ + LD_LIBRARY_PATH=$(CURDIR) \ + $(SCHEME) --libdirs "$(LIBDIRS)" --script tests/crypto-test.ss clean: - rm -f chez_crypto_shim.so + rm -f $(SHIM) chez_crypto_shim.so + rm -rf lib deleted file mode 100644 --- a/chez_crypto_shim.c +++ /dev/null @@ -1,386 +0,0 @@ -/* chez_crypto_shim.c — OpenSSL libcrypto wrapper for Chez Scheme FFI */ - -#include <openssl/evp.h> -#include <openssl/hmac.h> -#include <openssl/bn.h> -#include <openssl/dh.h> -#include <openssl/rand.h> -#include <openssl/err.h> -#include <openssl/kdf.h> -#include <openssl/crypto.h> -#include <stdlib.h> -#include <string.h> - -/* ---- Error handling ---- */ - -int chez_crypto_err_get(char *buf, int buflen) { - unsigned long e = ERR_get_error(); - if (e == 0) { - buf[0] = 0; - return 0; - } - ERR_error_string_n(e, buf, buflen); - return 1; -} - -/* ---- Random ---- */ - -int chez_rand_bytes(unsigned char *buf, int n) { - return RAND_bytes(buf, n); -} - -/* ---- Digest (Hash) ---- */ - -void *chez_digest_ctx_new(void) { - return EVP_MD_CTX_new(); -} - -void chez_digest_ctx_free(void *ctx) { - EVP_MD_CTX_free((EVP_MD_CTX *)ctx); -} - -int chez_digest_init(void *ctx, const char *algo) { - const EVP_MD *md = EVP_get_digestbyname(algo); - if (!md) return -1; - return EVP_DigestInit_ex((EVP_MD_CTX *)ctx, md, NULL) == 1 ? 0 : -2; -} - -int chez_digest_update(void *ctx, const unsigned char *data, int len) { - return EVP_DigestUpdate((EVP_MD_CTX *)ctx, data, len) == 1 ? 0 : -1; -} - -int chez_digest_final(void *ctx, unsigned char *out, int *outlen) { - unsigned int len = 0; - int rc = EVP_DigestFinal_ex((EVP_MD_CTX *)ctx, out, &len); - *outlen = (int)len; - return rc == 1 ? 0 : -1; -} - -int chez_digest_size(const char *algo) { - const EVP_MD *md = EVP_get_digestbyname(algo); - if (!md) return -1; - return EVP_MD_size(md); -} - -/* One-shot digest */ -int chez_digest(const char *algo, const unsigned char *data, int datalen, - unsigned char *out, int *outlen) { - const EVP_MD *md = EVP_get_digestbyname(algo); - if (!md) return -1; - EVP_MD_CTX *ctx = EVP_MD_CTX_new(); - if (!ctx) return -2; - unsigned int len = 0; - int rc = -3; - if (EVP_DigestInit_ex(ctx, md, NULL) == 1 && - EVP_DigestUpdate(ctx, data, datalen) == 1 && - EVP_DigestFinal_ex(ctx, out, &len) == 1) { - rc = 0; - } - *outlen = (int)len; - EVP_MD_CTX_free(ctx); - return rc; -} - -/* ---- HMAC ---- */ - -int chez_hmac(const char *algo, const unsigned char *key, int keylen, - const unsigned char *data, int datalen, - unsigned char *out, int *outlen) { - const EVP_MD *md = EVP_get_digestbyname(algo); - if (!md) return -1; - unsigned int len = 0; - unsigned char *result = HMAC(md, key, keylen, data, datalen, out, &len); - *outlen = (int)len; - return result ? 0 : -2; -} - -/* ---- Cipher (Symmetric Encryption) ---- */ - -void *chez_cipher_ctx_new(void) { - return EVP_CIPHER_CTX_new(); -} - -void chez_cipher_ctx_free(void *ctx) { - EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *)ctx); -} - -int chez_cipher_key_length(const char *algo) { - const EVP_CIPHER *c = EVP_get_cipherbyname(algo); - if (!c) return -1; - return EVP_CIPHER_key_length(c); -} - -int chez_cipher_iv_length(const char *algo) { - const EVP_CIPHER *c = EVP_get_cipherbyname(algo); - if (!c) return -1; - return EVP_CIPHER_iv_length(c); -} - -int chez_cipher_block_size(const char *algo) { - const EVP_CIPHER *c = EVP_get_cipherbyname(algo); - if (!c) return -1; - return EVP_CIPHER_block_size(c); -} - -int chez_encrypt_init(void *ctx, const char *algo, - const unsigned char *key, const unsigned char *iv) { - const EVP_CIPHER *c = EVP_get_cipherbyname(algo); - if (!c) return -1; - return EVP_EncryptInit_ex((EVP_CIPHER_CTX *)ctx, c, NULL, key, iv) == 1 ? 0 : -2; -} - -int chez_encrypt_update(void *ctx, const unsigned char *in, int inlen, - unsigned char *out, int *outlen) { - return EVP_EncryptUpdate((EVP_CIPHER_CTX *)ctx, out, outlen, in, inlen) == 1 ? 0 : -1; -} - -int chez_encrypt_final(void *ctx, unsigned char *out, int *outlen) { - return EVP_EncryptFinal_ex((EVP_CIPHER_CTX *)ctx, out, outlen) == 1 ? 0 : -1; -} - -int chez_decrypt_init(void *ctx, const char *algo, - const unsigned char *key, const unsigned char *iv) { - const EVP_CIPHER *c = EVP_get_cipherbyname(algo); - if (!c) return -1; - return EVP_DecryptInit_ex((EVP_CIPHER_CTX *)ctx, c, NULL, key, iv) == 1 ? 0 : -2; -} - -int chez_decrypt_update(void *ctx, const unsigned char *in, int inlen, - unsigned char *out, int *outlen) { - return EVP_DecryptUpdate((EVP_CIPHER_CTX *)ctx, out, outlen, in, inlen) == 1 ? 0 : -1; -} - -int chez_decrypt_final(void *ctx, unsigned char *out, int *outlen) { - return EVP_DecryptFinal_ex((EVP_CIPHER_CTX *)ctx, out, outlen) == 1 ? 0 : -1; -} - -/* One-shot encrypt */ -int chez_encrypt(const char *algo, const unsigned char *key, const unsigned char *iv, - const unsigned char *in, int inlen, - unsigned char *out, int *outlen) { - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - if (!ctx) return -1; - int len1 = 0, len2 = 0; - int rc = -2; - if (chez_encrypt_init(ctx, algo, key, iv) == 0 && - EVP_EncryptUpdate(ctx, out, &len1, in, inlen) == 1 && - EVP_EncryptFinal_ex(ctx, out + len1, &len2) == 1) { - *outlen = len1 + len2; - rc = 0; - } - EVP_CIPHER_CTX_free(ctx); - return rc; -} - -/* One-shot decrypt */ -int chez_decrypt(const char *algo, const unsigned char *key, const unsigned char *iv, - const unsigned char *in, int inlen, - unsigned char *out, int *outlen) { - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - if (!ctx) return -1; - int len1 = 0, len2 = 0; - int rc = -2; - if (chez_decrypt_init(ctx, algo, key, iv) == 0 && - EVP_DecryptUpdate(ctx, out, &len1, in, inlen) == 1 && - EVP_DecryptFinal_ex(ctx, out + len1, &len2) == 1) { - *outlen = len1 + len2; - rc = 0; - } - EVP_CIPHER_CTX_free(ctx); - return rc; -} - -/* ---- Ed25519 Sign/Verify ---- */ - -int chez_ed25519_keygen(unsigned char *privkey, int *privlen, - unsigned char *pubkey, int *publen) { - EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); - if (!pctx) return -1; - EVP_PKEY *pkey = NULL; - int rc = -2; - if (EVP_PKEY_keygen_init(pctx) == 1 && - EVP_PKEY_keygen(pctx, &pkey) == 1) { - size_t pl = 64, sl = 32; - if (EVP_PKEY_get_raw_private_key(pkey, privkey, &pl) == 1 && - EVP_PKEY_get_raw_public_key(pkey, pubkey, &sl) == 1) { - *privlen = (int)pl; - *publen = (int)sl; - rc = 0; - } - } - if (pkey) EVP_PKEY_free(pkey); - EVP_PKEY_CTX_free(pctx); - return rc; -} - -int chez_ed25519_sign(const unsigned char *privkey, int privlen, - const unsigned char *msg, int msglen, - unsigned char *sig, int *siglen) { - EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, privkey, privlen); - if (!pkey) return -1; - EVP_MD_CTX *mctx = EVP_MD_CTX_new(); - int rc = -2; - size_t sl = 64; - if (EVP_DigestSignInit(mctx, NULL, NULL, NULL, pkey) == 1 && - EVP_DigestSign(mctx, sig, &sl, msg, msglen) == 1) { - *siglen = (int)sl; - rc = 0; - } - EVP_MD_CTX_free(mctx); - EVP_PKEY_free(pkey); - return rc; -} - -int chez_ed25519_verify(const unsigned char *pubkey, int publen, - const unsigned char *msg, int msglen, - const unsigned char *sig, int siglen) { - EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, pubkey, publen); - if (!pkey) return -1; - EVP_MD_CTX *mctx = EVP_MD_CTX_new(); - int rc = EVP_DigestVerifyInit(mctx, NULL, NULL, NULL, pkey) == 1 && - EVP_DigestVerify(mctx, sig, siglen, msg, msglen) == 1 ? 1 : 0; - EVP_MD_CTX_free(mctx); - EVP_PKEY_free(pkey); - 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) { - BIGNUM *bn = BN_bin2bn(bin, binlen, NULL); - if (!bn) return -1; - int n = BN_num_bytes(bn); - BN_free(bn); - return n; -} - -/* ---- DH (Diffie-Hellman) ---- */ -/* Note: DH is deprecated in OpenSSL 3.x — included for API compat */ - -/* ---- Scrypt KDF ---- */ - -int chez_scrypt(const unsigned char *pass, int passlen, - const unsigned char *salt, int saltlen, - unsigned long long N, int r, int p, - unsigned char *out, int outlen) { - EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, NULL); - if (!pctx) return -1; - int rc = -2; - size_t derived_len = outlen; - if (EVP_PKEY_derive_init(pctx) == 1 && - EVP_PKEY_CTX_set1_pbe_pass(pctx, (const char *)pass, passlen) == 1 && - EVP_PKEY_CTX_set1_scrypt_salt(pctx, salt, saltlen) == 1 && - EVP_PKEY_CTX_set_scrypt_N(pctx, N) == 1 && - EVP_PKEY_CTX_set_scrypt_r(pctx, r) == 1 && - EVP_PKEY_CTX_set_scrypt_p(pctx, p) == 1 && - EVP_PKEY_derive(pctx, out, &derived_len) == 1) { - rc = (int)derived_len; - } - EVP_PKEY_CTX_free(pctx); - return rc; -} new file mode 100644 --- /dev/null +++ b/jerboa_crypto_shim.c @@ -0,0 +1,386 @@ +/* jerboa_crypto_shim.c — OpenSSL libcrypto wrapper for Chez Scheme FFI */ + +#include <openssl/evp.h> +#include <openssl/hmac.h> +#include <openssl/bn.h> +#include <openssl/dh.h> +#include <openssl/rand.h> +#include <openssl/err.h> +#include <openssl/kdf.h> +#include <openssl/crypto.h> +#include <stdlib.h> +#include <string.h> + +/* ---- Error handling ---- */ + +int jerboa_crypto_err_get(char *buf, int buflen) { + unsigned long e = ERR_get_error(); + if (e == 0) { + buf[0] = 0; + return 0; + } + ERR_error_string_n(e, buf, buflen); + return 1; +} + +/* ---- Random ---- */ + +int jerboa_rand_bytes(unsigned char *buf, int n) { + return RAND_bytes(buf, n); +} + +/* ---- Digest (Hash) ---- */ + +void *jerboa_digest_ctx_new(void) { + return EVP_MD_CTX_new(); +} + +void jerboa_digest_ctx_free(void *ctx) { + EVP_MD_CTX_free((EVP_MD_CTX *)ctx); +} + +int jerboa_digest_init(void *ctx, const char *algo) { + const EVP_MD *md = EVP_get_digestbyname(algo); + if (!md) return -1; + return EVP_DigestInit_ex((EVP_MD_CTX *)ctx, md, NULL) == 1 ? 0 : -2; +} + +int jerboa_digest_update(void *ctx, const unsigned char *data, int len) { + return EVP_DigestUpdate((EVP_MD_CTX *)ctx, data, len) == 1 ? 0 : -1; +} + +int jerboa_digest_final(void *ctx, unsigned char *out, int *outlen) { + unsigned int len = 0; + int rc = EVP_DigestFinal_ex((EVP_MD_CTX *)ctx, out, &len); + *outlen = (int)len; + return rc == 1 ? 0 : -1; +} + +int jerboa_digest_size(const char *algo) { + const EVP_MD *md = EVP_get_digestbyname(algo); + if (!md) return -1; + return EVP_MD_size(md); +} + +/* One-shot digest */ +int jerboa_digest(const char *algo, const unsigned char *data, int datalen, + unsigned char *out, int *outlen) { + const EVP_MD *md = EVP_get_digestbyname(algo); + if (!md) return -1; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) return -2; + unsigned int len = 0; + int rc = -3; + if (EVP_DigestInit_ex(ctx, md, NULL) == 1 && + EVP_DigestUpdate(ctx, data, datalen) == 1 && + EVP_DigestFinal_ex(ctx, out, &len) == 1) { + rc = 0; + } + *outlen = (int)len; + EVP_MD_CTX_free(ctx); + return rc; +} + +/* ---- HMAC ---- */ + +int jerboa_hmac(const char *algo, const unsigned char *key, int keylen, + const unsigned char *data, int datalen, + unsigned char *out, int *outlen) { + const EVP_MD *md = EVP_get_digestbyname(algo); + if (!md) return -1; + unsigned int len = 0; + unsigned char *result = HMAC(md, key, keylen, data, datalen, out, &len); + *outlen = (int)len; + return result ? 0 : -2; +} + +/* ---- Cipher (Symmetric Encryption) ---- */ + +void *jerboa_cipher_ctx_new(void) { + return EVP_CIPHER_CTX_new(); +} + +void jerboa_cipher_ctx_free(void *ctx) { + EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *)ctx); +} + +int jerboa_cipher_key_length(const char *algo) { + const EVP_CIPHER *c = EVP_get_cipherbyname(algo); + if (!c) return -1; + return EVP_CIPHER_key_length(c); +} + +int jerboa_cipher_iv_length(const char *algo) { + const EVP_CIPHER *c = EVP_get_cipherbyname(algo); + if (!c) return -1; + return EVP_CIPHER_iv_length(c); +} + +int jerboa_cipher_block_size(const char *algo) { + const EVP_CIPHER *c = EVP_get_cipherbyname(algo); + if (!c) return -1; + return EVP_CIPHER_block_size(c); +} + +int jerboa_encrypt_init(void *ctx, const char *algo, + const unsigned char *key, const unsigned char *iv) { + const EVP_CIPHER *c = EVP_get_cipherbyname(algo); + if (!c) return -1; + return EVP_EncryptInit_ex((EVP_CIPHER_CTX *)ctx, c, NULL, key, iv) == 1 ? 0 : -2; +} + +int jerboa_encrypt_update(void *ctx, const unsigned char *in, int inlen, + unsigned char *out, int *outlen) { + return EVP_EncryptUpdate((EVP_CIPHER_CTX *)ctx, out, outlen, in, inlen) == 1 ? 0 : -1; +} + +int jerboa_encrypt_final(void *ctx, unsigned char *out, int *outlen) { + return EVP_EncryptFinal_ex((EVP_CIPHER_CTX *)ctx, out, outlen) == 1 ? 0 : -1; +} + +int jerboa_decrypt_init(void *ctx, const char *algo, + const unsigned char *key, const unsigned char *iv) { + const EVP_CIPHER *c = EVP_get_cipherbyname(algo); + if (!c) return -1; + return EVP_DecryptInit_ex((EVP_CIPHER_CTX *)ctx, c, NULL, key, iv) == 1 ? 0 : -2; +} + +int jerboa_decrypt_update(void *ctx, const unsigned char *in, int inlen, + unsigned char *out, int *outlen) { + return EVP_DecryptUpdate((EVP_CIPHER_CTX *)ctx, out, outlen, in, inlen) == 1 ? 0 : -1; +} + +int jerboa_decrypt_final(void *ctx, unsigned char *out, int *outlen) { + return EVP_DecryptFinal_ex((EVP_CIPHER_CTX *)ctx, out, outlen) == 1 ? 0 : -1; +} + +/* One-shot encrypt */ +int jerboa_encrypt(const char *algo, const unsigned char *key, const unsigned char *iv, + const unsigned char *in, int inlen, + unsigned char *out, int *outlen) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) return -1; + int len1 = 0, len2 = 0; + int rc = -2; + if (jerboa_encrypt_init(ctx, algo, key, iv) == 0 && + EVP_EncryptUpdate(ctx, out, &len1, in, inlen) == 1 && + EVP_EncryptFinal_ex(ctx, out + len1, &len2) == 1) { + *outlen = len1 + len2; + rc = 0; + } + EVP_CIPHER_CTX_free(ctx); + return rc; +} + +/* One-shot decrypt */ +int jerboa_decrypt(const char *algo, const unsigned char *key, const unsigned char *iv, + const unsigned char *in, int inlen, + unsigned char *out, int *outlen) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) return -1; + int len1 = 0, len2 = 0; + int rc = -2; + if (jerboa_decrypt_init(ctx, algo, key, iv) == 0 && + EVP_DecryptUpdate(ctx, out, &len1, in, inlen) == 1 && + EVP_DecryptFinal_ex(ctx, out + len1, &len2) == 1) { + *outlen = len1 + len2; + rc = 0; + } + EVP_CIPHER_CTX_free(ctx); + return rc; +} + +/* ---- Ed25519 Sign/Verify ---- */ + +int jerboa_ed25519_keygen(unsigned char *privkey, int *privlen, + unsigned char *pubkey, int *publen) { + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); + if (!pctx) return -1; + EVP_PKEY *pkey = NULL; + int rc = -2; + if (EVP_PKEY_keygen_init(pctx) == 1 && + EVP_PKEY_keygen(pctx, &pkey) == 1) { + size_t pl = 64, sl = 32; + if (EVP_PKEY_get_raw_private_key(pkey, privkey, &pl) == 1 && + EVP_PKEY_get_raw_public_key(pkey, pubkey, &sl) == 1) { + *privlen = (int)pl; + *publen = (int)sl; + rc = 0; + } + } + if (pkey) EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(pctx); + return rc; +} + +int jerboa_ed25519_sign(const unsigned char *privkey, int privlen, + const unsigned char *msg, int msglen, + unsigned char *sig, int *siglen) { + EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, privkey, privlen); + if (!pkey) return -1; + EVP_MD_CTX *mctx = EVP_MD_CTX_new(); + int rc = -2; + size_t sl = 64; + if (EVP_DigestSignInit(mctx, NULL, NULL, NULL, pkey) == 1 && + EVP_DigestSign(mctx, sig, &sl, msg, msglen) == 1) { + *siglen = (int)sl; + rc = 0; + } + EVP_MD_CTX_free(mctx); + EVP_PKEY_free(pkey); + return rc; +} + +int jerboa_ed25519_verify(const unsigned char *pubkey, int publen, + const unsigned char *msg, int msglen, + const unsigned char *sig, int siglen) { + EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, pubkey, publen); + if (!pkey) return -1; + EVP_MD_CTX *mctx = EVP_MD_CTX_new(); + int rc = EVP_DigestVerifyInit(mctx, NULL, NULL, NULL, pkey) == 1 && + EVP_DigestVerify(mctx, sig, siglen, msg, msglen) == 1 ? 1 : 0; + EVP_MD_CTX_free(mctx); + EVP_PKEY_free(pkey); + return rc; +} + +/* ---- AEAD Encrypt/Decrypt (ChaCha20-Poly1305, AES-GCM, etc.) ---- */ + +/* + * jerboa_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 jerboa_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; +} + +/* + * jerboa_aead_decrypt — AEAD decrypt and verify authentication tag. + * + * Returns: 0 on success (tag verified), -1 on auth failure, negative on error. + */ +int jerboa_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 jerboa_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 jerboa_bn_bytes(const unsigned char *bin, int binlen) { + BIGNUM *bn = BN_bin2bn(bin, binlen, NULL); + if (!bn) return -1; + int n = BN_num_bytes(bn); + BN_free(bn); + return n; +} + +/* ---- DH (Diffie-Hellman) ---- */ +/* Note: DH is deprecated in OpenSSL 3.x — included for API compat */ + +/* ---- Scrypt KDF ---- */ + +int jerboa_scrypt(const unsigned char *pass, int passlen, + const unsigned char *salt, int saltlen, + unsigned long long N, int r, int p, + unsigned char *out, int outlen) { + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, NULL); + if (!pctx) return -1; + int rc = -2; + size_t derived_len = outlen; + if (EVP_PKEY_derive_init(pctx) == 1 && + EVP_PKEY_CTX_set1_pbe_pass(pctx, (const char *)pass, passlen) == 1 && + EVP_PKEY_CTX_set1_scrypt_salt(pctx, salt, saltlen) == 1 && + EVP_PKEY_CTX_set_scrypt_N(pctx, N) == 1 && + EVP_PKEY_CTX_set_scrypt_r(pctx, r) == 1 && + EVP_PKEY_CTX_set_scrypt_p(pctx, p) == 1 && + EVP_PKEY_derive(pctx, out, &derived_len) == 1) { + rc = (int)derived_len; + } + EVP_PKEY_CTX_free(pctx); + return rc; +} deleted file mode 100644 --- a/src/chez-crypto.sls +++ /dev/null @@ -1,358 +0,0 @@ -#!chezscheme -;;; chez-crypto — OpenSSL libcrypto for Chez Scheme - -(library (chez-crypto) - (export - ;; Random - random-bytes random-bytes! - ;; Digest (Hash) - digest digest-size - md5 sha1 sha224 sha256 sha384 sha512 - make-digest-ctx free-digest-ctx - digest-init! digest-update! digest-final! - ;; HMAC - hmac hmac-md5 hmac-sha1 hmac-sha256 hmac-sha384 hmac-sha512 - ;; Cipher (Symmetric) - encrypt decrypt - cipher-key-length cipher-iv-length cipher-block-size - make-cipher-ctx free-cipher-ctx - encrypt-init! encrypt-update! encrypt-final! - 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 - crypto-error-string) - - (import (chezscheme)) - - ;; Load shared objects. - ;; In static/binary builds where symbols are pre-registered via Sforeign_symbol, - ;; load-shared-object may fail (no dlopen) — guard to allow graceful fallback. - (define _l1 - (guard (e [#t (void)]) - (load-shared-object "libcrypto.so"))) - (define _l2 - (guard (e [#t (void)]) - (load-shared-object "chez_crypto_shim.so"))) - - ;; ---- FFI bindings ---- - (define c-err-get (foreign-procedure "chez_crypto_err_get" (u8* int) int)) - (define c-rand-bytes (foreign-procedure "chez_rand_bytes" (u8* int) int)) - (define c-digest (foreign-procedure "chez_digest" (string u8* int u8* u8*) int)) - (define c-digest-size (foreign-procedure "chez_digest_size" (string) int)) - (define c-digest-ctx-new (foreign-procedure "chez_digest_ctx_new" () void*)) - (define c-digest-ctx-free (foreign-procedure "chez_digest_ctx_free" (void*) void)) - (define c-digest-init (foreign-procedure "chez_digest_init" (void* string) int)) - (define c-digest-update (foreign-procedure "chez_digest_update" (void* u8* int) int)) - (define c-digest-final (foreign-procedure "chez_digest_final" (void* u8* u8*) int)) - (define c-hmac (foreign-procedure "chez_hmac" (string u8* int u8* int u8* u8*) int)) - (define c-cipher-ctx-new (foreign-procedure "chez_cipher_ctx_new" () void*)) - (define c-cipher-ctx-free (foreign-procedure "chez_cipher_ctx_free" (void*) void)) - (define c-cipher-key-length (foreign-procedure "chez_cipher_key_length" (string) int)) - (define c-cipher-iv-length (foreign-procedure "chez_cipher_iv_length" (string) int)) - (define c-cipher-block-size (foreign-procedure "chez_cipher_block_size" (string) int)) - (define c-encrypt-init (foreign-procedure "chez_encrypt_init" (void* string u8* u8*) int)) - (define c-encrypt-update (foreign-procedure "chez_encrypt_update" (void* u8* int u8* u8*) int)) - (define c-encrypt-final (foreign-procedure "chez_encrypt_final" (void* u8* u8*) int)) - (define c-decrypt-init (foreign-procedure "chez_decrypt_init" (void* string u8* u8*) int)) - (define c-decrypt-update (foreign-procedure "chez_decrypt_update" (void* u8* int u8* u8*) int)) - (define c-decrypt-final (foreign-procedure "chez_decrypt_final" (void* u8* u8*) int)) - (define c-encrypt (foreign-procedure "chez_encrypt" (string u8* u8* u8* int u8* u8*) int)) - (define c-decrypt (foreign-procedure "chez_decrypt" (string u8* u8* u8* int u8* u8*) int)) - (define c-ed25519-keygen (foreign-procedure "chez_ed25519_keygen" (u8* u8* u8* u8*) int)) - (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) - (when (< rc 0) - (error who (string-append "crypto error (rc=" (number->string rc) ")")))) - - (define (as-bytes x) - (if (string? x) (string->utf8 x) x)) - - (define (int-ref bv) - (bytevector-s32-native-ref bv 0)) - - (define (make-int-buf) - (make-bytevector 4 0)) - - ;; ---- Error ---- - (define (crypto-error-string) - (let ([buf (make-bytevector 256 0)]) - (if (= (c-err-get buf 256) 1) - (let loop ([i 0]) - (if (or (= i 256) (= (bytevector-u8-ref buf i) 0)) - (utf8->string (let ([r (make-bytevector i)]) - (bytevector-copy! buf 0 r 0 i) r)) - (loop (+ i 1)))) - #f))) - - ;; ---- Random ---- - (define (random-bytes n) - (let ([bv (make-bytevector n)]) - (random-bytes! bv) - bv)) - - (define (random-bytes! bv) - (let ([rc (c-rand-bytes bv (bytevector-length bv))]) - (unless (= rc 1) (error 'random-bytes! "RAND_bytes failed")))) - - ;; ---- Digest ---- - (define (digest-size algo) - (let ([n (c-digest-size algo)]) - (check-rc 'digest-size n) - n)) - - (define (digest algo data) - (let* ([data (as-bytes data)] - [out (make-bytevector 64 0)] - [lenp (make-int-buf)] - [rc (c-digest algo data (bytevector-length data) out lenp)]) - (check-rc 'digest rc) - (let ([len (int-ref lenp)] - [result (make-bytevector (int-ref lenp))]) - (bytevector-copy! out 0 result 0 len) - result))) - - (define (md5 data) (digest "md5" data)) - (define (sha1 data) (digest "sha1" data)) - (define (sha224 data) (digest "sha224" data)) - (define (sha256 data) (digest "sha256" data)) - (define (sha384 data) (digest "sha384" data)) - (define (sha512 data) (digest "sha512" data)) - - ;; Streaming digest - (define (make-digest-ctx) (c-digest-ctx-new)) - (define (free-digest-ctx ctx) (c-digest-ctx-free ctx)) - - (define (digest-init! ctx algo) - (check-rc 'digest-init! (c-digest-init ctx algo))) - - (define (digest-update! ctx data) - (let ([data (as-bytes data)]) - (check-rc 'digest-update! (c-digest-update ctx data (bytevector-length data))))) - - (define (digest-final! ctx) - (let ([out (make-bytevector 64 0)] - [lenp (make-int-buf)]) - (check-rc 'digest-final! (c-digest-final ctx out lenp)) - (let ([len (int-ref lenp)]