Skip to content

Commit 9cea4fd

Browse files
committed
crypto: accept key data in crypto.diffieHellman() and cleanup DH jobs
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent 1ccae7c commit 9cea4fd

File tree

9 files changed

+347
-187
lines changed

9 files changed

+347
-187
lines changed

doc/api/crypto.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4142,23 +4142,32 @@ added:
41424142
- v13.9.0
41434143
- v12.17.0
41444144
changes:
4145+
- version: REPLACEME
4146+
pr-url: https://github.com/nodejs/node/pull/62527
4147+
description: Accept key data in addition to KeyObject instances.
41454148
- version: v23.11.0
41464149
pr-url: https://github.com/nodejs/node/pull/57274
41474150
description: Optional callback argument added.
41484151
-->
41494152

41504153
* `options` {Object}
4151-
* `privateKey` {KeyObject}
4152-
* `publicKey` {KeyObject}
4154+
* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
4155+
* `publicKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
41534156
* `callback` {Function}
41544157
* `err` {Error}
41554158
* `secret` {Buffer}
41564159
* Returns: {Buffer} if the `callback` function is not provided.
41574160

41584161
Computes the Diffie-Hellman shared secret based on a `privateKey` and a `publicKey`.
4159-
Both keys must have the same `asymmetricKeyType` and must support either the DH or
4162+
Both keys must represent the same asymmetric key type and must support either the DH or
41604163
ECDH operation.
41614164

4165+
If `options.privateKey` is not a [`KeyObject`][], this function behaves as if
4166+
`options.privateKey` had been passed to [`crypto.createPrivateKey()`][].
4167+
4168+
If `options.publicKey` is not a [`KeyObject`][], this function behaves as if
4169+
`options.publicKey` had been passed to [`crypto.createPublicKey()`][].
4170+
41624171
If the `callback` function is provided this function uses libuv's threadpool.
41634172

41644173
### `crypto.encapsulate(key[, callback])`

lib/internal/crypto/diffiehellman.js

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const {
1717
DiffieHellman: _DiffieHellman,
1818
DiffieHellmanGroup: _DiffieHellmanGroup,
1919
ECDH: _ECDH,
20-
ECDHBitsJob,
2120
ECDHConvertKey: _ECDHConvertKey,
2221
kCryptoJobAsync,
2322
kCryptoJobSync,
@@ -52,9 +51,11 @@ const {
5251
} = require('internal/util');
5352

5453
const {
55-
KeyObject,
54+
isKeyObject,
5655
kAlgorithm,
5756
kKeyType,
57+
preparePrivateKey,
58+
preparePublicOrPrivateKey,
5859
} = require('internal/crypto/keys');
5960

6061
const {
@@ -284,31 +285,65 @@ function diffieHellman(options, callback) {
284285
validateFunction(callback, 'callback');
285286

286287
const { privateKey, publicKey } = options;
287-
if (!(privateKey instanceof KeyObject))
288+
289+
// TODO(@panva): remove these non-semver-major error code preserving measures
290+
// in a semver-major followup, the final state is just preparePublicOrPrivateKey
291+
// and preparePrivateKey
292+
if (privateKey == null)
288293
throw new ERR_INVALID_ARG_VALUE('options.privateKey', privateKey);
289294

290-
if (!(publicKey instanceof KeyObject))
295+
if (publicKey == null)
291296
throw new ERR_INVALID_ARG_VALUE('options.publicKey', publicKey);
292297

293-
if (privateKey.type !== 'private')
294-
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, 'private');
298+
if (isKeyObject(privateKey)) {
299+
if (privateKey.type !== 'private')
300+
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, 'private');
301+
}
295302

296-
if (publicKey.type !== 'public' && publicKey.type !== 'private') {
297-
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(publicKey.type,
298-
'private or public');
303+
if (isKeyObject(publicKey)) {
304+
if (publicKey.type !== 'public' && publicKey.type !== 'private') {
305+
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(publicKey.type,
306+
'private or public');
307+
}
299308
}
300309

301-
const privateType = privateKey.asymmetricKeyType;
302-
const publicType = publicKey.asymmetricKeyType;
303-
if (privateType !== publicType || !dhEnabledKeyTypes.has(privateType)) {
304-
throw new ERR_CRYPTO_INCOMPATIBLE_KEY('key types for Diffie-Hellman',
305-
`${privateType} and ${publicType}`);
310+
if (isKeyObject(privateKey) && isKeyObject(publicKey)) {
311+
const privateType = privateKey.asymmetricKeyType;
312+
const publicType = publicKey.asymmetricKeyType;
313+
if (privateType !== publicType || !dhEnabledKeyTypes.has(privateType)) {
314+
throw new ERR_CRYPTO_INCOMPATIBLE_KEY('key types for Diffie-Hellman',
315+
`${privateType} and ${publicType}`);
316+
}
306317
}
307318

319+
const {
320+
data: pubData,
321+
format: pubFormat,
322+
type: pubType,
323+
passphrase: pubPassphrase,
324+
namedCurve: pubNamedCurve,
325+
} = preparePublicOrPrivateKey(publicKey, 'options.publicKey');
326+
327+
const {
328+
data: privData,
329+
format: privFormat,
330+
type: privType,
331+
passphrase: privPassphrase,
332+
namedCurve: privNamedCurve,
333+
} = preparePrivateKey(privateKey, 'options.privateKey');
334+
308335
const job = new DHBitsJob(
309336
callback ? kCryptoJobAsync : kCryptoJobSync,
310-
publicKey[kHandle],
311-
privateKey[kHandle]);
337+
pubData,
338+
pubFormat,
339+
pubType,
340+
pubPassphrase,
341+
pubNamedCurve,
342+
privData,
343+
privFormat,
344+
privType,
345+
privPassphrase,
346+
privNamedCurve);
312347

313348
if (!callback) {
314349
const { 0: err, 1: secret } = job.run();
@@ -349,10 +384,18 @@ async function ecdhDeriveBits(algorithm, baseKey, length) {
349384
throw lazyDOMException('Named curve mismatch', 'InvalidAccessError');
350385
}
351386

352-
const bits = await jobPromise(() => new ECDHBitsJob(
387+
const bits = await jobPromise(() => new DHBitsJob(
353388
kCryptoJobAsync,
354389
key[kKeyObject][kHandle],
355-
baseKey[kKeyObject][kHandle]));
390+
undefined,
391+
undefined,
392+
undefined,
393+
undefined,
394+
baseKey[kKeyObject][kHandle],
395+
undefined,
396+
undefined,
397+
undefined,
398+
undefined));
356399

357400
// If a length is not specified, return the full derived secret
358401
if (length === null)

lib/internal/crypto/keys.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ function getKeyTypes(allowKeyObject, bufferOnly = false) {
628628
}
629629

630630

631-
function prepareAsymmetricKey(key, ctx) {
631+
function prepareAsymmetricKey(key, ctx, name = 'key') {
632632
if (isKeyObject(key)) {
633633
// Best case: A key object, as simple as that.
634634
return { data: getKeyObjectHandle(key, ctx) };
@@ -639,7 +639,7 @@ function prepareAsymmetricKey(key, ctx) {
639639
}
640640
if (isStringOrBuffer(key)) {
641641
// Expect PEM by default, mostly for backward compatibility.
642-
return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, 'key') };
642+
return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, name) };
643643
}
644644
if (typeof key === 'object') {
645645
const { key: data, encoding, format } = key;
@@ -654,23 +654,23 @@ function prepareAsymmetricKey(key, ctx) {
654654
return { data: getKeyObjectHandle(data[kKeyObject], ctx) };
655655
}
656656
if (format === 'jwk') {
657-
validateObject(data, 'key.key');
657+
validateObject(data, `${name}.key`);
658658
return { data, format: kKeyFormatJWK };
659659
} else if (format === 'raw-public' || format === 'raw-private' ||
660660
format === 'raw-seed') {
661661
if (!isStringOrBuffer(data)) {
662662
throw new ERR_INVALID_ARG_TYPE(
663-
'key.key',
663+
`${name}.key`,
664664
['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
665665
data);
666666
}
667-
validateString(key.asymmetricKeyType, 'key.asymmetricKeyType');
667+
validateString(key.asymmetricKeyType, `${name}.asymmetricKeyType`);
668668
if (key.asymmetricKeyType === 'ec') {
669-
validateString(key.namedCurve, 'key.namedCurve');
669+
validateString(key.namedCurve, `${name}.namedCurve`);
670670
}
671671
const rawFormat = parseKeyFormat(format, undefined, 'options.format');
672672
return {
673-
data: getArrayBufferOrView(data, 'key.key'),
673+
data: getArrayBufferOrView(data, `${name}.key`),
674674
format: rawFormat,
675675
type: key.asymmetricKeyType,
676676
namedCurve: key.namedCurve ?? null,
@@ -680,31 +680,31 @@ function prepareAsymmetricKey(key, ctx) {
680680
// Either PEM or DER using PKCS#1 or SPKI.
681681
if (!isStringOrBuffer(data)) {
682682
throw new ERR_INVALID_ARG_TYPE(
683-
'key.key',
683+
`${name}.key`,
684684
getKeyTypes(ctx !== kCreatePrivate),
685685
data);
686686
}
687687

688688
const isPublic =
689689
(ctx === kConsumePrivate || ctx === kCreatePrivate) ? false : undefined;
690690
return {
691-
data: getArrayBufferOrView(data, 'key', encoding),
691+
data: getArrayBufferOrView(data, `${name}.key`, encoding),
692692
...parseKeyEncoding(key, undefined, isPublic),
693693
};
694694
}
695695

696696
throw new ERR_INVALID_ARG_TYPE(
697-
'key',
697+
name,
698698
getKeyTypes(ctx !== kCreatePrivate),
699699
key);
700700
}
701701

702-
function preparePrivateKey(key) {
703-
return prepareAsymmetricKey(key, kConsumePrivate);
702+
function preparePrivateKey(key, name) {
703+
return prepareAsymmetricKey(key, kConsumePrivate, name);
704704
}
705705

706-
function preparePublicOrPrivateKey(key) {
707-
return prepareAsymmetricKey(key, kConsumePublic);
706+
function preparePublicOrPrivateKey(key, name) {
707+
return prepareAsymmetricKey(key, kConsumePublic, name);
708708
}
709709

710710
function prepareSecretKey(key, encoding, bufferOnly = false) {

lib/internal/crypto/sig.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ Sign.prototype.sign = function sign(options, encoding) {
135135
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
136136

137137
const { data, format, type, passphrase, namedCurve } =
138-
preparePrivateKey(options, true);
138+
preparePrivateKey(options);
139139

140140
// Options specific to RSA
141141
const rsaPadding = getPadding(options);
@@ -239,7 +239,7 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
239239
type,
240240
passphrase,
241241
namedCurve,
242-
} = preparePublicOrPrivateKey(options, true);
242+
} = preparePublicOrPrivateKey(options);
243243

244244
// Options specific to RSA
245245
const rsaPadding = getPadding(options);

src/crypto/crypto_dh.cc

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -477,20 +477,16 @@ Maybe<void> DHBitsTraits::AdditionalConfig(
477477
const FunctionCallbackInfo<Value>& args,
478478
unsigned int offset,
479479
DHBitsConfig* params) {
480-
CHECK(args[offset]->IsObject()); // public key
481-
CHECK(args[offset + 1]->IsObject()); // private key
480+
auto public_key = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset);
481+
if (!public_key) [[unlikely]]
482+
return Nothing<void>();
482483

483-
KeyObjectHandle* private_key;
484-
KeyObjectHandle* public_key;
484+
auto private_key = KeyObjectData::GetPrivateKeyFromJs(args, &offset, true);
485+
if (!private_key) [[unlikely]]
486+
return Nothing<void>();
485487

486-
ASSIGN_OR_RETURN_UNWRAP(&public_key, args[offset], Nothing<void>());
487-
ASSIGN_OR_RETURN_UNWRAP(&private_key, args[offset + 1], Nothing<void>());
488-
489-
CHECK(private_key->Data().GetKeyType() == kKeyTypePrivate);
490-
CHECK(public_key->Data().GetKeyType() != kKeyTypeSecret);
491-
492-
params->public_key = public_key->Data().addRef();
493-
params->private_key = private_key->Data().addRef();
488+
params->public_key = std::move(public_key);
489+
params->private_key = std::move(private_key);
494490

495491
return JustVoid();
496492
}

0 commit comments

Comments
 (0)