GeezerVison is showing github.com — crypto: add keyObject.export() 'jwk' format option · nodejs/node@011910b
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file 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 number | Diff line number | Diff line change |
|---|
@@ -1348,35 +1348,41 @@ keys. | 1348 | 1348 | ### `keyObject.export([options])` | 1349 | 1349 | <!-- YAML | 1350 | 1350 | 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. | 1351 | 1355 | --> | 1352 | 1356 |
| 1353 | 1357 | * `options`: {Object} | 1354 | | -* Returns: {string | Buffer} | | 1358 | +* Returns: {string | Buffer | Object} | 1355 | 1359 |
| 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: | 1358 | 1361 |
| 1359 | | -For asymmetric keys, the `options` parameter is used to determine the export | 1360 | | -format. | | 1362 | +* `format`: {string} Must be `'buffer'` (default) or `'jwk'`. | 1361 | 1363 |
| 1362 | 1364 | For public keys, the following encoding options can be used: | 1363 | 1365 |
| 1364 | 1366 | * `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'`. | 1366 | 1368 |
| 1367 | 1369 | For private keys, the following encoding options can be used: | 1368 | 1370 |
| 1369 | 1371 | * `type`: {string} Must be one of `'pkcs1'` (RSA only), `'pkcs8'` or | 1370 | 1372 | `'sec1'` (EC only). | 1371 | | -* `format`: {string} Must be `'pem'` or `'der'`. | | 1373 | +* `format`: {string} Must be `'pem'`, `'der'`, or `'jwk'`. | 1372 | 1374 | * `cipher`: {string} If specified, the private key will be encrypted with | 1373 | 1375 | the given `cipher` and `passphrase` using PKCS#5 v2.0 password based | 1374 | 1376 | encryption. | 1375 | 1377 | * `passphrase`: {string | Buffer} The passphrase to use for encryption, see | 1376 | 1378 | `cipher`. | 1377 | 1379 |
| 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. | 1380 | 1386 |
| 1381 | 1387 | PKCS#1, SEC1, and PKCS#8 type keys can be encrypted by using a combination of | 1382 | 1388 | 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. | 4355 | 4361 | [Crypto constants]: #crypto_crypto_constants_1 | 4356 | 4362 | [HTML 5.2]: https://www.w3.org/TR/html52/changes.html#features-removed | 4357 | 4363 | [HTML5's `keygen` element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen | | 4364 | +[JWK]: https://tools.ietf.org/html/rfc7517 | 4358 | 4365 | [NIST SP 800-131A]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf | 4359 | 4366 | [NIST SP 800-132]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf | 4360 | 4367 | [NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf | |
Collapse file 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 number | Diff line number | Diff line change |
|---|
@@ -919,6 +919,18 @@ added: v15.0.0 | 919 | 919 |
| 920 | 920 | Initialization of an asynchronous crypto operation failed. | 921 | 921 |
| | 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 | + | 922 | 934 | <a id="ERR_CRYPTO_OPERATION_FAILED"></a> | 923 | 935 | ### `ERR_CRYPTO_OPERATION_FAILED` | 924 | 936 | <!-- YAML | @@ -2716,6 +2728,8 @@ The native call from `process.cpuUsage` could not be processed. | 2716 | 2728 |
| 2717 | 2729 | [ES Module]: esm.md | 2718 | 2730 | [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 | 2719 | 2733 | [Node.js error codes]: #nodejs-error-codes | 2720 | 2734 | [RFC 7230 Section 3]: https://tools.ietf.org/html/rfc7230#section-3 | 2721 | 2735 | [Subresource Integrity specification]: https://www.w3.org/TR/SRI/#the-integrity-attribute | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/keys.js +55-5Lines changed: 55 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -22,6 +22,11 @@ const { | 22 | 22 | kKeyEncodingSEC1, | 23 | 23 | } = internalBinding('crypto'); | 24 | 24 |
| | 25 | +const { | | 26 | + validateObject, | | 27 | + validateOneOf, | | 28 | +} = require('internal/validators'); | | 29 | + | 25 | 30 | const { | 26 | 31 | codes: { | 27 | 32 | ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, | @@ -30,6 +35,8 @@ const { | 30 | 35 | ERR_INVALID_ARG_VALUE, | 31 | 36 | ERR_OUT_OF_RANGE, | 32 | 37 | ERR_OPERATION_FAILED, | | 38 | + ERR_CRYPTO_JWK_UNSUPPORTED_CURVE, | | 39 | + ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE, | 33 | 40 | } | 34 | 41 | } = require('internal/errors'); | 35 | 42 |
| @@ -124,13 +131,22 @@ const [ | 124 | 131 | return this[kHandle].getSymmetricKeySize(); | 125 | 132 | } | 126 | 133 |
| 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 | + } | 128 | 143 | return this[kHandle].export(); | 129 | 144 | } | 130 | 145 | } | 131 | 146 |
| 132 | 147 | const kAsymmetricKeyType = Symbol('kAsymmetricKeyType'); | 133 | 148 | const kAsymmetricKeyDetails = Symbol('kAsymmetricKeyDetails'); | | 149 | + const kAsymmetricKeyJWKProperties = Symbol('kAsymmetricKeyJWKProperties'); | 134 | 150 |
| 135 | 151 | function normalizeKeyDetails(details = {}) { | 136 | 152 | if (details.publicExponent !== undefined) { | @@ -163,18 +179,44 @@ const [ | 163 | 179 | return {}; | 164 | 180 | } | 165 | 181 | } | | 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 | + } | 166 | 204 | } | 167 | 205 |
| 168 | 206 | class PublicKeyObject extends AsymmetricKeyObject { | 169 | 207 | constructor(handle) { | 170 | 208 | super('public', handle); | 171 | 209 | } | 172 | 210 |
| 173 | | - export(encoding) { | | 211 | + export(options) { | | 212 | + if (options && options.format === 'jwk') { | | 213 | + const properties = this[kAsymmetricKeyJWKProperties](); | | 214 | + return this[kHandle].exportJwk(properties); | | 215 | + } | 174 | 216 | const { | 175 | 217 | format, | 176 | 218 | type | 177 | | - } = parsePublicKeyEncoding(encoding, this.asymmetricKeyType); | | 219 | + } = parsePublicKeyEncoding(options, this.asymmetricKeyType); | 178 | 220 | return this[kHandle].export(format, type); | 179 | 221 | } | 180 | 222 | } | @@ -184,13 +226,21 @@ const [ | 184 | 226 | super('private', handle); | 185 | 227 | } | 186 | 228 |
| 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 | + } | 188 | 238 | const { | 189 | 239 | format, | 190 | 240 | type, | 191 | 241 | cipher, | 192 | 242 | passphrase | 193 | | - } = parsePrivateKeyEncoding(encoding, this.asymmetricKeyType); | | 243 | + } = parsePrivateKeyEncoding(options, this.asymmetricKeyType); | 194 | 244 | return this[kHandle].export(format, type, cipher, passphrase); | 195 | 245 | } | 196 | 246 | } | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/errors.js +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -839,6 +839,8 @@ E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s', TypeError); | 839 | 839 | E('ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE', | 840 | 840 | 'Invalid key object type %s, expected %s.', TypeError); | 841 | 841 | 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); | 842 | 844 | E('ERR_CRYPTO_PBKDF2_ERROR', 'PBKDF2 error', Error); | 843 | 845 | E('ERR_CRYPTO_SCRYPT_INVALID_PARAMETER', 'Invalid scrypt parameter', Error); | 844 | 846 | E('ERR_CRYPTO_SCRYPT_NOT_SUPPORTED', 'Scrypt algorithm not supported', Error); | |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/keys/Makefile +41-1Lines changed: 41 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -75,6 +75,14 @@ all: \ | 75 | 75 | ed448_public.pem \ | 76 | 76 | x448_private.pem \ | 77 | 77 | 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 \ | 78 | 86 |
| 79 | 87 | # | 80 | 88 | # Create Certificate Authority: ca1 | @@ -663,7 +671,7 @@ rsa_cert_foafssl_b.modulus: rsa_cert_foafssl_b.crt | 663 | 671 |
| 664 | 672 | # Have to parse out the hex exponent | 665 | 673 | 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 | 667 | 675 |
| 668 | 676 | # openssl outputs `SPKAC=[SPKAC]`. That prefix needs to be removed to work with node | 669 | 677 | rsa_spkac.spkac: rsa_private.pem | @@ -733,6 +741,38 @@ x448_private.pem: | 733 | 741 | x448_public.pem: x448_private.pem | 734 | 742 | openssl pkey -in x448_private.pem -pubout -out x448_public.pem | 735 | 743 |
| | 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 | + | 736 | 776 | clean: | 737 | 777 | rm -f *.pfx *.pem *.srl ca2-database.txt ca2-serial fake-startcom-root-serial *.print *.old fake-startcom-root-issued-certs/*.pem | 738 | 778 | @> fake-startcom-root-database.txt | |
Collapse file Copy file name to clipboard +5Lines changed: 5 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +-----BEGIN PRIVATE KEY----- | | 2 | +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDxBsPQPIgMuMyQbx | | 3 | +zbb9toew6Ev6e9O6ZhpxLNgmAEqhRANCAARfSYxhH+6V5lIg+M3O0iQBLf+53kuE | | 4 | +2luIgWnp81/Ya1Gybj8tl4tJVu1GEwcTyt8hoA7vRACmCHnI5B1+bNpS | | 5 | +-----END PRIVATE KEY----- |
Collapse file Copy file name to clipboard +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +-----BEGIN PUBLIC KEY----- | | 2 | +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0mMYR/uleZSIPjNztIkAS3/ud5L | | 3 | +hNpbiIFp6fNf2GtRsm4/LZeLSVbtRhMHE8rfIaAO70QApgh5yOQdfmzaUg== | | 4 | +-----END PUBLIC KEY----- |
Collapse file Copy file name to clipboard +6Lines changed: 6 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 Copy file name to clipboard +5Lines changed: 5 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +-----BEGIN PUBLIC KEY----- | | 2 | +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhON3nzGJgv+08fdHpQxgRJFZzlK+GZDG | | 3 | +a5f3KnvM31cvvjJmsj4UeOgIdy3rDAjVfidHhtecNCGCfLqmrLjDena1NSzWzWH1 | | 4 | +u/oUdMKGo5XSabxzD7+8JZxjpc8sR9cl | | 5 | +-----END PUBLIC KEY----- |
Collapse file Copy file name to clipboard +8Lines changed: 8 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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----- |
|