diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2023-04-19 22:27:34 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-19 22:27:34 +0530 |
commit | 9496dfc68558a0d6e9fa0a3bf1fbde9883a88d07 (patch) | |
tree | fd37f0f325e288c7cf4a48426481d8553c38f6ef /ext/node/polyfills/internal/crypto/keygen.ts | |
parent | 53c9f5918cd07237c20b086945d4604baf1900fb (diff) |
fix(ext/node): implement asymmetric keygen (#18651)
Towards #18455
This commit implements the keypair generation for asymmetric keys for
the `generateKeyPair` API.
See how key material is managed in this implementation:
https://www.notion.so/denolandinc/node-crypto-design-99fc33f568d24e47a5e4b36002c5325d?pvs=4
Private and public key encoding depend on `KeyObject#export` which is
not implemented. I've also skipped ED448 and X448 since we need a crate
for that in WebCrypto too.
Diffstat (limited to 'ext/node/polyfills/internal/crypto/keygen.ts')
-rw-r--r-- | ext/node/polyfills/internal/crypto/keygen.ts | 275 |
1 files changed, 265 insertions, 10 deletions
diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts index b490cedd7..cdc616db6 100644 --- a/ext/node/polyfills/internal/crypto/keygen.ts +++ b/ext/node/polyfills/internal/crypto/keygen.ts @@ -1,3 +1,5 @@ +// deno-lint-ignore-file no-explicit-any + // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. @@ -8,13 +10,20 @@ import { setOwnedKey, } from "ext:deno_node/internal/crypto/keys.ts"; import { notImplemented } from "ext:deno_node/_utils.ts"; -import { ERR_INVALID_ARG_VALUE } from "ext:deno_node/internal/errors.ts"; import { + ERR_INCOMPATIBLE_OPTION_PAIR, + ERR_INVALID_ARG_VALUE, + ERR_MISSING_OPTION, +} from "ext:deno_node/internal/errors.ts"; +import { + validateBuffer, validateFunction, + validateInt32, validateInteger, validateObject, validateOneOf, validateString, + validateUint32, } from "ext:deno_node/internal/validators.mjs"; import { Buffer } from "ext:deno_node/buffer.ts"; import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts"; @@ -529,17 +538,34 @@ export function generateKeyPair( ) => void, ): void; export function generateKeyPair( - _type: KeyType, - _options: unknown, - _callback: ( + type: KeyType, + options: unknown, + callback: ( err: Error | null, - // deno-lint-ignore no-explicit-any publicKey: any, - // deno-lint-ignore no-explicit-any privateKey: any, ) => void, ) { - notImplemented("crypto.generateKeyPair"); + createJob(kAsync, type, options).then(([privateKey, publicKey]) => { + privateKey = new KeyObject("private", setOwnedKey(privateKey)); + publicKey = new KeyObject("public", setOwnedKey(publicKey)); + + if (typeof options === "object" && options !== null) { + const { publicKeyEncoding, privateKeyEncoding } = options as any; + + if (publicKeyEncoding) { + publicKey = publicKey.export(publicKeyEncoding); + } + + if (privateKeyEncoding) { + privateKey = privateKey.export(privateKeyEncoding); + } + } + + callback(null, publicKey, privateKey); + }).catch((err) => { + callback(err, null, null); + }); } export interface KeyPairKeyObjectResult { @@ -716,12 +742,241 @@ export function generateKeyPairSync( options?: X448KeyPairKeyObjectOptions, ): KeyPairKeyObjectResult; export function generateKeyPairSync( - _type: KeyType, - _options: unknown, + type: KeyType, + options: unknown, ): | KeyPairKeyObjectResult | KeyPairSyncResult<string | Buffer, string | Buffer> { - notImplemented("crypto.generateKeyPairSync"); + let [privateKey, publicKey] = createJob(kSync, type, options); + + privateKey = new KeyObject("private", setOwnedKey(privateKey)); + publicKey = new KeyObject("public", setOwnedKey(publicKey)); + + if (typeof options === "object" && options !== null) { + const { publicKeyEncoding, privateKeyEncoding } = options as any; + + if (publicKeyEncoding) { + publicKey = publicKey.export(publicKeyEncoding); + } + + if (privateKeyEncoding) { + privateKey = privateKey.export(privateKeyEncoding); + } + } + + return { publicKey, privateKey }; +} + +const kSync = 0; +const kAsync = 1; + +function createJob(mode, type, options) { + validateString(type, "type"); + + if (options !== undefined) { + validateObject(options, "options"); + } + + switch (type) { + case "rsa": + case "rsa-pss": { + validateObject(options, "options"); + const { modulusLength } = options; + validateUint32(modulusLength, "options.modulusLength"); + + let { publicExponent } = options; + if (publicExponent == null) { + publicExponent = 0x10001; + } else { + validateUint32(publicExponent, "options.publicExponent"); + } + + if (type === "rsa") { + if (mode === kSync) { + return ops.op_node_generate_rsa( + modulusLength, + publicExponent, + ); + } else { + return core.opAsync( + "op_node_generate_rsa_async", + modulusLength, + publicExponent, + ); + } + } + + const { + hash, + mgf1Hash, + hashAlgorithm, + mgf1HashAlgorithm, + saltLength, + } = options; + + if (saltLength !== undefined) { + validateInt32(saltLength, "options.saltLength", 0); + } + if (hashAlgorithm !== undefined) { + validateString(hashAlgorithm, "options.hashAlgorithm"); + } + if (mgf1HashAlgorithm !== undefined) { + validateString(mgf1HashAlgorithm, "options.mgf1HashAlgorithm"); + } + if (hash !== undefined) { + process.emitWarning( + '"options.hash" is deprecated, ' + + 'use "options.hashAlgorithm" instead.', + "DeprecationWarning", + "DEP0154", + ); + validateString(hash, "options.hash"); + if (hashAlgorithm && hash !== hashAlgorithm) { + throw new ERR_INVALID_ARG_VALUE("options.hash", hash); + } + } + if (mgf1Hash !== undefined) { + process.emitWarning( + '"options.mgf1Hash" is deprecated, ' + + 'use "options.mgf1HashAlgorithm" instead.', + "DeprecationWarning", + "DEP0154", + ); + validateString(mgf1Hash, "options.mgf1Hash"); + if (mgf1HashAlgorithm && mgf1Hash !== mgf1HashAlgorithm) { + throw new ERR_INVALID_ARG_VALUE("options.mgf1Hash", mgf1Hash); + } + } + + if (mode === kSync) { + return ops.op_node_generate_rsa( + modulusLength, + publicExponent, + ); + } else { + return core.opAsync( + "op_node_generate_rsa_async", + modulusLength, + publicExponent, + ); + } + } + case "dsa": { + validateObject(options, "options"); + const { modulusLength } = options; + validateUint32(modulusLength, "options.modulusLength"); + + let { divisorLength } = options; + if (divisorLength == null) { + divisorLength = 256; + } else { + validateInt32(divisorLength, "options.divisorLength", 0); + } + + if (mode === kSync) { + return ops.op_node_dsa_generate(modulusLength, divisorLength); + } + return core.opAsync( + "op_node_dsa_generate_async", + modulusLength, + divisorLength, + ); + } + case "ec": { + validateObject(options, "options"); + const { namedCurve } = options; + validateString(namedCurve, "options.namedCurve"); + const { paramEncoding } = options; + if (paramEncoding == null || paramEncoding === "named") { + // pass. + } else if (paramEncoding === "explicit") { + // TODO(@littledivy): Explicit param encoding is very rarely used, and not supported by the ring crate. + throw new TypeError("Explicit encoding is not supported"); + } else { + throw new ERR_INVALID_ARG_VALUE("options.paramEncoding", paramEncoding); + } + + if (mode === kSync) { + return ops.op_node_ec_generate(namedCurve); + } else { + return core.opAsync("op_node_ec_generate_async", namedCurve); + } + } + case "ed25519": { + if (mode === kSync) { + return ops.op_node_ed25519_generate(); + } + return core.opAsync("op_node_ed25519_generate_async"); + } + case "x25519": { + if (mode === kSync) { + return ops.op_node_x25519_generate(); + } + return core.opAsync("op_node_x25519_generate_async"); + } + case "ed448": + case "x448": { + notImplemented(type); + break; + } + case "dh": { + validateObject(options, "options"); + const { group, primeLength, prime, generator } = options; + if (group != null) { + if (prime != null) { + throw new ERR_INCOMPATIBLE_OPTION_PAIR("group", "prime"); + } + if (primeLength != null) { + throw new ERR_INCOMPATIBLE_OPTION_PAIR("group", "primeLength"); + } + if (generator != null) { + throw new ERR_INCOMPATIBLE_OPTION_PAIR("group", "generator"); + } + + validateString(group, "options.group"); + + if (mode === kSync) { + return ops.op_node_dh_generate_group(group); + } else { + return core.opAsync("op_node_dh_generate_group_async", group); + } + } + + if (prime != null) { + if (primeLength != null) { + throw new ERR_INCOMPATIBLE_OPTION_PAIR("prime", "primeLength"); + } + + validateBuffer(prime, "options.prime"); + } else if (primeLength != null) { + validateInt32(primeLength, "options.primeLength", 0); + } else { + throw new ERR_MISSING_OPTION( + "At least one of the group, prime, or primeLength options", + ); + } + + if (generator != null) { + validateInt32(generator, "options.generator", 0); + } + + const g = generator == null ? 2 : generator; + + if (mode === kSync) { + return ops.op_node_dh_generate(prime, primeLength ?? 0, g); + } else { + return core.opAsync( + "op_node_dh_generate_async", + prime, + primeLength ?? 0, + g, + ); + } + } + default: + // Fall through + } + throw new ERR_INVALID_ARG_VALUE("type", type, "must be a supported key type"); } export default { |