mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> Co-authored-by: Dave Caruso <me@paperdave.net>
1750 lines
62 KiB
TypeScript
1750 lines
62 KiB
TypeScript
"use strict";
|
|
|
|
import { describe, expect, it, test } from "bun:test";
|
|
import {
|
|
createCipheriv,
|
|
createDecipheriv,
|
|
createPrivateKey,
|
|
createPublicKey,
|
|
createSecretKey,
|
|
createSign,
|
|
createVerify,
|
|
generateKey,
|
|
generateKeyPair,
|
|
generateKeyPairSync,
|
|
generateKeySync,
|
|
KeyObject,
|
|
privateDecrypt,
|
|
privateEncrypt,
|
|
publicDecrypt,
|
|
publicEncrypt,
|
|
randomBytes,
|
|
sign,
|
|
verify,
|
|
} from "crypto";
|
|
import fs from "fs";
|
|
import { isWindows } from "harness";
|
|
import { createContext, runInContext, runInThisContext, Script } from "node:vm";
|
|
import path from "path";
|
|
|
|
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("error:06000066:public key routines:OPENSSL_internal:DECODE_ERROR");
|
|
});
|
|
|
|
[
|
|
{
|
|
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;
|
|
// Ed448 and X448 are not supported yet
|
|
const test = keyType === "x448" || keyType === "ed448" ? it.skip : it;
|
|
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",
|
|
},
|
|
// same as node
|
|
exportedD: "ABIIbmn3Gm_Y11uIDkC3g2ijpRxIrJEBY4i_JJYo5OougzTl3BX2ifRluPJMaaHcNerbQH_WdVkLLX86ShlHrRyJ",
|
|
},
|
|
].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" });
|
|
if (info.exportedD) {
|
|
expect(jwt.d).toEqual(info.exportedD);
|
|
jwt.d = info.jwk.d;
|
|
} else {
|
|
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" });
|
|
if (info.exportedD) {
|
|
expect(jwt.d).toEqual(info.exportedD);
|
|
jwt.d = info.jwk.d;
|
|
}
|
|
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("The property 'options.cipher' is invalid. Received undefined");
|
|
});
|
|
|
|
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(
|
|
/The "otherKeyObject" argument must be an instance of KeyObject. Received type number \(0\)/,
|
|
);
|
|
|
|
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 => {
|
|
it(`${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.", () => {
|
|
[512, 1024, 2048, 4096].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 === "x448" || type === "ed448" ? it.skip : it;
|
|
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.", () => {
|
|
[512, 1024, 2048, 4096].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 === "x448" || type === "ed448" ? it.skip : it;
|
|
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);
|
|
});
|
|
|
|
type TestRunInContextArg =
|
|
| { fn: typeof runInContext; isIsolated: true }
|
|
| { fn: typeof runInThisContext; isIsolated?: false };
|
|
|
|
function testRunInContext({ fn, isIsolated }: TestRunInContextArg) {
|
|
if (isIsolated) {
|
|
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,
|
|
);
|
|
});
|
|
} else {
|
|
test("can generate key", () => {
|
|
const prop = randomProp();
|
|
// @ts-expect-error
|
|
globalThis[prop] = generateKeySync;
|
|
try {
|
|
const result = fn(`${prop}("aes", { length: 128 })`);
|
|
expect(result).toBeDefined();
|
|
const keybuf = result.export();
|
|
expect(keybuf.byteLength).toBe(128 / 8);
|
|
} finally {
|
|
// @ts-expect-error
|
|
delete globalThis[prop];
|
|
}
|
|
});
|
|
}
|
|
}
|
|
describe("Script", () => {
|
|
describe("runInContext()", () => {
|
|
testRunInContext({
|
|
fn: (code, context, options) => {
|
|
const script = new Script(code, options);
|
|
return script.runInContext(context);
|
|
},
|
|
isIsolated: true,
|
|
});
|
|
});
|
|
describe("runInNewContext()", () => {
|
|
testRunInContext({
|
|
fn: (code, context, options) => {
|
|
const script = new Script(code, options);
|
|
return script.runInNewContext(context);
|
|
},
|
|
isIsolated: true,
|
|
});
|
|
});
|
|
describe("runInThisContext()", () => {
|
|
testRunInContext({
|
|
fn: (code: string, options: any) => {
|
|
const script = new Script(code, options);
|
|
return script.runInThisContext();
|
|
},
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test.todo("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(privateKey.type).toBe("private");
|
|
expect(privateKey.asymmetricKeyType).toBe("ed25519");
|
|
|
|
// TODO: this should be an empty object. Node doesn't always include the details.
|
|
expect(privateKey.asymmetricKeyDetails).toBeObject();
|
|
expect(publicKey.asymmetricKeyDetails).toBeObject();
|
|
|
|
{
|
|
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(/The property 'options.dsaEncoding' is invalid. Received 'kjshdakjshd'/);
|
|
|
|
expect(() => {
|
|
const signature = sign("sha256", Buffer.from("foo"), privateKey);
|
|
//@ts-ignore
|
|
verify("sha256", Buffer.from("foo"), { key: publicKey, dsaEncoding: "ieee-p136" }, signature);
|
|
}).toThrow(/The property 'options.dsaEncoding' is invalid. Received 'ieee-p136'/);
|
|
|
|
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(/The property 'options.dsaEncoding' is invalid. Received 'ieee-p136'/);
|
|
});
|
|
|
|
function randomProp() {
|
|
return "prop" + crypto.randomUUID().replace(/-/g, "");
|
|
}
|