aboutsummaryrefslogtreecommitdiff
path: root/test/js
diff options
context:
space:
mode:
authorGravatar Dylan Conway <dylan.conway567@gmail.com> 2023-10-10 15:28:08 -0700
committerGravatar Dylan Conway <dylan.conway567@gmail.com> 2023-10-10 15:28:08 -0700
commitee2e34866e3bc0d12ba5cb1d5041524776472d71 (patch)
tree3f71bf8153545396ba38294f8577e77ce0b12439 /test/js
parente6d97f2581959d77a5b486faefbfdf094abedf9b (diff)
parent6301778a589254e2c3c0d95f768fce303f528b03 (diff)
downloadbun-ee2e34866e3bc0d12ba5cb1d5041524776472d71.tar.gz
bun-ee2e34866e3bc0d12ba5cb1d5041524776472d71.tar.zst
bun-ee2e34866e3bc0d12ba5cb1d5041524776472d71.zip
Merge branch 'main' into dylan/github-api-option
Diffstat (limited to 'test/js')
-rw-r--r--test/js/bun/http/serve.test.ts28
-rw-r--r--test/js/node/crypto/crypto.hmac.test.ts426
-rw-r--r--test/js/node/crypto/crypto.key-objects.test.ts1643
-rw-r--r--test/js/node/crypto/fixtures/ec_p256_private.pem5
-rw-r--r--test/js/node/crypto/fixtures/ec_p256_public.pem4
-rw-r--r--test/js/node/crypto/fixtures/ec_p384_private.pem6
-rw-r--r--test/js/node/crypto/fixtures/ec_p384_public.pem5
-rw-r--r--test/js/node/crypto/fixtures/ec_p521_private.pem8
-rw-r--r--test/js/node/crypto/fixtures/ec_p521_public.pem6
-rw-r--r--test/js/node/crypto/fixtures/ec_secp256k1_private.pem5
-rw-r--r--test/js/node/crypto/fixtures/ec_secp256k1_public.pem4
-rw-r--r--test/js/node/crypto/fixtures/ed25519_private.pem3
-rw-r--r--test/js/node/crypto/fixtures/ed25519_public.pem3
-rw-r--r--test/js/node/crypto/fixtures/ed448_private.pem4
-rw-r--r--test/js/node/crypto/fixtures/ed448_public.pem4
-rw-r--r--test/js/node/crypto/fixtures/rsa_private.pem27
-rw-r--r--test/js/node/crypto/fixtures/rsa_private_2048.pem27
-rw-r--r--test/js/node/crypto/fixtures/rsa_private_4096.pem51
-rw-r--r--test/js/node/crypto/fixtures/rsa_private_encrypted.pem30
-rw-r--r--test/js/node/crypto/fixtures/rsa_pss_private_2048.pem28
-rw-r--r--test/js/node/crypto/fixtures/rsa_pss_public_2048.pem9
-rw-r--r--test/js/node/crypto/fixtures/rsa_public.pem9
-rw-r--r--test/js/node/crypto/fixtures/rsa_public_2048.pem9
-rw-r--r--test/js/node/crypto/fixtures/rsa_public_4096.pem14
-rw-r--r--test/js/node/crypto/fixtures/x25519_private.pem3
-rw-r--r--test/js/node/crypto/fixtures/x25519_public.pem3
-rw-r--r--test/js/node/crypto/fixtures/x448_private.pem4
-rw-r--r--test/js/node/crypto/fixtures/x448_public.pem4
-rw-r--r--test/js/node/process/process.test.js9
-rw-r--r--test/js/third_party/jsonwebtoken/async_sign.test.js159
-rw-r--r--test/js/third_party/jsonwebtoken/buffer.test.js10
-rw-r--r--test/js/third_party/jsonwebtoken/claim-aud.test.js423
-rw-r--r--test/js/third_party/jsonwebtoken/claim-exp.test.js316
-rw-r--r--test/js/third_party/jsonwebtoken/claim-iat.test.js254
-rw-r--r--test/js/third_party/jsonwebtoken/claim-iss.test.js185
-rw-r--r--test/js/third_party/jsonwebtoken/claim-jti.test.js135
-rw-r--r--test/js/third_party/jsonwebtoken/claim-nbf.test.js312
-rw-r--r--test/js/third_party/jsonwebtoken/claim-private.test.js55
-rw-r--r--test/js/third_party/jsonwebtoken/claim-sub.test.js133
-rw-r--r--test/js/third_party/jsonwebtoken/decoding.test.js9
-rw-r--r--test/js/third_party/jsonwebtoken/dsa-private.pem36
-rw-r--r--test/js/third_party/jsonwebtoken/dsa-public.pem36
-rw-r--r--test/js/third_party/jsonwebtoken/ecdsa-private.pem18
-rw-r--r--test/js/third_party/jsonwebtoken/ecdsa-public-invalid.pem9
-rw-r--r--test/js/third_party/jsonwebtoken/ecdsa-public-x509.pem19
-rw-r--r--test/js/third_party/jsonwebtoken/ecdsa-public.pem9
-rw-r--r--test/js/third_party/jsonwebtoken/encoding.test.js37
-rw-r--r--test/js/third_party/jsonwebtoken/expires_format.test.js10
-rw-r--r--test/js/third_party/jsonwebtoken/header-kid.test.js83
-rw-r--r--test/js/third_party/jsonwebtoken/invalid_exp.test.js54
-rw-r--r--test/js/third_party/jsonwebtoken/invalid_pub.pem19
-rw-r--r--test/js/third_party/jsonwebtoken/issue_147.test.js10
-rw-r--r--test/js/third_party/jsonwebtoken/issue_304.test.js43
-rw-r--r--test/js/third_party/jsonwebtoken/issue_70.test.js14
-rw-r--r--test/js/third_party/jsonwebtoken/jwt.asymmetric_signing.test.js208
-rw-r--r--test/js/third_party/jsonwebtoken/jwt.hs.test.js140
-rw-r--r--test/js/third_party/jsonwebtoken/jwt.malicious.test.js44
-rw-r--r--test/js/third_party/jsonwebtoken/noTimestamp.test.js10
-rw-r--r--test/js/third_party/jsonwebtoken/non_object_values.test.js16
-rw-r--r--test/js/third_party/jsonwebtoken/option-complete.test.js53
-rw-r--r--test/js/third_party/jsonwebtoken/option-maxAge.test.js62
-rw-r--r--test/js/third_party/jsonwebtoken/option-nonce.test.js41
-rw-r--r--test/js/third_party/jsonwebtoken/package.json8
-rw-r--r--test/js/third_party/jsonwebtoken/prime256v1-private.pem5
-rw-r--r--test/js/third_party/jsonwebtoken/priv.pem27
-rw-r--r--test/js/third_party/jsonwebtoken/pub.pem22
-rw-r--r--test/js/third_party/jsonwebtoken/rsa-private.pem27
-rw-r--r--test/js/third_party/jsonwebtoken/rsa-pss-invalid-salt-length-private.pem29
-rw-r--r--test/js/third_party/jsonwebtoken/rsa-pss-private.pem29
-rw-r--r--test/js/third_party/jsonwebtoken/rsa-public-key.pem8
-rw-r--r--test/js/third_party/jsonwebtoken/rsa-public-key.test.js44
-rw-r--r--test/js/third_party/jsonwebtoken/rsa-public.pem9
-rw-r--r--test/js/third_party/jsonwebtoken/schema.test.js72
-rw-r--r--test/js/third_party/jsonwebtoken/secp384r1-private.pem6
-rw-r--r--test/js/third_party/jsonwebtoken/secp521r1-private.pem7
-rw-r--r--test/js/third_party/jsonwebtoken/set_headers.test.js16
-rw-r--r--test/js/third_party/jsonwebtoken/test-utils.js116
-rw-r--r--test/js/third_party/jsonwebtoken/undefined_secretOrPublickey.test.js18
-rw-r--r--test/js/third_party/jsonwebtoken/validateAsymmetricKey.test.js209
-rw-r--r--test/js/third_party/jsonwebtoken/verify.test.js318
-rw-r--r--test/js/third_party/jsonwebtoken/wrong_alg.test.js49
-rw-r--r--test/js/web/abort/abort.signal.ts24
-rw-r--r--test/js/web/abort/abort.test.ts21
-rw-r--r--test/js/web/fetch/fetch.stream.test.ts136
84 files changed, 6543 insertions, 0 deletions
diff --git a/test/js/bun/http/serve.test.ts b/test/js/bun/http/serve.test.ts
index 67b7614b2..9b8448b55 100644
--- a/test/js/bun/http/serve.test.ts
+++ b/test/js/bun/http/serve.test.ts
@@ -1276,3 +1276,31 @@ it("server.requestIP (unix)", async () => {
connection.end();
server.stop(true);
});
+
+it("should response with HTTP 413 when request body is larger than maxRequestBodySize, issue#6031", async () => {
+ const server = Bun.serve({
+ port: 0,
+ maxRequestBodySize: 10,
+ fetch(req, server) {
+ return new Response("OK");
+ },
+ });
+
+ {
+ const resp = await fetch(`http://${server.hostname}:${server.port}`, {
+ method: "POST",
+ body: "A".repeat(10),
+ });
+ expect(resp.status).toBe(200);
+ expect(await resp.text()).toBe("OK");
+ }
+ {
+ const resp = await fetch(`http://${server.hostname}:${server.port}`, {
+ method: "POST",
+ body: "A".repeat(11),
+ });
+ expect(resp.status).toBe(413);
+ }
+
+ server.stop(true);
+});
diff --git a/test/js/node/crypto/crypto.hmac.test.ts b/test/js/node/crypto/crypto.hmac.test.ts
new file mode 100644
index 000000000..6a54d1a82
--- /dev/null
+++ b/test/js/node/crypto/crypto.hmac.test.ts
@@ -0,0 +1,426 @@
+import { Hmac, createHmac, createSecretKey } from "crypto";
+import { test, expect, describe } from "bun:test";
+
+function testHmac(algo, key, data, expected) {
+ if (!Array.isArray(data)) data = [data];
+ // If the key is a Buffer, test Hmac with a key object as well.
+ const keyWrappers = [key => key, ...(typeof key === "string" ? [] : [createSecretKey])];
+ const wrapperName = ["default", "KeyObject"];
+
+ for (const i in keyWrappers) {
+ const keyWrapper = keyWrappers[i];
+ test(`Hmac ${algo} with ${wrapperName[i]} key`, async () => {
+ const hmac = createHmac(algo, keyWrapper(key));
+ for (const chunk of data) hmac.update(chunk);
+ const actual = hmac.digest("hex");
+ expect(actual).toEqual(expected);
+ });
+ }
+}
+
+describe("crypto.Hmac", () => {
+ test.todo("Hmac is expected to return a new instance", async () => {
+ const instance = Hmac("sha256", "Node");
+ expect(instance instanceof Hmac).toBe(true);
+ });
+
+ test("createHmac should throw when using invalid options", async () => {
+ expect(() => createHmac(null)).toThrow("null is not an object");
+ expect(() => createHmac("sha1", null)).toThrow("null is not an object");
+ });
+
+ describe("test HMAC with multiple updates.", async () => {
+ testHmac("sha1", "Node", ["some data", "to hmac"], "19fd6e1ba73d9ed2224dd5094a71babe85d9a892");
+ });
+
+ describe("test HMAC with Wikipidia test cases", async () => {
+ const wikipedia = [
+ {
+ key: "key",
+ data: "The quick brown fox jumps over the lazy dog",
+ hmac: {
+ // HMACs lifted from Wikipedia.
+ md5: "80070713463e7749b90c2dc24911e275",
+ sha1: "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9",
+ sha256: "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc" + "2d1a3cd8",
+ },
+ },
+ {
+ key: "key",
+ data: "",
+ hmac: {
+ // Intermediate test to help debugging.
+ md5: "63530468a04e386459855da0063b6596",
+ sha1: "f42bb0eeb018ebbd4597ae7213711ec60760843f",
+ sha256: "5d5d139563c95b5967b9bd9a8c9b233a9dedb45072794cd232dc1b74" + "832607d0",
+ },
+ },
+ {
+ key: "",
+ data: "The quick brown fox jumps over the lazy dog",
+ hmac: {
+ // Intermediate test to help debugging.
+ md5: "ad262969c53bc16032f160081c4a07a0",
+ sha1: "2ba7f707ad5f187c412de3106583c3111d668de8",
+ sha256: "fb011e6154a19b9a4c767373c305275a5a69e8b68b0b4c9200c383dc" + "ed19a416",
+ },
+ },
+ {
+ key: "",
+ data: "",
+ hmac: {
+ // HMACs lifted from Wikipedia.
+ md5: "74e6f7298a9c2d168935f58c001bad88",
+ sha1: "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d",
+ sha256: "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c71214" + "4292c5ad",
+ },
+ },
+ ];
+
+ for (const { key, data, hmac } of wikipedia) {
+ for (const hash in hmac) testHmac(hash, key, data, hmac[hash]);
+ }
+ });
+
+ describe("HMAC-SHA-* (rfc 4231 Test Cases)", async () => {
+ const rfc4231 = [
+ {
+ key: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"),
+ data: Buffer.from("4869205468657265", "hex"), // 'Hi There'
+ hmac: {
+ sha224: "896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22",
+ sha256: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c" + "2e32cff7",
+ sha384:
+ "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c" + "7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6",
+ sha512:
+ "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b305" +
+ "45e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f170" +
+ "2e696c203a126854",
+ },
+ },
+ {
+ key: Buffer.from("4a656665", "hex"), // 'Jefe'
+ data: Buffer.from("7768617420646f2079612077616e7420666f72206e6f74686" + "96e673f", "hex"), // 'what do ya want for nothing?'
+ hmac: {
+ sha224: "a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44",
+ sha256: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b9" + "64ec3843",
+ sha384:
+ "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373" + "6322445e8e2240ca5e69e2c78b3239ecfab21649",
+ sha512:
+ "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7" +
+ "ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b" +
+ "636e070a38bce737",
+ },
+ },
+ {
+ key: Buffer.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "hex"),
+ data: Buffer.from(
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd" + "ddddddddddddddddddddddddddddddddddddddddddddddddddd",
+ "hex",
+ ),
+ hmac: {
+ sha224: "7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea",
+ sha256: "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514" + "ced565fe",
+ sha384:
+ "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5" + "5966144b2a5ab39dc13814b94e3ab6e101a34f27",
+ sha512:
+ "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33" +
+ "b2279d39bf3e848279a722c806b485a47e67c807b946a337bee89426" +
+ "74278859e13292fb",
+ },
+ },
+ {
+ key: Buffer.from("0102030405060708090a0b0c0d0e0f10111213141516171819", "hex"),
+ data: Buffer.from(
+ "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc" + "dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
+ "hex",
+ ),
+ hmac: {
+ sha224: "6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a",
+ sha256: "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff4" + "6729665b",
+ sha384:
+ "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e" + "1f573b4e6801dd23c4a7d679ccf8a386c674cffb",
+ sha512:
+ "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050" +
+ "361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2d" +
+ "e2adebeb10a298dd",
+ },
+ },
+
+ {
+ key: Buffer.from("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "hex"),
+ // 'Test With Truncation'
+ data: Buffer.from("546573742057697468205472756e636174696f6e", "hex"),
+ hmac: {
+ sha224: "0e2aea68a90c8d37c988bcdb9fca6fa8",
+ sha256: "a3b6167473100ee06e0c796c2955552b",
+ sha384: "3abf34c3503b2a23a46efc619baef897",
+ sha512: "415fad6271580a531d4179bc891d87a6",
+ },
+ truncate: true,
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaa",
+ "hex",
+ ),
+ // 'Test Using Larger Than Block-Size Key - Hash Key First'
+ data: Buffer.from(
+ "54657374205573696e67204c6172676572205468616e20426" +
+ "c6f636b2d53697a65204b6579202d2048617368204b657920" +
+ "4669727374",
+ "hex",
+ ),
+ hmac: {
+ sha224: "95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e",
+ sha256: "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f" + "0ee37f54",
+ sha384:
+ "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05" + "033ac4c60c2ef6ab4030fe8296248df163f44952",
+ sha512:
+ "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b0137" +
+ "83f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec" +
+ "8b915a985d786598",
+ },
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaa",
+ "hex",
+ ),
+ // 'This is a test using a larger than block-size key and a larger ' +
+ // 'than block-size data. The key needs to be hashed before being ' +
+ // 'used by the HMAC algorithm.'
+ data: Buffer.from(
+ "5468697320697320612074657374207573696e672061206c6" +
+ "172676572207468616e20626c6f636b2d73697a65206b6579" +
+ "20616e642061206c6172676572207468616e20626c6f636b2" +
+ "d73697a6520646174612e20546865206b6579206e65656473" +
+ "20746f20626520686173686564206265666f7265206265696" +
+ "e6720757365642062792074686520484d414320616c676f72" +
+ "6974686d2e",
+ "hex",
+ ),
+ hmac: {
+ sha224: "3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1",
+ sha256: "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f5153" + "5c3a35e2",
+ sha384:
+ "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82" + "461e99c5a678cc31e799176d3860e6110c46523e",
+ sha512:
+ "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d" +
+ "20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de04460" +
+ "65c97440fa8c6a58",
+ },
+ },
+ ];
+
+ for (let i = 0, l = rfc4231.length; i < l; i++) {
+ for (const hash in rfc4231[i].hmac) {
+ test(`Test HMAC-${hash} rfc 4231 case ${i + 1}`, async () => {
+ const str = createHmac(hash, rfc4231[i].key);
+ str.end(rfc4231[i].data);
+ let strRes = str.read().toString("hex");
+ let actual = createHmac(hash, rfc4231[i].key).update(rfc4231[i].data).digest("hex");
+ if (rfc4231[i].truncate) {
+ actual = actual.substr(0, 32); // first 128 bits == 32 hex chars
+ strRes = strRes.substr(0, 32);
+ }
+ const expected = rfc4231[i].hmac[hash];
+ // `Test HMAC-${hash} rfc 4231 case ${i + 1}: ${actual} must be ${expected}`);
+ expect(actual).toEqual(expected);
+ expect(actual).toEqual(strRes);
+ });
+ }
+ }
+ });
+
+ describe("HMAC-MD5/SHA1 (rfc 2202 Test Cases)", async () => {
+ const rfc2202_md5 = [
+ {
+ key: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"),
+ data: "Hi There",
+ hmac: "9294727a3638bb1c13f48ef8158bfc9d",
+ },
+ {
+ key: "Jefe",
+ data: "what do ya want for nothing?",
+ hmac: "750c783e6ab0b503eaa86e310a5db738",
+ },
+ {
+ key: Buffer.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "hex"),
+ data: Buffer.from(
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd" + "ddddddddddddddddddddddddddddddddddddddddddddddddddd",
+ "hex",
+ ),
+ hmac: "56be34521d144c88dbb8c733f0e8b3f6",
+ },
+ {
+ key: Buffer.from("0102030405060708090a0b0c0d0e0f10111213141516171819", "hex"),
+ data: Buffer.from(
+ "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc" +
+ "dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
+ "cdcdcdcdcd",
+ "hex",
+ ),
+ hmac: "697eaf0aca3a3aea3a75164746ffaa79",
+ },
+ {
+ key: Buffer.from("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "hex"),
+ data: "Test With Truncation",
+ hmac: "56461ef2342edc00f9bab995690efd4c",
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaa",
+ "hex",
+ ),
+ data: "Test Using Larger Than Block-Size Key - Hash Key First",
+ hmac: "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd",
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaa",
+ "hex",
+ ),
+ data: "Test Using Larger Than Block-Size Key and Larger Than One " + "Block-Size Data",
+ hmac: "6f630fad67cda0ee1fb1f562db3aa53e",
+ },
+ ];
+
+ for (const { key, data, hmac } of rfc2202_md5) {
+ describe(`rfc 2202 md5 case ${hmac}`, async () => {
+ testHmac("md5", key, data, hmac);
+ });
+ }
+
+ const rfc2202_sha1 = [
+ {
+ key: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"),
+ data: "Hi There",
+ hmac: "b617318655057264e28bc0b6fb378c8ef146be00",
+ },
+ {
+ key: "Jefe",
+ data: "what do ya want for nothing?",
+ hmac: "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79",
+ },
+ {
+ key: Buffer.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "hex"),
+ data: Buffer.from(
+ "ddddddddddddddddddddddddddddddddddddddddddddd" +
+ "ddddddddddddddddddddddddddddddddddddddddddddd" +
+ "dddddddddd",
+ "hex",
+ ),
+ hmac: "125d7342b9ac11cd91a39af48aa17b4f63f175d3",
+ },
+ {
+ key: Buffer.from("0102030405060708090a0b0c0d0e0f10111213141516171819", "hex"),
+ data: Buffer.from(
+ "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc" +
+ "dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
+ "cdcdcdcdcd",
+ "hex",
+ ),
+ hmac: "4c9007f4026250c6bc8414f9bf50c86c2d7235da",
+ },
+ {
+ key: Buffer.from("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "hex"),
+ data: "Test With Truncation",
+ hmac: "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04",
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaa",
+ "hex",
+ ),
+ data: "Test Using Larger Than Block-Size Key - Hash Key First",
+ hmac: "aa4ae5e15272d00e95705637ce8a3b55ed402112",
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaa",
+ "hex",
+ ),
+ data: "Test Using Larger Than Block-Size Key and Larger Than One " + "Block-Size Data",
+ hmac: "e8e99d0f45237d786d6bbaa7965c7808bbff1a91",
+ },
+ ];
+
+ for (const { key, data, hmac } of rfc2202_sha1) {
+ describe(`rfc 2202 sha1 case ${hmac}`, async () => {
+ testHmac("sha1", key, data, hmac);
+ });
+ }
+ });
+
+ test("sha256 w00t ucs2", async () => {
+ expect(createHmac("sha256", "w00t").digest("ucs2")).toEqual(createHmac("sha256", "w00t").digest().toString("ucs2"));
+ });
+
+ test("Check initialized -> uninitialized state transition after calling digest().", async () => {
+ {
+ const expected =
+ "\u0010\u0041\u0052\u00c5\u00bf\u00dc\u00a0\u007b\u00c6\u0033" +
+ "\u00ee\u00bd\u0046\u0019\u009f\u0002\u0055\u00c9\u00f4\u009d";
+
+ {
+ const h = createHmac("sha1", "key").update("data");
+ expect(h.digest("latin1")).toBe(expected);
+ expect(h.digest("latin1")).toBe("");
+ }
+ {
+ const h = createHmac("sha1", "key").update("data");
+ expect(h.digest("buffer")).toEqual(Buffer.from(expected, "latin1"));
+ expect(h.digest("buffer")).toEqual(Buffer.from(""));
+ }
+ }
+ {
+ const expected =
+ "\u00f4\u002b\u00b0\u00ee\u00b0\u0018\u00eb\u00bd\u0045\u0097" +
+ "\u00ae\u0072\u0013\u0071\u001e\u00c6\u0007\u0060\u0084\u003f";
+ {
+ const h = createHmac("sha1", "key");
+ expect(h.digest("latin1")).toBe(expected);
+ expect(h.digest("latin1")).toBe("");
+ }
+ {
+ const h = createHmac("sha1", "key");
+ expect(h.digest("buffer")).toEqual(Buffer.from(expected, "latin1"));
+ expect(h.digest("buffer")).toEqual(Buffer.from(""));
+ }
+ }
+ });
+ test("Invalid digest", async () => {
+ expect(() => createHmac("sha7", "key")).toThrow(/sha7 is not supported/);
+ });
+
+ test("secret digest", async () => {
+ const buf = Buffer.alloc(0);
+ const keyObject = createSecretKey(Buffer.alloc(0));
+ expect(createHmac("sha256", buf).update("foo").digest()).toEqual(
+ createHmac("sha256", keyObject).update("foo").digest(),
+ );
+ });
+});
diff --git a/test/js/node/crypto/crypto.key-objects.test.ts b/test/js/node/crypto/crypto.key-objects.test.ts
new file mode 100644
index 000000000..b124ca479
--- /dev/null
+++ b/test/js/node/crypto/crypto.key-objects.test.ts
@@ -0,0 +1,1643 @@
+"use strict";
+
+import {
+ createCipheriv,
+ createDecipheriv,
+ createSign,
+ createVerify,
+ createSecretKey,
+ createPublicKey,
+ createPrivateKey,
+ KeyObject,
+ randomBytes,
+ publicDecrypt,
+ publicEncrypt,
+ privateDecrypt,
+ privateEncrypt,
+ generateKeyPairSync,
+ generateKeySync,
+ generateKeyPair,
+ sign,
+ verify,
+ generateKey,
+} from "crypto";
+import { test, it, expect, describe } from "bun:test";
+import { createContext, Script } from "node:vm";
+import fs from "fs";
+import path from "path";
+
+const publicPem = fs.readFileSync(path.join(import.meta.dir, "fixtures", "rsa_public.pem"), "ascii");
+const privatePem = fs.readFileSync(path.join(import.meta.dir, "fixtures", "rsa_private.pem"), "ascii");
+const privateEncryptedPem = fs.readFileSync(
+ path.join(import.meta.dir, "fixtures", "rsa_private_encrypted.pem"),
+ "ascii",
+);
+
+// Constructs a regular expression for a PEM-encoded key with the given label.
+function getRegExpForPEM(label: string, cipher?: string) {
+ const head = `\\-\\-\\-\\-\\-BEGIN ${label}\\-\\-\\-\\-\\-`;
+ const rfc1421Header = cipher == null ? "" : `\nProc-Type: 4,ENCRYPTED\nDEK-Info: ${cipher},[^\n]+\n`;
+ const body = "([a-zA-Z0-9\\+/=]{64}\n)*[a-zA-Z0-9\\+/=]{1,64}";
+ const end = `\\-\\-\\-\\-\\-END ${label}\\-\\-\\-\\-\\-`;
+ return new RegExp(`^${head}${rfc1421Header}\n${body}\n${end}\n$`);
+}
+const pkcs1PubExp = getRegExpForPEM("RSA PUBLIC KEY");
+const pkcs1PrivExp = getRegExpForPEM("RSA PRIVATE KEY");
+const pkcs1EncExp = (cipher: string) => getRegExpForPEM("RSA PRIVATE KEY", cipher);
+const spkiExp = getRegExpForPEM("PUBLIC KEY");
+const pkcs8Exp = getRegExpForPEM("PRIVATE KEY");
+const pkcs8EncExp = getRegExpForPEM("ENCRYPTED PRIVATE KEY");
+const sec1Exp = getRegExpForPEM("EC PRIVATE KEY");
+const sec1EncExp = (cipher: string) => getRegExpForPEM("EC PRIVATE KEY", cipher);
+
+// Asserts that the size of the given key (in chars or bytes) is within 10% of
+// the expected size.
+function assertApproximateSize(key: any, expectedSize: number) {
+ const min = Math.floor(0.9 * expectedSize);
+ const max = Math.ceil(1.1 * expectedSize);
+ expect(key.length).toBeGreaterThanOrEqual(min);
+ expect(key.length).toBeLessThanOrEqual(max);
+}
+// Tests that a key pair can be used for encryption / decryption.
+function testEncryptDecrypt(publicKey: any, privateKey: any) {
+ const message = "Hello Node.js world!";
+ const plaintext = Buffer.from(message, "utf8");
+ for (const key of [publicKey, privateKey]) {
+ const ciphertext = publicEncrypt(key, plaintext);
+ const received = privateDecrypt(privateKey, ciphertext);
+ expect(received.toString("utf8")).toEqual(message);
+ }
+}
+
+// Tests that a key pair can be used for signing / verification.
+function testSignVerify(publicKey: any, privateKey: any) {
+ const message = Buffer.from("Hello Node.js world!");
+
+ function oldSign(algo: string, data: string | Buffer, key: any) {
+ return createSign(algo).update(data).sign(key);
+ }
+
+ function oldVerify(algo: string, data: string | Buffer, key: any, signature: any) {
+ return createVerify(algo).update(data).verify(key, signature);
+ }
+
+ for (const signFn of [sign, oldSign]) {
+ const signature = signFn("SHA256", message, privateKey);
+ for (const verifyFn of [verify, oldVerify]) {
+ for (const key of [publicKey, privateKey]) {
+ const okay = verifyFn("SHA256", message, key, signature);
+ expect(okay).toBeTrue();
+ }
+ }
+ }
+}
+
+describe("crypto.KeyObjects", () => {
+ test("Attempting to create a key using other than CryptoKey should throw", async () => {
+ expect(() => new KeyObject("secret", "")).toThrow();
+ expect(() => new KeyObject("secret")).toThrow();
+ expect(() => KeyObject.from("invalid_key")).toThrow();
+ });
+ test("basics of createSecretKey should work", async () => {
+ const keybuf = randomBytes(32);
+ const key = createSecretKey(keybuf);
+ expect(key.type).toBe("secret");
+ expect(key.toString()).toBe("[object KeyObject]");
+ expect(key.symmetricKeySize).toBe(32);
+ expect(key.asymmetricKeyType).toBe(undefined);
+ expect(key.asymmetricKeyDetails).toBe(undefined);
+
+ const exportedKey = key.export();
+ expect(keybuf).toEqual(exportedKey);
+
+ const plaintext = Buffer.from("Hello world", "utf8");
+
+ const cipher = createCipheriv("aes-256-ecb", key, null);
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
+
+ const decipher = createDecipheriv("aes-256-ecb", key, null);
+ const deciphered = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
+
+ expect(plaintext).toEqual(deciphered);
+ });
+
+ test("Passing an existing public key object to createPublicKey should throw", async () => {
+ // Passing an existing public key object to createPublicKey should throw.
+ const publicKey = createPublicKey(publicPem);
+ expect(() => createPublicKey(publicKey)).toThrow();
+
+ // Constructing a private key from a public key should be impossible, even
+ // if the public key was derived from a private key.
+ expect(() => createPrivateKey(createPublicKey(privatePem))).toThrow();
+
+ // Similarly, passing an existing private key object to createPrivateKey
+ // should throw.
+ const privateKey = createPrivateKey(privatePem);
+ expect(() => createPrivateKey(privateKey)).toThrow();
+ });
+
+ test("basics should work", async () => {
+ const jwk = {
+ e: "AQAB",
+ n:
+ "t9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe" +
+ "1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ" +
+ "MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE" +
+ "u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF" +
+ "di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q",
+ d:
+ "ktnq2LvIMqBj4txP82IEOorIRQGVsw1khbm8A-cEpuEkgM71Yi_0WzupKktucUeevQ5i0" +
+ "Yh8w9e1SJiTLDRAlJz66kdky9uejiWWl6zR4dyNZVMFYRM43ijLC-P8rPne9Fz16IqHFW" +
+ "5VbJqA1xCBhKmuPMsD71RNxZ4Hrsa7Kt_xglQTYsLbdGIwDmcZihId9VGXRzvmCPsDRf2" +
+ "fCkAj7HDeRxpUdEiEDpajADc-PWikra3r3b40tVHKWm8wxJLivOIN7GiYXKQIW6RhZgH-" +
+ "Rk45JIRNKxNagxdeXUqqyhnwhbTo1Hite0iBDexN9tgoZk0XmdYWBn6ElXHRZ7VCDQ",
+ p:
+ "8UovlB4nrBm7xH-u7XXBMbqxADQm5vaEZxw9eluc-tP7cIAI4sglMIvL_FMpbd2pEeP_B" +
+ "kR76NTDzzDuPAZvUGRavgEjy0O9j2NAs_WPK4tZF-vFdunhnSh4EHAF4Ij9kbsUi90NOp" +
+ "bGfVqPdOaHqzgHKoR23Cuusk9wFQ2XTV8",
+ q:
+ "wxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrWOb-76rSfuL8wGR4OBNmQdhLuU9zTIh22pog-X" +
+ "PnLPAecC-4yu_wtJ2SPCKiKDbJBre0CKPyRfGqzvA3njXwMxXazU4kGs-2Fg-xu_iKbaI" +
+ "jxXrclBLhkxhBtySrwAFhxxOk6fFcPLSs",
+ dp:
+ "qS_Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc_Gh4cnO-b7BNJ_-5L8WZog0vr" +
+ "6PgiLhrqBaCYm2wjpyoG2o2wDHm-NAlzN_wp3G2EFhrSxdOux-S1c0kpRcyoiAO2n29rN" +
+ "Da-jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8",
+ dq:
+ "WAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCDdCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP_" +
+ "Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNmbDXFPdG0G3hzQovx_9fajiRV4DWghLHeT9wzJ" +
+ "fZabRRiI0VQR472300AVEeX4vgbrDBn600",
+ qi:
+ "k7czBCT9rHn_PNwCa17hlTy88C4vXkwbz83Oa-aX5L4e5gw5lhcR2ZuZHLb2r6oMt9rl" +
+ "D7EIDItSs-u21LOXWPTAlazdnpYUyw_CzogM_PN-qNwMRXn5uXFFhmlP2mVg2EdELTahX" +
+ "ch8kWqHaCSX53yvqCtRKu_j76V31TfQZGM",
+ kty: "RSA",
+ };
+ const publicJwk = { kty: jwk.kty, e: jwk.e, n: jwk.n };
+
+ const publicKey = createPublicKey(publicPem);
+ expect(publicKey.type).toBe("public");
+ expect(publicKey.toString()).toBe("[object KeyObject]");
+ expect(publicKey.asymmetricKeyType).toBe("rsa");
+ expect(publicKey.symmetricKeySize).toBe(undefined);
+
+ const privateKey = createPrivateKey(privatePem);
+ expect(privateKey.type).toBe("private");
+ expect(privateKey.toString()).toBe("[object KeyObject]");
+ expect(privateKey.asymmetricKeyType).toBe("rsa");
+ expect(privateKey.symmetricKeySize).toBe(undefined);
+
+ // It should be possible to derive a public key from a private key.
+ const derivedPublicKey = createPublicKey(privateKey);
+ expect(derivedPublicKey.type).toBe("public");
+ expect(derivedPublicKey.toString()).toBe("[object KeyObject]");
+ expect(derivedPublicKey.asymmetricKeyType).toBe("rsa");
+ expect(derivedPublicKey.symmetricKeySize).toBe(undefined);
+
+ const publicKeyFromJwk = createPublicKey({ key: publicJwk, format: "jwk" });
+ expect(publicKey.type).toBe("public");
+ expect(publicKey.toString()).toBe("[object KeyObject]");
+ expect(publicKey.asymmetricKeyType).toBe("rsa");
+ expect(publicKey.symmetricKeySize).toBe(undefined);
+
+ const privateKeyFromJwk = createPrivateKey({ key: jwk, format: "jwk" });
+ expect(privateKey.type).toBe("private");
+ expect(privateKey.toString()).toBe("[object KeyObject]");
+ expect(privateKey.asymmetricKeyType).toBe("rsa");
+ expect(privateKey.symmetricKeySize).toBe(undefined);
+
+ // It should also be possible to import an encrypted private key as a public
+ // key.
+ const decryptedKey = createPublicKey({
+ key: privateKey.export({
+ type: "pkcs8",
+ format: "pem",
+ passphrase: Buffer.from("123"),
+ cipher: "aes-128-cbc",
+ }),
+ format: "pem",
+ passphrase: "123", // this is not documented, but it works
+ });
+ expect(decryptedKey.type).toBe("public");
+ expect(decryptedKey.asymmetricKeyType).toBe("rsa");
+
+ // Exporting the key using JWK should not work since this format does not
+ // support key encryption
+ expect(() => {
+ privateKey.export({ format: "jwk", passphrase: "secret" });
+ }).toThrow();
+
+ // Test exporting with an invalid options object, this should throw.
+ for (const opt of [undefined, null, "foo", 0, NaN]) {
+ expect(() => publicKey.export(opt)).toThrow();
+ }
+
+ for (const keyObject of [publicKey, derivedPublicKey, publicKeyFromJwk]) {
+ const exported = keyObject.export({ format: "jwk" });
+ expect(exported).toBeDefined();
+ const { kty, n, e } = exported as { kty: string; n: string; e: string };
+ expect({ kty, n, e }).toEqual({ kty: "RSA", n: jwk.n, e: jwk.e });
+ }
+
+ for (const keyObject of [privateKey, privateKeyFromJwk]) {
+ const exported = keyObject.export({ format: "jwk" });
+ expect(exported).toEqual(jwk);
+ }
+
+ const publicDER = publicKey.export({
+ format: "der",
+ type: "pkcs1",
+ });
+
+ const privateDER = privateKey.export({
+ format: "der",
+ type: "pkcs1",
+ });
+
+ expect(Buffer.isBuffer(publicDER)).toBe(true);
+ expect(Buffer.isBuffer(privateDER)).toBe(true);
+ const plaintext = Buffer.from("Hello world", "utf8");
+
+ const testDecryption = (fn, ciphertexts, decryptionKeys) => {
+ for (const ciphertext of ciphertexts) {
+ for (const key of decryptionKeys) {
+ const deciphered = fn(key, ciphertext);
+ expect(deciphered).toEqual(plaintext);
+ }
+ }
+ };
+
+ testDecryption(
+ privateDecrypt,
+ [
+ // Encrypt using the public key.
+ publicEncrypt(publicKey, plaintext),
+ publicEncrypt({ key: publicKey }, plaintext),
+ publicEncrypt({ key: publicJwk, format: "jwk" }, plaintext),
+
+ // Encrypt using the private key.
+ publicEncrypt(privateKey, plaintext),
+ publicEncrypt({ key: privateKey }, plaintext),
+ publicEncrypt({ key: jwk, format: "jwk" }, plaintext),
+
+ // Encrypt using a public key derived from the private key.
+ publicEncrypt(derivedPublicKey, plaintext),
+ publicEncrypt({ key: derivedPublicKey }, plaintext),
+
+ // Test distinguishing PKCS#1 public and private keys based on the
+ // DER-encoded data only.
+ publicEncrypt({ format: "der", type: "pkcs1", key: publicDER }, plaintext),
+ publicEncrypt({ format: "der", type: "pkcs1", key: privateDER }, plaintext),
+ ],
+ [
+ privateKey,
+ { format: "pem", key: privatePem },
+ { format: "der", type: "pkcs1", key: privateDER },
+ { key: jwk, format: "jwk" },
+ ],
+ );
+
+ testDecryption(
+ publicDecrypt,
+ [privateEncrypt(privateKey, plaintext)],
+ [
+ // Decrypt using the public key.
+ publicKey,
+ { format: "pem", key: publicPem },
+ { format: "der", type: "pkcs1", key: publicDER },
+ { key: publicJwk, format: "jwk" },
+
+ // Decrypt using the private key.
+ privateKey,
+ { format: "pem", key: privatePem },
+ { format: "der", type: "pkcs1", key: privateDER },
+ { key: jwk, format: "jwk" },
+ ],
+ );
+ });
+
+ test("This should not cause a crash: https://github.com/nodejs/node/issues/25247", async () => {
+ expect(() => createPrivateKey({ key: "" })).toThrow();
+ });
+ test("This should not abort either: https://github.com/nodejs/node/issues/29904", async () => {
+ expect(() => createPrivateKey({ key: Buffer.alloc(0), format: "der", type: "spki" })).toThrow();
+ });
+
+ test("BoringSSL will not parse PKCS#1", async () => {
+ // Unlike SPKI, PKCS#1 is a valid encoding for private keys (and public keys),
+ // so it should be accepted by createPrivateKey, but OpenSSL won't parse it.
+ expect(() => {
+ const key = createPublicKey(publicPem).export({
+ format: "der",
+ type: "pkcs1",
+ });
+ createPrivateKey({ key, format: "der", type: "pkcs1" });
+ }).toThrow("Invalid use of PKCS#1 as private key");
+ });
+
+ [
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ed25519_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ed25519_public.pem"), "ascii"),
+ keyType: "ed25519",
+ jwk: {
+ crv: "Ed25519",
+ x: "K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768",
+ d: "wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA",
+ kty: "OKP",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ed448_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ed448_public.pem"), "ascii"),
+ keyType: "ed448",
+ jwk: {
+ crv: "Ed448",
+ x: "oX_ee5-jlcU53-BbGRsGIzly0V-SZtJ_oGXY0udf84q2hTW2RdstLktvwpkVJOoNb7o" + "Dgc2V5ZUA",
+ d: "060Ke71sN0GpIc01nnGgMDkp0sFNQ09woVo4AM1ffax1-mjnakK0-p-S7-Xf859QewX" + "jcR9mxppY",
+ kty: "OKP",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "x25519_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "x25519_public.pem"), "ascii"),
+ keyType: "x25519",
+ jwk: {
+ crv: "X25519",
+ x: "aSb8Q-RndwfNnPeOYGYPDUN3uhAPnMLzXyfi-mqfhig",
+ d: "mL_IWm55RrALUGRfJYzw40gEYWMvtRkesP9mj8o8Omc",
+ kty: "OKP",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "x448_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "x448_public.pem"), "ascii"),
+ keyType: "x448",
+ jwk: {
+ crv: "X448",
+ x: "ioHSHVpTs6hMvghosEJDIR7ceFiE3-Xccxati64oOVJ7NWjfozE7ae31PXIUFq6cVYg" + "vSKsDFPA",
+ d: "tMNtrO_q8dlY6Y4NDeSTxNQ5CACkHiPvmukidPnNIuX_EkcryLEXt_7i6j6YZMKsrWy" + "S0jlSYJk",
+ kty: "OKP",
+ },
+ },
+ ].forEach(info => {
+ const keyType = info.keyType;
+ // X25519 implementation is incomplete, Ed448 and X448 are not supported yet
+ const test = keyType === "ed25519" ? it : it.skip;
+ let privateKey: KeyObject;
+ test(`${keyType} from Buffer should work`, async () => {
+ const key = createPrivateKey(info.private);
+ privateKey = key;
+ expect(key.type).toBe("private");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.symmetricKeySize).toBe(undefined);
+ expect(key.export({ type: "pkcs8", format: "pem" })).toEqual(info.private);
+ const jwt = key.export({ format: "jwk" });
+ expect(jwt).toEqual(info.jwk);
+ });
+
+ test(`${keyType} createPrivateKey from jwk should work`, async () => {
+ const key = createPrivateKey({ key: info.jwk, format: "jwk" });
+ expect(key.type).toBe("private");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.symmetricKeySize).toBe(undefined);
+ expect(key.export({ type: "pkcs8", format: "pem" })).toEqual(info.private);
+ const jwt = key.export({ format: "jwk" });
+ expect(jwt).toEqual(info.jwk);
+ });
+
+ [
+ ["public", info.public],
+ ["private", info.private],
+ ["jwk", { key: info.jwk, format: "jwk" }],
+ ].forEach(([name, input]) => {
+ test(`${keyType} createPublicKey using ${name} key should work`, async () => {
+ const key = createPublicKey(input);
+ expect(key.type).toBe("public");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.symmetricKeySize).toBe(undefined);
+ if (name == "public") {
+ expect(key.export({ type: "spki", format: "pem" })).toEqual(info.public);
+ }
+ if (name == "jwk") {
+ const jwt = { ...info.jwk };
+ delete jwt.d;
+ const jwk_exported = key.export({ format: "jwk" });
+ expect(jwk_exported).toEqual(jwt);
+ }
+ });
+ });
+ });
+
+ [
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p256_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p256_public.pem"), "ascii"),
+ keyType: "ec",
+ namedCurve: "prime256v1",
+ jwk: {
+ crv: "P-256",
+ d: "DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo",
+ kty: "EC",
+ x: "X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs",
+ y: "UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_secp256k1_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_secp256k1_public.pem"), "ascii"),
+ keyType: "ec",
+ namedCurve: "secp256k1",
+ jwk: {
+ crv: "secp256k1",
+ d: "c34ocwTwpFa9NZZh3l88qXyrkoYSxvC0FEsU5v1v4IM",
+ kty: "EC",
+ x: "cOzhFSpWxhalCbWNdP2H_yUkdC81C9T2deDpfxK7owA",
+ y: "-A3DAZTk9IPppN-f03JydgHaFvL1fAHaoXf4SX4NXyo",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p384_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p384_public.pem"), "ascii"),
+ keyType: "ec",
+ namedCurve: "secp384r1",
+ jwk: {
+ crv: "P-384",
+ d: "dwfuHuAtTlMRn7ZBCBm_0grpc1D_4hPeNAgevgelljuC0--k_LDFosDgBlLLmZsi",
+ kty: "EC",
+ x: "hON3nzGJgv-08fdHpQxgRJFZzlK-GZDGa5f3KnvM31cvvjJmsj4UeOgIdy3rDAjV",
+ y: "fidHhtecNCGCfLqmrLjDena1NSzWzWH1u_oUdMKGo5XSabxzD7-8JZxjpc8sR9cl",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p521_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p521_public.pem"), "ascii"),
+ keyType: "ec",
+ namedCurve: "secp521r1",
+ jwk: {
+ crv: "P-521",
+ d: "Eghuafcab9jXW4gOQLeDaKOlHEiskQFjiL8klijk6i6DNOXcFfaJ9GW48kxpodw16ttAf9Z1WQstfzpKGUetHIk",
+ kty: "EC",
+ x: "AaLFgjwZtznM3N7qsfb86awVXe6c6djUYOob1FN-kllekv0KEXV0bwcDjPGQz5f6MxL" + "CbhMeHRavUS6P10rsTtBn",
+ y: "Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkOSEASxPB" + "cvA2iFJRUyQ3whC00j0Np",
+ },
+ },
+ ].forEach(info => {
+ const { keyType, namedCurve } = info;
+ const test = namedCurve === "secp256k1" ? it.skip : it;
+ let privateKey: KeyObject;
+ test(`${keyType} ${namedCurve} createPrivateKey from Buffer should work`, async () => {
+ const key = createPrivateKey(info.private);
+ privateKey = key;
+ expect(key.type).toBe("private");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.asymmetricKeyDetails?.namedCurve).toBe(namedCurve);
+ expect(key.symmetricKeySize).toBe(undefined);
+ expect(key.export({ type: "pkcs8", format: "pem" })).toEqual(info.private);
+ const jwt = key.export({ format: "jwk" });
+ expect(jwt).toEqual(info.jwk);
+ });
+
+ test(`${keyType} ${namedCurve} createPrivateKey from jwk should work`, async () => {
+ const key = createPrivateKey({ key: info.jwk, format: "jwk" });
+ expect(key.type).toBe("private");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.asymmetricKeyDetails?.namedCurve).toBe(namedCurve);
+ expect(key.symmetricKeySize).toBe(undefined);
+ expect(key.export({ type: "pkcs8", format: "pem" })).toEqual(info.private);
+ const jwt = key.export({ format: "jwk" });
+ expect(jwt).toEqual(info.jwk);
+ });
+
+ [
+ ["public", info.public],
+ ["private", info.private],
+ ["jwk", { key: info.jwk, format: "jwk" }],
+ ].forEach(([name, input]) => {
+ test(`${keyType} ${namedCurve} createPublicKey using ${name} should work`, async () => {
+ const key = createPublicKey(input);
+ expect(key.type).toBe("public");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.asymmetricKeyDetails?.namedCurve).toBe(namedCurve);
+ expect(key.symmetricKeySize).toBe(undefined);
+ if (name == "public") {
+ expect(key.export({ type: "spki", format: "pem" })).toEqual(info.public);
+ }
+ if (name == "jwk") {
+ const jwt = { ...info.jwk };
+ delete jwt.d;
+ const jwk_exported = key.export({ format: "jwk" });
+ expect(jwk_exported).toEqual(jwt);
+ }
+
+ const pkey = privateKey || info.private;
+ const signature = createSign("sha256").update("foo").sign({ key: pkey });
+ const okay = createVerify("sha256").update("foo").verify({ key: key }, signature);
+ expect(okay).toBeTrue();
+ });
+ });
+ });
+
+ test("private encrypted should work", async () => {
+ // Reading an encrypted key without a passphrase should fail.
+ expect(() => createPrivateKey(privateEncryptedPem)).toThrow();
+ // Reading an encrypted key with a passphrase that exceeds OpenSSL's buffer
+ // size limit should fail with an appropriate error code.
+ expect(() =>
+ createPrivateKey({
+ key: privateEncryptedPem,
+ format: "pem",
+ passphrase: Buffer.alloc(1025, "a"),
+ }),
+ ).toThrow();
+ // The buffer has a size of 1024 bytes, so this passphrase should be permitted
+ // (but will fail decryption).
+ expect(() =>
+ createPrivateKey({
+ key: privateEncryptedPem,
+ format: "pem",
+ passphrase: Buffer.alloc(1024, "a"),
+ }),
+ ).toThrow();
+ const publicKey = createPublicKey({
+ key: privateEncryptedPem,
+ format: "pem",
+ passphrase: "password", // this is not documented but should work
+ });
+ expect(publicKey.type).toBe("public");
+ expect(publicKey.asymmetricKeyType).toBe("rsa");
+ expect(publicKey.symmetricKeySize).toBe(undefined);
+
+ const privateKey = createPrivateKey({
+ key: privateEncryptedPem,
+ format: "pem",
+ passphrase: "password",
+ });
+ expect(privateKey.type).toBe("private");
+ expect(privateKey.asymmetricKeyType).toBe("rsa");
+ expect(privateKey.symmetricKeySize).toBe(undefined);
+ });
+
+ [2048, 4096].forEach(suffix => {
+ test(`RSA-${suffix} should work`, async () => {
+ {
+ const publicPem = fs.readFileSync(path.join(import.meta.dir, "fixtures", `rsa_public_${suffix}.pem`), "ascii");
+ const privatePem = fs.readFileSync(
+ path.join(import.meta.dir, "fixtures", `rsa_private_${suffix}.pem`),
+ "ascii",
+ );
+ const publicKey = createPublicKey(publicPem);
+ const expectedKeyDetails = {
+ modulusLength: suffix,
+ publicExponent: 65537n,
+ };
+ expect(publicKey.type).toBe("public");
+ expect(publicKey.asymmetricKeyType).toBe("rsa");
+ expect(publicKey.asymmetricKeyDetails).toEqual(expectedKeyDetails);
+
+ const privateKey = createPrivateKey(privatePem);
+ expect(privateKey.type).toBe("private");
+ expect(privateKey.asymmetricKeyType).toBe("rsa");
+ expect(privateKey.asymmetricKeyDetails).toEqual(expectedKeyDetails);
+
+ for (const key of [privatePem, privateKey]) {
+ // Any algorithm should work.
+ for (const algo of ["sha1", "sha256"]) {
+ // Any salt length should work.
+ for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) {
+ const signature = createSign(algo).update("foo").sign({ key, saltLength });
+ for (const pkey of [key, publicKey, publicPem]) {
+ const okay = createVerify(algo).update("foo").verify({ key: pkey, saltLength }, signature);
+ expect(okay).toBeTrue();
+ }
+ }
+ }
+ }
+ }
+ });
+ });
+
+ test("Exporting an encrypted private key requires a cipher", async () => {
+ // Exporting an encrypted private key requires a cipher
+ const privateKey = createPrivateKey(privatePem);
+ expect(() => {
+ privateKey.export({
+ format: "pem",
+ type: "pkcs8",
+ passphrase: "super-secret",
+ });
+ }).toThrow(/cipher is required when passphrase is specified/);
+ });
+
+ test("secret export buffer format (default)", async () => {
+ const buffer = Buffer.from("Hello World");
+ const keyObject = createSecretKey(buffer);
+ expect(keyObject.export()).toEqual(buffer);
+ expect(keyObject.export({})).toEqual(buffer);
+ expect(keyObject.export({ format: "buffer" })).toEqual(buffer);
+ expect(keyObject.export({ format: undefined })).toEqual(buffer);
+ });
+
+ test('exporting an "oct" JWK from a secret', async () => {
+ const buffer = Buffer.from("Hello World");
+ const keyObject = createSecretKey(buffer);
+ const jwk = keyObject.export({ format: "jwk" });
+ expect(jwk).toEqual({ kty: "oct", k: "SGVsbG8gV29ybGQ" });
+ });
+
+ test("secret equals", async () => {
+ {
+ const first = Buffer.from("Hello");
+ const second = Buffer.from("World");
+ const keyObject = createSecretKey(first);
+ expect(createSecretKey(first).equals(createSecretKey(first))).toBeTrue();
+ expect(createSecretKey(first).equals(createSecretKey(second))).toBeFalse();
+
+ expect(() => keyObject.equals(0)).toThrow(/otherKey must be a KeyObject/);
+
+ expect(keyObject.equals(keyObject)).toBeTrue();
+ expect(keyObject.equals(createPublicKey(publicPem))).toBeFalse();
+ expect(keyObject.equals(createPrivateKey(privatePem))).toBeFalse();
+ }
+
+ {
+ const first = createSecretKey(Buffer.alloc(0));
+ const second = createSecretKey(new ArrayBuffer(0));
+ const third = createSecretKey(Buffer.alloc(1));
+ expect(first.equals(first)).toBeTrue();
+ expect(first.equals(second)).toBeTrue();
+ expect(first.equals(third)).toBeFalse();
+ expect(third.equals(first)).toBeFalse();
+ }
+ });
+
+ ["ed25519", "x25519"].forEach(keyType => {
+ const test = keyType === "ed25519" ? it : it.skip;
+ test(`${keyType} equals should work`, async () => {
+ const first = generateKeyPairSync(keyType);
+ const second = generateKeyPairSync(keyType);
+
+ const secret = generateKeySync("aes", { length: 128 });
+
+ expect(first.publicKey.equals(first.publicKey)).toBeTrue();
+
+ expect(first.publicKey.equals(createPublicKey(first.publicKey.export({ format: "pem", type: "spki" }))));
+
+ expect(first.publicKey.equals(second.publicKey)).toBeFalse();
+ expect(first.publicKey.equals(second.privateKey)).toBeFalse();
+ expect(first.publicKey.equals(secret)).toBeFalse();
+
+ expect(first.privateKey.equals(first.privateKey)).toBeTrue();
+ expect(
+ first.privateKey.equals(createPrivateKey(first.privateKey.export({ format: "pem", type: "pkcs8" }))),
+ ).toBeTrue();
+ expect(first.privateKey.equals(second.privateKey)).toBeFalse();
+ expect(first.privateKey.equals(second.publicKey)).toBeFalse();
+ expect(first.privateKey.equals(secret)).toBeFalse();
+ });
+ });
+
+ test("This should not cause a crash: https://github.com/nodejs/node/issues/44471", async () => {
+ for (const key of ["", "foo", null, undefined, true, Boolean]) {
+ expect(() => {
+ createPublicKey({ key, format: "jwk" });
+ }).toThrow();
+ expect(() => {
+ createPrivateKey({ key, format: "jwk" });
+ }).toThrow();
+ }
+ });
+
+ ["hmac", "aes"].forEach(type => {
+ [128, 256].forEach(length => {
+ test(`generateKey ${type} ${length}`, async () => {
+ {
+ const key = generateKeySync(type, { length });
+ expect(key).toBeDefined();
+ const keybuf = key.export();
+ expect(keybuf.byteLength).toBe(length / 8);
+ }
+
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKey(type, { length }, (err, key) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(key);
+ }
+ });
+
+ {
+ const key = await promise;
+ expect(key).toBeDefined();
+ const keybuf = key.export();
+ expect(keybuf.byteLength).toBe(length / 8);
+ }
+ });
+ });
+ });
+ describe("Test async elliptic curve key generation with 'jwk' encoding and named curve", () => {
+ ["P-384", "P-256", "P-521", "secp256k1"].forEach(curve => {
+ const test = curve === "secp256k1" ? it.skip : it;
+ test(`should work with ${curve}`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "ec",
+ {
+ namedCurve: curve,
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: any; privateKey: any }>);
+ expect(typeof publicKey).toBe("object");
+ expect(typeof privateKey).toBe("object");
+ expect(publicKey.x).toBe(privateKey.x);
+ expect(publicKey.y).toBe(publicKey.y);
+ expect(publicKey.d).toBeUndefined();
+ expect(privateKey.d).toBeDefined();
+ expect(publicKey.kty).toEqual("EC");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ expect(publicKey.crv).toEqual(curve);
+ expect(publicKey.crv).toEqual(privateKey.crv);
+ });
+ });
+ });
+
+ describe("Test async elliptic curve key generation with 'jwk' encoding and RSA.", () => {
+ [256, 1024, 2048].forEach(modulusLength => {
+ test(`should work with ${modulusLength}`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "rsa",
+ {
+ modulusLength,
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: any; privateKey: any }>);
+ expect(typeof publicKey).toEqual("object");
+ expect(typeof privateKey).toEqual("object");
+ expect(publicKey.kty).toEqual("RSA");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ expect(typeof publicKey.n).toEqual("string");
+ expect(publicKey.n).toEqual(privateKey.n);
+ expect(typeof publicKey.e).toEqual("string");
+ expect(publicKey.e).toEqual(privateKey.e);
+ expect(typeof privateKey.d).toEqual("string");
+ expect(typeof privateKey.p).toEqual("string");
+ expect(typeof privateKey.q).toEqual("string");
+ expect(typeof privateKey.dp).toEqual("string");
+ expect(typeof privateKey.dq).toEqual("string");
+ expect(typeof privateKey.qi).toEqual("string");
+ });
+ });
+ });
+
+ describe("Test async elliptic curve key generation with 'jwk' encoding", () => {
+ ["ed25519", "ed448", "x25519", "x448"].forEach(type => {
+ const test = type === "ed25519" ? it : it.skip;
+ test(`should work with ${type}`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ type,
+ {
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: any; privateKey: any }>);
+ expect(typeof publicKey).toEqual("object");
+ expect(typeof privateKey).toEqual("object");
+ expect(publicKey.x).toEqual(privateKey.x);
+ expect(publicKey.d).toBeUndefined();
+ expect(privateKey.d).toBeDefined();
+ expect(publicKey.kty).toEqual("OKP");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ const expectedCrv = `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
+ expect(publicKey.crv).toEqual(expectedCrv);
+ expect(publicKey.crv).toEqual(privateKey.crv);
+ });
+ });
+ });
+
+ test(`Test async RSA key generation with an encrypted private key, but encoded as DER`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "rsa",
+ {
+ publicExponent: 0x10001,
+ modulusLength: 512,
+ publicKeyEncoding: {
+ type: "pkcs1",
+ format: "der",
+ },
+ privateKeyEncoding: {
+ type: "pkcs1",
+ format: "pem",
+ cipher: "aes-256-cbc",
+ passphrase: "secret",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey: publicKeyDER, privateKey } = await (promise as Promise<{
+ publicKey: Buffer;
+ privateKey: string;
+ }>);
+ expect(Buffer.isBuffer(publicKeyDER)).toBeTrue();
+ assertApproximateSize(publicKeyDER, 74);
+
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs1EncExp("AES-256-CBC"));
+
+ const publicKey = {
+ key: publicKeyDER,
+ type: "pkcs1",
+ format: "der",
+ };
+ expect(() => {
+ testEncryptDecrypt(publicKey, privateKey);
+ }).toThrow();
+
+ const key = { key: privateKey, passphrase: "secret" };
+ testEncryptDecrypt(publicKey, key);
+ testSignVerify(publicKey, key);
+ });
+
+ test(`Test async RSA key generation with an encrypted private key`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "rsa",
+ {
+ publicExponent: 0x10001,
+ modulusLength: 512,
+ publicKeyEncoding: {
+ type: "pkcs1",
+ format: "der",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "der",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey: publicKeyDER, privateKey: privateKeyDER } = await (promise as Promise<{
+ publicKey: Buffer;
+ privateKey: Buffer;
+ }>);
+ expect(Buffer.isBuffer(publicKeyDER)).toBeTrue();
+ assertApproximateSize(publicKeyDER, 74);
+
+ expect(Buffer.isBuffer(privateKeyDER)).toBeTrue();
+
+ const publicKey = {
+ key: publicKeyDER,
+ type: "pkcs1",
+ format: "der",
+ };
+ const privateKey = {
+ key: privateKeyDER,
+ format: "der",
+ type: "pkcs8",
+ passphrase: "secret",
+ };
+ testEncryptDecrypt(publicKey, privateKey);
+ testSignVerify(publicKey, privateKey);
+ });
+
+ test(`Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted private key`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "ec",
+ {
+ namedCurve: "P-256",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "top secret",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: string; privateKey: string }>);
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs8EncExp);
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "top secret",
+ });
+ });
+
+ test(`Test async explicit elliptic curve key generation with an encrypted private key`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "ec",
+ {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "sec1",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "secret",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: string; privateKey: string }>);
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(sec1EncExp("AES-128-CBC"));
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "secret",
+ });
+ });
+
+ test(`Test async explicit elliptic curve key generation, e.g. for ECDSA, with a SEC1 private key`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "ec",
+ {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "sec1",
+ format: "pem",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: string; privateKey: string }>);
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(sec1Exp);
+ testSignVerify(publicKey, privateKey);
+ });
+
+ test(`Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted private key`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "ec",
+ {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "top secret",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: string; privateKey: string }>);
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs8EncExp);
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "top secret",
+ });
+ });
+
+ describe("Test sync elliptic curve key generation with 'jwk' encoding and named curve", () => {
+ ["P-384", "P-256", "P-521", "secp256k1"].forEach(curve => {
+ const test = curve === "secp256k1" ? it.skip : it;
+ test(`should work with ${curve}`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync("ec", {
+ namedCurve: curve,
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ });
+ expect(typeof publicKey).toBe("object");
+ expect(typeof privateKey).toBe("object");
+ expect(publicKey.x).toBe(privateKey.x);
+ expect(publicKey.y).toBe(publicKey.y);
+ expect(publicKey.d).toBeUndefined();
+ expect(privateKey.d).toBeDefined();
+ expect(publicKey.kty).toEqual("EC");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ expect(publicKey.crv).toEqual(curve);
+ expect(publicKey.crv).toEqual(privateKey.crv);
+ });
+ });
+ });
+
+ describe("Test sync elliptic curve key generation with 'jwk' encoding and RSA.", () => {
+ [256, 1024, 2048].forEach(modulusLength => {
+ test(`should work with ${modulusLength}`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync("rsa", {
+ modulusLength,
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ });
+ expect(typeof publicKey).toEqual("object");
+ expect(typeof privateKey).toEqual("object");
+ expect(publicKey.kty).toEqual("RSA");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ expect(typeof publicKey.n).toEqual("string");
+ expect(publicKey.n).toEqual(privateKey.n);
+ expect(typeof publicKey.e).toEqual("string");
+ expect(publicKey.e).toEqual(privateKey.e);
+ expect(typeof privateKey.d).toEqual("string");
+ expect(typeof privateKey.p).toEqual("string");
+ expect(typeof privateKey.q).toEqual("string");
+ expect(typeof privateKey.dp).toEqual("string");
+ expect(typeof privateKey.dq).toEqual("string");
+ expect(typeof privateKey.qi).toEqual("string");
+ });
+ });
+ });
+
+ describe("Test sync elliptic curve key generation with 'jwk' encoding", () => {
+ ["ed25519", "ed448", "x25519", "x448"].forEach(type => {
+ const test = type === "ed25519" ? it : it.skip;
+ test(`should work with ${type}`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync(type, {
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ });
+
+ expect(typeof publicKey).toEqual("object");
+ expect(typeof privateKey).toEqual("object");
+ expect(publicKey.x).toEqual(privateKey.x);
+ expect(publicKey.d).toBeUndefined();
+ expect(privateKey.d).toBeDefined();
+ expect(publicKey.kty).toEqual("OKP");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ const expectedCrv = `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
+ expect(publicKey.crv).toEqual(expectedCrv);
+ expect(publicKey.crv).toEqual(privateKey.crv);
+ });
+ });
+ });
+
+ test(`Test sync RSA key generation with an encrypted private key, but encoded as DER`, async () => {
+ const { publicKey: publicKeyDER, privateKey } = generateKeyPairSync("rsa", {
+ publicExponent: 0x10001,
+ modulusLength: 512,
+ publicKeyEncoding: {
+ type: "pkcs1",
+ format: "der",
+ },
+ privateKeyEncoding: {
+ type: "pkcs1",
+ format: "pem",
+ cipher: "aes-256-cbc",
+ passphrase: "secret",
+ },
+ });
+
+ expect(Buffer.isBuffer(publicKeyDER)).toBeTrue();
+ assertApproximateSize(publicKeyDER, 74);
+
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs1EncExp("AES-256-CBC"));
+
+ const publicKey = {
+ key: publicKeyDER,
+ type: "pkcs1",
+ format: "der",
+ };
+ expect(() => {
+ testEncryptDecrypt(publicKey, privateKey);
+ }).toThrow();
+
+ const key = { key: privateKey, passphrase: "secret" };
+ testEncryptDecrypt(publicKey, key);
+ testSignVerify(publicKey, key);
+ });
+
+ test(`Test sync RSA key generation with an encrypted private key`, async () => {
+ const { publicKey: publicKeyDER, privateKey: privateKeyDER } = generateKeyPairSync("rsa", {
+ publicExponent: 0x10001,
+ modulusLength: 512,
+ publicKeyEncoding: {
+ type: "pkcs1",
+ format: "der",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "der",
+ },
+ });
+
+ expect(Buffer.isBuffer(publicKeyDER)).toBeTrue();
+ assertApproximateSize(publicKeyDER, 74);
+
+ expect(Buffer.isBuffer(privateKeyDER)).toBeTrue();
+
+ const publicKey = {
+ key: publicKeyDER,
+ type: "pkcs1",
+ format: "der",
+ };
+ const privateKey = {
+ key: privateKeyDER,
+ format: "der",
+ type: "pkcs8",
+ passphrase: "secret",
+ };
+ testEncryptDecrypt(publicKey, privateKey);
+ testSignVerify(publicKey, privateKey);
+ });
+
+ test(`Test sync elliptic curve key generation, e.g. for ECDSA, with an encrypted private key`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync("ec", {
+ namedCurve: "P-256",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "top secret",
+ },
+ });
+
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs8EncExp);
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "top secret",
+ });
+ });
+
+ test(`Test sync explicit elliptic curve key generation with an encrypted private key`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync(
+ "ec",
+ {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "sec1",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "secret",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(sec1EncExp("AES-128-CBC"));
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "secret",
+ });
+ });
+
+ test(`Test sync explicit elliptic curve key generation, e.g. for ECDSA, with a SEC1 private key`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync("ec", {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "sec1",
+ format: "pem",
+ },
+ });
+
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(sec1Exp);
+ testSignVerify(publicKey, privateKey);
+ });
+
+ test(`Test sync elliptic curve key generation, e.g. for ECDSA, with an encrypted private key`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync("ec", {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "top secret",
+ },
+ });
+
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs8EncExp);
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "top secret",
+ });
+ });
+ // SKIPED because we round the key size to the nearest multiple of 8 like documented
+ test.skip(`this tests check that generateKeyPair returns correct bit length in KeyObject's asymmetricKeyDetails.`, async () => {
+ // This tests check that generateKeyPair returns correct bit length in
+ // https://github.com/nodejs/node/issues/46102#issuecomment-1372153541
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "rsa",
+ {
+ modulusLength: 513,
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: KeyObject; privateKey: KeyObject }>);
+ expect(publicKey.asymmetricKeyDetails?.modulusLength).toBe(513);
+ expect(privateKey.asymmetricKeyDetails?.modulusLength).toBe(513);
+ });
+
+ function testRunInContext(fn: any) {
+ test("can generate key", () => {
+ const context = createContext({ generateKeySync });
+ const result = fn(`generateKeySync("aes", { length: 128 })`, context);
+ expect(result).toBeDefined();
+ const keybuf = result.export();
+ expect(keybuf.byteLength).toBe(128 / 8);
+ });
+ test("can be used on another context", () => {
+ const context = createContext({ generateKeyPairSync, assertApproximateSize, testEncryptDecrypt, testSignVerify });
+ const result = fn(
+ `
+ const { publicKey: publicKeyDER, privateKey: privateKeyDER } = generateKeyPairSync(
+ "rsa",
+ {
+ publicExponent: 0x10001,
+ modulusLength: 512,
+ publicKeyEncoding: {
+ type: "pkcs1",
+ format: "der",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "der",
+ },
+ }
+ );
+
+
+ assertApproximateSize(publicKeyDER, 74);
+
+ const publicKey = {
+ key: publicKeyDER,
+ type: "pkcs1",
+ format: "der",
+ };
+ const privateKey = {
+ key: privateKeyDER,
+ format: "der",
+ type: "pkcs8",
+ passphrase: "secret",
+ };
+ testEncryptDecrypt(publicKey, privateKey);
+ testSignVerify(publicKey, privateKey);
+ `,
+ context,
+ );
+ });
+ }
+ describe("Script", () => {
+ describe("runInContext()", () => {
+ testRunInContext((code, context, options) => {
+ // @ts-expect-error
+ const script = new Script(code, options);
+ return script.runInContext(context);
+ });
+ });
+ describe("runInNewContext()", () => {
+ testRunInContext((code, context, options) => {
+ // @ts-expect-error
+ const script = new Script(code, options);
+ return script.runInNewContext(context);
+ });
+ });
+ describe("runInThisContext()", () => {
+ testRunInContext((code, context, options) => {
+ // @ts-expect-error
+ const script = new Script(code, options);
+ return script.runInThisContext(context);
+ });
+ });
+ });
+});
+
+test.todo("RSA-PSS should work", async () => {
+ // Test RSA-PSS.
+ {
+ // This key pair does not restrict the message digest algorithm or salt
+ // length.
+ // const publicPem = fs.readFileSync(path.join(import.meta.dir, "fixtures", "rsa_pss_public_2048.pem"), "ascii");
+ // const privatePem = fs.readFileSync(path.join(import.meta.dir, "fixtures", "rsa_pss_private_2048.pem"), "ascii");
+ // const publicKey = createPublicKey(publicPem);
+ // const privateKey = createPrivateKey(privatePem);
+ // // Because no RSASSA-PSS-params appears in the PEM, no defaults should be
+ // // added for the PSS parameters. This is different from an empty
+ // // RSASSA-PSS-params sequence (see test below).
+ // const expectedKeyDetails = {
+ // modulusLength: 2048,
+ // publicExponent: 65537n,
+ // };
+ // expect(publicKey.type).toBe("public");
+ // expect(publicKey.asymmetricKeyType).toBe("rsa-pss");
+ // expect(publicKey.asymmetricKeyDetails).toBe(expectedKeyDetails);
+ // expect(privateKey.type).toBe("private");
+ // expect(privateKey.asymmetricKeyType).toBe("rsa-pss");
+ // expect(privateKey.asymmetricKeyDetails).toBe(expectedKeyDetails);
+ // assert.throws(
+ // () => publicKey.export({ format: 'jwk' }),
+ // { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
+ // assert.throws(
+ // () => privateKey.export({ format: 'jwk' }),
+ // { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
+ // for (const key of [privatePem, privateKey]) {
+ // // Any algorithm should work.
+ // for (const algo of ['sha1', 'sha256']) {
+ // // Any salt length should work.
+ // for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) {
+ // const signature = createSign(algo)
+ // .update('foo')
+ // .sign({ key, saltLength });
+ // for (const pkey of [key, publicKey, publicPem]) {
+ // const okay = createVerify(algo)
+ // .update('foo')
+ // .verify({ key: pkey, saltLength }, signature);
+ // assert.ok(okay);
+ // }
+ // }
+ // }
+ // }
+ // // Exporting the key using PKCS#1 should not work since this would discard
+ // // any algorithm restrictions.
+ // assert.throws(() => {
+ // publicKey.export({ format: 'pem', type: 'pkcs1' });
+ // }, {
+ // code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS'
+ // });
+ // {
+ // // This key pair enforces sha1 as the message digest and the MGF1
+ // // message digest and a salt length of 20 bytes.
+ // const publicPem = fixtures.readKey('rsa_pss_public_2048_sha1_sha1_20.pem');
+ // const privatePem =
+ // fixtures.readKey('rsa_pss_private_2048_sha1_sha1_20.pem');
+ // const publicKey = createPublicKey(publicPem);
+ // const privateKey = createPrivateKey(privatePem);
+ // // Unlike the previous key pair, this key pair contains an RSASSA-PSS-params
+ // // sequence. However, because all values in the RSASSA-PSS-params are set to
+ // // their defaults (see RFC 3447), the ASN.1 structure contains an empty
+ // // sequence. Node.js should add the default values to the key details.
+ // const expectedKeyDetails = {
+ // modulusLength: 2048,
+ // publicExponent: 65537n,
+ // hashAlgorithm: 'sha1',
+ // mgf1HashAlgorithm: 'sha1',
+ // saltLength: 20
+ // };
+ // assert.strictEqual(publicKey.type, 'public');
+ // assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
+ // assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
+ // assert.strictEqual(privateKey.type, 'private');
+ // assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
+ // assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
+ // }
+ // {
+ // // This key pair enforces sha256 as the message digest and the MGF1
+ // // message digest and a salt length of at least 16 bytes.
+ // const publicPem =
+ // fixtures.readKey('rsa_pss_public_2048_sha256_sha256_16.pem');
+ // const privatePem =
+ // fixtures.readKey('rsa_pss_private_2048_sha256_sha256_16.pem');
+ // const publicKey = createPublicKey(publicPem);
+ // const privateKey = createPrivateKey(privatePem);
+ // assert.strictEqual(publicKey.type, 'public');
+ // assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
+ // assert.strictEqual(privateKey.type, 'private');
+ // assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
+ // for (const key of [privatePem, privateKey]) {
+ // // Signing with anything other than sha256 should fail.
+ // assert.throws(() => {
+ // createSign('sha1').sign(key);
+ // }, /digest not allowed/);
+ // // Signing with salt lengths less than 16 bytes should fail.
+ // for (const saltLength of [8, 10, 12]) {
+ // assert.throws(() => {
+ // createSign('sha1').sign({ key, saltLength });
+ // }, /pss saltlen too small/);
+ // }
+ // // Signing with sha256 and appropriate salt lengths should work.
+ // for (const saltLength of [undefined, 16, 18, 20]) {
+ // const signature = createSign('sha256')
+ // .update('foo')
+ // .sign({ key, saltLength });
+ // for (const pkey of [key, publicKey, publicPem]) {
+ // const okay = createVerify('sha256')
+ // .update('foo')
+ // .verify({ key: pkey, saltLength }, signature);
+ // assert.ok(okay);
+ // }
+ // }
+ // }
+ // }
+ // {
+ // // This key enforces sha512 as the message digest and sha256 as the MGF1
+ // // message digest.
+ // const publicPem =
+ // fixtures.readKey('rsa_pss_public_2048_sha512_sha256_20.pem');
+ // const privatePem =
+ // fixtures.readKey('rsa_pss_private_2048_sha512_sha256_20.pem');
+ // const publicKey = createPublicKey(publicPem);
+ // const privateKey = createPrivateKey(privatePem);
+ // const expectedKeyDetails = {
+ // modulusLength: 2048,
+ // publicExponent: 65537n,
+ // hashAlgorithm: 'sha512',
+ // mgf1HashAlgorithm: 'sha256',
+ // saltLength: 20
+ // };
+ // assert.strictEqual(publicKey.type, 'public');
+ // assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
+ // assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
+ // assert.strictEqual(privateKey.type, 'private');
+ // assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
+ // assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
+ // // Node.js usually uses the same hash function for the message and for MGF1.
+ // // However, when a different MGF1 message digest algorithm has been
+ // // specified as part of the key, it should automatically switch to that.
+ // // This behavior is required by sections 3.1 and 3.3 of RFC4055.
+ // for (const key of [privatePem, privateKey]) {
+ // // sha256 matches the MGF1 hash function and should be used internally,
+ // // but it should not be permitted as the main message digest algorithm.
+ // for (const algo of ['sha1', 'sha256']) {
+ // assert.throws(() => {
+ // createSign(algo).sign(key);
+ // }, /digest not allowed/);
+ // }
+ // // sha512 should produce a valid signature.
+ // const signature = createSign('sha512')
+ // .update('foo')
+ // .sign(key);
+ // for (const pkey of [key, publicKey, publicPem]) {
+ // const okay = createVerify('sha512')
+ // .update('foo')
+ // .verify(pkey, signature);
+ // assert.ok(okay);
+ // }
+ // }
+ // }
+ // }
+ }
+});
diff --git a/test/js/node/crypto/fixtures/ec_p256_private.pem b/test/js/node/crypto/fixtures/ec_p256_private.pem
new file mode 100644
index 000000000..6bb0bb9cf
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p256_private.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDxBsPQPIgMuMyQbx
+zbb9toew6Ev6e9O6ZhpxLNgmAEqhRANCAARfSYxhH+6V5lIg+M3O0iQBLf+53kuE
+2luIgWnp81/Ya1Gybj8tl4tJVu1GEwcTyt8hoA7vRACmCHnI5B1+bNpS
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_p256_public.pem b/test/js/node/crypto/fixtures/ec_p256_public.pem
new file mode 100644
index 000000000..08f7bd26d
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p256_public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0mMYR/uleZSIPjNztIkAS3/ud5L
+hNpbiIFp6fNf2GtRsm4/LZeLSVbtRhMHE8rfIaAO70QApgh5yOQdfmzaUg==
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_p384_private.pem b/test/js/node/crypto/fixtures/ec_p384_private.pem
new file mode 100644
index 000000000..06393e263
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p384_private.pem
@@ -0,0 +1,6 @@
+-----BEGIN PRIVATE KEY-----
+MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB3B+4e4C1OUxGftkEI
+Gb/SCulzUP/iE940CB6+B6WWO4LT76T8sMWiwOAGUsuZmyKhZANiAASE43efMYmC
+/7Tx90elDGBEkVnOUr4ZkMZrl/cqe8zfVy++MmayPhR46Ah3LesMCNV+J0eG15w0
+IYJ8uqasuMN6drU1LNbNYfW7+hR0woajldJpvHMPv7wlnGOlzyxH1yU=
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_p384_public.pem b/test/js/node/crypto/fixtures/ec_p384_public.pem
new file mode 100644
index 000000000..2b50f3bbc
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p384_public.pem
@@ -0,0 +1,5 @@
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhON3nzGJgv+08fdHpQxgRJFZzlK+GZDG
+a5f3KnvM31cvvjJmsj4UeOgIdy3rDAjVfidHhtecNCGCfLqmrLjDena1NSzWzWH1
+u/oUdMKGo5XSabxzD7+8JZxjpc8sR9cl
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_p521_private.pem b/test/js/node/crypto/fixtures/ec_p521_private.pem
new file mode 100644
index 000000000..e4a8a655c
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p521_private.pem
@@ -0,0 +1,8 @@
+-----BEGIN PRIVATE KEY-----
+MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAEghuafcab9jXW4gO
+QLeDaKOlHEiskQFjiL8klijk6i6DNOXcFfaJ9GW48kxpodw16ttAf9Z1WQstfzpK
+GUetHImhgYkDgYYABAGixYI8Gbc5zNze6rH2/OmsFV3unOnY1GDqG9RTfpJZXpL9
+ChF1dG8HA4zxkM+X+jMSwm4THh0Wr1Euj9dK7E7QZwHd35XsQXgH13Hjc0QR9dvJ
+BWzlg+luNTY8CkaqiBdur5oFv/AjpXRimYxZDkhAEsTwXLwNohSUVMkN8IQtNI9D
+aQ==
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_p521_public.pem b/test/js/node/crypto/fixtures/ec_p521_public.pem
new file mode 100644
index 000000000..c0ed00f64
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p521_public.pem
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBosWCPBm3Oczc3uqx9vzprBVd7pzp
+2NRg6hvUU36SWV6S/QoRdXRvBwOM8ZDPl/ozEsJuEx4dFq9RLo/XSuxO0GcB3d+V
+7EF4B9dx43NEEfXbyQVs5YPpbjU2PApGqogXbq+aBb/wI6V0YpmMWQ5IQBLE8Fy8
+DaIUlFTJDfCELTSPQ2k=
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_secp256k1_private.pem b/test/js/node/crypto/fixtures/ec_secp256k1_private.pem
new file mode 100644
index 000000000..f753c751b
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_secp256k1_private.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgc34ocwTwpFa9NZZh3l88
+qXyrkoYSxvC0FEsU5v1v4IOhRANCAARw7OEVKlbGFqUJtY10/Yf/JSR0LzUL1PZ1
+4Ol/ErujAPgNwwGU5PSD6aTfn9NycnYB2hby9XwB2qF3+El+DV8q
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_secp256k1_public.pem b/test/js/node/crypto/fixtures/ec_secp256k1_public.pem
new file mode 100644
index 000000000..e95322efb
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_secp256k1_public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEcOzhFSpWxhalCbWNdP2H/yUkdC81C9T2
+deDpfxK7owD4DcMBlOT0g+mk35/TcnJ2AdoW8vV8Adqhd/hJfg1fKg==
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/ed25519_private.pem b/test/js/node/crypto/fixtures/ed25519_private.pem
new file mode 100644
index 000000000..f837457cb
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ed25519_private.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIMFSujN0jIUIdzSvuxka0lfgVVkMdRTuaVvIYUHrvzXQ
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ed25519_public.pem b/test/js/node/crypto/fixtures/ed25519_public.pem
new file mode 100644
index 000000000..4127a471b
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ed25519_public.pem
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEAK1wIouqnuiA04b3WrMa+xKIKIpfHetNZRv3h9fBf768=
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/ed448_private.pem b/test/js/node/crypto/fixtures/ed448_private.pem
new file mode 100644
index 000000000..9643665d6
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ed448_private.pem
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MEcCAQAwBQYDK2VxBDsEOdOtCnu9bDdBqSHNNZ5xoDA5KdLBTUNPcKFaOADNX32s
+dfpo52pCtPqfku/l3/OfUHsF43EfZsaaWA==
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ed448_public.pem b/test/js/node/crypto/fixtures/ed448_public.pem
new file mode 100644
index 000000000..b767109b1
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ed448_public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEMwBQYDK2VxAzoAoX/ee5+jlcU53+BbGRsGIzly0V+SZtJ/oGXY0udf84q2hTW2
+RdstLktvwpkVJOoNb7oDgc2V5ZUA
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_private.pem b/test/js/node/crypto/fixtures/rsa_private.pem
new file mode 100644
index 000000000..215e5cc51
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_private.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAt9xYiIonscC3vz/A2ceR7KhZZlDu/5bye53nCVTcKnWd2seY
+6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq+/8i
+BkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2y/Hy4DjQKBq1ThZ0UBnK+9IhX37J
+u/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/kN2VnnbRUtkYTF97ggcv5h+hDpUQ
+jQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsFdi6hHcpZgbopPL630296iByyigQC
+PJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx9QIDAQABAoIBAQCS2erYu8gyoGPi
+3E/zYgQ6ishFAZWzDWSFubwD5wSm4SSAzvViL/RbO6kqS25xR569DmLRiHzD17VI
+mJMsNECUnPrqR2TL256OJZaXrNHh3I1lUwVhEzjeKMsL4/ys+d70XPXoiocVblVs
+moDXEIGEqa48ywPvVE3Fngeuxrsq3/GCVBNiwtt0YjAOZxmKEh31UZdHO+YI+wNF
+/Z8KQCPscN5HGlR0SIQOlqMANz49aKStrevdvjS1UcpabzDEkuK84g3saJhcpAhb
+pGFmAf5GTjkkhE0rE1qDF15dSqrKGfCFtOjUeK17SIEN7E322ChmTReZ1hYGfoSV
+cdFntUINAoGBAPFKL5QeJ6wZu8R/ru11wTG6sQA0Jub2hGccPXpbnPrT+3CACOLI
+JTCLy/xTKW3dqRHj/wZEe+jUw88w7jwGb1BkWr4BI8tDvY9jQLP1jyuLWRfrxXbp
+4Z0oeBBwBeCI/ZG7FIvdDTqWxn1aj3Tmh6s4ByqEdtwrrrJPcBUNl01fAoGBAMMR
+3RGE/ca6X6xz6kgUD6TtHVhiiRJK1jm/u+q0n7i/MBkeDgTZkHYS7lPc0yIdtqaI
+Plz5yzwHnAvuMrv8LSdkjwioig2yQa3tAij8kXxqs7wN5418DMV2s1OJBrPthYPs
+bv4im2iI8V63JQS4ZMYQbckq8ABYccTpOnxXDy0rAoGBAKkvzHa+QjERhjB9GyoT
+1FhLQIsVBmYSWrp1+cGO9V6HPxoeHJzvm+wTSf/uS/FmaINL6+j4Ii4a6gWgmJts
+I6cqBtqNsAx5vjQJczf8KdxthBYa0sXTrsfktXNJKUXMqIgDtp9vazQ2vozs8AQX
+FPAAhD3SzgkJdCBBRSTt97ZfAoGAWAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCD
+dCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP/Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNm
+bDXFPdG0G3hzQovx/9fajiRV4DWghLHeT9wzJfZabRRiI0VQR472300AVEeX4vgb
+rDBn600CgYEAk7czBCT9rHn/PNwCa17hlTy88C4vXkwbz83Oa+aX5L4e5gw5lhcR
+2ZuZHLb2r6oMt9rlD7EIDItSs+u21LOXWPTAlazdnpYUyw/CzogM/PN+qNwMRXn5
+uXFFhmlP2mVg2EdELTahXch8kWqHaCSX53yvqCtRKu/j76V31TfQZGM=
+-----END RSA PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_private_2048.pem b/test/js/node/crypto/fixtures/rsa_private_2048.pem
new file mode 100644
index 000000000..0e5bde2e0
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_private_2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEArk4OqxBqU5/k0FoUDU7CpZpjz6YJEXUpyqeJmFRVZPMUv/Rc
+7U4seLY+Qp6k26T/wlQ2WJWuyY+VJcbQNWLvjJWks5HWknwDuVs6sjuTM8CfHWn1
+960JkK5Ec2TjRhCQ1KJy+uc3GJLtWb4rWVgTbbaaC5fiR1/GeuJ8JH1Q50lB3mDs
+NGIk1U5jhNaYY82hYvlbErf6Ft5njHK0BOM5OTvQ6BBv7c363WNG7tYlNw1J40du
+p9OQPo5JmXN/h+sRbdgG8iUxrkRibuGv7loh52QQgq2snznuRMdKidRfUZjCDGgw
+bgK23Q7n8VZ9Y10j8PIvPTLJ83PX4lOEA37JlwIDAQABAoIBACoL2Ev5lLyBaI+9
++vJO2nNaL9OKSMu2SJODIJTnWwYUASBg0P3Jir6/r3sgi8IUJkH5UHbD/LrQcPkA
+4X7PU9vEyUsr1efWFIvk7t7JsjOctoVA5z2Mty74arivUIe5PUadvUC6/7Zk0u6A
+CjLuJRmlH7nGNKZk+xrvgWTH+fkgc5ddbFxoGH129RcVC+ePbsi1EF60C9KbJvp1
+xjUJ5cDtNYnZ/g+ULo6ZJjRG5kUCVSI8H/Nc/DmStKsjN0isKpNGofU5ArEwywGC
+Cqxz/tr4hT2haAkVEto04ooYpqDUSqGEfPpLWL+CjFNPfCsWJ1tX5LQRvpu6eukd
+FO72oVECgYEA4+Ot7RQtGOtPeaxl3P7sbEzBZk0W/ZhCk+mzE2iYh0QXU44VtjtO
+/9CENajTklckiwlxjqBZ5NO1SiMQKBcmqkoA03x/DEujo2rMVuNPoc6ZYp1Gc4qA
+4ImkMQNsM7Swum42rKE960WoiWW7dsdEAq6vqgeApZlMU8lcKRAlOZkCgYEAw85H
+3bjF7gMatVibsWzj0zp2L4616m2v5Z3YkgohGRAvm1912DI5ku5Nmy9am57Z1EP2
+UtDOxahd/Vf6mK9lR4YEbNW1TenykViQJ6lmljOFUeZEZYYO3O+fthkyN/42l5yn
+MyUANTTb2rvt8amdRr0ARdRqWJmt5NfJzYBV+q8CgYB1ZjuZoQVCiybcRcYMPX/K
+oxgW/avUZPYXgRNx8jZxqNBjiRUCVjdybhdOFXU5NI9s2SaZFV56Fd6VHM8b+CFB
+JPKcAMzqpqTccQ5nzJ6fevFl7iP3LekKw53EakD5uiI5SMH92OsvIymZ7sDOhgUx
+ZJC2hTrvFLRPjbJerSSgMQKBgAv5iZuduT0dI30DtkHbjvNUF/ZAnA+CNcetJ5mG
+1Q9bVg4CgIqAR9UcjdJ3yurJhDjfDylxa7Pa4CSmRMUhtOfy4kJlr3jcXeFVsTs7
+uPJmpDimBHjRAgew/+t7Dv8tpNkQ04jlMmYOnYN7CspEvUGePW4H15kjjOb563WN
+67QxAoGAdhJPaHVtDBUrobn50ORVkVNJVZPBhEDbR6tNtHsgEevH7iYjxAdxEeUa
+c9S2iV9lir3hkrQceiYWAADcphWfO1HyP5Uld4sJzYiEGbSM7a0zRQjYlQgXFbZo
+SAc6Gok78kwECPwpmeH4lpGVeKNmzEteSBVYxGb9b6C/SSsu7l0=
+-----END RSA PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_private_4096.pem b/test/js/node/crypto/fixtures/rsa_private_4096.pem
new file mode 100644
index 000000000..4177b9ef9
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_private_4096.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAxeStwofbjtZuol4lwKn1w08AzcSNLHfCqNFHa+W7er8is7LQ
+sPljtPT4yn4lsao83ngHFvSC3tbMiRNDpUHYqH2wBuUkuOmCtYkZLi0307H0CwcV
+V6W5P3tNEt80IJ+PqlRxtTknezUtbOasbIi/aornVWG+psgqDGrFZ4oTsWtiE0Sv
+i7sDqN5E2dijmH/YYnlnwqszgzHdtAnARp1bG34E64sqWCmLoGCfPdHtym/CSdxO
+LOsDV15jrwODZQ/TJZ5thkwKZRxu7g9fwlhA1PiI5WDP4reXNaqa2bSgrzpAljQE
+xYs4N0L7okSVOJQX9BEaoWtq8NLU8MpMdGoHNDU0Xr60Lfr58Z5qn8RGEvlTxoCb
+PJzPV2zgzD/lmEqft6NnfTclveA3sd8xSrOBUn4o3S8hS0b9Su7PBukHjM96/e0R
+eoIshSwXlQTLr2Ft8KwupyPm1ltNcTDtjqHcIWU6Bg+kPy9mxSVtGGZYAPtqGzNB
+A/m+oOja/OSPxAblPdln691DaDuZs5nuZCGwGcLaJWgiyoqvXAcyXDZFyH4OZZh8
+rsBLKbnFXHZ/ziG0cAozEygZEPJappw8Lx/ady7WL/SJjxooiKapc7Bnfy8eSLV3
++XAKxhLW/MQ6ChJ+e/8ExAY02ca4MpCvqwIk9TfV6FM8pWGqHzQFj0v3NL0CAwEA
+AQKCAgBTb8eTbZS09NRQwUFJql9kqbq9B1I+nYAFjbd/Vq1lY5FOEubKt1vCwEbl
+mapq7kwbwJ+8nftP2WEDqouq8chXwialwZdqH4ps4BEt1wLizvUGcUYeXlFs4p/s
+hQ+FccExH8mRjzeGSzWL5PZuDHoogchnx36K83pHIf15Wk5TT+NaHGunjoJMgOqm
+ryDK+5xQaL/G5Egj2LKRZksbet0fClMovNRtt5aXWCXL+uc3o0dXvPt5FN2jyLhe
+4ixUQAfWpKWpKgZ3+zUKSpElb/Bl2yRdEiSUgrPOfNAtWmsldnok2mnooHpjUmqm
+UCRaZpZy4YNI6/F6+Gmv3Ju/ubSvHzoxQLlvgUqWAnVshivF1TJImHSIiLIvBKPp
+29SD6maWIT1DC9sKC4E1gq7VO4762l1//zEOAY7XK0Z7LrbZO4WXHnsgFOpGthQ3
+g9Qi/SeM6mb5xEJTBUBTmkhGs1x8jolzca30mqv8T63W4PXkXHmZdK7vyH5useiI
+s0eGUeaYK892WgfxCBo24JCNQiAcH/wTwV4l4yROqeH2V4ShbIYmCzla++7vsPYW
+hAwQR9eH0+4ogTkaMQrm16plZk0ezVX9BKK8KTnd4G9/T18VstQbiowF2/cKnGKC
+OqrmoR2vHOksQdUJVmnwCRqU1symBxhY0GSIps98v+lUYExKQQKCAQEA/uVYE2/H
+eNcV/uWAI9LspANXHJE33TFMZ8SuyOYtp3MYJizmQ1uT7Om2LEanDnNiz+fAQhrE
+vo1sDIF9xOAde2qjIH+iDzcLvFPgC3gkQspFjU31M9OO5xAjzBxfL3KDiG2MtmTR
+hNuKJX56eCOqkEp6WKaWOA35ccaKYHxNzMS49weCv95ZPpR9q0J1sgzD7HtVh4yu
+XI01/BC8F0RmYjtsuUo+PmB6sO2K94uqqo0GPUos7Mhgrbff3L36EkOPgmRiA1AV
+Zy1sKKxUKspGQ3m1fg+CA/+GZGckvYkVot1lFrwmrS2dok8EhT1HcVJde+++jx7z
+JsRLgFRvKHXklwKCAQEAxsAfxIQjjjKmuyJCzIvxG7lnuzovdy4OEdSuJL4yK5m3
+4BHJHn+yHeRIcrDnJKUTUYffcH/OjOnJS94BA6wH1tEuvGQz6LV6UpwApZ1M/2md
+nP0eC2L2JtSRL8mdxfyqXDloWMpD7rncBZ6ChLEZ6sWYa6WBQTARmPVePyUpNNG2
+qymxN3/vRBGGBunD3j6zX0M0szWK5iU+qsYDy3KzCKG8FU7XxwzRbP7iARRD5Hpt
+Zmy2W52EJg1uhmlVXJMm32SEBfrD2oDmlnjAqaZdqi5Mq2e4uB3dhM9RwJppSALG
+BY6k9DeanAFbOlawMJri2pk7B0phCn+DN2pg0+W3ywKCAQBeTwzfZCQxmaMRxGg8
+2PWlWXcJotFAjdTvL95bho6tve/ZcBNiKKf6qB43E40L07VjpyODUdQpjLnFhsO5
+7BH8b+AbTh3v8zXsYDwtAi6oZ56EQavPmR7ubxJPms+9BmmUOLQvZ+39ch0S8lDt
+0oRxDp1l330FEGaSqhrYyCUg9khZXfYKd4IdnWNB0j0pu39iJ9/lXy/EHpsywB5X
+nX8kKUh45fdRrPC4NauNG6fxomwEkUU99oWOwNGbIs87orOeUvXQs/i3TB8QjXI2
+wtBsdsOn+KTqRci7rU3ysp3GvJOCbesBeDcyrnnFsn6Udx0Plgyzd4gPd+FXgeX+
+2l/RAoIBAH81FKAY2xD2RlTb1tlIcGeIQWZKFXs4VPUApP0LZt0VI+UcPRdyL7SG
+GgCeTTLdHQI/7rj4dGEoeRg/3XJWNyY8+KbHk5nMHaCmDJvzlAaduK10LDipfFba
+Epr9dif0Ua15aNn7i4NOHg7Sp0L6f1YOZkHvykzI0VqPIWVVCYyu9TWUF8Mn9SIh
+/SCLmjuy8ed1AlP5Xw9yoyt2VZNvtDtAGTuiHOVfxOL4N/rs149y9HZr+kOlC6G3
+Uxhgbqwz2tt8YCvblmNRwURpwRZUTvrPa28Bke713oRUlUSrD9txOwDvjZBpzmEv
+VQ5/0YEqgSvcizVdW8L2XiunwJWfIAUCggEBALr4RF9TYa37CImZOs+vJ8FGRKMz
+h1EUwO2PvuITvkTtu/7E4IjyxAo5dkAokkWQCGABciiDJJEYUWqcUX45qQChOgtm
+NU2od6f9tgyDFxN5KS8cE32NXV3rJXs3bBZmIKLSPETf3uIPuEpFPjpdR5v5jlV+
+TDjH4RrItE3hDCvypTXhXXMmWp3VfYbgEfIP03uR2iIhL+/g3BUqbrywPEsTViSN
+NM/uBDQyamXLXB1bQ2I/Ob41I82PD1iNCqGi7ZvZ3eVYGgUTQyw6Q4O8glTPP9cC
+SFVXwE9gHbLe8TqfTZCWrM6crGX6Bb6hV2tqNsA+7J69U9NGuw5GNqXjafU=
+-----END RSA PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_private_encrypted.pem b/test/js/node/crypto/fixtures/rsa_private_encrypted.pem
new file mode 100644
index 000000000..f1914289e
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_private_encrypted.pem
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,DB3D20E60E8FDC3356BD79712FF8EF7E
+
+K+vu0U3IFTJBBi6zW5Zng80O1jXq/ZmlOFs/j/SQpPwfW1Do9i/Dwa7ntBlTwrCm
+sd3IIPgu2ikfLwxvbxsZN540oCaCqaZ/bmmyzH3MyVDA9MllUu+X8+Q3ATzcYa9R
+U5XfF5DAXsSRnstCbmKagWVQpO0oX8k3ratfny6Ixq86Y82tK8+o5YiBFq1kqa+9
+4yat7IWQbqV5ifUtUPCHZwEqBt+WKazX05BqERjkckHdpfaDrBvSSPXTwoLm6uRR
+ktkUVpO4tHMZ4VlcTfFtpz8gdYYod0nM6vz26hvbESHSwztSgMhmKdsE5eqmYfgu
+F4WkEN4bqAiPjKK3jnUKPt/vg2oKYFQlVYFl9QnBjiRqcQTi3e9lwn1hI7uoMb6g
+HuaCc57JJHPN/ZLP3ts4ZxFbwUjTGioh5Zh6WozG3L3+Ujwq/sDrAskRyzdcuP7I
+Rs3oLbHY03OHyg8IbxR5Iu89l6FLqnR45yvbxXtZ7ImGOPM5Z9pB1CzDhGDx2F6g
+J/Kf/7ZF2DmYUVbVKDfESEDhRfuMAVzhasDPTRqipSA5QvJVQY+J/6QDPrNNmHVB
+4e4ouHIDWERUf0t1Be7THvP3X8OJozj2HApzqa5ZCaJDo8eaL8TCD5uH75ID5URJ
+VscGHaUXT8/sxfHi1x8BibW5W5J/akFsnrnJU/1BZgGznIxjf5tKfHGppSIVdlKP
+3ghYNmEIFPNJ6cxuUA0D2IOV4uO3FTCU6seIzvJhYkmXnticcZYGtmGxXKrodtzS
+J1YuaNkkO/YRZah285lQ6QCIhCFo4Oa4ILjgoTQISuw7nQj5ESyncauzLUBXKX0c
+XDUej64KNTvVF9UXdG48fYvNmSZWCnTye4UmPu17FmwpVra38U+EdoLyWyMIAI5t
+rP6Hhgc9BxOo41Im9QpTcAPfKAknP8Rbm3ACJG5T9FKq/c29d1E//eFR6SL51e/a
+yWdCgJN/FJOAX60+erPwoVoRFEttAeDPkklgFGdc8F4LIYAig9gEZ92ykFFz3fWz
+jIcUVLrL+IokFbPVUBoMihqVyMQsWH+5Qq9wjxf6EDIf0BVtm9U4BJoOkPStFIfF
+Kof7OVv7izyL8R/GIil9VQs9ftwkIUPeXx2Hw0bE3HJ3C8K4+mbLg3tKhGnBDU5Z
+Xm5mLHoCRBa3ZRFWZtigX7POszdLAzftYo8o65Be4OtPS+tQAORk9gHsXATv7dDB
+OGw61x5KA55LHVHhWaRvu3J8E7nhxw0q/HskyZhDC+Y+Xs6vmQSb4nO4ET4NYX1P
+m3PMdgGoqRDJ2jZw4eoQdRKCM0EHSepSAYpO1tcAXhPZS4ITogoRgPpVgOebEQUL
+nKNeNu/BxMSH/IH15jjDLF3TiEoguF9xdTaCxIBzE1SFpVO0u9m9vXpWdPThVgsb
+VcEI487p7v9iImP3BYPT8ZYvytC26EH0hyOrwhahTvTb4vXghkLIyvPUg1lZHc6e
+aPHb2AzYAHLnp/ehDQGKWrCOJ1JE2vBv8ZkLa+XZo7YASXBRZitPOMlvykEyzxmR
+QAmNhKGvFmeM2mmHAp0aC03rgF3lxNsXQ1CyfEdq3UV9ReSnttq8gtrJfCwxV+wY
+-----END RSA PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_pss_private_2048.pem b/test/js/node/crypto/fixtures/rsa_pss_private_2048.pem
new file mode 100644
index 000000000..ffca137a7
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_pss_private_2048.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADALBgkqhkiG9w0BAQoEggSoMIIEpAIBAAKCAQEAy4OMdS84PlgI5CRL
+bdbud9Ru7vprFr2YNNUmdT7D3YgApiv8CjzKXLiVDnbMET+lwmtag/EcZsxVCKov
+su30pYASBriHOiMVYui9+ZaJoQ9yI6lOjG1RbuUBJXNSjHBJxqBqmcgZOb1mdRr/
+eXzpAMWJ3hfuLojU2+zUSJ3/rvepepcLFG2q9nA0+PJskJ7Pnh3L0ydnv3U3hduM
+n5OVfm/Jx1FPyZpD184tJff+N+MY3s3hIcfuOnL9Pl4RPGeaTC4T1o460NaG6bG7
+c2Whg6NOaVgaFIaiNbrTTNCpVjeTyalsTXYlQQ3hiKjst0Q7pfFEkJDo8qiqLad1
+Msl59wIDAQABAoIBAQC6G8aqs0/f02nuGDLSc6cH9kCsUlz0ItW6GuJcfdVoFSNi
+0v5d7lGwkSveWk0ryOSw8rOHzUqHx3xLvDZ6jpkXcBMMClu/kq3QEb8JK90YaKOc
+cQvf52h83Pc7ZEatH1KYTcKudwp6fvXfSZ0vYEdD6WG2tHOgIollxSIsdjCHs1qi
+7baNHdK9T4DveuEZNcZ+LraZ1haHmFeqIPcy+KvpGuTaLCg5FPhH2jsIkw9apr7i
+iFLi+IJ7S5Bn/8XShmJWk4hPyx0jtIkC5r2iJnHf4x+XYWZfdo7oew3Dg6Pa7T6r
+I164Nnaw0u0LvO4gQdvYaJ/j9A602nHTp7Tsq8chAoGBAOtVHgIqpmdzwR5KjotW
+LuGXDdO9X6Xfge9ca2MlWH1jOj+zqEV7JtrjnZAzzOgP2kgqzpIR71Njs8wkaxTJ
+Tle0Ke6R/ghU9YOQgRByKjqJfQXHZnYFPsMg0diNYLroJ4SG8LO4+2SygTYZ4eKL
+qU0bda3QvQ7FL+rTNQBy01b9AoGBAN1jEQI80JxCT7AMvXE6nObIhbkASHte4yOE
+1CBwcOuBEGcuvMOvQVMzKITgea6+kgsu4ids4dM5PTPapQgpKqIIQ2/eSesaf56g
+73clGGSTPHJP0v+EfKg4+GYJf8o2swT0xDHkgWLgjjdsncB9hATc2j6DvHeau18d
+pgCLz9kDAoGAXl/SGvhTp2UqayVnKMW1I07agrGNLA4II5+iiS4u4InskCNSNhr/
+KATj6TJ82AuTdCGGmdmLapuvPQzVzI42VsGvlzcA8wJvOwW2XIwMF1GPy8N9eZL8
+6m+89+Uqh4oWXvVmjgx+9JEJdFLI3Xs4t+1tMfll+AhoAPoWZUmnK1kCgYAvEBxR
+iXQfg8lE97BeHcO1G/OxfGnsMCPBLT+bFcwrhGhkRv9B6kPM2BdJCB9WEpUhY3oY
+P4FSUdy85UIoFfhGMdOEOJEmNZ/jrPq7LVueJd63vlhwkU2exV2o82QDLNWpvA7p
+PFZ1Gp+hEKoIfaZPElQi7gZmtrIWaksb2pz42QKBgQCct9NP2qJfqeS4206RDnfv
+M238/O2lNhLWdSwY0g+tcN+I1sGs3+4vvrm95cxwAmXZyIM11wjdMcAPNxibodY7
+vufsebPHDBA0j0yuTjGkXefUKd1GdO88i5fppzxB7prDX9//DsWWrFhIMMRNYe0Q
+aeHd/NPuHcjZKcnaVBgukQ==
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_pss_public_2048.pem b/test/js/node/crypto/fixtures/rsa_pss_public_2048.pem
new file mode 100644
index 000000000..ade38f20a
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_pss_public_2048.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIDALBgkqhkiG9w0BAQoDggEPADCCAQoCggEBAMuDjHUvOD5YCOQkS23W7nfU
+bu76axa9mDTVJnU+w92IAKYr/Ao8yly4lQ52zBE/pcJrWoPxHGbMVQiqL7Lt9KWA
+Ega4hzojFWLovfmWiaEPciOpToxtUW7lASVzUoxwScagapnIGTm9ZnUa/3l86QDF
+id4X7i6I1Nvs1Eid/673qXqXCxRtqvZwNPjybJCez54dy9MnZ791N4XbjJ+TlX5v
+ycdRT8maQ9fOLSX3/jfjGN7N4SHH7jpy/T5eETxnmkwuE9aOOtDWhumxu3NloYOj
+TmlYGhSGojW600zQqVY3k8mpbE12JUEN4Yio7LdEO6XxRJCQ6PKoqi2ndTLJefcC
+AwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_public.pem b/test/js/node/crypto/fixtures/rsa_public.pem
new file mode 100644
index 000000000..8c30cfa52
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_public.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9xYiIonscC3vz/A2ceR
+7KhZZlDu/5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAG
+YbFswlNmeD44edEGM939B6Lq+/8iBkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2
+y/Hy4DjQKBq1ThZ0UBnK+9IhX37Ju/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/
+kN2VnnbRUtkYTF97ggcv5h+hDpUQjQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsF
+di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx
+9QIDAQAB
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_public_2048.pem b/test/js/node/crypto/fixtures/rsa_public_2048.pem
new file mode 100644
index 000000000..0c80ceb4d
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_public_2048.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArk4OqxBqU5/k0FoUDU7C
+pZpjz6YJEXUpyqeJmFRVZPMUv/Rc7U4seLY+Qp6k26T/wlQ2WJWuyY+VJcbQNWLv
+jJWks5HWknwDuVs6sjuTM8CfHWn1960JkK5Ec2TjRhCQ1KJy+uc3GJLtWb4rWVgT
+bbaaC5fiR1/GeuJ8JH1Q50lB3mDsNGIk1U5jhNaYY82hYvlbErf6Ft5njHK0BOM5
+OTvQ6BBv7c363WNG7tYlNw1J40dup9OQPo5JmXN/h+sRbdgG8iUxrkRibuGv7loh
+52QQgq2snznuRMdKidRfUZjCDGgwbgK23Q7n8VZ9Y10j8PIvPTLJ83PX4lOEA37J
+lwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_public_4096.pem b/test/js/node/crypto/fixtures/rsa_public_4096.pem
new file mode 100644
index 000000000..4fd0bbc1c
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_public_4096.pem
@@ -0,0 +1,14 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeStwofbjtZuol4lwKn1
+w08AzcSNLHfCqNFHa+W7er8is7LQsPljtPT4yn4lsao83ngHFvSC3tbMiRNDpUHY
+qH2wBuUkuOmCtYkZLi0307H0CwcVV6W5P3tNEt80IJ+PqlRxtTknezUtbOasbIi/
+aornVWG+psgqDGrFZ4oTsWtiE0Svi7sDqN5E2dijmH/YYnlnwqszgzHdtAnARp1b
+G34E64sqWCmLoGCfPdHtym/CSdxOLOsDV15jrwODZQ/TJZ5thkwKZRxu7g9fwlhA
+1PiI5WDP4reXNaqa2bSgrzpAljQExYs4N0L7okSVOJQX9BEaoWtq8NLU8MpMdGoH
+NDU0Xr60Lfr58Z5qn8RGEvlTxoCbPJzPV2zgzD/lmEqft6NnfTclveA3sd8xSrOB
+Un4o3S8hS0b9Su7PBukHjM96/e0ReoIshSwXlQTLr2Ft8KwupyPm1ltNcTDtjqHc
+IWU6Bg+kPy9mxSVtGGZYAPtqGzNBA/m+oOja/OSPxAblPdln691DaDuZs5nuZCGw
+GcLaJWgiyoqvXAcyXDZFyH4OZZh8rsBLKbnFXHZ/ziG0cAozEygZEPJappw8Lx/a
+dy7WL/SJjxooiKapc7Bnfy8eSLV3+XAKxhLW/MQ6ChJ+e/8ExAY02ca4MpCvqwIk
+9TfV6FM8pWGqHzQFj0v3NL0CAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/x25519_private.pem b/test/js/node/crypto/fixtures/x25519_private.pem
new file mode 100644
index 000000000..926c4e3a2
--- /dev/null
+++ b/test/js/node/crypto/fixtures/x25519_private.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VuBCIEIJi/yFpueUawC1BkXyWM8ONIBGFjL7UZHrD/Zo/KPDpn
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/x25519_public.pem b/test/js/node/crypto/fixtures/x25519_public.pem
new file mode 100644
index 000000000..e2d756bd1
--- /dev/null
+++ b/test/js/node/crypto/fixtures/x25519_public.pem
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VuAyEAaSb8Q+RndwfNnPeOYGYPDUN3uhAPnMLzXyfi+mqfhig=
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/x448_private.pem b/test/js/node/crypto/fixtures/x448_private.pem
new file mode 100644
index 000000000..61cd52c39
--- /dev/null
+++ b/test/js/node/crypto/fixtures/x448_private.pem
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MEYCAQAwBQYDK2VvBDoEOLTDbazv6vHZWOmODQ3kk8TUOQgApB4j75rpInT5zSLl
+/xJHK8ixF7f+4uo+mGTCrK1sktI5UmCZ
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/x448_public.pem b/test/js/node/crypto/fixtures/x448_public.pem
new file mode 100644
index 000000000..6475d0438
--- /dev/null
+++ b/test/js/node/crypto/fixtures/x448_public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEIwBQYDK2VvAzkAioHSHVpTs6hMvghosEJDIR7ceFiE3+Xccxati64oOVJ7NWjf
+ozE7ae31PXIUFq6cVYgvSKsDFPA=
+-----END PUBLIC KEY-----
diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js
index 4fb678dce..fd91fdef5 100644
--- a/test/js/node/process/process.test.js
+++ b/test/js/node/process/process.test.js
@@ -263,6 +263,15 @@ describe("process.exitCode", () => {
});
});
+it("process exitCode range (#6284)", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), join(import.meta.dir, "process-exitCode-fixture.js"), "255"],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(255);
+ expect(stdout.toString().trim()).toBe("PASS");
+});
+
it("process.exit", () => {
const { exitCode, stdout } = spawnSync({
cmd: [bunExe(), join(import.meta.dir, "process-exit-fixture.js")],
diff --git a/test/js/third_party/jsonwebtoken/async_sign.test.js b/test/js/third_party/jsonwebtoken/async_sign.test.js
new file mode 100644
index 000000000..6efb838d0
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/async_sign.test.js
@@ -0,0 +1,159 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+import jws from "jws";
+import { generateKeyPairSync } from "crypto";
+var PS_SUPPORTED = true;
+
+describe("signing a token asynchronously", function () {
+ describe("when signing a token", function () {
+ var secret = "shhhhhh";
+
+ it("should return the same result as singing synchronously", function (done) {
+ jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" }, function (err, asyncToken) {
+ if (err) return done(err);
+ var syncToken = jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" });
+ expect(typeof asyncToken).toBe("string");
+ expect(asyncToken.split(".")).toHaveLength(3);
+ expect(asyncToken).toEqual(syncToken);
+ done();
+ });
+ });
+
+ it("should work with empty options", function (done) {
+ jwt.sign({ abc: 1 }, "secret", {}, function (err) {
+ expect(err).toBeNull();
+ done();
+ });
+ });
+
+ it("should work without options object at all", function (done) {
+ jwt.sign({ abc: 1 }, "secret", function (err) {
+ expect(err).toBeNull();
+ done();
+ });
+ });
+
+ it("should work with none algorithm where secret is set", function (done) {
+ jwt.sign({ foo: "bar" }, "secret", { algorithm: "none" }, function (err, token) {
+ expect(typeof token).toBe("string");
+ expect(token.split(".")).toHaveLength(3);
+ done();
+ });
+ });
+
+ //Known bug: https://github.com/brianloveswords/node-jws/issues/62
+ //If you need this use case, you need to go for the non-callback-ish code style.
+ it.skip("should work with none algorithm where secret is falsy", function (done) {
+ jwt.sign({ foo: "bar" }, undefined, { algorithm: "none" }, function (err, token) {
+ expect(typeof token).toBe("string");
+ expect(token.split(".")).toHaveLength(3);
+ done();
+ });
+ });
+
+ it("should return error when secret is not a cert for RS256", function (done) {
+ //this throw an error because the secret is not a cert and RS256 requires a cert.
+ jwt.sign({ foo: "bar" }, secret, { algorithm: "RS256" }, function (err) {
+ expect(err).toBeTruthy();
+ done();
+ });
+ });
+
+ it("should not work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is false or not set", function (done) {
+ const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 1024 });
+
+ jwt.sign({ foo: "bar" }, privateKey, { algorithm: "RS256" }, function (err) {
+ expect(err).toBeTruthy();
+ done();
+ });
+ });
+
+ it("should work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is true", function (done) {
+ const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 1024 });
+
+ jwt.sign({ foo: "bar" }, privateKey, { algorithm: "RS256", allowInsecureKeySizes: true }, done);
+ });
+
+ if (PS_SUPPORTED) {
+ it("should return error when secret is not a cert for PS256", function (done) {
+ //this throw an error because the secret is not a cert and PS256 requires a cert.
+ jwt.sign({ foo: "bar" }, secret, { algorithm: "PS256" }, function (err) {
+ expect(err).toBeTruthy();
+ done();
+ });
+ });
+ }
+
+ it("should return error on wrong arguments", function (done) {
+ //this throw an error because the secret is not a cert and RS256 requires a cert.
+ jwt.sign({ foo: "bar" }, secret, { notBefore: {} }, function (err) {
+ expect(err).toBeTruthy();
+ done();
+ });
+ });
+
+ it("should return error on wrong arguments (2)", function (done) {
+ jwt.sign("string", "secret", { noTimestamp: true }, function (err) {
+ expect(err).toBeTruthy();
+ expect(err).toBeInstanceOf(Error);
+ done();
+ });
+ });
+
+ it("should not stringify the payload", function (done) {
+ jwt.sign("string", "secret", {}, function (err, token) {
+ if (err) {
+ return done(err);
+ }
+ expect(jws.decode(token).payload).toEqual("string");
+ done();
+ });
+ });
+
+ describe("when mutatePayload is not set", function () {
+ it("should not apply claims to the original payload object (mutatePayload defaults to false)", function (done) {
+ var originalPayload = { foo: "bar" };
+ jwt.sign(originalPayload, "secret", { notBefore: 60, expiresIn: 600 }, function (err) {
+ if (err) {
+ return done(err);
+ }
+ expect(originalPayload).not.toHaveProperty("nbf");
+ expect(originalPayload).not.toHaveProperty("exp");
+ done();
+ });
+ });
+ });
+
+ describe("when mutatePayload is set to true", function () {
+ it("should apply claims directly to the original payload object", function (done) {
+ var originalPayload = { foo: "bar" };
+ jwt.sign(originalPayload, "secret", { notBefore: 60, expiresIn: 600, mutatePayload: true }, function (err) {
+ if (err) {
+ return done(err);
+ }
+ expect(originalPayload).toHaveProperty("nbf");
+ expect(originalPayload).toHaveProperty("exp");
+ done();
+ });
+ });
+ });
+
+ describe("secret must have a value", function () {
+ [undefined, "", 0].forEach(function (secret) {
+ it(
+ "should return an error if the secret is falsy and algorithm is not set to none: " +
+ (typeof secret === "string" ? "(empty string)" : secret),
+ function (done) {
+ // This is needed since jws will not answer for falsy secrets
+ jwt.sign("string", secret, {}, function (err, token) {
+ expect(err).toBeTruthy();
+ expect(err.message).toEqual("secretOrPrivateKey must have a value");
+ expect(token).toBeFalsy();
+ done();
+ });
+ },
+ );
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/buffer.test.js b/test/js/third_party/jsonwebtoken/buffer.test.js
new file mode 100644
index 000000000..28d310221
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/buffer.test.js
@@ -0,0 +1,10 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+
+describe("buffer payload", function () {
+ it("should work", function () {
+ var payload = new Buffer("TkJyotZe8NFpgdfnmgINqg==", "base64");
+ var token = jwt.sign(payload, "signing key");
+ expect(jwt.decode(token)).toBe(payload.toString());
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/claim-aud.test.js b/test/js/third_party/jsonwebtoken/claim-aud.test.js
new file mode 100644
index 000000000..b850b265b
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/claim-aud.test.js
@@ -0,0 +1,423 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+import { expect, describe, it, beforeEach } from "bun:test";
+import util from "util";
+import testUtils from "./test-utils";
+
+function signWithAudience(audience, payload, callback) {
+ const options = { algorithm: "HS256" };
+ if (audience !== undefined) {
+ options.audience = audience;
+ }
+
+ testUtils.signJWTHelper(payload, "secret", options, callback);
+}
+
+function verifyWithAudience(token, audience, callback) {
+ testUtils.verifyJWTHelper(token, "secret", { audience }, callback);
+}
+
+describe("audience", function () {
+ describe('`jwt.sign` "audience" option validation', function () {
+ [true, false, null, -1, 1, 0, -1.1, 1.1, -Infinity, Infinity, NaN, {}, { foo: "bar" }].forEach(audience => {
+ it(`should error with with value ${util.inspect(audience)}`, function (done) {
+ signWithAudience(audience, {}, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"audience" must be a string or array');
+ });
+ });
+ });
+ });
+
+ // undefined needs special treatment because {} is not the same as {aud: undefined}
+ it("should error with with value undefined", function (done) {
+ testUtils.signJWTHelper({}, "secret", { audience: undefined, algorithm: "HS256" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"audience" must be a string or array');
+ });
+ });
+ });
+
+ it('should error when "aud" is in payload', function (done) {
+ signWithAudience("my_aud", { aud: "" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty(
+ "message",
+ 'Bad "options.audience" option. The payload already has an "aud" property.',
+ );
+ });
+ });
+ });
+
+ it("should error with a string payload", function (done) {
+ signWithAudience("my_aud", "a string payload", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid audience option for string payload");
+ });
+ });
+ });
+
+ it("should error with a Buffer payload", function (done) {
+ signWithAudience("my_aud", new Buffer("a Buffer payload"), err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid audience option for object payload");
+ });
+ });
+ });
+ });
+
+ describe('when signing and verifying a token with "audience" option', function () {
+ describe('with a "aud" of "urn:foo" in payload', function () {
+ let token;
+
+ beforeEach(function (done) {
+ signWithAudience("urn:foo", {}, (err, t) => {
+ token = t;
+ done(err);
+ });
+ });
+
+ [
+ undefined,
+ "urn:foo",
+ /^urn:f[o]{2}$/,
+ ["urn:no_match", "urn:foo"],
+ ["urn:no_match", /^urn:f[o]{2}$/],
+ [/^urn:no_match$/, /^urn:f[o]{2}$/],
+ [/^urn:no_match$/, "urn:foo"],
+ ].forEach(audience => {
+ it(`should verify and decode with verify "audience" option of ${util.inspect(audience)}`, function (done) {
+ verifyWithAudience(token, audience, (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", "urn:foo");
+ });
+ });
+ });
+ });
+
+ it(`should error on no match with a string verify "audience" option`, function (done) {
+ verifyWithAudience(token, "urn:no-match", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", `jwt audience invalid. expected: urn:no-match`);
+ });
+ });
+ });
+
+ it('should error on no match with an array of string verify "audience" option', function (done) {
+ verifyWithAudience(token, ["urn:no-match-1", "urn:no-match-2"], err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", `jwt audience invalid. expected: urn:no-match-1 or urn:no-match-2`);
+ });
+ });
+ });
+
+ it('should error on no match with a Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, /^urn:no-match$/, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", `jwt audience invalid. expected: /^urn:no-match$/`);
+ });
+ });
+ });
+
+ it('should error on no match with an array of Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, [/^urn:no-match-1$/, /^urn:no-match-2$/], err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty(
+ "message",
+ `jwt audience invalid. expected: /^urn:no-match-1$/ or /^urn:no-match-2$/`,
+ );
+ });
+ });
+ });
+
+ it('should error on no match with an array of a Regex and a string in verify "audience" option', function (done) {
+ verifyWithAudience(token, [/^urn:no-match$/, "urn:no-match"], err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", `jwt audience invalid. expected: /^urn:no-match$/ or urn:no-match`);
+ });
+ });
+ });
+ });
+
+ describe('with an array of ["urn:foo", "urn:bar"] for "aud" value in payload', function () {
+ let token;
+
+ beforeEach(function (done) {
+ signWithAudience(["urn:foo", "urn:bar"], {}, (err, t) => {
+ token = t;
+ done(err);
+ });
+ });
+
+ [
+ undefined,
+ "urn:foo",
+ /^urn:f[o]{2}$/,
+ ["urn:no_match", "urn:foo"],
+ ["urn:no_match", /^urn:f[o]{2}$/],
+ [/^urn:no_match$/, /^urn:f[o]{2}$/],
+ [/^urn:no_match$/, "urn:foo"],
+ ].forEach(audience => {
+ it(`should verify and decode with verify "audience" option of ${util.inspect(audience)}`, function (done) {
+ verifyWithAudience(token, audience, (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+ });
+
+ it(`should error on no match with a string verify "audience" option`, function (done) {
+ verifyWithAudience(token, "urn:no-match", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", `jwt audience invalid. expected: urn:no-match`);
+ });
+ });
+ });
+
+ it('should error on no match with an array of string verify "audience" option', function (done) {
+ verifyWithAudience(token, ["urn:no-match-1", "urn:no-match-2"], err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", `jwt audience invalid. expected: urn:no-match-1 or urn:no-match-2`);
+ });
+ });
+ });
+
+ it('should error on no match with a Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, /^urn:no-match$/, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", `jwt audience invalid. expected: /^urn:no-match$/`);
+ });
+ });
+ });
+
+ it('should error on no match with an array of Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, [/^urn:no-match-1$/, /^urn:no-match-2$/], err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty(
+ "message",
+ `jwt audience invalid. expected: /^urn:no-match-1$/ or /^urn:no-match-2$/`,
+ );
+ });
+ });
+ });
+
+ it('should error on no match with an array of a Regex and a string in verify "audience" option', function (done) {
+ verifyWithAudience(token, [/^urn:no-match$/, "urn:no-match"], err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", `jwt audience invalid. expected: /^urn:no-match$/ or urn:no-match`);
+ });
+ });
+ });
+
+ describe('when checking for a matching on both "urn:foo" and "urn:bar"', function () {
+ it('should verify with an array of stings verify "audience" option', function (done) {
+ verifyWithAudience(token, ["urn:foo", "urn:bar"], (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+
+ it('should verify with a Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, /^urn:[a-z]{3}$/, (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+
+ it('should verify with an array of Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, [/^urn:f[o]{2}$/, /^urn:b[ar]{2}$/], (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+ });
+
+ describe('when checking for a matching for "urn:foo"', function () {
+ it('should verify with a string verify "audience"', function (done) {
+ verifyWithAudience(token, "urn:foo", (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+
+ it('should verify with a Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, /^urn:f[o]{2}$/, (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+
+ it('should verify with an array of Regex verify "audience"', function (done) {
+ verifyWithAudience(token, [/^urn:no-match$/, /^urn:f[o]{2}$/], (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+
+ it('should verify with an array containing a string and a Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, ["urn:no_match", /^urn:f[o]{2}$/], (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+
+ it('should verify with an array containing a Regex and a string verify "audience" option', function (done) {
+ verifyWithAudience(token, [/^urn:no-match$/, "urn:foo"], (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+ });
+
+ describe('when checking matching for "urn:bar"', function () {
+ it('should verify with a string verify "audience"', function (done) {
+ verifyWithAudience(token, "urn:bar", (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+
+ it('should verify with a Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, /^urn:b[ar]{2}$/, (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+
+ it('should verify with an array of Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, [/^urn:no-match$/, /^urn:b[ar]{2}$/], (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+
+ it('should verify with an array containing a string and a Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, ["urn:no_match", /^urn:b[ar]{2}$/], (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+
+ it('should verify with an array containing a Regex and a string verify "audience" option', function (done) {
+ verifyWithAudience(token, [/^urn:no-match$/, "urn:bar"], (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("aud", ["urn:foo", "urn:bar"]);
+ });
+ });
+ });
+ });
+ });
+
+ describe('without a "aud" value in payload', function () {
+ let token;
+
+ beforeEach(function (done) {
+ signWithAudience(undefined, {}, (err, t) => {
+ token = t;
+ done(err);
+ });
+ });
+
+ it('should verify and decode without verify "audience" option', function (done) {
+ verifyWithAudience(token, undefined, (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).not.toHaveProperty("aud");
+ });
+ });
+ });
+
+ it('should error on no match with a string verify "audience" option', function (done) {
+ verifyWithAudience(token, "urn:no-match", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", "jwt audience invalid. expected: urn:no-match");
+ });
+ });
+ });
+
+ it('should error on no match with an array of string verify "audience" option', function (done) {
+ verifyWithAudience(token, ["urn:no-match-1", "urn:no-match-2"], err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", "jwt audience invalid. expected: urn:no-match-1 or urn:no-match-2");
+ });
+ });
+ });
+
+ it('should error on no match with a Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, /^urn:no-match$/, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", "jwt audience invalid. expected: /^urn:no-match$/");
+ });
+ });
+ });
+
+ it('should error on no match with an array of Regex verify "audience" option', function (done) {
+ verifyWithAudience(token, [/^urn:no-match-1$/, /^urn:no-match-2$/], err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty(
+ "message",
+ "jwt audience invalid. expected: /^urn:no-match-1$/ or /^urn:no-match-2$/",
+ );
+ });
+ });
+ });
+
+ it('should error on no match with an array of a Regex and a string in verify "audience" option', function (done) {
+ verifyWithAudience(token, [/^urn:no-match$/, "urn:no-match"], err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", "jwt audience invalid. expected: /^urn:no-match$/ or urn:no-match");
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/claim-exp.test.js b/test/js/third_party/jsonwebtoken/claim-exp.test.js
new file mode 100644
index 000000000..ee836a755
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/claim-exp.test.js
@@ -0,0 +1,316 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+import { expect, describe, it, beforeEach } from "bun:test";
+import util from "util";
+import testUtils from "./test-utils";
+import jws from "jws";
+import sinon from "sinon";
+
+function signWithExpiresIn(expiresIn, payload, callback) {
+ const options = { algorithm: "HS256" };
+ if (expiresIn !== undefined) {
+ options.expiresIn = expiresIn;
+ }
+ testUtils.signJWTHelper(payload, "secret", options, callback);
+}
+
+describe("expires", function () {
+ describe('`jwt.sign` "expiresIn" option validation', function () {
+ [
+ true,
+ false,
+ null,
+ -1.1,
+ 1.1,
+ -Infinity,
+ Infinity,
+ NaN,
+ " ",
+ "",
+ "invalid",
+ [],
+ ["foo"],
+ {},
+ { foo: "bar" },
+ ].forEach(expiresIn => {
+ it(`should error with with value ${util.inspect(expiresIn)}`, function (done) {
+ signWithExpiresIn(expiresIn, {}, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message");
+ });
+ });
+ });
+ });
+
+ // undefined needs special treatment because {} is not the same as {expiresIn: undefined}
+ it("should error with with value undefined", function (done) {
+ testUtils.signJWTHelper({}, "secret", { expiresIn: undefined, algorithm: "HS256" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty(
+ "message",
+ '"expiresIn" should be a number of seconds or string representing a timespan',
+ );
+ });
+ });
+ });
+
+ it('should error when "exp" is in payload', function (done) {
+ signWithExpiresIn(100, { exp: 100 }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty(
+ "message",
+ 'Bad "options.expiresIn" option the payload already has an "exp" property.',
+ );
+ });
+ });
+ });
+
+ it("should error with a string payload", function (done) {
+ signWithExpiresIn(100, "a string payload", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid expiresIn option for string payload");
+ });
+ });
+ });
+
+ it("should error with a Buffer payload", function (done) {
+ signWithExpiresIn(100, Buffer.from("a Buffer payload"), err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid expiresIn option for object payload");
+ });
+ });
+ });
+ });
+
+ describe('`jwt.sign` "exp" claim validation', function () {
+ [true, false, null, undefined, "", " ", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(exp => {
+ it(`should error with with value ${util.inspect(exp)}`, function (done) {
+ signWithExpiresIn(undefined, { exp }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"exp" should be a number of seconds');
+ });
+ });
+ });
+ });
+ });
+
+ describe('"exp" in payload validation', function () {
+ [true, false, null, -Infinity, Infinity, NaN, "", " ", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(exp => {
+ it(`should error with with value ${util.inspect(exp)}`, function (done) {
+ const header = { alg: "HS256" };
+ const payload = { exp };
+ const token = jws.sign({ header, payload, secret: "secret", encoding: "utf8" });
+ testUtils.verifyJWTHelper(token, "secret", { exp }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", "invalid exp value");
+ });
+ });
+ });
+ });
+ });
+
+ describe("when signing and verifying a token with expires option", function () {
+ let fakeClock;
+ beforeEach(function () {
+ fakeClock = sinon.useFakeTimers({ now: 60000 });
+ });
+
+ afterEach(function () {
+ fakeClock.uninstall();
+ });
+
+ it('should set correct "exp" with negative number of seconds', function (done) {
+ signWithExpiresIn(-10, {}, (e1, token) => {
+ fakeClock.tick(-10001);
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("exp", 50);
+ });
+ });
+ });
+ });
+
+ it('should set correct "exp" with positive number of seconds', function (done) {
+ signWithExpiresIn(10, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("exp", 70);
+ });
+ });
+ });
+ });
+
+ it('should set correct "exp" with zero seconds', function (done) {
+ signWithExpiresIn(0, {}, (e1, token) => {
+ fakeClock.tick(-1);
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("exp", 60);
+ });
+ });
+ });
+ });
+
+ it('should set correct "exp" with negative string timespan', function (done) {
+ signWithExpiresIn("-10 s", {}, (e1, token) => {
+ fakeClock.tick(-10001);
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("exp", 50);
+ });
+ });
+ });
+ });
+
+ it('should set correct "exp" with positive string timespan', function (done) {
+ signWithExpiresIn("10 s", {}, (e1, token) => {
+ fakeClock.tick(-10001);
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("exp", 70);
+ });
+ });
+ });
+ });
+
+ it('should set correct "exp" with zero string timespan', function (done) {
+ signWithExpiresIn("0 s", {}, (e1, token) => {
+ fakeClock.tick(-1);
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("exp", 60);
+ });
+ });
+ });
+ });
+
+ // TODO an exp of -Infinity should fail validation
+ it('should set null "exp" when given -Infinity', function (done) {
+ signWithExpiresIn(undefined, { exp: -Infinity }, (err, token) => {
+ const decoded = jwt.decode(token);
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("exp", null);
+ });
+ });
+ });
+
+ // TODO an exp of Infinity should fail validation
+ it('should set null "exp" when given value Infinity', function (done) {
+ signWithExpiresIn(undefined, { exp: Infinity }, (err, token) => {
+ const decoded = jwt.decode(token);
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("exp", null);
+ });
+ });
+ });
+
+ // TODO an exp of NaN should fail validation
+ it('should set null "exp" when given value NaN', function (done) {
+ signWithExpiresIn(undefined, { exp: NaN }, (err, token) => {
+ const decoded = jwt.decode(token);
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("exp", null);
+ });
+ });
+ });
+
+ it('should set correct "exp" when "iat" is passed', function (done) {
+ signWithExpiresIn(-10, { iat: 80 }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("exp", 70);
+ });
+ });
+ });
+ });
+
+ it('should verify "exp" using "clockTimestamp"', function (done) {
+ signWithExpiresIn(10, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { clockTimestamp: 69 }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iat", 60);
+ expect(decoded).toHaveProperty("exp", 70);
+ });
+ });
+ });
+ });
+
+ it('should verify "exp" using "clockTolerance"', function (done) {
+ signWithExpiresIn(5, {}, (e1, token) => {
+ fakeClock.tick(10000);
+ testUtils.verifyJWTHelper(token, "secret", { clockTimestamp: 6 }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iat", 60);
+ expect(decoded).toHaveProperty("exp", 65);
+ });
+ });
+ });
+ });
+
+ it('should ignore a expired token when "ignoreExpiration" is true', function (done) {
+ signWithExpiresIn("-10 s", {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { ignoreExpiration: true }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iat", 60);
+ expect(decoded).toHaveProperty("exp", 50);
+ });
+ });
+ });
+ });
+
+ it('should error on verify if "exp" is at current time', function (done) {
+ signWithExpiresIn(undefined, { exp: 60 }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.TokenExpiredError);
+ expect(e2).toHaveProperty("message", "jwt expired");
+ });
+ });
+ });
+ });
+
+ it('should error on verify if "exp" is before current time using clockTolerance', function (done) {
+ signWithExpiresIn(-5, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { clockTolerance: 5 }, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.TokenExpiredError);
+ expect(e2).toHaveProperty("message", "jwt expired");
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/claim-iat.test.js b/test/js/third_party/jsonwebtoken/claim-iat.test.js
new file mode 100644
index 000000000..6d72a58f6
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/claim-iat.test.js
@@ -0,0 +1,254 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+import { expect, describe, it, beforeEach } from "bun:test";
+import util from "util";
+import testUtils from "./test-utils";
+import jws from "jws";
+import sinon from "sinon";
+
+function signWithIssueAt(issueAt, options, callback) {
+ const payload = {};
+ if (issueAt !== undefined) {
+ payload.iat = issueAt;
+ }
+ const opts = Object.assign({ algorithm: "HS256" }, options);
+ // async calls require a truthy secret
+ // see: https://github.com/brianloveswords/node-jws/issues/62
+ testUtils.signJWTHelper(payload, "secret", opts, callback);
+}
+
+function verifyWithIssueAt(token, maxAge, options, secret, callback) {
+ const opts = Object.assign({ maxAge }, options);
+ testUtils.verifyJWTHelper(token, secret, opts, callback);
+}
+
+describe("issue at", function () {
+ describe('`jwt.sign` "iat" claim validation', function () {
+ [true, false, null, "", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(iat => {
+ it(`should error with iat of ${util.inspect(iat)}`, function (done) {
+ signWithIssueAt(iat, {}, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err.message).toEqual('"iat" should be a number of seconds');
+ });
+ });
+ });
+ });
+
+ // undefined needs special treatment because {} is not the same as {iat: undefined}
+ it("should error with iat of undefined", function (done) {
+ testUtils.signJWTHelper({ iat: undefined }, "secret", { algorithm: "HS256" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err.message).toEqual('"iat" should be a number of seconds');
+ });
+ });
+ });
+ });
+
+ describe('"iat" in payload with "maxAge" option validation', function () {
+ [true, false, null, undefined, -Infinity, Infinity, NaN, "", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(
+ iat => {
+ it(`should error with iat of ${util.inspect(iat)}`, function (done) {
+ const header = { alg: "HS256" };
+ const payload = { iat };
+ const token = jws.sign({ header, payload, secret: "secret", encoding: "utf8" });
+ verifyWithIssueAt(token, "1 min", {}, "secret", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err.message).toEqual("iat required when maxAge is specified");
+ });
+ });
+ });
+ },
+ );
+ });
+
+ describe("when signing a token", function () {
+ let fakeClock;
+ beforeEach(function () {
+ fakeClock = sinon.useFakeTimers({ now: 60000 });
+ });
+
+ afterEach(function () {
+ fakeClock.uninstall();
+ });
+
+ [
+ {
+ description: 'should default to current time for "iat"',
+ iat: undefined,
+ expectedIssueAt: 60,
+ options: {},
+ },
+ {
+ description: 'should sign with provided time for "iat"',
+ iat: 100,
+ expectedIssueAt: 100,
+ options: {},
+ },
+ // TODO an iat of -Infinity should fail validation
+ {
+ description: 'should set null "iat" when given -Infinity',
+ iat: -Infinity,
+ expectedIssueAt: null,
+ options: {},
+ },
+ // TODO an iat of Infinity should fail validation
+ {
+ description: 'should set null "iat" when given Infinity',
+ iat: Infinity,
+ expectedIssueAt: null,
+ options: {},
+ },
+ // TODO an iat of NaN should fail validation
+ {
+ description: 'should set to current time for "iat" when given value NaN',
+ iat: NaN,
+ expectedIssueAt: 60,
+ options: {},
+ },
+ {
+ description: 'should remove default "iat" with "noTimestamp" option',
+ iat: undefined,
+ expectedIssueAt: undefined,
+ options: { noTimestamp: true },
+ },
+ {
+ description: 'should remove provided "iat" with "noTimestamp" option',
+ iat: 10,
+ expectedIssueAt: undefined,
+ options: { noTimestamp: true },
+ },
+ ].forEach(testCase => {
+ it(testCase.description, function (done) {
+ signWithIssueAt(testCase.iat, testCase.options, (err, token) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(jwt.decode(token).iat).toEqual(testCase.expectedIssueAt);
+ });
+ });
+ });
+ });
+ });
+
+ describe("when verifying a token", function () {
+ let fakeClock;
+
+ beforeEach(function () {
+ fakeClock = sinon.useFakeTimers({ now: 60000 });
+ });
+
+ afterEach(function () {
+ fakeClock.uninstall();
+ });
+
+ [
+ {
+ description: 'should verify using "iat" before the "maxAge"',
+ clockAdvance: 10000,
+ maxAge: 11,
+ options: {},
+ },
+ {
+ description: 'should verify using "iat" before the "maxAge" with a provided "clockTimestamp',
+ clockAdvance: 60000,
+ maxAge: 11,
+ options: { clockTimestamp: 70 },
+ },
+ {
+ description: 'should verify using "iat" after the "maxAge" but within "clockTolerance"',
+ clockAdvance: 10000,
+ maxAge: 9,
+ options: { clockTimestamp: 2 },
+ },
+ ].forEach(testCase => {
+ it(testCase.description, function (done) {
+ const token = jwt.sign({}, "secret", { algorithm: "HS256" });
+ fakeClock.tick(testCase.clockAdvance);
+ verifyWithIssueAt(token, testCase.maxAge, testCase.options, "secret", (err, token) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(typeof token).toBe("object");
+ });
+ });
+ });
+ });
+
+ [
+ {
+ description: 'should throw using "iat" equal to the "maxAge"',
+ clockAdvance: 10000,
+ maxAge: 10,
+ options: {},
+ expectedError: "maxAge exceeded",
+ expectedExpiresAt: 70000,
+ },
+ {
+ description: 'should throw using "iat" after the "maxAge"',
+ clockAdvance: 10000,
+ maxAge: 9,
+ options: {},
+ expectedError: "maxAge exceeded",
+ expectedExpiresAt: 69000,
+ },
+ {
+ description: 'should throw using "iat" after the "maxAge" with a provided "clockTimestamp',
+ clockAdvance: 60000,
+ maxAge: 10,
+ options: { clockTimestamp: 70 },
+ expectedError: "maxAge exceeded",
+ expectedExpiresAt: 70000,
+ },
+ {
+ description: 'should throw using "iat" after the "maxAge" and "clockTolerance',
+ clockAdvance: 10000,
+ maxAge: 8,
+ options: { clockTolerance: 2 },
+ expectedError: "maxAge exceeded",
+ expectedExpiresAt: 68000,
+ },
+ ].forEach(testCase => {
+ it(testCase.description, function (done) {
+ const expectedExpiresAtDate = new Date(testCase.expectedExpiresAt);
+ const token = jwt.sign({}, "secret", { algorithm: "HS256" });
+ fakeClock.tick(testCase.clockAdvance);
+
+ verifyWithIssueAt(token, testCase.maxAge, testCase.options, "secret", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err.message).toEqual(testCase.expectedError);
+ expect(err.expiredAt).toStrictEqual(expectedExpiresAtDate);
+ });
+ });
+ });
+ });
+ });
+
+ describe("with string payload", function () {
+ it("should not add iat to string", function (done) {
+ const payload = "string payload";
+ const options = { algorithm: "HS256" };
+ testUtils.signJWTHelper(payload, "secret", options, (err, token) => {
+ const decoded = jwt.decode(token);
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toEqual(payload);
+ });
+ });
+ });
+
+ it("should not add iat to stringified object", function (done) {
+ const payload = "{}";
+ const options = { algorithm: "HS256", header: { typ: "JWT" } };
+ testUtils.signJWTHelper(payload, "secret", options, (err, token) => {
+ const decoded = jwt.decode(token);
+ testUtils.asyncCheck(done, () => {
+ expect(err).toEqual(null);
+ expect(JSON.stringify(decoded)).toEqual(payload);
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/claim-iss.test.js b/test/js/third_party/jsonwebtoken/claim-iss.test.js
new file mode 100644
index 000000000..3b2e9dacf
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/claim-iss.test.js
@@ -0,0 +1,185 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+import util from "util";
+import testUtils from "./test-utils";
+
+function signWithIssuer(issuer, payload, callback) {
+ const options = { algorithm: "HS256" };
+ if (issuer !== undefined) {
+ options.issuer = issuer;
+ }
+ testUtils.signJWTHelper(payload, "secret", options, callback);
+}
+
+describe("issuer", function () {
+ describe('`jwt.sign` "issuer" option validation', function () {
+ [true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ["foo"], {}, { foo: "bar" }].forEach(
+ issuer => {
+ it(`should error with with value ${util.inspect(issuer)}`, function (done) {
+ signWithIssuer(issuer, {}, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"issuer" must be a string');
+ });
+ });
+ });
+ },
+ );
+
+ // undefined needs special treatment because {} is not the same as {issuer: undefined}
+ it("should error with with value undefined", function (done) {
+ testUtils.signJWTHelper({}, "secret", { issuer: undefined, algorithm: "HS256" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"issuer" must be a string');
+ });
+ });
+ });
+
+ it('should error when "iss" is in payload', function (done) {
+ signWithIssuer("foo", { iss: "bar" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty(
+ "message",
+ 'Bad "options.issuer" option. The payload already has an "iss" property.',
+ );
+ });
+ });
+ });
+
+ it("should error with a string payload", function (done) {
+ signWithIssuer("foo", "a string payload", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid issuer option for string payload");
+ });
+ });
+ });
+
+ it("should error with a Buffer payload", function (done) {
+ signWithIssuer("foo", new Buffer("a Buffer payload"), err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid issuer option for object payload");
+ });
+ });
+ });
+ });
+
+ describe("when signing and verifying a token", function () {
+ it('should not verify "iss" if verify "issuer" option not provided', function (done) {
+ signWithIssuer(undefined, { iss: "foo" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iss", "foo");
+ });
+ });
+ });
+ });
+
+ describe('with string "issuer" option', function () {
+ it('should verify with a string "issuer"', function (done) {
+ signWithIssuer("foo", {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { issuer: "foo" }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iss", "foo");
+ });
+ });
+ });
+ });
+
+ it('should verify with a string "iss"', function (done) {
+ signWithIssuer(undefined, { iss: "foo" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { issuer: "foo" }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iss", "foo");
+ });
+ });
+ });
+ });
+
+ it('should error if "iss" does not match verify "issuer" option', function (done) {
+ signWithIssuer(undefined, { iss: "foobar" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { issuer: "foo" }, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(e2).toHaveProperty("message", "jwt issuer invalid. expected: foo");
+ });
+ });
+ });
+ });
+
+ it('should error without "iss" and with verify "issuer" option', function (done) {
+ signWithIssuer(undefined, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { issuer: "foo" }, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(e2).toHaveProperty("message", "jwt issuer invalid. expected: foo");
+ });
+ });
+ });
+ });
+ });
+
+ describe('with array "issuer" option', function () {
+ it('should verify with a string "issuer"', function (done) {
+ signWithIssuer("bar", {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { issuer: ["foo", "bar"] }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iss", "bar");
+ });
+ });
+ });
+ });
+
+ it('should verify with a string "iss"', function (done) {
+ signWithIssuer(undefined, { iss: "foo" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { issuer: ["foo", "bar"] }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iss", "foo");
+ });
+ });
+ });
+ });
+
+ it('should error if "iss" does not match verify "issuer" option', function (done) {
+ signWithIssuer(undefined, { iss: "foobar" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { issuer: ["foo", "bar"] }, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(e2).toHaveProperty("message", "jwt issuer invalid. expected: foo,bar");
+ });
+ });
+ });
+ });
+
+ it('should error without "iss" and with verify "issuer" option', function (done) {
+ signWithIssuer(undefined, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { issuer: ["foo", "bar"] }, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(e2).toHaveProperty("message", "jwt issuer invalid. expected: foo,bar");
+ });
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/claim-jti.test.js b/test/js/third_party/jsonwebtoken/claim-jti.test.js
new file mode 100644
index 000000000..18aa15df8
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/claim-jti.test.js
@@ -0,0 +1,135 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+import util from "util";
+import testUtils from "./test-utils";
+
+function signWithJWTId(jwtid, payload, callback) {
+ const options = { algorithm: "HS256" };
+ if (jwtid !== undefined) {
+ options.jwtid = jwtid;
+ }
+ testUtils.signJWTHelper(payload, "secret", options, callback);
+}
+
+describe("jwtid", function () {
+ describe('`jwt.sign` "jwtid" option validation', function () {
+ [true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ["foo"], {}, { foo: "bar" }].forEach(
+ jwtid => {
+ it(`should error with with value ${util.inspect(jwtid)}`, function (done) {
+ signWithJWTId(jwtid, {}, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"jwtid" must be a string');
+ });
+ });
+ });
+ },
+ );
+
+ // undefined needs special treatment because {} is not the same as {jwtid: undefined}
+ it("should error with with value undefined", function (done) {
+ testUtils.signJWTHelper({}, "secret", { jwtid: undefined, algorithm: "HS256" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"jwtid" must be a string');
+ });
+ });
+ });
+
+ it('should error when "jti" is in payload', function (done) {
+ signWithJWTId("foo", { jti: "bar" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty(
+ "message",
+ 'Bad "options.jwtid" option. The payload already has an "jti" property.',
+ );
+ });
+ });
+ });
+
+ it("should error with a string payload", function (done) {
+ signWithJWTId("foo", "a string payload", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid jwtid option for string payload");
+ });
+ });
+ });
+
+ it("should error with a Buffer payload", function (done) {
+ signWithJWTId("foo", new Buffer("a Buffer payload"), err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid jwtid option for object payload");
+ });
+ });
+ });
+ });
+
+ describe("when signing and verifying a token", function () {
+ it('should not verify "jti" if verify "jwtid" option not provided', function (done) {
+ signWithJWTId(undefined, { jti: "foo" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("jti", "foo");
+ });
+ });
+ });
+ });
+
+ describe('with "jwtid" option', function () {
+ it('should verify with "jwtid" option', function (done) {
+ signWithJWTId("foo", {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { jwtid: "foo" }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("jti", "foo");
+ });
+ });
+ });
+ });
+
+ it('should verify with "jti" in payload', function (done) {
+ signWithJWTId(undefined, { jti: "foo" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { jetid: "foo" }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("jti", "foo");
+ });
+ });
+ });
+ });
+
+ it('should error if "jti" does not match verify "jwtid" option', function (done) {
+ signWithJWTId(undefined, { jti: "bar" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { jwtid: "foo" }, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(e2).toHaveProperty("message", "jwt jwtid invalid. expected: foo");
+ });
+ });
+ });
+ });
+
+ it('should error without "jti" and with verify "jwtid" option', function (done) {
+ signWithJWTId(undefined, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { jwtid: "foo" }, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(e2).toHaveProperty("message", "jwt jwtid invalid. expected: foo");
+ });
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/claim-nbf.test.js b/test/js/third_party/jsonwebtoken/claim-nbf.test.js
new file mode 100644
index 000000000..9c2e54c5c
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/claim-nbf.test.js
@@ -0,0 +1,312 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+import { expect, describe, it, beforeEach } from "bun:test";
+import util from "util";
+import testUtils from "./test-utils";
+import jws from "jws";
+import sinon from "sinon";
+
+function signWithNotBefore(notBefore, payload, callback) {
+ const options = { algorithm: "HS256" };
+ if (notBefore !== undefined) {
+ options.notBefore = notBefore;
+ }
+ testUtils.signJWTHelper(payload, "secret", options, callback);
+}
+
+describe("not before", function () {
+ describe('`jwt.sign` "notBefore" option validation', function () {
+ [
+ true,
+ false,
+ null,
+ -1.1,
+ 1.1,
+ -Infinity,
+ Infinity,
+ NaN,
+ "",
+ " ",
+ "invalid",
+ [],
+ ["foo"],
+ {},
+ { foo: "bar" },
+ ].forEach(notBefore => {
+ it(`should error with with value ${util.inspect(notBefore)}`, function (done) {
+ signWithNotBefore(notBefore, {}, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message");
+ });
+ });
+ });
+ });
+
+ // undefined needs special treatment because {} is not the same as {notBefore: undefined}
+ it("should error with with value undefined", function (done) {
+ testUtils.signJWTHelper({}, "secret", { notBefore: undefined, algorithm: "HS256" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty(
+ "message",
+ '"notBefore" should be a number of seconds or string representing a timespan',
+ );
+ });
+ });
+ });
+
+ it('should error when "nbf" is in payload', function (done) {
+ signWithNotBefore(100, { nbf: 100 }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty(
+ "message",
+ 'Bad "options.notBefore" option the payload already has an "nbf" property.',
+ );
+ });
+ });
+ });
+
+ it("should error with a string payload", function (done) {
+ signWithNotBefore(100, "a string payload", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid notBefore option for string payload");
+ });
+ });
+ });
+
+ it("should error with a Buffer payload", function (done) {
+ signWithNotBefore(100, new Buffer("a Buffer payload"), err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid notBefore option for object payload");
+ });
+ });
+ });
+ });
+
+ describe('`jwt.sign` "nbf" claim validation', function () {
+ [true, false, null, undefined, "", " ", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(nbf => {
+ it(`should error with with value ${util.inspect(nbf)}`, function (done) {
+ signWithNotBefore(undefined, { nbf }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"nbf" should be a number of seconds');
+ });
+ });
+ });
+ });
+ });
+
+ describe('"nbf" in payload validation', function () {
+ [true, false, null, -Infinity, Infinity, NaN, "", " ", "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(nbf => {
+ it(`should error with with value ${util.inspect(nbf)}`, function (done) {
+ const header = { alg: "HS256" };
+ const payload = { nbf };
+ const token = jws.sign({ header, payload, secret: "secret", encoding: "utf8" });
+ testUtils.verifyJWTHelper(token, "secret", { nbf }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", "invalid nbf value");
+ });
+ });
+ });
+ });
+ });
+
+ describe('when signing and verifying a token with "notBefore" option', function () {
+ let fakeClock;
+ beforeEach(function () {
+ fakeClock = sinon.useFakeTimers({ now: 60000 });
+ });
+
+ afterEach(function () {
+ fakeClock.uninstall();
+ });
+
+ it('should set correct "nbf" with negative number of seconds', function (done) {
+ signWithNotBefore(-10, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("nbf", 50);
+ });
+ });
+ });
+ });
+
+ it('should set correct "nbf" with positive number of seconds', function (done) {
+ signWithNotBefore(10, {}, (e1, token) => {
+ fakeClock.tick(10000);
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("nbf", 70);
+ });
+ });
+ });
+ });
+
+ it('should set correct "nbf" with zero seconds', function (done) {
+ signWithNotBefore(0, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("nbf", 60);
+ });
+ });
+ });
+ });
+
+ it('should set correct "nbf" with negative string timespan', function (done) {
+ signWithNotBefore("-10 s", {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("nbf", 50);
+ });
+ });
+ });
+ });
+
+ it('should set correct "nbf" with positive string timespan', function (done) {
+ signWithNotBefore("10 s", {}, (e1, token) => {
+ fakeClock.tick(10000);
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("nbf", 70);
+ });
+ });
+ });
+ });
+
+ it('should set correct "nbf" with zero string timespan', function (done) {
+ signWithNotBefore("0 s", {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("nbf", 60);
+ });
+ });
+ });
+ });
+
+ // TODO an nbf of -Infinity should fail validation
+ it('should set null "nbf" when given -Infinity', function (done) {
+ signWithNotBefore(undefined, { nbf: -Infinity }, (err, token) => {
+ const decoded = jwt.decode(token);
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("nbf", null);
+ });
+ });
+ });
+
+ // TODO an nbf of Infinity should fail validation
+ it('should set null "nbf" when given value Infinity', function (done) {
+ signWithNotBefore(undefined, { nbf: Infinity }, (err, token) => {
+ const decoded = jwt.decode(token);
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("nbf", null);
+ });
+ });
+ });
+
+ // TODO an nbf of NaN should fail validation
+ it('should set null "nbf" when given value NaN', function (done) {
+ signWithNotBefore(undefined, { nbf: NaN }, (err, token) => {
+ const decoded = jwt.decode(token);
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("nbf", null);
+ });
+ });
+ });
+
+ it('should set correct "nbf" when "iat" is passed', function (done) {
+ signWithNotBefore(-10, { iat: 40 }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("nbf", 30);
+ });
+ });
+ });
+ });
+
+ it('should verify "nbf" using "clockTimestamp"', function (done) {
+ signWithNotBefore(10, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { clockTimestamp: 70 }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iat", 60);
+ expect(decoded).toHaveProperty("nbf", 70);
+ });
+ });
+ });
+ });
+
+ it('should verify "nbf" using "clockTolerance"', function (done) {
+ signWithNotBefore(5, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { clockTolerance: 6 }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iat", 60);
+ expect(decoded).toHaveProperty("nbf", 65);
+ });
+ });
+ });
+ });
+
+ it('should ignore a not active token when "ignoreNotBefore" is true', function (done) {
+ signWithNotBefore("10 s", {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { ignoreNotBefore: true }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("iat", 60);
+ expect(decoded).toHaveProperty("nbf", 70);
+ });
+ });
+ });
+ });
+
+ it('should error on verify if "nbf" is after current time', function (done) {
+ signWithNotBefore(undefined, { nbf: 61 }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.NotBeforeError);
+ expect(e2).toHaveProperty("message", "jwt not active");
+ });
+ });
+ });
+ });
+
+ it('should error on verify if "nbf" is after current time using clockTolerance', function (done) {
+ signWithNotBefore(5, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { clockTolerance: 4 }, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.NotBeforeError);
+ expect(e2).toHaveProperty("message", "jwt not active");
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/claim-private.test.js b/test/js/third_party/jsonwebtoken/claim-private.test.js
new file mode 100644
index 000000000..51c56edb2
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/claim-private.test.js
@@ -0,0 +1,55 @@
+"use strict";
+
+import { expect, describe, it } from "bun:test";
+import util from "util";
+import testUtils from "./test-utils";
+
+function signWithPayload(payload, callback) {
+ testUtils.signJWTHelper(payload, "secret", { algorithm: "HS256" }, callback);
+}
+
+describe("with a private claim", function () {
+ [true, false, null, -1, 0, 1, -1.1, 1.1, "", "private claim", "UTF8 - José", [], ["foo"], {}, { foo: "bar" }].forEach(
+ privateClaim => {
+ it(`should sign and verify with claim of ${util.inspect(privateClaim)}`, function (done) {
+ signWithPayload({ privateClaim }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("privateClaim", privateClaim);
+ });
+ });
+ });
+ });
+ },
+ );
+
+ // these values JSON.stringify to null
+ [-Infinity, Infinity, NaN].forEach(privateClaim => {
+ it(`should sign and verify with claim of ${util.inspect(privateClaim)}`, function (done) {
+ signWithPayload({ privateClaim }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("privateClaim", null);
+ });
+ });
+ });
+ });
+ });
+
+ // private claims with value undefined are not added to the payload
+ it(`should sign and verify with claim of undefined`, function (done) {
+ signWithPayload({ privateClaim: undefined }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).not.toHaveProperty("privateClaim");
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/claim-sub.test.js b/test/js/third_party/jsonwebtoken/claim-sub.test.js
new file mode 100644
index 000000000..6846a688d
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/claim-sub.test.js
@@ -0,0 +1,133 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+import util from "util";
+import testUtils from "./test-utils";
+
+function signWithSubject(subject, payload, callback) {
+ const options = { algorithm: "HS256" };
+ if (subject !== undefined) {
+ options.subject = subject;
+ }
+ testUtils.signJWTHelper(payload, "secret", options, callback);
+}
+
+describe("subject", function () {
+ describe('`jwt.sign` "subject" option validation', function () {
+ [true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ["foo"], {}, { foo: "bar" }].forEach(
+ subject => {
+ it(`should error with with value ${util.inspect(subject)}`, function (done) {
+ signWithSubject(subject, {}, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"subject" must be a string');
+ });
+ });
+ });
+ },
+ );
+
+ // undefined needs special treatment because {} is not the same as {subject: undefined}
+ it("should error with with value undefined", function (done) {
+ testUtils.signJWTHelper({}, "secret", { subject: undefined, algorithm: "HS256" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"subject" must be a string');
+ });
+ });
+ });
+
+ it('should error when "sub" is in payload', function (done) {
+ signWithSubject("foo", { sub: "bar" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty(
+ "message",
+ 'Bad "options.subject" option. The payload already has an "sub" property.',
+ );
+ });
+ });
+ });
+
+ it("should error with a string payload", function (done) {
+ signWithSubject("foo", "a string payload", err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid subject option for string payload");
+ });
+ });
+ });
+
+ it("should error with a Buffer payload", function (done) {
+ signWithSubject("foo", new Buffer("a Buffer payload"), err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", "invalid subject option for object payload");
+ });
+ });
+ });
+ });
+
+ describe('when signing and verifying a token with "subject" option', function () {
+ it('should verify with a string "subject"', function (done) {
+ signWithSubject("foo", {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { subject: "foo" }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("sub", "foo");
+ });
+ });
+ });
+ });
+
+ it('should verify with a string "sub"', function (done) {
+ signWithSubject(undefined, { sub: "foo" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { subject: "foo" }, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("sub", "foo");
+ });
+ });
+ });
+ });
+
+ it('should not verify "sub" if verify "subject" option not provided', function (done) {
+ signWithSubject(undefined, { sub: "foo" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", {}, (e2, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeNull();
+ expect(decoded).toHaveProperty("sub", "foo");
+ });
+ });
+ });
+ });
+
+ it('should error if "sub" does not match verify "subject" option', function (done) {
+ signWithSubject(undefined, { sub: "foo" }, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { subject: "bar" }, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(e2).toHaveProperty("message", "jwt subject invalid. expected: bar");
+ });
+ });
+ });
+ });
+
+ it('should error without "sub" and with verify "subject" option', function (done) {
+ signWithSubject(undefined, {}, (e1, token) => {
+ testUtils.verifyJWTHelper(token, "secret", { subject: "foo" }, e2 => {
+ testUtils.asyncCheck(done, () => {
+ expect(e1).toBeNull();
+ expect(e2).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(e2).toHaveProperty("message", "jwt subject invalid. expected: foo");
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/decoding.test.js b/test/js/third_party/jsonwebtoken/decoding.test.js
new file mode 100644
index 000000000..617c7f295
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/decoding.test.js
@@ -0,0 +1,9 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+
+describe("decoding", function () {
+ it("should not crash when decoding a null token", function () {
+ var decoded = jwt.decode("null");
+ expect(decoded).toEqual(null);
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/dsa-private.pem b/test/js/third_party/jsonwebtoken/dsa-private.pem
new file mode 100644
index 000000000..e73003a12
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/dsa-private.pem
@@ -0,0 +1,36 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIGWAIBAAKCAgEArzbPbt//BQpsYsnoZR4R9nXgcuvcXoH8WZjRsb4ZPfVJGchG
+7CfRMlG0HR34vcUpehNj5pAavErhfNnk1CEal0TyDsOkBY/+JG239zXgRzMYjSE6
+ptX5kj5pGv0uXVoozSP/JZblI8/Spd6TZkblLNAYOl3ssfcUGN4NFDXlzmiWvP+q
+6ZUgE8tD7CSryicICKmXcVQIa6AG8ultYa6mBAaewzMbiIt2TUo9smglpEqGeHoL
+CuLb3e7zLf0AhWDZOgTTfe1KFEiK6TXMe9HWYeP3MPuyKhS20GmT/Zcu5VN4wbr0
+bP+mTWk700oLJ0OPQ6YgGkyqBmh/Bsi/TqnpJWS/mjRbJEe3E2NmNMwmP4jwJ79V
+JClp5Gg9kbM6hPkmGNnhbbFzn3kwY3pi9/AiqpGyr3GUPhXvP7fYwAu/A5ISKw8r
+87j/EJntyIzm51fcm8Q0mq1IDt4tNkIOwJEIc45h9r7ZC1VAKkzlCa7XT04GguFo
+JMaJBYESYcOAmbKRojo8P/cN4fPuemuhQFQplkFIM6FtG9cJMo2ayp6ukH9Up8tn
+8j7YgE/m9BL9SnUIbNlti9j0cNgeKVn24WC38hw9D8M0/sR5gYyclWh/OotCttoQ
+I8ySZzSvB4GARZHbexagvg1EdV93ctYyAWGLkpJYAzuiXbt7FayG7e2ifYkCIQDp
+IldsAFGVaiJRQdiKsWdReOSjzH6h8cw6Co3OCISiOQKCAgEAnSU29U65jK3W2BiA
+fKTlTBx2yDUCDFeqnla5arZ2njGsUKiP2nocArAPLQggwk9rfqufybQltM8+zjmE
+zeb4mUCVhSbTH7BvP903U0YEabZJCHLx80nTywq2RgQs0Qmn43vs2U5EidYR0xj8
+CCNAH5gdzd9/CL1RYACHAf7zj4n68ZaNkAy9Jz1JjYXjP6IAxJh1W/Y0vsdFdIJ/
+dnuxsyMCUCSwDvSNApSfATO/tw+DCVpGgKo4qE8b8lsfXKeihuMzyXuSe/D98YN2
+UFWRTQ6gFxGrntg3LOn41RXSkXxzixgl7quacIJzm8jrFkDJSx4AZ8rgt/9JbThA
+XF9PVlCVv7GL1NztUs4cDK+zsJld4O1rlI3QOz5DWq9oA+Hj1MN3L9IW3Iv2Offo
+AaubXJhuv0xPWYmtCo06mPgSwkWPjDnGCbp1vuI8zPTsfyhsahuKeW0h8JttW4GB
+6CTtC1AVWA1pJug5pBo36S5G24ihRsdG3Q5/aTlnke7t7H1Tkh2KuvV9hD5a5Xtw
+cnuiEcKjyR0FWR81RdsAKh+7QNI3Lx75c95i22Aupon5R/Qkb05VzHdd299bb78c
+x5mW8Dsg4tKLF7kpDAcWmx7JpkPHQ+5V9N766sfZ+z/PiVWfNAK8gzJRn/ceLQcK
+C6uOhcZgN0o4UYrmYEy9icxJ44wCggIBAIu+yagyVMS+C5OqOprmtteh/+MyaYI+
+Q3oPXFR8eHLJftsBWev1kRfje1fdxzzx/k4SQMRbxxbMtGV74KNwRUzEWOkoyAHP
+AAjhMio1mxknPwAxRjWDOSE0drGJPyGpI9ZfpMUtvekQO7MCGqa45vPldY10RwZC
+VN66AIpxSF0MG1OEmgD+noHMI7moclw/nw+ZUPaIFxvPstlD4EsPDkdE0I6x3k3b
+UXlWAYAJFR6fNf8+Ki3xnjLjW9da3cU/p2H7+LrFDP+kPUGJpqr4bG606GUcV3Cl
+dznoqlgaudWgcQCQx0NPzi7k5O7PXr7C3UU0cg+5+GkviIzogaioxidvvchnG+UU
+0y5nVuji6G69j5sUhlcFXte31Nte2VUb6P8umo+mbDT0UkZZZzoOsCpw+cJ8OHOV
+emFIhVphNHqQt20Tq6WVRBx+p4+YNWiThvmLtmLh0QghdnUrJZxyXx7/p8K5SE9/
++qU11t5dUvYS+53U1gJ2kgIFO4Zt6gaoOyexTt5f4Ganh9IcJ01wegl5WT58aDtf
+hmw0HnOrgbWt4lRkxOra281hL74xcgtgMZQ32PTOy8wTEVTk03mmqlIq/dV4jgBc
+Nh1FGQwGEeGlfbuNSB4nqgMN6zn1PmI7oCWLD9XLR6VZTebF7pGfpHtYczyivuxf
+e1YOro6e0mUqAiEAx4K3cPG3dxH91uU3L+sS2vzqXEVn2BmSMmkGczSOgn4=
+-----END DSA PRIVATE KEY-----
diff --git a/test/js/third_party/jsonwebtoken/dsa-public.pem b/test/js/third_party/jsonwebtoken/dsa-public.pem
new file mode 100644
index 000000000..659d96b79
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/dsa-public.pem
@@ -0,0 +1,36 @@
+-----BEGIN PUBLIC KEY-----
+MIIGSDCCBDoGByqGSM44BAEwggQtAoICAQCvNs9u3/8FCmxiyehlHhH2deBy69xe
+gfxZmNGxvhk99UkZyEbsJ9EyUbQdHfi9xSl6E2PmkBq8SuF82eTUIRqXRPIOw6QF
+j/4kbbf3NeBHMxiNITqm1fmSPmka/S5dWijNI/8lluUjz9Kl3pNmRuUs0Bg6Xeyx
+9xQY3g0UNeXOaJa8/6rplSATy0PsJKvKJwgIqZdxVAhroAby6W1hrqYEBp7DMxuI
+i3ZNSj2yaCWkSoZ4egsK4tvd7vMt/QCFYNk6BNN97UoUSIrpNcx70dZh4/cw+7Iq
+FLbQaZP9ly7lU3jBuvRs/6ZNaTvTSgsnQ49DpiAaTKoGaH8GyL9OqeklZL+aNFsk
+R7cTY2Y0zCY/iPAnv1UkKWnkaD2RszqE+SYY2eFtsXOfeTBjemL38CKqkbKvcZQ+
+Fe8/t9jAC78DkhIrDyvzuP8Qme3IjObnV9ybxDSarUgO3i02Qg7AkQhzjmH2vtkL
+VUAqTOUJrtdPTgaC4WgkxokFgRJhw4CZspGiOjw/9w3h8+56a6FAVCmWQUgzoW0b
+1wkyjZrKnq6Qf1Sny2fyPtiAT+b0Ev1KdQhs2W2L2PRw2B4pWfbhYLfyHD0PwzT+
+xHmBjJyVaH86i0K22hAjzJJnNK8HgYBFkdt7FqC+DUR1X3dy1jIBYYuSklgDO6Jd
+u3sVrIbt7aJ9iQIhAOkiV2wAUZVqIlFB2IqxZ1F45KPMfqHxzDoKjc4IhKI5AoIC
+AQCdJTb1TrmMrdbYGIB8pOVMHHbINQIMV6qeVrlqtnaeMaxQqI/aehwCsA8tCCDC
+T2t+q5/JtCW0zz7OOYTN5viZQJWFJtMfsG8/3TdTRgRptkkIcvHzSdPLCrZGBCzR
+Cafje+zZTkSJ1hHTGPwII0AfmB3N338IvVFgAIcB/vOPifrxlo2QDL0nPUmNheM/
+ogDEmHVb9jS+x0V0gn92e7GzIwJQJLAO9I0ClJ8BM7+3D4MJWkaAqjioTxvyWx9c
+p6KG4zPJe5J78P3xg3ZQVZFNDqAXEaue2Dcs6fjVFdKRfHOLGCXuq5pwgnObyOsW
+QMlLHgBnyuC3/0ltOEBcX09WUJW/sYvU3O1SzhwMr7OwmV3g7WuUjdA7PkNar2gD
+4ePUw3cv0hbci/Y59+gBq5tcmG6/TE9Zia0KjTqY+BLCRY+MOcYJunW+4jzM9Ox/
+KGxqG4p5bSHwm21bgYHoJO0LUBVYDWkm6DmkGjfpLkbbiKFGx0bdDn9pOWeR7u3s
+fVOSHYq69X2EPlrle3Bye6IRwqPJHQVZHzVF2wAqH7tA0jcvHvlz3mLbYC6miflH
+9CRvTlXMd13b31tvvxzHmZbwOyDi0osXuSkMBxabHsmmQ8dD7lX03vrqx9n7P8+J
+VZ80AryDMlGf9x4tBwoLq46FxmA3SjhRiuZgTL2JzEnjjAOCAgYAAoICAQCLvsmo
+MlTEvguTqjqa5rbXof/jMmmCPkN6D1xUfHhyyX7bAVnr9ZEX43tX3cc88f5OEkDE
+W8cWzLRle+CjcEVMxFjpKMgBzwAI4TIqNZsZJz8AMUY1gzkhNHaxiT8hqSPWX6TF
+Lb3pEDuzAhqmuObz5XWNdEcGQlTeugCKcUhdDBtThJoA/p6BzCO5qHJcP58PmVD2
+iBcbz7LZQ+BLDw5HRNCOsd5N21F5VgGACRUenzX/Piot8Z4y41vXWt3FP6dh+/i6
+xQz/pD1Biaaq+GxutOhlHFdwpXc56KpYGrnVoHEAkMdDT84u5OTuz16+wt1FNHIP
+ufhpL4iM6IGoqMYnb73IZxvlFNMuZ1bo4uhuvY+bFIZXBV7Xt9TbXtlVG+j/LpqP
+pmw09FJGWWc6DrAqcPnCfDhzlXphSIVaYTR6kLdtE6ullUQcfqePmDVok4b5i7Zi
+4dEIIXZ1KyWccl8e/6fCuUhPf/qlNdbeXVL2Evud1NYCdpICBTuGbeoGqDsnsU7e
+X+Bmp4fSHCdNcHoJeVk+fGg7X4ZsNB5zq4G1reJUZMTq2tvNYS++MXILYDGUN9j0
+zsvMExFU5NN5pqpSKv3VeI4AXDYdRRkMBhHhpX27jUgeJ6oDDes59T5iO6Aliw/V
+y0elWU3mxe6Rn6R7WHM8or7sX3tWDq6OntJlKg==
+-----END PUBLIC KEY-----
diff --git a/test/js/third_party/jsonwebtoken/ecdsa-private.pem b/test/js/third_party/jsonwebtoken/ecdsa-private.pem
new file mode 100644
index 000000000..aad4c4d93
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/ecdsa-private.pem
@@ -0,0 +1,18 @@
+-----BEGIN EC PARAMETERS-----
+MIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP//////////
+/////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6
+k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3gZ9+
+kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO5+tK
+fA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racXnoTz
+ucrC/GMlUQIBAQ==
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MIIBaAIBAQQgeg2m9tJJsnURyjTUihohiJahj9ETy3csUIt4EYrV+J2ggfowgfcC
+AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////
+MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr
+vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE
+axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W
+K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8
+YyVRAgEBoUQDQgAEEWluurrkZECnq27UpNauq16f9+5DDMFJZ3HV43Ujc3tcXQ++
+N1T/0CAA8ve286f32s7rkqX/pPokI/HBpP5p3g==
+-----END EC PRIVATE KEY-----
diff --git a/test/js/third_party/jsonwebtoken/ecdsa-public-invalid.pem b/test/js/third_party/jsonwebtoken/ecdsa-public-invalid.pem
new file mode 100644
index 000000000..016d86d5f
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/ecdsa-public-invalid.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA
+AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////
+///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd
+NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5
+RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA
+//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABEfZiYJDbghTGQ+KGnHGSl6K
+yUqK/BL2uJIg7Z0bx48v6+L7Ve8MCS17eptkMT2e4l5B/ZGDVUHb6uZ5xFROLBw=
+-----END PUBLIC KEY-----
diff --git a/test/js/third_party/jsonwebtoken/ecdsa-public-x509.pem b/test/js/third_party/jsonwebtoken/ecdsa-public-x509.pem
new file mode 100644
index 000000000..ef9fe22c3
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/ecdsa-public-x509.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGjCCAsKgAwIBAgIJANuPNBWwp6wzMAkGByqGSM49BAEwRTELMAkGA1UEBhMC
+QVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdp
+dHMgUHR5IEx0ZDAeFw0xNzA2MTAxMTAzMjJaFw0yNzA2MDgxMTAzMjJaMEUxCzAJ
+BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGQwggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjO
+PQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAA
+AAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQaw
+zFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i8
+5uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2
+QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAE
+EWluurrkZECnq27UpNauq16f9+5DDMFJZ3HV43Ujc3tcXQ++N1T/0CAA8ve286f3
+2s7rkqX/pPokI/HBpP5p3qOBpzCBpDAdBgNVHQ4EFgQUAF43lnAvCztZZGaGMoxs
+cp6tpz8wdQYDVR0jBG4wbIAUAF43lnAvCztZZGaGMoxscp6tpz+hSaRHMEUxCzAJ
+BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l
+dCBXaWRnaXRzIFB0eSBMdGSCCQDbjzQVsKesMzAMBgNVHRMEBTADAQH/MAkGByqG
+SM49BAEDRwAwRAIgV039oh2RtcSwywQ/0dWAwc20NHxrgmKoQ5A3AS5A9d0CIBCV
+2AlKDFjmDC7zjldNhWbMcIlSSj71ghhhxeS0F8v1
+-----END CERTIFICATE-----
diff --git a/test/js/third_party/jsonwebtoken/ecdsa-public.pem b/test/js/third_party/jsonwebtoken/ecdsa-public.pem
new file mode 100644
index 000000000..6cfee2f8f
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/ecdsa-public.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA
+AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////
+///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd
+NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5
+RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA
+//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABBFpbrq65GRAp6tu1KTWrqte
+n/fuQwzBSWdx1eN1I3N7XF0PvjdU/9AgAPL3tvOn99rO65Kl/6T6JCPxwaT+ad4=
+-----END PUBLIC KEY-----
diff --git a/test/js/third_party/jsonwebtoken/encoding.test.js b/test/js/third_party/jsonwebtoken/encoding.test.js
new file mode 100644
index 000000000..c8ad38dc0
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/encoding.test.js
@@ -0,0 +1,37 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+
+describe("encoding", function () {
+ function b64_to_utf8(str) {
+ return decodeURIComponent(escape(atob(str)));
+ }
+
+ it("should properly encode the token (utf8)", function () {
+ var expected = "José";
+ var token = jwt.sign({ name: expected }, "shhhhh");
+ var decoded_name = JSON.parse(b64_to_utf8(token.split(".")[1])).name;
+ expect(decoded_name).toEqual(expected);
+ });
+
+ it("should properly encode the token (binary)", function () {
+ var expected = "José";
+ var token = jwt.sign({ name: expected }, "shhhhh", { encoding: "binary" });
+ var decoded_name = JSON.parse(atob(token.split(".")[1])).name;
+ expect(decoded_name).toEqual(expected);
+ });
+
+ it("should return the same result when decoding", function () {
+ var username = "測試";
+
+ var token = jwt.sign(
+ {
+ username: username,
+ },
+ "test",
+ );
+
+ var payload = jwt.verify(token, "test");
+
+ expect(payload.username).toEqual(username);
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/expires_format.test.js b/test/js/third_party/jsonwebtoken/expires_format.test.js
new file mode 100644
index 000000000..4d44243ab
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/expires_format.test.js
@@ -0,0 +1,10 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+
+describe("expires option", function () {
+ it("should throw on deprecated expiresInSeconds option", function () {
+ expect(function () {
+ jwt.sign({ foo: 123 }, "123", { expiresInSeconds: 5 });
+ }).toThrow('"expiresInSeconds" is not allowed');
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/header-kid.test.js b/test/js/third_party/jsonwebtoken/header-kid.test.js
new file mode 100644
index 000000000..0eeea3b08
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/header-kid.test.js
@@ -0,0 +1,83 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+import { expect, describe, it, beforeEach } from "bun:test";
+import util from "util";
+import testUtils from "./test-utils";
+
+function signWithKeyId(keyid, payload, callback) {
+ const options = { algorithm: "HS256" };
+ if (keyid !== undefined) {
+ options.keyid = keyid;
+ }
+ testUtils.signJWTHelper(payload, "secret", options, callback);
+}
+
+describe("keyid", function () {
+ describe('`jwt.sign` "keyid" option validation', function () {
+ [true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ["foo"], {}, { foo: "bar" }].forEach(
+ keyid => {
+ it(`should error with with value ${util.inspect(keyid)}`, function (done) {
+ signWithKeyId(keyid, {}, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"keyid" must be a string');
+ });
+ });
+ });
+ },
+ );
+
+ // undefined needs special treatment because {} is not the same as {keyid: undefined}
+ it("should error with with value undefined", function (done) {
+ testUtils.signJWTHelper({}, "secret", { keyid: undefined, algorithm: "HS256" }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(Error);
+ expect(err).toHaveProperty("message", '"keyid" must be a string');
+ });
+ });
+ });
+ });
+
+ describe("when signing a token", function () {
+ it('should not add "kid" header when "keyid" option not provided', function (done) {
+ signWithKeyId(undefined, {}, (err, token) => {
+ testUtils.asyncCheck(done, () => {
+ const decoded = jwt.decode(token, { complete: true });
+ expect(err).toBeNull();
+ expect(decoded.header).not.toHaveProperty("kid");
+ });
+ });
+ });
+
+ it('should add "kid" header when "keyid" option is provided and an object payload', function (done) {
+ signWithKeyId("foo", {}, (err, token) => {
+ testUtils.asyncCheck(done, () => {
+ const decoded = jwt.decode(token, { complete: true });
+ expect(err).toBeNull();
+ expect(decoded.header).toHaveProperty("kid", "foo");
+ });
+ });
+ });
+
+ it('should add "kid" header when "keyid" option is provided and a Buffer payload', function (done) {
+ signWithKeyId("foo", new Buffer("a Buffer payload"), (err, token) => {
+ testUtils.asyncCheck(done, () => {
+ const decoded = jwt.decode(token, { complete: true });
+ expect(err).toBeNull();
+ expect(decoded.header).toHaveProperty("kid", "foo");
+ });
+ });
+ });
+
+ it('should add "kid" header when "keyid" option is provided and a string payload', function (done) {
+ signWithKeyId("foo", "a string payload", (err, token) => {
+ testUtils.asyncCheck(done, () => {
+ const decoded = jwt.decode(token, { complete: true });
+ expect(err).toBeNull();
+ expect(decoded.header).toHaveProperty("kid", "foo");
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/invalid_exp.test.js b/test/js/third_party/jsonwebtoken/invalid_exp.test.js
new file mode 100644
index 000000000..b5310c3bc
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/invalid_exp.test.js
@@ -0,0 +1,54 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+
+describe("invalid expiration", function () {
+ it("should fail with string", function (done) {
+ var broken_token =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxMjMiLCJmb28iOiJhZGFzIn0.cDa81le-pnwJMcJi3o3PBwB7cTJMiXCkizIhxbXAKRg";
+
+ jwt.verify(broken_token, "123", function (err) {
+ expect(err.name).toEqual("JsonWebTokenError");
+ done();
+ });
+ });
+
+ it("should fail with 0", function (done) {
+ var broken_token =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjAsImZvbyI6ImFkYXMifQ.UKxix5T79WwfqAA0fLZr6UrhU-jMES2unwCOFa4grEA";
+
+ jwt.verify(broken_token, "123", function (err) {
+ expect(err.name).toEqual("TokenExpiredError");
+ done();
+ });
+ });
+
+ it("should fail with false", function (done) {
+ var broken_token =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOmZhbHNlLCJmb28iOiJhZGFzIn0.iBn33Plwhp-ZFXqppCd8YtED77dwWU0h68QS_nEQL8I";
+
+ jwt.verify(broken_token, "123", function (err) {
+ expect(err.name).toEqual("JsonWebTokenError");
+ done();
+ });
+ });
+
+ it("should fail with true", function (done) {
+ var broken_token =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOnRydWUsImZvbyI6ImFkYXMifQ.eOWfZCTM5CNYHAKSdFzzk2tDkPQmRT17yqllO-ItIMM";
+
+ jwt.verify(broken_token, "123", function (err) {
+ expect(err.name).toEqual("JsonWebTokenError");
+ done();
+ });
+ });
+
+ it("should fail with object", function (done) {
+ var broken_token =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOnt9LCJmb28iOiJhZGFzIn0.1JjCTsWLJ2DF-CfESjLdLfKutUt3Ji9cC7ESlcoBHSY";
+
+ jwt.verify(broken_token, "123", function (err) {
+ expect(err.name).toEqual("JsonWebTokenError");
+ done();
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/invalid_pub.pem b/test/js/third_party/jsonwebtoken/invalid_pub.pem
new file mode 100644
index 000000000..2482abbde
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/invalid_pub.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDJjCCAg6gAwIBAgIJAMyz3mSPlaW4MA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
+BAMUCyouYXV0aDAuY29tMB4XDTEzMDQxODE3MDE1MFoXDTI2MTIyNjE3MDE1MFow
+FjEUMBIGA1UEAxQLKi5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDZq1Ua0/BGm+TaBFoftKWeYMWrQG9Fx3g7ikErxljmyOvlwqkiat3q
+ixX+Dxw9TFb5gbBjNJ+L3nt4YefJgLsYvsHqkOUxWsB+HM/ulJRVnVrZm1tI3Nbg
+xO1BQ7DrGfBpq2KCxtQCaQFRlQJw1+qS5LwrdIvihB7Kc142VElCFFHJ6+09eMUy
+jy00Z5pfQr4Am6W6eEOS9ObDbNs4XgKOcWe5khWXj3UStou+VgbAg40XcYht2IbY
+gMfKF+VUZOy3+e+aRTqPOBU3MAeb0tvCCPUQJbNAUHgSKVhAvNf8mRwttVsOLT70
+anjjeCOd7RKS8fVKBwc2KtgNkghYdPY9AgMBAAGjdzB1MB0GA1UdDgQWBBSi4+X0
++MvCKDdd375mDhx/ZBbJ4DBGBgNVHSMEPzA9gBSi4+X0+MvCKDdd375mDhx/ZBbJ
+4KEapBgwFjEUMBIGA1UEAxQLKi5hdXRoMC5jb22CCQDMs95kj5WluDAMBgNVHRME
+BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBi0qPe0DzlPSufq+Gdk2Fwf1pGEtjA
+D34IxxJ9SX6r1DS/NIP7IOLUnNU8cP8BQWl7i413v29jJsNV457pjdmqf8J7OE9O
+eF5Yz1x91gY/27561Iga/TQeIVOlFQAgx66eLfUFFoAig3hz2srZo5TzYBixMJsS
+fYMXHPiU7KoLUqYXvpSXIllstQCu51KCC6t9H7wZ92lTES1v76hFY4edQ30sftPo
+kjAYWGEhMjPo/r4THcdSMqKXoRtCGEun4pTXid7MJcTgdGDrAJddLWi6SxKecEVB
+MhMu4XfUCdxCwqQPjHeJ+zE49A1CUdBB2FN3BNLbmTTwEBgmuwyGRzhj
+-----END CERTIFICATE-----
diff --git a/test/js/third_party/jsonwebtoken/issue_147.test.js b/test/js/third_party/jsonwebtoken/issue_147.test.js
new file mode 100644
index 000000000..294e8528a
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/issue_147.test.js
@@ -0,0 +1,10 @@
+import jwt from "jsonwebtoken";
+import { describe, it, expect } from "bun:test";
+
+describe("issue 147 - signing with a sealed payload", function () {
+ it("should put the expiration claim", function () {
+ var token = jwt.sign(Object.seal({ foo: 123 }), "123", { expiresIn: 10 });
+ var result = jwt.verify(token, "123");
+ expect(result.exp).toBeCloseTo(Math.floor(Date.now() / 1000) + 10, 0.2);
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/issue_304.test.js b/test/js/third_party/jsonwebtoken/issue_304.test.js
new file mode 100644
index 000000000..257bcc09d
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/issue_304.test.js
@@ -0,0 +1,43 @@
+import jwt from "jsonwebtoken";
+import { describe, it, expect } from "bun:test";
+
+describe("issue 304 - verifying values other than strings", function () {
+ it("should fail with numbers", function (done) {
+ jwt.verify(123, "foo", function (err) {
+ expect(err.name).toEqual("JsonWebTokenError");
+ done();
+ });
+ });
+
+ it("should fail with objects", function (done) {
+ jwt.verify({ foo: "bar" }, "biz", function (err) {
+ expect(err.name).toEqual("JsonWebTokenError");
+ done();
+ });
+ });
+
+ it("should fail with arrays", function (done) {
+ jwt.verify(["foo"], "bar", function (err) {
+ expect(err.name).toEqual("JsonWebTokenError");
+ done();
+ });
+ });
+
+ it("should fail with functions", function (done) {
+ jwt.verify(
+ function () {},
+ "foo",
+ function (err) {
+ expect(err.name).toEqual("JsonWebTokenError");
+ done();
+ },
+ );
+ });
+
+ it("should fail with booleans", function (done) {
+ jwt.verify(true, "foo", function (err) {
+ expect(err.name).toEqual("JsonWebTokenError");
+ done();
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/issue_70.test.js b/test/js/third_party/jsonwebtoken/issue_70.test.js
new file mode 100644
index 000000000..2dfeabe54
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/issue_70.test.js
@@ -0,0 +1,14 @@
+import jwt from "jsonwebtoken";
+import { describe, it } from "bun:test";
+
+describe("issue 70 - public key start with BEING PUBLIC KEY", function () {
+ it("should work", function (done) {
+ var fs = require("fs");
+ var cert_pub = fs.readFileSync(__dirname + "/rsa-public.pem");
+ var cert_priv = fs.readFileSync(__dirname + "/rsa-private.pem");
+
+ var token = jwt.sign({ foo: "bar" }, cert_priv, { algorithm: "RS256" });
+
+ jwt.verify(token, cert_pub, done);
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/jwt.asymmetric_signing.test.js b/test/js/third_party/jsonwebtoken/jwt.asymmetric_signing.test.js
new file mode 100644
index 000000000..848dadb1a
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/jwt.asymmetric_signing.test.js
@@ -0,0 +1,208 @@
+const PS_SUPPORTED = true;
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+import fs from "fs";
+import path from "path";
+
+function loadKey(filename) {
+ return fs.readFileSync(path.join(__dirname, filename));
+}
+
+const algorithms = {
+ RS256: {
+ pub_key: loadKey("pub.pem"),
+ priv_key: loadKey("priv.pem"),
+ invalid_pub_key: loadKey("invalid_pub.pem"),
+ },
+ ES256: {
+ // openssl ecparam -name secp256r1 -genkey -param_enc explicit -out ecdsa-private.pem
+ priv_key: loadKey("ecdsa-private.pem"),
+ // openssl ec -in ecdsa-private.pem -pubout -out ecdsa-public.pem
+ pub_key: loadKey("ecdsa-public.pem"),
+ invalid_pub_key: loadKey("ecdsa-public-invalid.pem"),
+ },
+};
+
+if (PS_SUPPORTED) {
+ algorithms.PS256 = {
+ pub_key: loadKey("pub.pem"),
+ priv_key: loadKey("priv.pem"),
+ invalid_pub_key: loadKey("invalid_pub.pem"),
+ };
+}
+
+describe("Asymmetric Algorithms", function () {
+ Object.keys(algorithms).forEach(function (algorithm) {
+ describe(algorithm, function () {
+ const pub = algorithms[algorithm].pub_key;
+ const priv = algorithms[algorithm].priv_key;
+
+ // "invalid" means it is not the public key for the loaded "priv" key
+ const invalid_pub = algorithms[algorithm].invalid_pub_key;
+
+ describe("when signing a token", function () {
+ const token = jwt.sign({ foo: "bar" }, priv, { algorithm: algorithm });
+
+ it("should be syntactically valid", function () {
+ expect(typeof token).toBe("string");
+ expect(token.split(".")).toHaveLength(3);
+ });
+
+ describe("asynchronous", function () {
+ (algorithm === "ES256" ? it.todo : it)("should validate with public key", function (done) {
+ jwt.verify(token, pub, function (err, decoded) {
+ if (err) return done(err);
+ expect(decoded).toBeDefined();
+ expect(decoded.foo).toBeTruthy();
+ expect("bar").toBe(decoded.foo);
+ done();
+ });
+ });
+
+ it("should throw with invalid public key", function (done) {
+ jwt.verify(token, invalid_pub, function (err, decoded) {
+ expect(decoded).toBeUndefined();
+ expect(err).toBeTruthy();
+ done();
+ });
+ });
+ });
+
+ describe("synchronous", function () {
+ (algorithm === "ES256" ? it.todo : it)("should validate with public key", function () {
+ const decoded = jwt.verify(token, pub);
+ expect(decoded).toBeDefined();
+ expect(decoded.foo).toBeTruthy();
+ expect("bar").toBe(decoded.foo);
+ });
+
+ it("should throw with invalid public key", function () {
+ const jwtVerify = jwt.verify.bind(null, token, invalid_pub);
+ expect(jwtVerify).toThrow();
+ });
+ });
+ });
+
+ describe("when signing a token with expiration", function () {
+ (algorithm === "ES256" ? it.todo : it)("should be valid expiration", function (done) {
+ const token = jwt.sign({ foo: "bar" }, priv, { algorithm: algorithm, expiresIn: "10m" });
+ jwt.verify(token, pub, function (err, decoded) {
+ if (err) return done(err);
+ expect(decoded).toBeTruthy();
+ expect(err).toBeNull();
+ done();
+ });
+ });
+
+ (algorithm === "ES256" ? it.todo : it)("should be invalid", function (done) {
+ // expired token
+ const token = jwt.sign({ foo: "bar" }, priv, { algorithm: algorithm, expiresIn: -1 * (10 * 60 * 1000) });
+ jwt.verify(token, pub, function (err, decoded) {
+ expect(decoded).toBeUndefined();
+ expect(err).toBeDefined();
+ expect(err.name).toBe("TokenExpiredError");
+ expect(err.expiredAt).toBeInstanceOf(Date);
+ expect(err).toBeInstanceOf(jwt.TokenExpiredError);
+ done();
+ });
+ });
+
+ (algorithm === "ES256" ? it.todo : it)("should NOT be invalid", function (done) {
+ // expired token
+ const token = jwt.sign({ foo: "bar" }, priv, { algorithm: algorithm, expiresIn: -1 * (10 * 60 * 1000) });
+
+ jwt.verify(token, pub, { ignoreExpiration: true }, function (err, decoded) {
+ expect(decoded).toBeDefined();
+ expect(decoded.foo).toBeDefined();
+ expect("bar").toBe(decoded.foo);
+ done();
+ });
+ });
+ });
+
+ describe("when verifying a malformed token", function () {
+ it("should throw", function (done) {
+ jwt.verify("fruit.fruit.fruit", pub, function (err, decoded) {
+ expect(decoded).toBeUndefined();
+ expect(err).toBeDefined();
+ expect(err.name).toBe("JsonWebTokenError");
+ done();
+ });
+ });
+ });
+
+ describe("when decoding a jwt token with additional parts", function () {
+ const token = jwt.sign({ foo: "bar" }, priv, { algorithm: algorithm });
+
+ it("should throw", function (done) {
+ jwt.verify(token + ".foo", pub, function (err, decoded) {
+ expect(decoded).toBeUndefined();
+ expect(err).toBeDefined();
+ done();
+ });
+ });
+ });
+
+ describe("when decoding a invalid jwt token", function () {
+ it("should return null", function (done) {
+ const payload = jwt.decode("whatever.token");
+ expect(payload).toBeNull();
+ done();
+ });
+ });
+
+ describe("when decoding a valid jwt token", function () {
+ it("should return the payload", function (done) {
+ const obj = { foo: "bar" };
+ const token = jwt.sign(obj, priv, { algorithm: algorithm });
+ const payload = jwt.decode(token);
+ expect(payload.foo).toEqual(obj.foo);
+ done();
+ });
+ it("should return the header and payload and signature if complete option is set", function (done) {
+ const obj = { foo: "bar" };
+ const token = jwt.sign(obj, priv, { algorithm: algorithm });
+ const decoded = jwt.decode(token, { complete: true });
+ expect(decoded.payload.foo).toEqual(obj.foo);
+ expect(decoded.header).toStrictEqual({ typ: "JWT", alg: algorithm });
+ expect(typeof decoded.signature).toBe("string");
+ done();
+ });
+ });
+ });
+ });
+
+ describe("when signing a token with an unsupported private key type", function () {
+ it.todo("should throw an error", function () {
+ const obj = { foo: "bar" };
+ const key = loadKey("dsa-private.pem");
+ const algorithm = "RS256";
+
+ expect(function () {
+ jwt.sign(obj, key, { algorithm });
+ }).toThrow('Unknown key type "dsa".');
+ });
+ });
+
+ describe("when signing a token with an incorrect private key type", function () {
+ it("should throw a validation error if key validation is enabled", function () {
+ const obj = { foo: "bar" };
+ const key = loadKey("rsa-private.pem");
+ const algorithm = "ES256";
+
+ expect(function () {
+ jwt.sign(obj, key, { algorithm });
+ }).toThrow(/"alg" parameter for "rsa" key type must be one of:/);
+ });
+
+ it("should throw an unknown error if key validation is disabled", function () {
+ const obj = { foo: "bar" };
+ const key = loadKey("rsa-private.pem");
+ const algorithm = "ES256";
+
+ expect(function () {
+ jwt.sign(obj, key, { algorithm, allowInvalidAsymmetricKeyTypes: true });
+ }).not.toThrow(/"alg" parameter for "rsa" key type must be one of:/);
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/jwt.hs.test.js b/test/js/third_party/jsonwebtoken/jwt.hs.test.js
new file mode 100644
index 000000000..65424f66a
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/jwt.hs.test.js
@@ -0,0 +1,140 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+import jws from "jws";
+import { generateKeyPairSync } from "crypto";
+
+describe("HS256", function () {
+ describe("when signing using HS256", function () {
+ it("should throw if the secret is an asymmetric key", function () {
+ const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
+
+ expect(function () {
+ jwt.sign({ foo: "bar" }, privateKey, { algorithm: "HS256" });
+ }).toThrow("must be a symmetric key");
+ });
+
+ it("should throw if the payload is undefined", function () {
+ expect(function () {
+ jwt.sign(undefined, "secret", { algorithm: "HS256" });
+ }).toThrow("payload is required");
+ });
+
+ it("should throw if options is not a plain object", function () {
+ expect(function () {
+ jwt.sign({ foo: "bar" }, "secret", ["HS256"]);
+ }).toThrow('Expected "options" to be a plain object');
+ });
+ });
+
+ describe("with a token signed using HS256", function () {
+ var secret = "shhhhhh";
+
+ var token = jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" });
+
+ it("should be syntactically valid", function () {
+ expect(typeof token).toBe("string");
+ expect(token.split(".")).toHaveLength(3);
+ });
+
+ it("should be able to validate without options", function (done) {
+ var callback = function (err, decoded) {
+ if (err) return done(err);
+ expect(decoded).toBeDefined();
+ expect(decoded.foo).toBeDefined();
+ expect("bar").toBe(decoded.foo);
+ done();
+ };
+ callback.issuer = "shouldn't affect";
+ jwt.verify(token, secret, callback);
+ });
+
+ it("should validate with secret", function (done) {
+ jwt.verify(token, secret, function (err, decoded) {
+ if (err) return done(err);
+ expect(decoded).toBeDefined();
+ expect(decoded.foo).toBeDefined();
+ done();
+ });
+ });
+
+ it("should throw with invalid secret", function (done) {
+ jwt.verify(token, "invalid secret", function (err, decoded) {
+ expect(decoded).toBeUndefined();
+ expect(err).toBeTruthy();
+ done();
+ });
+ });
+
+ it("should throw with secret and token not signed", function (done) {
+ const header = { alg: "none" };
+ const payload = { foo: "bar" };
+ const token = jws.sign({ header, payload, secret: "secret", encoding: "utf8" });
+ jwt.verify(token, "secret", function (err, decoded) {
+ expect(decoded).toBeUndefined();
+ expect(err).toBeTruthy();
+ done();
+ });
+ });
+
+ it("should throw with falsy secret and token not signed", function (done) {
+ const header = { alg: "none" };
+ const payload = { foo: "bar" };
+ const token = jws.sign({ header, payload, secret: null, encoding: "utf8" });
+ jwt.verify(token, "secret", function (err, decoded) {
+ expect(decoded).toBeUndefined();
+ expect(err).toBeTruthy();
+ done();
+ });
+ });
+
+ it("should throw when verifying null", function (done) {
+ jwt.verify(null, "secret", function (err, decoded) {
+ expect(decoded).toBeUndefined();
+ expect(err).toBeTruthy();
+ done();
+ });
+ });
+
+ it("should return an error when the token is expired", function (done) {
+ var token = jwt.sign({ exp: 1 }, secret, { algorithm: "HS256" });
+ jwt.verify(token, secret, { algorithm: "HS256" }, function (err, decoded) {
+ expect(decoded).toBeUndefined();
+ expect(err).toBeTruthy();
+ done();
+ });
+ });
+
+ it('should NOT return an error when the token is expired with "ignoreExpiration"', function (done) {
+ var token = jwt.sign({ exp: 1, foo: "bar" }, secret, { algorithm: "HS256" });
+ jwt.verify(token, secret, { algorithm: "HS256", ignoreExpiration: true }, function (err, decoded) {
+ if (err) return done(err);
+ expect(decoded).toBeDefined();
+ expect("bar").toBe(decoded.foo);
+ expect(decoded.foo).toBeDefined();
+ done();
+ });
+ });
+
+ it("should default to HS256 algorithm when no options are passed", function () {
+ var token = jwt.sign({ foo: "bar" }, secret);
+ var verifiedToken = jwt.verify(token, secret);
+ expect(verifiedToken).toBeDefined();
+ expect("bar").toBe(verifiedToken.foo);
+ });
+ });
+
+ describe("should fail verification gracefully with trailing space in the jwt", function () {
+ var secret = "shhhhhh";
+ var token = jwt.sign({ foo: "bar" }, secret, { algorithm: "HS256" });
+
+ it('should return the "invalid token" error', function (done) {
+ var malformedToken = token + " "; // corrupt the token by adding a space
+ jwt.verify(malformedToken, secret, { algorithm: "HS256", ignoreExpiration: true }, function (err) {
+ expect(err).not.toBeNull();
+ expect("JsonWebTokenError").toBe(err.name);
+ expect("invalid token").toBe(err.message);
+ done();
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/jwt.malicious.test.js b/test/js/third_party/jsonwebtoken/jwt.malicious.test.js
new file mode 100644
index 000000000..8e31859cb
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/jwt.malicious.test.js
@@ -0,0 +1,44 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+import crypto from "crypto";
+
+describe("when verifying a malicious token", function () {
+ // attacker has access to the public rsa key, but crafts the token as HS256
+ // with kid set to the id of the rsa key, instead of the id of the hmac secret.
+ // const maliciousToken = jwt.sign(
+ // {foo: 'bar'},
+ // pubRsaKey,
+ // {algorithm: 'HS256', keyid: 'rsaKeyId'}
+ // );
+ // consumer accepts self signed tokens (HS256) and third party tokens (RS256)
+ const options = { algorithms: ["RS256", "HS256"] };
+
+ const { publicKey: pubRsaKey } = crypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
+
+ it("should not allow HMAC verification with an RSA key in KeyObject format", function () {
+ const maliciousToken =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJzYUtleUlkIn0.eyJmb28iOiJiYXIiLCJpYXQiOjE2NTk1MTA2MDh9.cOcHI1TXPbxTMlyVTfjArSWskrmezbrG8iR7uJHwtrQ";
+
+ expect(() => jwt.verify(maliciousToken, pubRsaKey, options)).toThrow("must be a symmetric key");
+ });
+
+ it("should not allow HMAC verification with an RSA key in PEM format", function () {
+ const maliciousToken =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJzYUtleUlkIn0.eyJmb28iOiJiYXIiLCJpYXQiOjE2NTk1MTA2MDh9.cOcHI1TXPbxTMlyVTfjArSWskrmezbrG8iR7uJHwtrQ";
+
+ expect(() => jwt.verify(maliciousToken, pubRsaKey.export({ type: "spki", format: "pem" }), options)).toThrow(
+ "must be a symmetric key",
+ );
+ });
+
+ it("should not allow arbitrary execution from malicious Buffers containing objects with overridden toString functions", function () {
+ const token = jwt.sign({ "foo": "bar" }, "secret");
+ const maliciousBuffer = {
+ toString: () => {
+ throw new Error("Arbitrary Code Execution");
+ },
+ };
+
+ expect(() => jwt.verify(token, maliciousBuffer)).toThrow("not valid key material");
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/noTimestamp.test.js b/test/js/third_party/jsonwebtoken/noTimestamp.test.js
new file mode 100644
index 000000000..22a61b3eb
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/noTimestamp.test.js
@@ -0,0 +1,10 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+
+describe("noTimestamp", function () {
+ it("should work with string", function () {
+ var token = jwt.sign({ foo: 123 }, "123", { expiresIn: "5m", noTimestamp: true });
+ var result = jwt.verify(token, "123");
+ expect(result.exp).toBeCloseTo(Math.floor(Date.now() / 1000) + 5 * 60, 0.5);
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/non_object_values.test.js b/test/js/third_party/jsonwebtoken/non_object_values.test.js
new file mode 100644
index 000000000..55b5d59ae
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/non_object_values.test.js
@@ -0,0 +1,16 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+
+describe("non_object_values values", function () {
+ it("should work with string", function () {
+ var token = jwt.sign("hello", "123");
+ var result = jwt.verify(token, "123");
+ expect(result).toEqual("hello");
+ });
+
+ it("should work with number", function () {
+ var token = jwt.sign(123, "123");
+ var result = jwt.verify(token, "123");
+ expect(result).toEqual("123");
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/option-complete.test.js b/test/js/third_party/jsonwebtoken/option-complete.test.js
new file mode 100644
index 000000000..2b446e4e2
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/option-complete.test.js
@@ -0,0 +1,53 @@
+"use strict";
+
+import { expect, describe, it } from "bun:test";
+import testUtils from "./test-utils";
+import jws from "jws";
+import fs from "fs";
+import path from "path";
+
+describe("complete option", function () {
+ const secret = fs.readFileSync(path.join(__dirname, "priv.pem"));
+ const pub = fs.readFileSync(path.join(__dirname, "pub.pem"));
+
+ const header = { alg: "RS256" };
+ const payload = { iat: Math.floor(Date.now() / 1000) };
+ const signed = jws.sign({ header, payload, secret, encoding: "utf8" });
+ const signature = jws.decode(signed).signature;
+
+ [
+ {
+ description: "should return header, payload and signature",
+ complete: true,
+ },
+ ].forEach(testCase => {
+ it(testCase.description, function (done) {
+ testUtils.verifyJWTHelper(signed, pub, { typ: "JWT", complete: testCase.complete }, (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded.header).toHaveProperty("alg", header.alg);
+ expect(decoded.payload).toHaveProperty("iat", payload.iat);
+ expect(decoded).toHaveProperty("signature", signature);
+ });
+ });
+ });
+ });
+ [
+ {
+ description: "should return payload",
+ complete: false,
+ },
+ ].forEach(testCase => {
+ it(testCase.description, function (done) {
+ testUtils.verifyJWTHelper(signed, pub, { typ: "JWT", complete: testCase.complete }, (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded.header).toBeUndefined();
+ expect(decoded.payload).toBeUndefined();
+ expect(decoded.signature).toBeUndefined();
+ expect(decoded).toHaveProperty("iat", payload.iat);
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/option-maxAge.test.js b/test/js/third_party/jsonwebtoken/option-maxAge.test.js
new file mode 100644
index 000000000..e48525344
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/option-maxAge.test.js
@@ -0,0 +1,62 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+import { expect, describe, it, beforeEach, afterEach } from "bun:test";
+import util from "util";
+import sinon from "sinon";
+
+describe("maxAge option", function () {
+ let token;
+
+ let fakeClock;
+ beforeEach(function () {
+ fakeClock = sinon.useFakeTimers({ now: 60000 });
+ token = jwt.sign({ iat: 70 }, "secret", { algorithm: "HS256" });
+ });
+
+ afterEach(function () {
+ fakeClock.uninstall();
+ });
+
+ [
+ {
+ description: "should work with a positive string value",
+ maxAge: "3s",
+ },
+ {
+ description: "should work with a negative string value",
+ maxAge: "-3s",
+ },
+ {
+ description: "should work with a positive numeric value",
+ maxAge: 3,
+ },
+ {
+ description: "should work with a negative numeric value",
+ maxAge: -3,
+ },
+ ].forEach(testCase => {
+ it(testCase.description, function (done) {
+ expect(() => jwt.verify(token, "secret", { maxAge: "3s", algorithm: "HS256" })).not.toThrow();
+ jwt.verify(token, "secret", { maxAge: testCase.maxAge, algorithm: "HS256" }, err => {
+ expect(err).toBeNull();
+ done();
+ });
+ });
+ });
+
+ [true, "invalid", [], ["foo"], {}, { foo: "bar" }].forEach(maxAge => {
+ it(`should error with value ${util.inspect(maxAge)}`, function (done) {
+ expect(() => jwt.verify(token, "secret", { maxAge, algorithm: "HS256" })).toThrow(
+ '"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60',
+ );
+ jwt.verify(token, "secret", { maxAge, algorithm: "HS256" }, err => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err.message).toEqual(
+ '"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60',
+ );
+ done();
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/option-nonce.test.js b/test/js/third_party/jsonwebtoken/option-nonce.test.js
new file mode 100644
index 000000000..abe918d42
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/option-nonce.test.js
@@ -0,0 +1,41 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+import { expect, describe, it, beforeEach } from "bun:test";
+import util from "util";
+import testUtils from "./test-utils";
+
+describe("nonce option", function () {
+ let token;
+
+ beforeEach(function () {
+ token = jwt.sign({ nonce: "abcde" }, "secret", { algorithm: "HS256" });
+ });
+ [
+ {
+ description: "should work with a string",
+ nonce: "abcde",
+ },
+ ].forEach(testCase => {
+ it(testCase.description, function (done) {
+ testUtils.verifyJWTHelper(token, "secret", { nonce: testCase.nonce }, (err, decoded) => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeNull();
+ expect(decoded).toHaveProperty("nonce", "abcde");
+ });
+ });
+ });
+ });
+ [true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, "", " ", [], ["foo"], {}, { foo: "bar" }].forEach(
+ nonce => {
+ it(`should error with value ${util.inspect(nonce)}`, function (done) {
+ testUtils.verifyJWTHelper(token, "secret", { nonce }, err => {
+ testUtils.asyncCheck(done, () => {
+ expect(err).toBeInstanceOf(jwt.JsonWebTokenError);
+ expect(err).toHaveProperty("message", "nonce must be a non-empty string");
+ });
+ });
+ });
+ },
+ );
+});
diff --git a/test/js/third_party/jsonwebtoken/package.json b/test/js/third_party/jsonwebtoken/package.json
new file mode 100644
index 000000000..95228db02
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "jsonwebtoken",
+ "dependencies": {
+ "jsonwebtoken": "9.0.2",
+ "jws": "4.0.0",
+ "sinon": "16.1.0"
+ }
+}
diff --git a/test/js/third_party/jsonwebtoken/prime256v1-private.pem b/test/js/third_party/jsonwebtoken/prime256v1-private.pem
new file mode 100644
index 000000000..317366570
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/prime256v1-private.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMP1Xt/ic2jAHJva2Pll866d1jYL+dk3VdLytEU1+LFmoAoGCCqGSM49
+AwEHoUQDQgAEvIywoA1H1a2XpPPTqsRxSk6YnNRVsu4E+wTvb7uV6Yttvko9zWar
+jmtM3LHDXk/nHn+Pva0KD+lby8gb2daHGg==
+-----END EC PRIVATE KEY-----
diff --git a/test/js/third_party/jsonwebtoken/priv.pem b/test/js/third_party/jsonwebtoken/priv.pem
new file mode 100644
index 000000000..7be6d5abc
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/priv.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN
++H7GHp3/QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXi
+c78kOugMY1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6Gb
+RKzyTKcB58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRX
+kdDSHty6lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1K
+kyHFqWpxaJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABAoIBAQCYKw05YSNhXVPk
+eHLeW/pXuwR3OkCexPrakOmwMC0s2vIF7mChN0d6hvhVlUp68X7V8SnS2JxAGo8v
+iHY+Et3DdwZ3cxnzwh+BEhzgDfoIOmkoGppZPyX/K6klWtbGUrTtSISOWXbvEXQU
+G0qGAvDOzIGTsdMDX7slnU70Ac23JybPY5qBSiE+ky8U4dm2fUHMroWub4QP5vA/
+nqyWqX2FB/MEAbcujaknDQrFCtbmtUYlBbJCKGd9V3cGEqp6H7oH+ah2ofMc91gJ
+mCHk3YyWZB/bcVXH3CA+s1ywvCOVDBZ3Nw7Pt9zIcv6Rl9UKIy+Nx0QjXxR90Hla
+Tr0GHIShAoGBAPsD7uXm+0ksnGyKRYgvlVad8Z8FUFT6bf4B+vboDbx40FO8O/5V
+PraBPC5z8YRSBOQ/WfccPQzakkA28F2pXlRpXu5JcErVWnyyUiKpX5sw6iPenQR2
+JO9hY/GFbKiwUhVHpvWMcXFqFLSQu2A86jPnFFEfG48ZT4IhTzINKJVZAoGBAMKc
+B3YGfVfY9qiRFXzYRdSRLg5c8p/HzuWwXc9vfJ4kQTDkPXe/+nqD67rzeT54uVec
+jKoIrsCu4BfEaoyvOT+1KmUfdEpBgYZuuEC4CZf7dgKbXOpPVvZDMyJ/e7HyqTpw
+mvIYJLPm2fNAcAsnbrNX5mhLwwzEIltbplUUeRdrAoGBAKhZgPYsLkhrZRXevreR
+wkTvdUfD1pbHxtFfHqROCjhnhsFCM7JmFcNtdaFqHYczQxiZ7IqxI7jlNsVek2Md
+3qgaa5LBKlDmOuP67N9WXUrGSaJ5ATIm0qrB1Lf9VlzktIiVH8L7yHHaRby8fQ8U
+i7b3ukaV6HPW895A3M6iyJ8xAoGAInp4S+3MaTL0SFsj/nFmtcle6oaHKc3BlyoP
+BMBQyMfNkPbu+PdXTjtvGTknouzKkX4X4cwWAec5ppxS8EffEa1sLGxNMxa19vZI
+yJaShI21k7Ko3I5f7tNrDNKfPKCsYMEwgnHKluDwfktNTnyW/Uk2dgXuMaXSHHN5
+XZt59K8CgYArGVOWK7LUmf3dkTIs3tXBm4/IMtUZmWmcP9C8Xe/Dg/IdQhK5CIx4
+VXl8rgZNeX/5/4nJ8Q3LrdLau1Iz620trNRGU6sGMs3x4WQbSq93RRbFzfG1oK74
+IOo5yIBxImQOSk5jz31gF9RJb15SDBIxonuWv8qAERyUfvrmEwR0kg==
+-----END RSA PRIVATE KEY-----
diff --git a/test/js/third_party/jsonwebtoken/pub.pem b/test/js/third_party/jsonwebtoken/pub.pem
new file mode 100644
index 000000000..dd95d341e
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/pub.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDtTCCAp2gAwIBAgIJAMKR/NsyfcazMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTIxMTEyMjM0MzQxWhcNMTYxMjIxMjM0MzQxWjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN+H7GHp3/
+QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXic78kOugM
+Y1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6GbRKzyTKcB
+58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRXkdDSHty6
+lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1KkyHFqWpx
+aJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABo4GnMIGkMB0GA1UdDgQWBBTs83nk
+LtoXFlmBUts3EIxcVvkvcjB1BgNVHSMEbjBsgBTs83nkLtoXFlmBUts3EIxcVvkv
+cqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
+BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAMKR/NsyfcazMAwGA1UdEwQF
+MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABw7w/5k4d5dVDgd/OOOmXdaaCIKvt7d
+3ntlv1SSvAoKT8d8lt97Dm5RrmefBI13I2yivZg5bfTge4+vAV6VdLFdWeFp1b/F
+OZkYUv6A8o5HW0OWQYVX26zIqBcG2Qrm3reiSl5BLvpj1WSpCsYvs5kaO4vFpMak
+/ICgdZD+rxwxf8Vb/6fntKywWSLgwKH3mJ+Z0kRlpq1g1oieiOm1/gpZ35s0Yuor
+XZba9ptfLCYSggg/qc3d3d0tbHplKYkwFm7f5ORGHDSD5SJm+gI7RPE+4bO8q79R
+PAfbG1UGuJ0b/oigagciHhJp851SQRYf3JuNSc17BnK2L5IEtzjqr+Q=
+-----END CERTIFICATE-----
diff --git a/test/js/third_party/jsonwebtoken/rsa-private.pem b/test/js/third_party/jsonwebtoken/rsa-private.pem
new file mode 100644
index 000000000..746366b5b
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/rsa-private.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAvzoCEC2rpSpJQaWZbUmlsDNwp83Jr4fi6KmBWIwnj1MZ6CUQ
+7rBasuLI8AcfX5/10scSfQNCsTLV2tMKQaHuvyrVfwY0dINk+nkqB74QcT2oCCH9
+XduJjDuwWA4xLqAKuF96FsIes52opEM50W7/W7DZCKXkC8fFPFj6QF5ZzApDw2Qs
+u3yMRmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN40vXv9c4xiSafVvnx9BwYL7H1Q8N
+iK9LGEN6+JSWfgckQCs6UUBOXSZdreNN9zbQCwyzee7bOJqXUDAuLcFARzPw1EsZ
+AyjVtGCKIQ0/btqK+jFunT2NBC8RItanDZpptQIDAQABAoIBAQCsssO4Pra8hFMC
+gX7tr0x+tAYy1ewmpW8stiDFilYT33YPLKJ9HjHbSms0MwqHftwwTm8JDc/GXmW6
+qUui+I64gQOtIzpuW1fvyUtHEMSisI83QRMkF6fCSQm6jJ6oQAtOdZO6R/gYOPNb
+3gayeS8PbMilQcSRSwp6tNTVGyC33p43uUUKAKHnpvAwUSc61aVOtw2wkD062XzM
+hJjYpHm65i4V31AzXo8HF42NrAtZ8K/AuQZne5F/6F4QFVlMKzUoHkSUnTp60XZx
+X77GuyDeDmCgSc2J7xvR5o6VpjsHMo3ek0gJk5ZBnTgkHvnpbULCRxTmDfjeVPue
+v3NN2TBFAoGBAPxbqNEsXPOckGTvG3tUOAAkrK1hfW3TwvrW/7YXg1/6aNV4sklc
+vqn/40kCK0v9xJIv9FM/l0Nq+CMWcrb4sjLeGwHAa8ASfk6hKHbeiTFamA6FBkvQ
+//7GP5khD+y62RlWi9PmwJY21lEkn2mP99THxqvZjQiAVNiqlYdwiIc7AoGBAMH8
+f2Ay7Egc2KYRYU2qwa5E/Cljn/9sdvUnWM+gOzUXpc5sBi+/SUUQT8y/rY4AUVW6
+YaK7chG9YokZQq7ZwTCsYxTfxHK2pnG/tXjOxLFQKBwppQfJcFSRLbw0lMbQoZBk
+S+zb0ufZzxc2fJfXE+XeJxmKs0TS9ltQuJiSqCPPAoGBALEc84K7DBG+FGmCl1sb
+ZKJVGwwknA90zCeYtadrIT0/VkxchWSPvxE5Ep+u8gxHcqrXFTdILjWW4chefOyF
+5ytkTrgQAI+xawxsdyXWUZtd5dJq8lxLtx9srD4gwjh3et8ZqtFx5kCHBCu29Fr2
+PA4OmBUMfrs0tlfKgV+pT2j5AoGBAKnA0Z5XMZlxVM0OTH3wvYhI6fk2Kx8TxY2G
+nxsh9m3hgcD/mvJRjEaZnZto6PFoqcRBU4taSNnpRr7+kfH8sCht0k7D+l8AIutL
+ffx3xHv9zvvGHZqQ1nHKkaEuyjqo+5kli6N8QjWNzsFbdvBQ0CLJoqGhVHsXuWnz
+W3Z4cBbVAoGAEtnwY1OJM7+R2u1CW0tTjqDlYU2hUNa9t1AbhyGdI2arYp+p+umA
+b5VoYLNsdvZhqjVFTrYNEuhTJFYCF7jAiZLYvYm0C99BqcJnJPl7JjWynoNHNKw3
+9f6PIOE1rAmPE8Cfz/GFF5115ZKVlq+2BY8EKNxbCIy2d/vMEvisnXI=
+-----END RSA PRIVATE KEY-----
diff --git a/test/js/third_party/jsonwebtoken/rsa-pss-invalid-salt-length-private.pem b/test/js/third_party/jsonwebtoken/rsa-pss-invalid-salt-length-private.pem
new file mode 100644
index 000000000..cbafa662d
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/rsa-pss-invalid-salt-length-private.pem
@@ -0,0 +1,29 @@
+-----BEGIN PRIVATE KEY-----
+MIIE8gIBADBCBgkqhkiG9w0BAQowNaAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI
+hvcNAQEIMA0GCWCGSAFlAwQCAQUAogQCAgQABIIEpzCCBKMCAQACggEBAJy3FuDR
+1qKXsC8o+0xDJbuJCnysT71EFDGQY2/b3cZmxW3rzDYLyE65t2Go1jeK5Kxs+kwS
+1VxfefD8DifeDZN66wjRse4iWLcxmQB5FfishXOdozciimgXNvXJNS8X//feSofl
+vDQaTUI0NJnw1qQ2CB0pgGInwajsRKpWnDOhfk3NA/cmGlmfhTtDSTxq0ReytUie
+TjY7gy+S9YYm4bAgBcMeoup0GEPzYccK4+1yCmWzQZGFcrY1cuB9bL+vT7ajQFhe
+WVKlp6z35GyBF2zI7gJSkHpUHaWV5+Z9aTr6+YP6U7xuCRvXQ/l6BEOUjt4Es2YG
+3frgxeVbOs1gAakCAwEAAQKCAQAMvFxhnOwCfq1Ux9HUWsigOvzdMOuyB+xUMtXB
+625Uh1mYG0eXRNHcg/9BMoVmMiVvVdPphsZMIX45dWJ5HvSffafIKbJ6FdR73s3+
+WdjNQsf9o1v2SRpSZ0CSLO3ji+HDdQ89iBAJc/G/ZZq4v/fRlIqIRC0ozO5SGhFi
+fnNnRqH78d2KeJMX/g9jBZM8rJQCi+pb0keHmFmLJ5gZa4HokE8rWQJQY46PVYUH
+W2BwEJToMl3MPC7D95soWVuFt3KHnIWhuma/tnCmd2AUvcMrdWq0CwStH3vuX4LB
+vJug0toWkobt1tzZgzzCASb2EpzJj8UNxP1CzTQWsvl8OephAoGBAMVnmZeLHoh2
+kxn/+rXetZ4Msjgu19MHNQAtlMvqzwZLan0K/BhnHprJLy4SDOuQYIs+PYJuXdT7
+Yv2mp9kwTPz8glP9LAto4MDeDfCu0cyXmZb2VQcT/lqVyrwfx3Psqxm/Yxg62YKr
+aQE8WqgZGUdOvU9dYU+7EmPlYpdGpPVlAoGBAMs7ks+12oE6kci3WApdnt0kk5+f
+8fbQ0lp2vR3tEw8DURa5FnHWA4o46XvcMcuXwZBrpxANPNAxJJjMBs1hSkc8h4hd
+4vjtRNYJpj+uBdDIRmdqTzbpWv+hv8Xpiol5EVgnMVs2UZWDjoxQ+mYa1R8tAUfj
+ojzV2KBMWGCoHgj1AoGALki6JGQEBq72kpQILnhHUQVdC/s/s0TvUlldl+o4HBu2
+nhbjQL182YHuQ/kLenfhiwRO27QQ4A0JCrv2gt/mTTLPQ+4KU6qFd/MYhaQXoMay
+xkh/aydu7cJNRIqW80E8ZM8Q5u91bEPQXO/PubYYzTVTAba9SDpud2mjEiEIMFkC
+gYEAxINEQEgtkkuZ76UpIkzIcjkN7YlxJCFjZUnvL+KvTRL986TgyQ4RujOxwKx4
+Ec8ZwZX2opTKOt1p771IzorGkf87ZmayM9TpfLUz5dtVkD43pYOsOQKHlStIDgz2
+gltoo/6xwOrTFGlzCsa6eMR1U4Hm/SZlF8IHh2iLBFtLP4kCgYBqTi1XeWeVQVSA
+y9Wolv9kMoRh/Xh6F2D8bTTybGshDVO+P4YLM4lLxh5UDZAd/VOkdf3ZIcUGv022
+lxrYbLbIEGckMCpkdHeZH/1/iuJUeiCrXeyNlQsXBrmJKr/0lENniJHGpiSEyvY5
+D8Oafyjd7ZjUmyBFvS4heQEC6Pjo3Q==
+-----END PRIVATE KEY-----
diff --git a/test/js/third_party/jsonwebtoken/rsa-pss-private.pem b/test/js/third_party/jsonwebtoken/rsa-pss-private.pem
new file mode 100644
index 000000000..52b1c08e9
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/rsa-pss-private.pem
@@ -0,0 +1,29 @@
+-----BEGIN PRIVATE KEY-----
+MIIE8QIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI
+hvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAEggSnMIIEowIBAAKCAQEA00tEqqyF
+VnyvcVA2ewVoSicCMdQXmWyYM82sBWX0wcnn0WUuZp1zjux4xTvQ71Lhx95OJCQZ
+7r7b2192Im5ca37wNRbI6DhyXNdNVFXLFYlNAvgP+V0gIwlr6NgopdJqHCjYVv/g
+GOoesRZaDdtV1A3O9CXdJ34x2HZh7nhwYK5hqZDhUW4rd+5GzIIzwCJfwgTQpkIc
+18UeMMEoKJ6A0ixdpf43HqJ5fAB5nsbYFhyHpfiX1UO2EFJtSdbKEIbRmqcbNjG1
+tu1tjt6u8LI2coetLh/IYMbMfkyQz+eAUHLQCUb2R8BqLOL3hRqEsVTBo93UJlOs
+VWC1fKaq+HOEWQIDAQABAoIBAAet23PagPQTjwAZcAlzjlvs5AMHQsj5gznqwSmR
+ut3/e7SGrrOIXbv1iIQejZQ3w8CS/0MH/ttIRiRIaWTh9EDsjvKsU9FAxUNDiJTG
+k3LCbTFCQ7kGiJWiu4XDCWMmwmLTRzLjlMjtr/+JS5eSVPcNKMGDI3D9K0xDLSxQ
+u0DVigYgWOCWlejHCEU4yi6vBO0HlumWjVPelWb9GmihBDwCLUJtG0JA6H6rw+KS
+i6SNXcMGVKfjEghChRp+HaMvLvMgU44Ptnj8jhlfBctXInBY1is1FfDSWxXdVbUM
+1HdKXfV4A50GXSvJLiWP9ZZsaZ7NiBJK8IiJBXD72EFOzwECgYEA3RjnTJn9emzG
+84eIHZQujWWt4Tk/wjeLJYOYtAZpF7R3/fYLVypX9Bsw1IbwZodq/jChTjMaUkYt
+//FgUjF/t0uakEg1i+THPZvktNB8Q1E9NwHerB8HF/AD/jMALD+ejdLQ11Z4VScw
+zyNmSvD9I84/sgpms5YVKSH9sqww2RkCgYEA9KYws3sTfRLc1hlsS25V6+Zg3ZCk
+iGcp+zrxGC1gb2/PpRvEDBucZO21KbSRuQDavWIOZYl4fGu7s8wo2oF8RxOsHQsM
+LJyjklruvtjnvuoft/bGAv2zLQkNaj+f7IgK6965gIxcLYL66UPCZZkTfL5CoJis
+V0v2hBh1ES5bLUECgYEAuONeaLxNL9dO989akAGefDePFExfePYhshk91S2XLG+J
++CGMkjOioUsrpk3BMrwDSNU5zr8FP8/YH7OlrJYgCxN6CTWZMYb65hY7RskhYNnK
+qvkxUBYSRH49mJDlkBsTZ93nLmvs7Kh9NHqRzBGCXjLXKPdxsrPKtj7qfENqBeEC
+gYAC9dPXCCE3PTgw2wPlccNWZGY9qBdlkyH96TurmDj3gDnZ/JkFsHvW+M1dYNL2
+kx0Sd5JHBj/P+Zm+1jSUWEbBsWo+u7h8/bQ4/CKxanx7YefaWQESXjGB1P81jumH
+einvqrVB6fDfmBsjIW/DvPNwafjyaoaDU+b6uDUKbS4rQQKBgCe0pvDl5lO8FM81
+NP7GoCIu1gKBS+us1sgYE65ZFmVXJ6b5DckvobXSjM60G2N5w2xaXEXJsnwMApf1
+SClQUsgNWcSXRwL+w0pIdyFKS25BSfwUNQ9n7QLJcYgmflbARTfB3He/10vbFzTp
+G6ZAiKUp9bKFPzviII40AEPL2hPX
+-----END PRIVATE KEY-----
diff --git a/test/js/third_party/jsonwebtoken/rsa-public-key.pem b/test/js/third_party/jsonwebtoken/rsa-public-key.pem
new file mode 100644
index 000000000..eb9a29bad
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/rsa-public-key.pem
@@ -0,0 +1,8 @@
+-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEAvzoCEC2rpSpJQaWZbUmlsDNwp83Jr4fi6KmBWIwnj1MZ6CUQ7rBa
+suLI8AcfX5/10scSfQNCsTLV2tMKQaHuvyrVfwY0dINk+nkqB74QcT2oCCH9XduJ
+jDuwWA4xLqAKuF96FsIes52opEM50W7/W7DZCKXkC8fFPFj6QF5ZzApDw2Qsu3yM
+Rmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN40vXv9c4xiSafVvnx9BwYL7H1Q8NiK9L
+GEN6+JSWfgckQCs6UUBOXSZdreNN9zbQCwyzee7bOJqXUDAuLcFARzPw1EsZAyjV
+tGCKIQ0/btqK+jFunT2NBC8RItanDZpptQIDAQAB
+-----END RSA PUBLIC KEY-----
diff --git a/test/js/third_party/jsonwebtoken/rsa-public-key.test.js b/test/js/third_party/jsonwebtoken/rsa-public-key.test.js
new file mode 100644
index 000000000..c343cb0a9
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/rsa-public-key.test.js
@@ -0,0 +1,44 @@
+const PS_SUPPORTED = true;
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+import { generateKeyPairSync } from "crypto";
+
+describe("public key start with BEGIN RSA PUBLIC KEY", function () {
+ it("should work for RS family of algorithms", function (done) {
+ var fs = require("fs");
+ var cert_pub = fs.readFileSync(__dirname + "/rsa-public-key.pem");
+ var cert_priv = fs.readFileSync(__dirname + "/rsa-private.pem");
+
+ var token = jwt.sign({ foo: "bar" }, cert_priv, { algorithm: "RS256" });
+
+ jwt.verify(token, cert_pub, done);
+ });
+
+ it("should not work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is false or not set", function (done) {
+ const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 1024 });
+
+ expect(function () {
+ jwt.sign({ foo: "bar" }, privateKey, { algorithm: "RS256" });
+ }).toThrow("minimum key size");
+
+ done();
+ });
+
+ it("should work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is true", function (done) {
+ const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 1024 });
+
+ jwt.sign({ foo: "bar" }, privateKey, { algorithm: "RS256", allowInsecureKeySizes: true }, done);
+ });
+
+ if (PS_SUPPORTED) {
+ it("should work for PS family of algorithms", function (done) {
+ var fs = require("fs");
+ var cert_pub = fs.readFileSync(__dirname + "/rsa-public-key.pem");
+ var cert_priv = fs.readFileSync(__dirname + "/rsa-private.pem");
+
+ var token = jwt.sign({ foo: "bar" }, cert_priv, { algorithm: "PS256" });
+
+ jwt.verify(token, cert_pub, done);
+ });
+ }
+});
diff --git a/test/js/third_party/jsonwebtoken/rsa-public.pem b/test/js/third_party/jsonwebtoken/rsa-public.pem
new file mode 100644
index 000000000..9307812ab
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/rsa-public.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvzoCEC2rpSpJQaWZbUml
+sDNwp83Jr4fi6KmBWIwnj1MZ6CUQ7rBasuLI8AcfX5/10scSfQNCsTLV2tMKQaHu
+vyrVfwY0dINk+nkqB74QcT2oCCH9XduJjDuwWA4xLqAKuF96FsIes52opEM50W7/
+W7DZCKXkC8fFPFj6QF5ZzApDw2Qsu3yMRmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN
+40vXv9c4xiSafVvnx9BwYL7H1Q8NiK9LGEN6+JSWfgckQCs6UUBOXSZdreNN9zbQ
+Cwyzee7bOJqXUDAuLcFARzPw1EsZAyjVtGCKIQ0/btqK+jFunT2NBC8RItanDZpp
+tQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/test/js/third_party/jsonwebtoken/schema.test.js b/test/js/third_party/jsonwebtoken/schema.test.js
new file mode 100644
index 000000000..5d3845d46
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/schema.test.js
@@ -0,0 +1,72 @@
+var PS_SUPPORTED = true;
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+import fs from "fs";
+
+describe("schema", function () {
+ describe("sign options", function () {
+ var cert_rsa_priv = fs.readFileSync(__dirname + "/rsa-private.pem");
+ var cert_ecdsa_priv = fs.readFileSync(__dirname + "/ecdsa-private.pem");
+ var cert_secp384r1_priv = fs.readFileSync(__dirname + "/secp384r1-private.pem");
+ var cert_secp521r1_priv = fs.readFileSync(__dirname + "/secp521r1-private.pem");
+
+ function sign(options, secretOrPrivateKey) {
+ jwt.sign({ foo: 123 }, secretOrPrivateKey, options);
+ }
+
+ it("should validate algorithm", function () {
+ expect(function () {
+ sign({ algorithm: "foo" }, cert_rsa_priv);
+ }).toThrow(/"algorithm" must be a valid string enum value/);
+ sign({ algorithm: "none" }, null);
+ sign({ algorithm: "RS256" }, cert_rsa_priv);
+ sign({ algorithm: "RS384" }, cert_rsa_priv);
+ sign({ algorithm: "RS512" }, cert_rsa_priv);
+ if (PS_SUPPORTED) {
+ sign({ algorithm: "PS256" }, cert_rsa_priv);
+ sign({ algorithm: "PS384" }, cert_rsa_priv);
+ sign({ algorithm: "PS512" }, cert_rsa_priv);
+ }
+ sign({ algorithm: "ES256" }, cert_ecdsa_priv);
+ sign({ algorithm: "ES384" }, cert_secp384r1_priv);
+ sign({ algorithm: "ES512" }, cert_secp521r1_priv);
+ sign({ algorithm: "HS256" }, "superSecret");
+ sign({ algorithm: "HS384" }, "superSecret");
+ sign({ algorithm: "HS512" }, "superSecret");
+ });
+
+ it("should validate header", function () {
+ expect(function () {
+ sign({ header: "foo" }, "superSecret");
+ }).toThrow(/"header" must be an object/);
+ sign({ header: {} }, "superSecret");
+ });
+
+ it("should validate encoding", function () {
+ expect(function () {
+ sign({ encoding: 10 }, "superSecret");
+ }).toThrow(/"encoding" must be a string/);
+ sign({ encoding: "utf8" }, "superSecret");
+ });
+
+ it("should validate noTimestamp", function () {
+ expect(function () {
+ sign({ noTimestamp: 10 }, "superSecret");
+ }).toThrow(/"noTimestamp" must be a boolean/);
+ sign({ noTimestamp: true }, "superSecret");
+ });
+ });
+
+ describe("sign payload registered claims", function () {
+ function sign(payload) {
+ jwt.sign(payload, "foo123");
+ }
+
+ it("should validate exp", function () {
+ expect(function () {
+ sign({ exp: "1 monkey" });
+ }).toThrow(/"exp" should be a number of seconds/);
+ sign({ exp: 10.1 });
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/secp384r1-private.pem b/test/js/third_party/jsonwebtoken/secp384r1-private.pem
new file mode 100644
index 000000000..82336b6a2
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/secp384r1-private.pem
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCez58vZHVp+ArI7/fe835GAtRzE0AtrxGgQAY1U/uk2SQOaSw1ph61
+3Unr0ygS172gBwYFK4EEACKhZANiAARtwlnIqYqZxfiWR+/EM35nKHuLpOjUHiX1
+kEpSS03C9XlrBLNwLQfgjpYx9Qvqh26XAzTe74DYjcc748R+zZD2YAd3lV+OcdRE
+U+DWm4j5E6dlOXzvmw/3qxUcg3rRgR4=
+-----END EC PRIVATE KEY-----
diff --git a/test/js/third_party/jsonwebtoken/secp521r1-private.pem b/test/js/third_party/jsonwebtoken/secp521r1-private.pem
new file mode 100644
index 000000000..397a3df09
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/secp521r1-private.pem
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIBlWXKBKKCgTgf7+NS09TMv7/NO3RtMBn9xTe+46oNNNK405lrZ9mz
+WYtlsYvkdsc2Cx3v5V8JegaCOM+XtAZ0MNKgBwYFK4EEACOhgYkDgYYABAFNzaM7
+Zb9ug0p5KaZb5mjHrIshoVJSHaOXGtcjLVUakYVk0v9VsE+FKqyuLYcORUuAZdxl
+ITAlC5e5JZ0o8NEKbAE+8oOrePrItR3IFBtWO15p7qiRa2dBB8oQklFrmQaJYn4K
+fDV0hYpfu6ahpRNu2akR7aMXL/vXrptCH/n64q9KjA==
+-----END EC PRIVATE KEY-----
diff --git a/test/js/third_party/jsonwebtoken/set_headers.test.js b/test/js/third_party/jsonwebtoken/set_headers.test.js
new file mode 100644
index 000000000..2ed9831a5
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/set_headers.test.js
@@ -0,0 +1,16 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+
+describe("set header", function () {
+ it("should add the header", function () {
+ var token = jwt.sign({ foo: 123 }, "123", { header: { foo: "bar" } });
+ var decoded = jwt.decode(token, { complete: true });
+ expect(decoded.header.foo).toEqual("bar");
+ });
+
+ it("should allow overriding header", function () {
+ var token = jwt.sign({ foo: 123 }, "123", { header: { alg: "HS512" } });
+ var decoded = jwt.decode(token, { complete: true });
+ expect(decoded.header.alg).toEqual("HS512");
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/test-utils.js b/test/js/third_party/jsonwebtoken/test-utils.js
new file mode 100644
index 000000000..94a7e43e9
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/test-utils.js
@@ -0,0 +1,116 @@
+"use strict";
+
+import jwt from "jsonwebtoken";
+function expect(value) {
+ return {
+ toEqual: expected => {
+ if (typeof value === "object") {
+ if (typeof expected === "object") {
+ for (const propertyName in expected) {
+ expect(value[propertyName]).toEqual(expected[propertyName]);
+ }
+ return;
+ }
+ throw new Error(`Expected ${value} to strictly equal ${expected}`);
+ }
+ if (value !== expected) {
+ throw new Error(`Expected ${value} to equal ${expected}`);
+ }
+ },
+ toStrictEqual: expected => {
+ if (typeof value === "object") {
+ if (typeof expected === "object") {
+ for (const propertyName in expected) {
+ expect(value[propertyName]).toStrictEqual(expected[propertyName]);
+ }
+ return;
+ }
+ throw new Error(`Expected ${value} to strictly equal ${expected}`);
+ }
+ if (value !== expected) {
+ throw new Error(`Expected ${value} to strictly equal ${expected}`);
+ }
+ },
+ };
+}
+/**
+ * Correctly report errors that occur in an asynchronous callback
+ * @param {function(err): void} done The mocha callback
+ * @param {function(): void} testFunction The assertions function
+ */
+function asyncCheck(done, testFunction) {
+ try {
+ testFunction();
+ done();
+ } catch (err) {
+ done(err);
+ }
+}
+
+/**
+ * Base64-url encode a string
+ * @param str {string} The string to encode
+ * @returns {string} The encoded string
+ */
+function base64UrlEncode(str) {
+ return Buffer.from(str).toString("base64").replace(/[=]/g, "").replace(/\+/g, "-").replace(/\//g, "_");
+}
+
+/**
+ * Verify a JWT, ensuring that the asynchronous and synchronous calls to `verify` have the same result
+ * @param {string} jwtString The JWT as a string
+ * @param {string} secretOrPrivateKey The shared secret or private key
+ * @param {object} options Verify options
+ * @param {function(err, token):void} callback
+ */
+function verifyJWTHelper(jwtString, secretOrPrivateKey, options, callback) {
+ let error;
+ let syncVerified;
+ try {
+ syncVerified = jwt.verify(jwtString, secretOrPrivateKey, options);
+ } catch (err) {
+ error = err;
+ }
+ jwt.verify(jwtString, secretOrPrivateKey, options, (err, asyncVerifiedToken) => {
+ if (error) {
+ callback(err);
+ } else {
+ expect(syncVerified).toStrictEqual(asyncVerifiedToken);
+ callback(null, syncVerified);
+ }
+ });
+}
+
+/**
+ * Sign a payload to create a JWT, ensuring that the asynchronous and synchronous calls to `sign` have the same result
+ * @param {object} payload The JWT payload
+ * @param {string} secretOrPrivateKey The shared secret or private key
+ * @param {object} options Sign options
+ * @param {function(err, token):void} callback
+ */
+function signJWTHelper(payload, secretOrPrivateKey, options, callback) {
+ let error;
+ let syncSigned;
+ try {
+ syncSigned = jwt.sign(payload, secretOrPrivateKey, options);
+ } catch (err) {
+ error = err;
+ }
+ jwt.sign(payload, secretOrPrivateKey, options, (err, asyncSigned) => {
+ if (error) {
+ callback(err);
+ } else {
+ expect(syncSigned).toEqual(asyncSigned);
+ callback(null, syncSigned);
+ }
+ });
+}
+
+export { asyncCheck, base64UrlEncode, signJWTHelper, verifyJWTHelper };
+
+export default {
+ asyncCheck,
+ base64UrlEncode,
+ signJWTHelper,
+ verifyJWTHelper,
+};
diff --git a/test/js/third_party/jsonwebtoken/undefined_secretOrPublickey.test.js b/test/js/third_party/jsonwebtoken/undefined_secretOrPublickey.test.js
new file mode 100644
index 000000000..fb3f3b8d3
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/undefined_secretOrPublickey.test.js
@@ -0,0 +1,18 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+
+var TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M";
+
+describe("verifying without specified secret or public key", function () {
+ it("should not verify null", function () {
+ expect(function () {
+ jwt.verify(TOKEN, null);
+ }).toThrow(/secret or public key must be provided/);
+ });
+
+ it("should not verify undefined", function () {
+ expect(function () {
+ jwt.verify(TOKEN);
+ }).toThrow(/secret or public key must be provided/);
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/validateAsymmetricKey.test.js b/test/js/third_party/jsonwebtoken/validateAsymmetricKey.test.js
new file mode 100644
index 000000000..4bcf13cb0
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/validateAsymmetricKey.test.js
@@ -0,0 +1,209 @@
+import { expect, describe, it } from "bun:test";
+import { createPrivateKey } from "crypto";
+import fs from "fs";
+import path from "path";
+const PS_SUPPORTED = true;
+const ASYMMETRIC_KEY_DETAILS_SUPPORTED = true;
+const RSA_PSS_KEY_DETAILS_SUPPORTED = true;
+const allowedAlgorithmsForKeys = {
+ "ec": ["ES256", "ES384", "ES512"],
+ "rsa": ["RS256", "PS256", "RS384", "PS384", "RS512", "PS512"],
+ "rsa-pss": ["PS256", "PS384", "PS512"],
+};
+
+const allowedCurves = {
+ ES256: "prime256v1",
+ ES384: "secp384r1",
+ ES512: "secp521r1",
+};
+
+function validateAsymmetricKey(algorithm, key) {
+ if (!algorithm || !key) return;
+
+ const keyType = key.asymmetricKeyType;
+ if (!keyType) return;
+
+ const allowedAlgorithms = allowedAlgorithmsForKeys[keyType];
+
+ if (!allowedAlgorithms) {
+ throw new Error(`Unknown key type "${keyType}".`);
+ }
+
+ if (!allowedAlgorithms.includes(algorithm)) {
+ throw new Error(`"alg" parameter for "${keyType}" key type must be one of: ${allowedAlgorithms.join(", ")}.`);
+ }
+
+ /*
+ * Ignore the next block from test coverage because it gets executed
+ * conditionally depending on the Node version. Not ignoring it would
+ * prevent us from reaching the target % of coverage for versions of
+ * Node under 15.7.0.
+ */
+ /* istanbul ignore next */
+ if (ASYMMETRIC_KEY_DETAILS_SUPPORTED) {
+ switch (keyType) {
+ case "ec":
+ const keyCurve = key.asymmetricKeyDetails.namedCurve;
+ const allowedCurve = allowedCurves[algorithm];
+
+ if (keyCurve !== allowedCurve) {
+ throw new Error(`"alg" parameter "${algorithm}" requires curve "${allowedCurve}".`);
+ }
+ break;
+
+ case "rsa-pss":
+ if (RSA_PSS_KEY_DETAILS_SUPPORTED) {
+ const length = parseInt(algorithm.slice(-3), 10);
+ const { hashAlgorithm, mgf1HashAlgorithm, saltLength } = key.asymmetricKeyDetails;
+
+ if (hashAlgorithm !== `sha${length}` || mgf1HashAlgorithm !== hashAlgorithm) {
+ throw new Error(
+ `Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" ${algorithm}.`,
+ );
+ }
+
+ if (saltLength !== undefined && saltLength > length >> 3) {
+ throw new Error(
+ `Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" ${algorithm}.`,
+ );
+ }
+ }
+ break;
+ }
+ }
+}
+
+function loadKey(filename) {
+ return createPrivateKey(fs.readFileSync(path.join(__dirname, filename)));
+}
+
+const algorithmParams = {
+ RS256: {
+ invalidPrivateKey: loadKey("secp384r1-private.pem"),
+ },
+ ES256: {
+ invalidPrivateKey: loadKey("priv.pem"),
+ },
+};
+
+if (PS_SUPPORTED) {
+ algorithmParams.PS256 = {
+ invalidPrivateKey: loadKey("secp384r1-private.pem"),
+ };
+}
+
+describe("Asymmetric key validation", function () {
+ Object.keys(algorithmParams).forEach(function (algorithm) {
+ describe(algorithm, function () {
+ const keys = algorithmParams[algorithm];
+
+ describe("when validating a key with an invalid private key type", function () {
+ it("should throw an error", function () {
+ const expectedErrorMessage = /"alg" parameter for "[\w\d-]+" key type must be one of:/;
+
+ expect(function () {
+ validateAsymmetricKey(algorithm, keys.invalidPrivateKey);
+ }).toThrow(expectedErrorMessage);
+ });
+ });
+ });
+ });
+
+ describe("when the function has missing parameters", function () {
+ it("should pass the validation if no key has been provided", function () {
+ const algorithm = "ES256";
+ validateAsymmetricKey(algorithm);
+ });
+
+ it.todo("should pass the validation if no algorithm has been provided", function () {
+ const key = loadKey("dsa-private.pem");
+ validateAsymmetricKey(null, key);
+ });
+ });
+
+ describe("when validating a key with an unsupported type", function () {
+ it.todo("should throw an error", function () {
+ const algorithm = "RS256";
+ const key = loadKey("dsa-private.pem");
+ const expectedErrorMessage = 'Unknown key type "dsa".';
+
+ expect(function () {
+ validateAsymmetricKey(algorithm, key);
+ }).toThrow(expectedErrorMessage);
+ });
+ });
+
+ describe("Elliptic curve algorithms", function () {
+ const curvesAlgorithms = [
+ { algorithm: "ES256", curve: "prime256v1" },
+ { algorithm: "ES384", curve: "secp384r1" },
+ { algorithm: "ES512", curve: "secp521r1" },
+ ];
+
+ const curvesKeys = [
+ { curve: "prime256v1", key: loadKey("prime256v1-private.pem") },
+ { curve: "secp384r1", key: loadKey("secp384r1-private.pem") },
+ { curve: "secp521r1", key: loadKey("secp521r1-private.pem") },
+ ];
+
+ describe("when validating keys generated using Elliptic Curves", function () {
+ curvesAlgorithms.forEach(function (curveAlgorithm) {
+ curvesKeys.forEach(curveKeys => {
+ if (curveKeys.curve !== curveAlgorithm.curve) {
+ if (ASYMMETRIC_KEY_DETAILS_SUPPORTED) {
+ it(`should throw an error when validating an ${curveAlgorithm.algorithm} token for key with curve ${curveKeys.curve}`, function () {
+ expect(() => {
+ validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key);
+ }).toThrow(`"alg" parameter "${curveAlgorithm.algorithm}" requires curve "${curveAlgorithm.curve}".`);
+ });
+ } else {
+ it(`should pass the validation for incorrect keys if the Node version does not support checking the key's curve name`, function () {
+ expect(() => {
+ validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key);
+ }).not.toThrow();
+ });
+ }
+ } else {
+ it(`should accept an ${curveAlgorithm.algorithm} token for key with curve ${curveKeys.curve}`, function () {
+ expect(() => {
+ validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key);
+ }).not.toThrow();
+ });
+ }
+ });
+ });
+ });
+ });
+
+ if (RSA_PSS_KEY_DETAILS_SUPPORTED) {
+ describe.todo("RSA-PSS algorithms", function () {
+ // const key = loadKey('rsa-pss-private.pem');
+
+ it(`it should throw an error when validating a key with wrong RSA-RSS parameters`, function () {
+ const algorithm = "PS512";
+ expect(function () {
+ validateAsymmetricKey(algorithm, key);
+ }).toThrow(
+ 'Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" PS512',
+ );
+ });
+
+ it(`it should throw an error when validating a key with invalid salt length`, function () {
+ const algorithm = "PS256";
+ const shortSaltKey = loadKey("rsa-pss-invalid-salt-length-private.pem");
+ expect(function () {
+ validateAsymmetricKey(algorithm, shortSaltKey);
+ }).toThrow(
+ 'Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" PS256.',
+ );
+ });
+
+ it(`it should pass the validation when the key matches all the requirements for the algorithm`, function () {
+ expect(function () {
+ const algorithm = "PS256";
+ validateAsymmetricKey(algorithm, key);
+ }).not.toThrow();
+ });
+ });
+ }
+});
diff --git a/test/js/third_party/jsonwebtoken/verify.test.js b/test/js/third_party/jsonwebtoken/verify.test.js
new file mode 100644
index 000000000..c7583892f
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/verify.test.js
@@ -0,0 +1,318 @@
+import jwt from "jsonwebtoken";
+import { expect, describe, it, afterEach } from "bun:test";
+import jws from "jws";
+import sinon from "sinon";
+import fs from "fs";
+import path from "path";
+
+describe("verify", function () {
+ const pub = fs.readFileSync(path.join(__dirname, "pub.pem"));
+ const priv = fs.readFileSync(path.join(__dirname, "priv.pem"));
+
+ it("should first assume JSON claim set", function (done) {
+ const header = { alg: "RS256" };
+ const payload = { iat: Math.floor(Date.now() / 1000) };
+
+ const signed = jws.sign({
+ header: header,
+ payload: payload,
+ secret: priv,
+ encoding: "utf8",
+ });
+
+ jwt.verify(signed, pub, { typ: "JWT" }, function (err, p) {
+ if (err) return done(err);
+ expect(err).toBeNull();
+ expect(p).toEqual(payload);
+ done();
+ });
+ });
+
+ it("should not be able to verify unsigned token", function () {
+ const header = { alg: "none" };
+ const payload = { iat: Math.floor(Date.now() / 1000) };
+
+ const signed = jws.sign({
+ header: header,
+ payload: payload,
+ secret: "secret",
+ encoding: "utf8",
+ });
+
+ expect(function () {
+ jwt.verify(signed, "secret", { typ: "JWT" });
+ }).toThrow(/jwt signature is required/);
+ });
+
+ it("should not be able to verify unsigned token", function () {
+ const header = { alg: "none" };
+ const payload = { iat: Math.floor(Date.now() / 1000) };
+
+ const signed = jws.sign({
+ header: header,
+ payload: payload,
+ secret: "secret",
+ encoding: "utf8",
+ });
+
+ expect(function () {
+ jwt.verify(signed, undefined, { typ: "JWT" });
+ }).toThrow(/please specify "none" in "algorithms" to verify unsigned tokens/);
+ });
+
+ it("should be able to verify unsigned token when none is specified", function (done) {
+ const header = { alg: "none" };
+ const payload = { iat: Math.floor(Date.now() / 1000) };
+
+ const signed = jws.sign({
+ header: header,
+ payload: payload,
+ secret: "secret",
+ encoding: "utf8",
+ });
+
+ jwt.verify(signed, null, { typ: "JWT", algorithms: ["none"] }, function (err, p) {
+ if (err) return done(err);
+ expect(err).toBeNull();
+ expect(p).toEqual(payload);
+ done();
+ });
+ });
+
+ it("should not mutate options", function (done) {
+ const header = { alg: "HS256" };
+ const payload = { iat: Math.floor(Date.now() / 1000) };
+ const options = { typ: "JWT" };
+ const signed = jws.sign({
+ header: header,
+ payload: payload,
+ secret: "secret",
+ encoding: "utf8",
+ });
+
+ jwt.verify(signed, "secret", options, function (err) {
+ if (err) return done(err);
+ expect(err).toBeNull();
+ expect(Object.keys(options).length).toEqual(1);
+ done();
+ });
+ });
+
+ describe("secret or token as callback", function () {
+ const token =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU";
+ const key = "key";
+
+ const payload = { foo: "bar", iat: 1437018582, exp: 1437018592 };
+ const options = { algorithms: ["HS256"], ignoreExpiration: true };
+
+ it("without callback", function (done) {
+ jwt.verify(token, key, options, function (err, p) {
+ if (err) return done(err);
+ expect(err).toBeNull();
+ expect(p).toEqual(payload);
+ done();
+ });
+ });
+
+ it("simple callback", function (done) {
+ const keyFunc = function (header, callback) {
+ expect(header).toEqual({ alg: "HS256", typ: "JWT" });
+
+ callback(undefined, key);
+ };
+
+ jwt.verify(token, keyFunc, options, function (err, p) {
+ if (err) return done(err);
+ expect(err).toBeNull();
+ expect(p).toEqual(payload);
+ done();
+ });
+ });
+
+ it("should error if called synchronously", function (done) {
+ const keyFunc = function (header, callback) {
+ callback(undefined, key);
+ };
+
+ expect(function () {
+ jwt.verify(token, keyFunc, options);
+ }).toThrow(/verify must be called asynchronous if secret or public key is provided as a callback/);
+
+ done();
+ });
+
+ it("simple error", function (done) {
+ const keyFunc = function (header, callback) {
+ callback(new Error("key not found"));
+ };
+
+ jwt.verify(token, keyFunc, options, function (err, p) {
+ expect(err).toBeDefined();
+ expect(err.name).toBe("JsonWebTokenError");
+ expect(err.message).toMatch(/error in secret or public key callback/);
+ expect(p).toBeUndefined();
+ done();
+ });
+ });
+
+ it("delayed callback", function (done) {
+ const keyFunc = function (header, callback) {
+ setTimeout(function () {
+ callback(undefined, key);
+ }, 25);
+ };
+
+ jwt.verify(token, keyFunc, options, function (err, p) {
+ if (err) return done(err);
+ expect(err).toBeNull();
+ expect(p).toEqual(payload);
+ done();
+ });
+ });
+
+ it("delayed error", function (done) {
+ const keyFunc = function (header, callback) {
+ setTimeout(function () {
+ callback(new Error("key not found"));
+ }, 25);
+ };
+
+ jwt.verify(token, keyFunc, options, function (err, p) {
+ expect(err).toBeDefined();
+ expect(err.name).toBe("JsonWebTokenError");
+ expect(err.message).toMatch(/error in secret or public key callback/);
+ expect(p).toBeUndefined();
+ done();
+ });
+ });
+ });
+
+ describe("expiration", function () {
+ // { foo: 'bar', iat: 1437018582, exp: 1437018592 }
+ const token =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU";
+ const key = "key";
+
+ let clock;
+ afterEach(function () {
+ try {
+ clock.restore();
+ } catch (e) {}
+ });
+
+ it("should error on expired token", function (done) {
+ clock = sinon.useFakeTimers(1437018650000); // iat + 58s, exp + 48s
+ const options = { algorithms: ["HS256"] };
+
+ jwt.verify(token, key, options, function (err, p) {
+ expect(err).toBeDefined();
+ expect(err.name).toBe("TokenExpiredError");
+ expect(err.message).toBe("jwt expired");
+ expect(err?.expiredAt?.constructor?.name).toBe("Date");
+ expect(Number(err.expiredAt)).toBe(1437018592000);
+ expect(p).toBeUndefined();
+ done();
+ });
+ });
+
+ it("should not error on expired token within clockTolerance interval", function (done) {
+ clock = sinon.useFakeTimers(1437018594000); // iat + 12s, exp + 2s
+ const options = { algorithms: ["HS256"], clockTolerance: 5 };
+
+ jwt.verify(token, key, options, function (err, p) {
+ expect(err).toBeNull();
+ expect(p.foo).toBe("bar");
+ done();
+ });
+ });
+
+ describe("option: clockTimestamp", function () {
+ const clockTimestamp = 1000000000;
+ it("should verify unexpired token relative to user-provided clockTimestamp", function (done) {
+ const token = jwt.sign({ foo: "bar", iat: clockTimestamp, exp: clockTimestamp + 1 }, key);
+ jwt.verify(token, key, { clockTimestamp: clockTimestamp }, function (err) {
+ expect(err).toBeNull();
+ done();
+ });
+ });
+ it("should error on expired token relative to user-provided clockTimestamp", function (done) {
+ const token = jwt.sign({ foo: "bar", iat: clockTimestamp, exp: clockTimestamp + 1 }, key);
+ jwt.verify(token, key, { clockTimestamp: clockTimestamp + 1 }, function (err, p) {
+ expect(err).toBeDefined();
+ expect(err.name).toBe("TokenExpiredError");
+ expect(err.message).toBe("jwt expired");
+ expect(err?.expiredAt?.constructor?.name).toBe("Date");
+ expect(Number(err.expiredAt)).toBe((clockTimestamp + 1) * 1000);
+ expect(p).toBeUndefined();
+ done();
+ });
+ });
+ it("should verify clockTimestamp is a number", function (done) {
+ const token = jwt.sign({ foo: "bar", iat: clockTimestamp, exp: clockTimestamp + 1 }, key);
+ jwt.verify(token, key, { clockTimestamp: "notANumber" }, function (err, p) {
+ expect(err).toBeDefined();
+ expect(err.name).toBe("JsonWebTokenError");
+ expect(err.message).toBe("clockTimestamp must be a number");
+ expect(p).toBeUndefined();
+ done();
+ });
+ });
+ });
+
+ describe("option: maxAge and clockTimestamp", function () {
+ // { foo: 'bar', iat: 1437018582, exp: 1437018800 } exp = iat + 218s
+ const token =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODgwMH0.AVOsNC7TiT-XVSpCpkwB1240izzCIJ33Lp07gjnXVpA";
+ it("cannot be more permissive than expiration", function (done) {
+ const clockTimestamp = 1437018900; // iat + 318s (exp: iat + 218s)
+ const options = { algorithms: ["HS256"], clockTimestamp: clockTimestamp, maxAge: "1000y" };
+
+ jwt.verify(token, key, options, function (err, p) {
+ // maxAge not exceded, but still expired
+ expect(err).toBeDefined();
+ expect(err.name).toBe("TokenExpiredError");
+ expect(err.message).toBe("jwt expired");
+ expect(err?.expiredAt?.constructor?.name).toBe("Date");
+ expect(Number(err.expiredAt)).toBe(1437018800000);
+ expect(p).toBeUndefined();
+ done();
+ });
+ });
+ });
+ });
+
+ describe.todo("when verifying a token with an unsupported public key type", function () {
+ it("should throw an error", function () {
+ const token =
+ "eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2Njk5OTAwMDN9.YdjFWJtPg_9nccMnTfQyesWQ0UX-GsWrfCGit_HqjeIkNjoV6dkAJ8AtbnVEhA4oxwqSXx6ilMOfHEjmMlPtyyyVKkWKQHcIWYnqPbNSEv8a7Men8KhJTIWb4sf5YbhgSCpNvU_VIZjLO1Z0PzzgmEikp0vYbxZFAbCAlZCvUlcIc-kdjIRCnDJe0BBrYRxNLEJtYsf7D1yFIFIqw8-VP87yZdExA4eHsTaE84SgnL24ZK5h5UooDx-IRNd_rrMyio8kNy63grVxCWOtkXZ26iZk6v-HMsnBqxvUwR6-8wfaWrcpADkyUO1q3SNsoTdwtflbvfwgjo3uve0IvIzHMw";
+ const key = fs.readFileSync(path.join(__dirname, "dsa-public.pem"));
+
+ expect(function () {
+ jwt.verify(token, key);
+ }).toThrow('Unknown key type "dsa".');
+ });
+ });
+
+ describe("when verifying a token with an incorrect public key type", function () {
+ it("should throw a validation error if key validation is enabled", function () {
+ const token =
+ "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXkiOiJsb2FkIiwiaWF0IjoxNjcwMjMwNDE2fQ.7TYP8SB_9Tw1fNIfuG60b4tvoLPpDAVBQpV1oepnuKwjUz8GOw4fRLzclo0Q2YAXisJ3zIYMEFsHpYrflfoZJQ";
+ const key = fs.readFileSync(path.join(__dirname, "rsa-public.pem"));
+
+ expect(function () {
+ jwt.verify(token, key, { algorithms: ["ES256"] });
+ }).toThrow('"alg" parameter for "rsa" key type must be one of: RS256, PS256, RS384, PS384, RS512, PS512.');
+ });
+
+ it("should throw an unknown error if key validation is disabled", function () {
+ const token =
+ "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXkiOiJsb2FkIiwiaWF0IjoxNjcwMjMwNDE2fQ.7TYP8SB_9Tw1fNIfuG60b4tvoLPpDAVBQpV1oepnuKwjUz8GOw4fRLzclo0Q2YAXisJ3zIYMEFsHpYrflfoZJQ";
+ const key = fs.readFileSync(path.join(__dirname, "rsa-public.pem"));
+
+ expect(function () {
+ jwt.verify(token, key, { algorithms: ["ES256"], allowInvalidAsymmetricKeyTypes: true });
+ }).not.toThrow('"alg" parameter for "rsa" key type must be one of: RS256, PS256, RS384, PS384, RS512, PS512.');
+ });
+ });
+});
diff --git a/test/js/third_party/jsonwebtoken/wrong_alg.test.js b/test/js/third_party/jsonwebtoken/wrong_alg.test.js
new file mode 100644
index 000000000..948e467f9
--- /dev/null
+++ b/test/js/third_party/jsonwebtoken/wrong_alg.test.js
@@ -0,0 +1,49 @@
+var PS_SUPPORTED = true;
+import jwt from "jsonwebtoken";
+import { expect, describe, it } from "bun:test";
+import path from "path";
+import fs from "fs";
+
+var pub = fs.readFileSync(path.join(__dirname, "pub.pem"), "utf8");
+// priv is never used
+// var priv = fs.readFileSync(path.join(__dirname, 'priv.pem'));
+
+var TOKEN =
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MjY1NDY5MTl9.ETgkTn8BaxIX4YqvUWVFPmum3moNZ7oARZtSBXb_vP4";
+
+describe("when setting a wrong `header.alg`", function () {
+ describe("signing with pub key as symmetric", function () {
+ it("should not verify", function () {
+ expect(function () {
+ jwt.verify(TOKEN, pub);
+ }).toThrow(/invalid algorithm/);
+ });
+ });
+
+ describe("signing with pub key as HS256 and whitelisting only RS256", function () {
+ it("should not verify", function () {
+ expect(function () {
+ jwt.verify(TOKEN, pub, { algorithms: ["RS256"] });
+ }).toThrow(/invalid algorithm/);
+ });
+ });
+
+ if (PS_SUPPORTED) {
+ describe("signing with pub key as HS256 and whitelisting only PS256", function () {
+ it("should not verify", function () {
+ expect(function () {
+ jwt.verify(TOKEN, pub, { algorithms: ["PS256"] });
+ }).toThrow(/invalid algorithm/);
+ });
+ });
+ }
+
+ describe("signing with HS256 and checking with HS384", function () {
+ it("should not verify", function () {
+ expect(function () {
+ var token = jwt.sign({ foo: "bar" }, "secret", { algorithm: "HS256" });
+ jwt.verify(token, "some secret", { algorithms: ["HS384"] });
+ }).toThrow(/invalid algorithm/);
+ });
+ });
+});
diff --git a/test/js/web/abort/abort.signal.ts b/test/js/web/abort/abort.signal.ts
new file mode 100644
index 000000000..da402f637
--- /dev/null
+++ b/test/js/web/abort/abort.signal.ts
@@ -0,0 +1,24 @@
+import type { Server } from "bun";
+
+const server = Bun.serve({
+ port: 0,
+ async fetch() {
+ const signal = AbortSignal.timeout(1);
+ return await fetch("https://bun.sh", { signal });
+ },
+});
+
+function hostname(server: Server) {
+ if (server.hostname.startsWith(":")) return `[${server.hostname}]`;
+ return server.hostname;
+}
+
+let url = `http://${hostname(server)}:${server.port}/`;
+
+const responses: Response[] = [];
+for (let i = 0; i < 10; i++) {
+ responses.push(await fetch(url));
+}
+server.stop(true);
+// we fail if any of the requests succeeded
+process.exit(responses.every(res => res.status === 500) ? 0 : 1);
diff --git a/test/js/web/abort/abort.test.ts b/test/js/web/abort/abort.test.ts
index 4895e0d13..ef0b07a18 100644
--- a/test/js/web/abort/abort.test.ts
+++ b/test/js/web/abort/abort.test.ts
@@ -18,4 +18,25 @@ describe("AbortSignal", () => {
expect(stderr?.toString()).not.toContain("✗");
});
+
+ test("AbortSignal.timeout(n) should not freeze the process", async () => {
+ const fileName = join(import.meta.dir, "abort.signal.ts");
+
+ const server = Bun.spawn({
+ cmd: [bunExe(), fileName],
+ env: bunEnv,
+ cwd: tmpdir(),
+ });
+
+ const exitCode = await Promise.race([
+ server.exited,
+ (async () => {
+ await Bun.sleep(5000);
+ server.kill();
+ return 2;
+ })(),
+ ]);
+
+ expect(exitCode).toBe(0);
+ });
});
diff --git a/test/js/web/fetch/fetch.stream.test.ts b/test/js/web/fetch/fetch.stream.test.ts
index 98271ee79..82c63ba53 100644
--- a/test/js/web/fetch/fetch.stream.test.ts
+++ b/test/js/web/fetch/fetch.stream.test.ts
@@ -28,6 +28,142 @@ const smallText = Buffer.from("Hello".repeat(16));
const empty = Buffer.alloc(0);
describe("fetch() with streaming", () => {
+ it(`should be able to fail properly when reading from readable stream`, async () => {
+ let server: Server | null = null;
+ try {
+ server = Bun.serve({
+ port: 0,
+ async fetch(req) {
+ return new Response(
+ new ReadableStream({
+ async start(controller) {
+ controller.enqueue("Hello, World!");
+ await Bun.sleep(1000);
+ controller.enqueue("Hello, World!");
+ controller.close();
+ },
+ }),
+ {
+ status: 200,
+ headers: {
+ "Content-Type": "text/plain",
+ },
+ },
+ );
+ },
+ });
+
+ const server_url = `http://${server.hostname}:${server.port}`;
+ try {
+ const res = await fetch(server_url, { signal: AbortSignal.timeout(20) });
+ const reader = res.body?.getReader();
+ while (true) {
+ const { done } = await reader?.read();
+ if (done) break;
+ }
+ expect(true).toBe("unreachable");
+ } catch (err: any) {
+ if (err.name !== "TimeoutError") throw err;
+ expect(err.message).toBe("The operation timed out.");
+ }
+ } finally {
+ server?.stop();
+ }
+ });
+
+ it(`should be locked after start buffering`, async () => {
+ let server: Server | null = null;
+ try {
+ server = Bun.serve({
+ port: 0,
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ async start(controller) {
+ controller.enqueue("Hello, World!");
+ await Bun.sleep(10);
+ controller.enqueue("Hello, World!");
+ await Bun.sleep(10);
+ controller.enqueue("Hello, World!");
+ await Bun.sleep(10);
+ controller.enqueue("Hello, World!");
+ await Bun.sleep(10);
+ controller.close();
+ },
+ }),
+ {
+ status: 200,
+ headers: {
+ "Content-Type": "text/plain",
+ },
+ },
+ );
+ },
+ });
+
+ const server_url = `http://${server.hostname}:${server.port}`;
+ const res = await fetch(server_url);
+ try {
+ const promise = res.text(); // start buffering
+ res.body?.getReader(); // get a reader
+ const result = await promise; // should throw the right error
+ expect(result).toBe("unreachable");
+ } catch (err: any) {
+ if (err.name !== "TypeError") throw err;
+ expect(err.message).toBe("ReadableStream is locked");
+ }
+ } finally {
+ server?.stop();
+ }
+ });
+
+ it(`should be locked after start buffering when calling getReader`, async () => {
+ let server: Server | null = null;
+ try {
+ server = Bun.serve({
+ port: 0,
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ async start(controller) {
+ controller.enqueue("Hello, World!");
+ await Bun.sleep(10);
+ controller.enqueue("Hello, World!");
+ await Bun.sleep(10);
+ controller.enqueue("Hello, World!");
+ await Bun.sleep(10);
+ controller.enqueue("Hello, World!");
+ await Bun.sleep(10);
+ controller.close();
+ },
+ }),
+ {
+ status: 200,
+ headers: {
+ "Content-Type": "text/plain",
+ },
+ },
+ );
+ },
+ });
+
+ const server_url = `http://${server.hostname}:${server.port}`;
+ const res = await fetch(server_url);
+ try {
+ const body = res.body as ReadableStream<Uint8Array>;
+ const promise = res.text(); // start buffering
+ body.getReader(); // get a reader
+ const result = await promise; // should throw the right error
+ expect(result).toBe("unreachable");
+ } catch (err: any) {
+ if (err.name !== "TypeError") throw err;
+ expect(err.message).toBe("ReadableStream is locked");
+ }
+ } finally {
+ server?.stop();
+ }
+ });
+
it("can deflate with and without headers #4478", async () => {
let server: Server | null = null;
try {