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:
Filip Skokan 2021-01-26 18:21:14 +01:00
parent 211574b0c2
commit a8d7de1efb
14 changed files with 402 additions and 25 deletions

View File

@ -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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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

View File

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDxBsPQPIgMuMyQbx
zbb9toew6Ev6e9O6ZhpxLNgmAEqhRANCAARfSYxhH+6V5lIg+M3O0iQBLf+53kuE
2luIgWnp81/Ya1Gybj8tl4tJVu1GEwcTyt8hoA7vRACmCHnI5B1+bNpS
-----END PRIVATE KEY-----

4
test/fixtures/keys/ec_p256_public.pem vendored Normal file
View File

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0mMYR/uleZSIPjNztIkAS3/ud5L
hNpbiIFp6fNf2GtRsm4/LZeLSVbtRhMHE8rfIaAO70QApgh5yOQdfmzaUg==
-----END PUBLIC KEY-----

View File

@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB3B+4e4C1OUxGftkEI
Gb/SCulzUP/iE940CB6+B6WWO4LT76T8sMWiwOAGUsuZmyKhZANiAASE43efMYmC
/7Tx90elDGBEkVnOUr4ZkMZrl/cqe8zfVy++MmayPhR46Ah3LesMCNV+J0eG15w0
IYJ8uqasuMN6drU1LNbNYfW7+hR0woajldJpvHMPv7wlnGOlzyxH1yU=
-----END PRIVATE KEY-----

5
test/fixtures/keys/ec_p384_public.pem vendored Normal file
View File

@ -0,0 +1,5 @@
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhON3nzGJgv+08fdHpQxgRJFZzlK+GZDG
a5f3KnvM31cvvjJmsj4UeOgIdy3rDAjVfidHhtecNCGCfLqmrLjDena1NSzWzWH1
u/oUdMKGo5XSabxzD7+8JZxjpc8sR9cl
-----END PUBLIC KEY-----

View File

@ -0,0 +1,8 @@
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAEghuafcab9jXW4gO
QLeDaKOlHEiskQFjiL8klijk6i6DNOXcFfaJ9GW48kxpodw16ttAf9Z1WQstfzpK
GUetHImhgYkDgYYABAGixYI8Gbc5zNze6rH2/OmsFV3unOnY1GDqG9RTfpJZXpL9
ChF1dG8HA4zxkM+X+jMSwm4THh0Wr1Euj9dK7E7QZwHd35XsQXgH13Hjc0QR9dvJ
BWzlg+luNTY8CkaqiBdur5oFv/AjpXRimYxZDkhAEsTwXLwNohSUVMkN8IQtNI9D
aQ==
-----END PRIVATE KEY-----

6
test/fixtures/keys/ec_p521_public.pem vendored Normal file
View File

@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBosWCPBm3Oczc3uqx9vzprBVd7pzp
2NRg6hvUU36SWV6S/QoRdXRvBwOM8ZDPl/ozEsJuEx4dFq9RLo/XSuxO0GcB3d+V
7EF4B9dx43NEEfXbyQVs5YPpbjU2PApGqogXbq+aBb/wI6V0YpmMWQ5IQBLE8Fy8
DaIUlFTJDfCELTSPQ2k=
-----END PUBLIC KEY-----

View File

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgc34ocwTwpFa9NZZh3l88
qXyrkoYSxvC0FEsU5v1v4IOhRANCAARw7OEVKlbGFqUJtY10/Yf/JSR0LzUL1PZ1
4Ol/ErujAPgNwwGU5PSD6aTfn9NycnYB2hby9XwB2qF3+El+DV8q
-----END PRIVATE KEY-----

View File

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEcOzhFSpWxhalCbWNdP2H/yUkdC81C9T2
deDpfxK7owD4DcMBlOT0g+mk35/TcnJ2AdoW8vV8Adqhd/hJfg1fKg==
-----END PUBLIC KEY-----

View File

@ -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}.`
});
}