From 8c40fdca77afc41e15d636290a9c826b4ca1917a Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Fri, 22 Mar 2024 16:21:18 +0100 Subject: [PATCH] ucode-mod-pkgen: add ucode module for generating crypto keys/certificates This also includes a script to use the functionality from the command line Signed-off-by: Felix Fietkau --- package/utils/ucode-mod-pkgen/Makefile | 44 ++ package/utils/ucode-mod-pkgen/files/pkgen | 252 ++++++++ .../utils/ucode-mod-pkgen/src/CMakeLists.txt | 35 + package/utils/ucode-mod-pkgen/src/pk.h | 45 ++ package/utils/ucode-mod-pkgen/src/pkcs12.c | 612 ++++++++++++++++++ package/utils/ucode-mod-pkgen/src/ucode.c | 598 +++++++++++++++++ 6 files changed, 1586 insertions(+) create mode 100644 package/utils/ucode-mod-pkgen/Makefile create mode 100755 package/utils/ucode-mod-pkgen/files/pkgen create mode 100644 package/utils/ucode-mod-pkgen/src/CMakeLists.txt create mode 100644 package/utils/ucode-mod-pkgen/src/pk.h create mode 100644 package/utils/ucode-mod-pkgen/src/pkcs12.c create mode 100644 package/utils/ucode-mod-pkgen/src/ucode.c diff --git a/package/utils/ucode-mod-pkgen/Makefile b/package/utils/ucode-mod-pkgen/Makefile new file mode 100644 index 0000000000..918aa22a90 --- /dev/null +++ b/package/utils/ucode-mod-pkgen/Makefile @@ -0,0 +1,44 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=ucode-mod-pkgen +PKG_RELEASE:=1 +PKG_LICENSE:=GPL-2.0-or-later +PKG_MAINTAINER:=Felix Fietkau + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +CMAKE_INSTALL := 1 + +define Package/ucode-mod-pkgen + SECTION:=utils + CATEGORY:=Utilities + TITLE:=ucode module for generating public keys/certificates + DEPENDS:=+libucode +libmbedtls +endef + +define Package/ucode-mod-pkgen/description +The pkgen module provides functionality for generating cryptographic keys and +(self-)signed certificates. It supports exporting PEM/DER format files, as +well as PKCS#12 bundle for client cert/key pairs with CA. +endef + +define Package/pkgen + SECTION:=utils + CATEGORY:=Utilities + TITLE:=ucode script for generating public keys/certificates + DEPENDS:=+ucode +ucode-mod-pkgen +ucode-mod-fs +endef + +define Package/ucode-mod-pkgen/install + $(INSTALL_DIR) $(1)/usr/lib/ucode + $(CP) $(PKG_INSTALL_DIR)/usr/lib/ucode/pkgen.so $(1)/usr/lib/ucode/ +endef + +define Package/pkgen/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) ./files/pkgen $(1)/usr/bin +endef + +$(eval $(call BuildPackage,ucode-mod-pkgen)) +$(eval $(call BuildPackage,pkgen)) diff --git a/package/utils/ucode-mod-pkgen/files/pkgen b/package/utils/ucode-mod-pkgen/files/pkgen new file mode 100755 index 0000000000..e37e5f5329 --- /dev/null +++ b/package/utils/ucode-mod-pkgen/files/pkgen @@ -0,0 +1,252 @@ +#!/usr/bin/env ucode +'use strict'; + +import { basename, readfile, writefile, stdin } from "fs"; +let pk = require("pkgen"); +let valid_from = "20240101000000"; +let valid_to = "21001231235959"; +let subject, password, password_stdin; +let keytype = "ec"; +let keylen = 2048; +let keyexp = 65537; +let keycurve = "secp256r1"; +let no_ca; +let legacy; + +const usage_message = `Usage: ${basename(sourcepath())} [] [] + +Commands: + ca : Create a new CA. + (creates ca.pem, ca.key, ca.serial) + + cert : Create a new certificate/key using the CA + from ca.pem. (creates cert.pem and ca.key) + + cert_p12 : Create a new PKCS#12 certificate/key + using the CA from ca.pem. (creates ca.p12) + + selfsigned : Create a self-signed certificate + (creates cert.pem) + +Options: + -C Set EC curve type (default: ${keycurve}) + Possible values: secp521r1, secp384r1, secp256r1, + secp256k1, secp224r1, secp224k1, secp192r1, + secp192k1 + -E Set RSA key exponent (default: ${keyexp}) + -L Set RSA key length (default: ${keylen}) + -N Omit CA certificate for PKCS#12 files + -p Set PKCS#12 password to + -P Read PKCS#12 password from stdin + (default: random password, printed to stdout) + -s Set subject for generated certificate to . + -t rsa|ec Set key type to rsa or ec (default: ec) + -V Set validity for generated certificates. + (default: ${valid_from} ${valid_to}) + -W Use weaker PKCS#12 encryption for + compatibility with Windows and Apple systems + +`; + +function perror(msg) { + let err = pk.errno() == -1 ? "Invalid arguments" : pk.error(); + warn(`${msg}: ${err}\n`); + exit(1); +} + +function usage() { + warn(usage_message); + exit(1); +} + +function check_pem_path(pem_file) { + if (substr(pem_file, -4) != ".pem") { + warn(`Path with .pem extension expected\n`); + exit(1); + } + + return pem_file; +} + + +function gen_key() { + let key = pk.generate_key({ + type: keytype, + curve: keycurve, + size: keylen, + exponent: keyexp, + }); + + if (!key) + perror("Failed to generate CA key"); + + return key; +} + +function gen_cert(key, args) { + let cert = pk.generate_cert({ + subject_name: subject, + subject_key: key, + validity: [ valid_from, valid_to ], + ...args + }); + + if (!cert) + perror("Failed to generate certificate"); + + cert = cert.pem(); + if (!cert) + perror("Failed to complete certificate"); + + return cert; +} + +function gen_client_cert(ca_file, ca_data, key) { + let ca_base = substr(ca_file, 0, -4); + let ca_info = pk.cert_info(ca_data); + if (!length(ca_info)) + perror("Failed to load CA certificate"); + + let ca_key = pk.load_key(readfile(ca_base + ".key")); + if (!ca_key) + perror("Failed to load CA key"); + let ca_serial = +readfile(ca_base + ".serial"); + if (!ca_serial) + perror("Failed to load CA serial"); + + let cert = gen_cert(key, { + serial: ++ca_serial, + issuer_name: ca_info[0].subject, + issuer_key: ca_key, + }); + writefile(ca_base + ".serial", "" + ca_serial); + + return cert; +} + +let cmds = { + ca: function(args) { + let ca_file = check_pem_path(shift(args)); + let ca_base = substr(ca_file, 0, -4); + + let key = gen_key(); + let ca_cert = gen_cert(key, { + ca: true, + serial: 1, + issuer_name: subject, + issuer_key: key, + key_usage: [ "key_cert_sign" ], + }); + + writefile(ca_file, ca_cert); + writefile(ca_base + ".key", key.pem()); + writefile(ca_base + ".serial", "1"); + }, + + cert: function (args) { + let ca_file = check_pem_path(shift(args)); + let crt_file = check_pem_path(shift(args)); + let crt_base = substr(crt_file, 0, -4); + + let key = gen_key(); + let ca_data = readfile(ca_file); + let cert = gen_client_cert(ca_file, ca_data, key); + + writefile(crt_base + ".key", key.pem()); + writefile(crt_file, cert); + }, + + cert_p12: function (args) { + let ca_file = check_pem_path(shift(args)); + let p12_file = shift(args); + if (!p12_file) + usage(); + + let key = gen_key(); + let ca_data = readfile(ca_file); + let cert = gen_client_cert(ca_file, ca_data, key); + + if (password_stdin) + password = rtrim(stdin.read("line")); + else if (!password) + print((password = hexenc(readfile("/dev/urandom", 8))) + "\n"); + + let p12 = pk.generate_pkcs12({ + password, cert, key, legacy, + extra: no_ca ? null : [ ca_data ], + }); + + writefile(p12_file, p12); + }, + + selfsigned: function(args) { + let crt_file = check_pem_path(shift(args)); + let crt_base = substr(crt_file, 0, -4); + + let key = gen_key(); + let cert = gen_cert(key, { + serial: 1, + issuer_name: subject, + issuer_key: key, + }); + + writefile(crt_base + ".key", key.pem()); + writefile(crt_file, cert); + }, +}; + +while (substr(ARGV[0], 0, 1) == "-") { + let opt = substr(shift(ARGV), 1); + switch (opt) { + case 'C': + keycurve = shift(ARGV); + break; + case 'L': + keylen = +shift(ARGV); + break; + case 'N': + no_ca = true; + break; + case 'p': + password = shift(ARGV); + if (password_stdin) + usage(); + break; + case 'P': + password_stdin = true; + if (password) + usage(); + break; + case 's': + subject = shift(ARGV); + break; + case 't': + keytype = shift(ARGV); + if (keytype != "rsa" && keytype != "ec") { + warn(`Unsupported key type ${keytype}\n`); + exit(1); + } + break; + case 'V': + valid_from = shift(ARGV); + valid_to = shift(ARGV); + break; + case 'W': + legacy = true; + break; + default: + usage(); + break; + } +} + +let cmd = shift(ARGV); +if (!cmd || !cmds[cmd]) + usage(); + +if (subject == null) { + warn(`Missing -s option\n`); + exit(1); +} + +cmds[cmd](ARGV); diff --git a/package/utils/ucode-mod-pkgen/src/CMakeLists.txt b/package/utils/ucode-mod-pkgen/src/CMakeLists.txt new file mode 100644 index 0000000000..8794755160 --- /dev/null +++ b/package/utils/ucode-mod-pkgen/src/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.13) + +PROJECT(ucode-pkgen C) +ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -ffunction-sections -fwrapv -D_GNU_SOURCE) + +IF(CMAKE_C_COMPILER_VERSION VERSION_GREATER 6) + ADD_DEFINITIONS(-Wextra -Werror=implicit-function-declaration) + ADD_DEFINITIONS(-Wformat -Werror=format-security -Werror=format-nonliteral) +ENDIF() +ADD_DEFINITIONS(-Wmissing-declarations -Wno-error=unused-variable -Wno-unused-parameter) + +IF(APPLE) + SET(UCODE_MODULE_LINK_OPTIONS "LINKER:-undefined,dynamic_lookup") +ELSE() + SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "-Wl,--gc-sections") +ENDIF() + +IF(DEBUG) + ADD_DEFINITIONS(-DDEBUG -g3 -O0) +ELSE() + ADD_DEFINITIONS(-DNDEBUG) +ENDIF() + +FIND_LIBRARY(mbedtls NAMES mbedtls) +FIND_LIBRARY(ucode NAMES ucode) +FIND_PATH(mbedtls_include_dir NAMES mbedtls/pk.h) +FIND_PATH(ucode_include_dir NAMES ucode/module.h) +INCLUDE_DIRECTORIES(${mbedtls_include_dir} ${ucode_include_dir}) + +ADD_LIBRARY(pkgen_lib MODULE ucode.c pkcs12.c) +SET_TARGET_PROPERTIES(pkgen_lib PROPERTIES OUTPUT_NAME pkgen PREFIX "") +TARGET_LINK_OPTIONS(pkgen_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS}) +TARGET_LINK_LIBRARIES(pkgen_lib ${mbedtls}) + +INSTALL(TARGETS pkgen_lib LIBRARY DESTINATION lib/ucode) diff --git a/package/utils/ucode-mod-pkgen/src/pk.h b/package/utils/ucode-mod-pkgen/src/pk.h new file mode 100644 index 0000000000..9e2b316b6d --- /dev/null +++ b/package/utils/ucode-mod-pkgen/src/pk.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Felix Fietkau + */ +#ifndef __UCODE_PK_H +#define __UCODE_PK_H + +#include +#include + +#include +#include +#include +#include +#include + +#if MBEDTLS_VERSION_MAJOR < 3 +#define MBEDTLS_LEGACY +#endif + +int random_cb(void *ctx, unsigned char *out, size_t len); +uc_value_t *uc_generate_pkcs12(uc_vm_t *vm, size_t nargs); +int64_t get_int_arg(uc_value_t *obj, const char *key, int64_t defval); +extern int mbedtls_errno; +extern char buf[32 * 1024]; + +#define C(ret) \ + ({ \ + int __ret = (ret); \ + mbedtls_errno = __ret < 0 ? __ret : 0; \ + __ret; \ + }) + +#define CHECK(ret) do { \ + if (C(ret) < 0) \ + return NULL; \ +} while (0) +#define CHECK_INT(ret) do { \ + if (C(ret) < 0) \ + return -1; \ +} while (0) + +#define INVALID_ARG() do { C(-1); return NULL; } while (0) + +#endif diff --git a/package/utils/ucode-mod-pkgen/src/pkcs12.c b/package/utils/ucode-mod-pkgen/src/pkcs12.c new file mode 100644 index 0000000000..7d23b9d35b --- /dev/null +++ b/package/utils/ucode-mod-pkgen/src/pkcs12.c @@ -0,0 +1,612 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Felix Fietkau + */ +#include "pk.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define OID_TAG(n) MBEDTLS_OID_##n, MBEDTLS_OID_SIZE(MBEDTLS_OID_##n) + +#ifndef MBEDTLS_OID_AES_256_CBC +#define MBEDTLS_OID_AES_256_CBC MBEDTLS_OID_AES "\x2a" +#endif + +#define MBEDTLS_OID_PKCS9_LOCAL_KEY_ID MBEDTLS_OID_PKCS9 "\x15" +#define MBEDTLS_OID_PKCS9_CERT_TYPE MBEDTLS_OID_PKCS9 "\x16" +#define MBEDTLS_OID_PKCS9_CERT_TYPE_X509 MBEDTLS_OID_PKCS9_CERT_TYPE "\x01" + +#define MBEDTLS_OID_PKCS12_KEY_BAG MBEDTLS_OID_PKCS12 "\x0a\x01\x01" +#define MBEDTLS_OID_PKCS12_SHROUDED_KEY_BAG MBEDTLS_OID_PKCS12 "\x0a\x01\x02" +#define MBEDTLS_OID_PKCS12_CERT_BAG MBEDTLS_OID_PKCS12 "\x0a\x01\x03" + +#ifndef MBEDTLS_OID_PKCS7 +#define MBEDTLS_OID_PKCS7 MBEDTLS_OID_PKCS "\x07" +#define MBEDTLS_OID_PKCS7_DATA MBEDTLS_OID_PKCS7 "\x01" +#define MBEDTLS_OID_PKCS7_ENCRYPTED_DATA MBEDTLS_OID_PKCS7 "\x06" +#endif + +#define NUM_ITER 2048 +#define SALT_LEN 16 +#define IV_LEN 16 +#define IV_LEN_LEGACY 8 +#define KEY_LEN 32 +#define CERT_HASH_LEN 20 + +#define CONTEXT_TAG(n) (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | (n)) +#define SEQUENCE_TAG (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) +#define SET_TAG (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SET) + +#define CTX_ADD(ctx, n) \ + do { \ + int __n = (n); \ + if ((ctx)->p - (ctx)->start <= __n) \ + return -1; \ + (ctx)->p -= __n; \ + } while (0) + +struct pkcs12_ctx { + uint8_t *p, *start; + + uint8_t cert_hash[20]; + uint8_t *key_cert_hash; + + uint8_t salt[SALT_LEN]; + uint8_t iv[IV_LEN]; + + const char *password; + uint8_t *pwd; + size_t pwd_len; + + bool legacy; +}; + +#ifdef MBEDTLS_LEGACY +static inline int +mbedtls_pkcs5_pbkdf2_hmac_ext(mbedtls_md_type_t md_alg, const unsigned char *password, + size_t plen, const unsigned char *salt, size_t slen, + unsigned int iteration_count, + uint32_t key_length, unsigned char *output) +{ + mbedtls_md_context_t md_ctx; + const mbedtls_md_info_t *md_info; + int ret; + + md_info = mbedtls_md_info_from_type(md_alg); + if (!md_info) + return MBEDTLS_ERR_PKCS5_FEATURE_UNAVAILABLE; + + mbedtls_md_init(&md_ctx); + ret = mbedtls_md_setup(&md_ctx, md_info, 1); + if (ret) + goto out; + + ret = mbedtls_pkcs5_pbkdf2_hmac(&md_ctx, password, plen, salt, slen, + iteration_count, key_length, output); + +out: + mbedtls_md_free(&md_ctx); + return ret; +} +#endif + +static void +uc_p12_add_tag(struct pkcs12_ctx *ctx, uint8_t *end, uint8_t tag) +{ + mbedtls_asn1_write_len(&ctx->p, ctx->start, end - ctx->p); + mbedtls_asn1_write_tag(&ctx->p, ctx->start, tag); +} + +static void +uc_p12_add_sequence(struct pkcs12_ctx *ctx, uint8_t *end) +{ + uc_p12_add_tag(ctx, end, SEQUENCE_TAG); +} + +static void +uc_p12_add_algo(struct pkcs12_ctx *ctx, const char *oid, size_t oid_len, size_t par_len) +{ + mbedtls_asn1_write_algorithm_identifier(&ctx->p, ctx->start, oid, oid_len, par_len); +} + +static void +uc_p12_add_attribute(struct pkcs12_ctx *ctx, uint8_t *end, const char *oid, size_t oid_len) +{ + uc_p12_add_tag(ctx, end, SET_TAG); + mbedtls_asn1_write_oid(&ctx->p, ctx->start, oid, oid_len); + uc_p12_add_sequence(ctx, end); +} + +static void +uc_p12_add_localkeyid(struct pkcs12_ctx *ctx, bool key) +{ + uint8_t *end = ctx->p; + + ctx->p -= CERT_HASH_LEN; + if (key) + ctx->key_cert_hash = ctx->p; + else if (ctx->key_cert_hash) + memcpy(ctx->p, ctx->key_cert_hash, CERT_HASH_LEN); + uc_p12_add_tag(ctx, end, MBEDTLS_ASN1_OCTET_STRING); + uc_p12_add_attribute(ctx, end, OID_TAG(PKCS9_LOCAL_KEY_ID)); +} + +static void +uc_p12_add_bag(struct pkcs12_ctx *ctx, uint8_t *data_end, uint8_t *end, const char *oid, size_t oid_len) +{ + uc_p12_add_tag(ctx, data_end, CONTEXT_TAG(0)); + mbedtls_asn1_write_oid(&ctx->p, ctx->start, oid, oid_len); + uc_p12_add_sequence(ctx, end); +} + +static int +uc_p12_enc_setup(struct pkcs12_ctx *ctx, mbedtls_cipher_context_t *cipher) +{ + const mbedtls_cipher_info_t *cipher_info; + uint8_t key[KEY_LEN]; + int ret; + + random_cb(NULL, ctx->iv, IV_LEN); + ret = mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA256, + (void *)ctx->password, strlen(ctx->password), + ctx->salt, SALT_LEN, NUM_ITER, + KEY_LEN, key); + if (ret < 0) + return ret; + + cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_256_CBC); + ret = mbedtls_cipher_setup(cipher, cipher_info); + if (ret < 0) + return ret; + + return mbedtls_cipher_setkey(cipher, key, 8 * KEY_LEN, MBEDTLS_ENCRYPT); +} + +static int +uc_p12_enc_legacy(struct pkcs12_ctx *ctx, mbedtls_cipher_context_t *cipher) +{ + const mbedtls_cipher_info_t *cipher_info; + uint8_t key[24]; + int ret; + + ret = mbedtls_pkcs12_derivation(key, sizeof(key), ctx->pwd, ctx->pwd_len, + ctx->salt, SALT_LEN, MBEDTLS_MD_SHA1, + MBEDTLS_PKCS12_DERIVE_KEY, NUM_ITER); + if (ret < 0) + return ret; + + ret = mbedtls_pkcs12_derivation(ctx->iv, IV_LEN_LEGACY, ctx->pwd, ctx->pwd_len, + ctx->salt, SALT_LEN, MBEDTLS_MD_SHA1, + MBEDTLS_PKCS12_DERIVE_IV, NUM_ITER); + if (ret < 0) + return ret; + + cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_DES_EDE3_CBC); + ret = mbedtls_cipher_setup(cipher, cipher_info); + if (ret < 0) + return ret; + + return mbedtls_cipher_setkey(cipher, key, 8 * sizeof(key), MBEDTLS_ENCRYPT); +} + +static int +uc_p12_encrypt(struct pkcs12_ctx *ctx, uint8_t *end) +{ + mbedtls_cipher_context_t cipher; + size_t iv_len = ctx->legacy ? IV_LEN_LEGACY : IV_LEN; + size_t out_len = 0; + int ret; + + random_cb(NULL, ctx->salt, SALT_LEN); + + if (ctx->legacy) + ret = uc_p12_enc_legacy(ctx, &cipher); + else + ret = uc_p12_enc_setup(ctx, &cipher); + if (ret < 0) + goto out; + + ret = mbedtls_cipher_set_padding_mode(&cipher, MBEDTLS_PADDING_PKCS7); + if (ret < 0) + goto out; + + ret = mbedtls_cipher_crypt(&cipher, ctx->iv, iv_len, ctx->p, end - ctx->p, + (void *)buf, &out_len); + + if (ret < 0) + goto out; + + CTX_ADD(ctx, out_len - (end - ctx->p)); + memcpy(ctx->p, buf, out_len); + uc_p12_add_tag(ctx, end, MBEDTLS_ASN1_OCTET_STRING); + +out: + mbedtls_cipher_free(&cipher); + + return ret; +} + +static int +uc_p12_add_enc_params(struct pkcs12_ctx *ctx) +{ + uint8_t *par_end = ctx->p; + uint8_t *kdf_end; + + if (ctx->legacy) { + mbedtls_asn1_write_int(&ctx->p, ctx->start, NUM_ITER); + + CTX_ADD(ctx, SALT_LEN); + memcpy(ctx->p, ctx->salt, SALT_LEN); + uc_p12_add_tag(ctx, ctx->p + SALT_LEN, MBEDTLS_ASN1_OCTET_STRING); + + uc_p12_add_sequence(ctx, par_end); + + uc_p12_add_algo(ctx, OID_TAG(PKCS12_PBE_SHA1_DES3_EDE_CBC), par_end - ctx->p); + } else { + CTX_ADD(ctx, IV_LEN); + memcpy(ctx->p, ctx->iv, IV_LEN); + + uc_p12_add_tag(ctx, par_end, MBEDTLS_ASN1_OCTET_STRING); + uc_p12_add_algo(ctx, OID_TAG(AES_256_CBC), par_end - ctx->p); + + kdf_end = ctx->p; + uc_p12_add_algo(ctx, OID_TAG(HMAC_SHA256), 0); + mbedtls_asn1_write_int(&ctx->p, ctx->start, NUM_ITER); + CTX_ADD(ctx, SALT_LEN); + memcpy(ctx->p, ctx->salt, SALT_LEN); + uc_p12_add_tag(ctx, ctx->p + SALT_LEN, MBEDTLS_ASN1_OCTET_STRING); + uc_p12_add_sequence(ctx, kdf_end); + + uc_p12_add_algo(ctx, OID_TAG(PKCS5_PBKDF2), kdf_end - ctx->p); + uc_p12_add_sequence(ctx, par_end); + + uc_p12_add_algo(ctx, OID_TAG(PKCS5_PBES2), par_end - ctx->p); + } + + return 0; +} + +static int +uc_p12_add_cert(struct pkcs12_ctx *ctx, uc_value_t *arg, bool ca) +{ + const char *str = ucv_string_get(arg), *str_end; + uint8_t *bag_end, *end; + size_t len; + int ret; + +#define START_TAG "-----BEGIN CERTIFICATE-----" +#define END_TAG "-----END CERTIFICATE-----" + + if (!str) + return -1; + + str = strstr(str, START_TAG); + if (!str) + return -1; + + str += sizeof(START_TAG); + str_end = strstr(str, END_TAG); + if (!str_end) + return -1; + + if ((size_t)(str_end - str) > sizeof(buf) / 2) + return -1; + + ret = mbedtls_base64_decode((void *)buf, sizeof(buf) / 2, &len, + (const void *)str, str_end - str); + if (ret) + return ret; + + bag_end = ctx->p; + if (!ca && ctx->key_cert_hash) { + mbedtls_sha1((const void *)buf, len, ctx->key_cert_hash); + uc_p12_add_localkeyid(ctx, false); + uc_p12_add_tag(ctx, bag_end, SET_TAG); + } + + end = ctx->p; + CTX_ADD(ctx, len); + memcpy(ctx->p, buf, len); + uc_p12_add_tag(ctx, end, MBEDTLS_ASN1_OCTET_STRING); + + /* CertBag */ + uc_p12_add_tag(ctx, end, CONTEXT_TAG(0)); + mbedtls_asn1_write_oid(&ctx->p, ctx->start, OID_TAG(PKCS9_CERT_TYPE_X509)); + uc_p12_add_sequence(ctx, end); + + uc_p12_add_bag(ctx, end, bag_end, OID_TAG(PKCS12_CERT_BAG)); + + return 0; +} + +static int +uc_p12_add_key(struct pkcs12_ctx *ctx, uc_value_t *arg) +{ + mbedtls_pk_context *pk = ucv_resource_data(arg, "mbedtls.pk"); + uint8_t *bag_end, *end; + uint8_t *param = NULL; + size_t param_len = 0; + const char *oid; + size_t oid_len = 0; + int ret, len; + + if (!pk) + return -1; + + bag_end = ctx->p; + uc_p12_add_localkeyid(ctx, true); + uc_p12_add_tag(ctx, bag_end, SET_TAG); + + end = ctx->p; + len = mbedtls_pk_write_key_der(pk, (void *)ctx->start, end - ctx->start); + if (len < 0) + return len; + + ctx->p -= len; + + /* Convert EC key contents to PKCS#8 style */ + if (mbedtls_pk_get_type(pk) == MBEDTLS_PK_ECKEY) { + mbedtls_ecp_group_id grp_id; + mbedtls_asn1_buf tag_buf; + uint8_t *pkey_start, *pkey_end; + size_t seq_len, pkey_len, param_tag_len; + uint8_t *p = ctx->p; + uint8_t *_end = end; + uint8_t *_start; + int version; + + ret = mbedtls_asn1_get_tag(&p, end, &seq_len, SEQUENCE_TAG); + if (ret < 0) + return ret; + + _start = p; + _end = p + seq_len; + ret = mbedtls_asn1_get_int(&p, _end, &version); + if (ret < 0) + return ret; + + /* private key */ + ret = mbedtls_asn1_get_tag(&p, _end, &pkey_len, MBEDTLS_ASN1_OCTET_STRING); + if (ret < 0) + return ret; + pkey_start = p; + p += pkey_len; + pkey_end = p; + + /* parameters */ + ret = mbedtls_asn1_get_tag(&p, _end, ¶m_len, CONTEXT_TAG(0)); + if (ret < 0) + return ret; + + param = memcpy(buf, p, param_len); + p += param_len; + + /* overwrite parameters */ + param_tag_len = p - pkey_end; + ctx->p += param_tag_len; + _start += param_tag_len; + memmove(ctx->p, ctx->p - param_tag_len, p - ctx->p); + + /* replace sequence tag */ + ctx->p = _start; + uc_p12_add_sequence(ctx, end); + + /* check for Curve25519 or Curve448 */ + tag_buf = (mbedtls_asn1_buf){ + .p = (uint8_t *)buf, + .len = param_len, + }; + tag_buf.tag = *tag_buf.p; + ret = mbedtls_asn1_get_tag(&tag_buf.p, tag_buf.p + param_len, &tag_buf.len, tag_buf.tag); + if (ret < 0) + return ret; + + oid = MBEDTLS_OID_EC_ALG_UNRESTRICTED; + oid_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_EC_ALG_UNRESTRICTED); + +#ifdef MBEDTLS_LEGACY + (void)pkey_start; + (void)grp_id; +#else + ret = mbedtls_oid_get_ec_grp_algid(&tag_buf, &grp_id); + if (!ret && (grp_id == MBEDTLS_ECP_DP_CURVE25519 || + grp_id == MBEDTLS_ECP_DP_CURVE448)) { + ctx->p = end - pkey_len; + memmove(ctx->p, pkey_start, pkey_len); + uc_p12_add_tag(ctx, end, MBEDTLS_ASN1_OCTET_STRING); + } +#endif + } else { + mbedtls_oid_get_oid_by_pk_alg(mbedtls_pk_get_type(pk), &oid, &oid_len); + } + + uc_p12_add_tag(ctx, end, MBEDTLS_ASN1_OCTET_STRING); + + /* KeyBag */ + if (param_len) { + CTX_ADD(ctx, param_len); + memcpy(ctx->p, param, param_len); + } + uc_p12_add_algo(ctx, oid, oid_len, param_len); + mbedtls_asn1_write_int(&ctx->p, ctx->start, 0); + uc_p12_add_sequence(ctx, end); + + ret = uc_p12_encrypt(ctx, end); + if (ret < 0) + return ret; + + ret = uc_p12_add_enc_params(ctx); + if (ret < 0) + return ret; + + uc_p12_add_sequence(ctx, end); + + uc_p12_add_bag(ctx, end, bag_end, OID_TAG(PKCS12_SHROUDED_KEY_BAG)); + + return 0; +} + +static int +uc_p12_add_authsafe(struct pkcs12_ctx *ctx, uc_value_t *arg) +{ + uc_value_t *extra; + uint8_t *end = ctx->p; + size_t len; + int ret; + + /* authSafe contents */ + extra = ucv_object_get(arg, "extra", NULL); + if (extra) { + size_t len; + + if (ucv_type(extra) != UC_ARRAY) + return -1; + + len = ucv_array_length(extra); + for (size_t i = 0; i < len; i++) { + ret = uc_p12_add_cert(ctx, ucv_array_get(extra, i), true); + if (ret < 0) + return ret; + } + } + + ret = uc_p12_add_key(ctx, ucv_object_get(arg, "key", NULL)); + if (ret < 0) + return ret; + + ret = uc_p12_add_cert(ctx, ucv_object_get(arg, "cert", NULL), false); + if (ret < 0) + return ret; + + /* encrypted */ + uc_p12_add_sequence(ctx, end); + + ret = uc_p12_encrypt(ctx, end); + if (ret < 0) + return ret; + + uc_p12_add_tag(ctx, end, CONTEXT_TAG(0)); + + ret = uc_p12_add_enc_params(ctx); + if (ret < 0) + return ret; + + mbedtls_asn1_write_oid(&ctx->p, ctx->start, OID_TAG(PKCS7_DATA)); + uc_p12_add_sequence(ctx, end); + + mbedtls_asn1_write_int(&ctx->p, ctx->start, 0); + uc_p12_add_sequence(ctx, end); + uc_p12_add_tag(ctx, end, CONTEXT_TAG(0)); + mbedtls_asn1_write_oid(&ctx->p, ctx->start, OID_TAG(PKCS7_ENCRYPTED_DATA)); + uc_p12_add_sequence(ctx, end); + + /* authSafe contents */ + uc_p12_add_sequence(ctx, end); + + /* authSafe header */ + len = end - ctx->p; + uc_p12_add_tag(ctx, end, MBEDTLS_ASN1_OCTET_STRING); + uc_p12_add_tag(ctx, end, CONTEXT_TAG(0)); + mbedtls_asn1_write_oid(&ctx->p, ctx->start, OID_TAG(PKCS7_DATA)); + uc_p12_add_sequence(ctx, end); + + return len; +} + +static void * +uc_p12_add_mac_digest_info(struct pkcs12_ctx *ctx) +{ + uint8_t *end = ctx->p; + size_t len = 20; + + ctx->p -= len; + uc_p12_add_tag(ctx, end, MBEDTLS_ASN1_OCTET_STRING); + uc_p12_add_algo(ctx, OID_TAG(DIGEST_ALG_SHA1), 0); + uc_p12_add_sequence(ctx, end); + + return end - len; +} + +uc_value_t *uc_generate_pkcs12(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0), *pwd_arg; + mbedtls_md_context_t hmac; + uint8_t *end = (uint8_t *)&buf[sizeof(buf)]; + struct pkcs12_ctx ctx = { + .start = (uint8_t *)&buf[sizeof(buf) / 2], + .p = end, + }; + uint8_t *hash, *data; + uint8_t key[20], *salt; + size_t salt_len = 8; + int data_len; + uc_value_t *ret = NULL; + const char *passwd; + + if (ucv_type(arg) != UC_OBJECT) + INVALID_ARG(); + + ctx.legacy = ucv_is_truish(ucv_object_get(arg, "legacy", NULL)); + + mbedtls_md_init(&hmac); + pwd_arg = ucv_object_get(arg, "password", NULL); + passwd = ucv_string_get(pwd_arg); + if (!passwd) + INVALID_ARG(); + + /* password is expected to be a UTF-16 string */ + ctx.password = passwd; + ctx.pwd_len = 2 * strlen(passwd) + 2; + ctx.pwd = malloc(ctx.pwd_len); + for (size_t i = 0; i < ctx.pwd_len; i += 2) { + ctx.pwd[i] = 0; + ctx.pwd[i + 1] = passwd[i / 2]; + } + + /* MacData */ + mbedtls_asn1_write_int(&ctx.p, ctx.start, NUM_ITER); + + ctx.p -= salt_len; + salt = ctx.p; + random_cb(NULL, salt, salt_len); + uc_p12_add_tag(&ctx, salt + salt_len, MBEDTLS_ASN1_OCTET_STRING); + + hash = uc_p12_add_mac_digest_info(&ctx); + uc_p12_add_sequence(&ctx, end); + + data = ctx.p; + data_len = uc_p12_add_authsafe(&ctx, arg); + if (C(data_len) < 0) + goto out; + data -= data_len; + + mbedtls_asn1_write_int(&ctx.p, ctx.start, 3); + uc_p12_add_sequence(&ctx, end); + + if (C(mbedtls_pkcs12_derivation(key, sizeof(key), ctx.pwd, ctx.pwd_len, + salt, salt_len, MBEDTLS_MD_SHA1, + MBEDTLS_PKCS12_DERIVE_MAC_KEY, NUM_ITER))) + goto out; + + if (C(mbedtls_md_setup(&hmac, mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), 1))) + goto out; + if (C(mbedtls_md_hmac_starts(&hmac, key, sizeof(key)))) + goto out; + if (C(mbedtls_md_hmac_update(&hmac, data, data_len))) + goto out; + if (C(mbedtls_md_hmac_finish(&hmac, hash))) + goto out; + + ret = ucv_string_new_length((char *)ctx.p, end - ctx.p); + +out: + free(ctx.pwd); + mbedtls_md_free(&hmac); + return ret; +} diff --git a/package/utils/ucode-mod-pkgen/src/ucode.c b/package/utils/ucode-mod-pkgen/src/ucode.c new file mode 100644 index 0000000000..cb5569b977 --- /dev/null +++ b/package/utils/ucode-mod-pkgen/src/ucode.c @@ -0,0 +1,598 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Felix Fietkau + */ +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "pk.h" + +/* mbedtls < 3.x compat */ +#ifdef MBEDTLS_LEGACY +#define mbedtls_pk_parse_key(pk, key, keylen, passwd, passwdlen, random, random_ctx) \ + mbedtls_pk_parse_key(pk, key, keylen, passwd, passwdlen) +#endif + +static uc_resource_type_t *uc_pk_type, *uc_crt_type; +static uc_value_t *registry; +char buf[32 * 1024]; +int mbedtls_errno; + +struct uc_cert_wr { + mbedtls_x509write_cert crt; /* must be first */ + mbedtls_mpi mpi; + unsigned int reg; +}; + +static unsigned int uc_reg_add(uc_value_t *val) +{ + size_t i = 0; + + while (ucv_array_get(registry, i)) + i++; + + ucv_array_set(registry, i, ucv_get(val)); + + return i; +} + +int random_cb(void *ctx, unsigned char *out, size_t len) +{ +#ifdef linux + if (getrandom(out, len, 0) != (ssize_t) len) + return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED; +#else + static FILE *f; + + if (!f) + f = fopen("/dev/urandom", "r"); + if (fread(out, len, 1, f) != 1) + return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED; +#endif + + return 0; +} + +int64_t get_int_arg(uc_value_t *obj, const char *key, int64_t defval) +{ + uc_value_t *uval = ucv_object_get(obj, key, NULL); + int64_t val; + + if (!uval) + return defval; + + val = ucv_int64_get(uval); + if (errno || val < 0 || val > INT_MAX) + return INT_MIN; + + return val ? val : defval; +} + +static int +gen_rsa_key(mbedtls_pk_context *pk, uc_value_t *arg) +{ + int64_t key_size, exp; + + key_size = get_int_arg(arg, "size", 2048); + exp = get_int_arg(arg, "exponent", 65537); + if (key_size < 0 || exp < 0) + return -1; + + return mbedtls_rsa_gen_key(mbedtls_pk_rsa(*pk), random_cb, NULL, key_size, exp); +} + +static int +gen_ec_key(mbedtls_pk_context *pk, uc_value_t *arg) +{ + mbedtls_ecp_group_id curve; + const char *c_name; + uc_value_t *c_arg; + + c_arg = ucv_object_get(arg, "curve", NULL); + if (c_arg && ucv_type(c_arg) != UC_STRING) + return -1; + + c_name = ucv_string_get(c_arg); + if (!c_name) + curve = MBEDTLS_ECP_DP_SECP256R1; + else { + const mbedtls_ecp_curve_info *curve_info; + curve_info = mbedtls_ecp_curve_info_from_name(c_name); + if (!curve_info) + return MBEDTLS_ERR_PK_UNKNOWN_NAMED_CURVE; + + curve = curve_info->grp_id; + } + + return mbedtls_ecp_gen_key(curve, mbedtls_pk_ec(*pk), random_cb, NULL); +} + +static void free_pk(void *pk) +{ + if (!pk) + return; + + mbedtls_pk_free(pk); + free(pk); +} + +static void free_crt(void *ptr) +{ + struct uc_cert_wr *crt = ptr; + + if (!crt) + return; + + mbedtls_x509write_crt_free(&crt->crt); + mbedtls_mpi_free(&crt->mpi); + ucv_array_set(registry, crt->reg, NULL); + free(crt); +} + +static uc_value_t * +uc_generate_key(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *cur, *arg = uc_fn_arg(0); + mbedtls_pk_type_t pk_type; + mbedtls_pk_context *pk; + const char *type; + int ret; + + if (ucv_type(arg) != UC_OBJECT) + INVALID_ARG(); + + cur = ucv_object_get(arg, "type", NULL); + type = ucv_string_get(cur); + if (!type) + INVALID_ARG(); + + if (!strcmp(type, "rsa")) + pk_type = MBEDTLS_PK_RSA; + else if (!strcmp(type, "ec")) + pk_type = MBEDTLS_PK_ECKEY; + else + INVALID_ARG(); + + pk = calloc(1, sizeof(*pk)); + mbedtls_pk_init(pk); + mbedtls_pk_setup(pk, mbedtls_pk_info_from_type(pk_type)); + switch (pk_type) { + case MBEDTLS_PK_RSA: + ret = C(gen_rsa_key(pk, arg)); + break; + case MBEDTLS_PK_ECKEY: + ret = C(gen_ec_key(pk, arg)); + break; + default: + ret = -1; + } + + if (ret) { + free_pk(pk); + return NULL; + } + + return uc_resource_new(uc_pk_type, pk); +} + +static uc_value_t * +uc_load_key(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *keystr = uc_fn_arg(0); + uc_value_t *pub = uc_fn_arg(1); + uc_value_t *passwd = uc_fn_arg(2); + mbedtls_pk_context *pk; + int ret; + + if (ucv_type(keystr) != UC_STRING || + (pub && ucv_type(pub) != UC_BOOLEAN) || + (passwd && ucv_type(passwd) != UC_STRING)) + INVALID_ARG(); + + pk = calloc(1, sizeof(*pk)); + mbedtls_pk_init(pk); + if (ucv_is_truish(pub)) + ret = C(mbedtls_pk_parse_public_key(pk, (const uint8_t *)ucv_string_get(keystr), + ucv_string_length(keystr) + 1)); + else + ret = C(mbedtls_pk_parse_key(pk, (const uint8_t *)ucv_string_get(keystr), + ucv_string_length(keystr) + 1, + (const uint8_t *)ucv_string_get(passwd), + ucv_string_length(passwd) + 1, + random_cb, NULL)); + if (ret) { + free_pk(pk); + return NULL; + } + + return uc_resource_new(uc_pk_type, pk); +} + +static void +uc_cert_info_add(uc_value_t *info, const char *name, int len) +{ + uc_value_t *val; + + if (len < 0) + return; + + val = ucv_string_new_length(buf, len); + ucv_object_add(info, name, ucv_get(val)); +} + +static void +uc_cert_info_add_name(uc_value_t *info, const char *name, mbedtls_x509_name *dn) +{ + int len; + + len = mbedtls_x509_dn_gets(buf, sizeof(buf), dn); + uc_cert_info_add(info, name, len); +} + +static void +uc_cert_info_add_time(uc_value_t *info, const char *name, mbedtls_x509_time *t) +{ + int len; + + len = snprintf(buf, sizeof(buf), "%04d%02d%02d%02d%02d%02d", + t->year, t->mon, t->day, t->hour, t->min, t->sec); + uc_cert_info_add(info, name, len); +} + +static uc_value_t * +uc_cert_info(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + mbedtls_x509_crt crt, *cur; + uc_value_t *ret = NULL; + + if (ucv_type(arg) != UC_STRING) + return NULL; + + mbedtls_x509_crt_init(&crt); + if (C(mbedtls_x509_crt_parse(&crt, (const void *)ucv_string_get(arg), ucv_string_length(arg) + 1)) < 0) + goto out; + + ret = ucv_array_new(vm); + for (cur = &crt; cur && cur->version != 0; cur = cur->next) { + uc_value_t *info = ucv_object_new(vm); + int len; + + ucv_array_push(ret, ucv_get(info)); + ucv_object_add(info, "version", ucv_int64_new(cur->version)); + + uc_cert_info_add_name(info, "issuer", &cur->issuer); + uc_cert_info_add_name(info, "subject", &cur->issuer); + uc_cert_info_add_time(info, "valid_from", &cur->valid_from); + uc_cert_info_add_time(info, "valid_to", &cur->valid_to); + + len = mbedtls_x509_serial_gets(buf, sizeof(buf), &cur->serial); + uc_cert_info_add(info, "serial", len); + } + +out: + mbedtls_x509_crt_free(&crt); + return ret; +} + +static uc_value_t * +uc_pk_pem(uc_vm_t *vm, size_t nargs) +{ + mbedtls_pk_context *pk = uc_fn_thisval("mbedtls.pk"); + uc_value_t *pub = uc_fn_arg(0); + + if (!pk) + return NULL; + + if (ucv_is_truish(pub)) + CHECK(mbedtls_pk_write_pubkey_pem(pk, (void *)buf, sizeof(buf))); + else + CHECK(mbedtls_pk_write_key_pem(pk, (void *)buf, sizeof(buf))); + + return ucv_string_new(buf); +} + +static uc_value_t * +uc_pk_der(uc_vm_t *vm, size_t nargs) +{ + mbedtls_pk_context *pk = uc_fn_thisval("mbedtls.pk"); + uc_value_t *pub = uc_fn_arg(0); + int len; + + if (!pk) + return NULL; + + if (ucv_is_truish(pub)) + len = mbedtls_pk_write_pubkey_der(pk, (void *)buf, sizeof(buf)); + else + len = mbedtls_pk_write_key_der(pk, (void *)buf, sizeof(buf)); + if (len < 0) + CHECK(len); + + return ucv_string_new_length(buf + sizeof(buf) - len, len); +} + +static uc_value_t * +uc_crt_pem(uc_vm_t *vm, size_t nargs) +{ + mbedtls_x509write_cert *crt = uc_fn_thisval("mbedtls.crt"); + if (!crt) + return NULL; + + CHECK(mbedtls_x509write_crt_pem(crt, (void *)buf, sizeof(buf), random_cb, NULL)); + + return ucv_string_new(buf); +} + +static uc_value_t * +uc_crt_der(uc_vm_t *vm, size_t nargs) +{ + mbedtls_x509write_cert *crt = uc_fn_thisval("mbedtls.crt"); + int len; + + if (!crt) + return NULL; + + len = mbedtls_x509write_crt_der(crt, (void *)buf, sizeof(buf), random_cb, NULL); + if (len < 0) + CHECK(len); + + return ucv_string_new_length(buf, len); +} + +static int +uc_cert_set_validity(mbedtls_x509write_cert *crt, uc_value_t *arg) +{ + uc_value_t *from = ucv_array_get(arg, 0); + uc_value_t *to = ucv_array_get(arg, 1); + + if (ucv_type(from) != UC_STRING || ucv_type(to) != UC_STRING) + return -1; + + return mbedtls_x509write_crt_set_validity(crt, ucv_string_get(from), ucv_string_get(to)); +} + +static int +uc_cert_init(mbedtls_x509write_cert *crt, mbedtls_mpi *mpi, uc_value_t *reg, uc_value_t *arg) +{ + uc_value_t *cur; + int64_t serial; + int path_len; + int version; + bool ca; + + mbedtls_mpi_init(mpi); + mbedtls_x509write_crt_init(crt); + mbedtls_x509write_crt_set_md_alg(crt, MBEDTLS_MD_SHA256); + + ca = ucv_is_truish(ucv_object_get(arg, "ca", NULL)); + path_len = get_int_arg(arg, "max_pathlen", ca ? -1 : 0); + if (path_len < -1) + return -1; + + version = get_int_arg(arg, "version", 3); + if (version < 0 || version > 3) + return -1; + + serial = get_int_arg(arg, "serial", 1); + if (serial < 0) + return -1; + + mbedtls_mpi_lset(mpi, serial); + mbedtls_x509write_crt_set_serial(crt, mpi); + mbedtls_x509write_crt_set_version(crt, version - 1); + CHECK_INT(mbedtls_x509write_crt_set_basic_constraints(crt, ca, path_len)); + + cur = ucv_object_get(arg, "subject_name", NULL); + if (ucv_type(cur) == UC_STRING) + CHECK_INT(mbedtls_x509write_crt_set_subject_name(crt, ucv_string_get(cur))); + else + return -1; + cur = ucv_object_get(arg, "subject_key", NULL); + if (cur) { + mbedtls_pk_context *key = ucv_resource_data(cur, "mbedtls.pk"); + if (!key) + return -1; + + ucv_array_set(reg, 0, ucv_get(cur)); + mbedtls_x509write_crt_set_subject_key(crt, key); + mbedtls_x509write_crt_set_subject_key_identifier(crt); + } else + return -1; + + cur = ucv_object_get(arg, "issuer_name", NULL); + if (ucv_type(cur) == UC_STRING) + CHECK_INT(mbedtls_x509write_crt_set_issuer_name(crt, ucv_string_get(cur))); + else + return -1; + cur = ucv_object_get(arg, "issuer_key", NULL); + if (cur) { + mbedtls_pk_context *key = ucv_resource_data(cur, "mbedtls.pk"); + if (!key) + return -1; + + ucv_array_set(reg, 1, ucv_get(cur)); + mbedtls_x509write_crt_set_issuer_key(crt, key); + mbedtls_x509write_crt_set_authority_key_identifier(crt); + } else + return -1; + + cur = ucv_object_get(arg, "validity", NULL); + if (ucv_type(cur) != UC_ARRAY || ucv_array_length(cur) != 2) + return -1; + if (uc_cert_set_validity(crt, cur)) + return -1; + + cur = ucv_object_get(arg, "key_usage", NULL); + if (ucv_type(cur) == UC_ARRAY) { + static const struct { + const char *name; + uint8_t val; + } key_flags[] = { + { "digital_signature", MBEDTLS_X509_KU_DIGITAL_SIGNATURE }, + { "non_repudiation", MBEDTLS_X509_KU_NON_REPUDIATION }, + { "key_encipherment", MBEDTLS_X509_KU_KEY_ENCIPHERMENT }, + { "data_encipherment", MBEDTLS_X509_KU_DATA_ENCIPHERMENT }, + { "key_agreement", MBEDTLS_X509_KU_KEY_AGREEMENT }, + { "key_cert_sign", MBEDTLS_X509_KU_KEY_CERT_SIGN }, + { "crl_sign", MBEDTLS_X509_KU_CRL_SIGN }, + }; + uint8_t key_usage = 0; + size_t len = ucv_array_length(cur); + + for (size_t i = 0; i < len; i++) { + uc_value_t *val = ucv_array_get(cur, i); + const char *str; + size_t k; + + str = ucv_string_get(val); + if (!str) + return -1; + + for (k = 0; k < ARRAY_SIZE(key_flags); k++) + if (!strcmp(str, key_flags[k].name)) + break; + if (k == ARRAY_SIZE(key_flags)) + return -1; + + key_usage |= key_flags[k].val; + } + CHECK_INT(mbedtls_x509write_crt_set_key_usage(crt, key_usage)); + } else if (cur) + return -1; + +#ifndef MBEDTLS_LEGACY + cur = ucv_object_get(arg, "ext_key_usage", NULL); + if (ucv_type(cur) == UC_ARRAY && ucv_array_length(cur)) { + static const struct { + const char *name; + struct mbedtls_asn1_buf val; + } key_flags[] = { +#define __oid(name, val) \ + { \ + name, \ + { \ + .tag = MBEDTLS_ASN1_OID, \ + .len = sizeof(MBEDTLS_OID_##val), \ + .p = (uint8_t *)MBEDTLS_OID_##val, \ + } \ + } + __oid("server_auth", SERVER_AUTH), + __oid("client_auth", CLIENT_AUTH), + __oid("code_signing", CODE_SIGNING), + __oid("email_protection", EMAIL_PROTECTION), + __oid("time_stamping", TIME_STAMPING), + __oid("ocsp_signing", OCSP_SIGNING), + __oid("any", ANY_EXTENDED_KEY_USAGE) + }; + struct mbedtls_asn1_sequence *elem; + size_t len = ucv_array_length(cur); + + elem = calloc(len, sizeof(*elem)); + for (size_t i = 0; i < len; i++) { + uc_value_t *val = ucv_array_get(cur, i); + const char *str; + size_t k; + + str = ucv_string_get(val); + if (!str) + return -1; + + for (k = 0; k < ARRAY_SIZE(key_flags); k++) + if (!strcmp(str, key_flags[k].name)) + break; + + if (k == ARRAY_SIZE(key_flags)) { + free(elem); + return -1; + } + elem[i].buf = key_flags[k].val; + if (i + 1 < len) + elem[i].next = &elem[i + 1]; + } + + CHECK_INT(mbedtls_x509write_crt_set_ext_key_usage(crt, elem)); + } else if (cur) + return -1; +#endif + + return 0; +} + +static uc_value_t * +uc_generate_cert(uc_vm_t *vm, size_t nargs) +{ + struct uc_cert_wr *crt; + uc_value_t *arg = uc_fn_arg(0); + uc_value_t *reg; + + if (ucv_type(arg) != UC_OBJECT) + return NULL; + + reg = ucv_array_new(vm); + crt = calloc(1, sizeof(*crt)); + if (C(uc_cert_init(&crt->crt, &crt->mpi, reg, arg))) { + free(crt); + return NULL; + } + + crt->reg = uc_reg_add(reg); + + return uc_resource_new(uc_crt_type, crt); +} + +static uc_value_t * +uc_mbedtls_error(uc_vm_t *vm, size_t nargs) +{ + mbedtls_strerror(mbedtls_errno, buf, sizeof(buf)); + + return ucv_string_new(buf); +} + +static uc_value_t * +uc_mbedtls_errno(uc_vm_t *vm, size_t nargs) +{ + return ucv_int64_new(mbedtls_errno); +} + + +static const uc_function_list_t pk_fns[] = { + { "pem", uc_pk_pem }, + { "der", uc_pk_der }, +}; + +static const uc_function_list_t crt_fns[] = { + { "pem", uc_crt_pem }, + { "der", uc_crt_der }, +}; + +static const uc_function_list_t global_fns[] = { + { "load_key", uc_load_key }, + { "cert_info", uc_cert_info }, + { "generate_key", uc_generate_key }, + { "generate_cert", uc_generate_cert }, + { "generate_pkcs12", uc_generate_pkcs12 }, + { "errno", uc_mbedtls_errno }, + { "error", uc_mbedtls_error }, +}; + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_pk_type = uc_type_declare(vm, "mbedtls.pk", pk_fns, free_pk); + uc_crt_type = uc_type_declare(vm, "mbedtls.crt", crt_fns, free_crt); + uc_function_list_register(scope, global_fns); + + registry = ucv_array_new(vm); + uc_vm_registry_set(vm, "pkgen.registry", registry); +} -- 2.30.2