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()); + CHECK(key); + return Foreign(key->GetSymmetricKey(), key->GetSymmetricKeySize()); +} + +ByteSource::ByteSource(const char* data, char* allocated_data, size_t size) + : data_(data), + allocated_data_(allocated_data), + size_(size) {} + +ByteSource ByteSource::Allocated(char* data, size_t size) { + return ByteSource(data, data, size); +} + +ByteSource ByteSource::Foreign(const char* data, size_t size) { + return ByteSource(data, nullptr, size); +} + +enum KeyEncodingContext { + kKeyContextInput, + kKeyContextExport, + kKeyContextGenerate +}; + +static void GetKeyFormatAndTypeFromJs( + AsymmetricKeyEncodingConfig* config, + const FunctionCallbackInfo& args, + unsigned int* offset, + KeyEncodingContext context) { + // During key pair generation, it is possible not to specify a key encoding, + // which will lead to a key object being returned. + if (args[*offset]->IsUndefined()) { + CHECK_EQ(context, kKeyContextGenerate); + CHECK(args[*offset + 1]->IsUndefined()); + config->output_key_object_ = true; + } else { + config->output_key_object_ = false; + + CHECK(args[*offset]->IsInt32()); + config->format_ = static_cast( + args[*offset].As()->Value()); + + if (args[*offset + 1]->IsInt32()) { + config->type_ = Just(static_cast( + args[*offset + 1].As()->Value())); + } else { + CHECK(context == kKeyContextInput && config->format_ == kKeyFormatPEM); + CHECK(args[*offset + 1]->IsNullOrUndefined()); + config->type_ = Nothing(); + } + } + + *offset += 2; +} + +static PublicKeyEncodingConfig GetPublicKeyEncodingFromJs( + const FunctionCallbackInfo& args, + unsigned int* offset, + KeyEncodingContext context) { + PublicKeyEncodingConfig result; + GetKeyFormatAndTypeFromJs(&result, args, offset, context); + return result; +} + +static ManagedEVPPKey GetPublicKeyFromJs( + const FunctionCallbackInfo& args, + unsigned int* offset, + bool allow_key_object, + bool allow_certificate) { + if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) { + Environment* env = Environment::GetCurrent(args); + ByteSource key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]); + PublicKeyEncodingConfig config = + GetPublicKeyEncodingFromJs(args, offset, kKeyContextInput); + EVPKeyPointer pkey; + ParsePublicKey(&pkey, config, key.get(), key.size(), allow_certificate); + if (!pkey) + ThrowCryptoError(env, ERR_get_error(), "Failed to read public key"); + return ManagedEVPPKey(pkey.release()); + } else { + CHECK(args[*offset]->IsObject() && allow_key_object); + KeyObject* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args[*offset].As(), ManagedEVPPKey()); + CHECK_EQ(key->GetKeyType(), kKeyTypePublic); + (*offset) += 3; + return key->GetAsymmetricKey(); + } +} + +static NonCopyableMaybe GetPrivateKeyEncodingFromJs( + const FunctionCallbackInfo& args, + unsigned int* offset, + KeyEncodingContext context) { + Environment* env = Environment::GetCurrent(args); + + PrivateKeyEncodingConfig result; + GetKeyFormatAndTypeFromJs(&result, args, offset, context); + + if (result.output_key_object_) { + if (context != kKeyContextInput) + (*offset)++; + } else { + bool needs_passphrase = false; + if (context != kKeyContextInput) { + if (args[*offset]->IsString()) { + String::Utf8Value cipher_name(env->isolate(), + args[*offset].As()); + result.cipher_ = EVP_get_cipherbyname(*cipher_name); + if (result.cipher_ == nullptr) { + env->ThrowError("Unknown cipher"); + return NonCopyableMaybe(); + } + needs_passphrase = true; + } else { + CHECK(args[*offset]->IsNullOrUndefined()); + result.cipher_ = nullptr; + } + (*offset)++; + } + + if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) { + CHECK_IMPLIES(context != kKeyContextInput, result.cipher_ != nullptr); + + result.passphrase_ = ByteSource::NullTerminatedCopy(env, args[*offset]); + } else { + CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase); + } + } + + (*offset)++; + return NonCopyableMaybe(std::move(result)); +} + +static ManagedEVPPKey GetPrivateKeyFromJs( + const FunctionCallbackInfo& args, + unsigned int* offset, + bool allow_key_object) { + if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) { + Environment* env = Environment::GetCurrent(args); + ByteSource key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]); + NonCopyableMaybe config = + GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput); + if (config.IsEmpty()) + return ManagedEVPPKey(); + EVPKeyPointer pkey = + ParsePrivateKey(config.Release(), key.get(), key.size()); + if (!pkey) + ThrowCryptoError(env, ERR_get_error(), "Failed to read private key"); + return ManagedEVPPKey(pkey.release()); + } else { + CHECK(args[*offset]->IsObject() && allow_key_object); + KeyObject* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args[*offset].As(), ManagedEVPPKey()); + CHECK_EQ(key->GetKeyType(), kKeyTypePrivate); + (*offset) += 4; + return key->GetAsymmetricKey(); + } +} + +static bool IsRSAPrivateKey(const unsigned char* data, size_t size) { + // Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE. + if (size >= 2 && data[0] == 0x30) { + size_t offset; + if (data[1] & 0x80) { + // Long form. + size_t n_bytes = data[1] & ~0x80; + if (n_bytes + 2 > size || n_bytes > sizeof(size_t)) + return false; + size_t i, length = 0; + for (i = 0; i < n_bytes; i++) + length = (length << 8) | data[i + 2]; + offset = 2 + n_bytes; + size = std::min(size, length + 2); + } else { + // Short form. + offset = 2; + size = std::min(size, data[1] + 2); + } + + // An RSAPrivateKey sequence always starts with a single-byte integer whose + // value is either 0 or 1, whereas an RSAPublicKey starts with the modulus + // (which is the product of two primes and therefore at least 4), so we can + // decide the type of the structure based on the first three bytes of the + // sequence. + return size - offset >= 3 && + data[offset] == 2 && + data[offset + 1] == 1 && + !(data[offset + 2] & 0xfe); + } + + return false; +} + +static ManagedEVPPKey GetPublicOrPrivateKeyFromJs( + const FunctionCallbackInfo& args, + unsigned int* offset, + bool allow_key_object, + bool allow_certificate) { + if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) { + Environment* env = Environment::GetCurrent(args); + ByteSource data = ByteSource::FromStringOrBuffer(env, args[(*offset)++]); + NonCopyableMaybe config_ = + GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput); + if (config_.IsEmpty()) + return ManagedEVPPKey(); + PrivateKeyEncodingConfig config = config_.Release(); + EVPKeyPointer pkey; + if (config.format_ == kKeyFormatPEM) { + // For PEM, we can easily determine whether it is a public or private key + // by looking for the respective PEM tags. + ParsePublicKeyResult ret = ParsePublicKeyPEM(&pkey, data.get(), + data.size(), + allow_certificate); + if (ret == ParsePublicKeyResult::kParsePublicNotRecognized) { + pkey = ParsePrivateKey(config, data.get(), data.size()); + } + } else { + // For DER, the type determines how to parse it. SPKI, PKCS#8 and SEC1 are + // easy, but PKCS#1 can be a public key or a private key. + bool is_public; + switch (config.type_.ToChecked()) { + case kKeyEncodingPKCS1: + is_public = !IsRSAPrivateKey( + reinterpret_cast(data.get()), data.size()); + break; + case kKeyEncodingSPKI: + is_public = true; + break; + case kKeyEncodingPKCS8: + case kKeyEncodingSEC1: + is_public = false; + break; + default: + CHECK(!"Invalid key encoding type"); + } + + if (is_public) { + ParsePublicKey(&pkey, config, data.get(), data.size(), + allow_certificate); + } else { + pkey = ParsePrivateKey(config, data.get(), data.size()); + } + } + if (!pkey) + ThrowCryptoError(env, ERR_get_error(), "Failed to read asymmetric key"); + return ManagedEVPPKey(pkey.release()); + } else { + CHECK(args[*offset]->IsObject() && allow_key_object); + KeyObject* key = Unwrap(args[*offset].As()); + CHECK(key); + CHECK_NE(key->GetKeyType(), kKeyTypeSecret); + (*offset) += 4; + return key->GetAsymmetricKey(); + } +} + +static MaybeLocal WritePrivateKey( + Environment* env, + EVP_PKEY* pkey, + const PrivateKeyEncodingConfig& config) { + BIOPointer bio(BIO_new(BIO_s_mem())); + CHECK(bio); + + bool err; + + if (config.type_.ToChecked() == kKeyEncodingPKCS1) { + // PKCS#1 is only permitted 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. + const char* pass = config.passphrase_.get(); + err = PEM_write_bio_RSAPrivateKey( + bio.get(), rsa.get(), + config.cipher_, + reinterpret_cast(const_cast(pass)), + config.passphrase_.size(), + nullptr, nullptr) != 1; + } else { + // Encode PKCS#1 as DER. This does not permit encryption. + CHECK_EQ(config.format_, kKeyFormatDER); + CHECK_NULL(config.cipher_); + err = i2d_RSAPrivateKey_bio(bio.get(), rsa.get()) != 1; + } + } else if (config.type_.ToChecked() == kKeyEncodingPKCS8) { + if (config.format_ == kKeyFormatPEM) { + // Encode PKCS#8 as PEM. + err = PEM_write_bio_PKCS8PrivateKey( + bio.get(), pkey, + config.cipher_, + const_cast(config.passphrase_.get()), + config.passphrase_.size(), + nullptr, nullptr) != 1; + } else { + // Encode PKCS#8 as DER. + CHECK_EQ(config.format_, kKeyFormatDER); + err = i2d_PKCS8PrivateKey_bio( + bio.get(), pkey, + config.cipher_, + const_cast(config.passphrase_.get()), + config.passphrase_.size(), + nullptr, nullptr) != 1; + } + } else { + CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSEC1); + + // SEC1 is only permitted for EC keys. + CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_EC); + + ECKeyPointer ec_key(EVP_PKEY_get1_EC_KEY(pkey)); + if (config.format_ == kKeyFormatPEM) { + // Encode SEC1 as PEM. + const char* pass = config.passphrase_.get(); + err = PEM_write_bio_ECPrivateKey( + bio.get(), ec_key.get(), + config.cipher_, + reinterpret_cast(const_cast(pass)), + config.passphrase_.size(), + nullptr, nullptr) != 1; + } else { + // Encode SEC1 as DER. This does not permit encryption. + CHECK_EQ(config.format_, kKeyFormatDER); + CHECK_NULL(config.cipher_); + err = i2d_ECPrivateKey_bio(bio.get(), ec_key.get()) != 1; + } + } + + if (err) { + ThrowCryptoError(env, ERR_get_error(), "Failed to encode private key"); + return MaybeLocal(); + } + return BIOToStringOrBuffer(env, bio.get(), config.format_); +} + +ManagedEVPPKey::ManagedEVPPKey() : pkey_(nullptr) {} + +ManagedEVPPKey::ManagedEVPPKey(EVP_PKEY* pkey) : pkey_(pkey) {} + +ManagedEVPPKey::ManagedEVPPKey(const ManagedEVPPKey& key) : pkey_(nullptr) { + *this = key; +} + +ManagedEVPPKey::ManagedEVPPKey(ManagedEVPPKey&& key) { + *this = key; +} + +ManagedEVPPKey::~ManagedEVPPKey() { + EVP_PKEY_free(pkey_); +} + +ManagedEVPPKey& ManagedEVPPKey::operator=(const ManagedEVPPKey& key) { + EVP_PKEY_free(pkey_); + pkey_ = key.pkey_; + EVP_PKEY_up_ref(pkey_); + return *this; +} + +ManagedEVPPKey& ManagedEVPPKey::operator=(ManagedEVPPKey&& key) { + EVP_PKEY_free(pkey_); + pkey_ = key.pkey_; + key.pkey_ = nullptr; + return *this; +} + +ManagedEVPPKey::operator bool() const { + return pkey_ != nullptr; +} + +EVP_PKEY* ManagedEVPPKey::get() const { + return pkey_; +} + +Local KeyObject::Initialize(Environment* env, Local target) { + Local t = env->NewFunctionTemplate(New); + t->InstanceTemplate()->SetInternalFieldCount(1); + + env->SetProtoMethod(t, "init", Init); + env->SetProtoMethodNoSideEffect(t, "getSymmetricKeySize", + GetSymmetricKeySize); + env->SetProtoMethodNoSideEffect(t, "getAsymmetricKeyType", + GetAsymmetricKeyType); + env->SetProtoMethod(t, "export", Export); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObject"), + t->GetFunction(env->context()).ToLocalChecked()); + + return t->GetFunction(); +} + +Local KeyObject::Create(Environment* env, + KeyType key_type, + const ManagedEVPPKey& pkey) { + CHECK_NE(key_type, kKeyTypeSecret); + Local type = Integer::New(env->isolate(), key_type); + Local obj = + env->crypto_key_object_constructor()->NewInstance(env->context(), + 1, &type) + .ToLocalChecked(); + KeyObject* key = Unwrap(obj); + CHECK(key); + if (key_type == kKeyTypePublic) + key->InitPublic(pkey); + else + key->InitPrivate(pkey); + return obj; +} + +ManagedEVPPKey KeyObject::GetAsymmetricKey() const { + CHECK_NE(key_type_, kKeyTypeSecret); + return this->asymmetric_key_; +} + +const char* KeyObject::GetSymmetricKey() const { + CHECK_EQ(key_type_, kKeyTypeSecret); + return this->symmetric_key_.get(); +} + +size_t KeyObject::GetSymmetricKeySize() const { + CHECK_EQ(key_type_, kKeyTypeSecret); + return this->symmetric_key_len_; +} + +void KeyObject::New(const FunctionCallbackInfo& args) { + CHECK(args.IsConstructCall()); + CHECK(args[0]->IsInt32()); + KeyType key_type = static_cast(args[0].As()->Value()); + Environment* env = Environment::GetCurrent(args); + new KeyObject(env, args.This(), key_type); +} + +KeyType KeyObject::GetKeyType() const { + return this->key_type_; +} + +void KeyObject::Init(const FunctionCallbackInfo& args) { + KeyObject* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder()); + + unsigned int offset; + ManagedEVPPKey pkey; + + switch (key->key_type_) { + case kKeyTypeSecret: + CHECK_EQ(args.Length(), 1); + key->InitSecret(Buffer::Data(args[0]), Buffer::Length(args[0])); + break; + case kKeyTypePublic: + CHECK_EQ(args.Length(), 3); + + offset = 0; + pkey = GetPublicKeyFromJs(args, &offset, false, false); + if (!pkey) + return; + key->InitPublic(pkey); + break; + case kKeyTypePrivate: + CHECK_EQ(args.Length(), 4); + + offset = 0; + pkey = GetPrivateKeyFromJs(args, &offset, false); + if (!pkey) + return; + key->InitPrivate(pkey); + break; + default: + CHECK(false); + } +} + +void KeyObject::InitSecret(const char* key, size_t key_len) { + CHECK_EQ(this->key_type_, kKeyTypeSecret); + + char* mem = MallocOpenSSL(key_len); + memcpy(mem, key, key_len); + this->symmetric_key_ = std::unique_ptr>(mem, + [key_len](char* p) { + OPENSSL_clear_free(p, key_len); + }); + this->symmetric_key_len_ = key_len; +} + +void KeyObject::InitPublic(const ManagedEVPPKey& pkey) { + CHECK_EQ(this->key_type_, kKeyTypePublic); + CHECK(pkey); + this->asymmetric_key_ = pkey; +} + +void KeyObject::InitPrivate(const ManagedEVPPKey& pkey) { + CHECK_EQ(this->key_type_, kKeyTypePrivate); + CHECK(pkey); + this->asymmetric_key_ = pkey; +} + +Local KeyObject::GetAsymmetricKeyType() const { + CHECK_NE(this->key_type_, kKeyTypeSecret); + switch (EVP_PKEY_id(this->asymmetric_key_.get())) { + case EVP_PKEY_RSA: + return env()->crypto_rsa_string(); + case EVP_PKEY_DSA: + return env()->crypto_dsa_string(); + case EVP_PKEY_EC: + return env()->crypto_ec_string(); + default: + CHECK(false); + } +} + +void KeyObject::GetAsymmetricKeyType(const FunctionCallbackInfo& args) { + KeyObject* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder()); + + args.GetReturnValue().Set(key->GetAsymmetricKeyType()); +} + +void KeyObject::GetSymmetricKeySize(const FunctionCallbackInfo& args) { + KeyObject* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder()); + args.GetReturnValue().Set(static_cast(key->GetSymmetricKeySize())); +} + +void KeyObject::Export(const v8::FunctionCallbackInfo& args) { + KeyObject* key; + ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder()); + + MaybeLocal result; + if (key->key_type_ == kKeyTypeSecret) { + result = key->ExportSecretKey(); + } else if (key->key_type_ == kKeyTypePublic) { + unsigned int offset = 0; + PublicKeyEncodingConfig config = + GetPublicKeyEncodingFromJs(args, &offset, kKeyContextExport); + CHECK_EQ(offset, static_cast(args.Length())); + result = key->ExportPublicKey(config); + } else { + CHECK_EQ(key->key_type_, kKeyTypePrivate); + unsigned int offset = 0; + NonCopyableMaybe config = + GetPrivateKeyEncodingFromJs(args, &offset, kKeyContextExport); + if (config.IsEmpty()) + return; + CHECK_EQ(offset, static_cast(args.Length())); + result = key->ExportPrivateKey(config.Release()); + } + + if (!result.IsEmpty()) + args.GetReturnValue().Set(result.ToLocalChecked()); +} + +Local KeyObject::ExportSecretKey() const { + return Buffer::Copy(env(), symmetric_key_.get(), symmetric_key_len_) + .ToLocalChecked(); +} + +MaybeLocal KeyObject::ExportPublicKey( + const PublicKeyEncodingConfig& config) const { + return WritePublicKey(env(), asymmetric_key_.get(), config); +} + +MaybeLocal KeyObject::ExportPrivateKey( + const PrivateKeyEncodingConfig& config) const { + return WritePrivateKey(env(), asymmetric_key_.get(), config); +} + + void CipherBase::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -2837,6 +3669,15 @@ void CipherBase::InitIv(const char* cipher_type, } +static ByteSource GetSecretKeyBytes(Environment* env, Local value) { + // A key can be passed as a string, buffer or KeyObject with type 'secret'. + // If it is a string, we need to convert it to a buffer. We are not doing that + // in JS to avoid creating an unprotected copy on the heap. + return value->IsString() || Buffer::HasInstance(value) ? + ByteSource::FromStringOrBuffer(env, value) : + ByteSource::FromSymmetricKeyObject(value); +} + void CipherBase::InitIv(const FunctionCallbackInfo& args) { CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); @@ -2845,9 +3686,8 @@ void CipherBase::InitIv(const FunctionCallbackInfo& args) { CHECK_GE(args.Length(), 4); const node::Utf8Value cipher_type(env->isolate(), args[0]); - ssize_t key_len = Buffer::Length(args[1]); - const unsigned char* key_buf = reinterpret_cast( - Buffer::Data(args[1])); + const ByteSource key = GetSecretKeyBytes(env, args[1]); + ssize_t iv_len; const unsigned char* iv_buf; if (args[2]->IsNull()) { @@ -2868,7 +3708,12 @@ void CipherBase::InitIv(const FunctionCallbackInfo& args) { auth_tag_len = kNoAuthTagLength; } - cipher->InitIv(*cipher_type, key_buf, key_len, iv_buf, iv_len, auth_tag_len); + cipher->InitIv(*cipher_type, + reinterpret_cast(key.get()), + key.size(), + iv_buf, + iv_len, + auth_tag_len); } @@ -3324,9 +4169,8 @@ void Hmac::HmacInit(const FunctionCallbackInfo& args) { Environment* env = hmac->env(); const node::Utf8Value hash_type(env->isolate(), args[0]); - const char* buffer_data = Buffer::Data(args[1]); - size_t buffer_length = Buffer::Length(args[1]); - hmac->HmacInit(*hash_type, buffer_data, buffer_length); + ByteSource key = GetSecretKeyBytes(env, args[1]); + hmac->HmacInit(*hash_type, key.get(), key.size()); } @@ -3575,7 +4419,7 @@ void SignBase::CheckThrow(SignBase::Error error) { } } -static bool ApplyRSAOptions(const EVPKeyPointer& pkey, +static bool ApplyRSAOptions(const ManagedEVPPKey& pkey, EVP_PKEY_CTX* pkctx, int padding, int salt_len) { @@ -3637,7 +4481,7 @@ void Sign::SignUpdate(const FunctionCallbackInfo& args) { } static MallocedBuffer Node_SignFinal(EVPMDPointer&& mdctx, - const EVPKeyPointer& pkey, + const ManagedEVPPKey& pkey, int padding, int pss_salt_len) { unsigned char m[EVP_MAX_MD_SIZE]; @@ -3666,9 +4510,7 @@ static MallocedBuffer Node_SignFinal(EVPMDPointer&& mdctx, } Sign::SignResult Sign::SignFinal( - const char* key_pem, - int key_pem_len, - const char* passphrase, + const ManagedEVPPKey& pkey, int padding, int salt_len) { if (!mdctx_) @@ -3676,21 +4518,6 @@ Sign::SignResult Sign::SignFinal( EVPMDPointer mdctx = std::move(mdctx_); - BIOPointer bp(BIO_new_mem_buf(const_cast(key_pem), key_pem_len)); - if (!bp) - return SignResult(kSignPrivateKey); - - EVPKeyPointer pkey(PEM_read_bio_PrivateKey(bp.get(), - nullptr, - PasswordCallback, - const_cast(passphrase))); - - // Errors might be injected into OpenSSL's error stack - // without `pkey` being set to nullptr; - // cf. the test of `test_bad_rsa_privkey.pem` for an example. - if (!pkey || 0 != ERR_peek_error()) - return SignResult(kSignPrivateKey); - #ifdef NODE_FIPS_MODE /* Validate DSA2 parameters from FIPS 186-4 */ if (FIPS_mode() && EVP_PKEY_DSA == pkey->type) { @@ -3726,25 +4553,21 @@ void Sign::SignFinal(const FunctionCallbackInfo& args) { Sign* sign; ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder()); - unsigned int len = args.Length(); - - node::Utf8Value passphrase(env->isolate(), args[1]); - - size_t buf_len = Buffer::Length(args[0]); - char* buf = Buffer::Data(args[0]); + unsigned int offset = 0; + ManagedEVPPKey key = GetPrivateKeyFromJs(args, &offset, true); + if (!key) + return; - CHECK(args[2]->IsInt32()); - int padding = args[2].As()->Value(); + CHECK(args[offset]->IsInt32()); + int padding = args[offset].As()->Value(); - CHECK(args[3]->IsInt32()); - int salt_len = args[3].As()->Value(); + CHECK(args[offset + 1]->IsInt32()); + int salt_len = args[offset + 1].As()->Value(); ClearErrorOnReturn clear_error_on_return; SignResult ret = sign->SignFinal( - buf, - buf_len, - len >= 2 && !args[1]->IsNull() ? *passphrase : nullptr, + key, padding, salt_len); @@ -3760,72 +4583,6 @@ void Sign::SignFinal(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(rc); } -enum 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 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 ? kParsePublicOk : kParsePublicFailed; -} - -static ParsePublicKeyResult ParsePublicKey(EVPKeyPointer* pkey, - const char* key_pem, - int key_pem_len) { - BIOPointer bp(BIO_new_mem_buf(const_cast(key_pem), key_pem_len)); - if (!bp) - return 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 != 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 != kParsePublicNotRecognized) - 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; - }); -} - void Verify::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -3869,8 +4626,7 @@ void Verify::VerifyUpdate(const FunctionCallbackInfo& args) { } -SignBase::Error Verify::VerifyFinal(const char* key_pem, - int key_pem_len, +SignBase::Error Verify::VerifyFinal(const ManagedEVPPKey& pkey, const char* sig, int siglen, int padding, @@ -3879,15 +4635,11 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem, if (!mdctx_) return kSignNotInitialised; - EVPKeyPointer pkey; unsigned char m[EVP_MAX_MD_SIZE]; unsigned int m_len; *verify_result = false; EVPMDPointer mdctx = std::move(mdctx_); - if (ParsePublicKey(&pkey, key_pem, key_pem_len) != kParsePublicOk) - return kSignPublicKey; - if (!EVP_DigestFinal_ex(mdctx.get(), m, &m_len)) return kSignPublicKey; @@ -3915,20 +4667,20 @@ void Verify::VerifyFinal(const FunctionCallbackInfo& args) { Verify* verify; ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder()); - char* kbuf = Buffer::Data(args[0]); - ssize_t klen = Buffer::Length(args[0]); + unsigned int offset = 0; + ManagedEVPPKey pkey = GetPublicKeyFromJs(args, &offset, true, true); - char* hbuf = Buffer::Data(args[1]); - ssize_t hlen = Buffer::Length(args[1]); + char* hbuf = Buffer::Data(args[offset]); + ssize_t hlen = Buffer::Length(args[offset]); - CHECK(args[2]->IsInt32()); - int padding = args[2].As()->Value(); + CHECK(args[offset + 1]->IsInt32()); + int padding = args[offset + 1].As()->Value(); - CHECK(args[3]->IsInt32()); - int salt_len = args[3].As()->Value(); + CHECK(args[offset + 2]->IsInt32()); + int salt_len = args[offset + 2].As()->Value(); bool verify_result; - Error err = verify->VerifyFinal(kbuf, klen, hbuf, hlen, padding, salt_len, + Error err = verify->VerifyFinal(pkey, hbuf, hlen, padding, salt_len, &verify_result); if (err != kSignOk) return verify->CheckThrow(err); @@ -3939,36 +4691,12 @@ void Verify::VerifyFinal(const FunctionCallbackInfo& args) { template -bool PublicKeyCipher::Cipher(const char* key_pem, - int key_pem_len, - const char* passphrase, +bool PublicKeyCipher::Cipher(const ManagedEVPPKey& pkey, int padding, const unsigned char* data, int len, unsigned char** out, size_t* out_len) { - EVPKeyPointer pkey; - - // Check if this is a PKCS#8 or RSA public key before trying as X.509 and - // private key. - if (operation == kPublic) { - ParsePublicKeyResult pkeyres = ParsePublicKey(&pkey, key_pem, key_pem_len); - if (pkeyres == kParsePublicFailed) - return false; - } - if (!pkey) { - // Private key fallback. - BIOPointer bp(BIO_new_mem_buf(const_cast(key_pem), key_pem_len)); - if (!bp) - return false; - pkey.reset(PEM_read_bio_PrivateKey(bp.get(), - nullptr, - PasswordCallback, - const_cast(passphrase))); - if (!pkey) - return false; - } - EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); if (!ctx) return false; @@ -3995,18 +4723,17 @@ template & args) { Environment* env = Environment::GetCurrent(args); - THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Key"); - char* kbuf = Buffer::Data(args[0]); - ssize_t klen = Buffer::Length(args[0]); + unsigned int offset = 0; + ManagedEVPPKey pkey = GetPublicOrPrivateKeyFromJs(args, &offset, true, true); + if (!pkey) + return; - THROW_AND_RETURN_IF_NOT_BUFFER(env, args[1], "Data"); - char* buf = Buffer::Data(args[1]); - ssize_t len = Buffer::Length(args[1]); + THROW_AND_RETURN_IF_NOT_BUFFER(env, args[offset], "Data"); + char* buf = Buffer::Data(args[offset]); + ssize_t len = Buffer::Length(args[offset]); uint32_t padding; - if (!args[2]->Uint32Value(env->context()).To(&padding)) return; - - String::Utf8Value passphrase(args.GetIsolate(), args[3]); + if (!args[offset + 1]->Uint32Value(env->context()).To(&padding)) return; unsigned char* out_value = nullptr; size_t out_len = 0; @@ -4014,9 +4741,7 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo& args) { ClearErrorOnReturn clear_error_on_return; bool r = Cipher( - kbuf, - klen, - args.Length() >= 4 && !args[3]->IsNull() ? *passphrase : nullptr, + pkey, padding, reinterpret_cast(buf), len, @@ -5021,46 +5746,17 @@ class ECKeyPairGenerationConfig : public KeyPairGenerationConfig { const int param_encoding_; }; -enum PKEncodingType { - // RSAPublicKey / RSAPrivateKey according to PKCS#1. - PK_ENCODING_PKCS1, - // PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8. - PK_ENCODING_PKCS8, - // SubjectPublicKeyInfo according to X.509. - PK_ENCODING_SPKI, - // ECPrivateKey according to SEC1. - PK_ENCODING_SEC1 -}; - -enum PKFormatType { - PK_FORMAT_DER, - PK_FORMAT_PEM -}; - -struct KeyPairEncodingConfig { - PKEncodingType type_; - PKFormatType format_; -}; - -typedef KeyPairEncodingConfig PublicKeyEncodingConfig; - -struct PrivateKeyEncodingConfig : public KeyPairEncodingConfig { - const EVP_CIPHER* cipher_; - // This char* will be passed to OPENSSL_clear_free. - std::shared_ptr passphrase_; - unsigned int passphrase_length_; -}; - class GenerateKeyPairJob : public CryptoJob { public: GenerateKeyPairJob(Environment* env, std::unique_ptr config, PublicKeyEncodingConfig public_key_encoding, - PrivateKeyEncodingConfig private_key_encoding) + PrivateKeyEncodingConfig&& private_key_encoding) : CryptoJob(env), config_(std::move(config)), public_key_encoding_(public_key_encoding), - private_key_encoding_(private_key_encoding), + private_key_encoding_(std::forward( + private_key_encoding)), pkey_(nullptr) {} inline void DoThreadPoolWork() override { @@ -5089,7 +5785,7 @@ class GenerateKeyPairJob : public CryptoJob { EVP_PKEY* pkey = nullptr; if (EVP_PKEY_keygen(ctx.get(), &pkey) != 1) return false; - pkey_.reset(pkey); + pkey_ = ManagedEVPPKey(pkey); return true; } @@ -5116,197 +5812,59 @@ class GenerateKeyPairJob : public CryptoJob { } inline bool EncodeKeys(Local* pubkey, Local* privkey) { - EVP_PKEY* pkey = pkey_.get(); - BIOPointer bio(BIO_new(BIO_s_mem())); - CHECK(bio); - // Encode the public key. - if (public_key_encoding_.type_ == PK_ENCODING_PKCS1) { - // 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 (public_key_encoding_.format_ == PK_FORMAT_PEM) { - // Encode PKCS#1 as PEM. - if (PEM_write_bio_RSAPublicKey(bio.get(), rsa.get()) != 1) - return false; - } else { - // Encode PKCS#1 as DER. - CHECK_EQ(public_key_encoding_.format_, PK_FORMAT_DER); - if (i2d_RSAPublicKey_bio(bio.get(), rsa.get()) != 1) - return false; - } + if (public_key_encoding_.output_key_object_) { + // Note that this has the downside of containing sensitive data of the + // private key. + *pubkey = KeyObject::Create(env, kKeyTypePublic, pkey_); } else { - CHECK_EQ(public_key_encoding_.type_, PK_ENCODING_SPKI); - if (public_key_encoding_.format_ == PK_FORMAT_PEM) { - // Encode SPKI as PEM. - if (PEM_write_bio_PUBKEY(bio.get(), pkey) != 1) - return false; - } else { - // Encode SPKI as DER. - CHECK_EQ(public_key_encoding_.format_, PK_FORMAT_DER); - if (i2d_PUBKEY_bio(bio.get(), pkey) != 1) - return false; - } + MaybeLocal maybe_pubkey = + WritePublicKey(env, pkey_.get(), public_key_encoding_); + if (maybe_pubkey.IsEmpty()) + return false; + *pubkey = maybe_pubkey.ToLocalChecked(); } - // Convert the contents of the BIO to a JavaScript object. - BIOToStringOrBuffer(bio.get(), public_key_encoding_.format_, pubkey); - USE(BIO_reset(bio.get())); - - // Now do the same for the private key (which is a bit more difficult). - if (private_key_encoding_.type_ == PK_ENCODING_PKCS1) { - // PKCS#1 is only permitted for RSA keys. - CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_RSA); - - RSAPointer rsa(EVP_PKEY_get1_RSA(pkey)); - if (private_key_encoding_.format_ == PK_FORMAT_PEM) { - // Encode PKCS#1 as PEM. - char* pass = private_key_encoding_.passphrase_.get(); - if (PEM_write_bio_RSAPrivateKey( - bio.get(), rsa.get(), - private_key_encoding_.cipher_, - reinterpret_cast(pass), - private_key_encoding_.passphrase_length_, - nullptr, nullptr) != 1) - return false; - } else { - // Encode PKCS#1 as DER. This does not permit encryption. - CHECK_EQ(private_key_encoding_.format_, PK_FORMAT_DER); - CHECK_NULL(private_key_encoding_.cipher_); - if (i2d_RSAPrivateKey_bio(bio.get(), rsa.get()) != 1) - return false; - } - } else if (private_key_encoding_.type_ == PK_ENCODING_PKCS8) { - if (private_key_encoding_.format_ == PK_FORMAT_PEM) { - // Encode PKCS#8 as PEM. - if (PEM_write_bio_PKCS8PrivateKey( - bio.get(), pkey, - private_key_encoding_.cipher_, - private_key_encoding_.passphrase_.get(), - private_key_encoding_.passphrase_length_, - nullptr, nullptr) != 1) - return false; - } else { - // Encode PKCS#8 as DER. - CHECK_EQ(private_key_encoding_.format_, PK_FORMAT_DER); - if (i2d_PKCS8PrivateKey_bio( - bio.get(), pkey, - private_key_encoding_.cipher_, - private_key_encoding_.passphrase_.get(), - private_key_encoding_.passphrase_length_, - nullptr, nullptr) != 1) - return false; - } + // Now do the same for the private key. + if (private_key_encoding_.output_key_object_) { + *privkey = KeyObject::Create(env, kKeyTypePrivate, pkey_); } else { - CHECK_EQ(private_key_encoding_.type_, PK_ENCODING_SEC1); - - // SEC1 is only permitted for EC keys. - CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_EC); - - ECKeyPointer ec_key(EVP_PKEY_get1_EC_KEY(pkey)); - if (private_key_encoding_.format_ == PK_FORMAT_PEM) { - // Encode SEC1 as PEM. - char* pass = private_key_encoding_.passphrase_.get(); - if (PEM_write_bio_ECPrivateKey( - bio.get(), ec_key.get(), - private_key_encoding_.cipher_, - reinterpret_cast(pass), - private_key_encoding_.passphrase_length_, - nullptr, nullptr) != 1) - return false; - } else { - // Encode SEC1 as DER. This does not permit encryption. - CHECK_EQ(private_key_encoding_.format_, PK_FORMAT_DER); - CHECK_NULL(private_key_encoding_.cipher_); - if (i2d_ECPrivateKey_bio(bio.get(), ec_key.get()) != 1) - return false; - } + MaybeLocal maybe_privkey = + WritePrivateKey(env, pkey_.get(), private_key_encoding_); + if (maybe_privkey.IsEmpty()) + return false; + *privkey = maybe_privkey.ToLocalChecked(); } - BIOToStringOrBuffer(bio.get(), private_key_encoding_.format_, privkey); return true; } - inline void BIOToStringOrBuffer(BIO* bio, PKFormatType format, - Local* out) const { - BUF_MEM* bptr; - BIO_get_mem_ptr(bio, &bptr); - if (format == PK_FORMAT_PEM) { - // PEM is an ASCII format, so we will return it as a string. - *out = String::NewFromUtf8(env->isolate(), bptr->data, - NewStringType::kNormal, - bptr->length).ToLocalChecked(); - } else { - CHECK_EQ(format, PK_FORMAT_DER); - // DER is binary, return it as a buffer. - *out = Buffer::Copy(env, bptr->data, bptr->length).ToLocalChecked(); - } - } - private: CryptoErrorVector errors_; std::unique_ptr config_; PublicKeyEncodingConfig public_key_encoding_; PrivateKeyEncodingConfig private_key_encoding_; - EVPKeyPointer pkey_; + ManagedEVPPKey pkey_; }; void GenerateKeyPair(const FunctionCallbackInfo& args, - unsigned int n_opts, + unsigned int offset, std::unique_ptr config) { Environment* env = Environment::GetCurrent(args); - PublicKeyEncodingConfig public_key_encoding; - PrivateKeyEncodingConfig private_key_encoding; - - // Public key encoding: type (int) + pem (bool) - CHECK(args[n_opts]->IsInt32()); - public_key_encoding.type_ = static_cast( - args[n_opts].As()->Value()); - CHECK(args[n_opts + 1]->IsInt32()); - public_key_encoding.format_ = static_cast( - args[n_opts + 1].As()->Value()); - - // Private key encoding: type (int) + pem (bool) + cipher (optional, string) + - // passphrase (optional, string) - CHECK(args[n_opts + 2]->IsInt32()); - private_key_encoding.type_ = static_cast( - args[n_opts + 2].As()->Value()); - CHECK(args[n_opts + 1]->IsInt32()); - private_key_encoding.format_ = static_cast( - args[n_opts + 3].As()->Value()); - if (args[n_opts + 4]->IsString()) { - String::Utf8Value cipher_name(env->isolate(), - args[n_opts + 4].As()); - private_key_encoding.cipher_ = EVP_get_cipherbyname(*cipher_name); - if (private_key_encoding.cipher_ == nullptr) - return env->ThrowError("Unknown cipher"); - - // We need to take ownership of the string and want to avoid creating an - // unnecessary copy in memory, that's why we are not using String::Utf8Value - // here. - CHECK(args[n_opts + 5]->IsString()); - Local passphrase = args[n_opts + 5].As(); - int len = passphrase->Utf8Length(env->isolate()); - private_key_encoding.passphrase_length_ = len; - void* mem = OPENSSL_malloc(private_key_encoding.passphrase_length_ + 1); - CHECK_NOT_NULL(mem); - private_key_encoding.passphrase_.reset(static_cast(mem), - [len](char* p) { - OPENSSL_clear_free(p, len); - }); - passphrase->WriteUtf8(env->isolate(), - private_key_encoding.passphrase_.get()); - } else { - CHECK(args[n_opts + 5]->IsNullOrUndefined()); - private_key_encoding.cipher_ = nullptr; - private_key_encoding.passphrase_length_ = 0; - } + + PublicKeyEncodingConfig public_key_encoding = + GetPublicKeyEncodingFromJs(args, &offset, kKeyContextGenerate); + NonCopyableMaybe private_key_encoding = + GetPrivateKeyEncodingFromJs(args, &offset, kKeyContextGenerate); + + if (private_key_encoding.IsEmpty()) + return; std::unique_ptr job( new GenerateKeyPairJob(env, std::move(config), public_key_encoding, - private_key_encoding)); - if (args[n_opts + 6]->IsObject()) - return GenerateKeyPairJob::Run(std::move(job), args[n_opts + 6]); + private_key_encoding.Release())); + if (args[offset]->IsObject()) + return GenerateKeyPairJob::Run(std::move(job), args[offset]); env->PrintSyncTrace(); job->DoThreadPoolWork(); Local err, pubkey, privkey; @@ -5750,6 +6308,7 @@ void Initialize(Local target, Environment* env = Environment::GetCurrent(context); SecureContext::Initialize(env, target); + env->set_crypto_key_object_constructor(KeyObject::Initialize(env, target)); CipherBase::Initialize(env, target); DiffieHellman::Initialize(env, target); ECDH::Initialize(env, target); @@ -5778,12 +6337,15 @@ void Initialize(Local target, env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC); NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE); NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE); - NODE_DEFINE_CONSTANT(target, PK_ENCODING_PKCS1); - NODE_DEFINE_CONSTANT(target, PK_ENCODING_PKCS8); - NODE_DEFINE_CONSTANT(target, PK_ENCODING_SPKI); - NODE_DEFINE_CONSTANT(target, PK_ENCODING_SEC1); - NODE_DEFINE_CONSTANT(target, PK_FORMAT_DER); - NODE_DEFINE_CONSTANT(target, PK_FORMAT_PEM); + NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1); + NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8); + NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI); + NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1); + NODE_DEFINE_CONSTANT(target, kKeyFormatDER); + NODE_DEFINE_CONSTANT(target, kKeyFormatPEM); + NODE_DEFINE_CONSTANT(target, kKeyTypeSecret); + NODE_DEFINE_CONSTANT(target, kKeyTypePublic); + NODE_DEFINE_CONSTANT(target, kKeyTypePrivate); env->SetMethod(target, "randomBytes", RandomBytes); env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual); env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers); diff --git a/src/node_crypto.h b/src/node_crypto.h index 0ee45cf9ea2c02..ef8f2bce2e99f4 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -341,6 +341,163 @@ class SSLWrap { friend class SecureContext; }; +// A helper class representing a read-only byte array. When deallocated, its +// contents are zeroed. +class ByteSource { + public: + ByteSource() = default; + ByteSource(ByteSource&& other); + ~ByteSource(); + + ByteSource& operator=(ByteSource&& other); + + const char* get() const; + size_t size() const; + + static ByteSource FromStringOrBuffer(Environment* env, + v8::Local value); + + static ByteSource FromString(Environment* env, + v8::Local str, + bool ntc = false); + + static ByteSource FromBuffer(v8::Local buffer, + bool ntc = false); + + static ByteSource NullTerminatedCopy(Environment* env, + v8::Local value); + + static ByteSource FromSymmetricKeyObject(v8::Local handle); + + private: + const char* data_ = nullptr; + char* allocated_data_ = nullptr; + size_t size_ = 0; + + ByteSource(const char* data, char* allocated_data, size_t size); + + static ByteSource Allocated(char* data, size_t size); + static ByteSource Foreign(const char* data, size_t size); + + DISALLOW_COPY_AND_ASSIGN(ByteSource); +}; + +enum PKEncodingType { + // RSAPublicKey / RSAPrivateKey according to PKCS#1. + kKeyEncodingPKCS1, + // PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8. + kKeyEncodingPKCS8, + // SubjectPublicKeyInfo according to X.509. + kKeyEncodingSPKI, + // ECPrivateKey according to SEC1. + kKeyEncodingSEC1 +}; + +enum PKFormatType { + kKeyFormatDER, + kKeyFormatPEM +}; + +struct AsymmetricKeyEncodingConfig { + bool output_key_object_; + PKFormatType format_; + v8::Maybe type_ = v8::Nothing(); +}; + +typedef AsymmetricKeyEncodingConfig PublicKeyEncodingConfig; + +struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig { + const EVP_CIPHER* cipher_; + ByteSource passphrase_; +}; + +enum KeyType { + kKeyTypeSecret, + kKeyTypePublic, + kKeyTypePrivate +}; + +// This uses the built-in reference counter of OpenSSL to manage an EVP_PKEY +// which is slightly more efficient than using a shared pointer and easier to +// use. +class ManagedEVPPKey { + public: + ManagedEVPPKey(); + explicit ManagedEVPPKey(EVP_PKEY* pkey); + ManagedEVPPKey(const ManagedEVPPKey& key); + ManagedEVPPKey(ManagedEVPPKey&& key); + ~ManagedEVPPKey(); + + ManagedEVPPKey& operator=(const ManagedEVPPKey& key); + ManagedEVPPKey& operator=(ManagedEVPPKey&& key); + + operator bool() const; + EVP_PKEY* get() const; + + private: + EVP_PKEY* pkey_; +}; + +class KeyObject : public BaseObject { + public: + static v8::Local Initialize(Environment* env, + v8::Local target); + + static v8::Local Create(Environment* env, + KeyType type, + const ManagedEVPPKey& pkey); + + // TODO(tniessen): track the memory used by OpenSSL types + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(KeyObject) + SET_SELF_SIZE(KeyObject) + + KeyType GetKeyType() const; + + // These functions allow unprotected access to the raw key material and should + // only be used to implement cryptograohic operations requiring the key. + ManagedEVPPKey GetAsymmetricKey() const; + const char* GetSymmetricKey() const; + size_t GetSymmetricKeySize() const; + + protected: + static void New(const v8::FunctionCallbackInfo& args); + + static void Init(const v8::FunctionCallbackInfo& args); + void InitSecret(const char* key, size_t key_len); + void InitPublic(const ManagedEVPPKey& pkey); + void InitPrivate(const ManagedEVPPKey& pkey); + + static void GetAsymmetricKeyType( + const v8::FunctionCallbackInfo& args); + v8::Local GetAsymmetricKeyType() const; + + static void GetSymmetricKeySize( + const v8::FunctionCallbackInfo& args); + + static void Export(const v8::FunctionCallbackInfo& args); + v8::Local ExportSecretKey() const; + v8::MaybeLocal ExportPublicKey( + const PublicKeyEncodingConfig& config) const; + v8::MaybeLocal ExportPrivateKey( + const PrivateKeyEncodingConfig& config) const; + + KeyObject(Environment* env, + v8::Local wrap, + KeyType key_type) + : BaseObject(env, wrap), + key_type_(key_type), + symmetric_key_(nullptr, nullptr) { + MakeWeak(); + } + + private: + const KeyType key_type_; + std::unique_ptr> symmetric_key_; + unsigned int symmetric_key_len_; + ManagedEVPPKey asymmetric_key_; +}; + class CipherBase : public BaseObject { public: static void Initialize(Environment* env, v8::Local target); @@ -529,9 +686,7 @@ class Sign : public SignBase { }; SignResult SignFinal( - const char* key_pem, - int key_pem_len, - const char* passphrase, + const ManagedEVPPKey& pkey, int padding, int saltlen); @@ -550,8 +705,7 @@ class Verify : public SignBase { public: static void Initialize(Environment* env, v8::Local target); - Error VerifyFinal(const char* key_pem, - int key_pem_len, + Error VerifyFinal(const ManagedEVPPKey& key, const char* sig, int siglen, int padding, @@ -584,9 +738,7 @@ class PublicKeyCipher { template - static bool Cipher(const char* key_pem, - int key_pem_len, - const char* passphrase, + static bool Cipher(const ManagedEVPPKey& pkey, int padding, const unsigned char* data, int len, diff --git a/src/util.h b/src/util.h index 9a10904394de61..36a2ec9e3a6270 100644 --- a/src/util.h +++ b/src/util.h @@ -473,6 +473,29 @@ struct MallocedBuffer { MallocedBuffer& operator=(const MallocedBuffer&) = delete; }; +template +class NonCopyableMaybe { + public: + NonCopyableMaybe() : empty_(true) {} + explicit NonCopyableMaybe(T&& value) + : empty_(false), + value_(std::move(value)) {} + + bool IsEmpty() const { + return empty_; + } + + T&& Release() { + CHECK_EQ(empty_, false); + empty_ = true; + return std::move(value_); + } + + private: + bool empty_; + T value_; +}; + // Test whether some value can be called with (). template struct is_callable : std::is_function { }; diff --git a/test/parallel/test-crypto-cipheriv-decipheriv.js b/test/parallel/test-crypto-cipheriv-decipheriv.js index c0073abcfd4ee9..e4c7fced584c5f 100644 --- a/test/parallel/test-crypto-cipheriv-decipheriv.js +++ b/test/parallel/test-crypto-cipheriv-decipheriv.js @@ -101,8 +101,8 @@ function testCipher3(key, iv) { { code: 'ERR_INVALID_ARG_TYPE', type: TypeError, - message: 'The "key" argument must be one of type string, Buffer, ' + - 'TypedArray, or DataView. Received type object' + message: 'The "key" argument must be one of type Buffer, TypedArray, ' + + 'DataView, string, or KeyObject. Received type object' }); common.expectsError( @@ -138,8 +138,8 @@ function testCipher3(key, iv) { { code: 'ERR_INVALID_ARG_TYPE', type: TypeError, - message: 'The "key" argument must be one of type string, Buffer, ' + - 'TypedArray, or DataView. Received type object' + message: 'The "key" argument must be one of type Buffer, TypedArray, ' + + 'DataView, string, or KeyObject. Received type object' }); common.expectsError( diff --git a/test/parallel/test-crypto-hmac.js b/test/parallel/test-crypto-hmac.js index f21db29d36107f..9e0e364a4fa395 100644 --- a/test/parallel/test-crypto-hmac.js +++ b/test/parallel/test-crypto-hmac.js @@ -36,20 +36,37 @@ common.expectsError( { code: 'ERR_INVALID_ARG_TYPE', type: TypeError, - message: 'The "key" argument must be one of type string, TypedArray, or ' + - 'DataView. Received type object' + message: 'The "key" argument must be one of type Buffer, TypedArray, ' + + 'DataView, string, or KeyObject. Received type object' }); +function testHmac(algo, key, data, expected) { + // FIPS does not support MD5. + if (common.hasFipsCrypto && algo === 'md5') + return; + + if (!Array.isArray(data)) + data = [data]; + + // If the key is a Buffer, test Hmac with a key object as well. + const keyWrappers = [ + (key) => key, + ...(typeof key === 'string' ? [] : [crypto.createSecretKey]) + ]; + + for (const keyWrapper of keyWrappers) { + const hmac = crypto.createHmac(algo, keyWrapper(key)); + for (const chunk of data) + hmac.update(chunk); + const actual = hmac.digest('hex'); + assert.strictEqual(actual, expected); + } +} + { - // Test HMAC - const actual = crypto.createHmac('sha1', 'Node') - .update('some data') - .update('to hmac') - .digest('hex'); - const expected = '19fd6e1ba73d9ed2224dd5094a71babe85d9a892'; - assert.strictEqual(actual, - expected, - `Test HMAC: ${actual} must be ${expected}`); + // Test HMAC with multiple updates. + testHmac('sha1', 'Node', ['some data', 'to hmac'], + '19fd6e1ba73d9ed2224dd5094a71babe85d9a892'); } // Test HMAC (Wikipedia Test Cases) @@ -96,24 +113,11 @@ const wikipedia = [ }, ]; -for (let i = 0, l = wikipedia.length; i < l; i++) { - for (const hash in wikipedia[i].hmac) { - // FIPS does not support MD5. - if (common.hasFipsCrypto && hash === 'md5') - continue; - const expected = wikipedia[i].hmac[hash]; - const actual = crypto.createHmac(hash, wikipedia[i].key) - .update(wikipedia[i].data) - .digest('hex'); - assert.strictEqual( - actual, - expected, - `Test HMAC-${hash} wikipedia case ${i + 1}: ${actual} must be ${expected}` - ); - } +for (const { key, data, hmac } of wikipedia) { + for (const hash in hmac) + testHmac(hash, key, data, hmac[hash]); } - // Test HMAC-SHA-* (rfc 4231 Test Cases) const rfc4231 = [ { @@ -342,6 +346,10 @@ const rfc2202_md5 = [ hmac: '6f630fad67cda0ee1fb1f562db3aa53e' } ]; + +for (const { key, data, hmac } of rfc2202_md5) + testHmac('md5', key, data, hmac); + const rfc2202_sha1 = [ { key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), @@ -397,30 +405,8 @@ const rfc2202_sha1 = [ } ]; -if (!common.hasFipsCrypto) { - for (let i = 0, l = rfc2202_md5.length; i < l; i++) { - const actual = crypto.createHmac('md5', rfc2202_md5[i].key) - .update(rfc2202_md5[i].data) - .digest('hex'); - const expected = rfc2202_md5[i].hmac; - assert.strictEqual( - actual, - expected, - `Test HMAC-MD5 rfc 2202 case ${i + 1}: ${actual} must be ${expected}` - ); - } -} -for (let i = 0, l = rfc2202_sha1.length; i < l; i++) { - const actual = crypto.createHmac('sha1', rfc2202_sha1[i].key) - .update(rfc2202_sha1[i].data) - .digest('hex'); - const expected = rfc2202_sha1[i].hmac; - assert.strictEqual( - actual, - expected, - `Test HMAC-SHA1 rfc 2202 case ${i + 1}: ${actual} must be ${expected}` - ); -} +for (const { key, data, hmac } of rfc2202_sha1) + testHmac('sha1', key, data, hmac); common.expectsError( () => crypto.createHmac('sha256', 'w00t').digest('ucs2'), diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js new file mode 100644 index 00000000000000..dddbd5f2703d93 --- /dev/null +++ b/test/parallel/test-crypto-key-objects.js @@ -0,0 +1,107 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + createCipheriv, + createDecipheriv, + createSecretKey, + createPublicKey, + createPrivateKey, + randomBytes, + publicEncrypt, + privateDecrypt +} = require('crypto'); + +const fixtures = require('../common/fixtures'); + +const publicPem = fixtures.readSync('test_rsa_pubkey.pem', 'ascii'); +const privatePem = fixtures.readSync('test_rsa_privkey.pem', 'ascii'); + +{ + // Attempting to create an empty key should throw. + common.expectsError(() => { + createSecretKey(Buffer.alloc(0)); + }, { + type: RangeError, + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "key.byteLength" is out of range. ' + + 'It must be > 0. Received 0' + }); +} + +{ + const keybuf = randomBytes(32); + const key = createSecretKey(keybuf); + assert.strictEqual(key.type, 'secret'); + assert.strictEqual(key.symmetricKeySize, 32); + assert.strictEqual(key.asymmetricKeyType, undefined); + + const exportedKey = key.export(); + assert(keybuf.equals(exportedKey)); + + const plaintext = Buffer.from('Hello world', 'utf8'); + + const cipher = createCipheriv('aes-256-ecb', key, null); + const ciphertext = Buffer.concat([ + cipher.update(plaintext), cipher.final() + ]); + + const decipher = createDecipheriv('aes-256-ecb', key, null); + const deciphered = Buffer.concat([ + decipher.update(ciphertext), decipher.final() + ]); + + assert(plaintext.equals(deciphered)); +} + +{ + const publicKey = createPublicKey(publicPem); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + assert.strictEqual(publicKey.symmetricKeySize, undefined); + + const privateKey = createPrivateKey(privatePem); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + assert.strictEqual(privateKey.symmetricKeySize, undefined); + + const publicDER = publicKey.export({ + format: 'der', + type: 'pkcs1' + }); + + const privateDER = privateKey.export({ + format: 'der', + type: 'pkcs1' + }); + + assert(Buffer.isBuffer(publicDER)); + assert(Buffer.isBuffer(privateDER)); + + const plaintext = Buffer.from('Hello world', 'utf8'); + const ciphertexts = [ + publicEncrypt(publicKey, plaintext), + publicEncrypt({ key: publicKey }, plaintext), + // Test distinguishing PKCS#1 public and private keys based on the + // DER-encoded data only. + publicEncrypt({ format: 'der', type: 'pkcs1', key: publicDER }, plaintext), + publicEncrypt({ format: 'der', type: 'pkcs1', key: privateDER }, plaintext) + ]; + + const decryptionKeys = [ + privateKey, + { format: 'pem', key: privatePem }, + { format: 'der', type: 'pkcs1', key: privateDER } + ]; + + for (const ciphertext of ciphertexts) { + for (const key of decryptionKeys) { + const deciphered = privateDecrypt(key, ciphertext); + assert(plaintext.equals(deciphered)); + } + } +} diff --git a/test/parallel/test-crypto-keygen.js b/test/parallel/test-crypto-keygen.js index 241e4aa73ac684..43319c38599ebf 100644 --- a/test/parallel/test-crypto-keygen.js +++ b/test/parallel/test-crypto-keygen.js @@ -65,23 +65,6 @@ const pkcs8EncExp = getRegExpForPEM('ENCRYPTED PRIVATE KEY'); const sec1Exp = getRegExpForPEM('EC PRIVATE KEY'); const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); -// Since our own APIs only accept PEM, not DER, we need to convert DER to PEM -// for testing. -function convertDERToPEM(label, der) { - const base64 = der.toString('base64'); - const lines = []; - let i = 0; - while (i < base64.length) { - const n = Math.min(base64.length - i, 64); - lines.push(base64.substr(i, n)); - i += n; - } - const body = lines.join('\n'); - const r = `-----BEGIN ${label}-----\n${body}\n-----END ${label}-----\n`; - assert(getRegExpForPEM(label).test(r)); - return r; -} - { // To make the test faster, we will only test sync key generation once and // with a relatively small key. @@ -113,14 +96,16 @@ function convertDERToPEM(label, der) { } { + const publicKeyEncoding = { + type: 'pkcs1', + format: 'der' + }; + // Test async RSA key generation. generateKeyPair('rsa', { publicExponent: 0x10001, modulusLength: 512, - publicKeyEncoding: { - type: 'pkcs1', - format: 'der' - }, + publicKeyEncoding, privateKeyEncoding: { type: 'pkcs1', format: 'pem' @@ -128,16 +113,14 @@ function convertDERToPEM(label, der) { }, common.mustCall((err, publicKeyDER, privateKey) => { assert.ifError(err); - // The public key is encoded as DER (which is binary) instead of PEM. We - // will still need to convert it to PEM for testing. assert(Buffer.isBuffer(publicKeyDER)); - const publicKey = convertDERToPEM('RSA PUBLIC KEY', publicKeyDER); - assertApproximateSize(publicKey, 180); + assertApproximateSize(publicKeyDER, 74); assert.strictEqual(typeof privateKey, 'string'); assert(pkcs1PrivExp.test(privateKey)); assertApproximateSize(privateKey, 512); + const publicKey = { key: publicKeyDER, ...publicKeyEncoding }; testEncryptDecrypt(publicKey, privateKey); testSignVerify(publicKey, privateKey); })); @@ -146,10 +129,7 @@ function convertDERToPEM(label, der) { generateKeyPair('rsa', { publicExponent: 0x10001, modulusLength: 512, - publicKeyEncoding: { - type: 'pkcs1', - format: 'der' - }, + publicKeyEncoding, privateKeyEncoding: { type: 'pkcs1', format: 'pem', @@ -159,16 +139,14 @@ function convertDERToPEM(label, der) { }, common.mustCall((err, publicKeyDER, privateKey) => { assert.ifError(err); - // The public key is encoded as DER (which is binary) instead of PEM. We - // will still need to convert it to PEM for testing. assert(Buffer.isBuffer(publicKeyDER)); - const publicKey = convertDERToPEM('RSA PUBLIC KEY', publicKeyDER); - assertApproximateSize(publicKey, 180); + assertApproximateSize(publicKeyDER, 74); assert.strictEqual(typeof privateKey, 'string'); assert(pkcs1EncExp('AES-256-CBC').test(privateKey)); // Since the private key is encrypted, signing shouldn't work anymore. + const publicKey = { key: publicKeyDER, ...publicKeyEncoding }; assert.throws(() => { testSignVerify(publicKey, privateKey); }, /bad decrypt|asn1 encoding routines/); @@ -180,6 +158,11 @@ function convertDERToPEM(label, der) { } { + const privateKeyEncoding = { + type: 'pkcs8', + format: 'der' + }; + // Test async DSA key generation. generateKeyPair('dsa', { modulusLength: 512, @@ -189,10 +172,9 @@ function convertDERToPEM(label, der) { format: 'pem' }, privateKeyEncoding: { - type: 'pkcs8', - format: 'der', cipher: 'aes-128-cbc', - passphrase: 'secret' + passphrase: 'secret', + ...privateKeyEncoding } }, common.mustCall((err, publicKey, privateKeyDER) => { assert.ifError(err); @@ -201,19 +183,22 @@ function convertDERToPEM(label, der) { assert(spkiExp.test(publicKey)); // The private key is DER-encoded. assert(Buffer.isBuffer(privateKeyDER)); - const privateKey = convertDERToPEM('ENCRYPTED PRIVATE KEY', privateKeyDER); assertApproximateSize(publicKey, 440); - assertApproximateSize(privateKey, 512); + assertApproximateSize(privateKeyDER, 336); // Since the private key is encrypted, signing shouldn't work anymore. assert.throws(() => { - testSignVerify(publicKey, privateKey); + testSignVerify(publicKey, { + key: privateKeyDER, + ...privateKeyEncoding + }); }, /bad decrypt|asn1 encoding routines/); // Signing should work with the correct password. testSignVerify(publicKey, { - key: privateKey, + key: privateKeyDER, + ...privateKeyEncoding, passphrase: 'secret' }); })); @@ -369,8 +354,52 @@ function convertDERToPEM(label, der) { } { - // Missing / invalid publicKeyEncoding. - for (const enc of [undefined, null, 0, 'a', true]) { + // If no publicKeyEncoding is specified, a key object should be returned. + generateKeyPair('rsa', { + modulusLength: 1024, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }, common.mustCall((err, publicKey, privateKey) => { + assert.ifError(err); + + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa'); + + // The private key should still be a string. + assert.strictEqual(typeof privateKey, 'string'); + + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); + + // If no privateKeyEncoding is specified, a key object should be returned. + generateKeyPair('rsa', { + modulusLength: 1024, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } + }, common.mustCall((err, publicKey, privateKey) => { + assert.ifError(err); + + // The public key should still be a string. + assert.strictEqual(typeof publicKey, 'string'); + + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa'); + + testEncryptDecrypt(publicKey, privateKey); + testSignVerify(publicKey, privateKey); + })); +} + +{ + // Invalid publicKeyEncoding. + for (const enc of [0, 'a', true]) { common.expectsError(() => generateKeyPairSync('rsa', { modulusLength: 4096, publicKeyEncoding: enc, @@ -425,8 +454,8 @@ function convertDERToPEM(label, der) { }); } - // Missing / invalid privateKeyEncoding. - for (const enc of [undefined, null, 0, 'a', true]) { + // Invalid privateKeyEncoding. + for (const enc of [0, 'a', true]) { common.expectsError(() => generateKeyPairSync('rsa', { modulusLength: 4096, publicKeyEncoding: { diff --git a/test/parallel/test-crypto-rsa-dsa.js b/test/parallel/test-crypto-rsa-dsa.js index 744dc5657b089d..348fd15b74d495 100644 --- a/test/parallel/test-crypto-rsa-dsa.js +++ b/test/parallel/test-crypto-rsa-dsa.js @@ -100,7 +100,7 @@ const decryptError = assert.throws(() => { crypto.publicDecrypt({ key: rsaKeyPemEncrypted, - passphrase: [].concat.apply([], Buffer.from('password')) + passphrase: Buffer.from('wrong') }, encryptedBuffer); }, decryptError); } diff --git a/test/parallel/test-crypto-sign-verify.js b/test/parallel/test-crypto-sign-verify.js index eaf555ff57f9d8..0499b3091ca5a9 100644 --- a/test/parallel/test-crypto-sign-verify.js +++ b/test/parallel/test-crypto-sign-verify.js @@ -352,7 +352,7 @@ common.expectsError( code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError [ERR_INVALID_ARG_TYPE]', message: 'The "key" argument must be one of type string, Buffer, ' + - `TypedArray, or DataView. Received type ${type}` + `TypedArray, DataView, or KeyObject. Received type ${type}` }; assert.throws(() => sign.sign(input), errObj); diff --git a/tools/doc/type-parser.js b/tools/doc/type-parser.js index 527b44f969a77f..e3588856209e28 100644 --- a/tools/doc/type-parser.js +++ b/tools/doc/type-parser.js @@ -50,6 +50,7 @@ const customTypesMap = { 'ECDH': 'crypto.html#crypto_class_ecdh', 'Hash': 'crypto.html#crypto_class_hash', 'Hmac': 'crypto.html#crypto_class_hmac', + 'KeyObject': 'crypto.html#crypto_class_keyobject', 'Sign': 'crypto.html#crypto_class_sign', 'Verify': 'crypto.html#crypto_class_verify', 'crypto.constants': 'crypto.html#crypto_crypto_constants_1',