aboutsummaryrefslogtreecommitdiff
path: root/test/js
diff options
context:
space:
mode:
authorGravatar Dylan Conway <dylan.conway567@gmail.com> 2023-10-14 12:58:30 -0700
committerGravatar Dylan Conway <dylan.conway567@gmail.com> 2023-10-14 12:58:30 -0700
commitf9add8b6bea4df3cdbd56a21f17e4cab1a854e4e (patch)
tree8e5306104d81c67b771181337bba02cd9ec39453 /test/js
parent81a1a58d66c598ea35c42453d0ba4c6341a940fc (diff)
parent9b5e66453b0879ed77b71dcdbe50e4efa184261e (diff)
downloadbun-sdl.tar.gz
bun-sdl.tar.zst
bun-sdl.zip
Merge branch 'main' into sdlsdl
Diffstat (limited to 'test/js')
-rw-r--r--test/js/bun/dns/resolve-dns.test.ts126
-rw-r--r--test/js/bun/io/bun-write.test.js25
-rw-r--r--test/js/bun/test/expect.test.js21
-rw-r--r--test/js/bun/test/jest-extended.test.js21
-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/dns/node-dns.test.js11
-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
88 files changed, 6678 insertions, 41 deletions
diff --git a/test/js/bun/dns/resolve-dns.test.ts b/test/js/bun/dns/resolve-dns.test.ts
index 6102b0745..57b3a50c7 100644
--- a/test/js/bun/dns/resolve-dns.test.ts
+++ b/test/js/bun/dns/resolve-dns.test.ts
@@ -1,50 +1,96 @@
import { dns } from "bun";
import { describe, expect, it, test } from "bun:test";
import { withoutAggressiveGC } from "harness";
+import { isIP, isIPv4, isIPv6 } from "node:net";
-describe("dns.lookup", () => {
- const backends = [process.platform === "darwin" ? "system" : undefined, "libc", "c-ares"].filter(x => !!x) as (
- | "system"
- | "libc"
- | "c-ares"
- )[];
- for (let backend of backends) {
- it(backend + " parallell x 10", async () => {
- const promises = [];
- for (let i = 0; i < 10; i++) {
- promises.push(dns.lookup("localhost", { backend }));
- }
- const results = (await Promise.all(promises)).flat();
- withoutAggressiveGC(() => {
- for (let { family, address } of results) {
- if (family === 4) {
- expect(address).toBe("127.0.0.1");
- } else if (family === 6) {
- expect(address).toBe("::1");
- } else {
- throw new Error("Unknown family");
+const backends = ["system", "libc", "c-ares"];
+const validHostnames = ["localhost", "example.com"];
+const invalidHostnames = [`this-should-not-exist-${Math.floor(Math.random() * 99999)}.com`];
+const malformedHostnames = ["", " ", ".", " .", "localhost:80", "this is not a hostname"];
+
+describe("dns", () => {
+ describe.each(backends)("lookup() [backend: %s]", backend => {
+ describe.each(validHostnames)("%s", hostname => {
+ test.each([
+ {
+ options: { backend },
+ address: isIP,
+ },
+ {
+ options: { backend, family: 4 },
+ address: isIPv4,
+ family: 4,
+ },
+ {
+ options: { backend, family: "IPv4" },
+ address: isIPv4,
+ family: 4,
+ },
+ {
+ options: { backend, family: 6 },
+ address: isIPv6,
+ family: 6,
+ },
+ {
+ options: { backend, family: "IPv6" },
+ address: isIPv6,
+ family: 6,
+ },
+ {
+ options: { backend, family: 0 },
+ address: isIP,
+ },
+ {
+ options: { backend, family: "any" },
+ address: isIP,
+ },
+ ])("%j", async ({ options, address: expectedAddress, family: expectedFamily }) => {
+ // @ts-expect-error
+ const result = await dns.lookup(hostname, options);
+ expect(result).toBeArray();
+ expect(result.length).toBeGreaterThan(0);
+ withoutAggressiveGC(() => {
+ for (const { family, address, ttl } of result) {
+ expect(address).toBeString();
+ expect(expectedAddress(address)).toBeTruthy();
+ expect(family).toBeInteger();
+ if (expectedFamily !== undefined) {
+ expect(family).toBe(expectedFamily);
+ }
+ expect(ttl).toBeInteger();
}
- }
+ });
});
});
-
- it(backend + " remote", async () => {
- const [first, second] = await dns.lookup("google.com", { backend });
- console.log(first, second);
- });
- it(backend + " local", async () => {
- const [first, second] = await dns.lookup("localhost", { backend });
- console.log(first, second);
+ test.each(validHostnames)("%s [parallel x 10]", async hostname => {
+ const results = await Promise.all(
+ // @ts-expect-error
+ Array.from({ length: 10 }, () => dns.lookup(hostname, { backend })),
+ );
+ const answers = results.flat();
+ expect(answers).toBeArray();
+ expect(answers.length).toBeGreaterThan(10);
+ withoutAggressiveGC(() => {
+ for (const { family, address, ttl } of answers) {
+ expect(address).toBeString();
+ expect(isIP(address)).toBeTruthy();
+ expect(family).toBeInteger();
+ expect(ttl).toBeInteger();
+ }
+ });
});
-
- it(backend + " failing domain throws an error without taking a very long time", async () => {
- try {
- await dns.lookup("yololololololo1234567.com", { backend });
- throw 42;
- } catch (e: any) {
- expect(typeof e).not.toBe("number");
- expect(e.code).toBe("DNS_ENOTFOUND");
- }
+ test.each(invalidHostnames)("%s", hostname => {
+ // @ts-expect-error
+ expect(dns.lookup(hostname, { backend })).rejects.toMatchObject({
+ code: "DNS_ENOTFOUND",
+ });
});
- }
+ // TODO: causes segfaults
+ // test.each(malformedHostnames)("%s", (hostname) => {
+ // // @ts-expect-error
+ // expect(dns.lookup(hostname, { backend })).rejects.toMatchObject({
+ // code: "DNS_ENOTFOUND",
+ // });
+ // });
+ });
});
diff --git a/test/js/bun/io/bun-write.test.js b/test/js/bun/io/bun-write.test.js
index a05fa283a..fbcc99b66 100644
--- a/test/js/bun/io/bun-write.test.js
+++ b/test/js/bun/io/bun-write.test.js
@@ -80,6 +80,31 @@ it("Bun.file not found returns ENOENT", async () => {
await gcTick();
});
+it("Bun.write file not found returns ENOENT, issue#6336", async () => {
+ const dst = Bun.file(path.join(tmpdir(), "does/not/exist.txt"));
+ try {
+ await gcTick();
+ await Bun.write(dst, "");
+ await gcTick();
+ } catch (exception) {
+ expect(exception.code).toBe("ENOENT");
+ expect(exception.path).toBe(dst.name);
+ }
+
+ const src = Bun.file(path.join(tmpdir(), `test-bun-write-${Date.now()}.txt`));
+ await Bun.write(src, "");
+ try {
+ await gcTick();
+ await Bun.write(dst, src);
+ await gcTick();
+ } catch (exception) {
+ expect(exception.code).toBe("ENOENT");
+ expect(exception.path).toBe(dst.name);
+ } finally {
+ fs.unlinkSync(src.name);
+ }
+});
+
it("Bun.write('out.txt', 'string')", async () => {
for (let erase of [true, false]) {
if (erase) {
diff --git a/test/js/bun/test/expect.test.js b/test/js/bun/test/expect.test.js
index 8c9959d01..13b549179 100644
--- a/test/js/bun/test/expect.test.js
+++ b/test/js/bun/test/expect.test.js
@@ -3080,6 +3080,27 @@ describe("expect()", () => {
expect(Infinity).not.toBeWithin(-Infinity, Infinity);
});
+ test("toEqualIgnoringWhitespace()", () => {
+ expect("hello world").toEqualIgnoringWhitespace("hello world");
+ expect(" hello world ").toEqualIgnoringWhitespace("hello world");
+ expect(" h e l l o w o r l d ").toEqualIgnoringWhitespace("hello world");
+ expect(" hello\nworld ").toEqualIgnoringWhitespace("hello\nworld");
+ expect(`h
+ e
+ l
+ l
+ o`).toEqualIgnoringWhitespace("hello");
+ expect(`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec posuere felis. Aliquam tincidunt elit a nunc hendrerit maximus. Morbi semper tristique lectus, eget ullamcorper velit ullamcorper non. Aenean nibh augue, ultrices id ornare quis, eleifend id metus. Aliquam erat volutpat. Proin maximus, ligula at consequat venenatis, sapien odio auctor mi, sit amet placerat augue odio et orci. Vivamus tempus hendrerit tortor, et interdum est semper malesuada. Ut venenatis iaculis felis eget euismod. Suspendisse sed nisi eget massa fringilla rhoncus non quis enim. Mauris feugiat pellentesque justo, at sagittis augue sollicitudin vel. Pellentesque porttitor consequat mi nec varius. Praesent aliquet at justo nec finibus. Donec ut lorem eu ex dignissim pulvinar at sit amet sem. Ut fringilla sit amet dolor vitae convallis. Ut faucibus a purus sit amet fermentum.
+ Sed sit amet tortor magna. Pellentesque laoreet lorem at pulvinar efficitur. Nulla dictum nibh ac gravida semper. Duis tempus elit in ipsum feugiat porttitor.`).toEqualIgnoringWhitespace(
+ `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec posuere felis. Aliquam tincidunt elit a nunc hendrerit maximus. Morbi semper tristique lectus, eget ullamcorper velit ullamcorper non. Aenean nibh augue, ultrices id ornare quis, eleifend id metus. Aliquam erat volutpat. Proin maximus, ligula at consequat venenatis, sapien odio auctor mi, sit amet placerat augue odio et orci. Vivamus tempus hendrerit tortor, et interdum est semper malesuada. Ut venenatis iaculis felis eget euismod. Suspendisse sed nisi eget massa fringilla rhoncus non quis enim. Mauris feugiat pellentesque justo, at sagittis augue sollicitudin vel. Pellentesque porttitor consequat mi nec varius. Praesent aliquet at justo nec finibus. Donec ut lorem eu ex dignissim pulvinar at sit amet sem. Ut fringilla sit amet dolor vitae convallis. Ut faucibus a purus sit amet fermentum. Sed sit amet tortor magna. Pellentesque laoreet lorem at pulvinar efficitur. Nulla dictum nibh ac gravida semper. Duis tempus elit in ipsum feugiat porttitor.`,
+ );
+
+ expect("hello world").not.toEqualIgnoringWhitespace("hello world!");
+ expect(() => {
+ expect({}).not.toEqualIgnoringWhitespace({});
+ }).toThrow();
+ });
+
test("toBeSymbol()", () => {
expect(Symbol()).toBeSymbol();
expect(Symbol("")).toBeSymbol();
diff --git a/test/js/bun/test/jest-extended.test.js b/test/js/bun/test/jest-extended.test.js
index 7e018d310..0b8174433 100644
--- a/test/js/bun/test/jest-extended.test.js
+++ b/test/js/bun/test/jest-extended.test.js
@@ -587,7 +587,26 @@ describe("jest-extended", () => {
});
// test("toIncludeMultiple()")
- // test("toEqualIgnoringWhitespace()")
+ test("toEqualIgnoringWhitespace()", () => {
+ expect("hello world").toEqualIgnoringWhitespace("hello world");
+ expect(" hello world ").toEqualIgnoringWhitespace("hello world");
+ expect(" h e l l o w o r l d ").toEqualIgnoringWhitespace("hello world");
+ expect(" hello\nworld ").toEqualIgnoringWhitespace("hello\nworld");
+ expect(`h
+ e
+ l
+ l
+ o`).toEqualIgnoringWhitespace("hello");
+ expect(`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec posuere felis. Aliquam tincidunt elit a nunc hendrerit maximus. Morbi semper tristique lectus, eget ullamcorper velit ullamcorper non. Aenean nibh augue, ultrices id ornare quis, eleifend id metus. Aliquam erat volutpat. Proin maximus, ligula at consequat venenatis, sapien odio auctor mi, sit amet placerat augue odio et orci. Vivamus tempus hendrerit tortor, et interdum est semper malesuada. Ut venenatis iaculis felis eget euismod. Suspendisse sed nisi eget massa fringilla rhoncus non quis enim. Mauris feugiat pellentesque justo, at sagittis augue sollicitudin vel. Pellentesque porttitor consequat mi nec varius. Praesent aliquet at justo nec finibus. Donec ut lorem eu ex dignissim pulvinar at sit amet sem. Ut fringilla sit amet dolor vitae convallis. Ut faucibus a purus sit amet fermentum.
+ Sed sit amet tortor magna. Pellentesque laoreet lorem at pulvinar efficitur. Nulla dictum nibh ac gravida semper. Duis tempus elit in ipsum feugiat porttitor.`).toEqualIgnoringWhitespace(
+ `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec posuere felis. Aliquam tincidunt elit a nunc hendrerit maximus. Morbi semper tristique lectus, eget ullamcorper velit ullamcorper non. Aenean nibh augue, ultrices id ornare quis, eleifend id metus. Aliquam erat volutpat. Proin maximus, ligula at consequat venenatis, sapien odio auctor mi, sit amet placerat augue odio et orci. Vivamus tempus hendrerit tortor, et interdum est semper malesuada. Ut venenatis iaculis felis eget euismod. Suspendisse sed nisi eget massa fringilla rhoncus non quis enim. Mauris feugiat pellentesque justo, at sagittis augue sollicitudin vel. Pellentesque porttitor consequat mi nec varius. Praesent aliquet at justo nec finibus. Donec ut lorem eu ex dignissim pulvinar at sit amet sem. Ut fringilla sit amet dolor vitae convallis. Ut faucibus a purus sit amet fermentum. Sed sit amet tortor magna. Pellentesque laoreet lorem at pulvinar efficitur. Nulla dictum nibh ac gravida semper. Duis tempus elit in ipsum feugiat porttitor.`,
+ );
+
+ expect("hello world").not.toEqualIgnoringWhitespace("hello world!");
+ expect(() => {
+ expect({}).not.toEqualIgnoringWhitespace({});
+ }).toThrow("requires argument to be a string");
+ });
// Symbol
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/dns/node-dns.test.js b/test/js/node/dns/node-dns.test.js
index d549017b2..1988e9155 100644
--- a/test/js/node/dns/node-dns.test.js
+++ b/test/js/node/dns/node-dns.test.js
@@ -394,3 +394,14 @@ describe("dns.lookupService", () => {
expect(service).toStrictEqual(expected[1]);
});
});
+
+// Deprecated reference: https://nodejs.org/api/deprecations.html#DEP0118
+describe("lookup deprecated behavior", () => {
+ it.each([undefined, false, null, NaN, ""])("dns.lookup", domain => {
+ dns.lookup(domain, (error, address, family) => {
+ expect(error).toBeNull();
+ expect(address).toBeNull();
+ expect(family).toBe(4);
+ });
+ });
+});
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 {