Skip to content
File

Blob: src/node/internal/crypto_keys.ts

typescript901 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4//
5// Copyright Joyent, Inc. and other Node contributors.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the
9// "Software"), to deal in the Software without restriction, including
10// without limitation the rights to use, copy, modify, merge, publish,
11// distribute, sublicense, and/or sell copies of the Software, and to permit
12// persons to whom the Software is furnished to do so, subject to the
13// following conditions:
14//
15// The above copyright notice and this permission notice shall be included
16// in all copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
21// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
22// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT ORs
23// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24// USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26import { Buffer } from 'node-internal:internal_buffer';
27 
28import {
29 type KeyData,
30 type KeyObjectType,
31 type KeyExportResult,
32 type SecretKeyType,
33 type SecretKeyExportOptions,
34 type PublicKeyExportOptions,
35 type PrivateKeyExportOptions,
36 type ExportOptions,
37 type AsymmetricKeyDetails,
38 type AsymmetricKeyType,
39 type CreateAsymmetricKeyOptions,
40 type GenerateKeyOptions,
41 type GenerateKeyPairOptions,
42 type InnerExportOptions,
43 type InnerCreateAsymmetricKeyOptions,
44 type JsonWebKey,
45 default as cryptoImpl,
46} from 'node-internal:crypto';
47 
48import {
49 arrayBufferToUnsignedBigInt,
50 getArrayBufferOrView,
51 kHandle,
52} from 'node-internal:crypto_util';
53 
54import {
55 isAnyArrayBuffer,
56 isArrayBuffer,
57 isArrayBufferView,
58 isUint8Array,
59} from 'node-internal:internal_types';
60 
61import {
62 ERR_INCOMPATIBLE_OPTION_PAIR,
63 ERR_INVALID_ARG_TYPE,
64 ERR_METHOD_NOT_IMPLEMENTED,
65 ERR_MISSING_OPTION,
66} from 'node-internal:internal_errors';
67 
68import {
69 validateFunction,
70 validateInteger,
71 validateObject,
72 validateOneOf,
73 validateString,
74 validateInt32,
75 validateUint32,
76} from 'node-internal:validators';
77 
78import { inspect } from 'node-internal:internal_inspect';
79import { randomBytes } from 'node-internal:crypto_random';
80const kInspect = inspect.custom;
81 
82const kCustomPromisifyArgsSymbol = Symbol.for(
83 'nodejs.util.promisify.custom.args'
84);
85 
86// Key input contexts.
87export const KeyContext = {
88 kConsumePublic: 'kConsumePublic',
89 kConsumePrivate: 'kConsumePrivate',
90 kCreatePublic: 'kCreatePublic',
91 kCreatePrivate: 'kCreatePrivate',
92};
93 
94// In Node.js, the definition of KeyObject is a bit complicated because
95// KeyObject instances in Node.js can be transferred via postMessage() and
96// structuredClone(), etc, allowing instances to be shared across multiple
97// worker threads. We do not implement that model so we're essentially
98// re-implementing the Node.js API here instead of just taking their code.
99// Also in Node.js, CryptoKey is layered on top of KeyObject since KeyObject
100// existed first. We're, however, going to layer our KeyObject on top of
101// CryptoKey with a few augmentations.
102 
103function isStringOrBuffer(val: unknown): val is string | Buffer {
104 return (
105 typeof val === 'string' || isArrayBufferView(val) || isAnyArrayBuffer(val)
106 );
107}
108 
109function validateExportOptions(
110 options: ExportOptions,
111 type: KeyObjectType,
112 name = 'options'
113): asserts options is ExportOptions {
114 validateObject(options, name);
115 // Yes, converting to any is a bit of a cheat, but it allows us to check
116 // each option individually without having to do a bunch of type guards.
117 const opts = options;
118 if (opts.format !== undefined) {
119 validateString(opts.format, `${name}.format`);
120 } else {
121 options.format = 'buffer';
122 }
123 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
124 if ('type' in opts && opts.type !== undefined) {
125 validateString(opts.type, `${name}.type`);
126 }
127 if (type === 'private') {
128 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
129 if ('cipher' in opts && opts.cipher !== undefined) {
130 validateString(opts.cipher, `${name}.cipher`);
131 if (typeof opts.passphrase === 'string') {
132 opts.passphrase = Buffer.from(opts.passphrase, opts.encoding);
133 }
134 if (!isUint8Array(opts.passphrase)) {
135 throw new ERR_INVALID_ARG_TYPE(
136 `${name}.passphrase`,
137 ['string', 'Uint8Array'],
138 opts.passphrase
139 );
140 }
141 }
142 }
143}
144 
145export abstract class KeyObject {
146 [kHandle]: CryptoKey;
147 
148 constructor() {
149 // KeyObjects cannot be created with new ... use one of the
150 // create or generate methods, or use from to get from a
151 // CryptoKey.
152 throw new Error('Illegal constructor');
153 }
154 
155 static from(key: CryptoKey): KeyObject {
156 if (!(key instanceof CryptoKey)) {
157 throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
158 }
159 switch (key.type) {
160 case 'secret':
161 return Reflect.construct(
162 function (this: SecretKeyObject): void {
163 this[kHandle] = key;
164 },
165 [],
166 SecretKeyObject
167 ) as KeyObject;
168 case 'private':
169 return Reflect.construct(
170 function (this: PrivateKeyObject): void {
171 this[kHandle] = key;
172 },
173 [],
174 PrivateKeyObject
175 ) as KeyObject;
176 case 'public':
177 return Reflect.construct(
178 function (this: PublicKeyObject): void {
179 this[kHandle] = key;
180 },
181 [],
182 PublicKeyObject
183 ) as KeyObject;
184 }
185 }
186 
187 export(options: ExportOptions = {}): KeyExportResult {
188 validateObject(options, 'options');
189 
190 validateExportOptions(options, this.type);
191 
192 const ret = cryptoImpl.exportKey(
193 this[kHandle],
194 options as InnerExportOptions
195 );
196 if (typeof ret === 'string') return ret;
197 if (isUint8Array(ret)) {
198 return Buffer.from(
199 (ret as Uint8Array).buffer,
200 ret.byteOffset,
201 ret.byteLength
202 ) as KeyExportResult;
203 } else if (isArrayBuffer(ret)) {
204 return Buffer.from(
205 ret as ArrayBuffer,
206 0,
207 (ret as ArrayBuffer).byteLength
208 );
209 }
210 return ret;
211 }
212 
213 equals(otherKeyObject: KeyObject): boolean {
214 if (this === otherKeyObject || this[kHandle] === otherKeyObject[kHandle])
215 return true;
216 if (this.type !== otherKeyObject.type) return false;
217 if (!(otherKeyObject[kHandle] instanceof CryptoKey)) {
218 throw new ERR_INVALID_ARG_TYPE(
219 'otherKeyObject',
220 'KeyObject',
221 otherKeyObject
222 );
223 }
224 return cryptoImpl.equals(this[kHandle], otherKeyObject[kHandle]);
225 }
226 
227 abstract get type(): KeyObjectType;
228 
229 get [Symbol.toStringTag](): string {
230 return 'KeyObject';
231 }
232}
233 
234export function isKeyObject(obj: unknown): obj is KeyObject {
235 return obj != null && typeof obj === 'object' && kHandle in obj;
236}
237 
238export function getKeyObjectHandle(obj: KeyObject): CryptoKey {
239 return obj[kHandle];
240}
241 
242abstract class AsymmetricKeyObject extends KeyObject {
243 get asymmetricKeyDetails(): AsymmetricKeyDetails {
244 const detail = cryptoImpl.getAsymmetricKeyDetail(this[kHandle]);
245 if (isArrayBuffer(detail.publicExponent)) {
246 detail.publicExponent = arrayBufferToUnsignedBigInt(
247 detail.publicExponent
248 );
249 }
250 return detail;
251 }
252 
253 get asymmetricKeyType(): AsymmetricKeyType {
254 return cryptoImpl.getAsymmetricKeyType(this[kHandle]);
255 }
256 
257 toCryptoKey(): void {
258 // TODO(soon): Implement the toCryptoKey API (added in Node.js 23.0.0)
259 throw new ERR_METHOD_NOT_IMPLEMENTED('toCryptoKey');
260 }
261 
262 [kInspect](
263 depth: number,
264 options: {
265 depth?: number;
266 }
267 ): string | this {
268 if (depth < 0) return this;
269 
270 const opts = {
271 ...options,
272 depth: options.depth == null ? null : options.depth - 1,
273 };
274 
275 return `${this.constructor.name} ${inspect(
276 {
277 type: this.asymmetricKeyType,
278 details: this.asymmetricKeyDetails,
279 },
280 opts
281 )}`;
282 }
283}
284 
285export class PublicKeyObject extends AsymmetricKeyObject {
286 override export(options?: PublicKeyExportOptions): KeyExportResult {
287 return super.export(options);
288 }
289 
290 get type(): KeyObjectType {
291 return 'public';
292 }
293}
294 
295export class PrivateKeyObject extends AsymmetricKeyObject {
296 override export(options?: PrivateKeyExportOptions): KeyExportResult {
297 return super.export(options);
298 }
299 
300 get type(): KeyObjectType {
301 return 'private';
302 }
303}
304 
305export class SecretKeyObject extends KeyObject {
306 get symmetricKeySize(): number {
307 return (this[kHandle].algorithm as unknown as string).length | 0;
308 }
309 
310 override export(options?: SecretKeyExportOptions): KeyExportResult {
311 return super.export(options);
312 }
313 
314 get type(): KeyObjectType {
315 return 'secret';
316 }
317 
318 [kInspect](depth: number, options: { depth?: number }): string | this {
319 if (depth < 0) return this;
320 
321 const opts = {
322 ...options,
323 depth: options.depth == null ? null : options.depth - 1,
324 };
325 
326 return `${this.constructor.name} ${inspect(
327 {
328 size: this.symmetricKeySize,
329 },
330 opts
331 )}`;
332 }
333}
334 
335type ValidateKeyDataOptions = {
336 allowObject?: boolean;
337};
338function validateKeyData(
339 key: unknown,
340 name: string,
341 options: ValidateKeyDataOptions = {
342 allowObject: false,
343 }
344): void {
345 if (
346 key == null ||
347 (typeof key !== 'string' &&
348 options.allowObject &&
349 typeof key !== 'object' &&
350 !isArrayBufferView(key) &&
351 !isAnyArrayBuffer(key))
352 ) {
353 const expected = ['string', 'ArrayBuffer', 'TypedArray', 'DataView'];
354 if (options.allowObject) expected.push('object');
355 throw new ERR_INVALID_ARG_TYPE(name, expected, key);
356 }
357}
358 
359export function createSecretKey(
360 key: string,
361 encoding?: string
362): SecretKeyObject;
363export function createSecretKey(
364 key: ArrayBuffer | ArrayBufferView
365): SecretKeyObject;
366export function createSecretKey(
367 key: KeyData,
368 encoding?: string
369): SecretKeyObject {
370 validateKeyData(key, 'key');
371 if (typeof key === 'string') {
372 key = Buffer.from(key, encoding);
373 } else if (isAnyArrayBuffer(key)) {
374 // We want the key to be a copy of the original buffer, not a view.
375 key = Buffer.from(new Uint8Array(key));
376 } else if (isArrayBufferView(key)) {
377 // We want the key to be a copy of the original buffer, not a view.
378 key = Buffer.from(key as Buffer);
379 }
380 
381 // Node.js requires that the key data be less than 2 ** 32 - 1,
382 // however it enforces the limit silently... returning an empty
383 // key as opposed to throwing an error. Silly Node.js.
384 // But, it's all good because our runtime limits the size of
385 // buffer allocations to a strict maximum of 2,147,483,646 ... way
386 // more than necessary... no one actually *needs* a 17,179,869,168
387 // bit secret key do they? Good luck to the poor soul who tries.
388 
389 return KeyObject.from(cryptoImpl.createSecretKey(key)) as SecretKeyObject;
390}
391 
392export function prepareAsymmetricKey(
393 key: CreateAsymmetricKeyOptions | null | undefined,
394 ctx: (typeof KeyContext)[keyof typeof KeyContext]
395): InnerCreateAsymmetricKeyOptions {
396 // Safety check... key should not be undefined or null here.
397 if (key == null) {
398 throw new ERR_INVALID_ARG_TYPE(
399 'key',
400 ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView', 'string', 'object'],
401 key
402 );
403 }
404 
405 let normalized: CreateAsymmetricKeyOptions;
406 if (
407 isStringOrBuffer(key) ||
408 isAnyArrayBuffer(key) ||
409 isArrayBufferView(key)
410 ) {
411 normalized = { key, format: 'pem' } as CreateAsymmetricKeyOptions;
412 } else {
413 normalized = key;
414 }
415 
416 const {
417 key: data,
418 encoding = 'utf8',
419 format = 'pem',
420 type,
421 passphrase,
422 } = normalized;
423 
424 // The key data must be specified. The value has to be one of either a
425 // string, an ArrayBuffer, an ArrayBufferView, or a JWK object.
426 if (data == null || isKeyObject(data) || data instanceof CryptoKey) {
427 throw new ERR_INVALID_ARG_TYPE(
428 'options.key',
429 ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView', 'string', 'object'],
430 data
431 );
432 }
433 
434 if (isStringOrBuffer(data)) {
435 // When the key data is a string or buffer, the format must be
436 // one of either pem or der.
437 validateOneOf(format, 'format', ['pem', 'der']);
438 if (type !== undefined) {
439 if (ctx == KeyContext.kCreatePrivate) {
440 // When the key data is a string or buffer, the type must be
441 // one of either pkcs1, pkcs8, or sec1.
442 validateOneOf(type, 'type', ['pkcs1', 'pkcs8', 'sec1']);
443 } else if (ctx == KeyContext.kCreatePublic) {
444 validateOneOf(type, 'type', ['pkcs1', 'spki']);
445 }
446 }
447 return {
448 key: getArrayBufferOrView(data, 'key', encoding),
449 format,
450 type,
451 passphrase:
452 passphrase != null
453 ? getArrayBufferOrView(passphrase, 'passphrase', encoding)
454 : undefined,
455 };
456 }
457 
458 // Final type check. The key data at this point has to be an object that
459 // we will interpret as a JWK.
460 if (typeof data !== 'object') {
461 throw new ERR_INVALID_ARG_TYPE(
462 'key',
463 ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView', 'string', 'object'],
464 key
465 );
466 }
467 
468 // At this point we ignore all remaining options and assume the key is a
469 // JSON Web Key.
470 return {
471 key: data as JsonWebKey,
472 format: 'jwk',
473 type: undefined,
474 passphrase: undefined,
475 };
476}
477 
478export function createPrivateKey(key: string): PrivateKeyObject;
479export function createPrivateKey(
480 key: ArrayBuffer | ArrayBufferView
481): PrivateKeyObject;
482export function createPrivateKey(
483 key: CreateAsymmetricKeyOptions
484): PrivateKeyObject;
485export function createPrivateKey(
486 key: CreateAsymmetricKeyOptions | KeyData
487): PrivateKeyObject {
488 const cryptoKey = cryptoImpl.createPrivateKey(
489 prepareAsymmetricKey(
490 key as CreateAsymmetricKeyOptions,
491 KeyContext.kCreatePrivate
492 )
493 );
494 return KeyObject.from(cryptoKey) as PrivateKeyObject;
495}
496 
497export function createPublicKey(key: string): PublicKeyObject;
498export function createPublicKey(key: ArrayBuffer): PublicKeyObject;
499export function createPublicKey(key: ArrayBufferView): PublicKeyObject;
500export function createPublicKey(key: KeyObject): PublicKeyObject;
501export function createPublicKey(key: CryptoKey): PublicKeyObject;
502export function createPublicKey(
503 key: CreateAsymmetricKeyOptions
504): PublicKeyObject;
505export function createPublicKey(
506 key: CreateAsymmetricKeyOptions | KeyData | CryptoKey | KeyObject
507): PublicKeyObject {
508 // Passing a KeyObject or a CryptoKey allows deriving the public key
509 // from an existing private key.
510 
511 if (isKeyObject(key)) {
512 if (key.type !== 'private') {
513 throw new ERR_INVALID_ARG_TYPE('key', 'PrivateKeyObject', key);
514 }
515 return KeyObject.from(
516 cryptoImpl.createPublicKey({
517 key: key[kHandle],
518 // The following are ignored when key is a CryptoKey.
519 format: 'pem',
520 type: undefined,
521 passphrase: undefined,
522 })
523 ) as PublicKeyObject;
524 }
525 
526 if (key instanceof CryptoKey) {
527 if (key.type !== 'private') {
528 throw new ERR_INVALID_ARG_TYPE('key', 'PrivateKeyObject', key);
529 }
530 return KeyObject.from(
531 cryptoImpl.createPublicKey({
532 key,
533 // The following are ignored when key is a CryptoKey.
534 format: 'pem',
535 type: undefined,
536 passphrase: undefined,
537 })
538 ) as PublicKeyObject;
539 }
540 
541 const cryptoKey = cryptoImpl.createPublicKey(
542 prepareAsymmetricKey(
543 key as CreateAsymmetricKeyOptions,
544 KeyContext.kCreatePublic
545 )
546 );
547 return KeyObject.from(cryptoKey) as PublicKeyObject;
548}
549 
550// ======================================================================================
551 
552export type PublicKeyResult = KeyExportResult | PublicKeyObject;
553export type PrivateKeyResult = KeyExportResult | PrivateKeyObject;
554export type GenerateKeyCallback = (
555 err?: unknown,
556 key?: SecretKeyObject
557) => void;
558export type GenerateKeyPairCallback = (
559 err?: unknown,
560 publicKey?: PublicKeyResult,
561 privateKey?: PrivateKeyResult
562) => void;
563 
564export interface KeyObjectPair {
565 publicKey: PublicKeyResult;
566 privateKey: PrivateKeyResult;
567}
568 
569export function generateKey(
570 _type: SecretKeyType,
571 _options: GenerateKeyOptions,
572 callback: GenerateKeyCallback
573): void {
574 try {
575 // Unlike Node.js, which implements async crypto functions using the
576 // libuv thread pool, we don't actually perform async crypto operations.
577 // Here we just defer to the sync version of the function and then "fake"
578 // async by using queueMicrotask to call the callback.
579 const result = generateKeySync(_type, _options);
580 queueMicrotask(() => {
581 try {
582 callback(null, result);
583 } catch (err) {
584 reportError(err);
585 }
586 });
587 } catch (err) {
588 queueMicrotask(() => {
589 try {
590 callback(err);
591 } catch (otherErr) {
592 reportError(otherErr);
593 }
594 });
595 }
596}
597 
598export function generateKeyPair(
599 _type: AsymmetricKeyType,
600 _options: GenerateKeyPairOptions,
601 callback: GenerateKeyPairCallback
602): void {
603 validateFunction(callback, 'callback');
604 try {
605 // Unlike Node.js, which implements async crypto functions using the
606 // libuv thread pool, we don't actually perform async crypto operations.
607 // Here we just defer to the sync version of the function and then "fake"
608 // async by using queueMicrotask to call the callback.
609 const { publicKey, privateKey } = generateKeyPairSync(_type, _options);
610 queueMicrotask(() => {
611 try {
612 callback(null, publicKey, privateKey);
613 } catch (err) {
614 reportError(err);
615 }
616 });
617 } catch (err) {
618 queueMicrotask(() => {
619 try {
620 callback(err);
621 } catch (otherErr) {
622 reportError(otherErr);
623 }
624 });
625 }
626}
627 
628Object.defineProperty(generateKeyPair, kCustomPromisifyArgsSymbol, {
629 value: ['publicKey', 'privateKey'],
630 enumerable: false,
631});
632 
633export function generateKeySync(
634 type: SecretKeyType,
635 options: GenerateKeyOptions
636): SecretKeyObject {
637 validateOneOf(type, 'type', ['hmac', 'aes']);
638 validateObject(options, 'options');
639 const { length } = options;
640 
641 switch (type) {
642 case 'hmac': {
643 // The minimum is 8, and the maximum length is 65,536. If the length is
644 // not a multiple of 8, the generated key will be truncated to
645 // Math.floor(length / 8).
646 // Note that the upper bound of 65536 is intentionally more limited than
647 // what Node.js allows. This puts the maximum size limit on generated
648 // secret keys to 8192 bytes. We can adjust this up if necessary but
649 // it's a good starting point.
650 validateInteger(length, 'options.length', 8, 65536);
651 const buf = randomBytes(Math.floor(length / 8));
652 return createSecretKey(buf);
653 }
654 case 'aes': {
655 // The length must be one of 128, 192, or 256.
656 validateOneOf(length, 'options.length', [128, 192, 256]);
657 const buf = randomBytes(length / 8);
658 return createSecretKey(buf);
659 }
660 }
661}
662 
663export function generateKeyPairSync(
664 type: AsymmetricKeyType,
665 options: GenerateKeyPairOptions = {}
666): KeyObjectPair {
667 validateOneOf(type, 'type', [
668 'rsa',
669 'ec',
670 'ed25519',
671 'x25519',
672 'dh',
673 // BoringSSL does not support the 448 variants.
674 // 'dsa',
675 // 'rsa-pss',
676 // 'ed448',
677 // 'x448',
678 ]);
679 
680 validateObject(options, 'options');
681 
682 const {
683 modulusLength, // Used for RSA/DSA. number
684 publicExponent = 0x10001, // Used for RSA. number
685 // Historically Node.js had "hash" and "mgf1Hash" but these were deprecated.
686 // It is still possible to find uses of the old names in the wild.
687 // TODO(later): Uncomment the following when rsa-pss generation is supported.
688 // hashAlgorithm, // Used for RSA-PSS. string
689 // mgf1HashAlgorithm, // Used for RSA-PSS. string
690 // hash, // Deprecated, use hashAlgorithm instead. string
691 // mgf1Hash, // Deprecated, use mgf1HashAlgorithm instead. string
692 // saltLength, // Used for RSA-PSS. number
693 namedCurve, // Used for EC. string
694 prime, // Used for DH. Buffer/ArrayBufferView/ArrayBuffer
695 primeLength, // Used for DH. number
696 generator, // Used for DH. number
697 // This is fun... Node.js docs say the option is "groupName" while
698 // the code appears to check for "group".
699 group, // Used for DH (alias for groupName)
700 groupName, // Used for DH. string
701 paramEncoding = 'named', // For for EC. Value is 'named' or 'explicit'.
702 publicKeyEncoding, // value must be an object, same options as export
703 privateKeyEncoding, // value must be an object, same options as export
704 } = options;
705 
706 // TODO(later): The divisorLength option is only used for generating DSA
707 // keypairs, which we currently do not support.
708 // let { divisorLength } = options;
709 
710 if (publicKeyEncoding !== undefined) {
711 validateExportOptions(
712 publicKeyEncoding as ExportOptions,
713 'public',
714 'options.publicKeyEncoding'
715 );
716 }
717 if (privateKeyEncoding !== undefined) {
718 validateExportOptions(
719 privateKeyEncoding as ExportOptions,
720 'private',
721 'options.privateKeyEncoding'
722 );
723 }
724 
725 const handleKeyEncoding = (
726 pair: CryptoKeyPair
727 ): {
728 publicKey: KeyObject | KeyExportResult;
729 privateKey: KeyObject | KeyExportResult;
730 } => {
731 let publicKey: KeyExportResult | KeyObject = KeyObject.from(pair.publicKey);
732 let privateKey: KeyExportResult | KeyObject = KeyObject.from(
733 pair.privateKey
734 );
735 if (publicKeyEncoding !== undefined) {
736 publicKey = publicKey.export(publicKeyEncoding);
737 }
738 if (privateKeyEncoding !== undefined) {
739 privateKey = privateKey.export(privateKeyEncoding);
740 }
741 return { publicKey, privateKey };
742 };
743 
744 // Validation of the specific options depends on the type of key being
745 // generated.
746 
747 switch (type) {
748 case 'rsa': {
749 validateUint32(modulusLength, 'options.modulusLength');
750 validateUint32(publicExponent, 'options.publicExponent');
751 return handleKeyEncoding(
752 cryptoImpl.generateRsaKeyPair({
753 type,
754 modulusLength: modulusLength,
755 publicExponent: publicExponent,
756 })
757 ) as KeyObjectPair;
758 }
759 // TODO(later): BoringSSL does not support RSA-PSS key generation in the
760 // same way as Node.js. Later see if there's an alternative approach.
761 // case 'rsa-pss': {
762 // validateUint32(modulusLength, 'options.modulusLength');
763 // validateUint32(publicExponent, 'options.publicExponent');
764 
765 // if (saltLength !== undefined) {
766 // validateInt32(saltLength, 'options.saltLength', 0);
767 // }
768 // if (hashAlgorithm !== undefined) {
769 // validateString(hashAlgorithm, 'options.hashAlgorithm');
770 // }
771 // if (mgf1HashAlgorithm !== undefined) {
772 // validateString(mgf1HashAlgorithm, 'options.mgf1HashAlgorithm');
773 // }
774 // if (hash !== undefined) {
775 // validateString(hash, 'options.hash');
776 // if (hashAlgorithm && hash !== hashAlgorithm) {
777 // throw new ERR_INVALID_ARG_VALUE('options.hash', hash);
778 // }
779 // }
780 // if (mgf1Hash !== undefined) {
781 // validateString(mgf1Hash, 'options.mgf1Hash');
782 // if (mgf1HashAlgorithm && mgf1Hash !== mgf1HashAlgorithm) {
783 // throw new ERR_INVALID_ARG_VALUE('options.mgf1Hash', mgf1Hash);
784 // }
785 // }
786 // return handleKeyEncoding(
787 // cryptoImpl.generateRsaKeyPair({
788 // type,
789 // modulusLength: modulusLength!,
790 // publicExponent: publicExponent!,
791 // saltLength: saltLength!,
792 // hashAlgorithm: hash || hashAlgorithm!,
793 // mgf1HashAlgorithm: mgf1Hash || mgf1HashAlgorithm!,
794 // })
795 // ) as KeyObjectPair;
796 // }
797 // TODO(later): BoringSSL does not support DSA key generation in the
798 // same way as Node.js. Later see if there's an alternative approach.
799 // case 'dsa': {
800 // validateUint32(modulusLength, 'options.modulusLength');
801 // if (divisorLength == null) {
802 // divisorLength = undefined;
803 // } else {
804 // validateInt32(divisorLength, 'options.divisorLength', 0);
805 // }
806 // return handleKeyEncoding(
807 // cryptoImpl.generateDsaKeyPair({
808 // modulusLength: modulusLength!,
809 // divisorLength: divisorLength as number,
810 // })
811 // ) as KeyObjectPair;
812 // }
813 case 'ec': {
814 validateString(namedCurve, 'options.namedCurve');
815 validateOneOf(paramEncoding, 'options.paramEncoding', [
816 'named',
817 'explicit',
818 ]);
819 return handleKeyEncoding(
820 cryptoImpl.generateEcKeyPair({
821 namedCurve,
822 paramEncoding,
823 })
824 ) as KeyObjectPair;
825 }
826 case 'ed25519':
827 // Fall-through
828 // eslint-disable-next-line no-fallthrough
829 case 'x25519': {
830 // Nothing to validate...
831 return handleKeyEncoding(
832 cryptoImpl.generateEdKeyPair({ type })
833 ) as KeyObjectPair;
834 }
835 case 'dh': {
836 if (generator != null) {
837 validateInt32(generator, 'options.generator', 0);
838 }
839 
840 if (group != null || groupName != null) {
841 if (prime != null) {
842 throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime');
843 }
844 if (primeLength != null) {
845 throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'primeLength');
846 }
847 if (generator != null) {
848 throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'generator');
849 }
850 
851 const g = group || groupName;
852 
853 validateString(g, 'options.group');
854 
855 return handleKeyEncoding(
856 cryptoImpl.generateDhKeyPair({
857 primeOrGroup: g,
858 generator,
859 })
860 ) as KeyObjectPair;
861 }
862 
863 if (prime != null) {
864 if (primeLength != null) {
865 throw new ERR_INCOMPATIBLE_OPTION_PAIR('prime', 'primeLength');
866 }
867 
868 if (!isArrayBufferView(prime) && !isAnyArrayBuffer(prime)) {
869 throw new ERR_INVALID_ARG_TYPE(
870 'options.prime',
871 ['Buffer', 'TypedArray', 'ArrayBuffer'],
872 prime
873 );
874 }
875 } else if (primeLength != null) {
876 validateInt32(primeLength, 'options.primeLength', 0);
877 } else {
878 throw new ERR_MISSING_OPTION(
879 'At least one of the group, prime, or primeLength options'
880 );
881 }
882 
883 if (prime) {
884 return handleKeyEncoding(
885 cryptoImpl.generateDhKeyPair({
886 primeOrGroup: prime as BufferSource,
887 generator: generator as number,
888 })
889 ) as KeyObjectPair;
890 }
891 
892 return handleKeyEncoding(
893 cryptoImpl.generateDhKeyPair({
894 primeOrGroup: primeLength as number,
895 generator: generator as number,
896 })
897 ) as KeyObjectPair;
898 }
899 }
900}