mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 19:38:58 +00:00
* WIP sync close (shows ref count bug in stream)
* fix closing on PipeWriter and PipeReader
* remove old todos
* join
* Some shell changes
at least it compiles
* fix some compile errors
* fix ref/unref server on windows
* actually use the ref count in this places
* make windows compile again
* more tests passing
* Make shell compile again
* Slowly remove some `@panic("TODO SHELL")`
* Eliminate `@panic("TODO SHELL")` for BufferedWriter
* Holy cleansing of `@panic("TODO SHELL")`
at least it compiles now
* Okay now the shell compiles, but segfaults
* Fix compiler errors
* more stable stream and now Content-Range pass
* make windows compile again
* revert stuff until the fix is actually ready
* revert onDone thing
* Fix buffered writer for shell
* Fix buffered writer + shell/subproc.zig and windows build
* Fix for #8982 got lost in the merge
* Actually buffer subproc output
* Fix some stuff shell
* oops
* fix context deinit
* fix renderMissing
* shell: Fix array buffer
* more stable streams (#9053)
fix stream ref counting
* wip
* Remove `@panic("TODO")` on shell event loop tasks and Redirect open flags got lost in merge
* Support redirects
* fixes
cc @cirospaciari
* Update ReadableStreamInternals.ts
* Fix spurious error
* Update stream.js
* leak
* Fix UAF
cc @cirospaciari
* Fix memory leaks
* HOLY FUCK big refactor
* misc cleanup
* shell: Fix a bunch of tests
* clean up
* gitignore: fix ending newline
* get windows compiling again
* tidy
* hide linker warn with icu
* closeIfPossible
* Better leak test
* Fix forgetting to decrement reference count
* Update stdio.zig
* Fix shell windows build
* Stupid unreachable
* Woops
* basic echo hi works on windows
* Fix flaky test on Windows
* Fix windows regression in Bun.main (#9156)
* Fix windows regression in Bun.main
* Handle invalid handles
* Fix flaky test
* Better launch config
* Fixup
* Make this test less flaky on Windows
* Fixup
* Cygwin
* Support signal codes in subprocess.kill(), resolve file path
* Treat null as ignore
* Ignore carriage returns
* Fixup
* shell: Fix IOWriter bug
* shell: Use custom `open()`/`openat()`
* windows shell subproc works
* zack commit
* I think I understand WindowsStreamingWriter
* fix thing
* why were we doing this in tests
* shell: Fix rm
* shell: Add rm -rf node_modules/ test
* shell: use `.runAsTest()` in some places to make it easier to determine which test failed
* [autofix.ci] apply automated fixes
* woopsie
* Various changes
* Fix
* shell: abstract output task logic
* shell: mkdir builtin
* fixup
* stuff
* shell: Make writing length of 0 in IOWriter immediately resolve
* shell: Implement `touch`
* shell: basic `cat` working
* Make it compile on windows
* shell: Fix IOReader bug
* [autofix.ci] apply automated fixes
* fix windows kill on subprocess/process
* fix dns tests to match behavior on windows (same as nodejs)
* fix windows ci
* again
* move `close_handle` to flags in `PipeWriter` and fix shell hanging
* Fix `ls` not giving non-zero exit code on error
* Handle edgecase in is_atty
* Fix writer.flush() when there's no data
* Fix some tests
* Disable uv_unref on uv_process_t on Windows, for now.
* fix writer.end
* fix stdout.write
* fix child-process on win32
* Make this test less flaky on Windows
* Add assertion
* Make these the same
* Make it pass on windows
* Don't commit
* Log the test name
* Make this test less flaky on windows
* Make this test less flaky on windows
* Print which test is taking awhile in the runner
* fixups
* Fixups
* Add some assertions
* Bring back test concurrency
* shell: bring back redirect stdin
* make it compile again cc @zackradisic
* initialize env map with capacity
* some fixes
* cleanup
* oops
* fix leak, fix done
* fix unconsumedPromises on events
* always run expect
* Update child_process.test.ts
* fix reading special files
* Fix a test
* Deflake this test
* Make these comparisons easier
* Won't really fix it but slightly cleaner
* Update serve.test.ts
* Make the checks for if the body is already used more resilient
* Move this to the harness
* Make this test not hang in development
* Fix this test
* Make the logs better
* zero init some things
* Make this test better
* Fix readSocket
* Parallelize this test
* Handle EPipe and avoid big data
* This was a mistake
* Fix a bunch of things
* Fix memory leak
* Avoid sigpipe + optimize + delete dead code
* Make this take less time
* Make it bigger
* Remove some redundant code
* Update process.zig
* Merge and hopefully don't breka things along teh way
* Silence build warning
* Uncomment on posix
* Skip test on windows
* windows
* Cleanup test
* Update
* Deflake
* always
* less flaky test
* [autofix.ci] apply automated fixes
* logs
* fix uaf on shell IOReader
* stuff to make it work with mini event loop
* fix 2 double free scenarios, support redirections on windows
* shell: Make `1>&2` and `2>&1` work with libuv
* yoops
* Partial fix
* Partial fix
* fix build
* fix build
* ok
* Make a couple shell tests pass
* More logging
* fix
* fix
* Fix build issue
* more tests pass
* Deflake
* Deflake
* Use Output.panic instead of garbled text
* Formatting
* Introduce `bun.sys.File`, use it for `Output.Source.StreamType`, fix nested Output.scoped() calls, use Win32 `ReadFile` API for reading when it's not a libuv file descriptor.
This lets us avoid the subtle usages of `unreachable` in std.os when writing to stdout/stderr.
Previously, we were initializing the libuv loop immediately at launch due to checking for the existence of a bun build --compile'd executable. When the file descriptor is not from libuv, it's just overhead to use libuv
cc @paperdave, please tell me if Iany of that is incorrect or if you think this is a bad idea.
* Fix closing undefined memory file descriptors in spawn
cc @zackradisic
* pause instead of close
* Fix poorly-written test
* We don't need big numbers for this test
* sad workaround
* fixup
* Clearer error handling for this test
* Fix incorrect test
@electroid when ReadableStream isn't closed, hanging is the correct behavior when consuming buffered data. We cannot know if the buffered data is finished if the stream never closes.
* Fix build
* Remove known failing on windows
* Deflake
* Mark no longer failing
* show all the failing tests
* Sort the list of tests
* fix argument handling
* dont show "posix_spawn" as an error code on windows
* make bun-upgrade.test.ts pass on windows
* fix bunx and bun create again sorry
* a
* fix invalidexe because we should not be running javascript files as if they were exes
* Concurrency in test runner + better logging
* Revert "fix invalidexe because we should not be running javascript files as if they were exes"
This reverts commit da47cf8247.
* WIP: Unix fixes (#9322)
* wip
* [autofix.ci] apply automated fixes
* wip 2
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
* Update runner.node.mjs
* Update runner.node.mjs
* Document some environment variables
* shell: Make `Response` work with builtins
* Make it compile
* make pwd test pass
* [autofix.ci] apply automated fixes
* Fix printing garbage for source code previews
* Update javascript.zig
* Fix posix test failures
* Fix signal dispatch
cc @paperdave. Signals can be run from any thread. This causes an assertion failure when the receiving thread happens to not be the main thread. Easiest to reproduce on linux when you spawn 100 short-lived processes at once.
* windows
---------
Co-authored-by: cirospaciari <ciro.spaciari@gmail.com>
Co-authored-by: Zack Radisic <56137411+zackradisic@users.noreply.github.com>
Co-authored-by: Zack Radisic <zackradisic@Zacks-MBP-2.attlocal.net>
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Meghan Denny <meghan@bun.sh>
Co-authored-by: Zack Radisic <zack@theradisic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: dave caruso <me@paperdave.net>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
1700 lines
60 KiB
TypeScript
1700 lines
60 KiB
TypeScript
"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";
|
|
import { isWindows } from "harness";
|
|
|
|
function readFile(...args) {
|
|
const result = fs.readFileSync(...args);
|
|
|
|
if (isWindows) {
|
|
return result.replace(/\r\n/g, "\n");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const publicPem = readFile(path.join(import.meta.dir, "fixtures", "rsa_public.pem"), "ascii");
|
|
const privatePem = readFile(path.join(import.meta.dir, "fixtures", "rsa_private.pem"), "ascii");
|
|
const privateEncryptedPem = readFile(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: readFile(path.join(import.meta.dir, "fixtures", "ed25519_private.pem"), "ascii"),
|
|
public: readFile(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: readFile(path.join(import.meta.dir, "fixtures", "ed448_private.pem"), "ascii"),
|
|
public: readFile(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: readFile(path.join(import.meta.dir, "fixtures", "x25519_private.pem"), "ascii"),
|
|
public: readFile(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: readFile(path.join(import.meta.dir, "fixtures", "x448_private.pem"), "ascii"),
|
|
public: readFile(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: readFile(path.join(import.meta.dir, "fixtures", "ec_p256_private.pem"), "ascii"),
|
|
public: readFile(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: readFile(path.join(import.meta.dir, "fixtures", "ec_secp256k1_private.pem"), "ascii"),
|
|
public: readFile(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: readFile(path.join(import.meta.dir, "fixtures", "ec_p384_private.pem"), "ascii"),
|
|
public: readFile(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: readFile(path.join(import.meta.dir, "fixtures", "ec_p521_private.pem"), "ascii"),
|
|
public: readFile(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 = readFile(path.join(import.meta.dir, "fixtures", `rsa_public_${suffix}.pem`), "ascii");
|
|
const privatePem = readFile(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("RSA-PSS should work", async () => {
|
|
// Test RSA-PSS.
|
|
const expectedKeyDetails = {
|
|
modulusLength: 2048,
|
|
publicExponent: 65537n,
|
|
};
|
|
{
|
|
const { privateKey, publicKey } = generateKeyPairSync("rsa-pss", {
|
|
modulusLength: 2048,
|
|
publicExponent: 65537,
|
|
});
|
|
expect(publicKey.type).toBe("public");
|
|
expect(publicKey.asymmetricKeyType).toBe("rsa-pss");
|
|
expect(publicKey.asymmetricKeyDetails).toEqual(expectedKeyDetails);
|
|
expect(privateKey.type).toBe("private");
|
|
expect(privateKey.asymmetricKeyType).toBe("rsa-pss");
|
|
expect(privateKey.asymmetricKeyDetails).toEqual(expectedKeyDetails);
|
|
expect(() => publicKey.export({ format: "jwk" })).toThrow(/ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE/);
|
|
expect(() => privateKey.export({ format: "jwk" })).toThrow(/ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE/);
|
|
|
|
for (const key of [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 = sign(algo, Buffer.from("foo"), { key, saltLength });
|
|
for (const pkey of [key, publicKey]) {
|
|
const okay = verify(algo, Buffer.from("foo"), { key: pkey, saltLength }, signature);
|
|
expect(okay).toBeTrue();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Exporting the key using PKCS#1 should not work since this would discard
|
|
// any algorithm restrictions.
|
|
expect(() => {
|
|
publicKey.export({ format: "pem", type: "pkcs1" });
|
|
}).toThrow(/ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE/);
|
|
|
|
{
|
|
// 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 { privateKey, publicKey } = generateKeyPairSync("rsa-pss", {
|
|
modulusLength: 2048,
|
|
publicExponent: 65537,
|
|
hashAlgorithm: "sha1",
|
|
mgf1HashAlgorithm: "sha1",
|
|
saltLength: 20,
|
|
});
|
|
|
|
expect(publicKey.type).toBe("public");
|
|
expect(publicKey.asymmetricKeyType).toBe("rsa-pss");
|
|
// RSA_get0_pss_params returns NULL. In OpenSSL, this function retries RSA-PSS
|
|
// parameters associated with |RSA| objects, but BoringSSL does not support
|
|
// the id-RSASSA-PSS key encoding.
|
|
// We expect only modulusLength and publicExponent to be present.
|
|
expect(publicKey.asymmetricKeyDetails).toEqual(expectedKeyDetails);
|
|
expect(privateKey.type).toBe("private");
|
|
expect(privateKey.asymmetricKeyType).toBe("rsa-pss");
|
|
}
|
|
{
|
|
// This key pair enforces sha256 as the message digest and the MGF1
|
|
// message digest and a salt length of at least 16 bytes.
|
|
const { privateKey, publicKey } = generateKeyPairSync("rsa-pss", {
|
|
modulusLength: 2048,
|
|
publicExponent: 65537,
|
|
hashAlgorithm: "sha256",
|
|
saltLength: 16,
|
|
});
|
|
expect(publicKey.type).toBe("public");
|
|
expect(publicKey.asymmetricKeyType).toBe("rsa-pss");
|
|
expect(privateKey.type).toBe("private");
|
|
expect(privateKey.asymmetricKeyType).toBe("rsa-pss");
|
|
for (const key of [privateKey]) {
|
|
// Signing with anything other than sha256 should fail.
|
|
expect(() => {
|
|
sign("sha1", Buffer.from("foo"), key);
|
|
}).toThrow(/digest not allowed/);
|
|
// Signing with salt lengths less than 16 bytes should fail.
|
|
// We don't enforce this yet because of BoringSSL's limitations. TODO: check this
|
|
// for (const saltLength of [8, 10, 12]) {
|
|
// expect(() => {
|
|
// createSign("sha1").sign({ key, saltLength });
|
|
// }).toThrow(/pss saltlen too small/);
|
|
// }
|
|
// Signing with sha256 and appropriate salt lengths should work.
|
|
for (const saltLength of [undefined, 16, 18, 20]) {
|
|
const signature = sign("sha256", Buffer.from("foo"), { key, saltLength });
|
|
for (const pkey of [key, publicKey]) {
|
|
const okay = verify("sha256", Buffer.from("foo"), { key: pkey, saltLength }, signature);
|
|
expect(okay).toBeTrue();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: check how to use MGF1 and saltLength using BoringSSL
|
|
// {
|
|
// // 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);
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
}
|
|
});
|
|
|
|
test("Ed25519 should work", async () => {
|
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
|
|
expect(publicKey.type).toBe("public");
|
|
expect(publicKey.asymmetricKeyType).toBe("ed25519");
|
|
expect(publicKey.asymmetricKeyDetails).toEqual({ namedCurve: "Ed25519" });
|
|
expect(privateKey.type).toBe("private");
|
|
expect(privateKey.asymmetricKeyType).toBe("ed25519");
|
|
expect(privateKey.asymmetricKeyDetails).toEqual({ namedCurve: "Ed25519" });
|
|
|
|
{
|
|
const signature = sign(undefined, Buffer.from("foo"), privateKey);
|
|
const okay = verify(undefined, Buffer.from("foo"), publicKey, signature);
|
|
expect(okay).toBeTrue();
|
|
}
|
|
});
|
|
|
|
test("ECDSA should work", async () => {
|
|
const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
|
|
expect(publicKey.type).toBe("public");
|
|
expect(publicKey.asymmetricKeyType).toBe("ec");
|
|
expect(publicKey.asymmetricKeyDetails).toEqual({ namedCurve: "prime256v1" });
|
|
expect(privateKey.type).toBe("private");
|
|
expect(privateKey.asymmetricKeyType).toBe("ec");
|
|
expect(privateKey.asymmetricKeyDetails).toEqual({ namedCurve: "prime256v1" });
|
|
|
|
// default format (DER)
|
|
{
|
|
const signature = sign("sha256", Buffer.from("foo"), privateKey);
|
|
expect(signature.byteLength).not.toBe(64);
|
|
const okay = verify("sha256", Buffer.from("foo"), publicKey, signature);
|
|
expect(okay).toBeTrue();
|
|
}
|
|
// IeeeP1363 format
|
|
{
|
|
const signature = sign("sha256", Buffer.from("foo"), { key: privateKey, dsaEncoding: "ieee-p1363" });
|
|
expect(signature.byteLength).toBe(64);
|
|
|
|
const okay = verify("sha256", Buffer.from("foo"), { key: publicKey, dsaEncoding: "ieee-p1363" }, signature);
|
|
expect(okay).toBeTrue();
|
|
}
|
|
// DER format
|
|
{
|
|
const signature = sign("sha256", Buffer.from("foo"), { key: privateKey, dsaEncoding: "der" });
|
|
expect(signature.byteLength).not.toBe(64);
|
|
|
|
const okay = verify("sha256", Buffer.from("foo"), { key: publicKey, dsaEncoding: "der" }, signature);
|
|
expect(okay).toBeTrue();
|
|
}
|
|
|
|
expect(() => {
|
|
//@ts-ignore
|
|
sign("sha256", Buffer.from("foo"), { key: privateKey, dsaEncoding: "kjshdakjshd" });
|
|
}).toThrow(/invalid dsaEncoding/);
|
|
|
|
expect(() => {
|
|
const signature = sign("sha256", Buffer.from("foo"), privateKey);
|
|
//@ts-ignore
|
|
verify("sha256", Buffer.from("foo"), { key: publicKey, dsaEncoding: "ieee-p136" }, signature);
|
|
}).toThrow(/invalid dsaEncoding/);
|
|
|
|
expect(() => {
|
|
//@ts-ignore
|
|
const signature = sign("sha256", Buffer.from("foo"), { key: privateKey, dsaEncoding: "ieee-p136" });
|
|
verify("sha256", Buffer.from("foo"), { key: publicKey, dsaEncoding: "der" }, signature);
|
|
}).toThrow(/invalid dsaEncoding/);
|
|
});
|