From 55dcce4f7d6b16cbaf8fc47709c82a4d24f2758c Mon Sep 17 00:00:00 2001 From: Miguel Terron Date: Sun, 1 Jun 2025 21:23:34 +1200 Subject: [PATCH] Add LuaCrypto compatible functions (plus some auxiliary functions) as per #1136 --- third_party/mbedtls/config.h | 2 +- tool/net/BUILD.mk | 1 + tool/net/lcrypto.c | 1147 ++++++++++++++++++++++++++++++++++ tool/net/lcrypto.h | 10 + tool/net/lfuncs.h | 1 + tool/net/redbean.c | 1 + 6 files changed, 1161 insertions(+), 1 deletion(-) create mode 100644 tool/net/lcrypto.c create mode 100644 tool/net/lcrypto.h diff --git a/third_party/mbedtls/config.h b/third_party/mbedtls/config.h index 24f2c227b..88087503e 100644 --- a/third_party/mbedtls/config.h +++ b/third_party/mbedtls/config.h @@ -71,10 +71,10 @@ /* eliptic curves */ #define MBEDTLS_ECP_DP_SECP256R1_ENABLED #define MBEDTLS_ECP_DP_SECP384R1_ENABLED +#define MBEDTLS_ECP_DP_SECP521R1_ENABLED #define MBEDTLS_ECP_DP_CURVE25519_ENABLED #ifndef TINY #define MBEDTLS_ECP_DP_CURVE448_ENABLED -/*#define MBEDTLS_ECP_DP_SECP521R1_ENABLED*/ /*#define MBEDTLS_ECP_DP_BP384R1_ENABLED*/ /*#define MBEDTLS_ECP_DP_SECP192R1_ENABLED*/ /*#define MBEDTLS_ECP_DP_SECP224R1_ENABLED*/ diff --git a/tool/net/BUILD.mk b/tool/net/BUILD.mk index 06a80d5f8..6528b8854 100644 --- a/tool/net/BUILD.mk +++ b/tool/net/BUILD.mk @@ -100,6 +100,7 @@ TOOL_NET_REDBEAN_LUA_MODULES = \ o/$(MODE)/tool/net/lmaxmind.o \ o/$(MODE)/tool/net/lsqlite3.o \ o/$(MODE)/tool/net/largon2.o \ + o/$(MODE)/tool/net/lcrypto.o \ o/$(MODE)/tool/net/launch.o o/$(MODE)/tool/net/redbean.dbg: \ diff --git a/tool/net/lcrypto.c b/tool/net/lcrypto.c new file mode 100644 index 000000000..c33e73a03 --- /dev/null +++ b/tool/net/lcrypto.c @@ -0,0 +1,1147 @@ +#include "libc/log/log.h" +#include "net/https/https.h" +#include "third_party/lua/lauxlib.h" +#include "third_party/mbedtls/error.h" +#include "third_party/mbedtls/pk.h" +#include "third_party/mbedtls/rsa.h" +#include "third_party/mbedtls/ecdsa.h" +#include "third_party/mbedtls/x509_csr.h" +#include "third_party/mbedtls/oid.h" +#include "third_party/mbedtls/md.h" +#include "third_party/mbedtls/base64.h" + +// Standard C library and redbean utilities +#include "libc/errno.h" +#include "libc/mem/mem.h" +#include "libc/str/str.h" +#include "tool/net/luacheck.h" + +// Updated PemToJwk to parse PEM keys and convert them into JWK format +static int PemToJwk(lua_State *L) { + const char *pem_key = luaL_checkstring(L, 1); + + mbedtls_pk_context key; + mbedtls_pk_init(&key); + int ret; + + // Parse the PEM key + if ((ret = mbedtls_pk_parse_key(&key, (const unsigned char *)pem_key, strlen(pem_key) + 1, NULL, 0)) != 0 && + (ret = mbedtls_pk_parse_public_key(&key, (const unsigned char *)pem_key, strlen(pem_key) + 1)) != 0) { + lua_pushnil(L); + lua_pushfstring(L, "Failed to parse PEM key: -0x%04x", -ret); + mbedtls_pk_free(&key); + return 2; + } + + lua_newtable(L); // Create a new Lua table + + if (mbedtls_pk_get_type(&key) == MBEDTLS_PK_RSA) { + // Handle RSA keys + const mbedtls_rsa_context *rsa = mbedtls_pk_rsa(key); + size_t n_len = mbedtls_mpi_size(&rsa->N); + size_t e_len = mbedtls_mpi_size(&rsa->E); + + unsigned char *n = malloc(n_len); + unsigned char *e = malloc(e_len); + + if (!n || !e) { + lua_pushnil(L); + lua_pushstring(L, "Memory allocation failed"); + free(n); + free(e); + mbedtls_pk_free(&key); + return 2; + } + + mbedtls_mpi_write_binary(&rsa->N, n, n_len); + mbedtls_mpi_write_binary(&rsa->E, e, e_len); + + char *n_b64 = NULL, *e_b64 = NULL; + size_t n_b64_len, e_b64_len; + + mbedtls_base64_encode(NULL, 0, &n_b64_len, n, n_len); + mbedtls_base64_encode(NULL, 0, &e_b64_len, e, e_len); + + n_b64 = malloc(n_b64_len + 1); + e_b64 = malloc(e_b64_len + 1); + + if (!n_b64 || !e_b64) { + lua_pushnil(L); + lua_pushstring(L, "Memory allocation failed"); + free(n); + free(e); + free(n_b64); + free(e_b64); + mbedtls_pk_free(&key); + return 2; + } + + mbedtls_base64_encode((unsigned char *)n_b64, n_b64_len, &n_b64_len, n, n_len); + mbedtls_base64_encode((unsigned char *)e_b64, e_b64_len, &e_b64_len, e, e_len); + + n_b64[n_b64_len] = '\0'; + e_b64[e_b64_len] = '\0'; + + lua_pushstring(L, "RSA"); + lua_setfield(L, -2, "kty"); + lua_pushstring(L, n_b64); + lua_setfield(L, -2, "n"); + lua_pushstring(L, e_b64); + lua_setfield(L, -2, "e"); + + free(n); + free(e); + free(n_b64); + free(e_b64); + } else if (mbedtls_pk_get_type(&key) == MBEDTLS_PK_ECKEY) { + // Handle ECDSA keys + const mbedtls_ecp_keypair *ec = mbedtls_pk_ec(key); + const mbedtls_ecp_point *Q = &ec->Q; + size_t x_len = (ec->grp.pbits + 7) / 8; + size_t y_len = (ec->grp.pbits + 7) / 8; + + unsigned char *x = malloc(x_len); + unsigned char *y = malloc(y_len); + + if (!x || !y) { + lua_pushnil(L); + lua_pushstring(L, "Memory allocation failed"); + free(x); + free(y); + mbedtls_pk_free(&key); + return 2; + } + + mbedtls_mpi_write_binary(&Q->X, x, x_len); + mbedtls_mpi_write_binary(&Q->Y, y, y_len); + + char *x_b64 = NULL, *y_b64 = NULL; + size_t x_b64_len, y_b64_len; + + mbedtls_base64_encode(NULL, 0, &x_b64_len, x, x_len); + mbedtls_base64_encode(NULL, 0, &y_b64_len, y, y_len); + + x_b64 = malloc(x_b64_len + 1); + y_b64 = malloc(y_b64_len + 1); + + if (!x_b64 || !y_b64) { + lua_pushnil(L); + lua_pushstring(L, "Memory allocation failed"); + free(x); + free(y); + free(x_b64); + free(y_b64); + mbedtls_pk_free(&key); + return 2; + } + + mbedtls_base64_encode((unsigned char *)x_b64, x_b64_len, &x_b64_len, x, x_len); + mbedtls_base64_encode((unsigned char *)y_b64, y_b64_len, &y_b64_len, y, y_len); + + x_b64[x_b64_len] = '\0'; + y_b64[y_b64_len] = '\0'; + + lua_pushstring(L, "EC"); + lua_setfield(L, -2, "kty"); + lua_pushstring(L, mbedtls_ecp_curve_info_from_grp_id(ec->grp.id)->name); + lua_setfield(L, -2, "crv"); + lua_pushstring(L, x_b64); + lua_setfield(L, -2, "x"); + lua_pushstring(L, y_b64); + lua_setfield(L, -2, "y"); + + free(x); + free(y); + free(x_b64); + free(y_b64); + } else { + lua_pushnil(L); + lua_pushstring(L, "Unsupported key type"); + mbedtls_pk_free(&key); + return 2; + } + + mbedtls_pk_free(&key); + return 1; +} + +// CSR Creation Function +static int CreateCSR(lua_State *L) { + const char *key_pem = luaL_checkstring(L, 1); + const char *subject_name = luaL_checkstring(L, 2); + const char *san_list = luaL_optstring(L, 3, NULL); + + mbedtls_pk_context key; + mbedtls_x509write_csr req; + char buf[4096]; + int ret; + + mbedtls_pk_init(&key); + mbedtls_x509write_csr_init(&req); + + if ((ret = mbedtls_pk_parse_key(&key, (const unsigned char *)key_pem, strlen(key_pem) + 1, NULL, 0)) != 0) { + lua_pushnil(L); + lua_pushfstring(L, "Failed to parse key: %d", ret); + return 2; + } + + mbedtls_x509write_csr_set_subject_name(&req, subject_name); + mbedtls_x509write_csr_set_key(&req, &key); + mbedtls_x509write_csr_set_md_alg(&req, MBEDTLS_MD_SHA256); + + if (san_list) { + if ((ret = mbedtls_x509write_csr_set_extension(&req, MBEDTLS_OID_SUBJECT_ALT_NAME, MBEDTLS_OID_SIZE(MBEDTLS_OID_SUBJECT_ALT_NAME), (const unsigned char *)san_list, strlen(san_list))) != 0) { + lua_pushnil(L); + lua_pushfstring(L, "Failed to set SANs: %d", ret); + return 2; + } + } + + if ((ret = mbedtls_x509write_csr_pem(&req, (unsigned char *)buf, sizeof(buf), NULL, NULL)) < 0) { + lua_pushnil(L); + lua_pushfstring(L, "Failed to write CSR: %d", ret); + return 2; + } + + lua_pushstring(L, buf); + + mbedtls_pk_free(&key); + mbedtls_x509write_csr_free(&req); + + return 1; +} + + +static bool RSAGenerateKeyPair(char **private_key_pem, size_t *private_key_len, + char **public_key_pem, size_t *public_key_len, + unsigned int key_length) { + int rc; + mbedtls_pk_context key; + mbedtls_pk_init(&key); + + // Initialize as RSA key + if ((rc = mbedtls_pk_setup(&key, + mbedtls_pk_info_from_type(MBEDTLS_PK_RSA))) != 0) { + WARNF("Failed to setup key (grep -0x%04x)", -rc); + mbedtls_pk_free(&key); + return false; + } + + // Generate RSA key + if ((rc = mbedtls_rsa_gen_key(mbedtls_pk_rsa(key), GenerateHardRandom, 0, + key_length, 65537)) != 0) { + WARNF("Failed to generate key (grep -0x%04x)", -rc); + mbedtls_pk_free(&key); + return false; + } + + // Write private key to PEM + *private_key_len = 16000; // Buffer size for private key + *private_key_pem = calloc(1, *private_key_len); + if ((rc = mbedtls_pk_write_key_pem(&key, (unsigned char *)*private_key_pem, + *private_key_len)) != 0) { + WARNF("Failed to write private key (grep -0x%04x)", -rc); + free(*private_key_pem); + mbedtls_pk_free(&key); + return false; + } + *private_key_len = strlen(*private_key_pem); + + // Write public key to PEM + *public_key_len = 8000; // Buffer size for public key + *public_key_pem = calloc(1, *public_key_len); + if ((rc = mbedtls_pk_write_pubkey_pem(&key, (unsigned char *)*public_key_pem, + *public_key_len)) != 0) { + WARNF("Failed to write public key (grep -0x%04x)", -rc); + free(*private_key_pem); + free(*public_key_pem); + mbedtls_pk_free(&key); + return false; + } + *public_key_len = strlen(*public_key_pem); + + mbedtls_pk_free(&key); + return true; +} +/** + * Lua wrapper for RSA key pair generation + * + * Lua function signature: RSAGenerateKeyPair([key_length]) + * @param L Lua state + * @return 2 on success (private_key, public_key), 2 on failure (nil, + * error_message) + */ +static int LuaRSAGenerateKeyPair(lua_State *L) { + char *private_key, *public_key; + size_t private_len, public_len; + int key_length = 2048; // Default RSA key length + + // Get key length from Lua (optional parameter) + if (lua_gettop(L) >= 1 && !lua_isnil(L, 1)) { + key_length = luaL_checkinteger(L, 1); + // Validate key length (common RSA key lengths are 1024, 2048, 3072, 4096) + if (key_length != 1024 && key_length != 2048 && key_length != 3072 && + key_length != 4096) { + lua_pushnil(L); + lua_pushstring(L, + "Invalid RSA key length. Use 1024, 2048, 3072, or 4096."); + return 2; + } + } + + // Call the C function to generate the key pair + if (!RSAGenerateKeyPair(&private_key, &private_len, &public_key, &public_len, + key_length)) { + lua_pushnil(L); + lua_pushstring(L, "Failed to generate RSA key pair"); + return 2; + } + + // Push results to Lua + lua_pushstring(L, private_key); + lua_pushstring(L, public_key); + + // Clean up + free(private_key); + free(public_key); + + return 2; +} + +// RSA +static char *RSAEncrypt(const char *public_key_pem, const unsigned char *data, + size_t data_len, size_t *out_len) { + int rc; + + // Parse public key + mbedtls_pk_context key; + mbedtls_pk_init(&key); + if ((rc = mbedtls_pk_parse_public_key(&key, + (const unsigned char *)public_key_pem, + strlen(public_key_pem) + 1)) != 0) { + WARNF("Failed to parse public key (grep -0x%04x)", -rc); + mbedtls_pk_free(&key); + return NULL; + } + + // Check if key is RSA + if (mbedtls_pk_get_type(&key) != MBEDTLS_PK_RSA) { + WARNF("Key is not an RSA key"); + mbedtls_pk_free(&key); + return NULL; + } + + // Allocate output buffer + size_t key_size = mbedtls_pk_get_len(&key); + unsigned char *output = calloc(1, key_size); + if (!output) { + mbedtls_pk_free(&key); + return NULL; + } + + // Encrypt data + if ((rc = mbedtls_rsa_pkcs1_encrypt(mbedtls_pk_rsa(key), GenerateHardRandom, + 0, MBEDTLS_RSA_PUBLIC, data_len, data, + output)) != 0) { + WARNF("Encryption failed (grep -0x%04x)", -rc); + free(output); + mbedtls_pk_free(&key); + return NULL; + } + + *out_len = key_size; + mbedtls_pk_free(&key); + return (char *)output; +} +static int LuaRSAEncrypt(lua_State *L) { + const char *public_key = luaL_checkstring(L, 1); + size_t data_len; + const unsigned char *data = + (const unsigned char *)luaL_checklstring(L, 2, &data_len); + size_t out_len; + + char *encrypted = RSAEncrypt(public_key, data, data_len, &out_len); + if (!encrypted) { + lua_pushnil(L); + lua_pushstring(L, "Encryption failed"); + return 2; + } + + lua_pushlstring(L, encrypted, out_len); + free(encrypted); + + return 1; +} + +static char *RSADecrypt(const char *private_key_pem, + const unsigned char *encrypted_data, size_t encrypted_len, + size_t *out_len) { + int rc; + + // Parse private key + mbedtls_pk_context key; + mbedtls_pk_init(&key); + if ((rc = mbedtls_pk_parse_key(&key, (const unsigned char *)private_key_pem, + strlen(private_key_pem) + 1, NULL, 0)) != 0) { + WARNF("Failed to parse private key (grep -0x%04x)", -rc); + mbedtls_pk_free(&key); + return NULL; + } + + // Check if key is RSA + if (mbedtls_pk_get_type(&key) != MBEDTLS_PK_RSA) { + WARNF("Key is not an RSA key"); + mbedtls_pk_free(&key); + return NULL; + } + + // Allocate output buffer + size_t key_size = mbedtls_pk_get_len(&key); + unsigned char *output = calloc(1, key_size); + if (!output) { + mbedtls_pk_free(&key); + return NULL; + } + + // Decrypt data + size_t output_len = 0; + if ((rc = mbedtls_rsa_pkcs1_decrypt(mbedtls_pk_rsa(key), GenerateHardRandom, + 0, MBEDTLS_RSA_PRIVATE, &output_len, + encrypted_data, output, key_size)) != 0) { + WARNF("Decryption failed (grep -0x%04x)", -rc); + free(output); + mbedtls_pk_free(&key); + return NULL; + } + + *out_len = output_len; + mbedtls_pk_free(&key); + return (char *)output; +} +static int LuaRSADecrypt(lua_State *L) { + const char *private_key = luaL_checkstring(L, 1); + size_t encrypted_len; + const unsigned char *encrypted_data = + (const unsigned char *)luaL_checklstring(L, 2, &encrypted_len); + size_t out_len; + + char *decrypted = + RSADecrypt(private_key, encrypted_data, encrypted_len, &out_len); + if (!decrypted) { + lua_pushnil(L); + lua_pushstring(L, "Decryption failed"); + return 2; + } + + lua_pushlstring(L, decrypted, out_len); + free(decrypted); + + return 1; +} + +// RSA Signing +static char *RSASign(const char *private_key_pem, const unsigned char *data, + size_t data_len, const char *hash_algo_str, size_t *sig_len) { + int rc; + unsigned char hash[64]; // Large enough for SHA-512 + size_t hash_len = 32; // Default for SHA-256 + unsigned char *signature; + mbedtls_md_type_t hash_algo = MBEDTLS_MD_SHA256; // Default + + // Determine hash algorithm + if (hash_algo_str) { + if (strcasecmp(hash_algo_str, "sha256") == 0) { + hash_algo = MBEDTLS_MD_SHA256; + hash_len = 32; + } else if (strcasecmp(hash_algo_str, "sha384") == 0) { + hash_algo = MBEDTLS_MD_SHA384; + hash_len = 48; + } else if (strcasecmp(hash_algo_str, "sha512") == 0) { + hash_algo = MBEDTLS_MD_SHA512; + hash_len = 64; + } else { + return NULL; // Unsupported hash algorithm + } + } + + // Parse private key + mbedtls_pk_context key; + mbedtls_pk_init(&key); + if ((rc = mbedtls_pk_parse_key(&key, (const unsigned char *)private_key_pem, + strlen(private_key_pem) + 1, NULL, 0)) != 0) { + WARNF("Failed to parse private key (grep -0x%04x)", -rc); + mbedtls_pk_free(&key); + return NULL; + } + + // Check if key is RSA + if (mbedtls_pk_get_type(&key) != MBEDTLS_PK_RSA) { + WARNF("Key is not an RSA key"); + mbedtls_pk_free(&key); + return NULL; + } + + // Hash the message + if ((rc = mbedtls_md(mbedtls_md_info_from_type(hash_algo), data, data_len, + hash)) != 0) { + mbedtls_pk_free(&key); + return NULL; + } + + // Allocate buffer for signature + signature = malloc(MBEDTLS_PK_SIGNATURE_MAX_SIZE); + if (!signature) { + mbedtls_pk_free(&key); + return NULL; + } + + // Sign the hash + if ((rc = mbedtls_pk_sign(&key, hash_algo, hash, hash_len, signature, sig_len, + GenerateHardRandom, 0)) != 0) { + free(signature); + mbedtls_pk_free(&key); + return NULL; + } + + // Clean up + mbedtls_pk_free(&key); + + return (char *)signature; +} +static int LuaRSASign(lua_State *L) { + size_t msg_len, key_len; + const char *msg, *key_pem, *hash_algo_str = NULL; + unsigned char *signature; + size_t sig_len = 0; + + // Get parameters from Lua + key_pem = luaL_checklstring(L, 1, &key_len); + msg = luaL_checklstring(L, 2, &msg_len); + + // Optional hash algorithm parameter + if (!lua_isnoneornil(L, 3)) { + hash_algo_str = luaL_checkstring(L, 3); + } + + // Call the C implementation + signature = (unsigned char *)RSASign(key_pem, (const unsigned char *)msg, + msg_len, hash_algo_str, &sig_len); + + if (!signature) { + return luaL_error(L, "failed to sign message"); + } + + // Return the signature as a Lua string + lua_pushlstring(L, (char *)signature, sig_len); + + // Clean up + free(signature); + + return 1; +} + +static int RSAVerify(const char *public_key_pem, const unsigned char *data, + size_t data_len, const unsigned char *signature, + size_t sig_len, const char *hash_algo_str) { + int rc; + unsigned char hash[64]; // Large enough for SHA-512 + size_t hash_len = 32; // Default for SHA-256 + mbedtls_md_type_t hash_algo = MBEDTLS_MD_SHA256; // Default + + // Determine hash algorithm + if (hash_algo_str) { + if (strcasecmp(hash_algo_str, "sha256") == 0) { + hash_algo = MBEDTLS_MD_SHA256; + hash_len = 32; + } else if (strcasecmp(hash_algo_str, "sha384") == 0) { + hash_algo = MBEDTLS_MD_SHA384; + hash_len = 48; + } else if (strcasecmp(hash_algo_str, "sha512") == 0) { + hash_algo = MBEDTLS_MD_SHA512; + hash_len = 64; + } else { + return -1; // Unsupported hash algorithm + } + } + + // Parse public key + mbedtls_pk_context key; + mbedtls_pk_init(&key); + if ((rc = mbedtls_pk_parse_public_key(&key, + (const unsigned char *)public_key_pem, + strlen(public_key_pem) + 1)) != 0) { + WARNF("Failed to parse public key (grep -0x%04x)", -rc); + mbedtls_pk_free(&key); + return -1; + } + + // Check if key is RSA + if (mbedtls_pk_get_type(&key) != MBEDTLS_PK_RSA) { + WARNF("Key is not an RSA key"); + mbedtls_pk_free(&key); + return -1; + } + + // Hash the message + if ((rc = mbedtls_md(mbedtls_md_info_from_type(hash_algo), data, data_len, + hash)) != 0) { + mbedtls_pk_free(&key); + return -1; + } + + // Verify the signature + rc = mbedtls_pk_verify(&key, hash_algo, hash, hash_len, signature, sig_len); + + // Clean up + mbedtls_pk_free(&key); + + return rc; // 0 means success (valid signature) +} +static int LuaRSAVerify(lua_State *L) { + size_t msg_len, key_len, sig_len; + const char *msg, *key_pem, *signature, *hash_algo_str = NULL; + int result; + + // Get parameters from Lua + key_pem = luaL_checklstring(L, 1, &key_len); + msg = luaL_checklstring(L, 2, &msg_len); + signature = luaL_checklstring(L, 3, &sig_len); + + // Optional hash algorithm parameter + if (!lua_isnoneornil(L, 4)) { + hash_algo_str = luaL_checkstring(L, 4); + } + + // Call the C implementation + result = RSAVerify(key_pem, (const unsigned char *)msg, msg_len, + (const unsigned char *)signature, sig_len, hash_algo_str); + + // Return boolean result (0 means valid signature) + lua_pushboolean(L, result == 0); + + return 1; +} + + +// Supported curves mapping +typedef struct { + const char *name; + mbedtls_ecp_group_id id; +} curve_map_t; + +static const curve_map_t supported_curves[] = { + {"secp256r1", MBEDTLS_ECP_DP_SECP256R1}, + {"secp384r1", MBEDTLS_ECP_DP_SECP384R1}, + {"secp521r1", MBEDTLS_ECP_DP_SECP521R1}, + {"secp192r1", MBEDTLS_ECP_DP_SECP192R1}, + {"secp224r1", MBEDTLS_ECP_DP_SECP224R1}, + {"curve25519", MBEDTLS_ECP_DP_CURVE25519}, + {NULL, 0}}; + +typedef enum { SHA256, SHA384, SHA512 } hash_algorithm_t; + +static mbedtls_md_type_t hash_to_md_type(hash_algorithm_t hash_alg) { + switch (hash_alg) { + case SHA256: + return MBEDTLS_MD_SHA256; + case SHA384: + return MBEDTLS_MD_SHA384; + case SHA512: + return MBEDTLS_MD_SHA512; + default: + return MBEDTLS_MD_SHA256; // Default to SHA-256 + } +} + +static size_t get_hash_size(hash_algorithm_t hash_alg) { + switch (hash_alg) { + case SHA256: + return 32; + case SHA384: + return 48; + case SHA512: + return 64; + default: + return 32; // Default to SHA-256 + } +} + +static hash_algorithm_t string_to_hash_alg(const char *hash_name) { + if (!hash_name || !*hash_name) { + return SHA256; // Default to SHA-256 if no name provided + } + + if (strcasecmp(hash_name, "sha256") == 0 || + strcasecmp(hash_name, "sha-256") == 0) { + return SHA256; + } else if (strcasecmp(hash_name, "sha384") == 0 || + strcasecmp(hash_name, "sha-384") == 0) { + return SHA384; + } else if (strcasecmp(hash_name, "sha512") == 0 || + strcasecmp(hash_name, "sha-512") == 0) { + return SHA512; + } else { + WARNF("(ecdsa) Unknown hash algorithm '%s', using SHA-256", hash_name); + return SHA256; + } +} + +static int LuaListHashAlgorithms(lua_State *L) { + lua_newtable(L); + + lua_pushstring(L, "SHA256"); + lua_rawseti(L, -2, 1); + + lua_pushstring(L, "SHA384"); + lua_rawseti(L, -2, 2); + + lua_pushstring(L, "SHA512"); + lua_rawseti(L, -2, 3); + + // Add hyphenated versions + lua_pushstring(L, "SHA-256"); + lua_rawseti(L, -2, 4); + + lua_pushstring(L, "SHA-384"); + lua_rawseti(L, -2, 5); + + lua_pushstring(L, "SHA-512"); + lua_rawseti(L, -2, 6); + + return 1; +} +// List available curves +static int LuaListCurves(lua_State *L) { + const curve_map_t *curve = supported_curves; + int i = 1; + + lua_newtable(L); + + while (curve->name != NULL) { + lua_pushstring(L, curve->name); + lua_rawseti(L, -2, i++); + curve++; + } + + return 1; +} + +static int compute_hash(hash_algorithm_t hash_alg, const unsigned char *input, + size_t input_len, unsigned char *output, + size_t output_size) { + mbedtls_md_context_t md_ctx; + const mbedtls_md_info_t *md_info; + int ret; + + mbedtls_md_type_t md_type = hash_to_md_type(hash_alg); + md_info = mbedtls_md_info_from_type(md_type); + if (md_info == NULL) { + WARNF("(ecdsa) Unsupported hash algorithm"); + return -1; + } + + if (output_size < mbedtls_md_get_size(md_info)) { + WARNF("(ecdsa) Output buffer too small for hash"); + return -1; + } + + mbedtls_md_init(&md_ctx); + + ret = mbedtls_md_setup(&md_ctx, md_info, 0); // 0 = non-HMAC + if (ret != 0) { + WARNF("(ecdsa) Failed to set up hash context: -0x%04x", -ret); + goto cleanup; + } + + ret = mbedtls_md_starts(&md_ctx); + if (ret != 0) { + WARNF("(ecdsa) Failed to start hash operation: -0x%04x", -ret); + goto cleanup; + } + + ret = mbedtls_md_update(&md_ctx, input, input_len); + if (ret != 0) { + WARNF("(ecdsa) Failed to update hash: -0x%04x", -ret); + goto cleanup; + } + + ret = mbedtls_md_finish(&md_ctx, output); + if (ret != 0) { + WARNF("(ecdsa) Failed to finish hash: -0x%04x", -ret); + goto cleanup; + } + +cleanup: + mbedtls_md_free(&md_ctx); + return ret; +} + +// Find curve ID by name +static mbedtls_ecp_group_id find_curve_by_name(const char *name) { + const curve_map_t *curve = supported_curves; + + while (curve->name != NULL) { + if (strcasecmp(curve->name, name) == 0) { + return curve->id; + } + curve++; + } + + return MBEDTLS_ECP_DP_NONE; +} + +// Generate an ECDSA key pair and return in PEM format +static int ECDSAGenerateKeyPair(const char *curve_name, char **priv_key_pem, + char **pub_key_pem) { + mbedtls_pk_context key; + unsigned char output_buf[16000]; + int ret; + mbedtls_ecp_group_id curve_id; + + // Initialize output parameters to NULL in case of early return + if (priv_key_pem) + *priv_key_pem = NULL; + if (pub_key_pem) + *pub_key_pem = NULL; + + // Use secp256r1 as default if curve_name is NULL or empty + if (curve_name == NULL || curve_name[0] == '\0') { + curve_id = MBEDTLS_ECP_DP_SECP256R1; + VERBOSEF("(ecdsa) No curve specified, using default: secp256r1"); + } else { + // Find the curve by name + curve_id = find_curve_by_name(curve_name); + if (curve_id == MBEDTLS_ECP_DP_NONE) { + WARNF("(ecdsa) Unknown curve: %s, using default: secp256r1", curve_name); + curve_id = MBEDTLS_ECP_DP_SECP256R1; + } + } + + mbedtls_pk_init(&key); + + // Generate the key with the specified curve + ret = mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); + if (ret != 0) { + WARNF("(ecdsa) Failed to setup key: -0x%04x", -ret); + goto cleanup; + } + + ret = + mbedtls_ecp_gen_key(curve_id, mbedtls_pk_ec(key), GenerateHardRandom, 0); + if (ret != 0) { + WARNF("(ecdsa) Failed to generate key: -0x%04x", -ret); + goto cleanup; + } + + // Generate private key PEM + if (priv_key_pem != NULL) { + memset(output_buf, 0, sizeof(output_buf)); + ret = mbedtls_pk_write_key_pem(&key, output_buf, sizeof(output_buf)); + if (ret != 0) { + WARNF("(ecdsa) Failed to write private key: -0x%04x", -ret); + goto cleanup; + } + *priv_key_pem = strdup((char *)output_buf); + if (*priv_key_pem == NULL) { + WARNF("(ecdsa) Failed to allocate memory for private key PEM"); + ret = -1; + goto cleanup; + } + } + + // Generate public key PEM + if (pub_key_pem != NULL) { + memset(output_buf, 0, sizeof(output_buf)); + ret = mbedtls_pk_write_pubkey_pem(&key, output_buf, sizeof(output_buf)); + if (ret != 0) { + WARNF("(ecdsa) Failed to write public key: -0x%04x", -ret); + goto cleanup; + } + *pub_key_pem = strdup((char *)output_buf); + if (*pub_key_pem == NULL) { + WARNF("(ecdsa) Failed to allocate memory for public key PEM"); + ret = -1; + goto cleanup; + } + } + +cleanup: + mbedtls_pk_free(&key); + if (ret != 0) { + // Clean up on error + if (priv_key_pem && *priv_key_pem) { + free(*priv_key_pem); + *priv_key_pem = NULL; + } + if (pub_key_pem && *pub_key_pem) { + free(*pub_key_pem); + *pub_key_pem = NULL; + } + } + return ret; +} +// Lua binding for generating ECDSA keys +static int LuaECDSAGenerateKeyPair(lua_State *L) { + const char *curve_name = NULL; + char *priv_key_pem = NULL; + char *pub_key_pem = NULL; + + // Check if curve name is provided + if (lua_gettop(L) >= 1 && !lua_isnil(L, 1)) { + curve_name = luaL_checkstring(L, 1); + } + // If not provided, generate_key_pem will use the default + + int ret = ECDSAGenerateKeyPair(curve_name, &priv_key_pem, &pub_key_pem); + + if (ret == 0) { + lua_pushstring(L, priv_key_pem); + lua_pushstring(L, pub_key_pem); + free(priv_key_pem); + free(pub_key_pem); + return 2; + } else { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } +} + +// Sign a message using an ECDSA private key in PEM format +static int ECDSASign(const char *priv_key_pem, const char *message, + hash_algorithm_t hash_alg, unsigned char **signature, + size_t *sig_len) { + mbedtls_pk_context key; + unsigned char hash[64]; // Max hash size (SHA-512) + size_t hash_size; + int ret; + + *signature = NULL; + *sig_len = 0; + + if (!priv_key_pem) { + WARNF("(ecdsa) Private key is NULL"); + return -1; + } + + // Get the length of the PEM string (excluding null terminator) + size_t key_len = strlen(priv_key_pem); + if (key_len == 0) { + WARNF("(ecdsa) Private key is empty"); + return -1; + } + + // Get hash size for the selected algorithm + hash_size = get_hash_size(hash_alg); + + mbedtls_pk_init(&key); + + // Parse the private key from PEM directly without creating a copy + ret = mbedtls_pk_parse_key(&key, (const unsigned char *)priv_key_pem, + key_len + 1, NULL, 0); + + if (ret != 0) { + WARNF("(ecdsa) Failed to parse private key: -0x%04x", -ret); + goto cleanup; + } + + // Compute hash of the message using the specified algorithm + ret = compute_hash(hash_alg, (const unsigned char *)message, strlen(message), + hash, sizeof(hash)); + if (ret != 0) { + WARNF("(ecdsa) Failed to compute message hash"); + goto cleanup; + } + + // Allocate memory for signature (max size for ECDSA) + *signature = malloc(MBEDTLS_ECDSA_MAX_LEN); + if (*signature == NULL) { + WARNF("(ecdsa) Failed to allocate memory for signature"); + ret = -1; + goto cleanup; + } + + // Sign the hash using GenerateHardRandom + ret = mbedtls_pk_sign(&key, hash_to_md_type(hash_alg), hash, hash_size, + *signature, sig_len, GenerateHardRandom, 0); + + if (ret != 0) { + WARNF("(ecdsa) Failed to sign message: -0x%04x", -ret); + free(*signature); + *signature = NULL; + *sig_len = 0; + goto cleanup; + } + +cleanup: + mbedtls_pk_free(&key); + return ret; +} // Lua binding for signing a message +static int LuaECDSASign(lua_State *L) { + const char *hash_name = luaL_optstring(L, 1, "sha256"); // Default to SHA-256 + const char *message = luaL_checkstring(L, 2); + const char *priv_key_pem = luaL_checkstring(L, 3); + + hash_algorithm_t hash_alg = string_to_hash_alg(hash_name); + + unsigned char *signature = NULL; + size_t sig_len = 0; + + int ret = ECDSASign(priv_key_pem, message, hash_alg, &signature, &sig_len); + + if (ret == 0) { + lua_pushlstring(L, (const char *)signature, sig_len); + free(signature); + } else { + lua_pushnil(L); + } + + return 1; +} + +// Verify a signature using an ECDSA public key in PEM format +static int ECDSAVerify(const char *pub_key_pem, const char *message, + const unsigned char *signature, size_t sig_len, + hash_algorithm_t hash_alg) { + mbedtls_pk_context key; + unsigned char hash[64]; // Max hash size (SHA-512) + size_t hash_size; + int ret; + + if (!pub_key_pem) { + WARNF("(ecdsa) Public key is NULL"); + return -1; + } + + // Get the length of the PEM string (excluding null terminator) + size_t key_len = strlen(pub_key_pem); + if (key_len == 0) { + WARNF("(ecdsa) Public key is empty"); + return -1; + } + + // Get hash size for the selected algorithm + hash_size = get_hash_size(hash_alg); + + mbedtls_pk_init(&key); + + // Parse the public key from PEM + ret = mbedtls_pk_parse_public_key(&key, (const unsigned char *)pub_key_pem, + key_len + 1); + if (ret != 0) { + WARNF("(ecdsa) Failed to parse public key: -0x%04x", -ret); + goto cleanup; + } + + // Compute hash of the message using the specified algorithm + ret = compute_hash(hash_alg, (const unsigned char *)message, strlen(message), + hash, sizeof(hash)); + if (ret != 0) { + WARNF("(ecdsa) Failed to compute message hash"); + goto cleanup; + } + + // Verify the signature + ret = mbedtls_pk_verify(&key, hash_to_md_type(hash_alg), hash, hash_size, + signature, sig_len); + if (ret != 0) { + WARNF("(ecdsa) Signature verification failed: -0x%04x", -ret); + goto cleanup; + } + +cleanup: + mbedtls_pk_free(&key); + return ret; +} +static int LuaECDSAVerify(lua_State *L) { + const char *hash_name = luaL_optstring(L, 1, "sha256"); // Default to SHA-256 + const char *message = luaL_checkstring(L, 2); + const char *pub_key_pem = luaL_checkstring(L, 3); + size_t sig_len; + const unsigned char *signature = + (const unsigned char *)luaL_checklstring(L, 4, &sig_len); + + hash_algorithm_t hash_alg = string_to_hash_alg(hash_name); + + int ret = ECDSAVerify(pub_key_pem, message, signature, sig_len, hash_alg); + + lua_pushboolean(L, ret == 0); + return 1; +} + +static int LuaCryptoSign(lua_State *L) { + const char *dtype = luaL_checkstring(L, 1); // Type of signature (e.g., "rsa", "ecdsa") + lua_remove(L, 1); // Remove the first argument (key type or cipher type) before dispatching + + if (strcasecmp(dtype, "rsa") == 0) { + return LuaRSASign(L); + } else if (strcasecmp(dtype, "ecdsa") == 0) { + return LuaECDSASign(L); + } else { + return luaL_error(L, "Unsupported signature type: %s", dtype); + } +} + +static int LuaCryptoVerify(lua_State *L) { + const char *dtype = luaL_checkstring(L, 1); // Type of signature (e.g., "rsa", "ecdsa") + lua_remove(L, 1); // Remove the first argument (key type or cipher type) before dispatching + + if (strcasecmp(dtype, "rsa") == 0) { + return LuaRSAVerify(L); + } else if (strcasecmp(dtype, "ecdsa") == 0) { + return LuaECDSAVerify(L); + } else { + return luaL_error(L, "Unsupported signature type: %s", dtype); + } +} + +static int LuaCryptoEncrypt(lua_State *L) { + const char *cipher = luaL_checkstring(L, 1); // Cipher type (e.g., "rsa") + lua_remove(L, 1); // Remove the first argument (key type or cipher type) before dispatching + + if (strcasecmp(cipher, "rsa") == 0) { + return LuaRSAEncrypt(L); + } else { + return luaL_error(L, "Unsupported cipher type: %s", cipher); + } +} + +static int LuaCryptoDecrypt(lua_State *L) { + const char *cipher = luaL_checkstring(L, 1); // Cipher type (e.g., "rsa") + lua_remove(L, 1); // Remove the first argument (key type or cipher type) before dispatching + + if (strcasecmp(cipher, "rsa") == 0) { + return LuaRSADecrypt(L); + } else { + return luaL_error(L, "Unsupported cipher type: %s", cipher); + } +} + +static int LuaCryptoGenerateKeyPair(lua_State *L) { + const char *key_type = luaL_checkstring(L, 1); // Key type (e.g., "rsa", "ecdsa") + lua_remove(L, 1); // Remove the first argument (key type or cipher type) before dispatching + + if (strcasecmp(key_type, "rsa") == 0) { + return LuaRSAGenerateKeyPair(L); + } else if (strcasecmp(key_type, "ecdsa") == 0) { + return LuaECDSAGenerateKeyPair(L); + } else { + return luaL_error(L, "Unsupported key type: %s", key_type); + } +} + +static const luaL_Reg kLuaCrypto[] = { + {"sign", LuaCryptoSign}, // + {"verify", LuaCryptoVerify}, // + {"encrypt", LuaCryptoEncrypt}, // + {"decrypt", LuaCryptoDecrypt}, // + {"generatekeypair", LuaCryptoGenerateKeyPair}, // + {"PemToJwk", PemToJwk}, // + {"csrGenerate", CreateCSR}, // + {0}, // +}; + +int LuaCrypto(lua_State *L) { + luaL_newlib(L, kLuaCrypto); + return 1; +} diff --git a/tool/net/lcrypto.h b/tool/net/lcrypto.h new file mode 100644 index 000000000..e1e11ed65 --- /dev/null +++ b/tool/net/lcrypto.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_TOOL_NET_LCRYPTO_H_ +#define COSMOPOLITAN_TOOL_NET_LCRYPTO_H_ +#include "third_party/lua/lauxlib.h" +COSMOPOLITAN_C_START_ + +int LuaCrypto(lua_State *L); +int luaopen_lcrypto(lua_State *L); + +COSMOPOLITAN_C_END_ +#endif /* COSMOPOLITAN_TOOL_NET_LCRYPTO_H_ */ diff --git a/tool/net/lfuncs.h b/tool/net/lfuncs.h index 7bc3fc748..4fcbd0fa5 100644 --- a/tool/net/lfuncs.h +++ b/tool/net/lfuncs.h @@ -8,6 +8,7 @@ int LuaMaxmind(lua_State *); int LuaRe(lua_State *); int luaopen_argon2(lua_State *); int luaopen_lsqlite3(lua_State *); +int LuaCrypto(lua_State *); int LuaBarf(lua_State *); int LuaBenchmark(lua_State *); diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 93816d1aa..c7b9de601 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -5426,6 +5426,7 @@ static const luaL_Reg kLuaLibs[] = { {"path", LuaPath}, // {"re", LuaRe}, // {"unix", LuaUnix}, // + {"crypto", LuaCrypto}, // }; static void LuaSetArgv(lua_State *L) {