diff options
Diffstat (limited to 'lib/crypto-openssl.c')
-rw-r--r-- | lib/crypto-openssl.c | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/lib/crypto-openssl.c b/lib/crypto-openssl.c new file mode 100644 index 0000000..d0629fa --- /dev/null +++ b/lib/crypto-openssl.c @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2024 Mikael Magnusson <mikma@users.sourceforge.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdint.h> +#include <errno.h> +#include <alloca.h> + +#include <ucode/module.h> + +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/x509.h> + +static void __attribute__((constructor)) load(); +static void __attribute__((destructor)) unload(); + +#define PK_TYPE "crypto_openssl.pk" + +#define TRUE ucv_boolean_new(true) +#define FALSE ucv_boolean_new(false) + +static uc_resource_type_t *pk_type; + +static void raise_openssl_exception(uc_vm_t *vm, const char *msg) +{ + unsigned long err = ERR_get_error(); + char buf[120]; + + ERR_error_string_n(err, buf, sizeof(buf)); + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, msg, buf); +} + + +static uc_value_t * +md_digest(uc_vm_t *vm, size_t nargs) +{ + EVP_MD_CTX *mdctx = NULL; + uc_value_t *rv = NULL; + + uc_value_t *alg = uc_fn_arg(0); + uc_value_t *input = uc_fn_arg(1); + + if (ucv_type(alg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "alg not a string"); + goto fail; + } + + if (ucv_type(input) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "input not a string"); + goto fail; + } + + const EVP_MD *md = EVP_get_digestbyname(ucv_string_get(alg)); + + if (!md) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "unknown MD algorithm"); + goto fail; + } + + mdctx = EVP_MD_CTX_create(); + + if (1 != EVP_DigestInit_ex(mdctx, md, NULL)) { + EVP_MD_CTX_destroy(mdctx); + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "EVP_DigestInit_ex failed"); + goto fail; + } + + if (1 != EVP_DigestUpdate(mdctx, ucv_string_get(input), ucv_string_length(input))) { + EVP_MD_CTX_destroy(mdctx); + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "EVP_DigestUpdate failed"); + goto fail; + } + + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned int md_len = 0; + + if (1 != EVP_DigestFinal_ex(mdctx, md_value, &md_len)) { + EVP_MD_CTX_destroy(mdctx); + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "EVP_DigestFinal_ex failed"); + goto fail; + } + + rv = ucv_string_new_length((const char*)md_value, md_len); + +fail: + EVP_MD_CTX_destroy(mdctx); + return rv; +} + +struct context { + EVP_MD_CTX *mdctx; + EVP_PKEY *pkey; +}; + +static uc_value_t * +pk_init(uc_vm_t *vm, size_t nargs) +{ + struct context *ctx = calloc(1, sizeof(struct context)); + + ctx->mdctx = EVP_MD_CTX_create(); + return uc_resource_new(pk_type, ctx); +} + +static void +pk_free(void *ptr) +{ + struct context *ctx = ptr; + + EVP_PKEY_free(ctx->pkey); + EVP_MD_CTX_destroy(ctx->mdctx); + free(ctx); +} + +static uc_value_t * +pk_keygen(uc_vm_t *vm, size_t nargs) +{ + struct context *ctx = uc_fn_thisval(PK_TYPE); + + if (!ctx) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid " PK_TYPE " object"); + return NULL; + } + + EVP_PKEY_free(ctx->pkey); + ctx->pkey = NULL; + + uc_value_t *type = uc_fn_arg(0); + if (ucv_type(type) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "type is not a string"); + return NULL; + } + + const char *type_str = ucv_string_get(type); + + if (!OPENSSL_strcasecmp(type_str, "EC")) { + if (nargs != 2) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expected 2 arguments got %d", nargs); + return NULL; + } + + uc_value_t *curve_v = uc_fn_arg(1); + if (ucv_type(curve_v) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "curve is not a string"); + return NULL; + } + + const char *curve = ucv_string_get(curve_v); + ctx->pkey = EVP_PKEY_Q_keygen(NULL, NULL, type_str, curve); + } else if (!OPENSSL_strcasecmp(type_str, "RSA")) { + if (nargs != 2) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expected 2 arguments got %d", nargs); + return NULL; + } + + uc_value_t *size_v = uc_fn_arg(1); + if (ucv_type(size_v) != UC_INTEGER) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "size is not a string"); + return NULL; + } + + size_t size = ucv_to_integer(size_v); + ctx->pkey = EVP_PKEY_Q_keygen(NULL, NULL, type_str, size); + } else { + if (nargs != 1) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Expected 1 argument got %d", nargs); + return NULL; + } + + ctx->pkey = EVP_PKEY_Q_keygen(NULL, NULL, type_str); + } + + if (!ctx->pkey) { + raise_openssl_exception(vm, "Keygen failed: %s"); + return NULL; + } + + return TRUE; +} + +static uc_value_t * +pk_get_public_key(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *res = NULL; + struct context *ctx = uc_fn_thisval(PK_TYPE); + + if (!ctx) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid " PK_TYPE " object"); + return NULL; + } + + if (!ctx->pkey) + return NULL; + + unsigned char *key = NULL; + int size = i2d_PUBKEY(ctx->pkey, &key); + + if (size < 0) { + raise_openssl_exception(vm, "failed to encode public key: %s"); + goto fail; + } + + res = ucv_string_new_length((const char*)key, size); + +fail: + free(key); + return res; +} + + +static uc_value_t * +pk_set_raw_public_key(uc_vm_t *vm, size_t nargs) +{ + struct context *ctx = uc_fn_thisval(PK_TYPE); + + if (!ctx) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid " PK_TYPE " object"); + return NULL; + } + + uc_value_t *type = uc_fn_arg(0); + if (ucv_type(type) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "type is not a string"); + return NULL; + } + + const char *type_str = ucv_string_get(type); + int type_id = 0; + if (!OPENSSL_strcasecmp(type_str, SN_ED25519)) + type_id = EVP_PKEY_ED25519; + else if (!OPENSSL_strcasecmp(type_str, SN_ED448)) + type_id = EVP_PKEY_ED448; + else { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "type is not a valid raw key type (ED25519 or ED448)"); + return NULL; + } + + uc_value_t *key = uc_fn_arg(1); + if (ucv_type(key) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "key is not a string"); + return NULL; + } + + EVP_PKEY_free(ctx->pkey); + ctx->pkey = NULL; + + const unsigned char *key_str = (const unsigned char*)ucv_string_get(key); + ctx->pkey = EVP_PKEY_new_raw_public_key(type_id, NULL, key_str, ucv_string_length(key)); + + if (!ctx->pkey) { + unsigned long err = ERR_get_error(); + char buf[120]; + + ERR_error_string_n(err, buf, sizeof(buf)); + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "not a valid raw key: %s", buf); + return NULL; + } + + return TRUE; +} + +static uc_value_t * +pk_set_public_key(uc_vm_t *vm, size_t nargs) +{ + struct context *ctx = uc_fn_thisval(PK_TYPE); + + if (!ctx) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid " PK_TYPE " object"); + return NULL; + } + + uc_value_t *key = uc_fn_arg(0); + + EVP_PKEY_free(ctx->pkey); + ctx->pkey = NULL; + + if (ucv_type(key) == UC_NULL) { + return TRUE; + } + + if (ucv_type(key) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "key is not a string"); + return NULL; + } + + const unsigned char *key_str = (const unsigned char*)ucv_string_get(key); + + if (!d2i_PUBKEY(&ctx->pkey, &key_str, ucv_string_length(key))) { + unsigned long err = ERR_get_error(); + char buf[120]; + + ERR_error_string_n(err, buf, sizeof(buf)); + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "not a valid PEM/DER key: %s", buf); + return NULL; + } + + return TRUE; +} + +static uc_value_t * +pk_sign(uc_vm_t *vm, size_t nargs) +{ + struct context *ctx = uc_fn_thisval(PK_TYPE); + + if (!ctx) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid " PK_TYPE " object"); + return NULL; + } + + uc_value_t *md_alg = uc_fn_arg(0); + uc_value_t *msg = uc_fn_arg(1); + + if (md_alg != NULL && ucv_type(md_alg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "md_alg is not a string"); + return NULL; + } + + if (ucv_type(msg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "msg is not a string"); + return NULL; + } + + const EVP_MD *md = NULL; + if (md_alg) { + md = EVP_get_digestbyname(ucv_string_get(md_alg)); + if (!md) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "unknown MD algorithm"); + return NULL; + } + } + + if(1 != EVP_DigestSignInit(ctx->mdctx, NULL, md, NULL, ctx->pkey)) { + raise_openssl_exception(vm, "EVP_DigestSignInit failed: %s"); + return NULL; + } + + size_t siglen = 0; + + if(1 != EVP_DigestSign(ctx->mdctx, NULL, &siglen, + (const unsigned char*)ucv_string_get(msg), ucv_string_length(msg))) { + raise_openssl_exception(vm, "EVP_DigestSign failed: %s"); + return NULL; + } + + unsigned char *sig = alloca(siglen); + + if(1 != EVP_DigestSign(ctx->mdctx, sig, &siglen, + (const unsigned char*)ucv_string_get(msg), ucv_string_length(msg))) { + raise_openssl_exception(vm, "EVP_DigestSign failed: %s"); + return NULL; + } + + uc_value_t *rv = ucv_string_new_length((const char*)sig, siglen); + return rv; +} + +static uc_value_t * +pk_verify(uc_vm_t *vm, size_t nargs) +{ + struct context *ctx = uc_fn_thisval(PK_TYPE); + + if (!ctx) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid " PK_TYPE " object"); + return NULL; + } + + uc_value_t *md_alg = uc_fn_arg(0); + uc_value_t *msg = uc_fn_arg(1); + uc_value_t *sig = uc_fn_arg(2); + + if (md_alg != NULL && ucv_type(md_alg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "md_alg is not a string"); + return NULL; + } + + if (ucv_type(msg) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "msg is not a string"); + return NULL; + } + + if (ucv_type(sig) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "sig is not a string"); + return NULL; + } + + const EVP_MD *md = NULL; + if (md_alg) { + md = EVP_get_digestbyname(ucv_string_get(md_alg)); + if (!md) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "unknown MD algorithm"); + return NULL; + } + } + + if(1 != EVP_DigestVerifyInit(ctx->mdctx, NULL, md, NULL, ctx->pkey)) { + raise_openssl_exception(vm, "EVP_DigestVerifyInit failed: %s"); + return NULL; + } + + if(1 != EVP_DigestVerify(ctx->mdctx, + (const unsigned char*)ucv_string_get(sig), ucv_string_length(sig), + (const unsigned char*)ucv_string_get(msg), ucv_string_length(msg))) { + raise_openssl_exception(vm, "EVP_DigestVerify failed: %s"); + return NULL; + } else { + return TRUE; + } +} + +static const uc_function_list_t global_fns[] = { + { "md_digest", md_digest }, + { "pk", pk_init }, +}; + +static const uc_function_list_t pk_fns[] = { + { "keygen", pk_keygen }, + { "get_public_key", pk_get_public_key }, + { "set_raw_public_key", pk_set_raw_public_key }, + { "set_public_key", pk_set_public_key }, + { "sign", pk_sign }, + { "verify", pk_verify }, +}; + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_function_list_register(scope, global_fns); + + pk_type = uc_type_declare(vm, PK_TYPE, pk_fns, pk_free); +} + +static void load() +{ + OpenSSL_add_all_digests(); +} + +static void unload() +{ + /* Cleanup OpenSSL_add_all_digests */ + EVP_cleanup(); +} |