Skip to content

Commit 55e522c

Browse files
panvadanielleadams
authored andcommitted
crypto: support JWK objects in create*Key
PR-URL: #37254 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 4de3b84 commit 55e522c

4 files changed

Lines changed: 246 additions & 56 deletions

File tree

‎doc/api/crypto.md‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2452,6 +2452,9 @@ input.on('readable', () => {
24522452
<!-- YAML
24532453
added: v11.6.0
24542454
changes:
2455+
- version: REPLACEME
2456+
pr-url: https://github.com/nodejs/node/pull/37254
2457+
description: The key can also be a JWK object.
24552458
- version: v15.0.0
24562459
pr-url: https://github.com/nodejs/node/pull/35093
24572460
description: The key can also be an ArrayBuffer. The encoding option was
@@ -2460,11 +2463,12 @@ changes:
24602463

24612464
<!--lint disable maximum-line-length remark-lint-->
24622465
*`key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView}
2463-
*`key`: {string|ArrayBuffer|Buffer|TypedArray|DataView} The key material,
2464-
either in PEM or DER format.
2465-
*`format`: {string} Must be `'pem'` or `'der'`. **Default:**`'pem'`.
2466+
*`key`: {string|ArrayBuffer|Buffer|TypedArray|DataView|Object} The key
2467+
material, either in PEM, DER, or JWK format.
2468+
*`format`: {string} Must be `'pem'`, `'der'`, or '`'jwk'`.
2469+
**Default:**`'pem'`.
24662470
*`type`: {string} Must be `'pkcs1'`, `'pkcs8'` or `'sec1'`. This option is
2467-
required only if the `format` is `'der'` and ignored if it is `'pem'`.
2471+
required only if the `format` is `'der'` and ignored otherwise.
24682472
*`passphrase`: {string | Buffer} The passphrase to use for decryption.
24692473
*`encoding`: {string} The string encoding to use when `key` is a string.
24702474
* Returns: {KeyObject}
@@ -2481,6 +2485,9 @@ of the passphrase is limited to 1024 bytes.
24812485
<!-- YAML
24822486
added: v11.6.0
24832487
changes:
2488+
- version: REPLACEME
2489+
pr-url: https://github.com/nodejs/node/pull/37254
2490+
description: The key can also be a JWK object.
24842491
- version: v15.0.0
24852492
pr-url: https://github.com/nodejs/node/pull/35093
24862493
description: The key can also be an ArrayBuffer. The encoding option was
@@ -2496,10 +2503,12 @@ changes:
24962503

24972504
<!--lint disable maximum-line-length remark-lint-->
24982505
*`key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView}
2499-
*`key`: {string|ArrayBuffer|Buffer|TypedArray|DataView}
2500-
*`format`: {string} Must be `'pem'` or `'der'`. **Default:**`'pem'`.
2501-
*`type`: {string} Must be `'pkcs1'` or `'spki'`. This option is required
2502-
only if the `format` is `'der'`.
2506+
*`key`: {string|ArrayBuffer|Buffer|TypedArray|DataView|Object} The key
2507+
material, either in PEM, DER, or JWK format.
2508+
*`format`: {string} Must be `'pem'`, `'der'`, or '`'jwk'`.
2509+
**Default:**`'pem'`.
2510+
*`type`: {string} Must be `'pkcs1'` or `'spki'`. This option is
2511+
required only if the `format` is `'der'` and ignored otherwise.
25032512
*`encoding` {string} The string encoding to use when `key` is a string.
25042513
* Returns: {KeyObject}
25052514
<!--lint enable maximum-line-length remark-lint-->

‎lib/internal/crypto/keys.js‎

Lines changed: 141 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const {
2626
const{
2727
validateObject,
2828
validateOneOf,
29+
validateString,
2930
}=require('internal/validators');
3031

3132
const{
@@ -38,6 +39,7 @@ const {
3839
ERR_OPERATION_FAILED,
3940
ERR_CRYPTO_JWK_UNSUPPORTED_CURVE,
4041
ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE,
42+
ERR_CRYPTO_INVALID_JWK,
4143
}
4244
}=require('internal/errors');
4345

@@ -65,6 +67,8 @@ const {
6567

6668
const{ inspect }=require('internal/util/inspect');
6769

70+
const{ Buffer }=require('buffer');
71+
6872
constkAlgorithm=Symbol('kAlgorithm');
6973
constkExtractable=Symbol('kExtractable');
7074
constkKeyType=Symbol('kKeyType');
@@ -413,6 +417,122 @@ function getKeyTypes(allowKeyObject, bufferOnly = false) {
413417
returntypes;
414418
}
415419

420+
functiongetKeyObjectHandleFromJwk(key,ctx){
421+
validateObject(key,'key');
422+
validateOneOf(
423+
key.kty,'key.kty',['RSA','EC','OKP']);
424+
constisPublic=ctx===kConsumePublic||ctx===kCreatePublic;
425+
426+
if(key.kty==='OKP'){
427+
validateString(key.crv,'key.crv');
428+
validateOneOf(
429+
key.crv,'key.crv',['Ed25519','Ed448','X25519','X448']);
430+
validateString(key.x,'key.x');
431+
432+
if(!isPublic)
433+
validateString(key.d,'key.d');
434+
435+
letkeyData;
436+
if(isPublic)
437+
keyData=Buffer.from(key.x,'base64');
438+
else
439+
keyData=Buffer.from(key.d,'base64');
440+
441+
switch(key.crv){
442+
case'Ed25519':
443+
case'X25519':
444+
if(keyData.byteLength!==32){
445+
thrownewERR_CRYPTO_INVALID_JWK();
446+
}
447+
break;
448+
case'Ed448':
449+
if(keyData.byteLength!==57){
450+
thrownewERR_CRYPTO_INVALID_JWK();
451+
}
452+
break;
453+
case'X448':
454+
if(keyData.byteLength!==56){
455+
thrownewERR_CRYPTO_INVALID_JWK();
456+
}
457+
break;
458+
}
459+
460+
consthandle=newKeyObjectHandle();
461+
if(isPublic){
462+
handle.initEDRaw(
463+
`NODE-${key.crv.toUpperCase()}`,
464+
keyData,
465+
kKeyTypePublic);
466+
}else{
467+
handle.initEDRaw(
468+
`NODE-${key.crv.toUpperCase()}`,
469+
keyData,
470+
kKeyTypePrivate);
471+
}
472+
473+
returnhandle;
474+
}
475+
476+
if(key.kty==='EC'){
477+
validateString(key.crv,'key.crv');
478+
validateOneOf(
479+
key.crv,'key.crv',['P-256','secp256k1','P-384','P-521']);
480+
validateString(key.x,'key.x');
481+
validateString(key.y,'key.y');
482+
483+
constjwk={
484+
kty: key.kty,
485+
crv: key.crv,
486+
x: key.x,
487+
y: key.y
488+
};
489+
490+
if(!isPublic){
491+
validateString(key.d,'key.d');
492+
jwk.d=key.d;
493+
}
494+
495+
consthandle=newKeyObjectHandle();
496+
consttype=handle.initJwk(jwk,jwk.crv);
497+
if(type===undefined)
498+
thrownewERR_CRYPTO_INVALID_JWK();
499+
500+
returnhandle;
501+
}
502+
503+
// RSA
504+
validateString(key.n,'key.n');
505+
validateString(key.e,'key.e');
506+
507+
constjwk={
508+
kty: key.kty,
509+
n: key.n,
510+
e: key.e
511+
};
512+
513+
if(!isPublic){
514+
validateString(key.d,'key.d');
515+
validateString(key.p,'key.p');
516+
validateString(key.q,'key.q');
517+
validateString(key.dp,'key.dp');
518+
validateString(key.dq,'key.dq');
519+
validateString(key.qi,'key.qi');
520+
jwk.d=key.d;
521+
jwk.p=key.p;
522+
jwk.q=key.q;
523+
jwk.dp=key.dp;
524+
jwk.dq=key.dq;
525+
jwk.qi=key.qi;
526+
}
527+
528+
consthandle=newKeyObjectHandle();
529+
consttype=handle.initJwk(jwk);
530+
if(type===undefined)
531+
thrownewERR_CRYPTO_INVALID_JWK();
532+
533+
returnhandle;
534+
}
535+
416536
functionprepareAsymmetricKey(key,ctx){
417537
if(isKeyObject(key)){
418538
// Best case: A key object, as simple as that.
@@ -423,13 +543,15 @@ function prepareAsymmetricKey(key, ctx) {
423543
// Expect PEM by default, mostly for backward compatibility.
424544
return{format: kKeyFormatPEM,data: getArrayBufferOrView(key,'key')};
425545
}elseif(typeofkey==='object'){
426-
const{key: data, encoding }=key;
546+
const{key: data, encoding, format}=key;
427547
// The 'key' property can be a KeyObject as well to allow specifying
428548
// additional options such as padding along with the key.
429549
if(isKeyObject(data))
430550
return{data: getKeyObjectHandle(data,ctx)};
431551
elseif(isCryptoKey(data))
432552
return{data: getKeyObjectHandle(data[kKeyObject],ctx)};
553+
elseif(isJwk(data)&&format==='jwk')
554+
return{data: getKeyObjectHandleFromJwk(data,ctx),format: 'jwk'};
433555
// Either PEM or DER using PKCS#1 or SPKI.
434556
if(!isStringOrBuffer(data)){
435557
thrownewERR_INVALID_ARG_TYPE(
@@ -494,16 +616,26 @@ function createSecretKey(key, encoding) {
494616
functioncreatePublicKey(key){
495617
const{ format, type, data, passphrase }=
496618
prepareAsymmetricKey(key,kCreatePublic);
497-
consthandle=newKeyObjectHandle();
498-
handle.init(kKeyTypePublic,data,format,type,passphrase);
619+
lethandle;
620+
if(format==='jwk'){
621+
handle=data;
622+
}else{
623+
handle=newKeyObjectHandle();
624+
handle.init(kKeyTypePublic,data,format,type,passphrase);
625+
}
499626
returnnewPublicKeyObject(handle);
500627
}
501628

502629
functioncreatePrivateKey(key){
503630
const{ format, type, data, passphrase }=
504631
prepareAsymmetricKey(key,kCreatePrivate);
505-
consthandle=newKeyObjectHandle();
506-
handle.init(kKeyTypePrivate,data,format,type,passphrase);
632+
lethandle;
633+
if(format==='jwk'){
634+
handle=data;
635+
}else{
636+
handle=newKeyObjectHandle();
637+
handle.init(kKeyTypePrivate,data,format,type,passphrase);
638+
}
507639
returnnewPrivateKeyObject(handle);
508640
}
509641

@@ -609,6 +741,10 @@ function isCryptoKey(obj) {
609741
returnobj!=null&&obj[kKeyObject]!==undefined;
610742
}
611743

744+
functionisJwk(obj){
745+
returnobj!=null&&obj.kty!==undefined;
746+
}
747+
612748
module.exports={
613749
// Public API.
614750
createSecretKey,

‎lib/internal/errors.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,7 @@ E('ERR_CRYPTO_INCOMPATIBLE_KEY', 'Incompatible %s: %s', Error);
836836
E('ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS','The selected key encoding %s %s.',
837837
Error);
838838
E('ERR_CRYPTO_INVALID_DIGEST','Invalid digest: %s',TypeError);
839+
E('ERR_CRYPTO_INVALID_JWK','Invalid JWK data',TypeError);
839840
E('ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',
840841
'Invalid key object type %s, expected %s.',TypeError);
841842
E('ERR_CRYPTO_INVALID_STATE','Invalid state for operation %s',Error);

0 commit comments

Comments
 (0)