Created
September 21, 2021 20:30
-
-
Save xeioex/2d17327f13ca519750b4f5d6cf224e85 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1632256177 0 | |
| # Tue Sep 21 20:29:37 2021 +0000 | |
| # Node ID cc2b995738cdad13144ced7bfcd316475c14a049 | |
| # Parent 6feba0e602eeaa963aaf878dc6aa84b8ce2a668e | |
| 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 | |
| diff --git a/auto/make b/auto/make | |
| --- a/auto/make | |
| +++ b/auto/make | |
| @@ -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 --git a/auto/openssl b/auto/openssl | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/auto/openssl | |
| @@ -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 --git a/auto/sources b/auto/sources | |
| --- a/auto/sources | |
| +++ b/auto/sources | |
| @@ -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 --git a/auto/summary b/auto/summary | |
| --- a/auto/summary | |
| +++ b/auto/summary | |
| @@ -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 --git a/configure b/configure | |
| --- a/configure | |
| +++ b/configure | |
| @@ -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 --git a/external/njs_webcrypto.c b/external/njs_webcrypto.c | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/external/njs_webcrypto.c | |
| @@ -0,0 +1,2667 @@ | |
| + | |
| +/* | |
| + * 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, | |
| + NJS_KEY_FORMAT_PKCS8 = 2, | |
| + NJS_KEY_FORMAT_SPKI = 4, | |
| + NJS_KEY_FORMAT_JWK = 8, | |
| + NJS_KEY_FORMAT_UNKNOWN = 16, | |
| +} njs_webcrypto_key_format_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_KEY_USAGE_DECRYPT = 1, | |
| + NJS_KEY_USAGE_DERIVE_BITS = 2, | |
| + NJS_KEY_USAGE_DERIVE_KEY = 4, | |
| + NJS_KEY_USAGE_ENCRYPT = 8, | |
| + NJS_KEY_USAGE_GENERATE_KEY = 16, | |
| + NJS_KEY_USAGE_SIGN = 32, | |
| + NJS_KEY_USAGE_VERIFY = 64, | |
| + NJS_KEY_USAGE_WRAP_KEY = 128, | |
| + NJS_KEY_USAGE_UNSUPPORTED = 256, | |
| + NJS_KEY_USAGE_UNWRAP_KEY = 512, | |
| +} njs_webcrypto_key_usage_t; | |
| + | |
| + | |
| +typedef enum { | |
| + NJS_HASH_SHA1 = 0, | |
| + NJS_HASH_SHA256 = 1, | |
| + NJS_HASH_SHA384 = 2, | |
| + NJS_HASH_SHA512 = 3, | |
| +} njs_webcrypto_hash_t; | |
| + | |
| + | |
| +typedef enum { | |
| + NJS_CURVE_P256 = 0, | |
| + NJS_CURVE_P384 = 1, | |
| + NJS_CURVE_P521 = 2, | |
| +} 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; | |
| + goto fail; | |
| + } | |
| + | |
| + ret = EVP_CipherUpdate(ctx, dst, &outlen, data, dlen); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_%sUpdate() failed", | |
| + encrypt ? "Encrypt" : "Decrypt"); | |
| + ret = NJS_ERROR; | |
| + goto fail; | |
| + } | |
| + | |
| + ret = EVP_CipherFinal_ex(ctx, &dst[outlen], &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; | |
| + *olen = outlen; | |
| + | |
| + ret = NJS_OK; | |
| + | |
| +fail: | |
| + | |
| + EVP_CIPHER_CTX_free(ctx); | |
| + | |
| + return ret; | |
| +} | |
| + | |
| + | |
| +njs_inline njs_uint_t | |
| +njs_ceil_div(njs_uint_t dend, njs_uint_t dsor) | |
| +{ | |
| + return (dsor == 0) ? 0 : 1 + (dend - 1) / dsor; | |
| +} | |
| + | |
| + | |
| +njs_inline BIGNUM * | |
| +njs_bn_counter128(njs_str_t *ctr, njs_uint_t bits) | |
| +{ | |
| + njs_uint_t remainder, bytes; | |
| + uint8_t buf[16]; | |
| + | |
| + remainder = bits % 8; | |
| + | |
| + if (remainder == 0) { | |
| + bytes = bits / 8; | |
| + | |
| + return BN_bin2bn(&ctr->start[ctr->length - bytes], bytes, NULL); | |
| + } | |
| + | |
| + bytes = njs_ceil_div(bits, 8); | |
| + | |
| + memcpy(buf, &ctr->start[ctr->length - bytes], bytes); | |
| + | |
| + buf[0] &= ~(0xFF << remainder); | |
| + | |
| + return BN_bin2bn(buf, bytes, NULL); | |
| +} | |
| + | |
| + | |
| +njs_inline void | |
| +njs_counter128_reset(u_char *src, u_char *dst, njs_uint_t bits) | |
| +{ | |
| + size_t index; | |
| + njs_uint_t remainder, bytes; | |
| + | |
| + bytes = bits / 8; | |
| + remainder = bits % 8; | |
| + | |
| + memcpy(dst, src, 16); | |
| + | |
| + index = 16 - bytes; | |
| + | |
| + memset(&dst[index], 0, bytes); | |
| + | |
| + if (remainder) { | |
| + dst[index - 1] &= 0xff << remainder; | |
| + } | |
| +} | |
| + | |
| + | |
| +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) | |
| +{ | |
| + int len, len2; | |
| + u_char *dst; | |
| + int64_t length; | |
| + BIGNUM *total, *blocks, *left, *ctr; | |
| + njs_int_t ret; | |
| + njs_str_t iv; | |
| + njs_uint_t size1; | |
| + njs_value_t value; | |
| + const EVP_CIPHER *cipher; | |
| + u_char iv2[16]; | |
| + | |
| + static const njs_value_t string_counter = njs_string("counter"); | |
| + static const njs_value_t string_length = njs_string("length"); | |
| + | |
| + switch (key->raw.length) { | |
| + case 16: | |
| + cipher = EVP_aes_128_ctr(); | |
| + break; | |
| + | |
| + case 32: | |
| + cipher = EVP_aes_256_ctr(); | |
| + break; | |
| + | |
| + default: | |
| + njs_type_error(vm, "AES-CTR Invalid key length"); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ret = njs_value_property(vm, options, njs_value_arg(&string_counter), | |
| + &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + if (ret == NJS_DECLINED) { | |
| + njs_type_error(vm, "AES-CTR algorithm.counter 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; | |
| + } | |
| + | |
| + if (njs_slow_path(iv.length != 16)) { | |
| + njs_type_error(vm, "AES-CTR algorithm.counter must be 16 bytes long"); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ret = njs_value_property(vm, options, njs_value_arg(&string_length), | |
| + &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + if (ret == NJS_DECLINED) { | |
| + njs_type_error(vm, "AES-CTR algorithm.length is not provided"); | |
| + } | |
| + | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ret = njs_value_to_integer(vm, &value, &length); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + if (njs_slow_path(length == 0 || length > 128)) { | |
| + njs_type_error(vm, "AES-CTR algorithm.length " | |
| + "must be between 1 and 128"); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ctr = NULL; | |
| + blocks = NULL; | |
| + left = NULL; | |
| + | |
| + total = BN_new(); | |
| + if (njs_slow_path(total == NULL)) { | |
| + njs_webcrypto_error(vm, "BN_new() failed"); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ret = BN_lshift(total, BN_value_one(), length); | |
| + if (njs_slow_path(ret != 1)) { | |
| + njs_webcrypto_error(vm, "BN_lshift() failed"); | |
| + ret = NJS_ERROR; | |
| + goto fail; | |
| + } | |
| + | |
| + ctr = njs_bn_counter128(&iv, length); | |
| + if (njs_slow_path(ctr == NULL)) { | |
| + njs_webcrypto_error(vm, "BN_bin2bn() failed"); | |
| + ret = NJS_ERROR; | |
| + goto fail; | |
| + } | |
| + | |
| + blocks = BN_new(); | |
| + if (njs_slow_path(blocks == NULL)) { | |
| + njs_webcrypto_error(vm, "BN_new() failed"); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ret = BN_set_word(blocks, njs_ceil_div(data->length, AES_BLOCK_SIZE)); | |
| + if (njs_slow_path(ret != 1)) { | |
| + njs_webcrypto_error(vm, "BN_set_word() failed"); | |
| + ret = NJS_ERROR; | |
| + goto fail; | |
| + } | |
| + | |
| + ret = BN_cmp(blocks, total); | |
| + if (njs_slow_path(ret > 0)) { | |
| + njs_type_error(vm, "AES-CTR repeated counter"); | |
| + ret = NJS_ERROR; | |
| + goto fail; | |
| + } | |
| + | |
| + left = BN_new(); | |
| + if (njs_slow_path(left == NULL)) { | |
| + njs_webcrypto_error(vm, "BN_new() failed"); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ret = BN_sub(left, total, ctr); | |
| + if (njs_slow_path(ret != 1)) { | |
| + njs_webcrypto_error(vm, "BN_sub() failed"); | |
| + ret = NJS_ERROR; | |
| + goto fail; | |
| + } | |
| + | |
| + dst = njs_mp_alloc(njs_vm_memory_pool(vm), | |
| + data->length + EVP_MAX_BLOCK_LENGTH); | |
| + if (njs_slow_path(dst == NULL)) { | |
| + njs_memory_error(vm); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ret = BN_cmp(left, blocks); | |
| + if (ret >= 0) { | |
| + | |
| + /* | |
| + * Doing a single run if a counter is not wrapped-around | |
| + * during the ciphering. | |
| + * */ | |
| + | |
| + ret = njs_cipher_aes_ctr128(vm, cipher, key->raw.start, | |
| + data->start, data->length, iv.start, dst, | |
| + &len, encrypt); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + goto done; | |
| + } | |
| + | |
| + /* | |
| + * Otherwise splitting ciphering into two parts: | |
| + * Until the wrapping moment | |
| + * After the resetting counter to zero. | |
| + */ | |
| + | |
| + size1 = BN_get_word(left) * AES_BLOCK_SIZE; | |
| + | |
| + ret = njs_cipher_aes_ctr128(vm, cipher, key->raw.start, data->start, size1, | |
| + iv.start, dst, &len, encrypt); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + njs_counter128_reset(iv.start, (u_char *) iv2, length); | |
| + | |
| + ret = njs_cipher_aes_ctr128(vm, cipher, key->raw.start, &data->start[size1], | |
| + data->length - size1, iv2, &dst[size1], &len2, | |
| + encrypt); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + len += len2; | |
| + | |
| +done: | |
| + | |
| + ret = njs_vm_value_array_buffer_set(vm, njs_vm_retval(vm), dst, len); | |
| + | |
| +fail: | |
| + | |
| + BN_free(total); | |
| + | |
| + if (ctr != NULL) { | |
| + BN_free(ctr); | |
| + } | |
| + | |
| + if (blocks != NULL) { | |
| + BN_free(blocks); | |
| + } | |
| + | |
| + if (left != NULL) { | |
| + BN_free(left); | |
| + } | |
| + | |
| + return ret; | |
| +} | |
| + | |
| + | |
| +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) | |
| +{ | |
| + int olen_max, olen, olen2; | |
| + u_char *dst; | |
| + unsigned remainder; | |
| + njs_str_t iv; | |
| + 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"); | |
| + | |
| + switch (key->raw.length) { | |
| + case 16: | |
| + cipher = EVP_aes_128_cbc(); | |
| + break; | |
| + | |
| + case 32: | |
| + cipher = EVP_aes_256_cbc(); | |
| + break; | |
| + | |
| + default: | |
| + njs_type_error(vm, "AES-CBC 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-CBC 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; | |
| + } | |
| + | |
| + if (njs_slow_path(iv.length != 16)) { | |
| + njs_type_error(vm, "AES-CBC algorithm.iv must be 16 bytes long"); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + olen_max = data->length + AES_BLOCK_SIZE - 1; | |
| + remainder = olen_max % AES_BLOCK_SIZE; | |
| + | |
| + if (remainder != 0) { | |
| + olen_max += AES_BLOCK_SIZE - remainder; | |
| + } | |
| + | |
| + 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->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; | |
| + } | |
| + | |
| + dst = njs_mp_alloc(njs_vm_memory_pool(vm), olen_max); | |
| + if (njs_slow_path(dst == NULL)) { | |
| + njs_memory_error(vm); | |
| + ret = NJS_ERROR; | |
| + goto fail; | |
| + } | |
| + | |
| + ret = EVP_CipherUpdate(ctx, dst, &olen, data->start, data->length); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_%SUpdate() failed", | |
| + encrypt ? "Encrypt" : "Decrypt"); | |
| + ret = NJS_ERROR; | |
| + goto fail; | |
| + } | |
| + | |
| + ret = EVP_CipherFinal_ex(ctx, &dst[olen], &olen2); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_%sFinal_ex() failed", | |
| + encrypt ? "Encrypt" : "Decrypt"); | |
| + ret = NJS_ERROR; | |
| + goto fail; | |
| + } | |
| + | |
| + olen += olen2; | |
| + | |
| + ret = njs_vm_value_array_buffer_set(vm, njs_vm_retval(vm), dst, olen); | |
| + | |
| +fail: | |
| + | |
| + EVP_CIPHER_CTX_free(ctx); | |
| + | |
| + return ret; | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_ext_derive(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
| + njs_index_t derive_key) | |
| +{ | |
| + u_char *k; | |
| + size_t olen; | |
| + int64_t iterations, length; | |
| + EVP_PKEY *pkey; | |
| + unsigned usage, mask; | |
| + njs_int_t ret; | |
| + njs_str_t salt, info; | |
| + njs_value_t value, *aobject, *dobject; | |
| + const EVP_MD *md; | |
| + EVP_PKEY_CTX *pctx; | |
| + njs_mp_cleanup_t *cln; | |
| + njs_webcrypto_key_t *key, *dkey; | |
| + njs_webcrypto_hash_t hash; | |
| + njs_webcrypto_algorithm_t *alg, *dalg; | |
| + | |
| + static const njs_value_t string_info = njs_string("info"); | |
| + static const njs_value_t string_salt = njs_string("salt"); | |
| + static const njs_value_t string_length = njs_string("length"); | |
| + static const njs_value_t string_iterations = njs_string("iterations"); | |
| + | |
| + aobject = njs_arg(args, nargs, 1); | |
| + alg = njs_key_algorithm(vm, aobject); | |
| + 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, "\"baseKey\" is not a CryptoKey object"); | |
| + goto fail; | |
| + } | |
| + | |
| + mask = derive_key ? NJS_KEY_USAGE_DERIVE_KEY : NJS_KEY_USAGE_DERIVE_BITS; | |
| + if (njs_slow_path(!(key->usage & mask))) { | |
| + njs_type_error(vm, "provide key does not support \"%s\" operation", | |
| + derive_key ? "deriveKey" : "deriveBits"); | |
| + goto fail; | |
| + } | |
| + | |
| + if (njs_slow_path(key->alg != alg)) { | |
| + njs_type_error(vm, "cannot derive %s using \"%V\" with \"%V\" key", | |
| + derive_key ? "key" : "bits", | |
| + njs_algorithm_string(key->alg), | |
| + njs_algorithm_string(alg)); | |
| + goto fail; | |
| + } | |
| + | |
| + dobject = njs_arg(args, nargs, 3); | |
| + | |
| + if (derive_key) { | |
| + dalg = njs_key_algorithm(vm, dobject); | |
| + if (njs_slow_path(dalg == NULL)) { | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_value_property(vm, dobject, njs_value_arg(&string_length), | |
| + &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + if (ret == NJS_DECLINED) { | |
| + njs_type_error(vm, "derivedKeyAlgorithm.length " | |
| + "is not provided"); | |
| + goto fail; | |
| + } | |
| + } | |
| + | |
| + } else { | |
| + dalg = NULL; | |
| + njs_value_assign(&value, dobject); | |
| + } | |
| + | |
| + ret = njs_value_to_integer(vm, &value, &length); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + dkey = NULL; | |
| + length /= 8; | |
| + | |
| + if (derive_key) { | |
| + switch (dalg->type) { | |
| + case NJS_ALGORITHM_AES_GCM: | |
| + case NJS_ALGORITHM_AES_CTR: | |
| + case NJS_ALGORITHM_AES_CBC: | |
| + | |
| + if (length != 16 && length != 32) { | |
| + njs_type_error(vm, "deriveKey \"%V\" length must be 128 or 256", | |
| + njs_algorithm_string(dalg)); | |
| + goto fail; | |
| + } | |
| + | |
| + break; | |
| + | |
| + default: | |
| + njs_internal_error(vm, "not implemented deriveKey: \"%V\"", | |
| + njs_algorithm_string(dalg)); | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_key_usage(vm, njs_arg(args, nargs, 5), &usage); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + if (njs_slow_path(usage & ~dalg->usage)) { | |
| + njs_type_error(vm, "unsupported key usage for \"%V\" key", | |
| + njs_algorithm_string(alg)); | |
| + goto fail; | |
| + } | |
| + | |
| + dkey = njs_mp_zalloc(njs_vm_memory_pool(vm), | |
| + sizeof(njs_webcrypto_key_t)); | |
| + if (njs_slow_path(key == NULL)) { | |
| + njs_memory_error(vm); | |
| + goto fail; | |
| + } | |
| + | |
| + dkey->alg = dalg; | |
| + dkey->usage = usage; | |
| + } | |
| + | |
| + k = njs_mp_zalloc(njs_vm_memory_pool(vm), length); | |
| + if (njs_slow_path(k == NULL)) { | |
| + njs_memory_error(vm); | |
| + goto fail; | |
| + } | |
| + | |
| + switch (alg->type) { | |
| + case NJS_ALGORITHM_PBKDF2: | |
| + ret = njs_algorithm_hash(vm, aobject, &hash); | |
| + if (njs_slow_path(ret == NJS_ERROR)) { | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_value_property(vm, aobject, njs_value_arg(&string_salt), | |
| + &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + if (ret == NJS_DECLINED) { | |
| + njs_type_error(vm, "PBKDF2 algorithm.salt is not provided"); | |
| + } | |
| + | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_vm_value_to_bytes(vm, &salt, &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + if (njs_slow_path(salt.length < 16)) { | |
| + njs_type_error(vm, "PBKDF2 algorithm.salt must be " | |
| + "at least 16 bytes long"); | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_value_property(vm, aobject, njs_value_arg(&string_iterations), | |
| + &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + if (ret == NJS_DECLINED) { | |
| + njs_type_error(vm, "PBKDF2 algorithm.iterations " | |
| + "is not provided"); | |
| + } | |
| + | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_value_to_integer(vm, &value, &iterations); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + md = njs_algorithm_hash_digest(hash); | |
| + | |
| + ret = PKCS5_PBKDF2_HMAC((char *) key->raw.start, key->raw.length, | |
| + salt.start, salt.length, iterations, md, | |
| + length, k); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "PKCS5_PBKDF2_HMAC() failed"); | |
| + goto fail; | |
| + } | |
| + break; | |
| + | |
| + case NJS_ALGORITHM_HKDF: | |
| +#ifdef NJS_HAVE_OPENSSL_HKDF | |
| + ret = njs_algorithm_hash(vm, aobject, &hash); | |
| + if (njs_slow_path(ret == NJS_ERROR)) { | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_value_property(vm, aobject, njs_value_arg(&string_salt), | |
| + &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + if (ret == NJS_DECLINED) { | |
| + njs_type_error(vm, "HKDF algorithm.salt is not provided"); | |
| + } | |
| + | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_vm_value_to_bytes(vm, &salt, &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_value_property(vm, aobject, njs_value_arg(&string_info), | |
| + &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + if (ret == NJS_DECLINED) { | |
| + njs_type_error(vm, "HKDF algorithm.info is not provided"); | |
| + } | |
| + | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_vm_value_to_bytes(vm, &info, &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); | |
| + if (njs_slow_path(pctx == NULL)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_CTX_new_id() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + ret = EVP_PKEY_derive_init(pctx); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_derive_init() failed"); | |
| + goto free; | |
| + } | |
| + | |
| + md = njs_algorithm_hash_digest(hash); | |
| + | |
| + ret = EVP_PKEY_CTX_set_hkdf_md(pctx, md); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_CTX_set_hkdf_md() failed"); | |
| + goto free; | |
| + } | |
| + | |
| + ret = EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt.start, salt.length); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_CTX_set1_hkdf_salt() failed"); | |
| + goto free; | |
| + } | |
| + | |
| + ret = EVP_PKEY_CTX_set1_hkdf_key(pctx, key->raw.start, key->raw.length); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_CTX_set1_hkdf_key() failed"); | |
| + goto free; | |
| + } | |
| + | |
| + ret = EVP_PKEY_CTX_add1_hkdf_info(pctx, info.start, info.length); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_CTX_add1_hkdf_info() failed"); | |
| + goto free; | |
| + } | |
| + | |
| + olen = (size_t) length; | |
| + ret = EVP_PKEY_derive(pctx, k, &olen); | |
| + if (njs_slow_path(ret <= 0 || olen != (size_t) length)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_derive() failed"); | |
| + goto free; | |
| + } | |
| + | |
| +free: | |
| + | |
| + EVP_PKEY_CTX_free(pctx); | |
| + | |
| + if (njs_slow_path(ret <= 0)) { | |
| + goto fail; | |
| + } | |
| + | |
| + break; | |
| +#else | |
| + (void) pctx; | |
| + (void) olen; | |
| + (void) &string_info; | |
| + (void) &info; | |
| +#endif | |
| + | |
| + case NJS_ALGORITHM_ECDH: | |
| + default: | |
| + njs_internal_error(vm, "not implemented deriveKey " | |
| + "algorithm: \"%V\"", njs_algorithm_string(alg)); | |
| + goto fail; | |
| + } | |
| + | |
| + if (derive_key) { | |
| + if (dalg->type == NJS_ALGORITHM_HMAC) { | |
| + ret = njs_algorithm_hash(vm, dobject, &dkey->hash); | |
| + if (njs_slow_path(ret == NJS_ERROR)) { | |
| + goto fail; | |
| + } | |
| + | |
| + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, k, length); | |
| + if (njs_slow_path(pkey == NULL)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_new_mac_key() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + cln = njs_mp_cleanup_add(njs_vm_memory_pool(vm), 0); | |
| + if (cln == NULL) { | |
| + njs_memory_error(vm); | |
| + goto fail; | |
| + } | |
| + | |
| + cln->handler = njs_webcrypto_cleanup_pkey; | |
| + cln->data = key; | |
| + | |
| + dkey->pkey = pkey; | |
| + | |
| + } else { | |
| + dkey->raw.start = k; | |
| + dkey->raw.length = length; | |
| + } | |
| + | |
| + ret = njs_vm_external_create(vm, &value, | |
| + njs_webcrypto_crypto_key_proto_id, | |
| + dkey, 0); | |
| + } else { | |
| + ret = njs_vm_value_array_buffer_set(vm, &value, k, length); | |
| + } | |
| + | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + return njs_webcrypto_result(vm, &value, NJS_OK); | |
| + | |
| +fail: | |
| + | |
| + return njs_webcrypto_result(vm, njs_vm_retval(vm), NJS_ERROR); | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_ext_digest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
| + njs_index_t unused) | |
| +{ | |
| + unsigned olen; | |
| + u_char *dst; | |
| + njs_str_t data; | |
| + njs_int_t ret; | |
| + njs_value_t value; | |
| + const EVP_MD *md; | |
| + njs_webcrypto_hash_t hash; | |
| + | |
| + ret = njs_algorithm_hash(vm, njs_arg(args, nargs, 1), &hash); | |
| + if (njs_slow_path(ret == NJS_ERROR)) { | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_vm_value_to_bytes(vm, &data, njs_arg(args, nargs, 2)); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + md = njs_algorithm_hash_digest(hash); | |
| + olen = EVP_MD_size(md); | |
| + | |
| + dst = njs_mp_zalloc(njs_vm_memory_pool(vm), olen); | |
| + if (njs_slow_path(dst == NULL)) { | |
| + njs_memory_error(vm); | |
| + goto fail; | |
| + } | |
| + | |
| + ret = EVP_Digest(data.start, data.length, dst, &olen, md, NULL); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_Digest() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_vm_value_array_buffer_set(vm, &value, dst, olen); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + return njs_webcrypto_result(vm, &value, NJS_OK); | |
| + | |
| +fail: | |
| + | |
| + return njs_webcrypto_result(vm, njs_vm_retval(vm), NJS_ERROR); | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_ext_export_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
| + njs_index_t unused) | |
| +{ | |
| + njs_internal_error(vm, "\"exportKey\" not implemented"); | |
| + return NJS_ERROR; | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
| + njs_index_t unused) | |
| +{ | |
| + njs_internal_error(vm, "\"generateKey\" not implemented"); | |
| + return NJS_ERROR; | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_ext_import_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
| + njs_index_t unused) | |
| +{ | |
| + int nid; | |
| + BIO *bio; | |
| + RSA *rsa; | |
| + EC_KEY *ec; | |
| + unsigned usage; | |
| + EVP_PKEY *pkey; | |
| + njs_int_t ret; | |
| + njs_str_t key_data, format; | |
| + njs_value_t value, *options; | |
| + const u_char *start; | |
| + const EC_GROUP *group; | |
| + njs_mp_cleanup_t *cln; | |
| + njs_webcrypto_key_t *key; | |
| + PKCS8_PRIV_KEY_INFO *pkcs8; | |
| + njs_webcrypto_algorithm_t *alg; | |
| + njs_webcrypto_key_format_t fmt; | |
| + | |
| + static const int curves[] = { | |
| + NID_X9_62_prime256v1, | |
| + NID_secp384r1, | |
| + NID_secp521r1, | |
| + }; | |
| + | |
| + pkey = NULL; | |
| + | |
| + fmt = njs_key_format(vm, njs_arg(args, nargs, 1), &format); | |
| + if (njs_slow_path(fmt == NJS_KEY_FORMAT_UNKNOWN)) { | |
| + njs_type_error(vm, "unknown key format: \"%V\"", &format); | |
| + goto fail; | |
| + } | |
| + | |
| + options = njs_arg(args, nargs, 3); | |
| + alg = njs_key_algorithm(vm, options); | |
| + if (njs_slow_path(alg == NULL)) { | |
| + goto fail; | |
| + } | |
| + | |
| + if (njs_slow_path(!(fmt & alg->fmt))) { | |
| + njs_type_error(vm, "unsupported key fmt for \"%V\" key", | |
| + njs_algorithm_string(alg)); | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_key_usage(vm, njs_arg(args, nargs, 5), &usage); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + if (njs_slow_path(usage & ~alg->usage)) { | |
| + njs_type_error(vm, "unsupported key usage for \"%V\" key", | |
| + njs_algorithm_string(alg)); | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_vm_value_to_bytes(vm, &key_data, njs_arg(args, nargs, 2)); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + start = key_data.start; | |
| + | |
| + switch (fmt) { | |
| + case NJS_KEY_FORMAT_PKCS8: | |
| + bio = BIO_new_mem_buf(start, key_data.length); | |
| + if (njs_slow_path(bio == NULL)) { | |
| + njs_webcrypto_error(vm, "BIO_new_mem_buf() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + pkcs8 = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL); | |
| + if (njs_slow_path(pkcs8 == NULL)) { | |
| + BIO_free(bio); | |
| + njs_webcrypto_error(vm, "d2i_PKCS8_PRIV_KEY_INFO_bio() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + pkey = EVP_PKCS82PKEY(pkcs8); | |
| + if (njs_slow_path(pkey == NULL)) { | |
| + PKCS8_PRIV_KEY_INFO_free(pkcs8); | |
| + BIO_free(bio); | |
| + njs_webcrypto_error(vm, "EVP_PKCS82PKEY() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + PKCS8_PRIV_KEY_INFO_free(pkcs8); | |
| + BIO_free(bio); | |
| + | |
| + break; | |
| + | |
| + case NJS_KEY_FORMAT_SPKI: | |
| + pkey = d2i_PUBKEY(NULL, &start, key_data.length); | |
| + if (njs_slow_path(pkey == NULL)) { | |
| + njs_webcrypto_error(vm, "d2i_PUBKEY() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + break; | |
| + | |
| + case NJS_KEY_FORMAT_RAW: | |
| + break; | |
| + | |
| + default: | |
| + njs_internal_error(vm, "not implemented key format: \"%V\"", &format); | |
| + goto fail; | |
| + } | |
| + | |
| + key = njs_mp_zalloc(njs_vm_memory_pool(vm), sizeof(njs_webcrypto_key_t)); | |
| + if (njs_slow_path(key == NULL)) { | |
| + njs_memory_error(vm); | |
| + goto fail; | |
| + } | |
| + | |
| + key->alg = alg; | |
| + key->usage = usage; | |
| + | |
| + switch (alg->type) { | |
| + case NJS_ALGORITHM_RSA_OAEP: | |
| + case NJS_ALGORITHM_RSA_PSS: | |
| + case NJS_ALGORITHM_RSASSA_PKCS1_v1_5: | |
| + rsa = EVP_PKEY_get1_RSA(pkey); | |
| + if (njs_slow_path(rsa == NULL)) { | |
| + njs_webcrypto_error(vm, "RSA key is not found"); | |
| + goto fail; | |
| + } | |
| + | |
| + RSA_free(rsa); | |
| + | |
| + ret = njs_algorithm_hash(vm, options, &key->hash); | |
| + if (njs_slow_path(ret == NJS_ERROR)) { | |
| + goto fail; | |
| + } | |
| + | |
| + key->pkey = pkey; | |
| + | |
| + break; | |
| + | |
| + case NJS_ALGORITHM_ECDSA: | |
| + case NJS_ALGORITHM_ECDH: | |
| + ec = EVP_PKEY_get1_EC_KEY(pkey); | |
| + if (njs_slow_path(ec == NULL)) { | |
| + njs_webcrypto_error(vm, "EC key is not found"); | |
| + goto fail; | |
| + } | |
| + | |
| + group = EC_KEY_get0_group(ec); | |
| + nid = EC_GROUP_get_curve_name(group); | |
| + EC_KEY_free(ec); | |
| + | |
| + ret = njs_algorithm_curve(vm, options, &key->curve); | |
| + if (njs_slow_path(ret == NJS_ERROR)) { | |
| + goto fail; | |
| + } | |
| + | |
| + if (njs_slow_path(curves[key->curve] != nid)) { | |
| + njs_webcrypto_error(vm, "name curve mismatch"); | |
| + goto fail; | |
| + } | |
| + | |
| + key->pkey = pkey; | |
| + | |
| + break; | |
| + | |
| + case NJS_ALGORITHM_HMAC: | |
| + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key_data.start, | |
| + key_data.length); | |
| + if (njs_slow_path(pkey == NULL)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_new_mac_key() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_algorithm_hash(vm, options, &key->hash); | |
| + if (njs_slow_path(ret == NJS_ERROR)) { | |
| + goto fail; | |
| + } | |
| + | |
| + key->pkey = pkey; | |
| + | |
| + break; | |
| + | |
| + case NJS_ALGORITHM_AES_GCM: | |
| + case NJS_ALGORITHM_AES_CTR: | |
| + case NJS_ALGORITHM_AES_CBC: | |
| + case NJS_ALGORITHM_PBKDF2: | |
| + case NJS_ALGORITHM_HKDF: | |
| + key->raw = key_data; | |
| + default: | |
| + break; | |
| + } | |
| + | |
| + if (pkey != NULL) { | |
| + cln = njs_mp_cleanup_add(njs_vm_memory_pool(vm), 0); | |
| + if (cln == NULL) { | |
| + njs_memory_error(vm); | |
| + goto fail; | |
| + } | |
| + | |
| + cln->handler = njs_webcrypto_cleanup_pkey; | |
| + cln->data = key; | |
| + pkey = NULL; | |
| + } | |
| + | |
| + ret = njs_vm_external_create(vm, &value, njs_webcrypto_crypto_key_proto_id, | |
| + key, 0); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + return njs_webcrypto_result(vm, &value, NJS_OK); | |
| + | |
| +fail: | |
| + | |
| + if (pkey != NULL) { | |
| + EVP_PKEY_free(pkey); | |
| + } | |
| + | |
| + return njs_webcrypto_result(vm, njs_vm_retval(vm), NJS_ERROR); | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_set_rsa_padding(njs_vm_t *vm, njs_value_t *options, EVP_PKEY *pkey, | |
| + EVP_PKEY_CTX *ctx, njs_webcrypto_alg_t type) | |
| +{ | |
| + int padding; | |
| + int64_t salt_length; | |
| + njs_int_t ret; | |
| + njs_value_t value; | |
| + | |
| + static const njs_value_t string_saltl = njs_string("saltLength"); | |
| + | |
| + if (type == NJS_ALGORITHM_ECDSA) { | |
| + return NJS_OK; | |
| + } | |
| + | |
| + padding = (type == NJS_ALGORITHM_RSA_PSS) ? RSA_PKCS1_PSS_PADDING | |
| + : RSA_PKCS1_PADDING; | |
| + ret = EVP_PKEY_CTX_set_rsa_padding(ctx, padding); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_CTX_set_rsa_padding() failed"); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + if (padding == RSA_PKCS1_PSS_PADDING) { | |
| + ret = njs_value_property(vm, options, njs_value_arg(&string_saltl), | |
| + &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + if (ret == NJS_DECLINED) { | |
| + njs_type_error(vm, "RSA-PSS algorithm.saltLength " | |
| + "is not provided"); | |
| + } | |
| + | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ret = njs_value_to_integer(vm, &value, &salt_length); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ret = EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, salt_length); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, | |
| + "EVP_PKEY_CTX_set_rsa_pss_saltlen() failed"); | |
| + return NJS_ERROR; | |
| + } | |
| + } | |
| + | |
| + return NJS_OK; | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_ext_sign(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
| + njs_index_t verify) | |
| +{ | |
| + u_char *dst; | |
| + size_t olen, outlen; | |
| + unsigned mask, m_len; | |
| + njs_int_t ret; | |
| + njs_str_t data, sig; | |
| + EVP_MD_CTX *mctx; | |
| + njs_value_t value, *options; | |
| + EVP_PKEY_CTX *pctx; | |
| + const EVP_MD *md; | |
| + njs_webcrypto_key_t *key; | |
| + njs_webcrypto_hash_t hash; | |
| + njs_webcrypto_algorithm_t *alg; | |
| + unsigned char m[EVP_MAX_MD_SIZE]; | |
| + | |
| + mctx = NULL; | |
| + pctx = NULL; | |
| + | |
| + 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 = verify ? NJS_KEY_USAGE_VERIFY : NJS_KEY_USAGE_SIGN; | |
| + if (njs_slow_path(!(key->usage & mask))) { | |
| + njs_type_error(vm, "provide key does not support \"sign\" operation"); | |
| + goto fail; | |
| + } | |
| + | |
| + if (njs_slow_path(key->alg != alg)) { | |
| + njs_type_error(vm, "cannot %s using \"%V\" with \"%V\" key", | |
| + verify ? "verify" : "sign", | |
| + njs_algorithm_string(key->alg), | |
| + njs_algorithm_string(alg)); | |
| + goto fail; | |
| + } | |
| + | |
| + if (verify) { | |
| + ret = njs_vm_value_to_bytes(vm, &sig, njs_arg(args, nargs, 3)); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + ret = njs_vm_value_to_bytes(vm, &data, njs_arg(args, nargs, 4)); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + } else { | |
| + ret = njs_vm_value_to_bytes(vm, &data, njs_arg(args, nargs, 3)); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + } | |
| + | |
| + mctx = njs_evp_md_ctx_new(); | |
| + if (njs_slow_path(mctx == NULL)) { | |
| + njs_webcrypto_error(vm, "njs_evp_md_ctx_new() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + if (alg->type == NJS_ALGORITHM_ECDSA) { | |
| + ret = njs_algorithm_hash(vm, options, &hash); | |
| + if (njs_slow_path(ret == NJS_ERROR)) { | |
| + goto fail; | |
| + } | |
| + | |
| + } else { | |
| + hash = key->hash; | |
| + } | |
| + | |
| + md = njs_algorithm_hash_digest(hash); | |
| + | |
| + ret = EVP_DigestSignInit(mctx, NULL, md, NULL, key->pkey); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_DigestSignInit() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + ret = EVP_DigestSignUpdate(mctx, data.start, data.length); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_DigestSignUpdate() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + outlen = 0; | |
| + | |
| + switch (alg->type) { | |
| + case NJS_ALGORITHM_HMAC: | |
| + olen = EVP_MD_size(md); | |
| + | |
| + if (!verify) { | |
| + dst = njs_mp_zalloc(njs_vm_memory_pool(vm), olen); | |
| + if (njs_slow_path(dst == NULL)) { | |
| + njs_memory_error(vm); | |
| + goto fail; | |
| + } | |
| + | |
| + } else { | |
| + dst = (u_char *) &m[0]; | |
| + } | |
| + | |
| + ret = EVP_DigestSignFinal(mctx, dst, &outlen); | |
| + if (njs_slow_path(ret <= 0 || olen != outlen)) { | |
| + njs_webcrypto_error(vm, "EVP_DigestSignFinal() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + if (verify) { | |
| + ret = (sig.length == outlen && memcmp(sig.start, dst, outlen) == 0); | |
| + } | |
| + | |
| + break; | |
| + | |
| + case NJS_ALGORITHM_RSASSA_PKCS1_v1_5: | |
| + case NJS_ALGORITHM_RSA_PSS: | |
| + case NJS_ALGORITHM_ECDSA: | |
| + default: | |
| + ret = EVP_DigestFinal_ex(mctx, m, &m_len); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_DigestFinal_ex() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + olen = EVP_PKEY_size(key->pkey); | |
| + dst = njs_mp_zalloc(njs_vm_memory_pool(vm), olen); | |
| + if (njs_slow_path(dst == NULL)) { | |
| + njs_memory_error(vm); | |
| + goto fail; | |
| + } | |
| + | |
| + pctx = EVP_PKEY_CTX_new(key->pkey, NULL); | |
| + if (njs_slow_path(pctx == NULL)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_CTX_new() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + if (!verify) { | |
| + ret = EVP_PKEY_sign_init(pctx); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_sign_init() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + } else { | |
| + ret = EVP_PKEY_verify_init(pctx); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_verify_init() failed"); | |
| + goto fail; | |
| + } | |
| + } | |
| + | |
| + ret = njs_set_rsa_padding(vm, options, key->pkey, pctx, alg->type); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + ret = EVP_PKEY_CTX_set_signature_md(pctx, md); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_CTX_set_signature_md() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + if (!verify) { | |
| + outlen = olen; | |
| + ret = EVP_PKEY_sign(pctx, dst, &outlen, m, m_len); | |
| + if (njs_slow_path(ret <= 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_sign() failed"); | |
| + goto fail; | |
| + } | |
| + | |
| + } else { | |
| + ret = EVP_PKEY_verify(pctx, sig.start, sig.length, m, m_len); | |
| + if (njs_slow_path(ret < 0)) { | |
| + njs_webcrypto_error(vm, "EVP_PKEY_verify() failed"); | |
| + goto fail; | |
| + } | |
| + } | |
| + | |
| + EVP_PKEY_CTX_free(pctx); | |
| + | |
| + break; | |
| + } | |
| + | |
| + if (!verify) { | |
| + ret = njs_vm_value_array_buffer_set(vm, &value, dst, outlen); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + goto fail; | |
| + } | |
| + | |
| + } else { | |
| + njs_set_boolean(&value, ret != 0); | |
| + } | |
| + | |
| + njs_evp_md_ctx_free(mctx); | |
| + | |
| + return njs_webcrypto_result(vm, &value, NJS_OK); | |
| + | |
| +fail: | |
| + | |
| + if (mctx != NULL) { | |
| + njs_evp_md_ctx_free(mctx); | |
| + } | |
| + | |
| + if (pctx != NULL) { | |
| + EVP_PKEY_CTX_free(pctx); | |
| + } | |
| + | |
| + return njs_webcrypto_result(vm, njs_vm_retval(vm), NJS_ERROR); | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_ext_unwrap_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
| + njs_index_t unused) | |
| +{ | |
| + njs_internal_error(vm, "\"unwrapKey\" not implemented"); | |
| + return NJS_ERROR; | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_ext_wrap_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
| + njs_index_t unused) | |
| +{ | |
| + njs_internal_error(vm, "\"wrapKey\" not implemented"); | |
| + return NJS_ERROR; | |
| +} | |
| + | |
| + | |
| +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) | |
| +{ | |
| + njs_int_t ret; | |
| + njs_str_t fill; | |
| + | |
| + ret = njs_vm_value_to_bytes(vm, &fill, njs_arg(args, nargs, 1)); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + if (njs_slow_path(fill.length > 65536)) { | |
| + njs_type_error(vm, "requested length exceeds 65536 bytes"); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + if (RAND_bytes(fill.start, fill.length) != 1) { | |
| + njs_webcrypto_error(vm, "RAND_bytes() failed"); | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + return NJS_OK; | |
| +} | |
| + | |
| + | |
| +static void | |
| +njs_webcrypto_cleanup_pkey(void *data) | |
| +{ | |
| + njs_webcrypto_key_t *key = data; | |
| + | |
| + if (key->pkey != NULL) { | |
| + EVP_PKEY_free(key->pkey); | |
| + } | |
| +} | |
| + | |
| + | |
| +static njs_webcrypto_key_format_t | |
| +njs_key_format(njs_vm_t *vm, njs_value_t *value, njs_str_t *format) | |
| +{ | |
| + njs_int_t ret; | |
| + njs_uint_t fmt; | |
| + | |
| + static const struct { | |
| + njs_str_t name; | |
| + njs_uint_t value; | |
| + } formats[] = { | |
| + { njs_str("raw"), NJS_KEY_FORMAT_RAW }, | |
| + { njs_str("pkcs8"), NJS_KEY_FORMAT_PKCS8 }, | |
| + { njs_str("spki"), NJS_KEY_FORMAT_SPKI }, | |
| + { njs_str("jwk"), NJS_KEY_FORMAT_JWK }, | |
| + }; | |
| + | |
| + ret = njs_value_to_string(vm, value, value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + njs_string_get(value, format); | |
| + | |
| + fmt = 0; | |
| + | |
| + while (fmt < sizeof(formats) / sizeof(formats[0])) { | |
| + if (njs_strstr_eq(format, &formats[fmt].name)) { | |
| + return formats[fmt].value; | |
| + } | |
| + | |
| + fmt++; | |
| + } | |
| + | |
| + return NJS_KEY_FORMAT_UNKNOWN; | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_key_usage_array_handler(njs_vm_t *vm, njs_iterator_args_t *args, | |
| + njs_value_t *value, int64_t index) | |
| +{ | |
| + unsigned *mask; | |
| + njs_str_t u; | |
| + njs_int_t ret; | |
| + njs_value_t usage; | |
| + njs_webcrypto_entry_t *e; | |
| + | |
| + njs_value_assign(&usage, value); | |
| + | |
| + ret = njs_value_to_string(vm, &usage, &usage); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + njs_string_get(&usage, &u); | |
| + | |
| + for (e = &njs_webcrypto_usage[0]; e->name.length != 0; e++) { | |
| + if (njs_strstr_eq(&u, &e->name)) { | |
| + mask = args->data; | |
| + *mask |= e->value; | |
| + return NJS_OK; | |
| + } | |
| + } | |
| + | |
| + njs_type_error(vm, "unknown key usage: \"%V\"", &u); | |
| + | |
| + return NJS_ERROR; | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_key_usage(njs_vm_t *vm, njs_value_t *value, unsigned *mask) | |
| +{ | |
| + int64_t length; | |
| + njs_int_t ret; | |
| + njs_iterator_args_t args; | |
| + | |
| + ret = njs_object_length(vm, value, &length); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + *mask = 0; | |
| + | |
| + args.value = value; | |
| + args.from = 0; | |
| + args.to = length; | |
| + args.data = mask; | |
| + | |
| + return njs_object_iterate(vm, &args, njs_key_usage_array_handler); | |
| +} | |
| + | |
| + | |
| +static njs_webcrypto_algorithm_t * | |
| +njs_key_algorithm(njs_vm_t *vm, njs_value_t *options) | |
| +{ | |
| + njs_int_t ret; | |
| + njs_str_t a; | |
| + njs_value_t name; | |
| + njs_webcrypto_entry_t *e; | |
| + njs_webcrypto_algorithm_t *alg; | |
| + | |
| + static const njs_value_t string_name = njs_string("name"); | |
| + | |
| + if (njs_is_object(options)) { | |
| + ret = njs_value_property(vm, options, njs_value_arg(&string_name), | |
| + &name); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + if (ret == NJS_DECLINED) { | |
| + njs_type_error(vm, "algorithm name is not provided"); | |
| + } | |
| + | |
| + return NULL; | |
| + } | |
| + | |
| + } else { | |
| + njs_value_assign(&name, options); | |
| + } | |
| + | |
| + ret = njs_value_to_string(vm, &name, &name); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NULL; | |
| + } | |
| + | |
| + njs_string_get(&name, &a); | |
| + | |
| + for (e = &njs_webcrypto_alg[0]; e->name.length != 0; e++) { | |
| + if (njs_strstr_case_eq(&a, &e->name)) { | |
| + alg = (njs_webcrypto_algorithm_t *) e->value; | |
| + if (alg->usage & NJS_KEY_USAGE_UNSUPPORTED) { | |
| + njs_type_error(vm, "unsupported algorithm: \"%V\"", &a); | |
| + return NULL; | |
| + } | |
| + | |
| + return alg; | |
| + } | |
| + } | |
| + | |
| + njs_type_error(vm, "unknown algorithm name: \"%V\"", &a); | |
| + | |
| + return NULL; | |
| +} | |
| + | |
| + | |
| +static njs_str_t * | |
| +njs_algorithm_string(njs_webcrypto_algorithm_t *algorithm) | |
| +{ | |
| + njs_webcrypto_entry_t *e; | |
| + njs_webcrypto_algorithm_t *alg; | |
| + | |
| + for (e = &njs_webcrypto_alg[0]; e->name.length != 0; e++) { | |
| + alg = (njs_webcrypto_algorithm_t *) e->value; | |
| + if (alg->type == algorithm->type) { | |
| + break; | |
| + } | |
| + } | |
| + | |
| + return &e->name; | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_algorithm_hash(njs_vm_t *vm, njs_value_t *options, | |
| + njs_webcrypto_hash_t *hash) | |
| +{ | |
| + njs_int_t ret; | |
| + njs_str_t name; | |
| + njs_value_t value; | |
| + njs_webcrypto_entry_t *e; | |
| + | |
| + static const njs_value_t string_hash = njs_string("hash"); | |
| + | |
| + if (njs_is_object(options)) { | |
| + ret = njs_value_property(vm, options, njs_value_arg(&string_hash), | |
| + &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return ret; | |
| + } | |
| + | |
| + } else { | |
| + njs_value_assign(&value, options); | |
| + } | |
| + | |
| + ret = njs_value_to_string(vm, &value, &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + njs_string_get(&value, &name); | |
| + | |
| + for (e = &njs_webcrypto_hash[0]; e->name.length != 0; e++) { | |
| + if (njs_strstr_eq(&name, &e->name)) { | |
| + *hash = e->value; | |
| + return NJS_OK; | |
| + } | |
| + } | |
| + | |
| + njs_type_error(vm, "unknown hash name: \"%V\"", &name); | |
| + | |
| + return NJS_ERROR; | |
| +} | |
| + | |
| + | |
| +static const EVP_MD * | |
| +njs_algorithm_hash_digest(njs_webcrypto_hash_t hash) | |
| +{ | |
| + switch (hash) { | |
| + case NJS_HASH_SHA256: | |
| + return EVP_sha256(); | |
| + | |
| + case NJS_HASH_SHA384: | |
| + return EVP_sha384(); | |
| + | |
| + case NJS_HASH_SHA512: | |
| + return EVP_sha512(); | |
| + break; | |
| + | |
| + case NJS_HASH_SHA1: | |
| + default: | |
| + break; | |
| + } | |
| + | |
| + return EVP_sha1(); | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_algorithm_curve(njs_vm_t *vm, njs_value_t *options, | |
| + njs_webcrypto_curve_t *curve) | |
| +{ | |
| + njs_int_t ret; | |
| + njs_str_t name; | |
| + njs_value_t value; | |
| + njs_webcrypto_entry_t *e; | |
| + | |
| + static const njs_value_t string_curve = njs_string("namedCurve"); | |
| + | |
| + ret = njs_value_property(vm, options, njs_value_arg(&string_curve), | |
| + &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return ret; | |
| + } | |
| + | |
| + ret = njs_value_to_string(vm, &value, &value); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + njs_string_get(&value, &name); | |
| + | |
| + for (e = &njs_webcrypto_curve[0]; e->name.length != 0; e++) { | |
| + if (njs_strstr_eq(&name, &e->name)) { | |
| + *curve = e->value; | |
| + return NJS_OK; | |
| + } | |
| + } | |
| + | |
| + njs_type_error(vm, "unknown namedCurve: \"%V\"", &name); | |
| + | |
| + return NJS_ERROR; | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_promise_trampoline(njs_vm_t *vm, njs_value_t *args, | |
| + njs_uint_t nargs, njs_index_t unused) | |
| +{ | |
| + njs_function_t *callback; | |
| + | |
| + callback = njs_value_function(njs_argument(args, 1)); | |
| + | |
| + if (callback != NULL) { | |
| + return njs_vm_call(vm, callback, njs_argument(args, 2), 1); | |
| + } | |
| + | |
| + return NJS_OK; | |
| +} | |
| + | |
| + | |
| +static njs_int_t | |
| +njs_webcrypto_result(njs_vm_t *vm, njs_value_t *result, njs_int_t rc) | |
| +{ | |
| + njs_int_t ret; | |
| + njs_value_t retval, arguments[2]; | |
| + njs_function_t *callback; | |
| + njs_vm_event_t vm_event; | |
| + | |
| + ret = njs_vm_promise_create(vm, &retval, njs_value_arg(&arguments)); | |
| + if (ret != NJS_OK) { | |
| + goto error; | |
| + } | |
| + | |
| + callback = njs_vm_function_alloc(vm, njs_promise_trampoline); | |
| + if (callback == NULL) { | |
| + goto error; | |
| + } | |
| + | |
| + vm_event = njs_vm_add_event(vm, callback, 1, NULL, NULL); | |
| + if (vm_event == NULL) { | |
| + goto error; | |
| + } | |
| + | |
| + njs_value_assign(&arguments[0], &arguments[(rc != NJS_OK)]); | |
| + njs_value_assign(&arguments[1], result); | |
| + | |
| + ret = njs_vm_post_event(vm, vm_event, njs_value_arg(&arguments), 2); | |
| + if (ret == NJS_ERROR) { | |
| + goto error; | |
| + } | |
| + | |
| + njs_vm_retval_set(vm, njs_value_arg(&retval)); | |
| + | |
| + return NJS_OK; | |
| + | |
| +error: | |
| + | |
| + njs_vm_error(vm, "internal error"); | |
| + | |
| + return NJS_ERROR; | |
| +} | |
| + | |
| + | |
| +static u_char * | |
| +njs_cpystrn(u_char *dst, u_char *src, size_t n) | |
| +{ | |
| + if (n == 0) { | |
| + return dst; | |
| + } | |
| + | |
| + while (--n) { | |
| + *dst = *src; | |
| + | |
| + if (*dst == '\0') { | |
| + return dst; | |
| + } | |
| + | |
| + dst++; | |
| + src++; | |
| + } | |
| + | |
| + *dst = '\0'; | |
| + | |
| + return dst; | |
| +} | |
| + | |
| + | |
| +static void | |
| +njs_webcrypto_error(njs_vm_t *vm, const char *fmt, ...) | |
| +{ | |
| + int flags; | |
| + va_list args; | |
| + u_char *p, *last; | |
| + unsigned long n; | |
| + const char *data; | |
| + u_char errstr[NJS_MAX_ERROR_STR]; | |
| + | |
| + last = &errstr[NJS_MAX_ERROR_STR]; | |
| + | |
| + va_start(args, fmt); | |
| + p = njs_vsprintf(errstr, last - 1, fmt, args); | |
| + va_end(args); | |
| + | |
| + if (ERR_peek_error()) { | |
| + p = njs_cpystrn(p, (u_char *) " (SSL:", last - p); | |
| + | |
| + for ( ;; ) { | |
| + | |
| + n = ERR_peek_error_line_data(NULL, NULL, &data, &flags); | |
| + | |
| + if (n == 0) { | |
| + break; | |
| + } | |
| + | |
| + /* ERR_error_string_n() requires at least one byte */ | |
| + | |
| + if (p >= last - 1) { | |
| + goto next; | |
| + } | |
| + | |
| + *p++ = ' '; | |
| + | |
| + ERR_error_string_n(n, (char *) p, last - p); | |
| + | |
| + while (p < last && *p) { | |
| + p++; | |
| + } | |
| + | |
| + if (p < last && *data && (flags & ERR_TXT_STRING)) { | |
| + *p++ = ':'; | |
| + p = njs_cpystrn(p, (u_char *) data, last - p); | |
| + } | |
| + | |
| + next: | |
| + | |
| + (void) ERR_get_error(); | |
| + } | |
| + | |
| + if (p < last) { | |
| + *p++ = ')'; | |
| + } | |
| + } | |
| + | |
| + njs_vm_value_error_set(vm, njs_vm_retval(vm), "%*s", p - errstr, errstr); | |
| +} | |
| + | |
| + | |
| +njs_int_t | |
| +njs_external_webcrypto_init(njs_vm_t *vm) | |
| +{ | |
| + njs_int_t ret; | |
| + njs_int_t proto_id; | |
| + njs_str_t name; | |
| + njs_opaque_value_t value; | |
| + | |
| + OpenSSL_add_all_algorithms(); | |
| + | |
| + njs_webcrypto_crypto_key_proto_id = | |
| + njs_vm_external_prototype(vm, njs_ext_webcrypto_crypto_key, | |
| + njs_nitems(njs_ext_webcrypto_crypto_key)); | |
| + if (njs_slow_path(njs_webcrypto_crypto_key_proto_id < 0)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + proto_id = njs_vm_external_prototype(vm, njs_ext_webcrypto, | |
| + njs_nitems(njs_ext_webcrypto)); | |
| + if (njs_slow_path(proto_id < 0)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + ret = njs_vm_external_create(vm, njs_value_arg(&value), proto_id, NULL, 1); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + name.length = njs_length("crypto"); | |
| + name.start = (u_char *) "crypto"; | |
| + | |
| + ret = njs_vm_bind(vm, &name, njs_value_arg(&value), 1); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| + | |
| + return NJS_OK; | |
| +} | |
| diff --git a/external/njs_webcrypto.h b/external/njs_webcrypto.h | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/external/njs_webcrypto.h | |
| @@ -0,0 +1,15 @@ | |
| + | |
| +/* | |
| + * Copyright (C) Dmitry Volyntsev | |
| + * Copyright (C) NGINX, Inc. | |
| + */ | |
| + | |
| + | |
| +#ifndef _NJS_EXTERNAL_WEBCRYPTO_H_INCLUDED_ | |
| +#define _NJS_EXTERNAL_WEBCRYPTO_H_INCLUDED_ | |
| + | |
| + | |
| +njs_int_t njs_external_webcrypto_init(njs_vm_t *vm); | |
| + | |
| + | |
| +#endif /* _NJS_EXTERNAL_WEBCRYPTO_H_INCLUDED_ */ | |
| diff --git a/nginx/config b/nginx/config | |
| --- a/nginx/config | |
| +++ b/nginx/config | |
| @@ -1,8 +1,11 @@ | |
| ngx_addon_name="ngx_js_module" | |
| -NJS_DEPS="$ngx_addon_dir/ngx_js.h" | |
| +NJS_DEPS="$ngx_addon_dir/ngx_js.h \ | |
| + $ngx_addon_dir/ngx_js_fetch.h \ | |
| + $ngx_addon_dir/../external/njs_webcrypto.h" | |
| NJS_SRCS="$ngx_addon_dir/ngx_js.c \ | |
| - $ngx_addon_dir/ngx_js_fetch.c" | |
| + $ngx_addon_dir/ngx_js_fetch.c \ | |
| + $ngx_addon_dir/../external/njs_webcrypto.c" | |
| if [ $HTTP != NO ]; then | |
| ngx_module_type=HTTP_AUX_FILTER | |
| @@ -10,7 +13,7 @@ if [ $HTTP != NO ]; then | |
| ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build" | |
| ngx_module_deps="$ngx_addon_dir/../build/libnjs.a $NJS_DEPS" | |
| ngx_module_srcs="$ngx_addon_dir/ngx_http_js_module.c $NJS_SRCS" | |
| - ngx_module_libs="PCRE $ngx_addon_dir/../build/libnjs.a -lm" | |
| + ngx_module_libs="PCRE OPENSSL $ngx_addon_dir/../build/libnjs.a -lm" | |
| . auto/module | |
| @@ -25,7 +28,7 @@ if [ $STREAM != NO ]; then | |
| ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build" | |
| ngx_module_deps="$ngx_addon_dir/../build/libnjs.a $NJS_DEPS" | |
| ngx_module_srcs="$ngx_addon_dir/ngx_stream_js_module.c $NJS_SRCS" | |
| - ngx_module_libs="PCRE $ngx_addon_dir/../build/libnjs.a -lm" | |
| + ngx_module_libs="PCRE OPENSSL $ngx_addon_dir/../build/libnjs.a -lm" | |
| . auto/module | |
| fi | |
| diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c | |
| --- a/nginx/ngx_js.c | |
| +++ b/nginx/ngx_js.c | |
| @@ -10,6 +10,7 @@ | |
| #include <ngx_core.h> | |
| #include "ngx_js.h" | |
| #include "ngx_js_fetch.h" | |
| +#include "../external/njs_webcrypto.h" | |
| static njs_external_t ngx_js_ext_core[] = { | |
| @@ -139,6 +140,12 @@ ngx_js_core_init(njs_vm_t *vm, ngx_log_t | |
| return NGX_ERROR; | |
| } | |
| + ret = njs_external_webcrypto_init(vm); | |
| + if (ret != NJS_OK) { | |
| + ngx_log_error(NGX_LOG_EMERG, log, 0, "failed to add webcrypto object"); | |
| + return NGX_ERROR; | |
| + } | |
| + | |
| proto_id = njs_vm_external_prototype(vm, ngx_js_ext_core, | |
| njs_nitems(ngx_js_ext_core)); | |
| if (proto_id < 0) { | |
| diff --git a/src/njs_shell.c b/src/njs_shell.c | |
| --- a/src/njs_shell.c | |
| +++ b/src/njs_shell.c | |
| @@ -23,6 +23,11 @@ | |
| #endif | |
| +#if (NJS_HAVE_OPENSSL) | |
| +#include "../external/njs_webcrypto.h" | |
| +#include "../external/njs_webcrypto.c" | |
| +#endif | |
| + | |
| typedef struct { | |
| uint8_t disassemble; | |
| @@ -718,6 +723,13 @@ njs_externals_init(njs_vm_t *vm, njs_con | |
| return NJS_ERROR; | |
| } | |
| +#if (NJS_HAVE_OPENSSL) | |
| + ret = njs_external_webcrypto_init(vm); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| +#endif | |
| + | |
| return NJS_OK; | |
| } | |
| diff --git a/src/njs_str.c b/src/njs_str.c | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/src/njs_str.c | |
| @@ -0,0 +1,37 @@ | |
| + | |
| +/* | |
| + * Copyright (C) Igor Sysoev | |
| + * Copyright (C) NGINX, Inc. | |
| + */ | |
| + | |
| + | |
| +#include <njs_main.h> | |
| + | |
| + | |
| +njs_int_t | |
| +njs_strncasecmp(u_char *s1, u_char *s2, size_t n) | |
| +{ | |
| + njs_uint_t c1, c2; | |
| + | |
| + while (n) { | |
| + c1 = (njs_uint_t) *s1++; | |
| + c2 = (njs_uint_t) *s2++; | |
| + | |
| + c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1; | |
| + c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2; | |
| + | |
| + if (c1 == c2) { | |
| + | |
| + if (c1) { | |
| + n--; | |
| + continue; | |
| + } | |
| + | |
| + return 0; | |
| + } | |
| + | |
| + return c1 - c2; | |
| + } | |
| + | |
| + return 0; | |
| +} | |
| diff --git a/src/njs_str.h b/src/njs_str.h | |
| --- a/src/njs_str.h | |
| +++ b/src/njs_str.h | |
| @@ -134,4 +134,13 @@ njs_strstr_eq(s1, s2) | |
| && (memcmp((s1)->start, (s2)->start, (s1)->length) == 0)) | |
| +#define \ | |
| +njs_strstr_case_eq(s1, s2) \ | |
| + (((s1)->length == (s2)->length) \ | |
| + && (njs_strncasecmp((s1)->start, (s2)->start, (s1)->length) == 0)) | |
| + | |
| + | |
| +NJS_EXPORT njs_int_t njs_strncasecmp(u_char *s1, u_char *s2, size_t n); | |
| + | |
| + | |
| #endif /* _NJS_STR_H_INCLUDED_ */ | |
| diff --git a/src/test/njs_externals_test.c b/src/test/njs_externals_test.c | |
| --- a/src/test/njs_externals_test.c | |
| +++ b/src/test/njs_externals_test.c | |
| @@ -8,6 +8,11 @@ | |
| #include "njs_externals_test.h" | |
| +#if (NJS_HAVE_OPENSSL) | |
| +#include "../external/njs_webcrypto.h" | |
| +#include "../external/njs_webcrypto.c" | |
| +#endif | |
| + | |
| typedef struct { | |
| njs_lvlhsh_t hash; | |
| @@ -746,6 +751,15 @@ njs_externals_init_internal(njs_vm_t *vm | |
| njs_int_t | |
| njs_externals_shared_init(njs_vm_t *vm) | |
| { | |
| +#if (NJS_HAVE_OPENSSL) | |
| + njs_int_t ret; | |
| + | |
| + ret = njs_external_webcrypto_init(vm); | |
| + if (njs_slow_path(ret != NJS_OK)) { | |
| + return NJS_ERROR; | |
| + } | |
| +#endif | |
| + | |
| return njs_externals_init_internal(vm, njs_test_requests, 1, 1); | |
| } | |
| diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c | |
| --- a/src/test/njs_unit_test.c | |
| +++ b/src/test/njs_unit_test.c | |
| @@ -20632,6 +20632,26 @@ static njs_unit_test_t njs_disabled_den | |
| }; | |
| +static njs_unit_test_t njs_webcrypto_test[] = | |
| +{ | |
| + /* Statistic test | |
| + * bits1 is a random variable with Binomial distribution | |
| + * Expected value is N / 2 | |
| + * Standard deviation is sqrt(N / 4) | |
| + */ | |
| + { njs_str("function count1(v) {return v.toString(2).match(/1/g).length;}" | |
| + "let buf = new Uint32Array(32);" | |
| + "crypto.getRandomValues(buf);" | |
| + "let bits1 = buf.reduce((a, v)=> a + count1(v), 0);" | |
| + "let nbits = buf.length * 32;" | |
| + "let mean = nbits / 2;" | |
| + "let stddev = Math.sqrt(nbits / 4);" | |
| + "let condition = bits1 > (mean - 10 * stddev) && bits1 < (mean + 10 * stddev);" | |
| + "condition ? true : [buf, nbits, bits1, mean, stddev]"), | |
| + njs_str("true") }, | |
| +}; | |
| + | |
| + | |
| static njs_unit_test_t njs_module_test[] = | |
| { | |
| { njs_str("function f(){return 2}; var f; f()"), | |
| @@ -20865,12 +20885,20 @@ static njs_unit_test_t njs_externals_te | |
| { njs_str("$r2.uri == 'αβγ' && $r2.uri === 'αβγ'"), | |
| njs_str("true") }, | |
| +#if (NJS_TEST262) | |
| +#define N262 "$262," | |
| +#else | |
| +#define N262 "" | |
| +#endif | |
| + | |
| +#if (NJS_HAVE_OPENSSL) | |
| +#define NCRYPTO "crypto," | |
| +#else | |
| +#define NCRYPTO "" | |
| +#endif | |
| + | |
| { njs_str("Object.keys(this).sort()"), | |
| -#if (NJS_TEST262) | |
| - njs_str("$262,$r,$r2,$r3,$shared,global,njs,process") }, | |
| -#else | |
| - njs_str("$r,$r2,$r3,$shared,global,njs,process") }, | |
| -#endif | |
| + njs_str(N262 "$r,$r2,$r3,$shared," NCRYPTO "global,njs,process") }, | |
| { njs_str("Object.getOwnPropertySymbols($r2)[0] == Symbol.toStringTag"), | |
| njs_str("true") }, | |
| @@ -22934,6 +22962,17 @@ static njs_test_suite_t njs_suites[] = | |
| njs_nitems(njs_disabled_denormals_test), | |
| njs_disabled_denormals_tests }, | |
| + { | |
| +#if (NJS_HAVE_OPENSSL) | |
| + njs_str("webcrypto"), | |
| +#else | |
| + njs_str(""), | |
| +#endif | |
| + { .externals = 1, .repeat = 1, .unsafe = 1 }, | |
| + njs_webcrypto_test, | |
| + njs_nitems(njs_webcrypto_test), | |
| + njs_unit_test }, | |
| + | |
| { njs_str("module"), | |
| { .repeat = 1, .module = 1, .unsafe = 1 }, | |
| njs_module_test, | |
| diff --git a/test/njs_expect_test.exp b/test/njs_expect_test.exp | |
| --- a/test/njs_expect_test.exp | |
| +++ b/test/njs_expect_test.exp | |
| @@ -1109,6 +1109,38 @@ njs_run {"./test/js/promise_race.js"} \ | |
| njs_run {"./test/js/promise_race_throw.js"} \ | |
| "rejected:one" | |
| +# Webcrypto | |
| + | |
| +njs_run {"./test/webcrypto/rsa_decoding.js" "--match-exception-text"} \ | |
| +"RSA-OAEP decoding SUCCESS" | |
| + | |
| +njs_run {"./test/webcrypto/rsa.js" "--match-exception-text"} \ | |
| +"RSA-OAEP encoding/decoding SUCCESS" | |
| + | |
| +njs_run {"./test/webcrypto/aes_decoding.js" "--match-exception-text"} \ | |
| +"AES decoding SUCCESS" | |
| + | |
| +njs_run {"./test/webcrypto/aes.js" "--match-exception-text"} \ | |
| +"AES encoding/decoding SUCCESS" | |
| + | |
| +njs_run {"./test/webcrypto/derive.js" "--match-exception-text"} \ | |
| +"derive SUCCESS" | |
| + | |
| +njs_run {"./test/webcrypto/digest.js" "--match-exception-text"} \ | |
| +"SHA digest SUCCESS" | |
| + | |
| +njs_run {"./test/webcrypto/sign.js" "--match-exception-text"} \ | |
| +"HMAC sign SUCCESS | |
| +RSASSA-PKCS1-v1_5 sign SUCCESS | |
| +RSA-PSS sign SUCCESS | |
| +ECDSA sign SUCCESS" | |
| + | |
| +njs_run {"./test/webcrypto/verify.js" "--match-exception-text"} \ | |
| +"HMAC verify SUCCESS | |
| +RSASSA-PKCS1-v1_5 verify SUCCESS | |
| +RSA-PSS verify SUCCESS | |
| +ECDSA verify SUCCESS" | |
| + | |
| # Async/Await | |
| njs_run {"./test/js/async_await_inline.js"} \ | |
| diff --git a/test/webcrypto/README.rst b/test/webcrypto/README.rst | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/README.rst | |
| @@ -0,0 +1,136 @@ | |
| +=============== | |
| +WebCrypto tests | |
| +=============== | |
| + | |
| +Intro | |
| +===== | |
| + | |
| +Tests in this folder are expected to be compatible with node.js | |
| + | |
| +Tested versions | |
| +--------------- | |
| + | |
| +node: v16.4.0 | |
| +openssl: OpenSSL 1.1.1f 31 Mar 2020 | |
| + | |
| +Keys generation | |
| +=============== | |
| + | |
| +Generating RSA PKCS8/SPKI key files | |
| +----------------------------------- | |
| + | |
| +.. code-block:: shell | |
| + | |
| + openssl genrsa -out rsa.pem 1024 | |
| + openssl pkcs8 -inform PEM -in rsa.pem -nocrypt -topk8 -outform PEM -out rsa.pkcs8 | |
| + openssl rsa -in rsa.pkcs8 -pubout > rsa.spki | |
| + | |
| +Generating EC PKCS8/SPKI key files | |
| +---------------------------------- | |
| + | |
| +.. code-block:: shell | |
| + | |
| + openssl ecparam -name prime256v1 -genkey -noout -out ec.pem | |
| + openssl pkcs8 -inform PEM -in ec.pem -nocrypt -topk8 -outform PEM -out ec.pkcs8 | |
| + openssl ec -in ec.pkcs8 -pubout > ec.spki | |
| + | |
| +Encoding | |
| +======== | |
| + | |
| +Encoding data using RSA-OAEP | |
| +---------------------------- | |
| + | |
| +.. code-block:: shell | |
| + | |
| + echo -n "WAKAWAKA" > text.txt | |
| + openssl rsautl -inkey key.spki -pubin -in text.txt -out - -oaep -encrypt | \ | |
| + base64 > text.base64.rsa-oaep.enc | |
| + | |
| +Decoding ciphertext using RSA-OAEP | |
| +---------------------------------- | |
| + | |
| +.. code-block:: shell | |
| + | |
| + base64 -d text.base64.rsa-oaep.enc | openssl rsautl -inkey key.pkcs8 -in - -out - -oaep -decrypt | |
| + WAKAWAKA | |
| + | |
| +Encoding data using AES-GCM | |
| +--------------------------- | |
| + | |
| +.. code-block:: shell | |
| + | |
| + echo -n "AES-GCM-SECRET-TEXT" > text.txt | |
| + node ./test/webcrypto/aes_gcm_enc.js '{"in":"text.txt"}' > text.base64.aes-gcm128.enc | |
| + | |
| + echo -n "AES-GCM-96-TAG-LENGTH-SECRET-TEXT" > text.txt | |
| + node ./test/webcrypto/aes_gcm_enc.js '{"in":"text.txt","tagLength":96}' > text.base64.aes-gcm128-96.enc | |
| + | |
| +Encoding data using AES-CTR | |
| +--------------------------- | |
| + | |
| +.. code-block:: shell | |
| + | |
| + echo -n "AES-CTR-SECRET-TEXT" | \ | |
| + openssl enc -aes-128-ctr -K 00112233001122330011223300112233 -iv 44556677445566774455667744556677 | \ | |
| + base64 > text.base64.aes-ctr128.enc | |
| + | |
| +Encoding data using AES-CBC | |
| +--------------------------- | |
| + | |
| +.. code-block:: shell | |
| + | |
| + echo -n "AES-CBC-SECRET-TEXT" | \ | |
| + openssl enc -aes-128-cbc -K 00112233001122330011223300112233 -iv 44556677445566774455667744556677 | \ | |
| + base64 > text.base64.aes-cbc128.enc | |
| + | |
| +Signing | |
| +======= | |
| + | |
| +Signing data using HMAC | |
| +----------------------- | |
| + | |
| +.. code-block:: shell | |
| + | |
| + echo -n "SigneD-TExt" > text.txt | |
| + openssl dgst -sha256 -mac hmac -macopt hexkey:aabbcc -binary text.txt | \ | |
| + base64 > test/webcrypto/text.base64.sha256.hmac.sig | |
| + | |
| +Signing data using RSASSA-PKCS1-v1_5 | |
| +------------------------------------ | |
| + | |
| +.. code-block:: shell | |
| + | |
| + echo -n "SigneD-TExt" > text.txt | |
| + openssl dgst -sha256 -sigopt rsa_padding_mode:pkcs1 -sign test/webcrypto/rsa.pkcs8 text.txt | \ | |
| + base64 > test/webcrypto/text.base64.sha256.pkcs1.sig | |
| + base64 -d test/webcrypto/text.base64.sha256.pkcs1.sig > text.sha256.pkcs1.sig | |
| + openssl dgst -sha256 -sigopt rsa_padding_mode:pkcs1 -verify test/webcrypto/rsa.spki \ | |
| + -signature text.sha256.pkcs1.sig text.txt | |
| + Verified OK | |
| + | |
| +Signing data using RSA-PSS | |
| +-------------------------- | |
| + | |
| +.. code-block:: shell | |
| + | |
| + echo -n "SigneD-TExt" > text.txt | |
| + openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 -sign test/webcrypto/rsa.pkcs8 text.txt | \ | |
| + base64 > test/webcrypto/text.base64.sha256.rsa-pss.32.sig | |
| + base64 -d test/webcrypto/text.base64.sha256.rsa-pss.32.sig > text.sha256.rsa-pss.32.sig | |
| + openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 \ | |
| + -verify test/webcrypto/rsa.spki -signature text.sha256.rsa-pss.sig text.txt | |
| + Verified OK | |
| + | |
| +Signing data using ECDSA | |
| +------------------------ | |
| + | |
| +.. code-block:: shell | |
| + | |
| + echo -n "SigneD-TExt" > text.txt | |
| + openssl dgst -sha256 -binary text.txt > text.sha256 | |
| + openssl pkeyutl -sign -in text.sha256 -inkey test/webcrypto/ec.pkcs8 | \ | |
| + base64 > test/webcrypto/text.base64.sha256.ecdsa.sig | |
| + base64 -d test/webcrypto/text.base64.sha256.ecdsa.sig > text.sha256.ecdsa.sig | |
| + openssl pkeyutl -verify -in text.sha256 -pubin -inkey test/webcrypto/ec.spki -sigfile text.sha256.ecdsa.sig | |
| + Signature Verified Successfully | |
| + | |
| diff --git a/test/webcrypto/aes.js b/test/webcrypto/aes.js | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/aes.js | |
| @@ -0,0 +1,123 @@ | |
| +if (typeof crypto == 'undefined') { | |
| + crypto = require('crypto').webcrypto; | |
| +} | |
| + | |
| +async function run(tlist, T, prepare_args) { | |
| + function validate(t, r, i) { | |
| + if (r.status == "fulfilled" && !t[i].exception) { | |
| + return r.value === "SUCCESS"; | |
| + } | |
| + | |
| + if (r.status == "rejected" && t[i].exception) { | |
| + if (process.argv[2] === '--match-exception-text') { | |
| + /* is not compatible with node.js format */ | |
| + return r.reason.toString().startsWith(t[i].exception); | |
| + } | |
| + | |
| + return true; | |
| + } | |
| + | |
| + return false; | |
| + } | |
| + | |
| + for (let k = 0; k < tlist.length; k++) { | |
| + let ts = tlist[k]; | |
| + let results = await Promise.allSettled(ts.tests.map(t => T(prepare_args(t, ts.opts)))); | |
| + let r = results.map((r, i) => validate(ts.tests, r, i)); | |
| + | |
| + console.log(`${ts.name} ${r.every(v=>v == true) ? "SUCCESS" : "FAILED"}`); | |
| + | |
| + r.forEach((v, i) => { | |
| + if (!v) { | |
| + console.log(`FAILED ${i}: ${JSON.stringify(ts.tests[i])}\n with reason: ${results[i].reason}`); | |
| + } | |
| + }) | |
| + } | |
| +} | |
| + | |
| +function p(args, default_opts) { | |
| + let params = Object.assign({}, default_opts, args); | |
| + | |
| + params.key = Buffer.from(params.key, "hex"); | |
| + params.data = Buffer.from(params.data, "hex"); | |
| + params.iv = Buffer.from(params.iv, "hex"); | |
| + params.counter = Buffer.from(params.counter, "hex"); | |
| + | |
| + switch (params.name) { | |
| + case "AES-GCM": | |
| + if (params.additionalData) { | |
| + params.additionalData = Buffer.from(params.additionalData, "hex"); | |
| + } | |
| + | |
| + break; | |
| + } | |
| + | |
| + return params; | |
| +} | |
| + | |
| +async function test(params) { | |
| + let dkey = await crypto.subtle.importKey("raw", params.key, | |
| + {name: params.name}, | |
| + false, ["decrypt"]); | |
| + | |
| + let ekey = await crypto.subtle.importKey("raw", params.key, | |
| + {name: params.name}, | |
| + false, ["encrypt"]); | |
| + | |
| + let enc = await crypto.subtle.encrypt(params, ekey, params.data); | |
| + let plaintext = await crypto.subtle.decrypt(params, dkey, enc); | |
| + plaintext = Buffer.from(plaintext); | |
| + | |
| + if (params.data.compare(plaintext) != 0) { | |
| + throw Error(`${params.name} encoding/decoding failed length ${data.length}`); | |
| + } | |
| + | |
| + return 'SUCCESS'; | |
| +} | |
| + | |
| +let aes_tsuite = { | |
| + name: "AES encoding/decoding", | |
| + opts: { | |
| + iv: "44556677445566774455667744556677", | |
| + key: "00112233001122330011223300112233", | |
| + counter: "44556677445566774455667744556677", | |
| + length: 64 | |
| + }, | |
| + | |
| + tests: [ | |
| + { name: "AES-gcm", data: "aa" }, | |
| + { name: "aes-gcm", data: "aabbcc" }, | |
| + { name: "AES-GCM", data: "aabbcc", additionalData: "deafbeef"}, | |
| + { name: "AES-GCM", data: "aabbccdd".repeat(4) }, | |
| + { name: "AES-GCM", data: "aa", iv: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, | |
| + { name: "AES-GCM", data: "aabbcc", tagLength: 96 }, | |
| + { name: "AES-GCM", data: "aabbcc", tagLength: 112 }, | |
| + { name: "AES-GCM", data: "aabbcc", tagLength: 113, exception: "TypeError: AES-GCM Invalid tagLength" }, | |
| + { name: "AES-GCM", data: "aabbccdd".repeat(4096) }, | |
| + | |
| + { name: "AES-CTR", data: "aa" }, | |
| + { name: "AES-CTR", data: "aabbcc" }, | |
| + { name: "AES-CTR", data: "aabbccdd".repeat(4) }, | |
| + { name: "AES-CTR", data: "aabbccdd".repeat(4096) }, | |
| + { name: "AES-CTR", data: "aa", counter: "ffffffffffffffffffffffffffffffff" }, | |
| + { name: "AES-CTR", data: "aa", counter: "ffffffff", | |
| + exception: "TypeError: AES-CTR algorithm.counter must be 16 bytes long" }, | |
| + { name: "AES-CTR", data: "aabbcc", counter: "ffffffffffffffffffffffffffffffff" }, | |
| + { name: "AES-CTR", data: "aabbccdd".repeat(5), counter: "ffffffffffffffffffffffffffffffff" }, | |
| + { name: "AES-CTR", data: "aabbccdd".repeat(4096), counter: "fffffffffffffffffffffffffffffff0" }, | |
| + { name: "AES-CTR", data: "aabbccdd".repeat(4096), counter: "ffffffffffffffffffffffffffffffff" }, | |
| + { name: "AES-CTR", data: "aabbccdd".repeat(4096), counter: "ffffffffffffffffffffffffffffffff", length: 7, | |
| + exception: "TypeError: AES-CTR repeated counter" }, | |
| + { name: "AES-CTR", data: "aabbccdd".repeat(4096), counter: "ffffffffffffffffffffffffffffffff", length: 11 }, | |
| + { name: "AES-CTR", data: "aabbccdd".repeat(4096), length: 20 }, | |
| + { name: "AES-CTR", data: "aabbccdd".repeat(4096), length: 24 }, | |
| + { name: "AES-CTR", data: "aabbccdd", length: 129, | |
| + exception: "TypeError: AES-CTR algorithm.length must be between 1 and 128" }, | |
| + | |
| + { name: "AES-CBC", data: "aa" }, | |
| + { name: "AES-CBC", data: "aabbccdd".repeat(4) }, | |
| + { name: "AES-CBC", data: "aabbccdd".repeat(4096) }, | |
| + { name: "AES-CBC", data: "aabbccdd".repeat(5), iv: "ffffffffffffffffffffffffffffffff" }, | |
| +]}; | |
| + | |
| +run([aes_tsuite], test, p); | |
| diff --git a/test/webcrypto/aes_decoding.js b/test/webcrypto/aes_decoding.js | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/aes_decoding.js | |
| @@ -0,0 +1,116 @@ | |
| +const fs = require('fs'); | |
| + | |
| +if (typeof crypto == 'undefined') { | |
| + crypto = require('crypto').webcrypto; | |
| +} | |
| + | |
| +async function run(tlist, T, prepare_args) { | |
| + function validate(t, r, i) { | |
| + if (r.status == "fulfilled" && !t[i].exception) { | |
| + return r.value === "SUCCESS"; | |
| + } | |
| + | |
| + if (r.status == "rejected" && t[i].exception) { | |
| + if (process.argv[2] === '--match-exception-text') { | |
| + /* is not compatible with node.js format */ | |
| + return r.reason.toString().startsWith(t[i].exception); | |
| + } | |
| + | |
| + return true; | |
| + } | |
| + | |
| + return false; | |
| + } | |
| + | |
| + for (let k = 0; k < tlist.length; k++) { | |
| + let ts = tlist[k]; | |
| + let results = await Promise.allSettled(ts.tests.map(t => T(prepare_args(t, ts.opts)))); | |
| + let r = results.map((r, i) => validate(ts.tests, r, i)); | |
| + | |
| + console.log(`${ts.name} ${r.every(v=>v == true) ? "SUCCESS" : "FAILED"}`); | |
| + | |
| + r.forEach((v, i) => { | |
| + if (!v) { | |
| + console.log(`FAILED ${i}: ${JSON.stringify(ts.tests[i])}\n with reason: ${results[i].reason}`); | |
| + } | |
| + }) | |
| + } | |
| +} | |
| + | |
| +function base64decode(b64) { | |
| + const joined = b64.toString().split('\n').join(''); | |
| + return Buffer.from(joined, 'base64'); | |
| +} | |
| + | |
| +function p(args, default_opts) { | |
| + let params = Object.assign({}, default_opts, args); | |
| + | |
| + params.key = Buffer.from(params.key, "hex"); | |
| + params.iv = Buffer.from(params.iv, "hex"); | |
| + params.counter = Buffer.from(params.counter, "hex"); | |
| + | |
| + switch (params.name) { | |
| + case "AES-GCM": | |
| + if (params.additionalData) { | |
| + params.additionalData = Buffer.from(params.additionalData, "hex"); | |
| + } | |
| + | |
| + break; | |
| + } | |
| + | |
| + return params; | |
| +} | |
| + | |
| +async function test(params) { | |
| + let enc = base64decode(fs.readFileSync(`test/webcrypto/${params.file}`)); | |
| + let key = await crypto.subtle.importKey("raw", params.key, | |
| + {name: params.name}, | |
| + false, ["decrypt"]); | |
| + | |
| + let plaintext = await crypto.subtle.decrypt(params, key, enc); | |
| + plaintext = new TextDecoder().decode(plaintext); | |
| + | |
| + if (params.expected != plaintext) { | |
| + throw Error(`${params.name} decoding failed expected: "${params.expected}" vs "${plaintext}"`); | |
| + } | |
| + | |
| + return 'SUCCESS'; | |
| +} | |
| + | |
| +let aes_tsuite = { | |
| + name: "AES decoding", | |
| + opts: { | |
| + key: "00112233001122330011223300112233", | |
| + iv: "44556677445566774455667744556677", | |
| + counter: "44556677445566774455667744556677", | |
| + length: 64 | |
| + }, | |
| + | |
| + tests: [ | |
| + { name: "AES-GCM", file: "text.base64.aes-gcm128.enc", | |
| + expected: "AES-GCM-SECRET-TEXT" }, | |
| + { name: "AES-GCM", file: "text.base64.aes-gcm128-96.enc", | |
| + exception: "Error: EVP_DecryptFinal_ex() failed" }, | |
| + { name: "AES-GCM", file: "text.base64.aes-gcm128-96.enc", tagLength: 96, | |
| + expected: "AES-GCM-96-TAG-LENGTH-SECRET-TEXT" }, | |
| + { name: "AES-GCM", file: "text.base64.aes-gcm128-extra.enc", additionalData: "deadbeef", | |
| + expected: "AES-GCM-ADDITIONAL-DATA-SECRET-TEXT" }, | |
| + { name: "AES-GCM", file: "text.base64.aes-gcm256.enc", | |
| + key: "0011223300112233001122330011223300112233001122330011223300112233", | |
| + expected: "AES-GCM-256-SECRET-TEXT" }, | |
| + { name: "AES-GCM", file: "text.base64.aes-gcm256.enc", | |
| + key: "00112233001122330011223300112233001122330011223300112233001122", | |
| + exception: "TypeError: AES-GCM Invalid key length" }, | |
| + { name: "AES-CTR", file: "text.base64.aes-ctr128.enc", | |
| + expected: "AES-CTR-SECRET-TEXT" }, | |
| + { name: "AES-CTR", file: "text.base64.aes-ctr256.enc", | |
| + key: "0011223300112233001122330011223300112233001122330011223300112233", | |
| + expected: "AES-CTR-256-SECRET-TEXT" }, | |
| + { name: "AES-CBC", file: "text.base64.aes-cbc128.enc", | |
| + expected: "AES-CBC-SECRET-TEXT" }, | |
| + { name: "AES-CBC", file: "text.base64.aes-cbc256.enc", | |
| + key: "0011223300112233001122330011223300112233001122330011223300112233", | |
| + expected: "AES-CBC-256-SECRET-TEXT" }, | |
| +]}; | |
| + | |
| +run([aes_tsuite], test, p); | |
| diff --git a/test/webcrypto/aes_gcm_enc.js b/test/webcrypto/aes_gcm_enc.js | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/aes_gcm_enc.js | |
| @@ -0,0 +1,51 @@ | |
| +const fs = require('fs'); | |
| + | |
| +if (typeof crypto == 'undefined') { | |
| + crypto = require('crypto').webcrypto; | |
| +} | |
| + | |
| +function parse_options(argv) { | |
| + let opts = JSON.parse(argv[2] ? argv[2] : "{}"); | |
| + | |
| + if (!opts.key) { | |
| + opts.key = Buffer.from("00112233001122330011223300112233", "hex"); | |
| + | |
| + } else { | |
| + opts.key = Buffer.from(opts.key, "hex"); | |
| + } | |
| + | |
| + if (!opts.iv) { | |
| + opts.iv = Buffer.from("44556677445566774455667744556677", "hex"); | |
| + | |
| + } else { | |
| + opts.iv = Buffer.from(opts.iv, "hex"); | |
| + } | |
| + | |
| + if (opts.additionalData) { | |
| + opts.additionalData = Buffer.from(opts.additionalData, "hex"); | |
| + } | |
| + | |
| + if (!opts['in']) { | |
| + throw Error("opts.in is expected"); | |
| + } | |
| + | |
| + return opts; | |
| +} | |
| + | |
| +(async function main() { | |
| + let opts = parse_options(process.argv); | |
| + let stdin = fs.readFileSync(`test/webcrypto/${opts['in']}`); | |
| + let key = await crypto.subtle.importKey("raw", opts.key, | |
| + {name: "AES-GCM"}, | |
| + false, ["encrypt"]); | |
| + | |
| + let params = Object.assign(opts); | |
| + params.name = "AES-GCM"; | |
| + | |
| + let enc = await crypto.subtle.encrypt(params, key, stdin); | |
| + | |
| + console.log(Buffer.from(enc).toString("base64")); | |
| +})() | |
| +.catch(e => { | |
| + console.log(`exception:${e.stack}`); | |
| +}) | |
| diff --git a/test/webcrypto/derive.js b/test/webcrypto/derive.js | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/derive.js | |
| @@ -0,0 +1,149 @@ | |
| +if (typeof crypto == 'undefined') { | |
| + crypto = require('crypto').webcrypto; | |
| +} | |
| + | |
| +async function run(tlist, T, prepare_args) { | |
| + function validate(t, r, i) { | |
| + if (r.status == "fulfilled" && !t[i].exception) { | |
| + return r.value === "SUCCESS"; | |
| + } | |
| + | |
| + if (r.status == "rejected" && t[i].exception) { | |
| + if (process.argv[2] === '--match-exception-text') { | |
| + /* is not compatible with node.js format */ | |
| + return r.reason.toString().startsWith(t[i].exception); | |
| + } | |
| + | |
| + return true; | |
| + } | |
| + | |
| + if (r.status == "rejected" && t[i].optional) { | |
| + return r.reason.toString().startsWith("InternalError: not implemented"); | |
| + } | |
| + | |
| + return false; | |
| + } | |
| + | |
| + for (let k = 0; k < tlist.length; k++) { | |
| + let ts = tlist[k]; | |
| + let results = await Promise.allSettled(ts.tests.map(t => T(prepare_args(t, ts.opts)))); | |
| + let r = results.map((r, i) => validate(ts.tests, r, i)); | |
| + | |
| + console.log(`${ts.name} ${r.every(v=>v == true) ? "SUCCESS" : "FAILED"}`); | |
| + | |
| + r.forEach((v, i) => { | |
| + if (!v) { | |
| + console.log(`FAILED ${i}: ${JSON.stringify(ts.tests[i])}\n with reason: ${results[i].reason}`); | |
| + } | |
| + }) | |
| + } | |
| +} | |
| + | |
| +function merge(to, from) { | |
| + let r = Object.assign({}, to); | |
| + Object.keys(from).forEach(v => { | |
| + if (typeof r[v] == 'object' && typeof from[v] == 'object') { | |
| + r[v] = merge(r[v], from[v]); | |
| + | |
| + } else if (typeof from[v] == 'object') { | |
| + r[v] = Object.assign({}, from[v]); | |
| + | |
| + } else { | |
| + r[v] = from[v]; | |
| + } | |
| + }) | |
| + | |
| + return r; | |
| +}; | |
| + | |
| +function p(args, default_opts) { | |
| + let params = Object.assign({}, default_opts); | |
| + params = merge(params, args); | |
| + | |
| + params.algorithm.salt = Buffer.from(params.algorithm.salt, "hex"); | |
| + params.algorithm.info = Buffer.from(params.algorithm.info, "hex"); | |
| + params.derivedAlgorithm.iv = Buffer.from(params.derivedAlgorithm.iv, "hex"); | |
| + | |
| + return params; | |
| +} | |
| + | |
| +async function test(params) { | |
| + let r; | |
| + let encoder = new TextEncoder(); | |
| + let keyMaterial = await crypto.subtle.importKey("raw", encoder.encode(params.pass), | |
| + params.algorithm.name, | |
| + false, [ "deriveBits", "deriveKey" ]); | |
| + if (params.derive === "key") { | |
| + let key = await crypto.subtle.deriveKey(params.algorithm, keyMaterial, | |
| + params.derivedAlgorithm, | |
| + true, [ "encrypt", "decrypt" ]); | |
| + | |
| + r = await crypto.subtle.encrypt(params.derivedAlgorithm, key, | |
| + encoder.encode(params.text)); | |
| + } else { | |
| + | |
| + r = await crypto.subtle.deriveBits(params.algorithm, keyMaterial, params.length); | |
| + } | |
| + | |
| + r = Buffer.from(r).toString("hex"); | |
| + | |
| + if (params.expected != r) { | |
| + throw Error(`${params.algorithm.name} failed expected: "${params.expected}" vs "${r}"`); | |
| + } | |
| + | |
| + return "SUCCESS"; | |
| +} | |
| + | |
| +let derive_tsuite = { | |
| + name: "derive", | |
| + opts: { | |
| + text: "secReT", | |
| + pass: "passW0rd", | |
| + derive: "key", | |
| + optional: false, | |
| + length: 256, | |
| + algorithm: { | |
| + name: "PBKDF2", | |
| + salt: "00112233001122330011223300112233", | |
| + hash: "SHA-256", | |
| + info: "deadbeef", | |
| + iterations: 100000 | |
| + }, | |
| + derivedAlgorithm: { | |
| + name: "AES-GCM", | |
| + length: 256, | |
| + iv: "55667788556677885566778855667788" | |
| + } | |
| + }, | |
| + | |
| + tests: [ | |
| + { expected: "e7b55c9f9fda69b87648585f76c58109174aaa400cfa" }, | |
| + { pass: "pass2", expected: "e87d1787f2807ea0e1f7e1cb265b23004c575cf2ad7e" }, | |
| + { algorithm: { iterations: 10000 }, expected: "5add0059931ed1db1ca24c26dbe4de5719c43ed18a54" }, | |
| + { algorithm: { hash: "SHA-512" }, expected: "544d64e5e246fdd2ba290ea932b2d80ef411c76139f4" }, | |
| + { algorithm: { salt: "aabbccddaabbccddaabbccddaabbccdd" }, expected: "5c1304bedf840b1f6f7d1aa804fe870a8f949d762c32" }, | |
| + { algorithm: { salt: "aabbccddaabbccddaabbccddaabb" }, | |
| + exception: "TypeError: PBKDF2 algorithm.salt must be at least 16 bytes long" }, | |
| + { derivedAlgorithm: { length: 128 }, expected: "9e2d7bcc1f21f30ec3c32af9129b64507d086d129f2a" }, | |
| + { derivedAlgorithm: { length: 32 }, | |
| + exception: "TypeError: deriveKey \"AES-GCM\" length must be 128 or 256" }, | |
| + { derivedAlgorithm: { name: "AES-CBC" }, expected: "3ad6523692d44b6a7a90be7c2721786f" }, | |
| + | |
| + { derive: "bits", expected: "6458ed6e16b998d4e646422171087be8a1ee34bed463dfcb3dcd30842b1228fe" }, | |
| + { derive: "bits", pass: "pass2", expected: "ef8f75073fcadfd504d26610c743873e297ad90340c23ddc0e5f6bdb83cbabb2" }, | |
| + { derive: "bits", algorithm: { salt: "aabbccddaabbccddaabbccddaabbccdd" }, | |
| + expected: "22ceb295aa25b59c6bc5b383a089bd6999006c03f273ce3614a4fa0d90bd29ae" }, | |
| + { derive: "bits", algorithm: { hash: "SHA-1" }, | |
| + expected: "a2fc83498f7d07b4c8180c7ebfec2af0f3a7d6cb08bf8593d41d3c5c1e1c4d67" }, | |
| + { derive: "bits", algorithm: { hash: "SHA-1" }, length: 128, | |
| + expected: "a2fc83498f7d07b4c8180c7ebfec2af0" }, | |
| + { derive: "bits", algorithm: { hash: "SHA-1" }, length: 64, | |
| + expected: "a2fc83498f7d07b4" }, | |
| + | |
| + { algorithm: { name: "HKDF" }, optional: true, | |
| + expected: "18ea069ee3317d2db02e02f4a228f50dc80d9a2396e6" }, | |
| + { derive: "bits", algorithm: { name: "HKDF" }, optional: true, | |
| + expected: "e089c7491711306c69e077aa19fae6bfd2d4a6d240b0d37317d50472d7291a3e" }, | |
| +]}; | |
| + | |
| +run([derive_tsuite], test, p); | |
| diff --git a/test/webcrypto/digest.js b/test/webcrypto/digest.js | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/digest.js | |
| @@ -0,0 +1,88 @@ | |
| +if (typeof crypto == 'undefined') { | |
| + crypto = require('crypto').webcrypto; | |
| +} | |
| + | |
| +async function run(tlist, T, prepare_args) { | |
| + function validate(t, r, i) { | |
| + if (r.status == "fulfilled" && !t[i].exception) { | |
| + return r.value === "SUCCESS"; | |
| + } | |
| + | |
| + if (r.status == "rejected" && t[i].exception) { | |
| + if (process.argv[2] === '--match-exception-text') { | |
| + /* is not compatible with node.js format */ | |
| + return r.reason.toString().startsWith(t[i].exception); | |
| + } | |
| + | |
| + return true; | |
| + } | |
| + | |
| + return false; | |
| + } | |
| + | |
| + for (let k = 0; k < tlist.length; k++) { | |
| + let ts = tlist[k]; | |
| + let results = await Promise.allSettled(ts.tests.map(t => T(prepare_args(t, ts.opts)))); | |
| + let r = results.map((r, i) => validate(ts.tests, r, i)); | |
| + | |
| + console.log(`${ts.name} ${r.every(v=>v == true) ? "SUCCESS" : "FAILED"}`); | |
| + | |
| + r.forEach((v, i) => { | |
| + if (!v) { | |
| + console.log(`FAILED ${i}: ${JSON.stringify(ts.tests[i])}\n with reason: ${results[i].reason}`); | |
| + } | |
| + }) | |
| + } | |
| +} | |
| + | |
| +function p(args) { | |
| + let params = Object.assign({}, args); | |
| + params.data = Buffer.from(params.data, "hex"); | |
| + return params; | |
| +} | |
| + | |
| +async function test(params) { | |
| + let digest = await crypto.subtle.digest(params.name, params.data); | |
| + digest = Buffer.from(digest).toString("hex"); | |
| + | |
| + if (params.expected != digest) { | |
| + throw Error(`${params.name} digest failed expected: "${params.expected}" vs "${digest}"`); | |
| + } | |
| + | |
| + return 'SUCCESS'; | |
| +} | |
| + | |
| +let digest_tsuite = { | |
| + name: "SHA digest", | |
| + opts: { }, | |
| + | |
| + tests: [ | |
| + { name: "XXX", data: "", | |
| + exception: "TypeError: unknown hash name: \"XXX\"" }, | |
| + { name: "SHA-256", data: "", | |
| + expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, | |
| + { name: "SHA-256", data: "aabbccdd", | |
| + expected: "8d70d691c822d55638b6e7fd54cd94170c87d19eb1f628b757506ede5688d297" }, | |
| + { name: "SHA-256", data: "aabbccdd".repeat(4096), | |
| + expected: "25077ac2e5ba760f015ef34b93bc2b4682b6b48a94d65e21aaf2c8a3a62f6368" }, | |
| + { name: "SHA-384", data: "", | |
| + expected: "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" }, | |
| + { name: "SHA-384", data: "aabbccdd", | |
| + expected: "f9616ef3495efbae2f6af1a754620f3034487e9c60f3a9ef8138b5ed55cdd8d18ad9565653a5d68f678bd34cfa6f4490" }, | |
| + { name: "SHA-384", data: "aabbccdd".repeat(4096), | |
| + expected: "50502d6e89bc34ecc826e0d56ccba0e010eff7b2b532e3bd627f4c828f6c741bf518fc834559360ccf7770f1b4d655d8" }, | |
| + { name: "SHA-512", data: "", | |
| + expected: "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" }, | |
| + { name: "SHA-512", data: "aabbccdd", | |
| + expected: "48e218b30d4ea16305096fe35e84002a0d262eb3853131309423492228980c60238f9eed238285036f22e37c4662e40c80a461000a7aa9a03fb3cb6e4223e83b" }, | |
| + { name: "SHA-512", data: "aabbccdd".repeat(4096), | |
| + expected: "9fcd0bd297646e207a2d655feb4ed4473e07ff24560a1e180a5eb2a67824f68affd9c7b5a8f747b9c39201f5f86a0085bb636c6fc34c216d9c10b4d728be096a" }, | |
| + { name: "SHA-1", data: "", | |
| + expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709" }, | |
| + { name: "SHA-1", data: "aabbccdd", | |
| + expected: "a7b7e9592daa0896db0517bf8ad53e56b1246923" }, | |
| + { name: "SHA-1", data: "aabbccdd".repeat(4096), | |
| + expected: "cdea58919606ea9ae078f7595b192b84446f2189" }, | |
| +]}; | |
| + | |
| +run([digest_tsuite], test, p); | |
| diff --git a/test/webcrypto/ec.pkcs8 b/test/webcrypto/ec.pkcs8 | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/ec.pkcs8 | |
| @@ -0,0 +1,5 @@ | |
| +-----BEGIN PRIVATE KEY----- | |
| +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgE2sW0/4a3QXaSTJ0 | |
| +JKbSUbieKTD1UFtr7i/2CuetP6ChRANCAARxRSxlEa5VhF4aJNCX0ypHuKvp1kiD | |
| +D7ykz4XSmElZ3ODc5/+7jc9AAN1OH4aX1cUg+FOUHIhshKDOK94wu24y | |
| +-----END PRIVATE KEY----- | |
| diff --git a/test/webcrypto/ec.spki b/test/webcrypto/ec.spki | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/ec.spki | |
| @@ -0,0 +1,4 @@ | |
| +-----BEGIN PUBLIC KEY----- | |
| +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcUUsZRGuVYReGiTQl9MqR7ir6dZI | |
| +gw+8pM+F0phJWdzg3Of/u43PQADdTh+Gl9XFIPhTlByIbISgziveMLtuMg== | |
| +-----END PUBLIC KEY----- | |
| diff --git a/test/webcrypto/ec2.pkcs8 b/test/webcrypto/ec2.pkcs8 | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/ec2.pkcs8 | |
| @@ -0,0 +1,5 @@ | |
| +-----BEGIN PRIVATE KEY----- | |
| +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg56d4aW5UtAvpKMfr | |
| +E0M8OeCN/6ES0Q1Y+DeymtgvZ2ihRANCAATj283yk3EezOOEF6FRRwfeYNyJ65bj | |
| +1jwJ8w9N0zMIedRGg0OJHnNc/uoyu6s1M/BtG/vZJ8IJNHUayiVbqxVL | |
| +-----END PRIVATE KEY----- | |
| diff --git a/test/webcrypto/ec2.spki b/test/webcrypto/ec2.spki | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/ec2.spki | |
| @@ -0,0 +1,4 @@ | |
| +-----BEGIN PUBLIC KEY----- | |
| +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE49vN8pNxHszjhBehUUcH3mDcieuW | |
| +49Y8CfMPTdMzCHnURoNDiR5zXP7qMrurNTPwbRv72SfCCTR1GsolW6sVSw== | |
| +-----END PUBLIC KEY----- | |
| diff --git a/test/webcrypto/rsa.js b/test/webcrypto/rsa.js | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/rsa.js | |
| @@ -0,0 +1,106 @@ | |
| +const fs = require('fs'); | |
| + | |
| +if (typeof crypto == 'undefined') { | |
| + crypto = require('crypto').webcrypto; | |
| +} | |
| + | |
| +async function run(tlist, T, prepare_args) { | |
| + function validate(t, r, i) { | |
| + if (r.status == "fulfilled" && !t[i].exception) { | |
| + return r.value === "SUCCESS"; | |
| + } | |
| + | |
| + if (r.status == "rejected" && t[i].exception) { | |
| + if (process.argv[2] === '--match-exception-text') { | |
| + /* is not compatible with node.js format */ | |
| + return r.reason.toString().startsWith(t[i].exception); | |
| + } | |
| + | |
| + return true; | |
| + } | |
| + | |
| + return false; | |
| + } | |
| + | |
| + for (let k = 0; k < tlist.length; k++) { | |
| + let ts = tlist[k]; | |
| + let results = await Promise.allSettled(ts.tests.map(t => T(prepare_args(t, ts.opts)))); | |
| + let r = results.map((r, i) => validate(ts.tests, r, i)); | |
| + | |
| + console.log(`${ts.name} ${r.every(v=>v == true) ? "SUCCESS" : "FAILED"}`); | |
| + | |
| + r.forEach((v, i) => { | |
| + if (!v) { | |
| + console.log(`FAILED ${i}: ${JSON.stringify(ts.tests[i])}\n with reason: ${results[i].reason}`); | |
| + } | |
| + }) | |
| + } | |
| +} | |
| + | |
| +function pem_to_der(pem, type) { | |
| + const pemJoined = pem.toString().split('\n').join(''); | |
| + const pemHeader = `-----BEGIN ${type} KEY-----`; | |
| + const pemFooter = `-----END ${type} KEY-----`; | |
| + const pemContents = pemJoined.substring(pemHeader.length, pemJoined.length - pemFooter.length); | |
| + return Buffer.from(pemContents, 'base64'); | |
| +} | |
| + | |
| +function p(args, default_opts) { | |
| + let params = Object.assign({}, default_opts, args); | |
| + | |
| + params.data = Buffer.from(params.data, "hex"); | |
| + | |
| + return params; | |
| +} | |
| + | |
| +async function test(params) { | |
| + let spki = await crypto.subtle.importKey("spki", | |
| + pem_to_der(fs.readFileSync(`test/webcrypto/${params.spki}`), "PUBLIC"), | |
| + {name:"RSA-OAEP", hash:params.spki_hash}, | |
| + false, ["encrypt"]); | |
| + | |
| + let pkcs8 = await crypto.subtle.importKey("pkcs8", | |
| + pem_to_der(fs.readFileSync(`test/webcrypto/${params.pkcs8}`), "PRIVATE"), | |
| + {name:"RSA-OAEP", hash:params.pkcs8_hash}, | |
| + false, ["decrypt"]); | |
| + | |
| + let enc = await crypto.subtle.encrypt({name: "RSA-OAEP"}, spki, params.data); | |
| + | |
| + let plaintext = await crypto.subtle.decrypt({name: "RSA-OAEP"}, pkcs8, enc); | |
| + | |
| + plaintext = Buffer.from(plaintext); | |
| + | |
| + if (params.data.compare(plaintext) != 0) { | |
| + throw Error(`RSA-OAEP encoding/decoding failed expected: "${params.data}" vs "${plaintext}"`); | |
| + } | |
| + | |
| + return 'SUCCESS'; | |
| +}; | |
| + | |
| +let rsa_tsuite = { | |
| + name: "RSA-OAEP encoding/decoding", | |
| + opts: { | |
| + spki: "rsa.spki", | |
| + spki_hash: "SHA-256", | |
| + pkcs8: "rsa.pkcs8", | |
| + pkcs8_hash: "SHA-256", | |
| + }, | |
| + | |
| + tests: [ | |
| + { data: "aabbcc" }, | |
| + { data: "aabbccdd".repeat(4) }, | |
| + { data: "aabbccdd".repeat(7) }, | |
| + { data: "aabbcc", spki_hash: "SHA-1", pkcs8_hash: "SHA-1" }, | |
| + { data: "aabbccdd".repeat(4), spki_hash: "SHA-1", pkcs8_hash: "SHA-1" }, | |
| + { data: "aabbccdd".repeat(7), spki_hash: "SHA-1", pkcs8_hash: "SHA-1" }, | |
| + { data: "aabbcc", spki_hash: "SHA-384", pkcs8_hash: "SHA-384" }, | |
| + { data: "aabbccdd".repeat(4), spki_hash: "SHA-384", pkcs8_hash: "SHA-384" }, | |
| + { data: "aabbccdd".repeat(7), spki_hash: "SHA-384", pkcs8_hash: "SHA-384" }, | |
| + | |
| + { data: "aabbcc", spki_hash: "SHA-256", pkcs8_hash: "SHA-384", exception: "Error: EVP_PKEY_decrypt() failed" }, | |
| + { data: "aabbcc", spki_hash: "XXX", exception: "TypeError: unknown hash name: \"XXX\"" }, | |
| + { data: "aabbcc", spki: "rsa.spki.broken", exception: "Error: d2i_PUBKEY() failed" }, | |
| + { data: "aabbcc", spki: "rsa2.spki", exception: "Error: EVP_PKEY_decrypt() failed" }, | |
| +]}; | |
| + | |
| +run([rsa_tsuite], test, p); | |
| diff --git a/test/webcrypto/rsa.pkcs8 b/test/webcrypto/rsa.pkcs8 | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/rsa.pkcs8 | |
| @@ -0,0 +1,16 @@ | |
| +-----BEGIN PRIVATE KEY----- | |
| +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMlJsaCQvFQDOYcm | |
| +GWvl1AWYNTdcsBTD1KVrBdZGkhnnffD911ID84F/NMKcs3eanRrgC6p39pTHOzvD | |
| +6xgbTuWK70JSPejV9I1KOW3OcM9ttKG9wFAnkJ038flBajOKQsI6A0qNj5aYSXVo | |
| +BWMphgWgQiYJxDUC/R9Tf/P8jYjfAgMBAAECgYEAj06DQyCopFujYoASi0oWmGEU | |
| +SjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJT | |
| +G5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH/kOf | |
| ++znUc7eTvuzISs61x/kCQQD0BJvbLDlvx3u6esW47LLgQNw9ufMSlu5UYBJ4c+qQ | |
| +5HAeyp4Zt/AaWENhJitjQcLBSxIFIVw7dIN67RnTNK8VAkEA0yvzzgHo/PGYSlVj | |
| ++M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr+igqLHhzfynAQjjf39VrXuPuRL23 | |
| +REF1IwJBAKVFydo0peJTljXDmc+aYb0JsSINo9jfaSS0vU3gFOt2DYqNaW+56WGu | |
| +jlRqadCcZbBNjDL1WWbbj4HevTMT59ECQEWaKgzPolykwN5XUNE0DCp1ZwIAH1kb | |
| +Bjfo+sMVt0f9S1TsN9SmBl+4l1X7CY5zU3RATMH5FR+8ns83fM1ZieMCQQDZEQ+d | |
| +FAhouzJrnCXAXDTCHA9oBtNmnaN+C6G2DmCi79iu7sLHP9vzdgU+CgjrG4YTU5ex | |
| +aRFNOhLwW4hYKs0F | |
| +-----END PRIVATE KEY----- | |
| diff --git a/test/webcrypto/rsa.pkcs8.broken b/test/webcrypto/rsa.pkcs8.broken | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/rsa.pkcs8.broken | |
| @@ -0,0 +1,16 @@ | |
| +-----BEGIN PRIVATE KEY----- | |
| +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMlJsaCQvFQDOYcm | |
| +GWvl1AWYNTdcsBTD1KVrBdZGkhnnffD911ID84F/NMKcs3eanRrgC6p39pTHOzvD | |
| +6xgbTuWK70JSPejV9I1KOW3OcM9ttKG9wFAnkJ038flBajOKQsI6A0qNj5aYSXVo | |
| +BWMphgWgQiYJxDUC/R9Tf/P8jYjfAgMBAAECgYEAj06DQyCopFujYoASi0oWmGEU | |
| +SjUYO8BsrdSzVCnsLLsuZBwlZ4Peouyw4Hl2IIoYniCyzYwZJzVtC5Dh2MjgcrJT | |
| +G5nX3FfheuabGl4in0583C51ZYWlVpDvBWw8kJTfXjiKH4z6ZA9dWdT5Y3aH/kOf | |
| ++znUc7eTvuzISs61x/kCQQD0BJvbLDlvx3u6esW47LLgQNw9ufMSlu5UYBJ4c+qQ | |
| +5HAeyp4Zt/AaWENhJitjQcLBSxIFIVw7dIN67RnTNK8VAkEA0yvzzgHo/PGYSlVj | |
| ++M3965AwQF2wTXz82MZHv6EfcCHKuBfCSecr+igqLHhzfynAQjjf39VrXuPuRL23 | |
| +REF1IwJBAKVFydo0peJTljXDmc+aYb0JsSINo9jfaSS0vU3gFOt2DYqNaW+56WGu | |
| +jlRqadCcZbBNjDL1WWbbj4HevTMT59ECQEWaKgzPolykwN5XUNE0DCp1ZwIAH1kb | |
| +Bjfo+sMVt0f9S1TsN9SmBl+4l1X7CY5zU3RATMH5FR+8ns83fM1ZieMCQQDZEQ+d | |
| +FAhouzJrnCXAXDTCHA9oBtNmnaN+C6G2DmCi79iu7sLHP9vzdgU+CgjrG4YTU5ex | |
| +aRFNOhLwW4hYKs | |
| +-----END PRIVATE KEY----- | |
| diff --git a/test/webcrypto/rsa.spki b/test/webcrypto/rsa.spki | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/rsa.spki | |
| @@ -0,0 +1,6 @@ | |
| +-----BEGIN PUBLIC KEY----- | |
| +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJSbGgkLxUAzmHJhlr5dQFmDU3 | |
| +XLAUw9SlawXWRpIZ533w/ddSA/OBfzTCnLN3mp0a4Auqd/aUxzs7w+sYG07liu9C | |
| +Uj3o1fSNSjltznDPbbShvcBQJ5CdN/H5QWozikLCOgNKjY+WmEl1aAVjKYYFoEIm | |
| +CcQ1Av0fU3/z/I2I3wIDAQAB | |
| +-----END PUBLIC KEY----- | |
| diff --git a/test/webcrypto/rsa.spki.broken b/test/webcrypto/rsa.spki.broken | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/rsa.spki.broken | |
| @@ -0,0 +1,6 @@ | |
| +-----BEGIN PUBLIC KEY----- | |
| +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJSbGgkLxUAzmHJhlr5dQFmDU3 | |
| +XLAUw9SlawXWRpIZ533w/ddSA/OBfzTCnLN3mp0a4Auqd/aUxzs7w+sYG07liu9C | |
| +Uj3o1fSNSjltznDPbbShvcBQJ5CdN/H5QWozikLCOgNKjY+WmEl1aAVjKYYFoEIm | |
| +CcQ1Av0fU3/z/I2I3IDAQAB | |
| +-----END PUBLIC KEY----- | |
| diff --git a/test/webcrypto/rsa2.pkcs8 b/test/webcrypto/rsa2.pkcs8 | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/rsa2.pkcs8 | |
| @@ -0,0 +1,16 @@ | |
| +-----BEGIN PRIVATE KEY----- | |
| +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANUukQQW3CVlMsS6 | |
| +VaRnBhExg2gTCB6tvevFupCpq6sEuX5X6+4/QUTt4/wyWeMn5amaNAjGmDtsmeKX | |
| +85MGHPuKceLhjToJT5JGFFDwVB6pNyqBuRzEHYsvQz1TlqcSdo7U/YaQtqkEZsA/ | |
| +ZAjBSR4lP9ggOBpEq+bBs+2GaZZFAgMBAAECgYB8HMdK1TBICTncdQtlUqGiouv5 | |
| +TJM+oTJgMNbkYBPU1kRUPUXbiDIsuj8wVfQlHtZDvsYqkcyRVDHnTUX+w+FctIox | |
| +OU+3bKEZ/winaO3znvPVdy59/evpvQ0rnAGsxYBmyfZTqQgCxX+nMqAsLIplzORM | |
| +5zAOawEQfRyHGETHQQJBAPe6YoMHMM/ZVpQ0xwQasbQahcL2GCD4Hwv3neGEKZVz | |
| +Aos89/qkA+78hg6OxChxSJFxK0p35lu5TqF0QFX3MNUCQQDcTOBFZaGMHnnL+2uc | |
| +tTmjKMjt47Es5G/NLg5z4cLBeeaz2St8ISbtvuVtl3K9cjeNy4J30zvF5puZrTvw | |
| +/wexAkEAsfzRcNEGyh+erCdrYlCHox53QsesOGvtapzDa9eYRQ94IXBxvzx+swPu | |
| +kaET4Pbbq9wCvaN9+CMhErHC08Eh7QJAAQWaRLgj97JsfjW8Wg29JrSZugDEYaDt | |
| +o9YC2ybA8ITQPSVUvk6pD5FDHy8EqTxOZan8APJJ5LEdJ6lWDdghAQJBANKmYYmk | |
| +OcQtU29dwuzPkwZWFdl6mhwZdcOrFcjq2pSfxKjBfygXXykscF8pHeQsjPrKK3u6 | |
| +HNED24fqNlbYHi8= | |
| +-----END PRIVATE KEY----- | |
| diff --git a/test/webcrypto/rsa2.spki b/test/webcrypto/rsa2.spki | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/rsa2.spki | |
| @@ -0,0 +1,6 @@ | |
| +-----BEGIN PUBLIC KEY----- | |
| +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVLpEEFtwlZTLEulWkZwYRMYNo | |
| +Ewgerb3rxbqQqaurBLl+V+vuP0FE7eP8MlnjJ+WpmjQIxpg7bJnil/OTBhz7inHi | |
| +4Y06CU+SRhRQ8FQeqTcqgbkcxB2LL0M9U5anEnaO1P2GkLapBGbAP2QIwUkeJT/Y | |
| +IDgaRKvmwbPthmmWRQIDAQAB | |
| +-----END PUBLIC KEY----- | |
| diff --git a/test/webcrypto/rsa_decoding.js b/test/webcrypto/rsa_decoding.js | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/rsa_decoding.js | |
| @@ -0,0 +1,81 @@ | |
| +const fs = require('fs'); | |
| + | |
| +if (typeof crypto == 'undefined') { | |
| + crypto = require('crypto').webcrypto; | |
| +} | |
| + | |
| +async function run(tlist, T, prepare_args) { | |
| + function validate(t, r, i) { | |
| + if (r.status == "fulfilled" && !t[i].exception) { | |
| + return r.value === "SUCCESS"; | |
| + } | |
| + | |
| + if (r.status == "rejected" && t[i].exception) { | |
| + if (process.argv[2] === '--match-exception-text') { | |
| + /* is not compatible with node.js format */ | |
| + return r.reason.toString().startsWith(t[i].exception); | |
| + } | |
| + | |
| + return true; | |
| + } | |
| + | |
| + return false; | |
| + } | |
| + | |
| + for (let k = 0; k < tlist.length; k++) { | |
| + let ts = tlist[k]; | |
| + let results = await Promise.allSettled(ts.tests.map(t => T(prepare_args(t, ts.opts)))); | |
| + let r = results.map((r, i) => validate(ts.tests, r, i)); | |
| + | |
| + console.log(`${ts.name} ${r.every(v=>v == true) ? "SUCCESS" : "FAILED"}`); | |
| + | |
| + r.forEach((v, i) => { | |
| + if (!v) { | |
| + console.log(`FAILED ${i}: ${JSON.stringify(ts.tests[i])}\n with reason: ${results[i].reason}`); | |
| + } | |
| + }) | |
| + } | |
| +} | |
| + | |
| +function pem_to_der(pem) { | |
| + const pemJoined = pem.toString().split('\n').join(''); | |
| + const pemHeader = '-----BEGIN PRIVATE KEY-----'; | |
| + const pemFooter = '-----END PRIVATE KEY-----'; | |
| + const pemContents = pemJoined.substring(pemHeader.length, pemJoined.length - pemFooter.length); | |
| + return Buffer.from(pemContents, 'base64'); | |
| +} | |
| + | |
| +function base64decode(b64) { | |
| + const joined = b64.toString().split('\n').join(''); | |
| + return Buffer.from(joined, 'base64'); | |
| +} | |
| + | |
| +async function test(params) { | |
| + let pem = fs.readFileSync(`test/webcrypto/${params.pem}`); | |
| + let enc = base64decode(fs.readFileSync(`test/webcrypto/${params.src}`)); | |
| + | |
| + let key = await crypto.subtle.importKey("pkcs8", pem_to_der(pem), | |
| + {name:"RSA-OAEP", hash:"SHA-1"}, | |
| + false, ["decrypt"]); | |
| + | |
| + let plaintext = await crypto.subtle.decrypt({name: "RSA-OAEP"}, key, enc); | |
| + plaintext = new TextDecoder().decode(plaintext); | |
| + | |
| + if (params.expected != plaintext) { | |
| + throw Error(`RSA-OAEP decoding failed expected: "${params.expected}" vs "${plaintext}"`); | |
| + } | |
| + | |
| + return "SUCCESS"; | |
| +} | |
| + | |
| +let rsa_tsuite = { | |
| + name: "RSA-OAEP decoding", | |
| + opts: { }, | |
| + | |
| + tests: [ | |
| + { pem: "rsa.pkcs8", src: "text.base64.rsa-oaep.enc", expected: "WAKAWAKA" }, | |
| + { pem: "ec.pkcs8", src: "text.base64.rsa-oaep.enc", exception: "Error: RSA key is not found" }, | |
| + { pem: "rsa.pkcs8.broken", src: "text.base64.rsa-oaep.enc", exception: "Error: d2i_PKCS8_PRIV_KEY_INFO_bio() failed" }, | |
| +]}; | |
| + | |
| +run([rsa_tsuite], test, (v) => v); | |
| diff --git a/test/webcrypto/sign.js b/test/webcrypto/sign.js | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/sign.js | |
| @@ -0,0 +1,282 @@ | |
| +const fs = require('fs'); | |
| +if (typeof crypto == 'undefined') { | |
| + crypto = require('crypto').webcrypto; | |
| +} | |
| + | |
| +async function run(tlist, T, prepare_args) { | |
| + function validate(t, r, i) { | |
| + if (r.status == "fulfilled" && !t[i].exception) { | |
| + return r.value === "SUCCESS"; | |
| + } | |
| + | |
| + if (r.status == "rejected" && t[i].exception) { | |
| + if (process.argv[2] === '--match-exception-text') { | |
| + /* is not compatible with node.js format */ | |
| + return r.reason.toString().startsWith(t[i].exception); | |
| + } | |
| + | |
| + return true; | |
| + } | |
| + | |
| + return false; | |
| + } | |
| + | |
| + for (let k = 0; k < tlist.length; k++) { | |
| + let ts = tlist[k]; | |
| + let results = await Promise.allSettled(ts.tests.map(t => T(prepare_args(t, ts.opts)))); | |
| + let r = results.map((r, i) => validate(ts.tests, r, i)); | |
| + | |
| + console.log(`${ts.name} ${r.every(v=>v == true) ? "SUCCESS" : "FAILED"}`); | |
| + | |
| + r.forEach((v, i) => { | |
| + if (!v) { | |
| + console.log(`FAILED ${i}: ${JSON.stringify(ts.tests[i])}\n with reason: ${results[i].reason}`); | |
| + } | |
| + }) | |
| + } | |
| +} | |
| + | |
| +function merge(to, from) { | |
| + let r = Object.assign({}, to); | |
| + Object.keys(from).forEach(v => { | |
| + if (typeof r[v] == 'object' && typeof from[v] == 'object') { | |
| + r[v] = merge(r[v], from[v]); | |
| + | |
| + } else if (typeof from[v] == 'object') { | |
| + r[v] = Object.assign({}, from[v]); | |
| + | |
| + } else { | |
| + r[v] = from[v]; | |
| + } | |
| + }) | |
| + | |
| + return r; | |
| +}; | |
| + | |
| +function pem_to_der(pem, type) { | |
| + const pemJoined = pem.toString().split('\n').join(''); | |
| + const pemHeader = `-----BEGIN ${type} KEY-----`; | |
| + const pemFooter = `-----END ${type} KEY-----`; | |
| + const pemContents = pemJoined.substring(pemHeader.length, pemJoined.length - pemFooter.length); | |
| + return Buffer.from(pemContents, 'base64'); | |
| +} | |
| + | |
| +function base64decode(b64) { | |
| + const joined = b64.toString().split('\n').join(''); | |
| + return Buffer.from(joined, 'base64'); | |
| +} | |
| + | |
| +function p(args, default_opts) { | |
| + let key; | |
| + let encoder = new TextEncoder(); | |
| + let params = merge({}, default_opts); | |
| + params = merge(params, args); | |
| + | |
| + switch (params.sign_key.fmt) { | |
| + case "pkcs8": | |
| + let pem = fs.readFileSync(`test/webcrypto/${params.sign_key.key}`); | |
| + key = pem_to_der(pem, "PRIVATE"); | |
| + break; | |
| + case "raw": | |
| + key = encoder.encode(params.sign_key.key); | |
| + break; | |
| + default: | |
| + throw Error("Unknown sign key format"); | |
| + } | |
| + | |
| + params.sign_key.key = key; | |
| + | |
| + switch (params.verify_key.fmt) { | |
| + case "spki": | |
| + let pem = fs.readFileSync(`test/webcrypto/${params.verify_key.key}`); | |
| + key = pem_to_der(pem, "PUBLIC"); | |
| + break; | |
| + case "raw": | |
| + key = encoder.encode(params.verify_key.key); | |
| + break; | |
| + default: | |
| + throw Error("Unknown verify key format"); | |
| + } | |
| + | |
| + params.verify_key.key = key; | |
| + | |
| + return params; | |
| +} | |
| + | |
| +async function test(params) { | |
| + let encoder = new TextEncoder(); | |
| + let sign_key = await crypto.subtle.importKey(params.sign_key.fmt, | |
| + params.sign_key.key, | |
| + params.import_alg, | |
| + false, [ "sign" ]); | |
| + | |
| + let sig = await crypto.subtle.sign(params.sign_alg, sign_key, | |
| + encoder.encode(params.text)); | |
| + | |
| + if (params.verify) { | |
| + let verify_key = await crypto.subtle.importKey(params.verify_key.fmt, | |
| + params.verify_key.key, | |
| + params.import_alg, | |
| + false, [ "verify" ]); | |
| + | |
| + let r = await crypto.subtle.verify(params.sign_alg, verify_key, sig, | |
| + encoder.encode(params.text)); | |
| + | |
| + if (params.expected !== r) { | |
| + throw Error(`${params.sign_alg.name} failed expected: "${params.expected}" vs "${r}"`); | |
| + } | |
| + | |
| + if (params.expected === true) { | |
| + let broken_sig = Buffer.concat([Buffer.from(sig)]); | |
| + broken_sig[8] = 255 - broken_sig[8]; | |
| + | |
| + r = await crypto.subtle.verify(params.sign_alg, verify_key, broken_sig, | |
| + encoder.encode(params.text)); | |
| + if (r !== false) { | |
| + throw Error(`${params.sign_alg.name} BROKEN SIG failed expected: "false" vs "${r}"`); | |
| + } | |
| + | |
| + let broken_text = encoder.encode(params.text); | |
| + broken_text[0] = 255 - broken_text[0]; | |
| + | |
| + r = await crypto.subtle.verify(params.sign_alg, verify_key, sig, | |
| + broken_text); | |
| + if (r !== false) { | |
| + throw Error(`${params.sign_alg.name} BROKEN TEXT failed expected: "false" vs "${r}"`); | |
| + } | |
| + } | |
| + | |
| + } else { | |
| + sig = Buffer.from(sig).toString("hex"); | |
| + | |
| + if (params.expected !== sig) { | |
| + throw Error(`${params.sign_alg.name} failed expected: "${params.expected}" vs "${sig}"`); | |
| + } | |
| + } | |
| + | |
| + | |
| + return "SUCCESS"; | |
| +} | |
| + | |
| +let hmac_tsuite = { | |
| + name: "HMAC sign", | |
| + opts: { | |
| + text: "TExt-T0-SiGN", | |
| + sign_key: { key: "secretKEY", fmt: "raw" }, | |
| + verify_key: { key: "secretKEY", fmt: "raw" }, | |
| + verify: false, | |
| + import_alg: { | |
| + name: "HMAC", | |
| + hash: "SHA-256", | |
| + }, | |
| + sign_alg: { | |
| + name: "HMAC", | |
| + }, | |
| + }, | |
| + | |
| + tests: [ | |
| + { expected: "76d4f1b22d7544c34e86380c9ab7c756311810dc31e4af3b705045d263db1212" }, | |
| + { import_alg: { hash: "SHA-384" }, | |
| + expected: "4bdaa7e80868a9cda35ad78ae5d88c29f1ff97680317c5bc3df1deccf2dad0cf3edce945ed90ec53fa48d887a04d4963" }, | |
| + { import_alg: { hash: "SHA-512" }, | |
| + expected: "9dd589ae5e75b6fb8d453c072cc05e6f5eb3d29034d3a0df2559ffe158f3f99fef98a9d1ab2fca459cceea0be3cb7aa3269d77fc9382b56a9cd0571851339938" }, | |
| + { import_alg: { hash: "SHA-1" }, | |
| + expected: "0540c587e7ee607fb4fd5e814438ed50f261c244" }, | |
| + { sign_alg: { name: "ECDSA" }, exception: "TypeError: cannot sign using \"HMAC\" with \"ECDSA\" key" }, | |
| + | |
| + { verify: true, expected: true }, | |
| + { verify: true, import_alg: { hash: "SHA-384" }, expected: true }, | |
| + { verify: true, import_alg: { hash: "SHA-512" }, expected: true }, | |
| + { verify: true, import_alg: { hash: "SHA-1" }, expected: true }, | |
| + { verify: true, verify_key: { key: "secretKEY2" }, expected: false }, | |
| +]}; | |
| + | |
| +let rsassa_pkcs1_v1_5_tsuite = { | |
| + name: "RSASSA-PKCS1-v1_5 sign", | |
| + opts: { | |
| + text: "TExt-T0-SiGN", | |
| + sign_key: { key: "rsa.pkcs8", fmt: "pkcs8" }, | |
| + verify_key: { key: "rsa.spki", fmt: "spki" }, | |
| + import_alg: { | |
| + name: "RSASSA-PKCS1-v1_5", | |
| + hash: "SHA-256", | |
| + }, | |
| + sign_alg: { | |
| + name: "RSASSA-PKCS1-v1_5", | |
| + }, | |
| + }, | |
| + | |
| + tests: [ | |
| + { expected: "b126c528abd305dc2b7234de44ffa2190bd55f57087f75620196e8bdb05ba205e52ceca03e4799f30a6d61a6610878b1038a5dd869ab8c04ffe80d49d14407b2c2fe52ca78c9c409fcf7fee26188941f5072179c2bf2de43e637b089c32cf04f14ca01e7b9c33bbbec603b2815de0180b12a3269b0453aba158642e00303890d" }, | |
| + { import_alg: { hash: "SHA-512" }, | |
| + expected: "174adca014132f5b9871e1bda2c23fc50f57673c6915b9170d601c626022a03d66c1b8c2a4b8efa08edee83ad27cc05c0d33c7a52a9125fa5be0f99be40483d8123570f91d53f2af51ef0f2b43987182fd114db242f146ea0d7c4ead5d4a11043f83e67d5400fc66dc2b08d7d63122fcd11b495fb4115ecf57c51994f6c516b9" }, | |
| + { import_alg: { hash: "SHA-1" }, | |
| + expected: "0cc6377ae31a1b09a7c0a18d12e785e9734565bdeb808b3e41d8bc03adab9ffbd8b1764830fea8f1d8f327034f24296f3aad6112cc3a380db6ef01989f8f9cb608f75b1d9558c36785b6f932ee06729b139b5f02bb886fd1d4fb0f06246064993a421e55579c490c77c27a44c7cc0ea7dd6579cc69402177712ba0f69cac967d" }, | |
| + | |
| + { verify: true, expected: true }, | |
| + { verify: true, import_alg: { hash: "SHA-512" }, expected: true }, | |
| + { verify: true, import_alg: { hash: "SHA-1" }, expected: true }, | |
| + { verify: true, verify_key: { key: "rsa2.spki" }, expected: false }, | |
| +]}; | |
| + | |
| +let rsa_pss_tsuite = { | |
| + name: "RSA-PSS sign", | |
| + opts: { | |
| + text: "TExt-T0-SiGN", | |
| + sign_key: { key: "rsa.pkcs8", fmt: "pkcs8" }, | |
| + verify_key: { key: "rsa.spki", fmt: "spki" }, | |
| + import_alg: { | |
| + name: "RSA-PSS", | |
| + hash: "SHA-256", | |
| + }, | |
| + sign_alg: { | |
| + name: "RSA-PSS", | |
| + saltLength: 0, | |
| + }, | |
| + }, | |
| + | |
| + tests: [ | |
| + { expected: "c126f05ea6e13b3208540bd833f5886d95fe2c89f9b3102b564c9da3bc0c00d224e6ed9be664dee61dfcc0eee790f816c5cf6a0ffc320112d818b72d57de9adbb31d239c225d42395c906bde719bf4ad21c18c679d70186d2efc044fc4995773c5085c64c6d9b7a5fc96dd28176e2cd702a9f35fe64b960f21523ec19bb44408" }, | |
| + { import_alg: { hash: "SHA-512" }, expected: "3764287839843d25cb8ad109d0ffffd54a8f47fae02e9d2fa8a9363a7b0f98d0ede417c57c0d99a8c11cd502bbc95767a5f437b99cb30341c7af840889633e08cfdaae472bed3e68d451c67182ccd583457c6a9cf81c7e17fb391606f1bc02a83253975f153582ca1c31e9ba9b89dec4bf1d2a9b7b5024dd4dde317432ff26b1" }, | |
| + { import_alg: { hash: "SHA-1" }, expected: "73d39d22b028b13142b257d405a4a09d0622b97ef7b74e0953274744a76fedee0f283b678cfcaa8e4c38ef84033259f84c59ae987f9d049adea4379a9b0addb9f8b53ee6b64a4e32d8165d057444a1056706da648b88c6a4613022e03be5b6b9e8948d9527a95478f871bfe88dbc67127b038520af3400b942c85e0733bcad27" }, | |
| + | |
| + { verify: true, expected: true }, | |
| + { verify: true, import_alg: { hash: "SHA-512" }, expected: true }, | |
| + { verify: true, sign_alg: { saltLength: 32 }, expected: true }, | |
| + { verify: true, import_alg: { hash: "SHA-512" }, sign_alg: { saltLength: 32 }, | |
| + expected: true }, | |
| + { verify: true, verify_key: { key: "rsa2.spki" }, expected: false }, | |
| +]}; | |
| + | |
| +let ecdsa_tsuite = { | |
| + name: "ECDSA sign", | |
| + opts: { | |
| + text: "TExt-T0-SiGN", | |
| + sign_key: { key: "ec.pkcs8", fmt: "pkcs8" }, | |
| + verify_key: { key: "ec.spki", fmt: "spki" }, | |
| + import_alg: { | |
| + name: "ECDSA", | |
| + namedCurve: "P-256", | |
| + }, | |
| + sign_alg: { | |
| + name: "ECDSA", | |
| + hash: "SHA-256", | |
| + }, | |
| + }, | |
| + | |
| + tests: [ | |
| + { verify: true, expected: true }, | |
| + { verify: true, import_alg: { hash: "SHA-384" }, expected: true }, | |
| + { verify: true, import_alg: { hash: "SHA-512" }, expected: true }, | |
| + { verify: true, import_alg: { hash: "SHA-1" }, expected: true }, | |
| + { verify: true, verify_key: { key: "ec2.spki" }, expected: false }, | |
| + { verify: true, verify_key: { key: "rsa.spki" }, exception: "Error: EC key is not found" }, | |
| + { verify: true, import_alg: { namedCurve: "P-384" }, exception: "Error: name curve mismatch" }, | |
| +]}; | |
| + | |
| +run([ | |
| + hmac_tsuite, | |
| + rsassa_pkcs1_v1_5_tsuite, | |
| + rsa_pss_tsuite, | |
| + ecdsa_tsuite | |
| +], test, p); | |
| diff --git a/test/webcrypto/text.base64.aes-cbc128.enc b/test/webcrypto/text.base64.aes-cbc128.enc | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.aes-cbc128.enc | |
| @@ -0,0 +1,1 @@ | |
| +pKzTDFjJuyyWxBpM0++pVETg9638AXJwa9yXCL3Av0c= | |
| diff --git a/test/webcrypto/text.base64.aes-cbc256.enc b/test/webcrypto/text.base64.aes-cbc256.enc | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.aes-cbc256.enc | |
| @@ -0,0 +1,1 @@ | |
| +3gnuDWCYtwPW5TMPtj1LM/uJxnKknbvPn9gURBEcegE= | |
| diff --git a/test/webcrypto/text.base64.aes-ctr128.enc b/test/webcrypto/text.base64.aes-ctr128.enc | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.aes-ctr128.enc | |
| @@ -0,0 +1,1 @@ | |
| +UsVG2TjNHGbXaTZ3fG67MsxXPw== | |
| diff --git a/test/webcrypto/text.base64.aes-ctr256.enc b/test/webcrypto/text.base64.aes-ctr256.enc | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.aes-ctr256.enc | |
| @@ -0,0 +1,1 @@ | |
| +jnIqHDDRajcKkHwo4IormMSsBSDEI40= | |
| diff --git a/test/webcrypto/text.base64.aes-gcm128-96.enc b/test/webcrypto/text.base64.aes-gcm128-96.enc | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.aes-gcm128-96.enc | |
| @@ -0,0 +1,1 @@ | |
| +z4NZNzf3eauJvuFQTopsTxPSERfT4lpbnK1ILuqw81OBxqw8cpheqTfXi7U5 | |
| diff --git a/test/webcrypto/text.base64.aes-gcm128-extra.enc b/test/webcrypto/text.base64.aes-gcm128-extra.enc | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.aes-gcm128-extra.enc | |
| @@ -0,0 +1,1 @@ | |
| +z4NZNzf3eavxzIhNW4QOTRfQewfam0gzjLpOKIKwm1+QJfR0ElIvNEPnKHx4d+OxJMpT | |
| diff --git a/test/webcrypto/text.base64.aes-gcm128.enc b/test/webcrypto/text.base64.aes-gcm128.enc | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.aes-gcm128.enc | |
| @@ -0,0 +1,1 @@ | |
| +z4NZNzf3eavjzY9WSplsVxPEAuftE7KpHQoIoS+yI/lPxDk= | |
| diff --git a/test/webcrypto/text.base64.aes-gcm256.enc b/test/webcrypto/text.base64.aes-gcm256.enc | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.aes-gcm256.enc | |
| @@ -0,0 +1,1 @@ | |
| +JCRgSCD1e7h0ogveLrzbaUBby151RIajzxFhHyD4JpD36kBOL2Kz | |
| diff --git a/test/webcrypto/text.base64.rsa-oaep.enc b/test/webcrypto/text.base64.rsa-oaep.enc | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.rsa-oaep.enc | |
| @@ -0,0 +1,3 @@ | |
| +lRLk3t6LYvQBJkOYqWYSWYcHaPmsskb+vgcV3bwnfHF2MNp5oALe14mn/4m759oWCQIgXA/3kC1E | |
| +kHXOBERS2+wKOXD2hS68kZnnrfWq6//7yw7Fvzv9OjG5mYSUaKcO/p+zaJmorsgvOy+nyZJs+BPD | |
| +dKU3ohuz1MJ0wrGkki4= | |
| diff --git a/test/webcrypto/text.base64.sha1.ecdsa.sig b/test/webcrypto/text.base64.sha1.ecdsa.sig | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.sha1.ecdsa.sig | |
| @@ -0,0 +1,2 @@ | |
| +MEQCIAZ/sGPfuYivvm5UsqZgiR2jtT88d2moIgnAh6h1jKdVAiALKiu3myhI046rhEThSLyReuTu | |
| +eIEgeCPBa2xGZnFXEg== | |
| diff --git a/test/webcrypto/text.base64.sha1.hmac.sig b/test/webcrypto/text.base64.sha1.hmac.sig | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.sha1.hmac.sig | |
| @@ -0,0 +1,1 @@ | |
| +eVw25ESkzl+mDQs7z5VGkxqneZ4= | |
| diff --git a/test/webcrypto/text.base64.sha1.pkcs1.sig b/test/webcrypto/text.base64.sha1.pkcs1.sig | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.sha1.pkcs1.sig | |
| @@ -0,0 +1,3 @@ | |
| +H4lVoQebJkYFFJyXwBT2C6QDJ2OUQhQ153WjnOzaXLtlUtHdI7EOv8/hJ84ojDRJ4IyLXtGO8up9 | |
| +3WUIPw1tfwAI3X36MbMN04+HKzVabg4cTy0HnFu3k7D2hq+1vn6rT1Q7xT9C2SJBFmR/HxC2oHKz | |
| +NcpELOP8crsoqu0c3QY= | |
| diff --git a/test/webcrypto/text.base64.sha1.rsa-pss.16.sig b/test/webcrypto/text.base64.sha1.rsa-pss.16.sig | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.sha1.rsa-pss.16.sig | |
| @@ -0,0 +1,3 @@ | |
| +aHWhiEOYGTRZyJNeNoEELFFyN+ZYgFLI+rNyuuaQpLEWDHqbUY0tdGenvIiiUxN0GKq/72g6CvyH | |
| +RXq9VUL4Q+qkDbmROzBC0/P+RqgxpcNVJQx04RyGRVnw+l+GzE3rwbDCQG+95okBOxnac21thk/k | |
| +GBJQGXhimg6XkCK3vVo= | |
| diff --git a/test/webcrypto/text.base64.sha256.ecdsa.sig b/test/webcrypto/text.base64.sha256.ecdsa.sig | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.sha256.ecdsa.sig | |
| @@ -0,0 +1,2 @@ | |
| +MEUCIFEw11evEWohKswRe3Za0P0u7mvGj4kSnHix/EOKhxApAiEAq2QtwNvFg8RdY6t01ff8mUTP | |
| +nT1lEfMSRZmtuVxQuQA= | |
| diff --git a/test/webcrypto/text.base64.sha256.hmac.sig b/test/webcrypto/text.base64.sha256.hmac.sig | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.sha256.hmac.sig | |
| @@ -0,0 +1,1 @@ | |
| +UbsmYPe0ek53QMpDPP/Y4V50bRXQQGNrYCTuzg1tznE= | |
| diff --git a/test/webcrypto/text.base64.sha256.hmac.sig.broken b/test/webcrypto/text.base64.sha256.hmac.sig.broken | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.sha256.hmac.sig.broken | |
| @@ -0,0 +1,1 @@ | |
| +UbsmYPe0ek53QMpDPP/Y4V50bRXQQGNrYCAuzg1tznE= | |
| diff --git a/test/webcrypto/text.base64.sha256.pkcs1.sig b/test/webcrypto/text.base64.sha256.pkcs1.sig | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.sha256.pkcs1.sig | |
| @@ -0,0 +1,3 @@ | |
| +IyGM/e2IMJCrJmE91GkddTyCble3554d8KvpbL1k10QrRlDE1afCab7iwmz4j1yl8pNMbSTKIb0y | |
| +RfMQ3YlsKxzoJm2+pIrXErb2jF3emnVkUxNuSY3/ROK3rU8YirPbKhvkjulVwVlh4b6YpiXwuKTL | |
| +HDmHp7AOr7yzjS3VZrs= | |
| diff --git a/test/webcrypto/text.base64.sha256.rsa-pss.0.sig b/test/webcrypto/text.base64.sha256.rsa-pss.0.sig | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.sha256.rsa-pss.0.sig | |
| @@ -0,0 +1,3 @@ | |
| +DNQrIYaW4opZG1OdyFujH2rxgk06HB2eTUuCGyiN971pAVxCqYn0NhL7iMBrUrgsxnqBH+nNC1jg | |
| +AMFGe0rtJE/9blWb9QiNz/kwitFI4oztXkcCHcQYwatbQTGQgqeA2rY9N6w6QwMAYJeEd4Jm0lTE | |
| +oJx9N+C5QoArKBPgXxw= | |
| diff --git a/test/webcrypto/text.base64.sha256.rsa-pss.32.sig b/test/webcrypto/text.base64.sha256.rsa-pss.32.sig | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/text.base64.sha256.rsa-pss.32.sig | |
| @@ -0,0 +1,3 @@ | |
| +B+69Fvykn5ncPxA91DQv7K5r5D25f0LuEK60h4WNWev8Hl/Bzseq315o/Ja7RhlgtYltBZTETqUk | |
| +hkaWEWExCG1kw+xUbsz1HsdHJOwF+e52zirKGifondHVqTOl95aU5msRcuKtCEifnvgKqNE9c+Dz | |
| +l1hoPlgGrhtnyI0DlcM= | |
| diff --git a/test/webcrypto/verify.js b/test/webcrypto/verify.js | |
| new file mode 100644 | |
| --- /dev/null | |
| +++ b/test/webcrypto/verify.js | |
| @@ -0,0 +1,207 @@ | |
| +const fs = require('fs'); | |
| + | |
| +if (typeof crypto == 'undefined') { | |
| + crypto = require('crypto').webcrypto; | |
| +} | |
| + | |
| +async function run(tlist, T, prepare_args) { | |
| + function validate(t, r, i) { | |
| + if (r.status == "fulfilled" && !t[i].exception) { | |
| + return r.value === "SUCCESS"; | |
| + } | |
| + | |
| + if (r.status == "rejected" && t[i].exception) { | |
| + if (process.argv[2] === '--match-exception-text') { | |
| + /* is not compatible with node.js format */ | |
| + return r.reason.toString().startsWith(t[i].exception); | |
| + } | |
| + | |
| + return true; | |
| + } | |
| + | |
| + return false; | |
| + } | |
| + | |
| + for (let k = 0; k < tlist.length; k++) { | |
| + let ts = tlist[k]; | |
| + let results = await Promise.allSettled(ts.tests.map(t => T(prepare_args(t, ts.opts)))); | |
| + let r = results.map((r, i) => validate(ts.tests, r, i)); | |
| + | |
| + console.log(`${ts.name} ${r.every(v=>v == true) ? "SUCCESS" : "FAILED"}`); | |
| + | |
| + r.forEach((v, i) => { | |
| + if (!v) { | |
| + console.log(`FAILED ${i}: ${JSON.stringify(ts.tests[i])}\n with reason: ${results[i].reason}`); | |
| + } | |
| + }) | |
| + } | |
| +} | |
| + | |
| +function merge(to, from) { | |
| + let r = Object.assign({}, to); | |
| + Object.keys(from).forEach(v => { | |
| + if (typeof r[v] == 'object' && typeof from[v] == 'object') { | |
| + r[v] = merge(r[v], from[v]); | |
| + | |
| + } else if (typeof from[v] == 'object') { | |
| + r[v] = Object.assign({}, from[v]); | |
| + | |
| + } else { | |
| + r[v] = from[v]; | |
| + } | |
| + }) | |
| + | |
| + return r; | |
| +}; | |
| + | |
| +function base64decode(b64) { | |
| + const joined = b64.toString().split('\n').join(''); | |
| + return Buffer.from(joined, 'base64'); | |
| +} | |
| + | |
| +function pem_to_der(pem, type) { | |
| + const pemJoined = pem.toString().split('\n').join(''); | |
| + const pemHeader = `-----BEGIN ${type} KEY-----`; | |
| + const pemFooter = `-----END ${type} KEY-----`; | |
| + const pemContents = pemJoined.substring(pemHeader.length, pemJoined.length - pemFooter.length); | |
| + return Buffer.from(pemContents, 'base64'); | |
| +} | |
| + | |
| +function p(args, default_opts) { | |
| + let encoder = new TextEncoder(); | |
| + let params = merge({}, default_opts); | |
| + params = merge(params, args); | |
| + | |
| + switch (params.key.fmt) { | |
| + case "spki": | |
| + let pem = fs.readFileSync(`test/webcrypto/${params.key.file}`); | |
| + params.key.file = pem_to_der(pem, "PUBLIC"); | |
| + break; | |
| + case "raw": | |
| + params.key.file = Buffer.from(params.key.file, "hex"); | |
| + break; | |
| + } | |
| + | |
| + params.signature = base64decode(fs.readFileSync(`test/webcrypto/${params.signature}`)); | |
| + params.text = encoder.encode(params.text); | |
| + | |
| + return params; | |
| +} | |
| + | |
| + | |
| +async function test(params) { | |
| + let key = await crypto.subtle.importKey(params.key.fmt, | |
| + params.key.file, | |
| + params.import_alg, | |
| + false, ["verify"]); | |
| + | |
| + let r = await crypto.subtle.verify(params.verify_alg, | |
| + key, params.signature, | |
| + params.text); | |
| + | |
| + if (params.expected !== r) { | |
| + throw Error(`${params.import_alg.name} failed expected: "${params.expected}" vs "${r}"`); | |
| + } | |
| + | |
| + return 'SUCCESS'; | |
| +} | |
| + | |
| +let hmac_tsuite = { | |
| + name: "HMAC verify", | |
| + opts: { | |
| + text: "SigneD-TExt", | |
| + key: { fmt: "raw", file: "aabbcc" }, | |
| + import_alg: { | |
| + name: "HMAC", | |
| + hash: "SHA-256", | |
| + }, | |
| + verify_alg: { | |
| + name: "HMAC", | |
| + }, | |
| + }, | |
| + | |
| + tests: [ | |
| + { signature: "text.base64.sha256.hmac.sig", expected: true }, | |
| + { signature: "text.base64.sha256.hmac.sig.broken", expected: false }, | |
| + { import_alg: { hash: "SHA-1" }, signature: "text.base64.sha1.hmac.sig", expected: true }, | |
| + { import_alg: { hash: "SHA-1" }, signature: "text.base64.sha256.hmac.sig", expected: false }, | |
| + { key: { file: "aabbccdd" }, signature: "text.base64.sha256.hmac.sig", expected: false }, | |
| +]}; | |
| + | |
| +let rsassa_pkcs1_v1_5_tsuite = { | |
| + name: "RSASSA-PKCS1-v1_5 verify", | |
| + opts: { | |
| + text: "SigneD-TExt", | |
| + key: { fmt: "spki", file: "rsa.spki" }, | |
| + import_alg: { | |
| + name: "RSASSA-PKCS1-v1_5", | |
| + hash: "SHA-256", | |
| + }, | |
| + verify_alg: { | |
| + name: "RSASSA-PKCS1-v1_5", | |
| + }, | |
| + }, | |
| + | |
| + tests: [ | |
| + { signature: "text.base64.sha256.pkcs1.sig", expected: true }, | |
| + { text: "SigneD-TExt2", signature: "text.base64.sha256.pkcs1.sig", expected: false }, | |
| + { signature: "text.base64.sha1.pkcs1.sig", expected: false }, | |
| + { import_alg: { hash: "SHA-1" }, signature: "text.base64.sha1.pkcs1.sig", expected: true }, | |
| + { key: { file: "rsa2.spki"}, signature: "text.base64.sha256.pkcs1.sig", expected: false }, | |
| +]}; | |
| + | |
| +let rsa_pss_tsuite = { | |
| + name: "RSA-PSS verify", | |
| + opts: { | |
| + text: "SigneD-TExt", | |
| + key: { fmt: "spki", file: "rsa.spki" }, | |
| + import_alg: { | |
| + name: "RSA-PSS", | |
| + hash: "SHA-256", | |
| + }, | |
| + verify_alg: { | |
| + name: "RSA-PSS", | |
| + saltLength: 32, | |
| + }, | |
| + }, | |
| + | |
| + tests: [ | |
| + { signature: "text.base64.sha256.rsa-pss.32.sig", expected: true }, | |
| + { text: "SigneD-TExt2", signature: "text.base64.sha256.rsa-pss.32.sig", expected: false }, | |
| + { key: { file: "rsa2.spki"}, signature: "text.base64.sha256.rsa-pss.32.sig", expected: false }, | |
| + { verify_alg: { saltLength: 0 }, signature: "text.base64.sha256.rsa-pss.0.sig", expected: true }, | |
| + { verify_alg: { saltLength: 0 }, signature: "text.base64.sha256.rsa-pss.0.sig", expected: true }, | |
| + { import_alg: { hash: "SHA-1" }, signature: "text.base64.sha256.rsa-pss.32.sig", expected: false }, | |
| + { import_alg: { hash: "SHA-1" }, verify_alg: { saltLength: 16 }, signature: "text.base64.sha1.rsa-pss.16.sig", | |
| + expected: true }, | |
| + { verify_alg: { saltLength: 16 }, signature: "text.base64.sha256.rsa-pss.32.sig", expected: false }, | |
| +]}; | |
| + | |
| +let ecdsa_tsuite = { | |
| + name: "ECDSA verify", | |
| + opts: { | |
| + text: "SigneD-TExt", | |
| + key: { fmt: "spki", file: "ec.spki" }, | |
| + import_alg: { | |
| + name: "ECDSA", | |
| + namedCurve: "P-256", | |
| + }, | |
| + verify_alg: { | |
| + name: "ECDSA", | |
| + hash: "SHA-256", | |
| + }, | |
| + }, | |
| + | |
| + tests: [ | |
| + { signature: "text.base64.sha256.ecdsa.sig", expected: true }, | |
| + { signature: "text.base64.sha1.ecdsa.sig", expected: false }, | |
| + { verify_alg: { hash: "SHA-1"}, signature: "text.base64.sha1.ecdsa.sig", expected: true }, | |
| + { key: { file: "ec2.spki" }, signature: "text.base64.sha256.ecdsa.sig", expected: false }, | |
| +]}; | |
| + | |
| +run([ | |
| + hmac_tsuite, | |
| + rsassa_pkcs1_v1_5_tsuite, | |
| + rsa_pss_tsuite, | |
| + ecdsa_tsuite, | |
| +], test, p); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment