Skip to content

Commit b9d5e87

Browse files
panvasxa
authored andcommitted
crypto: accept key data in crypto.diffieHellman() and cleanup DH jobs
Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #62527 Backport-PR-URL: #63563 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent b46d52b commit b9d5e87

11 files changed

Lines changed: 474 additions & 204 deletions

File tree

doc/api/crypto.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4123,23 +4123,32 @@ added:
41234123
- v13.9.0
41244124
- v12.17.0
41254125
changes:
4126+
- version: REPLACEME
4127+
pr-url: https://github.com/nodejs/node/pull/62527
4128+
description: Accept key data in addition to KeyObject instances.
41264129
- version: v23.11.0
41274130
pr-url: https://github.com/nodejs/node/pull/57274
41284131
description: Optional callback argument added.
41294132
-->
41304133

41314134
* `options` {Object}
4132-
* `privateKey` {KeyObject}
4133-
* `publicKey` {KeyObject}
4135+
* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
4136+
* `publicKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
41344137
* `callback` {Function}
41354138
* `err` {Error}
41364139
* `secret` {Buffer}
41374140
* Returns: {Buffer} if the `callback` function is not provided.
41384141

41394142
Computes the Diffie-Hellman shared secret based on a `privateKey` and a `publicKey`.
4140-
Both keys must have the same `asymmetricKeyType` and must support either the DH or
4143+
Both keys must represent the same asymmetric key type and must support either the DH or
41414144
ECDH operation.
41424145

4146+
If `options.privateKey` is not a [`KeyObject`][], this function behaves as if
4147+
`options.privateKey` had been passed to [`crypto.createPrivateKey()`][].
4148+
4149+
If `options.publicKey` is not a [`KeyObject`][], this function behaves as if
4150+
`options.publicKey` had been passed to [`crypto.createPublicKey()`][].
4151+
41434152
If the `callback` function is provided this function uses libuv's threadpool.
41444153

41454154
### `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,
@@ -51,9 +50,11 @@ const {
5150
} = require('internal/util');
5251

5352
const {
54-
KeyObject,
53+
isKeyObject,
5554
kAlgorithm,
5655
kKeyType,
56+
preparePrivateKey,
57+
preparePublicOrPrivateKey,
5758
} = require('internal/crypto/keys');
5859

5960
const {
@@ -281,31 +282,65 @@ function diffieHellman(options, callback) {
281282
validateFunction(callback, 'callback');
282283

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

287-
if (!(publicKey instanceof KeyObject))
292+
if (publicKey == null)
288293
throw new ERR_INVALID_ARG_VALUE('options.publicKey', publicKey);
289294

290-
if (privateKey.type !== 'private')
291-
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, 'private');
295+
if (isKeyObject(privateKey)) {
296+
if (privateKey.type !== 'private')
297+
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, 'private');
298+
}
292299

293-
if (publicKey.type !== 'public' && publicKey.type !== 'private') {
294-
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(publicKey.type,
295-
'private or public');
300+
if (isKeyObject(publicKey)) {
301+
if (publicKey.type !== 'public' && publicKey.type !== 'private') {
302+
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(publicKey.type,
303+
'private or public');
304+
}
296305
}
297306

298-
const privateType = privateKey.asymmetricKeyType;
299-
const publicType = publicKey.asymmetricKeyType;
300-
if (privateType !== publicType || !dhEnabledKeyTypes.has(privateType)) {
301-
throw new ERR_CRYPTO_INCOMPATIBLE_KEY('key types for Diffie-Hellman',
302-
`${privateType} and ${publicType}`);
307+
if (isKeyObject(privateKey) && isKeyObject(publicKey)) {
308+
const privateType = privateKey.asymmetricKeyType;
309+
const publicType = publicKey.asymmetricKeyType;
310+
if (privateType !== publicType || !dhEnabledKeyTypes.has(privateType)) {
311+
throw new ERR_CRYPTO_INCOMPATIBLE_KEY('key types for Diffie-Hellman',
312+
`${privateType} and ${publicType}`);
313+
}
303314
}
304315

316+
const {
317+
data: pubData,
318+
format: pubFormat,
319+
type: pubType,
320+
passphrase: pubPassphrase,
321+
namedCurve: pubNamedCurve,
322+
} = preparePublicOrPrivateKey(publicKey, 'options.publicKey');
323+
324+
const {
325+
data: privData,
326+
format: privFormat,
327+
type: privType,
328+
passphrase: privPassphrase,
329+
namedCurve: privNamedCurve,
330+
} = preparePrivateKey(privateKey, 'options.privateKey');
331+
305332
const job = new DHBitsJob(
306333
callback ? kCryptoJobAsync : kCryptoJobSync,
307-
publicKey[kHandle],
308-
privateKey[kHandle]);
334+
pubData,
335+
pubFormat,
336+
pubType,
337+
pubPassphrase,
338+
pubNamedCurve,
339+
privData,
340+
privFormat,
341+
privType,
342+
privPassphrase,
343+
privNamedCurve);
309344

310345
if (!callback) {
311346
const { 0: err, 1: secret } = job.run();
@@ -346,10 +381,18 @@ async function ecdhDeriveBits(algorithm, baseKey, length) {
346381
throw lazyDOMException('Named curve mismatch', 'InvalidAccessError');
347382
}
348383

349-
const bits = await jobPromise(() => new ECDHBitsJob(
384+
const bits = await jobPromise(() => new DHBitsJob(
350385
kCryptoJobAsync,
351386
key[kKeyObject][kHandle],
352-
baseKey[kKeyObject][kHandle]));
387+
undefined,
388+
undefined,
389+
undefined,
390+
undefined,
391+
baseKey[kKeyObject][kHandle],
392+
undefined,
393+
undefined,
394+
undefined,
395+
undefined));
353396

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

lib/internal/crypto/keys.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -467,9 +467,9 @@ function parseKeyType(typeStr, required, keyType, isPublic, optionName) {
467467
throw new ERR_INVALID_ARG_VALUE(optionName, typeStr);
468468
}
469469

470-
function option(name, objName) {
471-
return objName === undefined ?
472-
`options.${name}` : `options.${objName}.${name}`;
470+
function option(name, prefix) {
471+
return prefix === undefined ?
472+
`options.${name}` : `${prefix}.${name}`;
473473
}
474474

475475
function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
@@ -629,7 +629,7 @@ function getKeyTypes(allowKeyObject, bufferOnly = false) {
629629
}
630630

631631

632-
function prepareAsymmetricKey(key, ctx) {
632+
function prepareAsymmetricKey(key, ctx, name = 'key') {
633633
if (isKeyObject(key)) {
634634
// Best case: A key object, as simple as that.
635635
return { data: getKeyObjectHandle(key, ctx) };
@@ -640,7 +640,7 @@ function prepareAsymmetricKey(key, ctx) {
640640
}
641641
if (isStringOrBuffer(key)) {
642642
// Expect PEM by default, mostly for backward compatibility.
643-
return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, 'key') };
643+
return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, name) };
644644
}
645645
if (typeof key === 'object') {
646646
const { key: data, encoding, format } = key;
@@ -655,7 +655,7 @@ function prepareAsymmetricKey(key, ctx) {
655655
return { data: getKeyObjectHandle(data[kKeyObject], ctx) };
656656
}
657657
if (format === 'jwk') {
658-
validateObject(data, 'key.key');
658+
validateObject(data, `${name}.key`);
659659
return { data, format: kKeyFormatJWK };
660660
} else if (format === 'raw-public' || format === 'raw-private' ||
661661
format === 'raw-seed') {
@@ -681,31 +681,31 @@ function prepareAsymmetricKey(key, ctx) {
681681
// Either PEM or DER using PKCS#1 or SPKI.
682682
if (!isStringOrBuffer(data)) {
683683
throw new ERR_INVALID_ARG_TYPE(
684-
'key.key',
684+
`${name}.key`,
685685
getKeyTypes(ctx !== kCreatePrivate),
686686
data);
687687
}
688688

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

697697
throw new ERR_INVALID_ARG_TYPE(
698-
'key',
698+
name,
699699
getKeyTypes(ctx !== kCreatePrivate),
700700
key);
701701
}
702702

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

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

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

lib/internal/crypto/sig.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,19 @@ function getIntOption(name, options) {
130130
return undefined;
131131
}
132132

133-
Sign.prototype.sign = function sign(options, encoding) {
134-
if (!options)
133+
Sign.prototype.sign = function sign(privateKey, encoding) {
134+
if (!privateKey)
135135
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
136136

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

140140
// Options specific to RSA
141-
const rsaPadding = getPadding(options);
142-
const pssSaltLength = getSaltLength(options);
141+
const rsaPadding = getPadding(privateKey);
142+
const pssSaltLength = getSaltLength(privateKey);
143143

144144
// Options specific to (EC)DSA
145-
const dsaSigEnc = getDSASignatureEncoding(options);
145+
const dsaSigEnc = getDSASignatureEncoding(privateKey);
146146

147147
const ret = this[kHandle].sign(data, format, type,
148148
passphrase, namedCurve,
@@ -232,21 +232,21 @@ ObjectSetPrototypeOf(Verify, Writable);
232232
Verify.prototype._write = Sign.prototype._write;
233233
Verify.prototype.update = Sign.prototype.update;
234234

235-
Verify.prototype.verify = function verify(options, signature, sigEncoding) {
235+
Verify.prototype.verify = function verify(key, signature, sigEncoding) {
236236
const {
237237
data,
238238
format,
239239
type,
240240
passphrase,
241241
namedCurve,
242-
} = preparePublicOrPrivateKey(options, true);
242+
} = preparePublicOrPrivateKey(key, 'key');
243243

244244
// Options specific to RSA
245-
const rsaPadding = getPadding(options);
246-
const pssSaltLength = getSaltLength(options);
245+
const rsaPadding = getPadding(key);
246+
const pssSaltLength = getSaltLength(key);
247247

248248
// Options specific to (EC)DSA
249-
const dsaSigEnc = getDSASignatureEncoding(options);
249+
const dsaSigEnc = getDSASignatureEncoding(key);
250250

251251
signature = getArrayBufferOrView(signature, 'signature', sigEncoding);
252252

src/crypto/crypto_dh.cc

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -505,20 +505,16 @@ Maybe<void> DHBitsTraits::AdditionalConfig(
505505
const FunctionCallbackInfo<Value>& args,
506506
unsigned int offset,
507507
DHBitsConfig* params) {
508-
CHECK(args[offset]->IsObject()); // public key
509-
CHECK(args[offset + 1]->IsObject()); // private key
508+
auto public_key = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset);
509+
if (!public_key) [[unlikely]]
510+
return Nothing<void>();
510511

511-
KeyObjectHandle* private_key;
512-
KeyObjectHandle* public_key;
512+
auto private_key = KeyObjectData::GetPrivateKeyFromJs(args, &offset, true);
513+
if (!private_key) [[unlikely]]
514+
return Nothing<void>();
513515

514-
ASSIGN_OR_RETURN_UNWRAP(&public_key, args[offset], Nothing<void>());
515-
ASSIGN_OR_RETURN_UNWRAP(&private_key, args[offset + 1], Nothing<void>());
516-
517-
CHECK(private_key->Data().GetKeyType() == kKeyTypePrivate);
518-
CHECK(public_key->Data().GetKeyType() != kKeyTypeSecret);
519-
520-
params->public_key = public_key->Data().addRef();
521-
params->private_key = private_key->Data().addRef();
516+
params->public_key = std::move(public_key);
517+
params->private_key = std::move(private_key);
522518

523519
return JustVoid();
524520
}

0 commit comments

Comments
 (0)