[njs] QuickJS: crypto module.
noreply at nginx.com
noreply at nginx.com
Tue Mar 18 21:46:02 UTC 2025
details: https://github.com/nginx/njs/commit/d5359d17f151bf172697e2ac353377b469b64c0f
branches: master
commit: d5359d17f151bf172697e2ac353377b469b64c0f
user: Vadim Zhestikov <v.zhestikov at f5.com>
date: Wed, 5 Mar 2025 18:54:41 -0800
description:
QuickJS: crypto module.
---
auto/qjs_modules | 6 +
external/qjs_crypto_module.c | 659 ++++++++++++++++++++++++++++++++++++++++
external/qjs_webcrypto_module.c | 23 --
src/qjs.c | 103 +++++++
src/qjs.h | 21 +-
src/test/njs_unit_test.c | 265 ----------------
test/crypto.t.mjs | 447 +++++++++++++++++++++++++++
7 files changed, 1235 insertions(+), 289 deletions(-)
diff --git a/auto/qjs_modules b/auto/qjs_modules
index d82f9d9f..1381d2c5 100644
--- a/auto/qjs_modules
+++ b/auto/qjs_modules
@@ -7,6 +7,12 @@ njs_module_srcs=src/qjs_buffer.c
. auto/qjs_module
+njs_module_name=qjs_crypto_module
+njs_module_incs=
+njs_module_srcs=external/qjs_crypto_module.c
+
+. auto/qjs_module
+
njs_module_name=qjs_fs_module
njs_module_incs=
njs_module_srcs=external/qjs_fs_module.c
diff --git a/external/qjs_crypto_module.c b/external/qjs_crypto_module.c
new file mode 100644
index 00000000..2b1bb26b
--- /dev/null
+++ b/external/qjs_crypto_module.c
@@ -0,0 +1,659 @@
+
+/*
+ * Copyright (C) Vadim Zhestkov
+ * Copyright (C) F5, Inc.
+ */
+
+#include <qjs.h>
+#include "njs_hash.h"
+
+typedef void (*qjs_hash_init)(njs_hash_t *ctx);
+typedef void (*qjs_hash_update)(njs_hash_t *ctx, const void *data, size_t size);
+typedef void (*qjs_hash_final)(u_char result[32], njs_hash_t *ctx);
+
+typedef JSValue (*qjs_digest_encode)(JSContext *cx, const njs_str_t *src);
+
+
+typedef struct {
+ njs_str_t name;
+
+ size_t size;
+ qjs_hash_init init;
+ qjs_hash_update update;
+ qjs_hash_final final;
+} qjs_hash_alg_t;
+
+typedef struct {
+ njs_hash_t ctx;
+ qjs_hash_alg_t *alg;
+} qjs_digest_t;
+
+typedef struct {
+ u_char opad[64];
+ njs_hash_t ctx;
+ qjs_hash_alg_t *alg;
+} qjs_hmac_t;
+
+
+typedef struct {
+ njs_str_t name;
+
+ qjs_digest_encode encode;
+} qjs_crypto_enc_t;
+
+
+static qjs_hash_alg_t *qjs_crypto_algorithm(JSContext *cx, JSValueConst val);
+static qjs_crypto_enc_t *qjs_crypto_encoding(JSContext *cx, JSValueConst val);
+static JSValue qjs_buffer_digest(JSContext *cx, const njs_str_t *src);
+static JSValue qjs_crypto_create_hash(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue qjs_hash_prototype_update(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int hmac);
+static JSValue qjs_hash_prototype_digest(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int hmac);
+static JSValue qjs_hash_prototype_copy(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue qjs_crypto_create_hmac(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static void qjs_hash_finalizer(JSRuntime *rt, JSValue val);
+static void qjs_hmac_finalizer(JSRuntime *rt, JSValue val);
+static int qjs_crypto_module_init(JSContext *cx, JSModuleDef *m);
+static JSModuleDef * qjs_crypto_init(JSContext *cx, const char *module_name);
+
+
+static qjs_hash_alg_t qjs_hash_algorithms[] = {
+
+ {
+ njs_str("md5"),
+ 16,
+ njs_md5_init,
+ njs_md5_update,
+ njs_md5_final
+ },
+
+ {
+ njs_str("sha1"),
+ 20,
+ njs_sha1_init,
+ njs_sha1_update,
+ njs_sha1_final
+ },
+
+ {
+ njs_str("sha256"),
+ 32,
+ njs_sha2_init,
+ njs_sha2_update,
+ njs_sha2_final
+ },
+
+ {
+ njs_null_str,
+ 0,
+ NULL,
+ NULL,
+ NULL
+ }
+
+};
+
+
+static qjs_crypto_enc_t qjs_encodings[] = {
+
+ {
+ njs_str("buffer"),
+ qjs_buffer_digest
+ },
+
+ {
+ njs_str("hex"),
+ qjs_string_hex
+ },
+
+ {
+ njs_str("base64"),
+ qjs_string_base64
+ },
+
+ {
+ njs_str("base64url"),
+ qjs_string_base64url
+ },
+
+ {
+ njs_null_str,
+ NULL
+ }
+
+};
+
+
+static const JSCFunctionListEntry qjs_crypto_export[] = {
+ JS_CFUNC_DEF("createHash", 1, qjs_crypto_create_hash),
+ JS_CFUNC_DEF("createHmac", 2, qjs_crypto_create_hmac),
+};
+
+
+static const JSCFunctionListEntry qjs_hash_proto_proto[] = {
+ JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Hash", JS_PROP_CONFIGURABLE),
+ JS_CFUNC_MAGIC_DEF("update", 2, qjs_hash_prototype_update, 0),
+ JS_CFUNC_MAGIC_DEF("digest", 1, qjs_hash_prototype_digest, 0),
+ JS_CFUNC_DEF("copy", 0, qjs_hash_prototype_copy),
+ JS_CFUNC_DEF("constructor", 1, qjs_crypto_create_hash),
+};
+
+
+static const JSCFunctionListEntry qjs_hmac_proto_proto[] = {
+ JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Hmac", JS_PROP_CONFIGURABLE),
+ JS_CFUNC_MAGIC_DEF("update", 2, qjs_hash_prototype_update, 1),
+ JS_CFUNC_MAGIC_DEF("digest", 1, qjs_hash_prototype_digest, 1),
+ JS_CFUNC_DEF("constructor", 2, qjs_crypto_create_hmac),
+};
+
+
+static JSClassDef qjs_hash_class = {
+ "Hash",
+ .finalizer = qjs_hash_finalizer,
+};
+
+
+static JSClassDef qjs_hmac_class = {
+ "Hmac",
+ .finalizer = qjs_hmac_finalizer,
+};
+
+
+qjs_module_t qjs_crypto_module = {
+ .name = "crypto",
+ .init = qjs_crypto_init,
+};
+
+
+static JSValue
+qjs_crypto_create_hash(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ JSValue obj;
+ qjs_digest_t *dgst;
+ qjs_hash_alg_t *alg;
+
+ alg = qjs_crypto_algorithm(cx, argv[0]);
+ if (alg == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ dgst = js_malloc(cx, sizeof(qjs_digest_t));
+ if (dgst == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ dgst->alg = alg;
+ alg->init(&dgst->ctx);
+
+ obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_CRYPTO_HASH);
+ if (JS_IsException(obj)) {
+ js_free(cx, dgst);
+ return obj;
+ }
+
+ JS_SetOpaque(obj, dgst);
+
+ return obj;
+}
+
+
+static JSValue
+qjs_hash_prototype_update(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv, int hmac)
+{
+ njs_str_t str, content;
+ njs_hash_t *uctx;
+ qjs_hmac_t *hctx;
+ qjs_bytes_t bytes;
+ qjs_digest_t *dgst;
+ const qjs_buffer_encoding_t *enc;
+
+ void (*update)(njs_hash_t *ctx, const void *data, size_t size);
+
+ if (!hmac) {
+ dgst = JS_GetOpaque2(cx, this_val, QJS_CORE_CLASS_CRYPTO_HASH);
+ if (dgst == NULL) {
+ return JS_ThrowTypeError(cx, "\"this\" is not a hash object");
+ }
+
+ if (dgst->alg == NULL) {
+ return JS_ThrowTypeError(cx, "Digest already called");
+ }
+
+ update = dgst->alg->update;
+ uctx = &dgst->ctx;
+
+ } else {
+ hctx = JS_GetOpaque2(cx, this_val, QJS_CORE_CLASS_CRYPTO_HMAC);
+ if (hctx == NULL) {
+ return JS_ThrowTypeError(cx, "\"this\" is not a hmac object");
+ }
+
+ if (hctx->alg == NULL) {
+ return JS_ThrowTypeError(cx, "Digest already called");
+ }
+
+ update = hctx->alg->update;
+ uctx = &hctx->ctx;
+ }
+
+ if (JS_IsString(argv[0])) {
+ enc = qjs_buffer_encoding(cx, argv[1], 1);
+ if (enc == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ str.start = (u_char *) JS_ToCStringLen(cx, &str.length, argv[0]);
+ if (str.start == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ if (enc->decode_length != NULL) {
+ content.length = enc->decode_length(cx, &str);
+ content.start = js_malloc(cx, content.length);
+ if (content.start == NULL) {
+ JS_FreeCString(cx, (const char *) str.start);
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ if (enc->decode(cx, &str, &content) != 0) {
+ JS_FreeCString(cx, (const char *) str.start);
+ JS_FreeCString(cx, (const char *) content.start);
+ return JS_EXCEPTION;
+ }
+
+ JS_FreeCString(cx, (const char *) str.start);
+
+ update(uctx, content.start, content.length);
+ js_free(cx, content.start);
+
+ } else {
+ update(uctx, str.start, str.length);
+ JS_FreeCString(cx, (const char *) str.start);
+ }
+
+ } else if (qjs_is_typed_array(cx, argv[0])) {
+ if (qjs_to_bytes(cx, &bytes, argv[0]) != 0) {
+ return JS_EXCEPTION;
+ }
+
+ update(uctx, bytes.start, bytes.length);
+
+ } else {
+ return JS_ThrowTypeError(cx,
+ "data is not a string or Buffer-like object");
+ }
+
+ return JS_DupValue(cx, this_val);
+}
+
+
+static JSValue
+qjs_hash_prototype_digest(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv, int hmac)
+{
+ njs_str_t str;
+ qjs_hmac_t *hctx;
+ qjs_digest_t *dgst;
+ qjs_hash_alg_t *alg;
+ qjs_crypto_enc_t *enc;
+ u_char hash1[32],digest[32];
+
+ if (!hmac) {
+ dgst = JS_GetOpaque2(cx, this_val, QJS_CORE_CLASS_CRYPTO_HASH);
+ if (dgst == NULL) {
+ return JS_ThrowTypeError(cx, "\"this\" is not a hash object");
+ }
+
+ alg = dgst->alg;
+ if (alg == NULL) {
+ return JS_ThrowTypeError(cx, "Digest already called");
+ }
+
+ dgst->alg = NULL;
+
+ alg->final(digest, &dgst->ctx);
+
+ } else {
+ hctx = JS_GetOpaque2(cx, this_val, QJS_CORE_CLASS_CRYPTO_HMAC);
+ if (hctx == NULL) {
+ return JS_ThrowTypeError(cx, "\"this\" is not a hmac object");
+ }
+
+ alg = hctx->alg;
+ if (alg == NULL) {
+ return JS_ThrowTypeError(cx, "Digest already called");
+ }
+
+ hctx->alg = NULL;
+
+ alg->final(hash1, &hctx->ctx);
+
+ alg->init(&hctx->ctx);
+ alg->update(&hctx->ctx, hctx->opad, 64);
+ alg->update(&hctx->ctx, hash1, alg->size);
+ alg->final(digest, &hctx->ctx);
+ }
+
+ str.start = digest;
+ str.length = alg->size;
+
+ if (argc == 0) {
+ return qjs_buffer_digest(cx, &str);
+ }
+
+ enc = qjs_crypto_encoding(cx, argv[0]);
+ if (enc == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ return enc->encode(cx, &str);
+}
+
+
+static JSValue
+qjs_hash_prototype_copy(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ JSValue obj;
+ qjs_digest_t *dgst, *copy;
+
+ dgst = JS_GetOpaque2(cx, this_val, QJS_CORE_CLASS_CRYPTO_HASH);
+ if (dgst == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ if (dgst->alg == NULL) {
+ return JS_ThrowTypeError(cx, "Digest already called");
+ }
+
+ copy = js_malloc(cx, sizeof(qjs_digest_t));
+ if (copy == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ memcpy(copy, dgst, sizeof(qjs_digest_t));
+
+ obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_CRYPTO_HASH);
+ if (JS_IsException(obj)) {
+ js_free(cx, copy);
+ return obj;
+ }
+
+ JS_SetOpaque(obj, copy);
+
+ return obj;
+}
+
+
+static JSValue
+qjs_crypto_create_hmac(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ int i;
+ JS_BOOL key_is_string;
+ JSValue obj;
+ njs_str_t key;
+ qjs_hmac_t *hmac;
+ qjs_bytes_t bytes;
+ qjs_hash_alg_t *alg;
+ u_char digest[32], key_buf[64];
+
+ alg = qjs_crypto_algorithm(cx, argv[0]);
+ if (alg == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ key_is_string = JS_IsString(argv[1]);
+ if (key_is_string) {
+ key.start = (u_char *) JS_ToCStringLen(cx, &key.length, argv[1]);
+ if (key.start == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ } else if (qjs_is_typed_array(cx, argv[1])) {
+ if (qjs_to_bytes(cx, &bytes, argv[1]) != 0) {
+ return JS_EXCEPTION;
+ }
+
+ key.start = bytes.start;
+ key.length = bytes.length;
+
+ } else {
+ return JS_ThrowTypeError(cx,
+ "key is not a string or Buffer-like object");
+ }
+
+ hmac = js_malloc(cx, sizeof(qjs_hmac_t));
+ if (hmac == NULL) {
+ if (key_is_string) {
+ JS_FreeCString(cx, (const char *) key.start);
+ }
+
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ hmac->alg = alg;
+
+ if (key.length > sizeof(key_buf)) {
+ alg->init(&hmac->ctx);
+ alg->update(&hmac->ctx, key.start, key.length);
+ alg->final(digest, &hmac->ctx);
+
+ memcpy(key_buf, digest, alg->size);
+ memset(key_buf + alg->size, 0, sizeof(key_buf) - alg->size);
+
+ } else {
+ memcpy(key_buf, key.start, key.length);
+ memset(key_buf + key.length, 0, sizeof(key_buf) - key.length);
+ }
+
+ if (key_is_string) {
+ JS_FreeCString(cx, (const char *) key.start);
+ }
+
+ for (i = 0; i < 64; i++) {
+ hmac->opad[i] = key_buf[i] ^ 0x5c;
+ }
+
+ for (i = 0; i < 64; i++) {
+ key_buf[i] ^= 0x36;
+ }
+
+ alg->init(&hmac->ctx);
+ alg->update(&hmac->ctx, key_buf, 64);
+
+ obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_CRYPTO_HMAC);
+ if (JS_IsException(obj)) {
+ js_free(cx, hmac);
+ return obj;
+ }
+
+ JS_SetOpaque(obj, hmac);
+
+ return obj;
+}
+
+
+static void
+qjs_hash_finalizer(JSRuntime *rt, JSValue val)
+{
+ qjs_digest_t *dgst;
+
+ dgst = JS_GetOpaque(val, QJS_CORE_CLASS_CRYPTO_HASH);
+ if (dgst != NULL) {
+ js_free_rt(rt, dgst);
+ }
+}
+
+
+static void
+qjs_hmac_finalizer(JSRuntime *rt, JSValue val)
+{
+ qjs_hmac_t *hmac;
+
+ hmac = JS_GetOpaque(val, QJS_CORE_CLASS_CRYPTO_HMAC);
+ if (hmac != NULL) {
+ js_free_rt(rt, hmac);
+ }
+}
+
+
+static qjs_hash_alg_t *
+qjs_crypto_algorithm(JSContext *cx, JSValueConst val)
+{
+ njs_str_t name;
+ qjs_hash_alg_t *a, *alg;
+
+ name.start = (u_char *) JS_ToCStringLen(cx, &name.length, val);
+ if (name.start == NULL) {
+ JS_ThrowTypeError(cx, "algorithm must be a string");
+ return NULL;
+ }
+
+ alg = NULL;
+
+ for (a = &qjs_hash_algorithms[0]; a->name.start != NULL; a++) {
+ if (njs_strstr_eq(&name, &a->name)) {
+ alg = a;
+ break;
+ }
+ }
+
+ JS_FreeCString(cx, (const char *) name.start);
+
+ if (alg == NULL) {
+ JS_ThrowTypeError(cx, "not supported algorithm");
+ }
+
+ return alg;
+}
+
+
+static qjs_crypto_enc_t *
+qjs_crypto_encoding(JSContext *cx, JSValueConst val)
+{
+ njs_str_t name;
+ qjs_crypto_enc_t *e, *enc;
+
+ if (JS_IsNullOrUndefined(val)) {
+ return &qjs_encodings[0];
+ }
+
+ name.start = (u_char *) JS_ToCStringLen(cx, &name.length, val);
+ if (name.start == NULL) {
+ return NULL;
+ }
+
+ enc = NULL;
+
+ for (e = &qjs_encodings[1]; e->name.start != NULL; e++) {
+ if (njs_strstr_eq(&name, &e->name)) {
+ enc = e;
+ break;
+ }
+ }
+
+ JS_FreeCString(cx, (const char *) name.start);
+
+ if (enc == NULL) {
+ JS_ThrowTypeError(cx, "Unknown digest encoding");
+ }
+
+ return enc;
+}
+
+
+static JSValue
+qjs_buffer_digest(JSContext *cx, const njs_str_t *src)
+{
+ return qjs_buffer_create(cx, src->start, src->length);
+}
+
+
+static int
+qjs_crypto_module_init(JSContext *cx, JSModuleDef *m)
+{
+ JSValue crypto_obj;
+
+ crypto_obj = JS_NewObject(cx);
+ if (JS_IsException(crypto_obj)) {
+ return -1;
+ }
+
+ JS_SetPropertyFunctionList(cx, crypto_obj, qjs_crypto_export,
+ njs_nitems(qjs_crypto_export));
+
+ if (JS_SetModuleExport(cx, m, "default", crypto_obj) != 0) {
+ return -1;
+ }
+
+ return JS_SetModuleExportList(cx, m, qjs_crypto_export,
+ njs_nitems(qjs_crypto_export));
+}
+
+
+static JSModuleDef *
+qjs_crypto_init(JSContext *cx, const char *module_name)
+{
+ JSValue proto;
+ JSModuleDef *m;
+
+ if (!JS_IsRegisteredClass(JS_GetRuntime(cx),
+ QJS_CORE_CLASS_CRYPTO_HASH))
+ {
+ if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_CRYPTO_HASH,
+ &qjs_hash_class) < 0)
+ {
+ return NULL;
+ }
+
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return NULL;
+ }
+
+ JS_SetPropertyFunctionList(cx, proto, qjs_hash_proto_proto,
+ njs_nitems(qjs_hash_proto_proto));
+
+ JS_SetClassProto(cx, QJS_CORE_CLASS_CRYPTO_HASH, proto);
+
+ if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_CRYPTO_HMAC,
+ &qjs_hmac_class) < 0)
+ {
+ return NULL;
+ }
+
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return NULL;
+ }
+
+ JS_SetPropertyFunctionList(cx, proto, qjs_hmac_proto_proto,
+ njs_nitems(qjs_hmac_proto_proto));
+
+ JS_SetClassProto(cx, QJS_CORE_CLASS_CRYPTO_HMAC, proto);
+ }
+
+ m = JS_NewCModule(cx, module_name, qjs_crypto_module_init);
+ if (m == NULL) {
+ return NULL;
+ }
+
+ if (JS_AddModuleExport(cx, m, "default") < 0) {
+ return NULL;
+ }
+
+ if (JS_AddModuleExportList(cx, m, qjs_crypto_export,
+ njs_nitems(qjs_crypto_export)) != 0)
+ {
+ return NULL;
+ }
+
+ return m;
+}
diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c
index 730feb6e..a1983651 100644
--- a/external/qjs_webcrypto_module.c
+++ b/external/qjs_webcrypto_module.c
@@ -1238,29 +1238,6 @@ fail:
}
-static JSValue
-qjs_string_base64url(JSContext *cx, const njs_str_t *src)
-{
- size_t padding;
- njs_str_t dst;
- u_char buf[/* qjs_base64_encoded_length(512) */ 686];
-
- if (src->length == 0) {
- return JS_NewStringLen(cx, "", 0);
- }
-
- padding = src->length % 3;
- padding = (4 >> padding) & 0x03;
-
- dst.start = buf;
- dst.length = qjs_base64_encode_length(cx, src) - padding;
-
- qjs_base64url_encode(cx, src, &dst);
-
- return JS_NewStringLen(cx, (const char *) dst.start, dst.length);
-}
-
-
static JSValue
qjs_export_base64url_bignum(JSContext *cx, const BIGNUM *v, size_t size)
{
diff --git a/src/qjs.c b/src/qjs.c
index ed9a6178..15a575a2 100644
--- a/src/qjs.c
+++ b/src/qjs.c
@@ -1025,3 +1025,106 @@ qjs_string_create_chb(JSContext *cx, njs_chb_t *chain)
return val;
}
+
+
+JSValue
+qjs_string_hex(JSContext *cx, const njs_str_t *src)
+{
+ JSValue ret;
+ njs_str_t dst;
+ u_char buf[1024];
+
+ if (src->length == 0) {
+ return JS_NewStringLen(cx, "", 0);
+ }
+
+ dst.start = buf;
+ dst.length = qjs_hex_encode_length(cx, src);
+
+ if (dst.length <= sizeof(buf)) {
+ qjs_hex_encode(cx, src, &dst);
+ ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length);
+
+ } else {
+ dst.start = js_malloc(cx, dst.length);
+ if (dst.start == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ qjs_hex_encode(cx, src, &dst);
+ ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length);
+ js_free(cx, dst.start);
+ }
+
+ return ret;
+}
+
+
+JSValue
+qjs_string_base64(JSContext *cx, const njs_str_t *src)
+{
+ JSValue ret;
+ njs_str_t dst;
+ u_char buf[1024];
+
+ if (src->length == 0) {
+ return JS_NewStringLen(cx, "", 0);
+ }
+
+ dst.start = buf;
+ dst.length = qjs_base64_encode_length(cx, src);
+
+ if (dst.length <= sizeof(buf)) {
+ qjs_base64_encode(cx, src, &dst);
+ ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length);
+
+ } else {
+ dst.start = js_malloc(cx, dst.length);
+ if (dst.start == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ qjs_base64_encode(cx, src, &dst);
+ ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length);
+ js_free(cx, dst.start);
+ }
+
+ return ret;
+}
+
+
+JSValue
+qjs_string_base64url(JSContext *cx, const njs_str_t *src)
+{
+ size_t padding;
+ JSValue ret;
+ njs_str_t dst;
+ u_char buf[1024];
+
+ if (src->length == 0) {
+ return JS_NewStringLen(cx, "", 0);
+ }
+
+ padding = src->length % 3;
+ padding = (4 >> padding) & 0x03;
+
+ dst.start = buf;
+ dst.length = qjs_base64_encode_length(cx, src) - padding;
+
+ if (dst.length <= sizeof(buf)) {
+ qjs_base64url_encode(cx, src, &dst);
+ ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length);
+
+ } else {
+ dst.start = js_malloc(cx, dst.length);
+ if (dst.start == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ qjs_base64url_encode(cx, src, &dst);
+ ret = JS_NewStringLen(cx, (const char *) dst.start, dst.length);
+ js_free(cx, dst.start);
+ }
+
+ return ret;
+}
diff --git a/src/qjs.h b/src/qjs.h
index 7c560d5f..54f96dfe 100644
--- a/src/qjs.h
+++ b/src/qjs.h
@@ -42,7 +42,9 @@
#define QJS_CORE_CLASS_ID_FS_DIRENT (QJS_CORE_CLASS_ID_OFFSET + 5)
#define QJS_CORE_CLASS_ID_FS_FILEHANDLE (QJS_CORE_CLASS_ID_OFFSET + 6)
#define QJS_CORE_CLASS_ID_WEBCRYPTO_KEY (QJS_CORE_CLASS_ID_OFFSET + 7)
-#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_OFFSET + 8)
+#define QJS_CORE_CLASS_CRYPTO_HASH (QJS_CORE_CLASS_ID_OFFSET + 8)
+#define QJS_CORE_CLASS_CRYPTO_HMAC (QJS_CORE_CLASS_ID_OFFSET + 9)
+#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_OFFSET + 10)
typedef JSModuleDef *(*qjs_addon_init_pt)(JSContext *ctx, const char *name);
@@ -102,6 +104,20 @@ typedef struct {
u_char *start;
} qjs_bytes_t;
+
+njs_inline int
+qjs_is_typed_array(JSContext *cx, JSValue val)
+{
+ JS_BOOL exception;
+
+ val = JS_GetTypedArrayBuffer(cx, val, NULL, NULL, NULL);
+ exception = JS_IsException(val);
+ JS_FreeValue(cx, val);
+
+ return !exception;
+}
+
+
int qjs_to_bytes(JSContext *ctx, qjs_bytes_t *data, JSValueConst value);
void qjs_bytes_free(JSContext *ctx, qjs_bytes_t *data);
JSValue qjs_typed_array_data(JSContext *ctx, JSValueConst value,
@@ -111,6 +127,9 @@ JSValue qjs_typed_array_data(JSContext *ctx, JSValueConst value,
JS_NewStringLen(ctx, (const char *) (data), len)
JSValue qjs_string_create_chb(JSContext *cx, njs_chb_t *chain);
+JSValue qjs_string_hex(JSContext *cx, const njs_str_t *src);
+JSValue qjs_string_base64(JSContext *cx, const njs_str_t *src);
+JSValue qjs_string_base64url(JSContext *cx, const njs_str_t *src);
static inline JS_BOOL JS_IsNullOrUndefined(JSValueConst v)
{
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
index 4f5e5c91..f39660f5 100644
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -20239,265 +20239,6 @@ static njs_unit_test_t njs_fs_module_test[] =
};
-static njs_unit_test_t njs_crypto_module_test[] =
-{
- { njs_str("import x from 'crypto'"),
- njs_str("undefined") },
-
- { njs_str("import x from 'crypto' 1"),
- njs_str("SyntaxError: Unexpected token \"1\" in 1") },
-
- { njs_str("if (1) {import x from 'crypto'}"),
- njs_str("SyntaxError: Illegal import statement in 1") },
-
- { njs_str("var h = require('crypto').createHash('sha1');"
- "[Object.prototype.toString.call(h), njs.dump(h),h]"),
- njs_str("[object Hash],Hash {},[object Hash]") },
-
- { njs_str("var h = require('crypto').createHash('sha1');"
- "var Hash = h.constructor; "
- "Hash('sha1').update('AB').digest('hex')"),
- njs_str("06d945942aa26a61be18c3e22bf19bbca8dd2b5d") },
-
- { njs_str("var hash = require('crypto').createHash.bind(undefined, 'md5');"
- "['hex', 'base64', 'base64url'].map(e => {"
- " var h = hash().update('AB').digest().toString(e);"
- " var h2 = hash().update(Buffer.from('XABX').subarray(1,3)).digest(e);"
- " var h3 = hash().update('A').update('B').digest(e);"
- " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};"
- " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};"
- " return h;"
- "})"),
- njs_str("b86fc6b051f63d73de262d4c34e3a0a9,"
- "uG/GsFH2PXPeJi1MNOOgqQ==,"
- "uG_GsFH2PXPeJi1MNOOgqQ") },
-
- { njs_str("var hash = require('crypto').createHash.bind(undefined, 'sha1');"
- "['hex', 'base64', 'base64url'].map(e => {"
- " var h = hash().update('4142', 'hex').digest().toString(e);"
- " var h2 = hash().update(Buffer.from('XABX').subarray(1,3)).digest(e);"
- " var h3 = hash().update('A').update('B').digest(e);"
- " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};"
- " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};"
- " return h;"
- "})"),
- njs_str("06d945942aa26a61be18c3e22bf19bbca8dd2b5d,"
- "BtlFlCqiamG+GMPiK/GbvKjdK10=,"
- "BtlFlCqiamG-GMPiK_GbvKjdK10") },
-
- { njs_str("var hash = require('crypto').createHash.bind(undefined, 'sha1');"
- "['hex', 'base64', 'base64url'].every(e => {"
- " var h = hash().digest(e);"
- " var h2 = hash().update('').digest(e);"
- " if (h !== h2) {throw new Error(`digest($e):$h != update('').digest($e):$h2`)};"
- " return true;"
- "})"),
- njs_str("true") },
-
- { njs_str("var hash = require('crypto').createHash.bind(undefined, 'sha1');"
- "["
- " ['AB'],"
- " ['4142', 'hex'],"
- " ['QUI=', 'base64'],"
- " ['QUI', 'base64url']"
- "].every(args => {"
- " return hash().update(args[0], args[1]).digest('hex') === '06d945942aa26a61be18c3e22bf19bbca8dd2b5d';"
- "})"),
- njs_str("true") },
-
- { njs_str("var hash = require('crypto').createHash.bind(undefined, 'sha256');"
- "['hex', 'base64', 'base64url'].map(e => {"
- " var h = hash().update('AB').digest().toString(e);"
- " var h2 = hash().update(Buffer.from('XABX').subarray(1,3)).digest(e);"
- " var h3 = hash().update('A').update('B').digest(e);"
- " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};"
- " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};"
- " return h;"
- "})"),
- njs_str("38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153,"
- "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH/SrjP9aWQVM=,"
- "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH_SrjP9aWQVM") },
-
- { njs_str("const crypto = require('crypto');"
- "let hash = crypto.createHash('sha256');"
- "let digests = [];"
- "hash.update('one');"
- "digests.push(hash.copy().digest('hex'));"
- "hash.update('two');"
- "digests.push(hash.copy().digest('hex'));"
- "hash.update('three');"
- "digests.push(hash.copy().digest('hex'));"
- "digests"),
- njs_str("7692c3ad3540bb803c020b3aee66cd8887123234ea0c6e7143c0add73ff431ed,"
- "25b6746d5172ed6352966a013d93ac846e1110d5a25e8f183b5931f4688842a1,"
- "4592092e1061c7ea85af2aed194621cc17a2762bae33a79bf8ce33fd0168b801") },
-
- { njs_str("const crypto = require('crypto');"
- "let hash = crypto.createHash('sha256');"
- "hash.update('one').digest();"
- "hash.copy()"),
- njs_str("Error: Digest already called") },
-
- { njs_str("var hash = require('crypto').createHash;"
- "njs.dump(['', 'abc'.repeat(100)].map(v => {"
- " return ['md5', 'sha1', 'sha256'].map(h => {"
- " return hash(h).update(v).digest('hex');"
- " })"
- "}))"),
- njs_str("[['d41d8cd98f00b204e9800998ecf8427e',"
- "'da39a3ee5e6b4b0d3255bfef95601890afd80709',"
- "'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'],"
- "['f571117acbd8153c8dc3c81b8817773a',"
- "'c95466320eaae6d19ee314ae4f135b12d45ced9a',"
- "'d9f5aeb06abebb3be3f38adec9a2e3b94228d52193be923eb4e24c9b56ee0930']]") },
-
- { njs_str("var h = require('crypto').createHash()"),
- njs_str("TypeError: algorithm must be a string") },
-
- { njs_str("var h = require('crypto').createHash([])"),
- njs_str("TypeError: algorithm must be a string") },
-
- { njs_str("var h = require('crypto').createHash('sha512')"),
- njs_str("TypeError: not supported algorithm: \"sha512\"") },
-
- { njs_str("var h = require('crypto').createHash('sha1');"
- "h.update()"),
- njs_str("TypeError: data is not a string or Buffer-like object") },
-
- { njs_str("var h = require('crypto').createHash('sha1');"
- "h.update({})"),
- njs_str("TypeError: data is not a string or Buffer-like object") },
-
- { njs_str("var h = require('crypto').createHash('sha1');"
- "h.update('A').digest('latin1')"),
- njs_str("TypeError: Unknown digest encoding: \"latin1\"") },
-
- { njs_str("require('crypto').createHash('sha1').digest() instanceof Buffer"),
- njs_str("true") },
-
- { njs_str("var h = require('crypto').createHash('sha1');"
- "h.update('A').digest('hex'); h.digest('hex')"),
- njs_str("Error: Digest already called") },
-
- { njs_str("var h = require('crypto').createHash('sha1');"
- "h.update('A').digest('hex'); h.update('B')"),
- njs_str("Error: Digest already called") },
-
- { njs_str("typeof require('crypto').createHash('md5')"),
- njs_str("object") },
-
- { njs_str("var h = require('crypto').createHmac('sha1', '');"
- "[Object.prototype.toString.call(h), njs.dump(h),h]"),
- njs_str("[object Hmac],Hmac {},[object Hmac]") },
-
- { njs_str("var hmac = require('crypto').createHmac.bind(undefined, 'md5', '');"
- "['hex', 'base64', 'base64url'].map(e => {"
- " var h = hmac().update('AB').digest().toString(e);"
- " var h2 = hmac().update(Buffer.from('XABX').subarray(1,3)).digest(e);"
- " var h3 = hmac().update('A').update('B').digest(e);"
- " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};"
- " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};"
- " return h;"
- "})"),
- njs_str("9e0e9e545ef63d41dfb653daecf8ebc7,"
- "ng6eVF72PUHftlPa7Pjrxw==,"
- "ng6eVF72PUHftlPa7Pjrxw") },
-
- { njs_str("var hmac = require('crypto').createHmac.bind(undefined, 'sha1', '');"
- "['hex', 'base64', 'base64url'].map(e => {"
- " var h = hmac().update('AB').digest().toString(e);"
- " var h2 = hmac().update(Buffer.from('XABX').subarray(1,3)).digest(e);"
- " var h3 = hmac().update('A').update('B').digest(e);"
- " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};"
- " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};"
- " return h;"
- "})"),
- njs_str("d32c0b6637cc2dfe4670f3fe48ef4434123c4810,"
- "0ywLZjfMLf5GcPP+SO9ENBI8SBA=,"
- "0ywLZjfMLf5GcPP-SO9ENBI8SBA") },
-
- { njs_str("var hash = require('crypto').createHmac.bind(undefined, 'sha1', '');"
- "["
- " ['AB'],"
- " ['4142', 'hex'],"
- " ['QUI=', 'base64'],"
- " ['QUI', 'base64url']"
- "].every(args => {"
- " return hash().update(args[0], args[1]).digest('hex') === 'd32c0b6637cc2dfe4670f3fe48ef4434123c4810';"
- "})"),
- njs_str("true") },
-
- { njs_str("var hmac = require('crypto').createHmac.bind(undefined, 'sha256', '');"
- "['hex', 'base64', 'base64url'].map(e => {"
- " var h = hmac().update('AB').digest().toString(e);"
- " var h2 = hmac().update(Buffer.from('AB')).digest(e);"
- " var h3 = hmac().update('A').update('B').digest(e);"
- " if (h !== h2) {throw new Error(`digest().toString($e):$h != digest($e):$h2`)};"
- " if (h !== h3) {throw new Error(`digest().toString($e):$h != update('A').update('B').digest($e):$h3`)};"
- " return h;"
- "})"),
- njs_str("d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3,"
- "1TQACVSWJnzwLl29Swv5+/tfNvMR6n2YCa9Uh0IXQ+M=,"
- "1TQACVSWJnzwLl29Swv5-_tfNvMR6n2YCa9Uh0IXQ-M") },
-
- { njs_str("var hmac = require('crypto').createHmac;"
- "njs.dump(['', 'abc'.repeat(100)].map(v => {"
- " return ['md5', 'sha1', 'sha256'].map(h => {"
- " return hmac(h, Buffer.from('secret')).update(v).digest('hex');"
- " })"
- "}))"),
- njs_str("[['5c8db03f04cec0f43bcb060023914190',"
- "'25af6174a0fcecc4d346680a72b7ce644b9a88e8',"
- "'f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169'],"
- "['91eb74a225cdd3bbfccc34396c6e3ac5',"
- "'0aac71e3a813a7acc4a809cfdedb2ecba04ffc5e',"
- "'8660d2d51d6f20f61d5aadfb6c43df7fd05fc2fc4967d8aec1846f3d9ec03987']]") },
-
- { njs_str("var h = require('crypto').createHmac('sha1', '');"
- "var Hmac = h.constructor; "
- "Hmac('sha1', '').digest('hex')"),
- njs_str("fbdb1d1b18aa6c08324b7d64b71fb76370690e1d") },
-
- { njs_str("require('crypto').createHmac('sha1', '').digest() instanceof Buffer"),
- njs_str("true") },
-
- { njs_str("var h = require('crypto').createHmac('sha256', 'A'.repeat(64));"
- "h.update('AB').digest('hex')"),
- njs_str("ee9dce43b12eb3e865614ad9c1a8d4fad4b6eac2b64647bd24cd192888d3f367") },
-
- { njs_str("var h = require('crypto').createHmac('sha256', 'A'.repeat(100));"
- "h.update('AB').digest('hex')"),
- njs_str("5647b6c429701ff512f0f18232b4507065d2376ca8899a816a0a6e721bf8ddcc") },
-
- { njs_str("var h = require('crypto').createHmac()"),
- njs_str("TypeError: algorithm must be a string") },
-
- { njs_str("var h = require('crypto').createHmac([])"),
- njs_str("TypeError: algorithm must be a string") },
-
- { njs_str("var h = require('crypto').createHmac('sha512', '')"),
- njs_str("TypeError: not supported algorithm: \"sha512\"") },
-
- { njs_str("var h = require('crypto').createHmac('sha1', [])"),
- njs_str("TypeError: key is not a string or Buffer-like object") },
-
- { njs_str("var h = require('crypto').createHmac('sha1', 'secret key');"
- "h.update('A').digest('hex'); h.digest('hex')"),
- njs_str("Error: Digest already called") },
-
- { njs_str("var h = require('crypto').createHmac('sha1', 'secret key');"
- "h.update('A').digest('hex'); h.update('B')"),
- njs_str("Error: Digest already called") },
-
- { njs_str("typeof require('crypto').createHmac('md5', 'a')"),
- njs_str("object") },
-
- { njs_str("var cr = require('crypto'); var h = cr.createHash('sha1');"
- "h.update.call(cr.createHmac('sha1', 's'), '')"),
- njs_str("TypeError: \"this\" is not a hash object") },
-};
-
-
#define NJS_XML_DOC "const xml = require('xml');" \
"let data = `<note><to b=\"bar\" a= \"foo\" >Tove</to><from>Jani</from></note>`;" \
"let doc = xml.parse(data);"
@@ -23570,12 +23311,6 @@ static njs_test_suite_t njs_suites[] =
njs_nitems(njs_fs_module_test),
njs_unit_test },
- { njs_str("crypto module"),
- { .repeat = 1, .unsafe = 1 },
- njs_crypto_module_test,
- njs_nitems(njs_crypto_module_test),
- njs_unit_test },
-
{ njs_str("externals"),
{ .externals = 1, .repeat = 1, .unsafe = 1 },
njs_externals_test,
diff --git a/test/crypto.t.mjs b/test/crypto.t.mjs
new file mode 100644
index 00000000..ad237d88
--- /dev/null
+++ b/test/crypto.t.mjs
@@ -0,0 +1,447 @@
+/*---
+includes: [runTsuite.js]
+flags: [async]
+---*/
+
+import cr from 'crypto';
+
+let createHash_tsuite = {
+ name: "createHash tests",
+ skip: () => !cr.createHash,
+ T: async (params) => {
+ var h = params.hash_value ? params.hash_value(params.hash)
+ : cr.createHash(params.hash);
+
+ if (typeof h !== 'object') {
+ throw Error(`unexpected result "${h}" is not object`);
+ }
+
+ for (let i = 0; i < params.data.length; i++) {
+ let args = params.data[i];
+
+ if (Array.isArray(args)) {
+ h.update(args[0], args[1]);
+
+ } else {
+ h.update(args);
+ }
+ }
+
+ let r = h.digest(params.digest);
+
+ if (!params.digest) {
+ if (!(r instanceof Buffer)) {
+ throw Error(`unexpected result "${r}" is not Buffer`);
+ }
+
+ if (r.compare(params.expected) !== 0) {
+ throw Error(`unexpected output "${r}" != "${params.expected}"`);
+ }
+
+ } else {
+ if (typeof r !== 'string') {
+ throw Error(`unexpected result "${r}" is not string`);
+ }
+
+ if (r !== params.expected) {
+ throw Error(`unexpected output "${r}" != "${params.expected}"`);
+ }
+ }
+
+ return 'SUCCESS';
+ },
+
+ tests: [
+ { hash: 'md5', data: [], digest: 'hex',
+ expected: "d41d8cd98f00b204e9800998ecf8427e" },
+ { hash: 'md5', data: [''], digest: 'hex',
+ expected: "d41d8cd98f00b204e9800998ecf8427e" },
+ { hash: 'md5', data: [''],
+ expected: Buffer.from("d41d8cd98f00b204e9800998ecf8427e", "hex") },
+
+ { hash: 'md5', data: ['AB'], digest: 'hex',
+ expected: "b86fc6b051f63d73de262d4c34e3a0a9" },
+ { hash: 'md5', data: ['A', 'B'], digest: 'hex',
+ expected: "b86fc6b051f63d73de262d4c34e3a0a9" },
+ { hash: 'md5', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex',
+ expected: "b86fc6b051f63d73de262d4c34e3a0a9" },
+
+ { hash: 'md5', data: [['4142', 'hex']], digest: 'hex',
+ expected: "b86fc6b051f63d73de262d4c34e3a0a9" },
+ { hash: 'md5', data: [['QUI=', 'base64']], digest: 'hex',
+ expected: "b86fc6b051f63d73de262d4c34e3a0a9" },
+ { hash: 'md5', data: [['QUI', 'base64url']], digest: 'hex',
+ expected: "b86fc6b051f63d73de262d4c34e3a0a9" },
+
+ { hash: 'md5', data: ['abc'.repeat(100)], digest: 'hex',
+ expected: "f571117acbd8153c8dc3c81b8817773a" },
+
+ { hash: 'md5', data: ['AB'], digest: 'base64',
+ expected: "uG/GsFH2PXPeJi1MNOOgqQ==" },
+ { hash: 'md5', data: ['A', 'B'], digest: 'base64',
+ expected: "uG/GsFH2PXPeJi1MNOOgqQ==" },
+ { hash: 'md5', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64',
+ expected: "uG/GsFH2PXPeJi1MNOOgqQ==" },
+
+ { hash: 'md5', data: ['AB'], digest: 'base64url',
+ expected: "uG_GsFH2PXPeJi1MNOOgqQ" },
+ { hash: 'md5', data: ['A', 'B'], digest: 'base64url',
+ expected: "uG_GsFH2PXPeJi1MNOOgqQ" },
+ { hash: 'md5', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url',
+ expected: "uG_GsFH2PXPeJi1MNOOgqQ" },
+
+ { hash: 'sha1', data: [], digest: 'hex',
+ expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709" },
+ { hash: 'sha1', data: [''], digest: 'hex',
+ expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709" },
+ { hash: 'sha1', data: [''],
+ expected: Buffer.from("da39a3ee5e6b4b0d3255bfef95601890afd80709", "hex") },
+
+ { hash: 'sha1', data: ['AB'], digest: 'hex',
+ expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" },
+ { hash: 'sha1', data: ['A', 'B'], digest: 'hex',
+ expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" },
+ { hash: 'sha1', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex',
+ expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" },
+
+ { hash: 'sha1', data: [['4142', 'hex']], digest: 'hex',
+ expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" },
+ { hash: 'sha1', data: [['QUI=', 'base64']], digest: 'hex',
+ expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" },
+ { hash: 'sha1', data: [['QUI', 'base64url']], digest: 'hex',
+ expected: "06d945942aa26a61be18c3e22bf19bbca8dd2b5d" },
+
+ { hash: 'sha1', data: ['abc'.repeat(100)], digest: 'hex',
+ expected: "c95466320eaae6d19ee314ae4f135b12d45ced9a" },
+
+ { hash: 'sha1', data: ['AB'], digest: 'base64',
+ expected: "BtlFlCqiamG+GMPiK/GbvKjdK10=" },
+ { hash: 'sha1', data: ['A', 'B'], digest: 'base64',
+ expected: "BtlFlCqiamG+GMPiK/GbvKjdK10=" },
+ { hash: 'sha1', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64',
+ expected: "BtlFlCqiamG+GMPiK/GbvKjdK10=" },
+
+ { hash: 'sha1', data: ['AB'], digest: 'base64url',
+ expected: "BtlFlCqiamG-GMPiK_GbvKjdK10" },
+ { hash: 'sha1', data: ['A', 'B'], digest: 'base64url',
+ expected: "BtlFlCqiamG-GMPiK_GbvKjdK10" },
+ { hash: 'sha1', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url',
+ expected: "BtlFlCqiamG-GMPiK_GbvKjdK10" },
+
+ { hash: 'sha256', data: [], digest: 'hex',
+ expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" },
+ { hash: 'sha256', data: [''], digest: 'hex',
+ expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" },
+ { hash: 'sha256', data: [''],
+ expected: Buffer.from("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "hex") },
+
+ { hash: 'sha256', data: ['AB'], digest: 'hex',
+ expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" },
+ { hash: 'sha256', data: ['A', 'B'], digest: 'hex',
+ expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" },
+ { hash: 'sha256', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex',
+ expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" },
+
+ { hash: 'sha256', data: [['4142', 'hex']], digest: 'hex',
+ expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" },
+ { hash: 'sha256', data: [['QUI=', 'base64']], digest: 'hex',
+ expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" },
+ { hash: 'sha256', data: [['QUI', 'base64url']], digest: 'hex',
+ expected: "38164fbd17603d73f696b8b4d72664d735bb6a7c88577687fd2ae33fd6964153" },
+
+ { hash: 'sha256', data: ['abc'.repeat(100)], digest: 'hex',
+ expected: "d9f5aeb06abebb3be3f38adec9a2e3b94228d52193be923eb4e24c9b56ee0930" },
+
+ { hash: 'sha256', data: ['AB'], digest: 'base64',
+ expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH/SrjP9aWQVM=" },
+ { hash: 'sha256', data: ['A', 'B'], digest: 'base64',
+ expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH/SrjP9aWQVM=" },
+ { hash: 'sha256', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64',
+ expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH/SrjP9aWQVM=" },
+
+ { hash: 'sha256', data: ['AB'], digest: 'base64url',
+ expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH_SrjP9aWQVM" },
+ { hash: 'sha256', data: ['A', 'B'], digest: 'base64url',
+ expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH_SrjP9aWQVM" },
+ { hash: 'sha256', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url',
+ expected: "OBZPvRdgPXP2lri01yZk1zW7anyIV3aH_SrjP9aWQVM" },
+
+ { hash: 'sha1',
+ hash_value(hash) {
+ var Hash = cr.createHash(hash).constructor;
+ return Hash(hash);
+ },
+ data: [], digest: 'hex',
+ expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709" },
+
+ { hash: 'sha1',
+ hash_value(hash) {
+ var h = cr.createHash(hash);
+ h.copy().digest('hex');
+ return h;
+ },
+ data: [], digest: 'hex',
+ expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709" },
+
+ { hash: 'sha1',
+ hash_value(hash) {
+ var h = cr.createHash(hash);
+ h.digest('hex');
+ return h;
+ },
+ data: [], digest: 'hex',
+ exception: "TypeError: Digest already called" },
+
+ { hash: 'sha1',
+ hash_value(hash) {
+ var h = cr.createHash(hash);
+ h.digest('hex');
+ h.copy();
+ return h;
+ },
+ data: [], digest: 'hex',
+ exception: "TypeError: Digest already called" },
+
+ { hash: 'sha1',
+ hash_value(hash) {
+ var h = cr.createHash(hash);
+ h.update('AB');
+ return h;
+ },
+ data: [], digest: 'hex',
+ exception: "TypeError: Digest already called" },
+
+ { hash: 'sha1', data: [undefined], digest: 'hex',
+ exception: "TypeError: data is not a string or Buffer-like object" },
+ { hash: 'sha1', data: [{}], digest: 'hex',
+ exception: "TypeError: data is not a string or Buffer-like object" },
+
+ { hash: 'unknown', data: [], digest: 'hex',
+ exception: 'TypeError: not supported algorithm: "unknown"' },
+ { hash: 'sha1', data: [], digest: 'unknown',
+ exception: 'TypeError: unknown digest type: "unknown"' },
+]};
+
+
+let createHmac_tsuite = {
+ name: "createHmac tests",
+ skip: () => !cr.createHmac,
+ T: async (params) => {
+ var h = params.hmac_value ? params.hmac_value(params.hash, params.key)
+ : cr.createHmac(params.hash, params.key || '');
+
+ if (typeof h !== 'object') {
+ throw Error(`unexpected result "${h}" is not object`);
+ }
+
+ for (let i = 0; i < params.data.length; i++) {
+ let args = params.data[i];
+
+ if (Array.isArray(args)) {
+ h.update(args[0], args[1]);
+
+ } else {
+ h.update(args);
+ }
+ }
+
+ let r = h.digest(params.digest);
+
+ if (!params.digest) {
+ if (!(r instanceof Buffer)) {
+ throw Error(`unexpected result "${r}" is not Buffer`);
+ }
+
+ if (r.compare(params.expected) !== 0) {
+ throw Error(`unexpected output "${r}" != "${params.expected}"`);
+ }
+
+ } else {
+ if (typeof r !== 'string') {
+ throw Error(`unexpected result "${r}" is not string`);
+ }
+
+ if (r !== params.expected) {
+ throw Error(`unexpected output "${r}" != "${params.expected}"`);
+ }
+ }
+
+ return 'SUCCESS';
+ },
+
+ tests: [
+ { hash: 'md5', key: '', data: [], digest: 'hex',
+ expected: "74e6f7298a9c2d168935f58c001bad88" },
+ { hash: 'md5', key: '', data: [''], digest: 'hex',
+ expected: "74e6f7298a9c2d168935f58c001bad88" },
+ { hash: 'md5', key: '', data: [''],
+ expected: Buffer.from("74e6f7298a9c2d168935f58c001bad88", "hex") },
+
+ { hash: 'md5', key: '', data: ['AB'], digest: 'hex',
+ expected: "9e0e9e545ef63d41dfb653daecf8ebc7" },
+ { hash: 'md5', key: '', data: ['A', 'B'], digest: 'hex',
+ expected: "9e0e9e545ef63d41dfb653daecf8ebc7" },
+ { hash: 'md5', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex',
+ expected: "9e0e9e545ef63d41dfb653daecf8ebc7" },
+
+ { hash: 'md5', key: '', data: [['4142', 'hex']], digest: 'hex',
+ expected: "9e0e9e545ef63d41dfb653daecf8ebc7" },
+ { hash: 'md5', key: '', data: [['QUI=', 'base64']], digest: 'hex',
+ expected: "9e0e9e545ef63d41dfb653daecf8ebc7" },
+ { hash: 'md5', key: '', data: [['QUI', 'base64url']], digest: 'hex',
+ expected: "9e0e9e545ef63d41dfb653daecf8ebc7" },
+
+ { hash: 'md5', key: Buffer.from('secret'), data: ['abc'.repeat(100)], digest: 'hex',
+ expected: "91eb74a225cdd3bbfccc34396c6e3ac5" },
+
+ { hash: 'md5', key: '', data: ['AB'], digest: 'base64',
+ expected: "ng6eVF72PUHftlPa7Pjrxw==" },
+ { hash: 'md5', key: '', data: ['A', 'B'], digest: 'base64',
+ expected: "ng6eVF72PUHftlPa7Pjrxw==" },
+ { hash: 'md5', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64',
+ expected: "ng6eVF72PUHftlPa7Pjrxw==" },
+
+ { hash: 'md5', key: '', data: ['AB'], digest: 'base64url',
+ expected: "ng6eVF72PUHftlPa7Pjrxw" },
+ { hash: 'md5', key: '', data: ['A', 'B'], digest: 'base64url',
+ expected: "ng6eVF72PUHftlPa7Pjrxw" },
+ { hash: 'md5', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url',
+ expected: "ng6eVF72PUHftlPa7Pjrxw" },
+
+ { hash: 'sha1', key: '', data: [], digest: 'hex',
+ expected: "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d" },
+ { hash: 'sha1', key: '', data: [''], digest: 'hex',
+ expected: "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d" },
+ { hash: 'sha1', key: '', data: [''],
+ expected: Buffer.from("fbdb1d1b18aa6c08324b7d64b71fb76370690e1d", "hex") },
+
+ { hash: 'sha1', key: '', data: ['AB'], digest: 'hex',
+ expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" },
+ { hash: 'sha1', key: '', data: ['A', 'B'], digest: 'hex',
+ expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" },
+ { hash: 'sha1', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex',
+ expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" },
+
+ { hash: 'sha1', key: '', data: [['4142', 'hex']], digest: 'hex',
+ expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" },
+ { hash: 'sha1', key: '', data: [['QUI=', 'base64']], digest: 'hex',
+ expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" },
+ { hash: 'sha1', key: '', data: [['QUI', 'base64url']], digest: 'hex',
+ expected: "d32c0b6637cc2dfe4670f3fe48ef4434123c4810" },
+
+ { hash: 'sha1', key: Buffer.from('secret'), data: ['abc'.repeat(100)], digest: 'hex',
+ expected: "0aac71e3a813a7acc4a809cfdedb2ecba04ffc5e" },
+
+ { hash: 'sha1', key: '', data: ['AB'], digest: 'base64',
+ expected: "0ywLZjfMLf5GcPP+SO9ENBI8SBA=" },
+ { hash: 'sha1', key: '', data: ['A', 'B'], digest: 'base64',
+ expected: "0ywLZjfMLf5GcPP+SO9ENBI8SBA=" },
+ { hash: 'sha1', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64',
+ expected: "0ywLZjfMLf5GcPP+SO9ENBI8SBA=" },
+
+ { hash: 'sha1', key: '', data: ['AB'], digest: 'base64url',
+ expected: "0ywLZjfMLf5GcPP-SO9ENBI8SBA" },
+ { hash: 'sha1', key: '', data: ['A', 'B'], digest: 'base64url',
+ expected: "0ywLZjfMLf5GcPP-SO9ENBI8SBA" },
+ { hash: 'sha1', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url',
+ expected: "0ywLZjfMLf5GcPP-SO9ENBI8SBA" },
+
+ { hash: 'sha256', key: '', data: [], digest: 'hex',
+ expected: "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad" },
+ { hash: 'sha256', key: '', data: [''], digest: 'hex',
+ expected: "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad" },
+ { hash: 'sha256', key: '', data: [''],
+ expected: Buffer.from("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", "hex") },
+
+ { hash: 'sha256', key: '', data: ['AB'], digest: 'hex',
+ expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" },
+ { hash: 'sha256', key: '', data: ['A', 'B'], digest: 'hex',
+ expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" },
+ { hash: 'sha256', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'hex',
+ expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" },
+
+ { hash: 'sha256', key: '', data: [['4142', 'hex']], digest: 'hex',
+ expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" },
+ { hash: 'sha256', key: '', data: [['QUI=', 'base64']], digest: 'hex',
+ expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" },
+ { hash: 'sha256', key: '', data: [['QUI', 'base64url']], digest: 'hex',
+ expected: "d53400095496267cf02e5dbd4b0bf9fbfb5f36f311ea7d9809af5487421743e3" },
+
+ { hash: 'sha256', key: Buffer.from('secret'), data: ['abc'.repeat(100)], digest: 'hex',
+ expected: "8660d2d51d6f20f61d5aadfb6c43df7fd05fc2fc4967d8aec1846f3d9ec03987" },
+
+ { hash: 'sha256', key: '', data: ['AB'], digest: 'base64',
+ expected: "1TQACVSWJnzwLl29Swv5+/tfNvMR6n2YCa9Uh0IXQ+M=" },
+ { hash: 'sha256', key: '', data: ['A', 'B'], digest: 'base64',
+ expected: "1TQACVSWJnzwLl29Swv5+/tfNvMR6n2YCa9Uh0IXQ+M=" },
+ { hash: 'sha256', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64',
+ expected: "1TQACVSWJnzwLl29Swv5+/tfNvMR6n2YCa9Uh0IXQ+M=" },
+
+ { hash: 'sha256', key: '', data: ['AB'], digest: 'base64url',
+ expected: "1TQACVSWJnzwLl29Swv5-_tfNvMR6n2YCa9Uh0IXQ-M" },
+ { hash: 'sha256', key: '', data: ['A', 'B'], digest: 'base64url',
+ expected: "1TQACVSWJnzwLl29Swv5-_tfNvMR6n2YCa9Uh0IXQ-M" },
+ { hash: 'sha256', key: '', data: [Buffer.from('XABX').subarray(1,3)], digest: 'base64url',
+ expected: "1TQACVSWJnzwLl29Swv5-_tfNvMR6n2YCa9Uh0IXQ-M" },
+
+ { hash: 'sha256', key: 'A'.repeat(64), data: ['AB'], digest: 'hex',
+ expected: "ee9dce43b12eb3e865614ad9c1a8d4fad4b6eac2b64647bd24cd192888d3f367" },
+
+ { hash: 'sha256', key: 'A'.repeat(100), data: ['AB'], digest: 'hex',
+ expected: "5647b6c429701ff512f0f18232b4507065d2376ca8899a816a0a6e721bf8ddcc" },
+
+ { hash: 'sha1',
+ hmac_value(hash, key) {
+ var Hmac = cr.createHmac(hash, key).constructor;
+ return Hmac(hash, key);
+ },
+ key: '', data: [], digest: 'hex',
+ expected: "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d" },
+
+ { hash: 'sha1',
+ hmac_value(hash, key) {
+ var h = cr.createHmac(hash, key);
+ h.digest('hex');
+ return h;
+ },
+ key: '', data: [], digest: 'hex',
+ exception: "TypeError: Digest already called" },
+
+ { hash: 'sha1',
+ hmac_value(hash, key) {
+ var h = cr.createHmac(hash, key);
+ h.digest('hex');
+ h.update('A');
+ return h;
+ },
+ key: '', data: [], digest: 'hex',
+ exception: "TypeError: Digest already called" },
+
+ { hash: 'sha1', key: '', data: [undefined], digest: 'hex',
+ exception: "TypeError: data is not a string or Buffer-like object" },
+ { hash: 'sha1', key: '', data: [{}], digest: 'hex',
+ exception: "TypeError: data is not a string or Buffer-like object" },
+
+ { hash: 'unknown', key: '', data: [], digest: 'hex',
+ exception: 'TypeError: not supported algorithm: "unknown"' },
+ { hash: 'sha1', key: '', data: [], digest: 'unknown',
+ exception: 'TypeError: unknown digest type: "unknown"' },
+
+ { hash: 'sha1', key: [], data: [], digest: 'hex',
+ exception: 'TypeError: key is not a string or Buffer-like object' },
+
+ { hash: 'sha1',
+ hmac_value(hash, key) {
+ var h = cr.createHash('sha1');
+ h.update.call(cr.createHmac(hash, key), '');
+ },
+ key: '', data: [], digest: 'hex',
+ exception: 'TypeError: "this" is not a hash object' },
+]};
+
+
+run([
+ createHash_tsuite,
+ createHmac_tsuite
+])
+.then($DONE, $DONE);
More information about the nginx-devel
mailing list