details:   
https://github.com/nginx/njs/commit/d5359d17f151bf172697e2ac353377b469b64c0f
branches:  master
commit:    d5359d17f151bf172697e2ac353377b469b64c0f
user:      Vadim Zhestikov <v.zhesti...@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);
_______________________________________________
nginx-devel mailing list
nginx-devel@nginx.org
https://mailman.nginx.org/mailman/listinfo/nginx-devel

Reply via email to