summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/macos.yml2
-rw-r--r--.github/workflows/openwrt-ci-pull-request.yml2
-rw-r--r--CMakeLists.txt33
-rw-r--r--docs/README.md4
-rw-r--r--jsdoc/conf.json4
-rw-r--r--lib/crypto-mbedtls.c416
-rw-r--r--lib/crypto-openssl.c445
-rw-r--r--lib/crypto.uc146
-rw-r--r--tests/custom/05_crypto/01_rsa85
-rw-r--r--tests/custom/05_crypto/02_ecdsa125
-rw-r--r--tests/custom/05_crypto/03_eddsa70
-rw-r--r--tests/custom/05_crypto/04_keygen80
12 files changed, 1406 insertions, 6 deletions
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index f8eb43b..983cf2c 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -15,7 +15,7 @@ jobs:
- name: Setup
run: |
- brew install json-c libmd
+ brew install json-c libmd mbedtls@3 openssl@3
- name: Build minimal version
run: |
diff --git a/.github/workflows/openwrt-ci-pull-request.yml b/.github/workflows/openwrt-ci-pull-request.yml
index 4db2cb8..5729a48 100644
--- a/.github/workflows/openwrt-ci-pull-request.yml
+++ b/.github/workflows/openwrt-ci-pull-request.yml
@@ -6,7 +6,7 @@ on:
env:
CI_ENABLE_UNIT_TESTING: 1
- CI_TARGET_BUILD_DEPENDS: libnl-tiny ubus uci
+ CI_TARGET_BUILD_DEPENDS: libnl-tiny ubus uci mbedtls openssl
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d7e7006..a71ecde 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -30,6 +30,8 @@ find_library(libubus NAMES ubus)
find_library(libblobmsg_json NAMES blobmsg_json)
find_package(ZLIB)
find_library(libmd NAMES libmd.a md)
+find_library(libmbedtls NAMES mbedtls)
+find_package(OpenSSL)
if(LINUX)
find_library(libnl_tiny NAMES nl-tiny)
@@ -59,6 +61,14 @@ if(libmd)
set(DEFAULT_DIGEST_SUPPORT ON)
endif()
+if(libmbedtls)
+ set(DEFAULT_CRYPTO_MBEDTLS_SUPPORT ON)
+endif()
+
+if(OpenSSL_FOUND)
+ set(DEFAULT_CRYPTO_OPENSSL_SUPPORT ON)
+endif()
+
option(DEBUG_SUPPORT "Debug plugin support" ON)
option(FS_SUPPORT "Filesystem plugin support" ON)
option(MATH_SUPPORT "Math plugin support" ON)
@@ -74,6 +84,8 @@ option(SOCKET_SUPPORT "Socket plugin support" ON)
option(ZLIB_SUPPORT "Zlib plugin support" ${DEFAULT_ZLIB_SUPPORT})
option(DIGEST_SUPPORT "Digest plugin support" ${DEFAULT_DIGEST_SUPPORT})
option(DIGEST_SUPPORT_EXTENDED "Enable additional hash algorithms" ${DEFAULT_DIGEST_SUPPORT})
+option(CRYPTO_MBEDTLS_SUPPORT "Crypto Mbed-TLS plugin support" ${DEFAULT_CRYPTO_MBEDTLS_SUPPORT})
+option(CRYPTO_OPENSSL_SUPPORT "Crypto OpenSSL plugin support" ${DEFAULT_CRYPTO_OPENSSL_SUPPORT})
set(LIB_SEARCH_PATH "${CMAKE_INSTALL_PREFIX}/lib/ucode/*.so:${CMAKE_INSTALL_PREFIX}/share/ucode/*.uc:./*.so:./*.uc" CACHE STRING "Default library search path")
string(REPLACE ":" "\", \"" LIB_SEARCH_DEFINE "${LIB_SEARCH_PATH}")
@@ -322,6 +334,27 @@ if(DIGEST_SUPPORT)
target_link_libraries(digest_lib ${libmd})
endif()
+if(CRYPTO_MBEDTLS_SUPPORT)
+ set(LIBRARIES ${LIBRARIES} crypto_mbedtls_lib)
+ add_library(crypto_mbedtls_lib MODULE lib/crypto-mbedtls.c)
+ set_target_properties(crypto_mbedtls_lib PROPERTIES OUTPUT_NAME crypto_mbedtls PREFIX "")
+ target_link_options(crypto_mbedtls_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS} -Wl,-no-as-needed)
+ target_link_libraries(crypto_mbedtls_lib ${libmbedtls})
+endif()
+
+if(CRYPTO_OPENSSL_SUPPORT)
+ set(LIBRARIES ${LIBRARIES} crypto_openssl_lib)
+ add_library(crypto_openssl_lib MODULE lib/crypto-openssl.c)
+ set_target_properties(crypto_openssl_lib PROPERTIES OUTPUT_NAME crypto_openssl PREFIX "")
+ target_include_directories(crypto_openssl_lib PRIVATE ${OPENSSL_INCLUDE_DIR})
+ target_link_options(crypto_openssl_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ target_link_libraries(crypto_openssl_lib ${OPENSSL_LIBRARIES})
+endif()
+
+if(CRYPTO_MBEDTLS_SUPPORT OR CRYPTO_OPENSSL_SUPPORT)
+ install(FILES lib/crypto.uc DESTINATION share/ucode)
+endif()
+
if(UNIT_TESTING)
enable_testing()
add_definitions(-DUNIT_TESTING)
diff --git a/docs/README.md b/docs/README.md
index 92ab103..077e303 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -69,11 +69,11 @@ command.
### MacOS
-To build on MacOS, first install *cmake*, *json-c* and *libmd* via
+To build on MacOS, first install *cmake*, *json-c*, *libmd* and *openssl*/*mbedtls* via
[Homebrew](https://brew.sh/), then clone the ucode repository and execute
*cmake* followed by *make*:
- $ brew install cmake json-c libmd
+ $ brew install cmake json-c libmd openssl
$ git clone https://github.com/jow-/ucode.git
$ cd ucode/
$ cmake -DUBUS_SUPPORT=OFF -DUCI_SUPPORT=OFF -DULOOP_SUPPORT=OFF -DCMAKE_BUILD_RPATH=/usr/local/lib -DCMAKE_INSTALL_RPATH=/usr/local/lib .
diff --git a/jsdoc/conf.json b/jsdoc/conf.json
index 9d3c995..523243b 100644
--- a/jsdoc/conf.json
+++ b/jsdoc/conf.json
@@ -5,8 +5,8 @@
},
"source": {
"include": ["."],
- "exclude": ["CMakeFiles"],
- "includePattern": ".+\\.c$"
+ "exclude": ["CMakeFiles", "tests"],
+ "includePattern": ".+\\.(c|uc)$"
},
"plugins": [
"plugins/markdown",
diff --git a/lib/crypto-mbedtls.c b/lib/crypto-mbedtls.c
new file mode 100644
index 0000000..3fa4cfd
--- /dev/null
+++ b/lib/crypto-mbedtls.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2024-2025 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 <mbedtls/ctr_drbg.h>
+#include <mbedtls/entropy.h>
+#include <mbedtls/error.h>
+#include <mbedtls/md.h>
+#include <mbedtls/pk.h>
+
+#define PK_TYPE "crypto_mbedtls.pk"
+
+#define TRUE ucv_boolean_new(true)
+#define FALSE ucv_boolean_new(false)
+
+const char *personalization = "ucode-crypto-mbedtls";
+
+struct context {
+ mbedtls_pk_context pk;
+ mbedtls_ctr_drbg_context ctr_drbg;
+};
+
+static uc_resource_type_t *pk_type;
+static mbedtls_entropy_context entropy;
+
+static void __attribute__((constructor)) load();
+static void __attribute__((destructor)) unload();
+
+static uc_value_t *
+md_digest(uc_vm_t *vm, size_t nargs)
+{
+ 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 is not a string");
+ return NULL;
+ }
+
+ if (ucv_type(input) != UC_STRING) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "input is not a string");
+ return NULL;
+ }
+
+ const mbedtls_md_info_t *info = mbedtls_md_info_from_string(ucv_string_get(alg));
+
+ if (!info) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "unknown MD algorithm");
+ return NULL;
+ }
+
+ unsigned char size = mbedtls_md_get_size(info);
+ char *output = alloca(size);
+
+ if (mbedtls_md(info, (const unsigned char*)ucv_string_get(input), ucv_string_length(input), (unsigned char*)output)) {
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "bad input data");
+ return NULL;
+ }
+
+ uc_value_t *rv = ucv_string_new_length(output, size);
+ return rv;
+}
+
+static uc_value_t *
+pk_init(uc_vm_t *vm, size_t nargs)
+{
+ struct context *ctx = calloc(1, sizeof(struct context));
+
+ mbedtls_pk_init(&ctx->pk);
+ mbedtls_ctr_drbg_init(&ctx->ctr_drbg);
+
+ int ret = mbedtls_ctr_drbg_seed(&ctx->ctr_drbg, mbedtls_entropy_func, &entropy,
+ (const unsigned char *) personalization,
+ strlen(personalization) );
+ if( ret != 0 ) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Problem with random generator");
+ return NULL;
+ }
+
+ return uc_resource_new(pk_type, ctx);
+}
+
+static void
+pk_free(void *ptr)
+{
+ struct context *ctx = ptr;
+
+ mbedtls_pk_free(&ctx->pk);
+ mbedtls_ctr_drbg_free(&ctx->ctr_drbg);
+ free(ctx);
+}
+
+struct alias {
+ const char *alias;
+ const char *name;
+};
+
+static struct alias curve_aliases[] = {
+ { alias: "P-192", name: "secp192r1"},
+ { alias: "P-224", name: "secp224r1"},
+ { alias: "P-256", name: "secp256r1"},
+ { alias: "P-384", name: "secp384r1"},
+ { alias: "P-521", name: "secp521r1"},
+ { alias: NULL, name: NULL},
+};
+
+static const char *translate_curve(const char *type)
+{
+ if (!type)
+ return NULL;
+
+ for (int i=0; curve_aliases[i].alias; i++) {
+ if (!strcmp(type, curve_aliases[i].alias))
+ return curve_aliases[i].name;
+ }
+
+ return type;
+};
+
+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;
+ }
+
+ 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 (!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 = translate_curve(ucv_string_get(curve_v));
+ const mbedtls_ecp_curve_info *curve_info = mbedtls_ecp_curve_info_from_name(curve);
+
+ if (!curve_info) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Unsupported curve: %s", curve);
+ return NULL;
+ }
+
+ const mbedtls_pk_info_t *info = mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY);
+ if (mbedtls_pk_setup(&ctx->pk, info)) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "PK setup failed");
+ return NULL;
+ }
+ int err = mbedtls_ecp_gen_key(curve_info->grp_id,
+ mbedtls_pk_ec(ctx->pk),
+ mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
+ if (err != 0) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "EC gen key failed");
+ return NULL;
+ }
+ } else if (!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 number");
+ return NULL;
+ }
+
+ size_t size = ucv_to_integer(size_v);
+ const mbedtls_pk_info_t *info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA);
+ if (mbedtls_pk_setup(&ctx->pk, info)) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "PK setup failed");
+ return NULL;
+ }
+
+ int err = mbedtls_rsa_gen_key(mbedtls_pk_rsa(ctx->pk),
+ mbedtls_ctr_drbg_random, &ctx->ctr_drbg,
+ size, 65537);
+ if (err != 0) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "RSA gen key failed");
+ return NULL;
+ }
+ } else {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Unsupported key type %s", type_str);
+ return NULL;
+ }
+
+ return TRUE;
+}
+
+static uc_value_t *
+pk_get_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;
+ }
+
+ if (!mbedtls_pk_get_len(&ctx->pk)) {
+ // No key
+ return NULL;
+ }
+
+ unsigned char buf[16000];
+ int res = mbedtls_pk_write_pubkey_der(&ctx->pk, buf, sizeof(buf));
+ if (res <= 0) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "failed to get public key");
+ return NULL;
+ }
+
+ uc_value_t *rv = ucv_string_new_length((const char*)(buf + sizeof(buf) - res), res);
+ return rv;
+}
+
+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);
+ if (ucv_type(key) == UC_NULL) {
+ mbedtls_pk_free(&ctx->pk);
+ return NULL;
+ }
+
+ if (ucv_type(key) != UC_STRING) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "key is not a string");
+ return NULL;
+ }
+
+ int err = mbedtls_pk_parse_public_key(&ctx->pk, (const unsigned char*)ucv_string_get(key), ucv_string_length(key));
+ if (err)
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "not a valid DER key %s: %s",
+ mbedtls_high_level_strerr(err),
+ mbedtls_low_level_strerr(err));
+ return NULL;
+}
+
+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 *input = uc_fn_arg(1);
+
+ if (ucv_type(md_alg) != UC_STRING) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "md_alg is not a string");
+ return NULL;
+ }
+
+ if (ucv_type(input) != UC_STRING) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "input is not a string");
+ return NULL;
+ }
+
+ const mbedtls_md_info_t *md_info = mbedtls_md_info_from_string(ucv_string_get(md_alg));
+ if (!md_info) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "unknown MD algorithm");
+ return NULL;
+ }
+
+ unsigned char hash_len = mbedtls_md_get_size(md_info);
+ unsigned char *hash = alloca(hash_len);
+
+ if (mbedtls_md(md_info, (const unsigned char*)ucv_string_get(input), ucv_string_length(input), (unsigned char*)hash)) {
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "bad input data");
+ return NULL;
+ }
+
+ const mbedtls_md_type_t md_type = mbedtls_md_get_type(md_info);
+ unsigned char sig[MBEDTLS_PK_SIGNATURE_MAX_SIZE];
+ size_t sig_len = sizeof(sig);
+
+ if (mbedtls_pk_sign(&ctx->pk, md_type, hash, hash_len,
+ sig, &sig_len,
+ mbedtls_ctr_drbg_random, &ctx->ctr_drbg)) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "pk sign failed");
+ return NULL;
+ }
+
+ uc_value_t *rv = ucv_string_new_length((const char*)sig, sig_len);
+ 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 *input = uc_fn_arg(1);
+ uc_value_t *sig = uc_fn_arg(2);
+
+ if (ucv_type(md_alg) != UC_STRING) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "md_alg is not a string");
+ return NULL;
+ }
+
+ if (ucv_type(input) != UC_STRING) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "input 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 mbedtls_md_info_t *md_info = mbedtls_md_info_from_string(ucv_string_get(md_alg));
+
+ if (!md_info) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "unknown MD algorithm");
+ return NULL;
+ }
+
+ unsigned char hash_size = mbedtls_md_get_size(md_info);
+ unsigned char *hash = alloca(hash_size);
+
+ if (mbedtls_md(md_info, (const unsigned char*)ucv_string_get(input), ucv_string_length(input), (unsigned char*)hash)) {
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "bad input data");
+ return NULL;
+ }
+
+ const mbedtls_md_type_t md_type = mbedtls_md_get_type(md_info);
+
+ int err = mbedtls_pk_verify(&ctx->pk, md_type,
+ hash, hash_size,
+ (const unsigned char*)ucv_string_get(sig), ucv_string_length(sig));
+ if (err) {
+ uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "validation failed: %s: %s",
+ mbedtls_high_level_strerr(err),
+ mbedtls_low_level_strerr(err));
+ return FALSE;
+ }
+
+ 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_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()
+{
+ mbedtls_entropy_init(&entropy);
+}
+
+static void unload()
+{
+ mbedtls_entropy_free(&entropy);
+}
diff --git a/lib/crypto-openssl.c b/lib/crypto-openssl.c
new file mode 100644
index 0000000..128518b
--- /dev/null
+++ b/lib/crypto-openssl.c
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2024-2025 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 is not a string");
+ goto fail;
+ }
+
+ if (ucv_type(input) != UC_STRING) {
+ uc_vm_raise_exception(vm, EXCEPTION_TYPE, "input is 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)) {
+ 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))) {
+ 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)) {
+ 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))) {
+ raise_openssl_exception(vm, "not a valid PEM/DER key: %s");
+ 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();
+}
diff --git a/lib/crypto.uc b/lib/crypto.uc
new file mode 100644
index 0000000..43581c5
--- /dev/null
+++ b/lib/crypto.uc
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+/**
+ * # Crypto
+ *
+ * The `crypto` module provides message digest and message signing and
+ * verification. There are two alternative implementations, `crypto_openssl`
+ * and `crypto_mbedtls`. The `crypto_openssl` is preferred if available since
+ * it supports EdDSA in addition to all algorithms supported
+ * by `crypto_mbedtls`.
+ *
+ * @module crypto
+ */
+
+/**
+ * Represents a public key context.
+ *
+ * @class module:crypto.pk
+ * @hideconstructor
+ */
+
+/**
+ * @function module:crypto#md_digest
+ *
+ * @param {string} alg
+ * Message digest algorithm.
+ *
+ * @param {string} input
+ * Input to the message digest algorithm.
+ *
+ * @returns {string}
+ */
+
+/**
+ * @function module:crypto#pk
+ *
+ * @returns {crypto.pk}
+ */
+
+/**
+ * @function module:crypto.pk#get_public_key
+ *
+ * @returns {?string} - Public key in DER format.
+ */
+
+/**
+ * @function module:crypto.pk#keygen
+ *
+ * @param {('EC'|'RSA'|'ED25519')} type
+ * Public key type.
+ *
+ * @param {('P-192'|'P-224'|'P-256'|'P-384'|'P-521'|'brainpoolP256r1'|'brainpoolP384r1'|'brainpoolP512r1'|number)} [param]
+ * EC curve name (`string`), or RSA key length (`number`).
+ *
+ * @returns {string}
+ */
+
+/**
+ * @function module:crypto.pk#set_public_key
+ *
+ * @param {?string} key
+ * A public key in DER format.
+ */
+
+/**
+ * Available only if the `crypto_openssl` module is installed.
+ *
+ * @function module:crypto.pk#set_raw_public_key
+ *
+ * @param {('ED25519'|'ED448')} type
+ * @param {string} key
+ * Public key in raw format.
+ */
+
+/**
+ * @function module:crypto.pk#sign
+ *
+ * @param {?('SHA1'|'SHA224'|'SHA256'|'SHA384'|'SHA512'|string)} alg
+ * The message digest algorithm.
+ *
+ * @param {string} input
+ * The message to be signed.
+ *
+ * @returns {string}
+ */
+
+/**
+ * @function module:crypto.pk#verify
+ *
+ * @param {?('SHA1'|'SHA224'|'SHA256'|'SHA384'|'SHA512'|string)} alg
+ * The message digest algorithm.
+ *
+ * @param {string} input
+ * The message to be verified.
+ *
+ * @param {string} sig
+ * The signature to be verified.
+ *
+ * @returns {boolean}
+ */
+
+let crypto;
+
+try {
+ crypto = require('crypto_openssl');
+} catch {
+ try {
+ crypto = require('crypto_mbedtls');
+ } catch {
+ die(`No module named 'crypto_openssl' or 'crypto_mbedtls' could be found`);
+ }
+}
+
+export
+function md_digest(...args) {
+ return crypto.md_digest(...args);
+};
+
+export
+function md_list(...args) {
+ return crypto.md_list(...args);
+};
+
+export
+function pk_list(...args) {
+ return crypto.pk_list(...args);
+};
+
+export
+function pk(...args) {
+ return crypto.pk(...args);
+};
diff --git a/tests/custom/05_crypto/01_rsa b/tests/custom/05_crypto/01_rsa
new file mode 100644
index 0000000..c07c690
--- /dev/null
+++ b/tests/custom/05_crypto/01_rsa
@@ -0,0 +1,85 @@
+A few test vectors from SigGen15_186-2.txt in 186-2rsatestvectors.zip
+
+-- Testcase --
+{%
+ import { pk as pk_mbedtls } from 'crypto_mbedtls';
+ import { pk as pk_openssl } from 'crypto_openssl';
+ import { test_rsa } from './files/rsa.uc';
+
+ const pubkeyPem = `-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4LFLmc1hzT25wgdmaIQT
+JPoxdPM85m/9UUOU00F40ppJSTJ2tndyM+fUaj5ovHyn6JnpAdVPbe4HScPkjd9o
+aFhn7irmbfiOtWP22xN6n2sXWhEuDtqDaOiORe/hzhS8YBbVJjlicGavGHLHL2C5
+FhwdI37rNLD4QbPwiW+f4OFrD3Q1LRASksxGSn54YbvrhvbfYVHLJlQXxmxWXtiX
+S9j8mE1d39TrkaPVI0zhtUZ/Ot43X4AuwHKT8SNu+jBovJGxWFUch1xdwKnW+jIb
++UIfCN6skQ41wcKFSe6O7YMwz3BZX/cLlLSZB+J2mKnZEfesBwavyxpKOf6ziwqA
+SQIDAQAB
+-----END PUBLIC KEY-----`;
+
+ const pubkeyLines = split(pubkeyPem, '\n');
+ const pubkeyB64 = join('', splice(splice(pubkeyLines, length(pubkeyLines)-1), 0, 1));
+ const pubkey = b64dec(pubkeyB64);
+
+ const msg1 = '544be0a4044edcdc1240f2182109f826d784ea613486dfd5221d3ba44d1525a934c13c5a81f885f3955da8d168e35d1b909121a89f832d1db232a85647f51c084fc727a4854a737efd0ce72e00091f3617721ae666ad337d3b9d5391e72364b1cde50948b84e8cc472d618f8928328bf95af3fe3300dda3de5e7a21dd5a9e7ec';
+ const s1 = 'd0d03ccb3b30b7c9c4d6eeee2ec26d069246e019fb8fa2f3a9b72c9bbe231d93ce053df805a045e2ef6bd8d08bfb0c36922e5a6f10b947b2607f596b6cbd3c9eefef56f5396805e8b28b1ca182c78c0b12b9796aa856af69c35504f8acc7afa74bc0f77a1d61da94944057a9ee72d2f0a96cbaa2f64676f5318b71e56f519d0da1ce8f42db0ebe5045fcc726e39fb0032f2287918f9190f3fb3d4de542030441f6736c6205a2bcd2450eb411085311c7320baa4268fd2fd8bcc8ebfddbb60740cff0b3b00f618777ebcfb3468f309d923c957c8170727a5458ac2c9070f93cfc37d31cf9f1a35d0cc3abf25af8dc9e1590ce59ab39d01cf0c154ab8d0635c5e9';
+
+ const msg256 = '6504921a97cd57aa8f3863dc32e1f2d0b57aff63106e59f6afc3f9726b459388bae16b3e224f6aa7f4f471f13606eda6e1f1ac2b4df9ef8de921c07c2f4c8598d7a3d6ec4b368cb85ce61a74338221118a303e821c0f277b591af6795f50c40226127a2efacce4662fd7076c109eb59b18005e7165f6294a6976436ee397774e';
+ const s256 = '335ffadc0b1b8bd2b1eb670dd246e76dcccdc955a1687a15f74aa3e1596ebd43e607c640525f89dda95809cfd065f1be4e4a249477d24f400d4d4c9438a0af95b26b28b416e42aa950e2a52851b52132048f1b1ce944322fc99c1aabb49b7fae4c2f0fef674b50adee3bbb5c6c33822b608e4b9577275ca20c710af9fc41b1c01d9c0ff6f0d8324dc08e1a76e232d8feaa06c73bbf64053bea35f1c528b2722764822ef1ff06246e75a9a22a10da4ea84fc2441bea24b35506f8447fcf69093c5d21ab0305cce2c7ea9ffac357c664b491fc55f2919ec490c38accbab378c252ac2df3845acff575ec7524cd2f586cca1497c74f24b299d6d6254c8cdb1d227d';
+
+ const msg384 = 'db2e9fbefd9832d29e6015b976734ff15a5c1177a9153b38e61d22c8a6ca9056ade10de054645da32eab4ad6eab6a4977bc28526771ad951bd301e2f5bec6911af44aab6cc0d30be1e1170615261026170edec3d4a5123a81af24f39674acf642880fda92c3cb30bb9b90ec4741c71378004cd26ea622025458b3c4f918bca37';
+ const s384 = '211398c068e60ae49ee2b8b7cd96171373f4664cf45b62415d9bcf119184c03274aec2b3f04c598043d1d8642d53edad68d0e0bc33fdc65ed69334ab52303c839cbbaf0586508007dcf4ad5d297f568039dc16a1e6c08108a0050d5dcf47523e4868e4be72f20d789b06bcb92484efa2fcd903ae280f42a509dadbe62dce1c6d3aef6e4ff78a745c8500ff0572748abb1a220495c1c103a72c940206347c16ae6082dd1e4624525aa8ed8f118d09be2b8535d042b29ea3e2c6ab3c990b4b59501c3bb9d602c7813a55f4efc129f4dfd0fabe0cf6b8c8c1d9126c5440cbec5405722927389b3ca80a0872711388b5b099cce6effedfbd9befc9646ea8a258fdd0';
+
+ const msg512 = 'e4dba4692a6628b501f776fec7fe973d655154268f669bfd47c624aba3be5311d158619c588ab71aa0ac9accb52f0dbc488df350f77c7520ce67a3050d1e5e722bcd75081c2b0e64d0f3483cfc981eaa1c358fc7b9c2fb7ce78ed19513e96717fb2129d4feb1f63c96b4c77623a092b0ea306eb35da2f7ba9d23f4843d8837a8';
+ const s512 = 'dfe58c84ec3ee5f11b265e8e7d99b416d8e7b166a38d2b0f9027be73887fad28947994a2bc227dafcb272d2d410af31afe16b96f51c9ddf62b417d03e2af63ea4a58d41e4649712177c85788d837fadd223c4ea7f635237a93b181fe1c0fd3bf2d8a7997cc9bb6ca099a0a36c4b9e91aa780a3d5edbc283cc316a153a101fc8c33d0035e6c1e1aca731ea765a1e5fe1406ef7ba8bb8f335b8d2e6ce0b9fee2f5416e4536b280fdc40860c17e9da6bb361128ee53754bf68f54c2878a4ac4d349703066a6fb96220e15285fcb41b398b4567c0c167762eb6ceb4f0537c7fb7103487c78a98093209771fc4f4a9821cc8eb11f15a0e35c54f6d5d31d8e8646c229';
+
+ // Test signature (from 04_keygen) against wrong key.
+ const s256_fail = '3f602377b1702ba2f8e67f39c5856341512ef0ad4fb9091017c5561b1041d9b6b351218ec457700a0a51b0829a34bb027126d95a2af373313d6026d840d98300f1a1d08c9343403a0e8d765fce97743dfbbaa8c57285e3d1d1d06eb9e7da16a9196908dc87cf1c615535cf7ae7c9c98fced8f1a4a188f1ec1490785076f923ad2f58f655c08de1083cfb6dea21f01cb822b7fd42ba57405a771b487b8aa50db6cd08fc00830c2e5833c0dd471f8280f9a0582870af365848e3d8f42145883c28cea10f401a7e008c497f73595ba8d6787bd21f89b7995a290b31014df281928fbbea4a73bb2989234167d3487ccb919f21993577006519a88a6208b4f70d91b2';
+
+ test_rsa(pk_mbedtls(), pubkey, 'SHA1', msg1, s1);
+ test_rsa(pk_mbedtls(), pubkey, 'SHA256', msg256, s256);
+ test_rsa(pk_mbedtls(), pubkey, 'SHA384', msg384, s384);
+ test_rsa(pk_mbedtls(), pubkey, 'SHA512', msg512, s512);
+ test_rsa(pk_mbedtls(), pubkey, 'SHA256', hexenc('Message'), s256_fail);
+
+ test_rsa(pk_openssl(), pubkey, 'SHA1', msg1, s1);
+ test_rsa(pk_openssl(), pubkey, 'SHA256', msg256, s256);
+ test_rsa(pk_openssl(), pubkey, 'SHA384', msg384, s384);
+ test_rsa(pk_openssl(), pubkey, 'SHA512', msg512, s512);
+ test_rsa(pk_openssl(), pubkey, 'SHA256', hexenc('Message'), s256_fail);
+%}
+-- End --
+
+-- Expect stdout --
+true
+true
+true
+true
+exception
+true
+true
+true
+true
+exception
+-- End --
+
+-- Expect stderr --
+-- End --
+
+-- File rsa.uc --
+export
+function test_rsa(pk, pubkey, mdAlg, msg, s)
+{
+ const data = hexdec(msg);
+ const signature = hexdec(s);
+ pk.set_public_key(pubkey);
+ const pkey = pk.get_public_key();
+ assert(pkey == pubkey);
+ let verify = 'undefined';
+ try {
+ verify = pk.verify(mdAlg, data, signature);
+ } catch {
+ verify = 'exception'
+ }
+ print(verify, '\n');
+};
+-- End --
diff --git a/tests/custom/05_crypto/02_ecdsa b/tests/custom/05_crypto/02_ecdsa
new file mode 100644
index 0000000..194a5ce
--- /dev/null
+++ b/tests/custom/05_crypto/02_ecdsa
@@ -0,0 +1,125 @@
+Some test vectors from RFC 6979
+
+-- Testcase --
+{%
+ import { pk as pk_mbedtls } from 'crypto_mbedtls';
+ import { pk as pk_openssl } from 'crypto_openssl';
+ import { test_ecdsa } from './files/ecdsa.uc';
+
+ const tests = {
+ p192: {
+ alg: 'SHA1',
+ data: 'sample',
+ // Ux = 'AC2C77F529F91689FEA0EA5EFEC7F210D8EEA0B9E047ED56'
+ // Uy = '3BC723E57670BD4887EBC732C523063D0A7C957BC97C1C43'
+ // DER encoded public key
+ key: '3049301306072a8648ce3d020106082a8648ce3d03010103320004ac2c77f529f91689fea0ea5efec7f210d8eea0b9e047ed563bc723e57670bd4887ebc732c523063d0a7c957bc97c1c43',
+ // r = '98C6BD12B23EAF5E2A2045132086BE3EB8EBD62ABF6698FF'
+ // s = '57A22B07DEA9530F8DE9471B1DC6624472E8E2844BC25B64'
+ // DER encoded signature
+ sig: '303502190098c6bd12b23eaf5e2a2045132086be3eb8ebd62abf6698ff021857a22b07dea9530f8de9471b1dc6624472e8e2844bc25b64',
+ },
+ p224: {
+ alg: 'SHA224',
+ data: 'sample',
+ // Ux = '00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C'
+ // Uy = 'EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A'
+ // DER encoded public key
+ key: '304e301006072a8648ce3d020106052b81040021033a000400cf08da5ad719e42707fa431292dea11244d64fc51610d94b130d6ceeab6f3debe455e3dbf85416f7030cbd94f34f2d6f232c69f3c1385a',
+ // r = '1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E'
+ // s = 'A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBC'
+ // DER encoded signature
+ sig: '303d021c1cdfe6662dde1e4a1ec4cdedf6a1f5a2fb7fbd9145c12113e6abfd3e021d00a6694fd7718a21053f225d3f46197ca699d45006c06f871808f43ebc',
+ },
+ p256: {
+ alg: 'SHA256',
+ data: 'sample',
+ // Ux = '60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6'
+ // Uy = '7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299'
+ // DER encoded public key
+ key: '3059301306072a8648ce3d020106082a8648ce3d0301070342000460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb67903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299',
+ // r = 'EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716'
+ // s = 'F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8'
+ // DER encoded signature
+ sig: '3046022100efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716022100f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8',
+ },
+ p384: {
+ alg: 'SHA384',
+ data: 'sample',
+ // Ux = 'EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13'
+ // Uy = '8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720'
+ // DER encoded public key
+ key: '3076301006072a8648ce3d020106052b8104002203620004ec3a4e415b4e19a4568618029f427fa5da9a8bc4ae92e02e06aae5286b300c64def8f0ea9055866064a254515480bc138015d9b72d7d57244ea8ef9ac0c621896708a59367f9dfb9f54ca84b3f1c9db1288b231c3ae0d4fe7344fd2533264720',
+ // r = '94EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE46'
+ // s = '99EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8'
+ // DER encoded signature
+ sig: '306602310094edbb92a5ecb8aad4736e56c691916b3f88140666ce9fa73d64c4ea95ad133c81a648152e44acf96e36dd1e80fabe4602310099ef4aeb15f178cea1fe40db2603138f130e740a19624526203b6351d0a3a94fa329c145786e679e7b82c71a38628ac8',
+ },
+ p521: {
+ alg: 'SHA512',
+ data: 'sample',
+ // Ux = '01894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4'
+ // Uy = '00493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5'
+ // DER encoded public key
+ key: '30819b301006072a8648ce3d020106052b81040023038186000401894550d0785932e00eaa23b694f213f8c3121f86dc97a04e5a7167db4e5bcd371123d46e45db6b5d5370a7f20fb633155d38ffa16d2bd761dcac474b9a2f5023a400493101c962cd4d2fddf782285e64584139c2f91b47f87ff82354d6630f746a28a0db25741b5b34a828008b22acc23f924faafbd4d33f81ea66956dfeaa2bfdfcf5',
+ // r = 'C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F174E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA'
+ // s = '617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF282623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67A'
+ // DER encoded signature
+ sig: '308187024200c328fafcbd79dd77850370c46325d987cb525569fb63c5d3bc53950e6d4c5f174e25a1ee9017b5d450606add152b534931d7d4e8455cc91f9b15bf05ec36e377fa0241617cce7cf5064806c467f678d3b4080d6f1cc50af26ca209417308281b68af282623eaa63e5b5c0723d8b8c37ff0777b1a20f8ccb1dccc43997f1ee0e44da4a67a',
+ },
+ p192_fail: {
+ // Test signature (from 04_keygen) against wrong key.
+ alg: 'SHA1',
+ data: 'Message',
+ key: '3049301306072a8648ce3d020106082a8648ce3d03010103320004ac2c77f529f91689fea0ea5efec7f210d8eea0b9e047ed563bc723e57670bd4887ebc732c523063d0a7c957bc97c1c43',
+ sig: '303402181e2dbd03ca5a4dbbdda18a967969c0ac246ef6b0562f572e021838e05d8ba04345299188147b2636015b777223016f32cde2',
+ },
+ };
+
+ for (test in tests) {
+ test_ecdsa(pk_mbedtls(), tests[test]);
+ test_ecdsa(pk_openssl(), tests[test]);
+ }
+%}
+-- End --
+
+-- Expect stdout --
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+exception
+exception
+-- End --
+
+-- Expect stderr --
+-- End --
+
+-- File ecdsa.uc --
+export
+function test_ecdsa(pk, test)
+{
+ const mdAlg = test.alg;
+ const data = test.data;
+ const key = test.key;
+ const sig = test.sig;
+
+ const pubkeyDer = hexdec(key);
+ pk.set_public_key(pubkeyDer);
+ const pkey = pk.get_public_key();
+ assert(pkey == pubkeyDer);
+ let verify = 'undefined';
+ try {
+ verify = pk.verify(mdAlg, data, hexdec(sig));
+ } catch {
+ verify = 'exception';
+ }
+ print(verify, '\n');
+};
+-- End --
diff --git a/tests/custom/05_crypto/03_eddsa b/tests/custom/05_crypto/03_eddsa
new file mode 100644
index 0000000..468e3b7
--- /dev/null
+++ b/tests/custom/05_crypto/03_eddsa
@@ -0,0 +1,70 @@
+-- Testcase --
+{%
+ import { pk as pk_openssl } from 'crypto_openssl';
+ import { test_eddsa } from './files/eddsa.uc';
+
+ const tests = {
+ test_1: {
+ // raw key = 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a',
+ key: '302a300506032b6570032100d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a',
+ msg: '',
+ sig: 'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b',
+ },
+ test_2: {
+ // raw key = '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c'
+ key: '302a300506032b65700321003d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c',
+ msg: '72',
+ sig: '92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00',
+ },
+ test_3: {
+ // raw key = 'fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025'
+ key: '302a300506032b6570032100fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025',
+ msg: 'af82',
+ sig: '6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a',
+ },
+ test_4_fail: {
+ key: '302a300506032b6570032100d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a',
+ msg: hexenc('Message'),
+ sig: '8e06163289f37c4e8a15cb3ddcf7379247f7820cea8488531579b32bf8aed7dc5dd4839e800ecc1911a8984344ef281db684b6f538d1c38a0a4570849e32fd02',
+ },
+ };
+
+ for (test in tests) {
+ test_eddsa(pk_openssl(), tests[test]);
+ }
+%}
+-- End --
+
+-- Expect stdout --
+true
+true
+true
+exception
+-- End --
+
+-- Expect stderr --
+-- End --
+
+-- File eddsa.uc --
+export
+function test_eddsa(pk, test)
+{
+ const pkey = test.key;
+ const msg = test.msg;
+ const s = test.sig;
+
+ const data = hexdec(msg);
+ const signature = hexdec(s);
+
+ //pk.set_raw_public_key('ED25519', hexdec(pkey));
+ pk.set_public_key(hexdec(pkey));
+
+ let verify = 'undefined';
+ try {
+ verify = pk.verify(null, data, signature);
+ } catch {
+ verify = 'exception';
+ }
+ print(verify, '\n');
+};
+-- End --
diff --git a/tests/custom/05_crypto/04_keygen b/tests/custom/05_crypto/04_keygen
new file mode 100644
index 0000000..97a94b8
--- /dev/null
+++ b/tests/custom/05_crypto/04_keygen
@@ -0,0 +1,80 @@
+-- Testcase --
+{%
+ import { pk as pk_mbedtls } from 'crypto_mbedtls';
+ import { pk as pk_openssl } from 'crypto_openssl';
+ import { test_keygen } from './files/keygen.uc';
+
+ curves = [
+ 'P-192',
+ 'P-224',
+ 'P-256',
+ 'P-384',
+ 'P-521',
+ 'brainpoolP256r1',
+ 'brainpoolP384r1',
+ 'brainpoolP512r1',
+ 'secp192k1',
+ 'secp224k1',
+ 'secp256k1',
+ ];
+
+
+ for (c in curves) {
+ test_keygen(pk_openssl, pk_mbedtls, "EC", "SHA256", c);
+ test_keygen(pk_mbedtls, pk_openssl, "EC", "SHA256", c);
+ }
+ test_keygen(pk_openssl, pk_mbedtls, "RSA", "SHA256", 2048);
+ test_keygen(pk_mbedtls, pk_openssl, "RSA", "SHA256", 2048);
+
+ test_keygen(pk_openssl, pk_openssl, "ED25519", null);
+%}
+-- End --
+
+-- Expect stdout --
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+-- End --
+
+-- Expect stderr --
+-- End --
+
+-- File keygen.uc --
+export
+function test_keygen(pk_init, other_pk_init, type, md_alg, ...args)
+{
+ let pk = pk_init();
+ let res = pk.keygen(type, ...args);
+ let pkey = pk.get_public_key();
+ const data = "Message";
+ const signature = pk.sign(md_alg, data);
+
+ let other_pk = other_pk_init();
+ other_pk.set_public_key(pkey);
+
+ const verify = other_pk.verify(md_alg, data, signature);
+ print(verify, '\n');
+};
+-- End --