mirror of https://github.com/nodejs/node.git
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: https://github.com/nodejs/node/pull/37081 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
parent
211574b0c2
commit
a8d7de1efb
|
@ -1348,35 +1348,41 @@ keys.
|
|||
### `keyObject.export([options])`
|
||||
<!-- YAML
|
||||
added: v11.6.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/37081
|
||||
description: Added support for `'jwk'` format.
|
||||
-->
|
||||
|
||||
* `options`: {Object}
|
||||
* Returns: {string | Buffer}
|
||||
* Returns: {string | Buffer | Object}
|
||||
|
||||
For symmetric keys, this function allocates a `Buffer` containing the key
|
||||
material and ignores any options.
|
||||
For symmetric keys, the following encoding options can be used:
|
||||
|
||||
For asymmetric keys, the `options` parameter is used to determine the export
|
||||
format.
|
||||
* `format`: {string} Must be `'buffer'` (default) or `'jwk'`.
|
||||
|
||||
For public keys, the following encoding options can be used:
|
||||
|
||||
* `type`: {string} Must be one of `'pkcs1'` (RSA only) or `'spki'`.
|
||||
* `format`: {string} Must be `'pem'` or `'der'`.
|
||||
* `format`: {string} Must be `'pem'`, `'der'`, or `'jwk'`.
|
||||
|
||||
For private keys, the following encoding options can be used:
|
||||
|
||||
* `type`: {string} Must be one of `'pkcs1'` (RSA only), `'pkcs8'` or
|
||||
`'sec1'` (EC only).
|
||||
* `format`: {string} Must be `'pem'` or `'der'`.
|
||||
* `format`: {string} Must be `'pem'`, `'der'`, or `'jwk'`.
|
||||
* `cipher`: {string} If specified, the private key will be encrypted with
|
||||
the given `cipher` and `passphrase` using PKCS#5 v2.0 password based
|
||||
encryption.
|
||||
* `passphrase`: {string | Buffer} The passphrase to use for encryption, see
|
||||
`cipher`.
|
||||
|
||||
When PEM encoding was selected, the result will be a string, otherwise it will
|
||||
be a buffer containing the data encoded as DER.
|
||||
The result type depends on the selected encoding format, when PEM the
|
||||
result is a string, when DER it will be a buffer containing the data
|
||||
encoded as DER, when [JWK][] it will be an object.
|
||||
|
||||
When [JWK][] encoding format was selected, all other encoding options are
|
||||
ignored.
|
||||
|
||||
PKCS#1, SEC1, and PKCS#8 type keys can be encrypted by using a combination of
|
||||
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.
|
|||
[Crypto constants]: #crypto_crypto_constants_1
|
||||
[HTML 5.2]: https://www.w3.org/TR/html52/changes.html#features-removed
|
||||
[HTML5's `keygen` element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen
|
||||
[JWK]: https://tools.ietf.org/html/rfc7517
|
||||
[NIST SP 800-131A]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf
|
||||
[NIST SP 800-132]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf
|
||||
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||
|
|
|
@ -919,6 +919,18 @@ added: v15.0.0
|
|||
|
||||
Initialization of an asynchronous crypto operation failed.
|
||||
|
||||
<a id="ERR_CRYPTO_JWK_UNSUPPORTED_CURVE"></a>
|
||||
### `ERR_CRYPTO_JWK_UNSUPPORTED_CURVE`
|
||||
|
||||
Key's Elliptic Curve is not registered for use in the
|
||||
[JSON Web Key Elliptic Curve Registry][].
|
||||
|
||||
<a id="ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE"></a>
|
||||
### `ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE`
|
||||
|
||||
Key's Asymmetric Key Type is not registered for use in the
|
||||
[JSON Web Key Types Registry][].
|
||||
|
||||
<a id="ERR_CRYPTO_OPERATION_FAILED"></a>
|
||||
### `ERR_CRYPTO_OPERATION_FAILED`
|
||||
<!-- YAML
|
||||
|
@ -2716,6 +2728,8 @@ The native call from `process.cpuUsage` could not be processed.
|
|||
|
||||
[ES Module]: esm.md
|
||||
[ICU]: intl.md#intl_internationalization_support
|
||||
[JSON Web Key Elliptic Curve Registry]: https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve
|
||||
[JSON Web Key Types Registry]: https://www.iana.org/assignments/jose/jose.xhtml#web-key-types
|
||||
[Node.js error codes]: #nodejs-error-codes
|
||||
[RFC 7230 Section 3]: https://tools.ietf.org/html/rfc7230#section-3
|
||||
[Subresource Integrity specification]: https://www.w3.org/TR/SRI/#the-integrity-attribute
|
||||
|
|
|
@ -22,6 +22,11 @@ const {
|
|||
kKeyEncodingSEC1,
|
||||
} = internalBinding('crypto');
|
||||
|
||||
const {
|
||||
validateObject,
|
||||
validateOneOf,
|
||||
} = require('internal/validators');
|
||||
|
||||
const {
|
||||
codes: {
|
||||
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
|
||||
|
@ -30,6 +35,8 @@ const {
|
|||
ERR_INVALID_ARG_VALUE,
|
||||
ERR_OUT_OF_RANGE,
|
||||
ERR_OPERATION_FAILED,
|
||||
ERR_CRYPTO_JWK_UNSUPPORTED_CURVE,
|
||||
ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE,
|
||||
}
|
||||
} = require('internal/errors');
|
||||
|
||||
|
@ -124,13 +131,22 @@ const [
|
|||
return this[kHandle].getSymmetricKeySize();
|
||||
}
|
||||
|
||||
export() {
|
||||
export(options) {
|
||||
if (options !== undefined) {
|
||||
validateObject(options, 'options');
|
||||
validateOneOf(
|
||||
options.format, 'options.format', [undefined, 'buffer', 'jwk']);
|
||||
if (options.format === 'jwk') {
|
||||
return this[kHandle].exportJwk({});
|
||||
}
|
||||
}
|
||||
return this[kHandle].export();
|
||||
}
|
||||
}
|
||||
|
||||
const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
|
||||
const kAsymmetricKeyDetails = Symbol('kAsymmetricKeyDetails');
|
||||
const kAsymmetricKeyJWKProperties = Symbol('kAsymmetricKeyJWKProperties');
|
||||
|
||||
function normalizeKeyDetails(details = {}) {
|
||||
if (details.publicExponent !== undefined) {
|
||||
|
@ -163,6 +179,28 @@ const [
|
|||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
[kAsymmetricKeyJWKProperties]() {
|
||||
switch (this.asymmetricKeyType) {
|
||||
case 'rsa': return {};
|
||||
case 'ec':
|
||||
switch (this.asymmetricKeyDetails.namedCurve) {
|
||||
case 'prime256v1': return { crv: 'P-256' };
|
||||
case 'secp256k1': return { crv: 'secp256k1' };
|
||||
case 'secp384r1': return { crv: 'P-384' };
|
||||
case 'secp521r1': return { crv: 'P-521' };
|
||||
default:
|
||||
throw new ERR_CRYPTO_JWK_UNSUPPORTED_CURVE(
|
||||
this.asymmetricKeyDetails.namedCurve);
|
||||
}
|
||||
case 'ed25519': return { crv: 'Ed25519' };
|
||||
case 'ed448': return { crv: 'Ed448' };
|
||||
case 'x25519': return { crv: 'X25519' };
|
||||
case 'x448': return { crv: 'X448' };
|
||||
default:
|
||||
throw new ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PublicKeyObject extends AsymmetricKeyObject {
|
||||
|
@ -170,11 +208,15 @@ const [
|
|||
super('public', handle);
|
||||
}
|
||||
|
||||
export(encoding) {
|
||||
export(options) {
|
||||
if (options && options.format === 'jwk') {
|
||||
const properties = this[kAsymmetricKeyJWKProperties]();
|
||||
return this[kHandle].exportJwk(properties);
|
||||
}
|
||||
const {
|
||||
format,
|
||||
type
|
||||
} = parsePublicKeyEncoding(encoding, this.asymmetricKeyType);
|
||||
} = parsePublicKeyEncoding(options, this.asymmetricKeyType);
|
||||
return this[kHandle].export(format, type);
|
||||
}
|
||||
}
|
||||
|
@ -184,13 +226,21 @@ const [
|
|||
super('private', handle);
|
||||
}
|
||||
|
||||
export(encoding) {
|
||||
export(options) {
|
||||
if (options && options.format === 'jwk') {
|
||||
if (options.passphrase !== undefined) {
|
||||
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
|
||||
'jwk', 'does not support encryption');
|
||||
}
|
||||
const properties = this[kAsymmetricKeyJWKProperties]();
|
||||
return this[kHandle].exportJwk(properties);
|
||||
}
|
||||
const {
|
||||
format,
|
||||
type,
|
||||
cipher,
|
||||
passphrase
|
||||
} = parsePrivateKeyEncoding(encoding, this.asymmetricKeyType);
|
||||
} = parsePrivateKeyEncoding(options, this.asymmetricKeyType);
|
||||
return this[kHandle].export(format, type, cipher, passphrase);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -839,6 +839,8 @@ E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s', TypeError);
|
|||
E('ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',
|
||||
'Invalid key object type %s, expected %s.', TypeError);
|
||||
E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s', Error);
|
||||
E('ERR_CRYPTO_JWK_UNSUPPORTED_CURVE', 'Unsupported JWK EC curve: %s.', Error);
|
||||
E('ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', 'Unsupported JWK Key Type.', Error);
|
||||
E('ERR_CRYPTO_PBKDF2_ERROR', 'PBKDF2 error', Error);
|
||||
E('ERR_CRYPTO_SCRYPT_INVALID_PARAMETER', 'Invalid scrypt parameter', Error);
|
||||
E('ERR_CRYPTO_SCRYPT_NOT_SUPPORTED', 'Scrypt algorithm not supported', Error);
|
||||
|
|
|
@ -75,6 +75,14 @@ all: \
|
|||
ed448_public.pem \
|
||||
x448_private.pem \
|
||||
x448_public.pem \
|
||||
ec_p256_private.pem \
|
||||
ec_p256_public.pem \
|
||||
ec_p384_private.pem \
|
||||
ec_p384_public.pem \
|
||||
ec_p521_private.pem \
|
||||
ec_p521_public.pem \
|
||||
ec_secp256k1_private.pem \
|
||||
ec_secp256k1_public.pem \
|
||||
|
||||
#
|
||||
# Create Certificate Authority: ca1
|
||||
|
@ -663,7 +671,7 @@ rsa_cert_foafssl_b.modulus: rsa_cert_foafssl_b.crt
|
|||
|
||||
# Have to parse out the hex exponent
|
||||
rsa_cert_foafssl_b.exponent: rsa_cert_foafssl_b.crt
|
||||
openssl x509 -in rsa_cert_foafssl_b.crt -text | grep -o 'Exponent:.*' | sed 's/\(.*(\|).*\)//g' > rsa_cert_foafssl_b.exponent
|
||||
openssl x509 -in rsa_cert_foafssl_b.crt -text | grep -o 'Exponent:.*' | sed 's/\(.*(\|).*\)//g' > rsa_cert_foafssl_b.exponent
|
||||
|
||||
# openssl outputs `SPKAC=[SPKAC]`. That prefix needs to be removed to work with node
|
||||
rsa_spkac.spkac: rsa_private.pem
|
||||
|
@ -733,6 +741,38 @@ x448_private.pem:
|
|||
x448_public.pem: x448_private.pem
|
||||
openssl pkey -in x448_private.pem -pubout -out x448_public.pem
|
||||
|
||||
ec_p256_private.pem:
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out sec1_ec_p256_private.pem
|
||||
openssl pkcs8 -topk8 -nocrypt -in sec1_ec_p256_private.pem -out ec_p256_private.pem
|
||||
rm sec1_ec_p256_private.pem
|
||||
|
||||
ec_p256_public.pem: ec_p256_private.pem
|
||||
openssl ec -in ec_p256_private.pem -pubout -out ec_p256_public.pem
|
||||
|
||||
ec_p384_private.pem:
|
||||
openssl ecparam -name secp384r1 -genkey -noout -out sec1_ec_p384_private.pem
|
||||
openssl pkcs8 -topk8 -nocrypt -in sec1_ec_p384_private.pem -out ec_p384_private.pem
|
||||
rm sec1_ec_p384_private.pem
|
||||
|
||||
ec_p384_public.pem: ec_p384_private.pem
|
||||
openssl ec -in ec_p384_private.pem -pubout -out ec_p384_public.pem
|
||||
|
||||
ec_p521_private.pem:
|
||||
openssl ecparam -name secp521r1 -genkey -noout -out sec1_ec_p521_private.pem
|
||||
openssl pkcs8 -topk8 -nocrypt -in sec1_ec_p521_private.pem -out ec_p521_private.pem
|
||||
rm sec1_ec_p521_private.pem
|
||||
|
||||
ec_p521_public.pem: ec_p521_private.pem
|
||||
openssl ec -in ec_p521_private.pem -pubout -out ec_p521_public.pem
|
||||
|
||||
ec_secp256k1_private.pem:
|
||||
openssl ecparam -name secp256k1 -genkey -noout -out sec1_ec_secp256k1_private.pem
|
||||
openssl pkcs8 -topk8 -nocrypt -in sec1_ec_secp256k1_private.pem -out ec_secp256k1_private.pem
|
||||
rm sec1_ec_secp256k1_private.pem
|
||||
|
||||
ec_secp256k1_public.pem: ec_secp256k1_private.pem
|
||||
openssl ec -in ec_secp256k1_private.pem -pubout -out ec_secp256k1_public.pem
|
||||
|
||||
clean:
|
||||
rm -f *.pfx *.pem *.srl ca2-database.txt ca2-serial fake-startcom-root-serial *.print *.old fake-startcom-root-issued-certs/*.pem
|
||||
@> fake-startcom-root-database.txt
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDxBsPQPIgMuMyQbx
|
||||
zbb9toew6Ev6e9O6ZhpxLNgmAEqhRANCAARfSYxhH+6V5lIg+M3O0iQBLf+53kuE
|
||||
2luIgWnp81/Ya1Gybj8tl4tJVu1GEwcTyt8hoA7vRACmCHnI5B1+bNpS
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,4 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0mMYR/uleZSIPjNztIkAS3/ud5L
|
||||
hNpbiIFp6fNf2GtRsm4/LZeLSVbtRhMHE8rfIaAO70QApgh5yOQdfmzaUg==
|
||||
-----END PUBLIC KEY-----
|
|
@ -0,0 +1,6 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB3B+4e4C1OUxGftkEI
|
||||
Gb/SCulzUP/iE940CB6+B6WWO4LT76T8sMWiwOAGUsuZmyKhZANiAASE43efMYmC
|
||||
/7Tx90elDGBEkVnOUr4ZkMZrl/cqe8zfVy++MmayPhR46Ah3LesMCNV+J0eG15w0
|
||||
IYJ8uqasuMN6drU1LNbNYfW7+hR0woajldJpvHMPv7wlnGOlzyxH1yU=
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhON3nzGJgv+08fdHpQxgRJFZzlK+GZDG
|
||||
a5f3KnvM31cvvjJmsj4UeOgIdy3rDAjVfidHhtecNCGCfLqmrLjDena1NSzWzWH1
|
||||
u/oUdMKGo5XSabxzD7+8JZxjpc8sR9cl
|
||||
-----END PUBLIC KEY-----
|
|
@ -0,0 +1,8 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAEghuafcab9jXW4gO
|
||||
QLeDaKOlHEiskQFjiL8klijk6i6DNOXcFfaJ9GW48kxpodw16ttAf9Z1WQstfzpK
|
||||
GUetHImhgYkDgYYABAGixYI8Gbc5zNze6rH2/OmsFV3unOnY1GDqG9RTfpJZXpL9
|
||||
ChF1dG8HA4zxkM+X+jMSwm4THh0Wr1Euj9dK7E7QZwHd35XsQXgH13Hjc0QR9dvJ
|
||||
BWzlg+luNTY8CkaqiBdur5oFv/AjpXRimYxZDkhAEsTwXLwNohSUVMkN8IQtNI9D
|
||||
aQ==
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,6 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBosWCPBm3Oczc3uqx9vzprBVd7pzp
|
||||
2NRg6hvUU36SWV6S/QoRdXRvBwOM8ZDPl/ozEsJuEx4dFq9RLo/XSuxO0GcB3d+V
|
||||
7EF4B9dx43NEEfXbyQVs5YPpbjU2PApGqogXbq+aBb/wI6V0YpmMWQ5IQBLE8Fy8
|
||||
DaIUlFTJDfCELTSPQ2k=
|
||||
-----END PUBLIC KEY-----
|
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgc34ocwTwpFa9NZZh3l88
|
||||
qXyrkoYSxvC0FEsU5v1v4IOhRANCAARw7OEVKlbGFqUJtY10/Yf/JSR0LzUL1PZ1
|
||||
4Ol/ErujAPgNwwGU5PSD6aTfn9NycnYB2hby9XwB2qF3+El+DV8q
|
||||
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,4 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEcOzhFSpWxhalCbWNdP2H/yUkdC81C9T2
|
||||
deDpfxK7owD4DcMBlOT0g+mk35/TcnJ2AdoW8vV8Adqhd/hJfg1fKg==
|
||||
-----END PUBLIC KEY-----
|
|
@ -18,7 +18,9 @@ const {
|
|||
publicDecrypt,
|
||||
publicEncrypt,
|
||||
privateDecrypt,
|
||||
privateEncrypt
|
||||
privateEncrypt,
|
||||
getCurves,
|
||||
generateKeyPairSync
|
||||
} = require('crypto');
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
@ -156,6 +158,56 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
|
|||
});
|
||||
}
|
||||
|
||||
const jwk = {
|
||||
e: 'AQAB',
|
||||
n: 't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe' +
|
||||
'1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ' +
|
||||
'MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE' +
|
||||
'u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF' +
|
||||
'di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q',
|
||||
d: 'ktnq2LvIMqBj4txP82IEOorIRQGVsw1khbm8A-cEpuEkgM71Yi_0WzupKktucUeevQ5i0' +
|
||||
'Yh8w9e1SJiTLDRAlJz66kdky9uejiWWl6zR4dyNZVMFYRM43ijLC-P8rPne9Fz16IqHFW' +
|
||||
'5VbJqA1xCBhKmuPMsD71RNxZ4Hrsa7Kt_xglQTYsLbdGIwDmcZihId9VGXRzvmCPsDRf2' +
|
||||
'fCkAj7HDeRxpUdEiEDpajADc-PWikra3r3b40tVHKWm8wxJLivOIN7GiYXKQIW6RhZgH-' +
|
||||
'Rk45JIRNKxNagxdeXUqqyhnwhbTo1Hite0iBDexN9tgoZk0XmdYWBn6ElXHRZ7VCDQ',
|
||||
p: '8UovlB4nrBm7xH-u7XXBMbqxADQm5vaEZxw9eluc-tP7cIAI4sglMIvL_FMpbd2pEeP_B' +
|
||||
'kR76NTDzzDuPAZvUGRavgEjy0O9j2NAs_WPK4tZF-vFdunhnSh4EHAF4Ij9kbsUi90NOp' +
|
||||
'bGfVqPdOaHqzgHKoR23Cuusk9wFQ2XTV8',
|
||||
q: 'wxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrWOb-76rSfuL8wGR4OBNmQdhLuU9zTIh22pog-X' +
|
||||
'PnLPAecC-4yu_wtJ2SPCKiKDbJBre0CKPyRfGqzvA3njXwMxXazU4kGs-2Fg-xu_iKbaI' +
|
||||
'jxXrclBLhkxhBtySrwAFhxxOk6fFcPLSs',
|
||||
dp: 'qS_Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc_Gh4cnO-b7BNJ_-5L8WZog0vr' +
|
||||
'6PgiLhrqBaCYm2wjpyoG2o2wDHm-NAlzN_wp3G2EFhrSxdOux-S1c0kpRcyoiAO2n29rN' +
|
||||
'Da-jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8',
|
||||
dq: 'WAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCDdCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP_' +
|
||||
'Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNmbDXFPdG0G3hzQovx_9fajiRV4DWghLHeT9wzJ' +
|
||||
'fZabRRiI0VQR472300AVEeX4vgbrDBn600',
|
||||
qi: 'k7czBCT9rHn_PNwCa17hlTy88C4vXkwbz83Oa-aX5L4e5gw5lhcR2ZuZHLb2r6oMt9rl' +
|
||||
'D7EIDItSs-u21LOXWPTAlazdnpYUyw_CzogM_PN-qNwMRXn5uXFFhmlP2mVg2EdELTahX' +
|
||||
'ch8kWqHaCSX53yvqCtRKu_j76V31TfQZGM',
|
||||
kty: 'RSA',
|
||||
};
|
||||
|
||||
for (const keyObject of [publicKey, derivedPublicKey]) {
|
||||
assert.deepStrictEqual(
|
||||
keyObject.export({ format: 'jwk' }),
|
||||
{ kty: 'RSA', n: jwk.n, e: jwk.e }
|
||||
);
|
||||
}
|
||||
assert.deepStrictEqual(
|
||||
privateKey.export({ format: 'jwk' }),
|
||||
jwk
|
||||
);
|
||||
|
||||
// Exporting the key using JWK should not work since this format does not
|
||||
// support key encryption
|
||||
assert.throws(() => {
|
||||
privateKey.export({ format: 'jwk', passphrase: 'secret' });
|
||||
}, {
|
||||
message: 'The selected key encoding jwk does not support encryption.',
|
||||
code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS'
|
||||
});
|
||||
|
||||
const publicDER = publicKey.export({
|
||||
format: 'der',
|
||||
type: 'pkcs1'
|
||||
|
@ -254,36 +306,150 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
|
|||
[
|
||||
{ private: fixtures.readKey('ed25519_private.pem', 'ascii'),
|
||||
public: fixtures.readKey('ed25519_public.pem', 'ascii'),
|
||||
keyType: 'ed25519' },
|
||||
keyType: 'ed25519',
|
||||
jwk: {
|
||||
crv: 'Ed25519',
|
||||
x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',
|
||||
d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA',
|
||||
kty: 'OKP'
|
||||
} },
|
||||
{ private: fixtures.readKey('ed448_private.pem', 'ascii'),
|
||||
public: fixtures.readKey('ed448_public.pem', 'ascii'),
|
||||
keyType: 'ed448' },
|
||||
keyType: 'ed448',
|
||||
jwk: {
|
||||
crv: 'Ed448',
|
||||
x: 'oX_ee5-jlcU53-BbGRsGIzly0V-SZtJ_oGXY0udf84q2hTW2RdstLktvwpkVJOoNb7o' +
|
||||
'Dgc2V5ZUA',
|
||||
d: '060Ke71sN0GpIc01nnGgMDkp0sFNQ09woVo4AM1ffax1-mjnakK0-p-S7-Xf859QewX' +
|
||||
'jcR9mxppY',
|
||||
kty: 'OKP'
|
||||
} },
|
||||
{ private: fixtures.readKey('x25519_private.pem', 'ascii'),
|
||||
public: fixtures.readKey('x25519_public.pem', 'ascii'),
|
||||
keyType: 'x25519' },
|
||||
keyType: 'x25519',
|
||||
jwk: {
|
||||
crv: 'X25519',
|
||||
x: 'aSb8Q-RndwfNnPeOYGYPDUN3uhAPnMLzXyfi-mqfhig',
|
||||
d: 'mL_IWm55RrALUGRfJYzw40gEYWMvtRkesP9mj8o8Omc',
|
||||
kty: 'OKP'
|
||||
} },
|
||||
{ private: fixtures.readKey('x448_private.pem', 'ascii'),
|
||||
public: fixtures.readKey('x448_public.pem', 'ascii'),
|
||||
keyType: 'x448' },
|
||||
keyType: 'x448',
|
||||
jwk: {
|
||||
crv: 'X448',
|
||||
x: 'ioHSHVpTs6hMvghosEJDIR7ceFiE3-Xccxati64oOVJ7NWjfozE7ae31PXIUFq6cVYg' +
|
||||
'vSKsDFPA',
|
||||
d: 'tMNtrO_q8dlY6Y4NDeSTxNQ5CACkHiPvmukidPnNIuX_EkcryLEXt_7i6j6YZMKsrWy' +
|
||||
'S0jlSYJk',
|
||||
kty: 'OKP'
|
||||
} },
|
||||
].forEach((info) => {
|
||||
const keyType = info.keyType;
|
||||
|
||||
{
|
||||
const exportOptions = { type: 'pkcs8', format: 'pem' };
|
||||
const key = createPrivateKey(info.private);
|
||||
assert.strictEqual(key.type, 'private');
|
||||
assert.strictEqual(key.asymmetricKeyType, keyType);
|
||||
assert.strictEqual(key.symmetricKeySize, undefined);
|
||||
assert.strictEqual(key.export(exportOptions), info.private);
|
||||
assert.strictEqual(
|
||||
key.export({ type: 'pkcs8', format: 'pem' }), info.private);
|
||||
assert.deepStrictEqual(
|
||||
key.export({ format: 'jwk' }), info.jwk);
|
||||
}
|
||||
|
||||
{
|
||||
const exportOptions = { type: 'spki', format: 'pem' };
|
||||
[info.private, info.public].forEach((pem) => {
|
||||
const key = createPublicKey(pem);
|
||||
assert.strictEqual(key.type, 'public');
|
||||
assert.strictEqual(key.asymmetricKeyType, keyType);
|
||||
assert.strictEqual(key.symmetricKeySize, undefined);
|
||||
assert.strictEqual(key.export(exportOptions), info.public);
|
||||
assert.strictEqual(
|
||||
key.export({ type: 'spki', format: 'pem' }), info.public);
|
||||
const jwk = { ...info.jwk };
|
||||
delete jwk.d;
|
||||
assert.deepStrictEqual(
|
||||
key.export({ format: 'jwk' }), jwk);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
[
|
||||
{ private: fixtures.readKey('ec_p256_private.pem', 'ascii'),
|
||||
public: fixtures.readKey('ec_p256_public.pem', 'ascii'),
|
||||
keyType: 'ec',
|
||||
namedCurve: 'prime256v1',
|
||||
jwk: {
|
||||
crv: 'P-256',
|
||||
d: 'DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo',
|
||||
kty: 'EC',
|
||||
x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',
|
||||
y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI'
|
||||
} },
|
||||
{ private: fixtures.readKey('ec_secp256k1_private.pem', 'ascii'),
|
||||
public: fixtures.readKey('ec_secp256k1_public.pem', 'ascii'),
|
||||
keyType: 'ec',
|
||||
namedCurve: 'secp256k1',
|
||||
jwk: {
|
||||
crv: 'secp256k1',
|
||||
d: 'c34ocwTwpFa9NZZh3l88qXyrkoYSxvC0FEsU5v1v4IM',
|
||||
kty: 'EC',
|
||||
x: 'cOzhFSpWxhalCbWNdP2H_yUkdC81C9T2deDpfxK7owA',
|
||||
y: '-A3DAZTk9IPppN-f03JydgHaFvL1fAHaoXf4SX4NXyo'
|
||||
} },
|
||||
{ private: fixtures.readKey('ec_p384_private.pem', 'ascii'),
|
||||
public: fixtures.readKey('ec_p384_public.pem', 'ascii'),
|
||||
keyType: 'ec',
|
||||
namedCurve: 'secp384r1',
|
||||
jwk: {
|
||||
crv: 'P-384',
|
||||
d: 'dwfuHuAtTlMRn7ZBCBm_0grpc1D_4hPeNAgevgelljuC0--k_LDFosDgBlLLmZsi',
|
||||
kty: 'EC',
|
||||
x: 'hON3nzGJgv-08fdHpQxgRJFZzlK-GZDGa5f3KnvM31cvvjJmsj4UeOgIdy3rDAjV',
|
||||
y: 'fidHhtecNCGCfLqmrLjDena1NSzWzWH1u_oUdMKGo5XSabxzD7-8JZxjpc8sR9cl'
|
||||
} },
|
||||
{ private: fixtures.readKey('ec_p521_private.pem', 'ascii'),
|
||||
public: fixtures.readKey('ec_p521_public.pem', 'ascii'),
|
||||
keyType: 'ec',
|
||||
namedCurve: 'secp521r1',
|
||||
jwk: {
|
||||
crv: 'P-521',
|
||||
d: 'ABIIbmn3Gm_Y11uIDkC3g2ijpRxIrJEBY4i_JJYo5OougzTl3BX2ifRluPJMaaHcNer' +
|
||||
'bQH_WdVkLLX86ShlHrRyJ',
|
||||
kty: 'EC',
|
||||
x: 'AaLFgjwZtznM3N7qsfb86awVXe6c6djUYOob1FN-kllekv0KEXV0bwcDjPGQz5f6MxL' +
|
||||
'CbhMeHRavUS6P10rsTtBn',
|
||||
y: 'Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkOSEASxPB' +
|
||||
'cvA2iFJRUyQ3whC00j0Np'
|
||||
} },
|
||||
].forEach((info) => {
|
||||
const { keyType, namedCurve } = info;
|
||||
|
||||
{
|
||||
const key = createPrivateKey(info.private);
|
||||
assert.strictEqual(key.type, 'private');
|
||||
assert.strictEqual(key.asymmetricKeyType, keyType);
|
||||
assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
|
||||
assert.strictEqual(key.symmetricKeySize, undefined);
|
||||
assert.strictEqual(
|
||||
key.export({ type: 'pkcs8', format: 'pem' }), info.private);
|
||||
assert.deepStrictEqual(
|
||||
key.export({ format: 'jwk' }), info.jwk);
|
||||
}
|
||||
|
||||
{
|
||||
[info.private, info.public].forEach((pem) => {
|
||||
const key = createPublicKey(pem);
|
||||
assert.strictEqual(key.type, 'public');
|
||||
assert.strictEqual(key.asymmetricKeyType, keyType);
|
||||
assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
|
||||
assert.strictEqual(key.symmetricKeySize, undefined);
|
||||
assert.strictEqual(
|
||||
key.export({ type: 'spki', format: 'pem' }), info.public);
|
||||
const jwk = { ...info.jwk };
|
||||
delete jwk.d;
|
||||
assert.deepStrictEqual(
|
||||
key.export({ format: 'jwk' }), jwk);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -321,6 +487,9 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
|
|||
assert.strictEqual(publicKey.type, 'public');
|
||||
assert.strictEqual(publicKey.asymmetricKeyType, 'dsa');
|
||||
assert.strictEqual(publicKey.symmetricKeySize, undefined);
|
||||
assert.throws(
|
||||
() => publicKey.export({ format: 'jwk' }),
|
||||
{ code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
|
||||
|
||||
const privateKey = createPrivateKey({
|
||||
key: privateDsa,
|
||||
|
@ -330,7 +499,9 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
|
|||
assert.strictEqual(privateKey.type, 'private');
|
||||
assert.strictEqual(privateKey.asymmetricKeyType, 'dsa');
|
||||
assert.strictEqual(privateKey.symmetricKeySize, undefined);
|
||||
|
||||
assert.throws(
|
||||
() => privateKey.export({ format: 'jwk' }),
|
||||
{ code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -350,6 +521,13 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
|
|||
assert.strictEqual(privateKey.type, 'private');
|
||||
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
|
||||
|
||||
assert.throws(
|
||||
() => publicKey.export({ format: 'jwk' }),
|
||||
{ code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
|
||||
assert.throws(
|
||||
() => privateKey.export({ format: 'jwk' }),
|
||||
{ code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
|
||||
|
||||
for (const key of [privatePem, privateKey]) {
|
||||
// Any algorithm should work.
|
||||
for (const algo of ['sha1', 'sha256']) {
|
||||
|
@ -485,3 +663,46 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
|
|||
message: "The property 'options.cipher' is invalid. Received undefined"
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// SecretKeyObject export buffer format (default)
|
||||
const buffer = Buffer.from('Hello World');
|
||||
const keyObject = createSecretKey(buffer);
|
||||
assert.deepStrictEqual(keyObject.export(), buffer);
|
||||
assert.deepStrictEqual(keyObject.export({}), buffer);
|
||||
assert.deepStrictEqual(keyObject.export({ format: 'buffer' }), buffer);
|
||||
assert.deepStrictEqual(keyObject.export({ format: undefined }), buffer);
|
||||
}
|
||||
|
||||
{
|
||||
// Exporting an "oct" JWK from a SecretKeyObject
|
||||
const buffer = Buffer.from('Hello World');
|
||||
const keyObject = createSecretKey(buffer);
|
||||
assert.deepStrictEqual(
|
||||
keyObject.export({ format: 'jwk' }),
|
||||
{ kty: 'oct', k: 'SGVsbG8gV29ybGQ' }
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// Exporting a JWK unsupported curve EC key
|
||||
const supported = ['prime256v1', 'secp256k1', 'secp384r1', 'secp521r1'];
|
||||
// Find an unsupported curve regardless of whether a FIPS compliant crypto
|
||||
// provider is currently in use.
|
||||
const namedCurve = getCurves().find((curve) => !supported.includes(curve));
|
||||
assert(namedCurve);
|
||||
const keyPair = generateKeyPairSync('ec', { namedCurve });
|
||||
const { publicKey, privateKey } = keyPair;
|
||||
assert.throws(
|
||||
() => publicKey.export({ format: 'jwk' }),
|
||||
{
|
||||
code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE',
|
||||
message: `Unsupported JWK EC curve: ${namedCurve}.`
|
||||
});
|
||||
assert.throws(
|
||||
() => privateKey.export({ format: 'jwk' }),
|
||||
{
|
||||
code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE',
|
||||
message: `Unsupported JWK EC curve: ${namedCurve}.`
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue