From 65ae5e8bd6970e1d8caa4a1cfd56567c5d4e439e Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Tue, 25 Feb 2025 22:35:55 -0800 Subject: [PATCH 1/3] Test262: using default prepare_args() where appropriate. --- test/buffer.t.js | 36 ++++-------------------------------- test/harness/runTsuite.js | 12 +++++++++++- test/querystring.t.mjs | 20 -------------------- test/text_decoder.t.js | 3 --- test/text_encoder.t.js | 9 --------- test/xml/saml_verify.t.mjs | 8 -------- test/zlib.t.mjs | 11 ++--------- 7 files changed, 17 insertions(+), 82 deletions(-) diff --git a/test/buffer.t.js b/test/buffer.t.js index 8e3f4ca9f..01e25beda 100644 --- a/test/buffer.t.js +++ b/test/buffer.t.js @@ -3,13 +3,6 @@ includes: [compatBuffer.js, runTsuite.js, compareArray.js] flags: [async] ---*/ -function p(args, default_opts) { - let params = merge({}, default_opts); - params = merge(params, args); - - return params; -} - let alloc_tsuite = { name: "Buffer.alloc() tests", skip: () => (!has_buffer()), @@ -23,7 +16,6 @@ let alloc_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: { encoding: 'utf-8' }, tests: [ @@ -56,7 +48,6 @@ let byteLength_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: { encoding: 'utf-8' }, tests: [ @@ -95,7 +86,6 @@ let concat_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ { buffers: [ Buffer.from('abc'), @@ -122,7 +112,6 @@ let compare_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -161,7 +150,6 @@ let comparePrototype_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -212,7 +200,6 @@ let copy_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -249,7 +236,6 @@ let equals_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -285,7 +271,6 @@ let fill_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa' }, @@ -336,7 +321,7 @@ let from_tsuite = { return 'SUCCESS'; }, - prepare_args: p, + opts: { fmt: 'utf-8' }, tests: [ @@ -445,7 +430,6 @@ let includes_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -471,7 +455,6 @@ let indexOf_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -519,7 +502,7 @@ let isBuffer_tsuite = { return 'SUCCESS'; }, - prepare_args: p, + opts: {}, tests: [ @@ -543,7 +526,6 @@ let isEncoding_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -594,7 +576,6 @@ let lastIndexOf_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -659,7 +640,6 @@ let readXIntXX_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -719,7 +699,6 @@ let readFloat_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -748,7 +727,6 @@ let readGeneric_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -789,7 +767,6 @@ let slice_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -818,7 +795,6 @@ let subarray_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -841,7 +817,6 @@ let swap_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: { swap: 'swap16' }, tests: [ @@ -867,8 +842,8 @@ let toJSON_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, + tests: [ { value: '', expected: { type: 'Buffer', data: [] } }, { value: 'αβγ', expected: { type: 'Buffer', data: [0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3] } }, @@ -893,7 +868,7 @@ let toString_tsuite = { return 'SUCCESS'; }, - prepare_args: p, + opts: { fmt: 'utf-8' }, tests: [ @@ -937,7 +912,6 @@ let write_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -981,7 +955,6 @@ let writeXIntXX_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -1022,7 +995,6 @@ let writeGeneric_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ diff --git a/test/harness/runTsuite.js b/test/harness/runTsuite.js index 1823e4a20..068a19e19 100644 --- a/test/harness/runTsuite.js +++ b/test/harness/runTsuite.js @@ -26,7 +26,10 @@ async function run(tlist) { return Promise.resolve("SKIPPED"); } - return ts.T(ts.prepare_args(t, ts.opts)); + let prepare_args = ts.prepare_args ? ts.prepare_args + : default_prepare_args; + + return ts.T(prepare_args(t, ts.opts)); } catch (e) { return Promise.reject(e); @@ -57,6 +60,13 @@ async function run(tlist) { } } +function default_prepare_args(args, default_opts) { + let params = merge({}, default_opts); + params = merge(params, args); + + return params; +} + function merge(to, from) { let r = Object.assign(Array.isArray(to) ? [] : {}, to); Object.keys(from).forEach(v => { diff --git a/test/querystring.t.mjs b/test/querystring.t.mjs index b382d10d7..cb42305e2 100644 --- a/test/querystring.t.mjs +++ b/test/querystring.t.mjs @@ -16,12 +16,7 @@ let escape_tsuite = { return 'SUCCESS'; }, - prepare_args: (args, default_opts) => { - let params = merge({}, default_opts); - params = merge(params, args); - return params; - }, opts: { }, tests: [ @@ -66,12 +61,7 @@ let parse_tsuite = { return 'SUCCESS'; }, - prepare_args: (args, default_opts) => { - let params = merge({}, default_opts); - params = merge(params, args); - return params; - }, opts: { }, tests: [ @@ -174,12 +164,7 @@ let stringify_tsuite = { return 'SUCCESS'; }, - prepare_args: (args, default_opts) => { - let params = merge({}, default_opts); - params = merge(params, args); - return params; - }, opts: { }, tests: [ @@ -230,12 +215,7 @@ let unescape_tsuite = { return 'SUCCESS'; }, - prepare_args: (args, default_opts) => { - let params = merge({}, default_opts); - params = merge(params, args); - return params; - }, opts: { }, tests: [ diff --git a/test/text_decoder.t.js b/test/text_decoder.t.js index 2eb879c00..a6fced2bb 100644 --- a/test/text_decoder.t.js +++ b/test/text_decoder.t.js @@ -40,7 +40,6 @@ let stream_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -96,7 +95,6 @@ let fatal_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -126,7 +124,6 @@ let ignoreBOM_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ diff --git a/test/text_encoder.t.js b/test/text_encoder.t.js index e790ae378..10324bcaf 100644 --- a/test/text_encoder.t.js +++ b/test/text_encoder.t.js @@ -4,13 +4,6 @@ includes: [runTsuite.js, compareArray.js] flags: [async] ---*/ -function p(args, default_opts) { - let params = merge({}, default_opts); - params = merge(params, args); - - return params; -} - let encode_tsuite = { name: "TextEncoder() encode tests", T: async (params) => { @@ -33,7 +26,6 @@ let encode_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ @@ -66,7 +58,6 @@ let encodeinto_tsuite = { return 'SUCCESS'; }, - prepare_args: p, opts: {}, tests: [ diff --git a/test/xml/saml_verify.t.mjs b/test/xml/saml_verify.t.mjs index 875fbd40c..d6f06a149 100644 --- a/test/xml/saml_verify.t.mjs +++ b/test/xml/saml_verify.t.mjs @@ -271,18 +271,10 @@ async function signatureSAML(signature, key_data, produce) { signedInfoC14n); } -function p(args, default_opts) { - let params = merge({}, default_opts); - params = merge(params, args); - - return params; -} - let saml_verify_tsuite = { name: "SAML verify", skip: () => (!has_njs() || !has_webcrypto() || !has_xml()), T: verify, - prepare_args: p, opts: { key: { fmt: "spki", file: "rsa.spki" }, }, diff --git a/test/zlib.t.mjs b/test/zlib.t.mjs index d972f6f83..faef1fb01 100644 --- a/test/zlib.t.mjs +++ b/test/zlib.t.mjs @@ -5,13 +5,6 @@ flags: [async] import zlib from 'zlib'; -function p(args, default_opts) { - let params = merge({}, default_opts); - params = merge(params, args); - - return params; -} - let deflateSync_tsuite = { name: "deflateSync()/deflateRawSync() tests", skip: () => !zlib.deflateRawSync, @@ -29,7 +22,7 @@ let deflateSync_tsuite = { return 'SUCCESS'; }, - prepare_args: p, + opts: { raw: true }, tests: [ @@ -72,7 +65,7 @@ let inflateSync_tsuite = { return 'SUCCESS'; }, - prepare_args: p, + opts: { raw: true }, tests: [ From 7d27a831521ef2fb066c9b685ddcce9a0619e7e1 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Tue, 25 Feb 2025 22:44:39 -0800 Subject: [PATCH 2/3] Test262: allowing to omit empty default option argument. --- test/buffer.t.js | 37 ------------------------------- test/fs/methods.t.mjs | 4 ---- test/harness/runTsuite.js | 2 +- test/querystring.t.mjs | 8 ------- test/text_decoder.t.js | 6 ----- test/text_encoder.t.js | 4 ---- test/webcrypto/digest.t.mjs | 1 - test/webcrypto/import.t.mjs | 4 ---- test/webcrypto/rsa_decoding.t.mjs | 1 - 9 files changed, 1 insertion(+), 66 deletions(-) diff --git a/test/buffer.t.js b/test/buffer.t.js index 01e25beda..378211048 100644 --- a/test/buffer.t.js +++ b/test/buffer.t.js @@ -86,7 +86,6 @@ let concat_tsuite = { return 'SUCCESS'; }, - opts: {}, tests: [ { buffers: [ Buffer.from('abc'), Buffer.from(new Uint8Array([0x64, 0x65, 0x66]).buffer, 1) ], @@ -112,8 +111,6 @@ let compare_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf1: Buffer.from('abc'), buf2: Buffer.from('abc'), expected: 0 }, { buf1: Buffer.from('abc'), @@ -150,8 +147,6 @@ let comparePrototype_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abc'), target: Buffer.from('abc'), expected: 0 }, { buf: Buffer.from('abc'), @@ -200,8 +195,6 @@ let copy_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), target: Buffer.from('123456'), expected: 6, expected_buf: 'abcdef' }, @@ -236,7 +229,6 @@ let equals_tsuite = { return 'SUCCESS'; }, - opts: {}, tests: [ { buf1: Buffer.from('abc'), buf2: Buffer.from('abc'), expected: true }, @@ -271,7 +263,6 @@ let fill_tsuite = { return 'SUCCESS'; }, - opts: {}, tests: [ { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa' }, { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa', offset: 0, end: 3 }, @@ -430,8 +421,6 @@ let includes_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), value: 'abc', expected: true }, { buf: Buffer.from('abcdef'), value: 'def', expected: true }, @@ -455,8 +444,6 @@ let indexOf_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), value: 'abc', expected: 0 }, { buf: Buffer.from('abcdef'), value: 'def', expected: 3 }, @@ -503,8 +490,6 @@ let isBuffer_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: Buffer.from('α'), expected: true }, { value: new Uint8Array(10), expected: false }, @@ -526,8 +511,6 @@ let isEncoding_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: 'utf-8', expected: true }, { value: 'utf8', expected: true }, @@ -576,8 +559,6 @@ let lastIndexOf_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), value: 'abc', expected: 0 }, { buf: Buffer.from('abcabc'), value: 'abc', expected: 3 }, @@ -640,8 +621,6 @@ let readXIntXX_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, expected: [ -86,170,-17494,-21829,48042,43707,-573785174,-1430532899,3721182122,2864434397 ] }, @@ -699,8 +678,6 @@ let readFloat_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ {} ], @@ -727,8 +704,6 @@ let readGeneric_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, length: 1, expected: [ 170, 170, -86, -86 ] }, @@ -767,8 +742,6 @@ let slice_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), start: 1, expected: 'bcdef' }, { buf: Buffer.from('abcdef'), start: 1, end: 3, expected: 'bc' }, @@ -795,8 +768,6 @@ let subarray_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { buf: Buffer.from('abcdef'), start: 0, end: 3, expected: 'Zbc' }, { buf: Buffer.from('abcdef'), start: 1, expected: 'bcdef' }, @@ -842,8 +813,6 @@ let toJSON_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: '', expected: { type: 'Buffer', data: [] } }, { value: 'αβγ', expected: { type: 'Buffer', data: [0xCE, 0xB1, 0xCE, 0xB2, 0xCE, 0xB3] } }, @@ -912,8 +881,6 @@ let write_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: 'abc', expected: 3, expected_buf: 'abcZZZZZZZ' }, { value: 'abc', offset: 1, expected: 3, expected_buf: 'ZabcZZZZZZ' }, @@ -955,8 +922,6 @@ let writeXIntXX_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { write: 'writeInt8', value: 0xaa, exception: 'RangeError: Index out of range' }, { write: 'writeInt8', value: 0x00, offset: 3, expected: 4, expected_buf: '5a5a5a005a5a5a5a5a5a' }, @@ -995,8 +960,6 @@ let writeGeneric_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { write: 'writeUIntLE', value: 0xaa, length: 1, exception: 'RangeError: Index out of range' }, diff --git a/test/fs/methods.t.mjs b/test/fs/methods.t.mjs index 928d16821..705c1b5a8 100644 --- a/test/fs/methods.t.mjs +++ b/test/fs/methods.t.mjs @@ -1008,7 +1008,6 @@ let readSync_tsuite = { skip: () => (!has_buffer()), T: read_test, prepare_args: p, - opts: {}, get tests() { return read_tests() }, }; @@ -1017,7 +1016,6 @@ let readFh_tsuite = { skip: () => (!has_buffer()), T: readFh_test, prepare_args: p, - opts: {}, get tests() { return read_tests() }, }; @@ -1213,7 +1211,6 @@ let writeSync_tsuite = { skip: () => (!has_buffer()), T: write_test, prepare_args: p, - opts: {}, get tests() { return write_tests() }, }; @@ -1222,7 +1219,6 @@ let writeFh_tsuite = { skip: () => (!has_buffer()), T: writeFh_test, prepare_args: p, - opts: {}, get tests() { return write_tests() }, }; diff --git a/test/harness/runTsuite.js b/test/harness/runTsuite.js index 068a19e19..2a324fbeb 100644 --- a/test/harness/runTsuite.js +++ b/test/harness/runTsuite.js @@ -29,7 +29,7 @@ async function run(tlist) { let prepare_args = ts.prepare_args ? ts.prepare_args : default_prepare_args; - return ts.T(prepare_args(t, ts.opts)); + return ts.T(prepare_args(t, ts.opts ? ts.opts : {})); } catch (e) { return Promise.reject(e); diff --git a/test/querystring.t.mjs b/test/querystring.t.mjs index cb42305e2..8ea14050e 100644 --- a/test/querystring.t.mjs +++ b/test/querystring.t.mjs @@ -17,8 +17,6 @@ let escape_tsuite = { return 'SUCCESS'; }, - opts: { }, - tests: [ { value: '', expected: '' }, { value: 'baz=fuz', expected: 'baz%3Dfuz' }, @@ -62,8 +60,6 @@ let parse_tsuite = { return 'SUCCESS'; }, - opts: { }, - tests: [ { value: '', expected: {} }, { value: 'baz=fuz', expected: { baz:'fuz' } }, @@ -165,8 +161,6 @@ let stringify_tsuite = { return 'SUCCESS'; }, - opts: { }, - tests: [ { obj: {}, expected: '' }, { obj: { baz:'fuz', muz:'tax' }, expected: 'baz=fuz&muz=tax' }, @@ -216,8 +210,6 @@ let unescape_tsuite = { return 'SUCCESS'; }, - opts: { }, - tests: [ { value: '', expected: '' }, { value: 'baz%3Dfuz', expected: 'baz=fuz' }, diff --git a/test/text_decoder.t.js b/test/text_decoder.t.js index a6fced2bb..afc91fc46 100644 --- a/test/text_decoder.t.js +++ b/test/text_decoder.t.js @@ -40,8 +40,6 @@ let stream_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { chunks: [new Uint8Array([0xF0, 0x9F, 0x8C, 0x9F])], expected: ['🌟'] }, @@ -95,8 +93,6 @@ let fatal_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { chunks: [new Uint8Array([0xF0, 0xA0, 0xAE, 0xB7])], expected: ['𠮷'] }, @@ -124,8 +120,6 @@ let ignoreBOM_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: new Uint8Array([239, 187, 191, 50]), opts: {ignoreBOM: true}, diff --git a/test/text_encoder.t.js b/test/text_encoder.t.js index 10324bcaf..25173cd1b 100644 --- a/test/text_encoder.t.js +++ b/test/text_encoder.t.js @@ -26,8 +26,6 @@ let encode_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: "", expected: [] }, { value: "abc", expected: [97, 98, 99] }, @@ -58,8 +56,6 @@ let encodeinto_tsuite = { return 'SUCCESS'; }, - opts: {}, - tests: [ { value: "", dest: new Uint8Array(4), expected: [], read: 0 }, { value: "aα", dest: new Uint8Array(3), expected: [97, 206, 177], read: 2 }, diff --git a/test/webcrypto/digest.t.mjs b/test/webcrypto/digest.t.mjs index 4318fe166..f07f0346d 100644 --- a/test/webcrypto/digest.t.mjs +++ b/test/webcrypto/digest.t.mjs @@ -25,7 +25,6 @@ let digest_tsuite = { skip: () => (!has_buffer() || !has_webcrypto()), T: test, prepare_args: p, - opts: { }, tests: [ { name: "XXX", data: "", diff --git a/test/webcrypto/import.t.mjs b/test/webcrypto/import.t.mjs index 1b8040061..9ec0fb607 100644 --- a/test/webcrypto/import.t.mjs +++ b/test/webcrypto/import.t.mjs @@ -85,7 +85,6 @@ let aes_tsuite = { skip: () => (!has_webcrypto()), T: test, prepare_args: p, - opts: {}, tests: [ { key: { fmt: "jwk", @@ -186,7 +185,6 @@ let ec_tsuite = { skip: () => (!has_webcrypto()), T: test, prepare_args: p, - opts: {}, tests: [ { key: { fmt: "jwk", @@ -350,7 +348,6 @@ let hmac_tsuite = { skip: () => (!has_webcrypto()), T: test, prepare_args: p, - opts: {}, tests: [ { key: { fmt: "raw", @@ -465,7 +462,6 @@ let rsa_tsuite = { skip: () => (!has_webcrypto()), T: test, prepare_args: p, - opts: {}, tests: [ { key: { fmt: "jwk", diff --git a/test/webcrypto/rsa_decoding.t.mjs b/test/webcrypto/rsa_decoding.t.mjs index 08874491c..179072ff7 100644 --- a/test/webcrypto/rsa_decoding.t.mjs +++ b/test/webcrypto/rsa_decoding.t.mjs @@ -29,7 +29,6 @@ let rsa_tsuite = { name: "RSA-OAEP decoding", T: test, prepare_args: (v) => v, - opts: { }, tests: [ { pem: "rsa.pkcs8", src: "text.base64.rsa-oaep.enc", expected: "WAKAWAKA" }, From 03879f04e67175ace7bb58ebf38255983320271a Mon Sep 17 00:00:00 2001 From: Vadim Zhestikov Date: Wed, 5 Mar 2025 18:54:41 -0800 Subject: [PATCH 3/3] 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(-) create mode 100644 external/qjs_crypto_module.c create mode 100644 test/crypto.t.mjs diff --git a/auto/qjs_modules b/auto/qjs_modules index d82f9d9f3..1381d2c5b 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 000000000..2b1bb26b2 --- /dev/null +++ b/external/qjs_crypto_module.c @@ -0,0 +1,659 @@ + +/* + * Copyright (C) Vadim Zhestkov + * Copyright (C) F5, Inc. + */ + +#include +#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 730feb6ea..a19836518 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -1238,29 +1238,6 @@ qjs_cipher_aes_cbc(JSContext *cx, njs_str_t *data, qjs_webcrypto_key_t *key, } -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 ed9a61785..15a575a28 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 7c560d5fc..54f96dfec 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 4f5e5c910..f39660f5f 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 = `ToveJani`;" \ "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 000000000..ad237d887 --- /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);