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