diff --git a/doc/api/crypto.md b/doc/api/crypto.md
index 736ac360c903d7..017eb91e67882f 100644
--- a/doc/api/crypto.md
+++ b/doc/api/crypto.md
@@ -1101,6 +1101,81 @@ encoding of `'utf8'` is enforced. If `data` is a [`Buffer`][], `TypedArray`, or
This can be called many times with new data as it is streamed.
+## Class: KeyObject
+
+
+Node.js uses an internal `KeyObject` class which should not be accessed
+directly. Instead, factory functions exist to create instances of this class
+in a secure manner, see [`crypto.createSecretKey()`][],
+[`crypto.createPublicKey()`][] and [`crypto.createPrivateKey()`][]. A
+`KeyObject` can represent a symmetric or asymmetric key, and each kind of key
+exposes different functions.
+
+Most applications should consider using the new `KeyObject` API instead of
+passing keys as strings or `Buffer`s due to improved security features.
+
+### keyObject.asymmetricKeyType
+
+* {string}
+
+For asymmetric keys, this property represents the type of the embedded key
+(`'rsa'`, `'dsa'` or `'ec'`). This property is `undefined` for symmetric keys.
+
+### keyObject.export([options])
+
+* `options`: {Object}
+* Returns: {string | Buffer}
+
+For symmetric keys, this function allocates a `Buffer` containing the key
+material and ignores any options.
+
+For asymmetric keys, the `options` parameter is used to determine the export
+format.
+
+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'`.
+
+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'`.
+* `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.
+
+### keyObject.symmetricSize
+
+* {number}
+
+For secret keys, this property represents the size of the key in bytes. This
+property is `undefined` for asymmetric keys.
+
+### keyObject.type
+
+* {string}
+
+Depending on the type of this `KeyObject`, this property is either
+`'secret'` for secret (symmetric) keys, `'public'` for public (asymmetric) keys
+or `'private'` for private (asymmetric) keys.
+
## Class: Sign
-* `privateKey` {string | Object}
- - `key` {string}
- - `passphrase` {string}
+* `privateKey` {Object | string | Buffer | KeyObject}
- `padding` {integer}
- `saltLength` {integer}
* `outputEncoding` {string} The [encoding][] of the return value.
@@ -1184,12 +1260,10 @@ changes:
Calculates the signature on all the data passed through using either
[`sign.update()`][] or [`sign.write()`][stream-writable-write].
-The `privateKey` argument can be an object or a string. If `privateKey` is a
-string, it is treated as a raw key with no passphrase. If `privateKey` is an
-object, it must contain one or more of the following properties:
+If `privateKey` is not a [`KeyObject`][], this function behaves as if
+`privateKey` had been passed to [`crypto.createPrivateKey()`][]. If it is an
+object, the following additional properties can be passed:
-* `key`: {string} - PEM encoded private key (required)
-* `passphrase`: {string} - passphrase for the private key
* `padding`: {integer} - Optional padding value for RSA, one of the following:
* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
@@ -1299,18 +1373,20 @@ changes:
pr-url: https://github.com/nodejs/node/pull/11705
description: Support for RSASSA-PSS and additional options was added.
-->
-* `object` {string | Object}
+* `object` {Object | string | Buffer | KeyObject}
+ - `padding` {integer}
+ - `saltLength` {integer}
* `signature` {string | Buffer | TypedArray | DataView}
* `signatureEncoding` {string} The [encoding][] of the `signature` string.
* Returns: {boolean} `true` or `false` depending on the validity of the
signature for the data and public key.
Verifies the provided data using the given `object` and `signature`.
-The `object` argument can be either a string containing a PEM encoded object,
-which can be an RSA public key, a DSA public key, or an X.509 certificate,
-or an object with one or more of the following properties:
-* `key`: {string} - PEM encoded public key (required)
+If `object` is not a [`KeyObject`][], this function behaves as if
+`object` had been passed to [`crypto.createPublicKey()`][]. If it is an
+object, the following additional properties can be passed:
+
* `padding`: {integer} - Optional padding value for RSA, one of the following:
* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`
@@ -1436,6 +1512,9 @@ Adversaries][] for details.
* `algorithm` {string}
-* `key` {string | Buffer | TypedArray | DataView}
+* `key` {string | Buffer | TypedArray | DataView | KeyObject}
* `iv` {string | Buffer | TypedArray | DataView}
* `options` {Object} [`stream.transform` options][]
* Returns: {Cipher}
@@ -1474,7 +1553,8 @@ display the available cipher algorithms.
The `key` is the raw key used by the `algorithm` and `iv` is an
[initialization vector][]. Both arguments must be `'utf8'` encoded strings,
-[Buffers][`Buffer`], `TypedArray`, or `DataView`s. If the cipher does not need
+[Buffers][`Buffer`], `TypedArray`, or `DataView`s. The `key` may optionally be
+a [`KeyObject`][] of type `secret`. If the cipher does not need
an initialization vector, `iv` may be `null`.
Initialization vectors should be unpredictable and unique; ideally, they will be
@@ -1525,6 +1605,9 @@ to create the `Decipher` object.
* `algorithm` {string}
-* `key` {string | Buffer | TypedArray | DataView}
+* `key` {string | Buffer | TypedArray | DataView | KeyObject}
* `options` {Object} [`stream.transform` options][]
* Returns: {Hmac}
@@ -1689,7 +1777,8 @@ On recent releases of OpenSSL, `openssl list -digest-algorithms`
(`openssl list-message-digest-algorithms` for older versions of OpenSSL) will
display the available digest algorithms.
-The `key` is the HMAC key used to generate the cryptographic HMAC hash.
+The `key` is the HMAC key used to generate the cryptographic HMAC hash. If it is
+a [`KeyObject`][], its type must be `secret`.
Example: generating the sha256 HMAC of a file
@@ -1711,6 +1800,47 @@ input.on('readable', () => {
});
```
+### crypto.createPrivateKey(key)
+
+* `key` {Object | string | Buffer}
+ - `key`: {string | Buffer} The key material, either in PEM or DER format.
+ - `format`: {string} Must be `'pem'` or `'der'`. **Default:** `'pem'`.
+ - `type`: {string} Must be `'pkcs1'`, `'pkcs8'` or `'sec1'`. This option is
+ required only if the `format` is `'der'` and ignored if it is `'pem'`.
+ - `passphrase`: {string | Buffer} The passphrase to use for decryption.
+* Returns: {KeyObject}
+
+Creates and returns a new key object containing a private key. If `key` is a
+string or `Buffer`, `format` is assumed to be `'pem'`; otherwise, `key`
+must be an object with the properties described above.
+
+### crypto.createPublicKey(key)
+
+* `key` {Object | string | Buffer}
+ - `key`: {string | Buffer}
+ - `format`: {string} Must be `'pem'` or `'der'`. **Default:** `'pem'`.
+ - `type`: {string} Must be `'pkcs1'` or `'spki'`. This option is required
+ only if the `format` is `'der'`.
+* Returns: {KeyObject}
+
+Creates and returns a new key object containing a public key. If `key` is a
+string or `Buffer`, `format` is assumed to be `'pem'`; otherwise, `key`
+must be an object with the properties described above.
+
+### crypto.createSecretKey(key)
+
+* `key` {Buffer}
+* Returns: {KeyObject}
+
+Creates and returns a new key object containing a secret key for symmetric
+encryption or `Hmac`.
+
### crypto.createSign(algorithm[, options])
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
* `options`: {Object}
@@ -1747,27 +1882,22 @@ added: v10.12.0
- `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
- `divisorLength`: {number} Size of `q` in bits (DSA).
- `namedCurve`: {string} Name of the curve to use (EC).
- - `publicKeyEncoding`: {Object}
- - `type`: {string} Must be one of `'pkcs1'` (RSA only) or `'spki'`.
- - `format`: {string} Must be `'pem'` or `'der'`.
- - `privateKeyEncoding`: {Object}
- - `type`: {string} Must be one of `'pkcs1'` (RSA only), `'pkcs8'` or
- `'sec1'` (EC only).
- - `format`: {string} Must be `'pem'` or `'der'`.
- - `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} The passphrase to use for encryption, see `cipher`.
+ - `publicKeyEncoding`: {Object} See [`keyObject.export()`][].
+ - `privateKeyEncoding`: {Object} See [`keyObject.export()`][].
* `callback`: {Function}
- `err`: {Error}
- - `publicKey`: {string|Buffer}
- - `privateKey`: {string|Buffer}
+ - `publicKey`: {string | Buffer | KeyObject}
+ - `privateKey`: {string | Buffer | KeyObject}
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
are currently supported.
+If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
+behaves as if [`keyObject.export()`][] had been called on its result. Otherwise,
+the respective part of the key is returned as a [`KeyObject`].
+
It is recommended to encode public keys as `'spki'` and private keys as
-`'pkcs8'` with encryption:
+`'pkcs8'` with encryption for long-term storage:
```js
const { generateKeyPair } = require('crypto');
@@ -1789,11 +1919,7 @@ generateKeyPair('rsa', {
```
On completion, `callback` will be called with `err` set to `undefined` and
-`publicKey` / `privateKey` representing the generated key pair. When PEM
-encoding was selected, the result will be a string, otherwise it will be a
-buffer containing the data encoded as DER. Note that Node.js itself does not
-accept DER, it is supported for interoperability with other libraries such as
-WebCrypto only.
+`publicKey` / `privateKey` representing the generated key pair.
If this method is invoked as its [`util.promisify()`][]ed version, it returns
a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
@@ -1801,6 +1927,11 @@ a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
### crypto.generateKeyPairSync(type, options)
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
* `options`: {Object}
@@ -1818,10 +1949,11 @@ added: v10.12.0
- `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} The passphrase to use for encryption, see `cipher`.
+ - `passphrase`: {string | Buffer} The passphrase to use for encryption, see
+ `cipher`.
* Returns: {Object}
- - `publicKey`: {string|Buffer}
- - `privateKey`: {string|Buffer}
+ - `publicKey`: {string | Buffer | KeyObject}
+ - `privateKey`: {string | Buffer | KeyObject}
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
are currently supported.
@@ -2062,10 +2194,12 @@ An array of supported digest functions can be retrieved using
### crypto.privateDecrypt(privateKey, buffer)
-* `privateKey` {Object | string}
- - `key` {string} A PEM encoded private key.
- - `passphrase` {string} An optional passphrase for the private key.
+* `privateKey` {Object | string | Buffer | KeyObject}
- `padding` {crypto.constants} An optional padding value defined in
`crypto.constants`, which may be: `crypto.constants.RSA_NO_PADDING`,
`crypto.constants.RSA_PKCS1_PADDING`, or
@@ -2076,16 +2210,22 @@ added: v0.11.14
Decrypts `buffer` with `privateKey`. `buffer` was previously encrypted using
the corresponding public key, for example using [`crypto.publicEncrypt()`][].
-`privateKey` can be an object or a string. If `privateKey` is a string, it is
-treated as the key with no passphrase and will use `RSA_PKCS1_OAEP_PADDING`.
+If `privateKey` is not a [`KeyObject`][], this function behaves as if
+`privateKey` had been passed to [`crypto.createPrivateKey()`][]. If it is an
+object, the `padding` property can be passed. Otherwise, this function uses
+`RSA_PKCS1_OAEP_PADDING`.
### crypto.privateEncrypt(privateKey, buffer)
-* `privateKey` {Object | string}
- - `key` {string} A PEM encoded private key.
- - `passphrase` {string} An optional passphrase for the private key.
+* `privateKey` {Object | string | Buffer | KeyObject}
+ - `key` {string | Buffer | KeyObject} A PEM encoded private key.
+ - `passphrase` {string | Buffer} An optional passphrase for the private key.
- `padding` {crypto.constants} An optional padding value defined in
`crypto.constants`, which may be: `crypto.constants.RSA_NO_PADDING` or
`crypto.constants.RSA_PKCS1_PADDING`.
@@ -2095,16 +2235,21 @@ added: v1.1.0
Encrypts `buffer` with `privateKey`. The returned data can be decrypted using
the corresponding public key, for example using [`crypto.publicDecrypt()`][].
-`privateKey` can be an object or a string. If `privateKey` is a string, it is
-treated as the key with no passphrase and will use `RSA_PKCS1_PADDING`.
+If `privateKey` is not a [`KeyObject`][], this function behaves as if
+`privateKey` had been passed to [`crypto.createPrivateKey()`][]. If it is an
+object, the `padding` property can be passed. Otherwise, this function uses
+`RSA_PKCS1_PADDING`.
### crypto.publicDecrypt(key, buffer)
-* `key` {Object | string}
- - `key` {string} A PEM encoded public or private key.
- - `passphrase` {string} An optional passphrase for the private key.
+* `key` {Object | string | Buffer | KeyObject}
+ - `passphrase` {string | Buffer} An optional passphrase for the private key.
- `padding` {crypto.constants} An optional padding value defined in
`crypto.constants`, which may be: `crypto.constants.RSA_NO_PADDING` or
`crypto.constants.RSA_PKCS1_PADDING`.
@@ -2114,8 +2259,10 @@ added: v1.1.0
Decrypts `buffer` with `key`.`buffer` was previously encrypted using
the corresponding private key, for example using [`crypto.privateEncrypt()`][].
-`key` can be an object or a string. If `key` is a string, it is treated as
-the key with no passphrase and will use `RSA_PKCS1_PADDING`.
+If `key` is not a [`KeyObject`][], this function behaves as if
+`key` had been passed to [`crypto.createPublicKey()`][]. If it is an
+object, the `padding` property can be passed. Otherwise, this function uses
+`RSA_PKCS1_PADDING`.
Because RSA public keys can be derived from private keys, a private key may
be passed instead of a public key.
@@ -2123,10 +2270,14 @@ be passed instead of a public key.
### crypto.publicEncrypt(key, buffer)
-* `key` {Object | string}
- - `key` {string} A PEM encoded public or private key.
- - `passphrase` {string} An optional passphrase for the private key.
+* `key` {Object | string | Buffer | KeyObject}
+ - `key` {string | Buffer | KeyObject} A PEM encoded public or private key.
+ - `passphrase` {string | Buffer} An optional passphrase for the private key.
- `padding` {crypto.constants} An optional padding value defined in
`crypto.constants`, which may be: `crypto.constants.RSA_NO_PADDING`,
`crypto.constants.RSA_PKCS1_PADDING`, or
@@ -2138,8 +2289,10 @@ Encrypts the content of `buffer` with `key` and returns a new
[`Buffer`][] with encrypted content. The returned data can be decrypted using
the corresponding private key, for example using [`crypto.privateDecrypt()`][].
-`key` can be an object or a string. If `key` is a string, it is treated as
-the key with no passphrase and will use `RSA_PKCS1_OAEP_PADDING`.
+If `key` is not a [`KeyObject`][], this function behaves as if
+`key` had been passed to [`crypto.createPublicKey()`][]. If it is an
+object, the `padding` property can be passed. Otherwise, this function uses
+`RSA_PKCS1_OAEP_PADDING`.
Because RSA public keys can be derived from private keys, a private key may
be passed instead of a public key.
@@ -2917,6 +3070,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
[`Buffer`]: buffer.html
[`EVP_BytesToKey`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_BytesToKey.html
+[`KeyObject`]: #crypto_class_keyobject
[`UV_THREADPOOL_SIZE`]: cli.html#cli_uv_threadpool_size_size
[`cipher.final()`]: #crypto_cipher_final_outputencoding
[`cipher.update()`]: #crypto_cipher_update_data_inputencoding_outputencoding
@@ -2928,6 +3082,9 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
[`crypto.createECDH()`]: #crypto_crypto_createecdh_curvename
[`crypto.createHash()`]: #crypto_crypto_createhash_algorithm_options
[`crypto.createHmac()`]: #crypto_crypto_createhmac_algorithm_key_options
+[`crypto.createPrivateKey()`]: #crypto_crypto_createprivatekey_key
+[`crypto.createPublicKey()`]: #crypto_crypto_createpublickey_key
+[`crypto.createSecretKey()`]: #crypto_crypto_createsecretkey_key
[`crypto.createSign()`]: #crypto_crypto_createsign_algorithm_options
[`crypto.createVerify()`]: #crypto_crypto_createverify_algorithm_options
[`crypto.getCurves()`]: #crypto_crypto_getcurves
@@ -2949,6 +3106,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
[`hash.update()`]: #crypto_hash_update_data_inputencoding
[`hmac.digest()`]: #crypto_hmac_digest_encoding
[`hmac.update()`]: #crypto_hmac_update_data_inputencoding
+[`keyObject.export()`]: #crypto_keyobject_export_options
[`sign.sign()`]: #crypto_sign_sign_privatekey_outputencoding
[`sign.update()`]: #crypto_sign_update_data_inputencoding
[`stream.Writable` options]: stream.html#stream_constructor_new_stream_writable_options
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 05ecad70214fb4..d7e0023e3e7830 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -763,6 +763,11 @@ The selected public or private key encoding is incompatible with other options.
An invalid [crypto digest algorithm][] was specified.
+
+### ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE
+
+The given crypto key object's type is invalid for the attempted operation.
+
### ERR_CRYPTO_INVALID_STATE
diff --git a/lib/crypto.js b/lib/crypto.js
index c1526e20fef8d0..5b83a669c643d7 100644
--- a/lib/crypto.js
+++ b/lib/crypto.js
@@ -59,6 +59,11 @@ const {
generateKeyPair,
generateKeyPairSync
} = require('internal/crypto/keygen');
+const {
+ createSecretKey,
+ createPublicKey,
+ createPrivateKey
+} = require('internal/crypto/keys');
const {
DiffieHellman,
DiffieHellmanGroup,
@@ -149,6 +154,9 @@ module.exports = exports = {
createECDH,
createHash,
createHmac,
+ createPrivateKey,
+ createPublicKey,
+ createSecretKey,
createSign,
createVerify,
getCiphers,
diff --git a/lib/internal/crypto/cipher.js b/lib/internal/crypto/cipher.js
index cdb92465ece578..0a0514103399a0 100644
--- a/lib/internal/crypto/cipher.js
+++ b/lib/internal/crypto/cipher.js
@@ -12,6 +12,11 @@ const {
} = require('internal/errors').codes;
const { validateString } = require('internal/validators');
+const {
+ preparePrivateKey,
+ preparePublicOrPrivateKey,
+ prepareSecretKey
+} = require('internal/crypto/keys');
const {
getDefaultEncoding,
kHandle,
@@ -38,19 +43,25 @@ const { deprecate, normalizeEncoding } = require('internal/util');
// Lazy loaded for startup performance.
let StringDecoder;
-function rsaFunctionFor(method, defaultPadding) {
+function rsaFunctionFor(method, defaultPadding, keyType) {
return (options, buffer) => {
- const key = options.key || options;
+ const { format, type, data, passphrase } =
+ keyType === 'private' ?
+ preparePrivateKey(options) :
+ preparePublicOrPrivateKey(options);
const padding = options.padding || defaultPadding;
- const passphrase = options.passphrase || null;
- return method(toBuf(key), buffer, padding, passphrase);
+ return method(data, format, type, passphrase, buffer, padding);
};
}
-const publicEncrypt = rsaFunctionFor(_publicEncrypt, RSA_PKCS1_OAEP_PADDING);
-const publicDecrypt = rsaFunctionFor(_publicDecrypt, RSA_PKCS1_PADDING);
-const privateEncrypt = rsaFunctionFor(_privateEncrypt, RSA_PKCS1_PADDING);
-const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING);
+const publicEncrypt = rsaFunctionFor(_publicEncrypt, RSA_PKCS1_OAEP_PADDING,
+ 'public');
+const publicDecrypt = rsaFunctionFor(_publicDecrypt, RSA_PKCS1_PADDING,
+ 'private');
+const privateEncrypt = rsaFunctionFor(_privateEncrypt, RSA_PKCS1_PADDING,
+ 'private');
+const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING,
+ 'public');
function getDecoder(decoder, encoding) {
encoding = normalizeEncoding(encoding);
@@ -105,11 +116,7 @@ function createCipher(cipher, password, options, decipher) {
function createCipherWithIV(cipher, key, options, decipher, iv) {
validateString(cipher, 'cipher');
- key = toBuf(key);
- if (!isArrayBufferView(key)) {
- throw invalidArrayBufferView('key', key);
- }
-
+ key = prepareSecretKey(key);
iv = toBuf(iv);
if (iv !== null && !isArrayBufferView(iv)) {
throw invalidArrayBufferView('iv', iv);
diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js
index 6803d8fa954e73..7c697eb4772a19 100644
--- a/lib/internal/crypto/hash.js
+++ b/lib/internal/crypto/hash.js
@@ -12,6 +12,10 @@ const {
toBuf
} = require('internal/crypto/util');
+const {
+ prepareSecretKey
+} = require('internal/crypto/keys');
+
const { Buffer } = require('buffer');
const {
@@ -88,10 +92,7 @@ function Hmac(hmac, key, options) {
if (!(this instanceof Hmac))
return new Hmac(hmac, key, options);
validateString(hmac, 'hmac');
- if (typeof key !== 'string' && !isArrayBufferView(key)) {
- throw new ERR_INVALID_ARG_TYPE('key',
- ['string', 'TypedArray', 'DataView'], key);
- }
+ key = prepareSecretKey(key);
this[kHandle] = new _Hmac();
this[kHandle].init(hmac, toBuf(key));
this[kState] = {
diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js
index 7222d301f08692..7c0c4110439860 100644
--- a/lib/internal/crypto/keygen.js
+++ b/lib/internal/crypto/keygen.js
@@ -6,24 +6,32 @@ const {
generateKeyPairDSA,
generateKeyPairEC,
OPENSSL_EC_NAMED_CURVE,
- OPENSSL_EC_EXPLICIT_CURVE,
- PK_ENCODING_PKCS1,
- PK_ENCODING_PKCS8,
- PK_ENCODING_SPKI,
- PK_ENCODING_SEC1,
- PK_FORMAT_DER,
- PK_FORMAT_PEM
+ OPENSSL_EC_EXPLICIT_CURVE
} = internalBinding('crypto');
+const {
+ parsePublicKeyEncoding,
+ parsePrivateKeyEncoding,
+
+ PublicKeyObject,
+ PrivateKeyObject
+} = require('internal/crypto/keys');
const { customPromisifyArgs } = require('internal/util');
const { isUint32, validateString } = require('internal/validators');
const {
- ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CALLBACK,
ERR_INVALID_OPT_VALUE
} = require('internal/errors').codes;
+const { isArrayBufferView } = require('internal/util/types');
+
+function wrapKey(key, ctor) {
+ if (typeof key === 'string' || isArrayBufferView(key))
+ return key;
+ return new ctor(key);
+}
+
function generateKeyPair(type, options, callback) {
if (typeof options === 'function') {
callback = options;
@@ -38,6 +46,9 @@ function generateKeyPair(type, options, callback) {
const wrap = new AsyncWrap(Providers.KEYPAIRGENREQUEST);
wrap.ondone = (ex, pubkey, privkey) => {
if (ex) return callback.call(wrap, ex);
+ // If no encoding was chosen, return key objects instead.
+ pubkey = wrapKey(pubkey, PublicKeyObject);
+ privkey = wrapKey(privkey, PrivateKeyObject);
callback.call(wrap, null, pubkey, privkey);
};
@@ -69,86 +80,32 @@ function handleError(impl, wrap) {
function parseKeyEncoding(keyType, options) {
const { publicKeyEncoding, privateKeyEncoding } = options;
- if (publicKeyEncoding == null || typeof publicKeyEncoding !== 'object')
- throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding', publicKeyEncoding);
-
- const { format: strPublicFormat, type: strPublicType } = publicKeyEncoding;
-
- let publicType;
- if (strPublicType === 'pkcs1') {
- if (keyType !== 'rsa') {
- throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
- strPublicType, 'can only be used for RSA keys');
- }
- publicType = PK_ENCODING_PKCS1;
- } else if (strPublicType === 'spki') {
- publicType = PK_ENCODING_SPKI;
+ let publicFormat, publicType;
+ if (publicKeyEncoding == null) {
+ publicFormat = publicType = undefined;
+ } else if (typeof publicKeyEncoding === 'object') {
+ ({
+ format: publicFormat,
+ type: publicType
+ } = parsePublicKeyEncoding(publicKeyEncoding, keyType,
+ 'publicKeyEncoding'));
} else {
- throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding.type', strPublicType);
+ throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding', publicKeyEncoding);
}
- let publicFormat;
- if (strPublicFormat === 'der') {
- publicFormat = PK_FORMAT_DER;
- } else if (strPublicFormat === 'pem') {
- publicFormat = PK_FORMAT_PEM;
+ let privateFormat, privateType, cipher, passphrase;
+ if (privateKeyEncoding == null) {
+ privateFormat = privateType = undefined;
+ } else if (typeof privateKeyEncoding === 'object') {
+ ({
+ format: privateFormat,
+ type: privateType,
+ cipher,
+ passphrase
+ } = parsePrivateKeyEncoding(privateKeyEncoding, keyType,
+ 'privateKeyEncoding'));
} else {
- throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding.format',
- strPublicFormat);
- }
-
- if (privateKeyEncoding == null || typeof privateKeyEncoding !== 'object')
throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding', privateKeyEncoding);
-
- const {
- cipher,
- passphrase,
- format: strPrivateFormat,
- type: strPrivateType
- } = privateKeyEncoding;
-
- let privateType;
- if (strPrivateType === 'pkcs1') {
- if (keyType !== 'rsa') {
- throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
- strPrivateType, 'can only be used for RSA keys');
- }
- privateType = PK_ENCODING_PKCS1;
- } else if (strPrivateType === 'pkcs8') {
- privateType = PK_ENCODING_PKCS8;
- } else if (strPrivateType === 'sec1') {
- if (keyType !== 'ec') {
- throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
- strPrivateType, 'can only be used for EC keys');
- }
- privateType = PK_ENCODING_SEC1;
- } else {
- throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.type', strPrivateType);
- }
-
- let privateFormat;
- if (strPrivateFormat === 'der') {
- privateFormat = PK_FORMAT_DER;
- } else if (strPrivateFormat === 'pem') {
- privateFormat = PK_FORMAT_PEM;
- } else {
- throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.format',
- strPrivateFormat);
- }
-
- if (cipher != null) {
- if (typeof cipher !== 'string')
- throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.cipher', cipher);
- if (privateFormat === PK_FORMAT_DER &&
- (privateType === PK_ENCODING_PKCS1 ||
- privateType === PK_ENCODING_SEC1)) {
- throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
- strPrivateType, 'does not support encryption');
- }
- if (typeof passphrase !== 'string') {
- throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.passphrase',
- passphrase);
- }
}
return {
@@ -181,8 +138,8 @@ function check(type, options, callback) {
}
impl = (wrap) => generateKeyPairRSA(modulusLength, publicExponent,
- publicType, publicFormat,
- privateType, privateFormat,
+ publicFormat, publicType,
+ privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
@@ -200,8 +157,8 @@ function check(type, options, callback) {
}
impl = (wrap) => generateKeyPairDSA(modulusLength, divisorLength,
- publicType, publicFormat,
- privateType, privateFormat,
+ publicFormat, publicType,
+ privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
@@ -219,8 +176,8 @@ function check(type, options, callback) {
throw new ERR_INVALID_OPT_VALUE('paramEncoding', paramEncoding);
impl = (wrap) => generateKeyPairEC(namedCurve, paramEncoding,
- publicType, publicFormat,
- privateType, privateFormat,
+ publicFormat, publicType,
+ privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js
new file mode 100644
index 00000000000000..ad828350806f3a
--- /dev/null
+++ b/lib/internal/crypto/keys.js
@@ -0,0 +1,337 @@
+'use strict';
+
+const {
+ KeyObject: KeyObjectHandle,
+ kKeyTypeSecret,
+ kKeyTypePublic,
+ kKeyTypePrivate,
+ kKeyFormatPEM,
+ kKeyFormatDER,
+ kKeyEncodingPKCS1,
+ kKeyEncodingPKCS8,
+ kKeyEncodingSPKI,
+ kKeyEncodingSEC1
+} = internalBinding('crypto');
+const {
+ ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
+ ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
+ ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_ARG_VALUE,
+ ERR_INVALID_OPT_VALUE,
+ ERR_OUT_OF_RANGE
+} = require('internal/errors').codes;
+const { kHandle } = require('internal/crypto/util');
+
+const { isArrayBufferView } = require('internal/util/types');
+
+const kKeyType = Symbol('kKeyType');
+
+const encodingNames = [];
+for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'],
+ [kKeyEncodingSPKI, 'spki'], [kKeyEncodingSEC1, 'sec1']])
+ encodingNames[m[0]] = m[1];
+
+class KeyObject {
+ constructor(type, handle) {
+ if (type !== 'secret' && type !== 'public' && type !== 'private')
+ throw new ERR_INVALID_ARG_VALUE('type', type);
+ if (typeof handle !== 'object')
+ throw new ERR_INVALID_ARG_TYPE('handle', 'string', handle);
+
+ this[kKeyType] = type;
+
+ Object.defineProperty(this, kHandle, {
+ value: handle,
+ enumerable: false,
+ configurable: false,
+ writable: false
+ });
+ }
+
+ get type() {
+ return this[kKeyType];
+ }
+}
+
+class SecretKeyObject extends KeyObject {
+ constructor(handle) {
+ super('secret', handle);
+ }
+
+ get symmetricKeySize() {
+ return this[kHandle].getSymmetricKeySize();
+ }
+
+ export() {
+ return this[kHandle].export();
+ }
+}
+
+const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
+
+class AsymmetricKeyObject extends KeyObject {
+ get asymmetricKeyType() {
+ return this[kAsymmetricKeyType] ||
+ (this[kAsymmetricKeyType] = this[kHandle].getAsymmetricKeyType());
+ }
+}
+
+class PublicKeyObject extends AsymmetricKeyObject {
+ constructor(handle) {
+ super('public', handle);
+ }
+
+ export(encoding) {
+ const {
+ format,
+ type
+ } = parsePublicKeyEncoding(encoding, this.asymmetricKeyType);
+ return this[kHandle].export(format, type);
+ }
+}
+
+class PrivateKeyObject extends AsymmetricKeyObject {
+ constructor(handle) {
+ super('private', handle);
+ }
+
+ export(encoding) {
+ const {
+ format,
+ type,
+ cipher,
+ passphrase
+ } = parsePrivateKeyEncoding(encoding, this.asymmetricKeyType);
+ return this[kHandle].export(format, type, cipher, passphrase);
+ }
+}
+
+function parseKeyFormat(formatStr, defaultFormat, optionName) {
+ if (formatStr === undefined && defaultFormat !== undefined)
+ return defaultFormat;
+ else if (formatStr === 'pem')
+ return kKeyFormatPEM;
+ else if (formatStr === 'der')
+ return kKeyFormatDER;
+ throw new ERR_INVALID_OPT_VALUE(optionName, formatStr);
+}
+
+function parseKeyType(typeStr, required, keyType, isPublic, optionName) {
+ if (typeStr === undefined && !required) {
+ return undefined;
+ } else if (typeStr === 'pkcs1') {
+ if (keyType !== undefined && keyType !== 'rsa') {
+ throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
+ typeStr, 'can only be used for RSA keys');
+ }
+ return kKeyEncodingPKCS1;
+ } else if (typeStr === 'spki' && isPublic !== false) {
+ return kKeyEncodingSPKI;
+ } else if (typeStr === 'pkcs8' && isPublic !== true) {
+ return kKeyEncodingPKCS8;
+ } else if (typeStr === 'sec1' && isPublic !== true) {
+ if (keyType !== undefined && keyType !== 'ec') {
+ throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
+ typeStr, 'can only be used for EC keys');
+ }
+ return kKeyEncodingSEC1;
+ }
+
+ throw new ERR_INVALID_OPT_VALUE(optionName, typeStr);
+}
+
+function option(name, objName) {
+ return objName === undefined ? name : `${objName}.${name}`;
+}
+
+function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
+ const { format: formatStr, type: typeStr } = enc;
+
+ const isInput = keyType === undefined;
+ const format = parseKeyFormat(formatStr,
+ isInput ? kKeyFormatPEM : undefined,
+ option('format', objName));
+
+ const type = parseKeyType(typeStr,
+ !isInput || format === kKeyFormatDER,
+ keyType,
+ isPublic,
+ option('type', objName));
+
+ return { format, type };
+}
+
+function isStringOrBuffer(val) {
+ return typeof val === 'string' || isArrayBufferView(val);
+}
+
+function parseKeyEncoding(enc, keyType, isPublic, objName) {
+ const isInput = keyType === undefined;
+
+ const {
+ format,
+ type
+ } = parseKeyFormatAndType(enc, keyType, isPublic, objName);
+
+ let cipher, passphrase;
+ if (isPublic !== true) {
+ ({ cipher, passphrase } = enc);
+
+ if (!isInput && cipher != null) {
+ if (typeof cipher !== 'string')
+ throw new ERR_INVALID_OPT_VALUE(option('cipher', objName), cipher);
+ if (format === kKeyFormatDER &&
+ (type === kKeyEncodingPKCS1 ||
+ type === kKeyEncodingSEC1)) {
+ throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
+ encodingNames[type], 'does not support encryption');
+ }
+ }
+
+ if ((isInput && passphrase !== undefined &&
+ !isStringOrBuffer(passphrase)) ||
+ (!isInput && cipher != null && !isStringOrBuffer(passphrase))) {
+ throw new ERR_INVALID_OPT_VALUE(option('passphrase', objName),
+ passphrase);
+ }
+ }
+
+ return { format, type, cipher, passphrase };
+}
+
+// Parses the public key encoding based on an object. keyType must be undefined
+// when this is used to parse an input encoding and must be a valid key type if
+// used to parse an output encoding.
+function parsePublicKeyEncoding(enc, keyType, objName) {
+ return parseKeyFormatAndType(enc, keyType, true, objName);
+}
+
+// Parses the private key encoding based on an object. keyType must be undefined
+// when this is used to parse an input encoding and must be a valid key type if
+// used to parse an output encoding.
+function parsePrivateKeyEncoding(enc, keyType, objName) {
+ return parseKeyEncoding(enc, keyType, false, objName);
+}
+
+function getKeyObjectHandle(key, isPublic, allowKeyObject) {
+ if (!allowKeyObject) {
+ return new ERR_INVALID_ARG_TYPE(
+ 'key',
+ ['string', 'Buffer', 'TypedArray', 'DataView'],
+ key
+ );
+ }
+ if (isPublic != null) {
+ const expectedType = isPublic ? 'public' : 'private';
+ if (key.type !== expectedType)
+ throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, expectedType);
+ }
+ return key[kHandle];
+}
+
+function prepareAsymmetricKey(key, isPublic, allowKeyObject = true) {
+ if (isKeyObject(key)) {
+ // Best case: A key object, as simple as that.
+ return { data: getKeyObjectHandle(key, isPublic, allowKeyObject) };
+ } else if (typeof key === 'string' || isArrayBufferView(key)) {
+ // Expect PEM by default, mostly for backward compatibility.
+ return { format: kKeyFormatPEM, data: key };
+ } else if (typeof key === 'object') {
+ const data = key.key;
+ // The 'key' property can be a KeyObject as well to allow specifying
+ // additional options such as padding along with the key.
+ if (isKeyObject(data))
+ return { data: getKeyObjectHandle(data, isPublic, allowKeyObject) };
+ // Either PEM or DER using PKCS#1 or SPKI.
+ if (!isStringOrBuffer(data)) {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'key',
+ ['string', 'Buffer', 'TypedArray', 'DataView',
+ ...(allowKeyObject ? ['KeyObject'] : [])],
+ key);
+ }
+ return { data, ...parseKeyEncoding(key, undefined, isPublic) };
+ } else {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'key',
+ ['string', 'Buffer', 'TypedArray', 'DataView',
+ ...(allowKeyObject ? ['KeyObject'] : [])],
+ key
+ );
+ }
+}
+
+function preparePublicKey(key, allowKeyObject) {
+ return prepareAsymmetricKey(key, true, allowKeyObject);
+}
+
+function preparePrivateKey(key, allowKeyObject) {
+ return prepareAsymmetricKey(key, false, allowKeyObject);
+}
+
+function preparePublicOrPrivateKey(key, allowKeyObject) {
+ return prepareAsymmetricKey(key, undefined, allowKeyObject);
+}
+
+function prepareSecretKey(key, bufferOnly = false) {
+ if (!isArrayBufferView(key) && (bufferOnly || typeof key !== 'string')) {
+ if (isKeyObject(key) && !bufferOnly) {
+ if (key.type !== 'secret')
+ throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
+ return key[kHandle];
+ } else {
+ throw new ERR_INVALID_ARG_TYPE(
+ 'key',
+ ['Buffer', 'TypedArray', 'DataView',
+ ...(bufferOnly ? [] : ['string', 'KeyObject'])],
+ key);
+ }
+ }
+ return key;
+}
+
+function createSecretKey(key) {
+ key = prepareSecretKey(key, true);
+ if (key.byteLength === 0)
+ throw new ERR_OUT_OF_RANGE('key.byteLength', '> 0', key.byteLength);
+ const handle = new KeyObjectHandle(kKeyTypeSecret);
+ handle.init(key);
+ return new SecretKeyObject(handle);
+}
+
+function createPublicKey(key) {
+ const { format, type, data } = preparePublicKey(key, false);
+ const handle = new KeyObjectHandle(kKeyTypePublic);
+ handle.init(data, format, type);
+ return new PublicKeyObject(handle);
+}
+
+function createPrivateKey(key) {
+ const { format, type, data, passphrase } = preparePrivateKey(key, false);
+ const handle = new KeyObjectHandle(kKeyTypePrivate);
+ handle.init(data, format, type, passphrase);
+ return new PrivateKeyObject(handle);
+}
+
+function isKeyObject(key) {
+ return key instanceof KeyObject;
+}
+
+module.exports = {
+ // Public API.
+ createSecretKey,
+ createPublicKey,
+ createPrivateKey,
+
+ // These are designed for internal use only and should not be exposed.
+ parsePublicKeyEncoding,
+ parsePrivateKeyEncoding,
+ preparePublicKey,
+ preparePrivateKey,
+ preparePublicOrPrivateKey,
+ prepareSecretKey,
+ SecretKeyObject,
+ PublicKeyObject,
+ PrivateKeyObject,
+ isKeyObject
+};
diff --git a/lib/internal/crypto/sig.js b/lib/internal/crypto/sig.js
index 9f02c866739f24..a694ba856777c1 100644
--- a/lib/internal/crypto/sig.js
+++ b/lib/internal/crypto/sig.js
@@ -17,6 +17,10 @@ const {
toBuf,
validateArrayBufferView,
} = require('internal/crypto/util');
+const {
+ preparePrivateKey,
+ preparePublicKey
+} = require('internal/crypto/keys');
const { Writable } = require('stream');
const { inherits } = require('util');
@@ -71,21 +75,18 @@ Sign.prototype.sign = function sign(options, encoding) {
if (!options)
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
- var key = options.key || options;
- var passphrase = options.passphrase || null;
+ const { data, format, type, passphrase } = preparePrivateKey(options, true);
// Options specific to RSA
- var rsaPadding = getPadding(options);
-
- var pssSaltLength = getSaltLength(options);
+ const rsaPadding = getPadding(options);
+ const pssSaltLength = getSaltLength(options);
- key = validateArrayBufferView(key, 'key');
-
- var ret = this[kHandle].sign(key, passphrase, rsaPadding, pssSaltLength);
+ const ret = this[kHandle].sign(data, format, type, passphrase, rsaPadding,
+ pssSaltLength);
encoding = encoding || getDefaultEncoding();
if (encoding && encoding !== 'buffer')
- ret = ret.toString(encoding);
+ return ret.toString(encoding);
return ret;
};
@@ -107,7 +108,12 @@ Verify.prototype._write = Sign.prototype._write;
Verify.prototype.update = Sign.prototype.update;
Verify.prototype.verify = function verify(options, signature, sigEncoding) {
- var key = options.key || options;
+ const {
+ data,
+ format,
+ type
+ } = preparePublicKey(options, true);
+
sigEncoding = sigEncoding || getDefaultEncoding();
// Options specific to RSA
@@ -115,12 +121,11 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
var pssSaltLength = getSaltLength(options);
- key = validateArrayBufferView(key, 'key');
-
signature = validateArrayBufferView(toBuf(signature, sigEncoding),
'signature');
- return this[kHandle].verify(key, signature, rsaPadding, pssSaltLength);
+ return this[kHandle].verify(data, format, type, signature,
+ rsaPadding, pssSaltLength);
};
legacyNativeHandle(Verify);
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 04c68c2af21874..f1965e825ec3d2 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -573,6 +573,8 @@ E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed', Error);
E('ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS', 'The selected key encoding %s %s.',
Error);
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_PBKDF2_ERROR', 'PBKDF2 error', Error);
E('ERR_CRYPTO_SCRYPT_INVALID_PARAMETER', 'Invalid scrypt parameter', Error);
diff --git a/node.gyp b/node.gyp
index f2d5fe8db83d03..9dc31b98ca5af7 100644
--- a/node.gyp
+++ b/node.gyp
@@ -100,6 +100,7 @@
'lib/internal/crypto/diffiehellman.js',
'lib/internal/crypto/hash.js',
'lib/internal/crypto/keygen.js',
+ 'lib/internal/crypto/keys.js',
'lib/internal/crypto/pbkdf2.js',
'lib/internal/crypto/random.js',
'lib/internal/crypto/scrypt.js',
diff --git a/src/env.h b/src/env.h
index 8363a7cee30004..a1ace30c59bc94 100644
--- a/src/env.h
+++ b/src/env.h
@@ -139,6 +139,9 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(chunks_sent_since_last_write_string, "chunksSentSinceLastWrite") \
V(code_string, "code") \
V(constants_string, "constants") \
+ V(crypto_dsa_string, "dsa") \
+ V(crypto_ec_string, "ec") \
+ V(crypto_rsa_string, "rsa") \
V(cwd_string, "cwd") \
V(dest_string, "dest") \
V(destroyed_string, "destroyed") \
@@ -321,6 +324,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(async_wrap_object_ctor_template, v8::FunctionTemplate) \
V(buffer_prototype_object, v8::Object) \
V(context, v8::Context) \
+ V(crypto_key_object_constructor, v8::Function) \
V(domain_callback, v8::Function) \
V(domexception_function, v8::Function) \
V(fd_constructor_template, v8::ObjectTemplate) \
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 42fe00b1d0458b..655929343df904 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -71,6 +71,7 @@ using v8::DontDelete;
using v8::EscapableHandleScope;
using v8::Exception;
using v8::External;
+using v8::Function;
using v8::FunctionCallback;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
@@ -2668,6 +2669,837 @@ static bool IsSupportedAuthenticatedMode(const EVP_CIPHER_CTX* ctx) {
return IsSupportedAuthenticatedMode(cipher);
}
+template
+static T* MallocOpenSSL(size_t count) {
+ void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T)));
+ CHECK_NOT_NULL(mem);
+ return static_cast(mem);
+}
+
+enum class ParsePublicKeyResult {
+ kParsePublicOk,
+ kParsePublicNotRecognized,
+ kParsePublicFailed
+};
+
+static ParsePublicKeyResult TryParsePublicKey(
+ EVPKeyPointer* pkey,
+ const BIOPointer& bp,
+ const char* name,
+ // NOLINTNEXTLINE(runtime/int)
+ std::function parse) {
+ unsigned char* der_data;
+ long der_len; // NOLINT(runtime/int)
+
+ // This skips surrounding data and decodes PEM to DER.
+ {
+ MarkPopErrorOnReturn mark_pop_error_on_return;
+ if (PEM_bytes_read_bio(&der_data, &der_len, nullptr, name,
+ bp.get(), nullptr, nullptr) != 1)
+ return ParsePublicKeyResult::kParsePublicNotRecognized;
+ }
+
+ // OpenSSL might modify the pointer, so we need to make a copy before parsing.
+ const unsigned char* p = der_data;
+ pkey->reset(parse(&p, der_len));
+ OPENSSL_clear_free(der_data, der_len);
+
+ return *pkey ? ParsePublicKeyResult::kParsePublicOk :
+ ParsePublicKeyResult::kParsePublicFailed;
+}
+
+static ParsePublicKeyResult ParsePublicKeyPEM(EVPKeyPointer* pkey,
+ const char* key_pem,
+ int key_pem_len,
+ bool allow_certificate) {
+ BIOPointer bp(BIO_new_mem_buf(const_cast(key_pem), key_pem_len));
+ if (!bp)
+ return ParsePublicKeyResult::kParsePublicFailed;
+
+ ParsePublicKeyResult ret;
+
+ // Try PKCS#8 first.
+ ret = TryParsePublicKey(pkey, bp, "PUBLIC KEY",
+ [](const unsigned char** p, long l) { // NOLINT(runtime/int)
+ return d2i_PUBKEY(nullptr, p, l);
+ });
+ if (ret != ParsePublicKeyResult::kParsePublicNotRecognized)
+ return ret;
+
+ // Maybe it is PKCS#1.
+ CHECK(BIO_reset(bp.get()));
+ ret = TryParsePublicKey(pkey, bp, "RSA PUBLIC KEY",
+ [](const unsigned char** p, long l) { // NOLINT(runtime/int)
+ return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l);
+ });
+ if (ret != ParsePublicKeyResult::kParsePublicNotRecognized ||
+ !allow_certificate)
+ return ret;
+
+ // X.509 fallback.
+ CHECK(BIO_reset(bp.get()));
+ return TryParsePublicKey(pkey, bp, "CERTIFICATE",
+ [](const unsigned char** p, long l) { // NOLINT(runtime/int)
+ X509Pointer x509(d2i_X509(nullptr, p, l));
+ return x509 ? X509_get_pubkey(x509.get()) : nullptr;
+ });
+}
+
+static bool ParsePublicKey(EVPKeyPointer* pkey,
+ const PublicKeyEncodingConfig& config,
+ const char* key,
+ size_t key_len,
+ bool allow_certificate) {
+ if (config.format_ == kKeyFormatPEM) {
+ ParsePublicKeyResult r =
+ ParsePublicKeyPEM(pkey, key, key_len, allow_certificate);
+ return r == ParsePublicKeyResult::kParsePublicOk;
+ } else {
+ CHECK_EQ(config.format_, kKeyFormatDER);
+ const unsigned char* p = reinterpret_cast(key);
+ if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
+ pkey->reset(d2i_PublicKey(EVP_PKEY_RSA, nullptr, &p, key_len));
+ return pkey;
+ } else {
+ CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSPKI);
+ pkey->reset(d2i_PUBKEY(nullptr, &p, key_len));
+ return pkey;
+ }
+ }
+}
+
+static inline Local BIOToStringOrBuffer(Environment* env,
+ BIO* bio,
+ PKFormatType format) {
+ BUF_MEM* bptr;
+ BIO_get_mem_ptr(bio, &bptr);
+ if (format == kKeyFormatPEM) {
+ // PEM is an ASCII format, so we will return it as a string.
+ return String::NewFromUtf8(env->isolate(), bptr->data,
+ NewStringType::kNormal,
+ bptr->length).ToLocalChecked();
+ } else {
+ CHECK_EQ(format, kKeyFormatDER);
+ // DER is binary, return it as a buffer.
+ return Buffer::Copy(env, bptr->data, bptr->length).ToLocalChecked();
+ }
+}
+
+static bool WritePublicKeyInner(EVP_PKEY* pkey,
+ const BIOPointer& bio,
+ const PublicKeyEncodingConfig& config) {
+ if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
+ // PKCS#1 is only valid for RSA keys.
+ CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_RSA);
+ RSAPointer rsa(EVP_PKEY_get1_RSA(pkey));
+ if (config.format_ == kKeyFormatPEM) {
+ // Encode PKCS#1 as PEM.
+ return PEM_write_bio_RSAPublicKey(bio.get(), rsa.get()) == 1;
+ } else {
+ // Encode PKCS#1 as DER.
+ CHECK_EQ(config.format_, kKeyFormatDER);
+ return i2d_RSAPublicKey_bio(bio.get(), rsa.get()) == 1;
+ }
+ } else {
+ CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSPKI);
+ if (config.format_ == kKeyFormatPEM) {
+ // Encode SPKI as PEM.
+ return PEM_write_bio_PUBKEY(bio.get(), pkey) == 1;
+ } else {
+ // Encode SPKI as DER.
+ CHECK_EQ(config.format_, kKeyFormatDER);
+ return i2d_PUBKEY_bio(bio.get(), pkey) == 1;
+ }
+ }
+}
+
+static MaybeLocal WritePublicKey(Environment* env,
+ EVP_PKEY* pkey,
+ const PublicKeyEncodingConfig& config) {
+ BIOPointer bio(BIO_new(BIO_s_mem()));
+ CHECK(bio);
+
+ if (!WritePublicKeyInner(pkey, bio, config)) {
+ ThrowCryptoError(env, ERR_get_error(), "Failed to encode public key");
+ return MaybeLocal();
+ }
+ return BIOToStringOrBuffer(env, bio.get(), config.format_);
+}
+
+static EVPKeyPointer ParsePrivateKey(const PrivateKeyEncodingConfig& config,
+ const char* key,
+ size_t key_len) {
+ EVPKeyPointer pkey;
+
+ if (config.format_ == kKeyFormatPEM) {
+ BIOPointer bio(BIO_new_mem_buf(key, key_len));
+ CHECK(bio);
+
+ char* pass = const_cast(config.passphrase_.get());
+ pkey.reset(PEM_read_bio_PrivateKey(bio.get(),
+ nullptr,
+ PasswordCallback,
+ pass));
+ } else {
+ CHECK_EQ(config.format_, kKeyFormatDER);
+
+ if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
+ const unsigned char* p = reinterpret_cast(key);
+ pkey.reset(d2i_PrivateKey(EVP_PKEY_RSA, nullptr, &p, key_len));
+ } else if (config.type_.ToChecked() == kKeyEncodingPKCS8) {
+ BIOPointer bio(BIO_new_mem_buf(key, key_len));
+ CHECK(bio);
+ char* pass = const_cast(config.passphrase_.get());
+ pkey.reset(d2i_PKCS8PrivateKey_bio(bio.get(),
+ nullptr,
+ PasswordCallback,
+ pass));
+ } else {
+ CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSEC1);
+ const unsigned char* p = reinterpret_cast(key);
+ pkey.reset(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &p, key_len));
+ }
+ }
+
+ // OpenSSL can fail to parse the key but still return a non-null pointer.
+ if (ERR_peek_error() != 0)
+ pkey.reset();
+
+ return pkey;
+}
+
+ByteSource::ByteSource(ByteSource&& other)
+ : data_(other.data_),
+ allocated_data_(other.allocated_data_),
+ size_(other.size_) {
+ other.allocated_data_ = nullptr;
+}
+
+ByteSource::~ByteSource() {
+ OPENSSL_clear_free(allocated_data_, size_);
+}
+
+ByteSource& ByteSource::operator=(ByteSource&& other) {
+ if (&other != this) {
+ OPENSSL_clear_free(allocated_data_, size_);
+ data_ = other.data_;
+ allocated_data_ = other.allocated_data_;
+ other.allocated_data_ = nullptr;
+ size_ = other.size_;
+ }
+ return *this;
+}
+
+const char* ByteSource::get() const {
+ return data_;
+}
+
+size_t ByteSource::size() const {
+ return size_;
+}
+
+ByteSource ByteSource::FromStringOrBuffer(Environment* env,
+ Local value) {
+ return Buffer::HasInstance(value) ? FromBuffer(value)
+ : FromString(env, value.As());
+}
+
+ByteSource ByteSource::FromString(Environment* env, Local str,
+ bool ntc) {
+ CHECK(str->IsString());
+ size_t size = str->Utf8Length(env->isolate());
+ size_t alloc_size = ntc ? size + 1 : size;
+ char* data = MallocOpenSSL(alloc_size);
+ int opts = String::NO_OPTIONS;
+ if (!ntc) opts |= String::NO_NULL_TERMINATION;
+ str->WriteUtf8(env->isolate(), data, alloc_size, nullptr, opts);
+ return Allocated(data, size);
+}
+
+ByteSource ByteSource::FromBuffer(Local buffer, bool ntc) {
+ size_t size = Buffer::Length(buffer);
+ if (ntc) {
+ char* data = MallocOpenSSL(size + 1);
+ memcpy(data, Buffer::Data(buffer), size);
+ data[size] = 0;
+ return Allocated(data, size);
+ }
+ return Foreign(Buffer::Data(buffer), size);
+}
+
+ByteSource ByteSource::NullTerminatedCopy(Environment* env,
+ Local value) {
+ return Buffer::HasInstance(value) ? FromBuffer(value, true)
+ : FromString(env, value.As(), true);
+}
+
+ByteSource ByteSource::FromSymmetricKeyObject(Local handle) {
+ CHECK(handle->IsObject());
+ KeyObject* key = Unwrap(handle.As