Return expected data when using Promises with crypto.generateKeyPair (#13600)

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
Wilmer Paulino
2024-08-30 20:14:47 -07:00
committed by GitHub
parent adb54f1849
commit 76c4145f0e
5 changed files with 140 additions and 75 deletions

View File

@@ -0,0 +1,79 @@
const kCustomPromisifiedSymbol = Symbol.for("nodejs.util.promisify.custom");
const kCustomPromisifyArgsSymbol = Symbol("customPromisifyArgs");
function defineCustomPromisify(target, callback) {
Object.defineProperty(target, kCustomPromisifiedSymbol, {
value: callback,
__proto__: null,
configurable: true,
});
return callback;
}
function defineCustomPromisifyArgs(target, args) {
Object.defineProperty(target, kCustomPromisifyArgsSymbol, {
__proto__: null,
value: args,
enumerable: false,
});
return args;
}
var promisify = function promisify(original) {
if (typeof original !== "function") throw new TypeError('The "original" argument must be of type Function');
const custom = original[kCustomPromisifiedSymbol];
if (custom) {
if (typeof custom !== "function") {
throw new TypeError('The "util.promisify.custom" argument must be of type Function');
}
// ensure that we don't create another promisified function wrapper
return defineCustomPromisify(custom, custom);
}
const callbackArgs = original[kCustomPromisifyArgsSymbol];
function fn(...originalArgs) {
const { promise, resolve, reject } = Promise.withResolvers();
try {
original.$apply(this, [
...originalArgs,
function (err, ...values) {
if (err) {
return reject(err);
}
if (callbackArgs !== undefined && values.length > 0) {
if (!Array.isArray(callbackArgs)) {
throw new TypeError('The "customPromisifyArgs" argument must be of type Array');
}
if (callbackArgs.length !== values.length) {
throw new Error("Mismatched length in promisify callback args");
}
const result = {};
for (let i = 0; i < callbackArgs.length; i++) {
result[callbackArgs[i]] = values[i];
}
resolve(result);
} else {
resolve(values[0]);
}
},
]);
} catch (err) {
reject(err);
}
return promise;
}
Object.setPrototypeOf(fn, Object.getPrototypeOf(original));
defineCustomPromisify(fn, fn);
return Object.defineProperties(fn, Object.getOwnPropertyDescriptors(original));
};
promisify.custom = kCustomPromisifiedSymbol;
export default {
defineCustomPromisify,
defineCustomPromisifyArgs,
promisify,
};

View File

@@ -11935,14 +11935,17 @@ function _generateKeyPairSync(algorithm, options) {
}
crypto_exports.generateKeyPairSync = _generateKeyPairSync;
crypto_exports.generateKeyPair = function (algorithm, options, callback) {
function _generateKeyPair(algorithm, options, callback) {
try {
const result = _generateKeyPairSync(algorithm, options);
typeof callback === "function" && callback(null, result.publicKey, result.privateKey);
} catch (err) {
typeof callback === "function" && callback(err);
}
};
}
const { defineCustomPromisifyArgs } = require("internal/promisify");
defineCustomPromisifyArgs(_generateKeyPair, ["publicKey", "privateKey"]);
crypto_exports.generateKeyPair = _generateKeyPair;
crypto_exports.createSecretKey = function (key, encoding) {
if (key instanceof KeyObject || key instanceof CryptoKey) {

View File

@@ -1,4 +1,32 @@
// Hardcoded module "node:timers"
const { defineCustomPromisify } = require("internal/promisify");
// Lazily load node:timers/promises promisified functions onto the global timers.
{
const { setTimeout: timeout, setImmediate: immediate, setInterval: interval } = globalThis;
if (timeout && $isCallable(timeout)) {
defineCustomPromisify(timeout, function setTimeout(arg1) {
const fn = defineCustomPromisify(timeout, require("node:timers/promises").setTimeout);
return fn.$apply(this, arguments);
});
}
if (immediate && $isCallable(immediate)) {
defineCustomPromisify(immediate, function setImmediate(arg1) {
const fn = defineCustomPromisify(immediate, require("node:timers/promises").setImmediate);
return fn.$apply(this, arguments);
});
}
if (interval && $isCallable(interval)) {
defineCustomPromisify(interval, function setInterval(arg1) {
const fn = defineCustomPromisify(interval, require("node:timers/promises").setInterval);
return fn.$apply(this, arguments);
});
}
}
export default {
setTimeout,
clearTimeout,

View File

@@ -3,6 +3,7 @@ const types = require("node:util/types");
/** @type {import('node-inspect-extracted')} */
const utl = require("internal/util/inspect");
const { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } = require("internal/errors");
const { promisify } = require("internal/promisify");
const internalErrorName = $newZigFunction("node_util_binding.zig", "internalErrorName", 1);
@@ -158,80 +159,7 @@ var _extend = function (origin, add) {
}
return origin;
};
var kCustomPromisifiedSymbol = Symbol.for("nodejs.util.promisify.custom");
function defineCustomPromisify(target, callback) {
Object.defineProperty(target, kCustomPromisifiedSymbol, {
value: callback,
__proto__: null,
configurable: true,
});
return callback;
}
// Lazily load node:timers/promises promisifed functions onto the global timers.
// This is not a complete solution, as one could load these without loading the "util" module
// But it is better than nothing.
{
const { setTimeout: timeout, setImmediate: immediate, setInterval: interval } = globalThis;
if (timeout && $isCallable(timeout)) {
defineCustomPromisify(timeout, function setTimeout(arg1) {
const fn = defineCustomPromisify(timeout, require("node:timers/promises").setTimeout);
return fn.$apply(this, arguments);
});
}
if (immediate && $isCallable(immediate)) {
defineCustomPromisify(immediate, function setImmediate(arg1) {
const fn = defineCustomPromisify(immediate, require("node:timers/promises").setImmediate);
return fn.$apply(this, arguments);
});
}
if (interval && $isCallable(interval)) {
defineCustomPromisify(interval, function setInterval(arg1) {
const fn = defineCustomPromisify(interval, require("node:timers/promises").setInterval);
return fn.$apply(this, arguments);
});
}
}
var promisify = function promisify(original) {
if (typeof original !== "function") throw new TypeError('The "original" argument must be of type Function');
const custom = original[kCustomPromisifiedSymbol];
if (custom) {
if (typeof custom !== "function") {
throw new TypeError('The "util.promisify.custom" argument must be of type Function');
}
// ensure that we don't create another promisified function wrapper
return defineCustomPromisify(custom, custom);
}
function fn(...originalArgs) {
const { promise, resolve, reject } = Promise.withResolvers();
try {
original.$apply(this, [
...originalArgs,
function (err, ...values) {
if (err) {
return reject(err);
}
resolve(values[0]);
},
]);
} catch (err) {
reject(err);
}
return promise;
}
Object.setPrototypeOf(fn, Object.getPrototypeOf(original));
defineCustomPromisify(fn, fn);
return Object.defineProperties(fn, getOwnPropertyDescriptors(original));
};
promisify.custom = kCustomPromisifiedSymbol;
function callbackifyOnRejected(reason, cb) {
if (!reason) {
var newReason = new Error("Promise was rejected with a falsy value");

View File

@@ -0,0 +1,27 @@
const crypto = require("crypto");
const util = require("util");
if (!crypto.generateKeyPair) {
test.skip("missing crypto.generateKeyPair");
}
test("09469", async () => {
const generateKeyPairAsync = util.promisify(crypto.generateKeyPair);
const ret = await generateKeyPairAsync("rsa", {
publicExponent: 3,
modulusLength: 512,
publicKeyEncoding: {
type: "pkcs1",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
},
});
expect(Object.keys(ret)).toHaveLength(2);
const { publicKey, privateKey } = ret;
expect(typeof publicKey).toBe("string");
expect(typeof privateKey).toBe("string");
});