[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