[njs] Introduced WebCrypto API according to W3C spec.
Dmitry Volyntsev
xeioex at nginx.com
Mon Oct 11 15:08:58 UTC 2021
details: https://hg.nginx.org/njs/rev/a4c3c333c05d
branches:
changeset: 1720:a4c3c333c05d
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Mon Oct 11 15:06:15 2021 +0000
description:
Introduced WebCrypto API according to W3C spec.
The following methods were implemented:
crypto.getRandomValues()
crypto.subtle.importKey()
format: raw, pkcs8, spki
algorithm: AES-CBC, AES-CTR, AES-GCM,
ECDSA, HKDF, HMAC, PBKDF2,
RSASSA-PKCS1-v1_5, RSA-OAEP, RSA-PSS
crypto.subtle.decrypt()
crypto.subtle.encrypt()
algorithm: AES-CBC, AES-CTR, AES-GCM,
RSA-OAEP
crypto.subtle.deriveBits()
crypto.subtle.deriveKey()
algorithm: HKDF, PBKDF2
crypto.subtle.digest()
algorithm: SHA-1, SHA-256, SHA-384, SHA-512
crypto.subtle.sign()
crypto.subtle.verify()
algorithm: ECDSA, HMAC, RSASSA-PKCS1-v1_5, RSA-PSS
diffstat:
auto/make | 5 +-
auto/openssl | 56 +
auto/sources | 1 +
auto/summary | 4 +
configure | 3 +-
external/njs_webcrypto.c | 2666 +++++++++++++++++++++
external/njs_webcrypto.h | 15 +
nginx/config | 11 +-
nginx/ngx_js.c | 7 +
src/njs_shell.c | 12 +
src/njs_str.c | 37 +
src/njs_str.h | 9 +
src/test/njs_externals_test.c | 14 +
src/test/njs_unit_test.c | 49 +-
test/njs_expect_test.exp | 32 +
test/ts/test.ts | 25 +-
test/webcrypto/README.rst | 136 +
test/webcrypto/aes.js | 123 +
test/webcrypto/aes_decoding.js | 116 +
test/webcrypto/aes_gcm_enc.js | 51 +
test/webcrypto/derive.js | 149 +
test/webcrypto/digest.js | 88 +
test/webcrypto/ec.pkcs8 | 5 +
test/webcrypto/ec.spki | 4 +
test/webcrypto/ec2.pkcs8 | 5 +
test/webcrypto/ec2.spki | 4 +
test/webcrypto/rsa.js | 106 +
test/webcrypto/rsa.pkcs8 | 16 +
test/webcrypto/rsa.pkcs8.broken | 16 +
test/webcrypto/rsa.spki | 6 +
test/webcrypto/rsa.spki.broken | 6 +
test/webcrypto/rsa2.pkcs8 | 16 +
test/webcrypto/rsa2.spki | 6 +
test/webcrypto/rsa_decoding.js | 81 +
test/webcrypto/sign.js | 282 ++
test/webcrypto/text.base64.aes-cbc128.enc | 1 +
test/webcrypto/text.base64.aes-cbc256.enc | 1 +
test/webcrypto/text.base64.aes-ctr128.enc | 1 +
test/webcrypto/text.base64.aes-ctr256.enc | 1 +
test/webcrypto/text.base64.aes-gcm128-96.enc | 1 +
test/webcrypto/text.base64.aes-gcm128-extra.enc | 1 +
test/webcrypto/text.base64.aes-gcm128.enc | 1 +
test/webcrypto/text.base64.aes-gcm256.enc | 1 +
test/webcrypto/text.base64.rsa-oaep.enc | 3 +
test/webcrypto/text.base64.sha1.ecdsa.sig | 2 +
test/webcrypto/text.base64.sha1.hmac.sig | 1 +
test/webcrypto/text.base64.sha1.pkcs1.sig | 3 +
test/webcrypto/text.base64.sha1.rsa-pss.16.sig | 3 +
test/webcrypto/text.base64.sha256.ecdsa.sig | 2 +
test/webcrypto/text.base64.sha256.hmac.sig | 1 +
test/webcrypto/text.base64.sha256.hmac.sig.broken | 1 +
test/webcrypto/text.base64.sha256.pkcs1.sig | 3 +
test/webcrypto/text.base64.sha256.rsa-pss.0.sig | 3 +
test/webcrypto/text.base64.sha256.rsa-pss.32.sig | 3 +
test/webcrypto/verify.js | 207 +
ts/index.d.ts | 1 +
ts/njs_core.d.ts | 2 +-
ts/njs_webcrypto.d.ts | 226 +
58 files changed, 4615 insertions(+), 16 deletions(-)
diffs (truncated from 5014 to 1000 lines):
diff -r 66bd2cc7fd87 -r a4c3c333c05d auto/make
--- a/auto/make Mon Oct 11 17:46:24 2021 +0300
+++ b/auto/make Mon Oct 11 15:06:15 2021 +0000
@@ -75,7 +75,7 @@ cat << END >> $NJS_MAKEFILE
$NJS_BUILD_DIR/njs: \\
$NJS_BUILD_DIR/libnjs.a \\
- src/njs_shell.c
+ src/njs_shell.c external/njs_webcrypto.h external/njs_webcrypto.c
\$(NJS_LINK) -o $NJS_BUILD_DIR/njs \$(NJS_CFLAGS) \\
$NJS_LIB_AUX_CFLAGS \$(NJS_LIB_INCS) -Injs \\
src/njs_shell.c \\
@@ -159,7 +159,8 @@ njs_dep_post=`njs_gen_dep_post $njs_dep
cat << END >> $NJS_MAKEFILE
-$NJS_BUILD_DIR/$njs_externals_obj: $njs_src
+$NJS_BUILD_DIR/$njs_externals_obj: \\
+ $njs_src external/njs_webcrypto.h external/njs_webcrypto.c
\$(NJS_CC) -c \$(NJS_CFLAGS) $NJS_LIB_AUX_CFLAGS \\
\$(NJS_LIB_INCS) -Injs \\
-o $NJS_BUILD_DIR/$njs_externals_obj \\
diff -r 66bd2cc7fd87 -r a4c3c333c05d auto/openssl
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/auto/openssl Mon Oct 11 15:06:15 2021 +0000
@@ -0,0 +1,56 @@
+
+# Copyright (C) Dmitry Volyntsev
+# Copyright (C) NGINX, Inc.
+
+
+NJS_OPENSSL_LIB=
+NJS_HAVE_OPENSSL=NO
+
+
+njs_found=no
+
+
+njs_feature="OpenSSL library"
+njs_feature_name=NJS_HAVE_OPENSSL
+njs_feature_run=yes
+njs_feature_incs=
+njs_feature_libs="-lcrypto"
+njs_feature_test="#include <openssl/evp.h>
+
+ int main() {
+ OpenSSL_add_all_algorithms();
+ return 0;
+ }"
+. auto/feature
+
+
+if [ $njs_found = yes ]; then
+ njs_feature="OpenSSL HKDF"
+ njs_feature_name=NJS_HAVE_OPENSSL_HKDF
+ njs_feature_test="#include <openssl/evp.h>
+ #include <openssl/kdf.h>
+
+ int main(void) {
+ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+
+ EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256());
+ EVP_PKEY_CTX_free(pctx);
+
+ return 0;
+ }"
+ . auto/feature
+
+ njs_feature="OpenSSL EVP_MD_CTX_new()"
+ njs_feature_name=NJS_HAVE_OPENSSL_EVP_MD_CTX_NEW
+ njs_feature_test="#include <openssl/evp.h>
+
+ int main(void) {
+ EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+ EVP_MD_CTX_free(ctx);
+ return 0;
+ }"
+ . auto/feature
+
+ NJS_HAVE_OPENSSL=YES
+ NJS_OPENSSL_LIB="$njs_feature_libs"
+fi
diff -r 66bd2cc7fd87 -r a4c3c333c05d auto/sources
--- a/auto/sources Mon Oct 11 17:46:24 2021 +0300
+++ b/auto/sources Mon Oct 11 15:06:15 2021 +0000
@@ -2,6 +2,7 @@ NJS_LIB_SRCS=" \
src/njs_diyfp.c \
src/njs_dtoa.c \
src/njs_dtoa_fixed.c \
+ src/njs_str.c \
src/njs_strtod.c \
src/njs_murmur_hash.c \
src/njs_djb_hash.c \
diff -r 66bd2cc7fd87 -r a4c3c333c05d auto/summary
--- a/auto/summary Mon Oct 11 17:46:24 2021 +0300
+++ b/auto/summary Mon Oct 11 15:06:15 2021 +0000
@@ -15,6 +15,10 @@ if [ $NJS_HAVE_READLINE = YES ]; then
echo " + using readline library: $NJS_READLINE_LIB"
fi
+if [ $NJS_HAVE_OPENSSL = YES ]; then
+ echo " + using OpenSSL library: $NJS_OPENSSL_LIB"
+fi
+
echo
echo " njs build dir: $NJS_BUILD_DIR"
diff -r 66bd2cc7fd87 -r a4c3c333c05d configure
--- a/configure Mon Oct 11 17:46:24 2021 +0300
+++ b/configure Mon Oct 11 15:06:15 2021 +0000
@@ -26,12 +26,13 @@ set -u
. auto/explicit_bzero
. auto/pcre
. auto/readline
+. auto/openssl
. auto/sources
NJS_LIB_AUX_CFLAGS="$NJS_PCRE_CFLAGS"
NJS_LIBS="$NJS_LIBRT"
-NJS_LIB_AUX_LIBS="$NJS_PCRE_LIB"
+NJS_LIB_AUX_LIBS="$NJS_PCRE_LIB $NJS_OPENSSL_LIB"
. auto/make
diff -r 66bd2cc7fd87 -r a4c3c333c05d external/njs_webcrypto.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/external/njs_webcrypto.c Mon Oct 11 15:06:15 2021 +0000
@@ -0,0 +1,2666 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ */
+
+
+#include <njs_main.h>
+#include "njs_webcrypto.h"
+
+#include <openssl/bn.h>
+#include <openssl/bio.h>
+#include <openssl/x509.h>
+#include <openssl/evp.h>
+#include <openssl/aes.h>
+#include <openssl/rsa.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/crypto.h>
+
+#if NJS_HAVE_OPENSSL_HKDF
+#include <openssl/kdf.h>
+#endif
+
+#if NJS_HAVE_OPENSSL_EVP_MD_CTX_NEW
+#define njs_evp_md_ctx_new() EVP_MD_CTX_new();
+#define njs_evp_md_ctx_free(_ctx) EVP_MD_CTX_free(_ctx);
+#else
+#define njs_evp_md_ctx_new() EVP_MD_CTX_create();
+#define njs_evp_md_ctx_free(_ctx) EVP_MD_CTX_destroy(_ctx);
+#endif
+
+
+typedef enum {
+ NJS_KEY_FORMAT_RAW = 1 << 1,
+ NJS_KEY_FORMAT_PKCS8 = 1 << 2,
+ NJS_KEY_FORMAT_SPKI = 1 << 3,
+ NJS_KEY_FORMAT_JWK = 1 << 4,
+ NJS_KEY_FORMAT_UNKNOWN = 1 << 5,
+} njs_webcrypto_key_format_t;
+
+
+typedef enum {
+ NJS_KEY_USAGE_DECRYPT = 1 << 1,
+ NJS_KEY_USAGE_DERIVE_BITS = 1 << 2,
+ NJS_KEY_USAGE_DERIVE_KEY = 1 << 3,
+ NJS_KEY_USAGE_ENCRYPT = 1 << 4,
+ NJS_KEY_USAGE_GENERATE_KEY = 1 << 5,
+ NJS_KEY_USAGE_SIGN = 1 << 6,
+ NJS_KEY_USAGE_VERIFY = 1 << 7,
+ NJS_KEY_USAGE_WRAP_KEY = 1 << 8,
+ NJS_KEY_USAGE_UNSUPPORTED = 1 << 9,
+ NJS_KEY_USAGE_UNWRAP_KEY = 1 << 10,
+} njs_webcrypto_key_usage_t;
+
+
+typedef enum {
+ NJS_ALGORITHM_RSA_OAEP,
+ NJS_ALGORITHM_AES_GCM,
+ NJS_ALGORITHM_AES_CTR,
+ NJS_ALGORITHM_AES_CBC,
+ NJS_ALGORITHM_RSASSA_PKCS1_v1_5,
+ NJS_ALGORITHM_RSA_PSS,
+ NJS_ALGORITHM_ECDSA,
+ NJS_ALGORITHM_ECDH,
+ NJS_ALGORITHM_PBKDF2,
+ NJS_ALGORITHM_HKDF,
+ NJS_ALGORITHM_HMAC,
+} njs_webcrypto_alg_t;
+
+
+typedef enum {
+ NJS_HASH_SHA1,
+ NJS_HASH_SHA256,
+ NJS_HASH_SHA384,
+ NJS_HASH_SHA512,
+} njs_webcrypto_hash_t;
+
+
+typedef enum {
+ NJS_CURVE_P256,
+ NJS_CURVE_P384,
+ NJS_CURVE_P521,
+} njs_webcrypto_curve_t;
+
+
+typedef struct {
+ njs_str_t name;
+ uintptr_t value;
+} njs_webcrypto_entry_t;
+
+
+typedef struct {
+ njs_webcrypto_alg_t type;
+ unsigned usage;
+ unsigned fmt;
+} njs_webcrypto_algorithm_t;
+
+
+typedef struct {
+ njs_webcrypto_algorithm_t *alg;
+ unsigned usage;
+ njs_webcrypto_hash_t hash;
+ njs_webcrypto_curve_t curve;
+
+ EVP_PKEY *pkey;
+ njs_str_t raw;
+} njs_webcrypto_key_t;
+
+
+typedef int (*EVP_PKEY_cipher_init_t)(EVP_PKEY_CTX *ctx);
+typedef int (*EVP_PKEY_cipher_t)(EVP_PKEY_CTX *ctx, unsigned char *out,
+ size_t *outlen, const unsigned char *in, size_t inlen);
+
+
+static njs_int_t njs_ext_cipher(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_cipher_pkey(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_index_t encrypt);
+static njs_int_t njs_cipher_aes_gcm(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_value_t *options, njs_bool_t encrypt);
+static njs_int_t njs_cipher_aes_ctr(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_value_t *options, njs_bool_t encrypt);
+static njs_int_t njs_cipher_aes_cbc(njs_vm_t *vm, njs_str_t *data,
+ njs_webcrypto_key_t *key, njs_value_t *options, njs_bool_t encrypt);
+static njs_int_t njs_ext_derive(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t derive_key);
+static njs_int_t njs_ext_digest(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_export_key(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_import_key(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_sign(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t verify);
+static njs_int_t njs_ext_unwrap_key(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_wrap_key(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+static njs_int_t njs_ext_get_random_values(njs_vm_t *vm, njs_value_t *args,
+ njs_uint_t nargs, njs_index_t unused);
+
+static void njs_webcrypto_cleanup_pkey(void *data);
+static njs_webcrypto_key_format_t njs_key_format(njs_vm_t *vm,
+ njs_value_t *value, njs_str_t *format);
+static njs_int_t njs_key_usage(njs_vm_t *vm, njs_value_t *value,
+ unsigned *mask);
+static njs_webcrypto_algorithm_t *njs_key_algorithm(njs_vm_t *vm,
+ njs_value_t *value);
+static njs_str_t *njs_algorithm_string(njs_webcrypto_algorithm_t *algorithm);
+static njs_int_t njs_algorithm_hash(njs_vm_t *vm, njs_value_t *value,
+ njs_webcrypto_hash_t *hash);
+static const EVP_MD *njs_algorithm_hash_digest(njs_webcrypto_hash_t hash);
+static njs_int_t njs_algorithm_curve(njs_vm_t *vm, njs_value_t *value,
+ njs_webcrypto_curve_t *curve);
+
+static njs_int_t njs_webcrypto_result(njs_vm_t *vm, njs_value_t *result,
+ njs_int_t rc);
+static void njs_webcrypto_error(njs_vm_t *vm, const char *fmt, ...);
+
+static njs_webcrypto_entry_t njs_webcrypto_alg[] = {
+
+#define njs_webcrypto_algorithm(type, usage_mask, fmt_mask) \
+ (uintptr_t) & (njs_webcrypto_algorithm_t) { type, usage_mask, fmt_mask }
+
+ {
+ njs_str("RSA-OAEP"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_RSA_OAEP,
+ NJS_KEY_USAGE_ENCRYPT |
+ NJS_KEY_USAGE_DECRYPT |
+ NJS_KEY_USAGE_WRAP_KEY |
+ NJS_KEY_USAGE_UNWRAP_KEY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_PKCS8 |
+ NJS_KEY_FORMAT_SPKI)
+ },
+
+ {
+ njs_str("AES-GCM"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_AES_GCM,
+ NJS_KEY_USAGE_ENCRYPT |
+ NJS_KEY_USAGE_DECRYPT |
+ NJS_KEY_USAGE_WRAP_KEY |
+ NJS_KEY_USAGE_UNWRAP_KEY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_str("AES-CTR"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_AES_CTR,
+ NJS_KEY_USAGE_ENCRYPT |
+ NJS_KEY_USAGE_DECRYPT |
+ NJS_KEY_USAGE_WRAP_KEY |
+ NJS_KEY_USAGE_UNWRAP_KEY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_str("AES-CBC"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_AES_CBC,
+ NJS_KEY_USAGE_ENCRYPT |
+ NJS_KEY_USAGE_DECRYPT |
+ NJS_KEY_USAGE_WRAP_KEY |
+ NJS_KEY_USAGE_UNWRAP_KEY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_str("RSASSA-PKCS1-v1_5"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_RSASSA_PKCS1_v1_5,
+ NJS_KEY_USAGE_SIGN |
+ NJS_KEY_USAGE_VERIFY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_PKCS8 |
+ NJS_KEY_FORMAT_SPKI)
+ },
+
+ {
+ njs_str("RSA-PSS"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_RSA_PSS,
+ NJS_KEY_USAGE_SIGN |
+ NJS_KEY_USAGE_VERIFY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_PKCS8 |
+ NJS_KEY_FORMAT_SPKI)
+ },
+
+ {
+ njs_str("ECDSA"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_ECDSA,
+ NJS_KEY_USAGE_SIGN |
+ NJS_KEY_USAGE_VERIFY |
+ NJS_KEY_USAGE_GENERATE_KEY,
+ NJS_KEY_FORMAT_PKCS8 |
+ NJS_KEY_FORMAT_SPKI)
+ },
+
+ {
+ njs_str("ECDH"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_ECDH,
+ NJS_KEY_USAGE_DERIVE_KEY |
+ NJS_KEY_USAGE_DERIVE_BITS |
+ NJS_KEY_USAGE_GENERATE_KEY |
+ NJS_KEY_USAGE_UNSUPPORTED,
+ NJS_KEY_FORMAT_UNKNOWN)
+ },
+
+ {
+ njs_str("PBKDF2"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_PBKDF2,
+ NJS_KEY_USAGE_DERIVE_KEY |
+ NJS_KEY_USAGE_DERIVE_BITS,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_str("HKDF"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_HKDF,
+ NJS_KEY_USAGE_DERIVE_KEY |
+ NJS_KEY_USAGE_DERIVE_BITS,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_str("HMAC"),
+ njs_webcrypto_algorithm(NJS_ALGORITHM_HMAC,
+ NJS_KEY_USAGE_GENERATE_KEY |
+ NJS_KEY_USAGE_SIGN |
+ NJS_KEY_USAGE_VERIFY,
+ NJS_KEY_FORMAT_RAW)
+ },
+
+ {
+ njs_null_str,
+ 0
+ }
+};
+
+
+static njs_webcrypto_entry_t njs_webcrypto_hash[] = {
+ { njs_str("SHA-256"), NJS_HASH_SHA256 },
+ { njs_str("SHA-384"), NJS_HASH_SHA384 },
+ { njs_str("SHA-512"), NJS_HASH_SHA512 },
+ { njs_str("SHA-1"), NJS_HASH_SHA1 },
+ { njs_null_str, 0 }
+};
+
+
+static njs_webcrypto_entry_t njs_webcrypto_curve[] = {
+ { njs_str("P-256"), NJS_CURVE_P256 },
+ { njs_str("P-384"), NJS_CURVE_P384 },
+ { njs_str("P-521"), NJS_CURVE_P521 },
+ { njs_null_str, 0 }
+};
+
+
+static njs_webcrypto_entry_t njs_webcrypto_usage[] = {
+ { njs_str("decrypt"), NJS_KEY_USAGE_DECRYPT },
+ { njs_str("deriveBits"), NJS_KEY_USAGE_DERIVE_BITS },
+ { njs_str("deriveKey"), NJS_KEY_USAGE_DERIVE_KEY },
+ { njs_str("encrypt"), NJS_KEY_USAGE_ENCRYPT },
+ { njs_str("sign"), NJS_KEY_USAGE_SIGN },
+ { njs_str("unwrapKey"), NJS_KEY_USAGE_UNWRAP_KEY },
+ { njs_str("verify"), NJS_KEY_USAGE_VERIFY },
+ { njs_str("wrapKey"), NJS_KEY_USAGE_WRAP_KEY },
+ { njs_null_str, 0 }
+};
+
+
+static njs_external_t njs_ext_webcrypto_crypto_key[] = {
+
+ {
+ .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+ .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+ .u.property = {
+ .value = "CryptoKey",
+ }
+ },
+};
+
+
+static njs_external_t njs_ext_subtle_webcrypto[] = {
+
+ {
+ .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+ .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+ .u.property = {
+ .value = "SubtleCrypto",
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("decrypt"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_cipher,
+ .magic8 = 0,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("deriveBits"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_derive,
+ .magic8 = 0,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("deriveKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_derive,
+ .magic8 = 1,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("digest"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_digest,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("encrypt"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_cipher,
+ .magic8 = 1,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("exportKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_export_key,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("generateKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_generate_key,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("importKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_import_key,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("sign"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_sign,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("unwrapKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_unwrap_key,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("verify"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_sign,
+ .magic8 = 1,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("wrapKey"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_wrap_key,
+ }
+ },
+
+};
+
+static njs_external_t njs_ext_webcrypto[] = {
+
+ {
+ .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+ .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+ .u.property = {
+ .value = "Crypto",
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_METHOD,
+ .name.string = njs_str("getRandomValues"),
+ .writable = 1,
+ .configurable = 1,
+ .enumerable = 1,
+ .u.method = {
+ .native = njs_ext_get_random_values,
+ }
+ },
+
+ {
+ .flags = NJS_EXTERN_OBJECT,
+ .name.string = njs_str("subtle"),
+ .enumerable = 1,
+ .writable = 1,
+ .u.object = {
+ .enumerable = 1,
+ .properties = njs_ext_subtle_webcrypto,
+ .nproperties = njs_nitems(njs_ext_subtle_webcrypto),
+ }
+ },
+
+};
+
+
+static njs_int_t njs_webcrypto_crypto_key_proto_id;
+
+
+static njs_int_t
+njs_ext_cipher(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+ njs_index_t encrypt)
+{
+ unsigned mask;
+ njs_int_t ret;
+ njs_str_t data;
+ njs_value_t *options;
+ njs_webcrypto_key_t *key;
+ njs_webcrypto_algorithm_t *alg;
+
+ options = njs_arg(args, nargs, 1);
+ alg = njs_key_algorithm(vm, options);
+ if (njs_slow_path(alg == NULL)) {
+ goto fail;
+ }
+
+ key = njs_vm_external(vm, njs_webcrypto_crypto_key_proto_id,
+ njs_arg(args, nargs, 2));
+ if (njs_slow_path(key == NULL)) {
+ njs_type_error(vm, "\"key\" is not a CryptoKey object");
+ goto fail;
+ }
+
+ mask = encrypt ? NJS_KEY_USAGE_ENCRYPT : NJS_KEY_USAGE_DECRYPT;
+ if (njs_slow_path(!(key->usage & mask))) {
+ njs_type_error(vm, "provide key does not support %s operation",
+ encrypt ? "encrypt" : "decrypt");
+ goto fail;
+ }
+
+ if (njs_slow_path(key->alg != alg)) {
+ njs_type_error(vm, "cannot %s using \"%V\" with \"%V\" key",
+ encrypt ? "encrypt" : "decrypt",
+ njs_algorithm_string(key->alg),
+ njs_algorithm_string(alg));
+ goto fail;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &data, njs_arg(args, nargs, 3));
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto fail;
+ }
+
+ switch (alg->type) {
+ case NJS_ALGORITHM_RSA_OAEP:
+ ret = njs_cipher_pkey(vm, &data, key, encrypt);
+ break;
+
+ case NJS_ALGORITHM_AES_GCM:
+ ret = njs_cipher_aes_gcm(vm, &data, key, options, encrypt);
+ break;
+
+ case NJS_ALGORITHM_AES_CTR:
+ ret = njs_cipher_aes_ctr(vm, &data, key, options, encrypt);
+ break;
+
+ case NJS_ALGORITHM_AES_CBC:
+ default:
+ ret = njs_cipher_aes_cbc(vm, &data, key, options, encrypt);
+ }
+
+ return njs_webcrypto_result(vm, njs_vm_retval(vm), ret);
+
+fail:
+
+ return njs_webcrypto_result(vm, njs_vm_retval(vm), NJS_ERROR);
+}
+
+
+static njs_int_t
+njs_cipher_pkey(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key,
+ njs_index_t encrypt)
+{
+ u_char *dst;
+ size_t outlen;
+ njs_int_t ret;
+ const EVP_MD *md;
+ EVP_PKEY_CTX *ctx;
+ EVP_PKEY_cipher_t cipher;
+ EVP_PKEY_cipher_init_t init;
+
+ ctx = EVP_PKEY_CTX_new(key->pkey, NULL);
+ if (njs_slow_path(ctx == NULL)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_CTX_new() failed");
+ return NJS_ERROR;
+ }
+
+ if (encrypt) {
+ init = EVP_PKEY_encrypt_init;
+ cipher = EVP_PKEY_encrypt;
+
+ } else {
+ init = EVP_PKEY_decrypt_init;
+ cipher = EVP_PKEY_decrypt;
+ }
+
+ ret = init(ctx);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_%scrypt_init() failed",
+ encrypt ? "en" : "de");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ md = njs_algorithm_hash_digest(key->hash);
+
+ EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
+ EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md);
+ EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md);
+
+ ret = cipher(ctx, NULL, &outlen, data->start, data->length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_%scrypt() failed",
+ encrypt ? "en" : "de");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ dst = njs_mp_alloc(njs_vm_memory_pool(vm), outlen);
+ if (njs_slow_path(dst == NULL)) {
+ njs_memory_error(vm);
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = cipher(ctx, dst, &outlen, data->start, data->length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_PKEY_%scrypt() failed",
+ encrypt ? "en" : "de");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = njs_vm_value_array_buffer_set(vm, njs_vm_retval(vm), dst, outlen);
+
+fail:
+
+ EVP_PKEY_CTX_free(ctx);
+
+ return ret;
+}
+
+
+static njs_int_t
+njs_cipher_aes_gcm(njs_vm_t *vm, njs_str_t *data, njs_webcrypto_key_t *key,
+ njs_value_t *options, njs_bool_t encrypt)
+{
+ int len, outlen, dstlen;
+ u_char *dst, *p;
+ int64_t taglen;
+ njs_str_t iv, aad;
+ njs_int_t ret;
+ njs_value_t value;
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher;
+
+ static const njs_value_t string_iv = njs_string("iv");
+ static const njs_value_t string_ad = njs_string("additionalData");
+ static const njs_value_t string_tl = njs_string("tagLength");
+
+ switch (key->raw.length) {
+ case 16:
+ cipher = EVP_aes_128_gcm();
+ break;
+
+ case 32:
+ cipher = EVP_aes_256_gcm();
+ break;
+
+ default:
+ njs_type_error(vm, "AES-GCM Invalid key length");
+ return NJS_ERROR;
+ }
+
+ ret = njs_value_property(vm, options, njs_value_arg(&string_iv), &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ njs_type_error(vm, "AES-GCM algorithm.iv is not provided");
+ }
+
+ return NJS_ERROR;
+ }
+
+ ret = njs_vm_value_to_bytes(vm, &iv, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ taglen = 128;
+
+ ret = njs_value_property(vm, options, njs_value_arg(&string_tl), &value);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return NJS_ERROR;
+ }
+
+ if (njs_is_defined(&value)) {
+ ret = njs_value_to_integer(vm, &value, &taglen);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+ }
+
+ if (njs_slow_path(taglen != 32
+ && taglen != 64
+ && taglen != 96
+ && taglen != 104
+ && taglen != 112
+ && taglen != 120
+ && taglen != 128))
+ {
+ njs_type_error(vm, "AES-GCM Invalid tagLength");
+ return NJS_ERROR;
+ }
+
+ taglen /= 8;
+
+ if (njs_slow_path(!encrypt && (data->length < (size_t) taglen))) {
+ njs_type_error(vm, "AES-GCM data is too short");
+ return NJS_ERROR;
+ }
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (njs_slow_path(ctx == NULL)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_new() failed");
+ return NJS_ERROR;
+ }
+
+ ret = EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encrypt);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sInit_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv.length, NULL);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_ctrl() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ ret = EVP_CipherInit_ex(ctx, NULL, NULL, key->raw.start, iv.start,
+ encrypt);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sInit_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ if (!encrypt) {
+ ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, taglen,
+ &data->start[data->length - taglen]);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_ctrl() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+ }
+
+ ret = njs_value_property(vm, options, njs_value_arg(&string_ad), &value);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return NJS_ERROR;
+ }
+
+ aad.length = 0;
+
+ if (njs_is_defined(&value)) {
+ ret = njs_vm_value_to_bytes(vm, &aad, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+ }
+
+ if (aad.length != 0) {
+ ret = EVP_CipherUpdate(ctx, NULL, &outlen, aad.start, aad.length);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sUpdate() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+ }
+
+ dstlen = data->length + EVP_CIPHER_CTX_block_size(ctx) + taglen;
+ dst = njs_mp_alloc(njs_vm_memory_pool(vm), dstlen);
+ if (njs_slow_path(dst == NULL)) {
+ njs_memory_error(vm);
+ return NJS_ERROR;
+ }
+
+ ret = EVP_CipherUpdate(ctx, dst, &outlen, data->start,
+ data->length - (encrypt ? 0 : taglen));
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sUpdate() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ p = &dst[outlen];
+ len = EVP_CIPHER_CTX_block_size(ctx);
+
+ ret = EVP_CipherFinal_ex(ctx, p, &len);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sFinal_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ outlen += len;
+ p += len;
+
+ if (encrypt) {
+ ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, taglen, p);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_ctrl() failed");
+ ret = NJS_ERROR;
+ goto fail;
+ }
+
+ outlen += taglen;
+ }
+
+ ret = njs_vm_value_array_buffer_set(vm, njs_vm_retval(vm), dst, outlen);
+
+fail:
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ return ret;
+}
+
+
+static njs_int_t
+njs_cipher_aes_ctr128(njs_vm_t *vm, const EVP_CIPHER *cipher, u_char *key,
+ u_char *data, size_t dlen, u_char *counter, u_char *dst, int *olen,
+ njs_bool_t encrypt)
+{
+ int len, outlen;
+ njs_int_t ret;
+ EVP_CIPHER_CTX *ctx;
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (njs_slow_path(ctx == NULL)) {
+ njs_webcrypto_error(vm, "EVP_CIPHER_CTX_new() failed");
+ return NJS_ERROR;
+ }
+
+ ret = EVP_CipherInit_ex(ctx, cipher, NULL, key, counter, encrypt);
+ if (njs_slow_path(ret <= 0)) {
+ njs_webcrypto_error(vm, "EVP_%sInit_ex() failed",
+ encrypt ? "Encrypt" : "Decrypt");
+ ret = NJS_ERROR;
More information about the nginx-devel
mailing list