Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 011910b

Browse filesBrowse files
panvadanielleadams
authored andcommitted
crypto: add keyObject.export() 'jwk' format option
Adds [JWK](https://tools.ietf.org/html/rfc7517) keyObject.export format option. Supported key types: `ec`, `rsa`, `ed25519`, `ed448`, `x25519`, `x448`, and symmetric keys, resulting in JWK `kty` (Key Type) values `EC`, `RSA`, `OKP`, and `oct`. `rsa-pss` is not supported since the JWK format does not support PSS Parameters. `EC` JWK curves supported are `P-256`, `secp256k1`, `P-384`, and `P-521` PR-URL: #37081 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
1 parent dc84c18 commit 011910b
Copy full SHA for 011910b

File tree

Expand file treeCollapse file tree

14 files changed

+402
-25
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

14 files changed

+402
-25
lines changed
Open diff view settings
Collapse file

‎doc/api/crypto.md‎

Copy file name to clipboardExpand all lines: doc/api/crypto.md
+16-9Lines changed: 16 additions & 9 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1348,35 +1348,41 @@ keys.
13481348
### `keyObject.export([options])`
13491349
<!-- YAML
13501350
added: v11.6.0
1351+
changes:
1352+
- version: REPLACEME
1353+
pr-url: https://github.com/nodejs/node/pull/37081
1354+
description: Added support for `'jwk'` format.
13511355
-->
13521356

13531357
* `options`: {Object}
1354-
* Returns: {string | Buffer}
1358+
* Returns: {string | Buffer | Object}
13551359

1356-
For symmetric keys, this function allocates a `Buffer` containing the key
1357-
material and ignores any options.
1360+
For symmetric keys, the following encoding options can be used:
13581361

1359-
For asymmetric keys, the `options` parameter is used to determine the export
1360-
format.
1362+
* `format`: {string} Must be `'buffer'` (default) or `'jwk'`.
13611363

13621364
For public keys, the following encoding options can be used:
13631365

13641366
* `type`: {string} Must be one of `'pkcs1'` (RSA only) or `'spki'`.
1365-
* `format`: {string} Must be `'pem'` or `'der'`.
1367+
* `format`: {string} Must be `'pem'`, `'der'`, or `'jwk'`.
13661368

13671369
For private keys, the following encoding options can be used:
13681370

13691371
* `type`: {string} Must be one of `'pkcs1'` (RSA only), `'pkcs8'` or
13701372
`'sec1'` (EC only).
1371-
* `format`: {string} Must be `'pem'` or `'der'`.
1373+
* `format`: {string} Must be `'pem'`, `'der'`, or `'jwk'`.
13721374
* `cipher`: {string} If specified, the private key will be encrypted with
13731375
the given `cipher` and `passphrase` using PKCS#5 v2.0 password based
13741376
encryption.
13751377
* `passphrase`: {string | Buffer} The passphrase to use for encryption, see
13761378
`cipher`.
13771379

1378-
When PEM encoding was selected, the result will be a string, otherwise it will
1379-
be a buffer containing the data encoded as DER.
1380+
The result type depends on the selected encoding format, when PEM the
1381+
result is a string, when DER it will be a buffer containing the data
1382+
encoded as DER, when [JWK][] it will be an object.
1383+
1384+
When [JWK][] encoding format was selected, all other encoding options are
1385+
ignored.
13801386

13811387
PKCS#1, SEC1, and PKCS#8 type keys can be encrypted by using a combination of
13821388
the `cipher` and `format` options. The PKCS#8 `type` can be used with any
@@ -4355,6 +4361,7 @@ See the [list of SSL OP Flags][] for details.
43554361
[Crypto constants]: #crypto_crypto_constants_1
43564362
[HTML 5.2]: https://www.w3.org/TR/html52/changes.html#features-removed
43574363
[HTML5's `keygen` element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen
4364+
[JWK]: https://tools.ietf.org/html/rfc7517
43584365
[NIST SP 800-131A]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf
43594366
[NIST SP 800-132]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf
43604367
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
Collapse file

‎doc/api/errors.md‎

Copy file name to clipboardExpand all lines: doc/api/errors.md
+14Lines changed: 14 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,18 @@ added: v15.0.0
919919

920920
Initialization of an asynchronous crypto operation failed.
921921

922+
<a id="ERR_CRYPTO_JWK_UNSUPPORTED_CURVE"></a>
923+
### `ERR_CRYPTO_JWK_UNSUPPORTED_CURVE`
924+
925+
Key's Elliptic Curve is not registered for use in the
926+
[JSON Web Key Elliptic Curve Registry][].
927+
928+
<a id="ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE"></a>
929+
### `ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE`
930+
931+
Key's Asymmetric Key Type is not registered for use in the
932+
[JSON Web Key Types Registry][].
933+
922934
<a id="ERR_CRYPTO_OPERATION_FAILED"></a>
923935
### `ERR_CRYPTO_OPERATION_FAILED`
924936
<!-- YAML
@@ -2716,6 +2728,8 @@ The native call from `process.cpuUsage` could not be processed.
27162728

27172729
[ES Module]: esm.md
27182730
[ICU]: intl.md#intl_internationalization_support
2731+
[JSON Web Key Elliptic Curve Registry]: https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve
2732+
[JSON Web Key Types Registry]: https://www.iana.org/assignments/jose/jose.xhtml#web-key-types
27192733
[Node.js error codes]: #nodejs-error-codes
27202734
[RFC 7230 Section 3]: https://tools.ietf.org/html/rfc7230#section-3
27212735
[Subresource Integrity specification]: https://www.w3.org/TR/SRI/#the-integrity-attribute
Collapse file

‎lib/internal/crypto/keys.js‎

Copy file name to clipboardExpand all lines: lib/internal/crypto/keys.js
+55-5Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ const {
2222
kKeyEncodingSEC1,
2323
} = internalBinding('crypto');
2424

25+
const {
26+
validateObject,
27+
validateOneOf,
28+
} = require('internal/validators');
29+
2530
const {
2631
codes: {
2732
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
@@ -30,6 +35,8 @@ const {
3035
ERR_INVALID_ARG_VALUE,
3136
ERR_OUT_OF_RANGE,
3237
ERR_OPERATION_FAILED,
38+
ERR_CRYPTO_JWK_UNSUPPORTED_CURVE,
39+
ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE,
3340
}
3441
} = require('internal/errors');
3542

@@ -124,13 +131,22 @@ const [
124131
return this[kHandle].getSymmetricKeySize();
125132
}
126133

127-
export() {
134+
export(options) {
135+
if (options !== undefined) {
136+
validateObject(options, 'options');
137+
validateOneOf(
138+
options.format, 'options.format', [undefined, 'buffer', 'jwk']);
139+
if (options.format === 'jwk') {
140+
return this[kHandle].exportJwk({});
141+
}
142+
}
128143
return this[kHandle].export();
129144
}
130145
}
131146

132147
const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
133148
const kAsymmetricKeyDetails = Symbol('kAsymmetricKeyDetails');
149+
const kAsymmetricKeyJWKProperties = Symbol('kAsymmetricKeyJWKProperties');
134150

135151
function normalizeKeyDetails(details = {}) {
136152
if (details.publicExponent !== undefined) {
@@ -163,18 +179,44 @@ const [
163179
return {};
164180
}
165181
}
182+
183+
[kAsymmetricKeyJWKProperties]() {
184+
switch (this.asymmetricKeyType) {
185+
case 'rsa': return {};
186+
case 'ec':
187+
switch (this.asymmetricKeyDetails.namedCurve) {
188+
case 'prime256v1': return { crv: 'P-256' };
189+
case 'secp256k1': return { crv: 'secp256k1' };
190+
case 'secp384r1': return { crv: 'P-384' };
191+
case 'secp521r1': return { crv: 'P-521' };
192+
default:
193+
throw new ERR_CRYPTO_JWK_UNSUPPORTED_CURVE(
194+
this.asymmetricKeyDetails.namedCurve);
195+
}
196+
case 'ed25519': return { crv: 'Ed25519' };
197+
case 'ed448': return { crv: 'Ed448' };
198+
case 'x25519': return { crv: 'X25519' };
199+
case 'x448': return { crv: 'X448' };
200+
default:
201+
throw new ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE();
202+
}
203+
}
166204
}
167205

168206
class PublicKeyObject extends AsymmetricKeyObject {
169207
constructor(handle) {
170208
super('public', handle);
171209
}
172210

173-
export(encoding) {
211+
export(options) {
212+
if (options && options.format === 'jwk') {
213+
const properties = this[kAsymmetricKeyJWKProperties]();
214+
return this[kHandle].exportJwk(properties);
215+
}
174216
const {
175217
format,
176218
type
177-
} = parsePublicKeyEncoding(encoding, this.asymmetricKeyType);
219+
} = parsePublicKeyEncoding(options, this.asymmetricKeyType);
178220
return this[kHandle].export(format, type);
179221
}
180222
}
@@ -184,13 +226,21 @@ const [
184226
super('private', handle);
185227
}
186228

187-
export(encoding) {
229+
export(options) {
230+
if (options && options.format === 'jwk') {
231+
if (options.passphrase !== undefined) {
232+
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
233+
'jwk', 'does not support encryption');
234+
}
235+
const properties = this[kAsymmetricKeyJWKProperties]();
236+
return this[kHandle].exportJwk(properties);
237+
}
188238
const {
189239
format,
190240
type,
191241
cipher,
192242
passphrase
193-
} = parsePrivateKeyEncoding(encoding, this.asymmetricKeyType);
243+
} = parsePrivateKeyEncoding(options, this.asymmetricKeyType);
194244
return this[kHandle].export(format, type, cipher, passphrase);
195245
}
196246
}
Collapse file

‎lib/internal/errors.js‎

Copy file name to clipboardExpand all lines: lib/internal/errors.js
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,8 @@ E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s', TypeError);
839839
E('ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',
840840
'Invalid key object type %s, expected %s.', TypeError);
841841
E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s', Error);
842+
E('ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', 'Unsupported JWK EC curve: %s.', Error);
843+
E('ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', 'Unsupported JWK Key Type.', Error);
842844
E('ERR_CRYPTO_PBKDF2_ERROR', 'PBKDF2 error', Error);
843845
E('ERR_CRYPTO_SCRYPT_INVALID_PARAMETER', 'Invalid scrypt parameter', Error);
844846
E('ERR_CRYPTO_SCRYPT_NOT_SUPPORTED', 'Scrypt algorithm not supported', Error);
Collapse file

‎test/fixtures/keys/Makefile‎

Copy file name to clipboardExpand all lines: test/fixtures/keys/Makefile
+41-1Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@ all: \
7575
ed448_public.pem \
7676
x448_private.pem \
7777
x448_public.pem \
78+
ec_p256_private.pem \
79+
ec_p256_public.pem \
80+
ec_p384_private.pem \
81+
ec_p384_public.pem \
82+
ec_p521_private.pem \
83+
ec_p521_public.pem \
84+
ec_secp256k1_private.pem \
85+
ec_secp256k1_public.pem \
7886

7987
#
8088
# Create Certificate Authority: ca1
@@ -663,7 +671,7 @@ rsa_cert_foafssl_b.modulus: rsa_cert_foafssl_b.crt
663671

664672
# Have to parse out the hex exponent
665673
rsa_cert_foafssl_b.exponent: rsa_cert_foafssl_b.crt
666-
openssl x509 -in rsa_cert_foafssl_b.crt -text | grep -o 'Exponent:.*' | sed 's/\(.*(\|).*\)//g' > rsa_cert_foafssl_b.exponent
674+
openssl x509 -in rsa_cert_foafssl_b.crt -text | grep -o 'Exponent:.*' | sed 's/\(.*(\|).*\)//g' > rsa_cert_foafssl_b.exponent
667675

668676
# openssl outputs `SPKAC=[SPKAC]`. That prefix needs to be removed to work with node
669677
rsa_spkac.spkac: rsa_private.pem
@@ -733,6 +741,38 @@ x448_private.pem:
733741
x448_public.pem: x448_private.pem
734742
openssl pkey -in x448_private.pem -pubout -out x448_public.pem
735743

744+
ec_p256_private.pem:
745+
openssl ecparam -name prime256v1 -genkey -noout -out sec1_ec_p256_private.pem
746+
openssl pkcs8 -topk8 -nocrypt -in sec1_ec_p256_private.pem -out ec_p256_private.pem
747+
rm sec1_ec_p256_private.pem
748+
749+
ec_p256_public.pem: ec_p256_private.pem
750+
openssl ec -in ec_p256_private.pem -pubout -out ec_p256_public.pem
751+
752+
ec_p384_private.pem:
753+
openssl ecparam -name secp384r1 -genkey -noout -out sec1_ec_p384_private.pem
754+
openssl pkcs8 -topk8 -nocrypt -in sec1_ec_p384_private.pem -out ec_p384_private.pem
755+
rm sec1_ec_p384_private.pem
756+
757+
ec_p384_public.pem: ec_p384_private.pem
758+
openssl ec -in ec_p384_private.pem -pubout -out ec_p384_public.pem
759+
760+
ec_p521_private.pem:
761+
openssl ecparam -name secp521r1 -genkey -noout -out sec1_ec_p521_private.pem
762+
openssl pkcs8 -topk8 -nocrypt -in sec1_ec_p521_private.pem -out ec_p521_private.pem
763+
rm sec1_ec_p521_private.pem
764+
765+
ec_p521_public.pem: ec_p521_private.pem
766+
openssl ec -in ec_p521_private.pem -pubout -out ec_p521_public.pem
767+
768+
ec_secp256k1_private.pem:
769+
openssl ecparam -name secp256k1 -genkey -noout -out sec1_ec_secp256k1_private.pem
770+
openssl pkcs8 -topk8 -nocrypt -in sec1_ec_secp256k1_private.pem -out ec_secp256k1_private.pem
771+
rm sec1_ec_secp256k1_private.pem
772+
773+
ec_secp256k1_public.pem: ec_secp256k1_private.pem
774+
openssl ec -in ec_secp256k1_private.pem -pubout -out ec_secp256k1_public.pem
775+
736776
clean:
737777
rm -f *.pfx *.pem *.srl ca2-database.txt ca2-serial fake-startcom-root-serial *.print *.old fake-startcom-root-issued-certs/*.pem
738778
@> fake-startcom-root-database.txt
Collapse file
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDxBsPQPIgMuMyQbx
3+
zbb9toew6Ev6e9O6ZhpxLNgmAEqhRANCAARfSYxhH+6V5lIg+M3O0iQBLf+53kuE
4+
2luIgWnp81/Ya1Gybj8tl4tJVu1GEwcTyt8hoA7vRACmCHnI5B1+bNpS
5+
-----END PRIVATE KEY-----
Collapse file
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0mMYR/uleZSIPjNztIkAS3/ud5L
3+
hNpbiIFp6fNf2GtRsm4/LZeLSVbtRhMHE8rfIaAO70QApgh5yOQdfmzaUg==
4+
-----END PUBLIC KEY-----
Collapse file
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB3B+4e4C1OUxGftkEI
3+
Gb/SCulzUP/iE940CB6+B6WWO4LT76T8sMWiwOAGUsuZmyKhZANiAASE43efMYmC
4+
/7Tx90elDGBEkVnOUr4ZkMZrl/cqe8zfVy++MmayPhR46Ah3LesMCNV+J0eG15w0
5+
IYJ8uqasuMN6drU1LNbNYfW7+hR0woajldJpvHMPv7wlnGOlzyxH1yU=
6+
-----END PRIVATE KEY-----
Collapse file
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhON3nzGJgv+08fdHpQxgRJFZzlK+GZDG
3+
a5f3KnvM31cvvjJmsj4UeOgIdy3rDAjVfidHhtecNCGCfLqmrLjDena1NSzWzWH1
4+
u/oUdMKGo5XSabxzD7+8JZxjpc8sR9cl
5+
-----END PUBLIC KEY-----
Collapse file
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAEghuafcab9jXW4gO
3+
QLeDaKOlHEiskQFjiL8klijk6i6DNOXcFfaJ9GW48kxpodw16ttAf9Z1WQstfzpK
4+
GUetHImhgYkDgYYABAGixYI8Gbc5zNze6rH2/OmsFV3unOnY1GDqG9RTfpJZXpL9
5+
ChF1dG8HA4zxkM+X+jMSwm4THh0Wr1Euj9dK7E7QZwHd35XsQXgH13Hjc0QR9dvJ
6+
BWzlg+luNTY8CkaqiBdur5oFv/AjpXRimYxZDkhAEsTwXLwNohSUVMkN8IQtNI9D
7+
aQ==
8+
-----END PRIVATE KEY-----

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.