summaryrefslogtreecommitdiff
path: root/lib/internal
diff options
context:
space:
mode:
authorTobias Nießen <tniessen@tnie.de>2018-09-20 19:53:44 +0200
committerTobias Nießen <tniessen@tnie.de>2018-12-24 14:50:16 +0100
commit823d86c47ce15fba8875fcebd412593b02aab362 (patch)
treea023dac2cbda38b6e194c7324e501e4054715af5 /lib/internal
parent5570df407aae1e329955b1093e0e5b8bde270213 (diff)
downloadandroid-node-v8-823d86c47ce15fba8875fcebd412593b02aab362.tar.gz
android-node-v8-823d86c47ce15fba8875fcebd412593b02aab362.tar.bz2
android-node-v8-823d86c47ce15fba8875fcebd412593b02aab362.zip
crypto: add key object API
This commit makes multiple important changes: 1. A new key object API is introduced. The KeyObject class itself is not exposed to users, instead, several new APIs can be used to construct key objects: createSecretKey, createPrivateKey and createPublicKey. The new API also allows to convert between different key formats, and even though the API itself is not compatible to the WebCrypto standard in any way, it makes interoperability much simpler. 2. Key objects can be used instead of the raw key material in all relevant crypto APIs. 3. The handling of asymmetric keys has been unified and greatly improved. Node.js now fully supports both PEM-encoded and DER-encoded public and private keys. 4. Conversions between buffers and strings have been moved to native code for sensitive data such as symmetric keys due to security considerations such as zeroing temporary buffers. 5. For compatibility with older versions of the crypto API, this change allows to specify Buffers and strings as the "passphrase" option when reading or writing an encoded key. Note that this can result in unexpected behavior if the password contains a null byte. PR-URL: https://github.com/nodejs/node/pull/24234 Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
Diffstat (limited to 'lib/internal')
-rw-r--r--lib/internal/crypto/cipher.js33
-rw-r--r--lib/internal/crypto/hash.js9
-rw-r--r--lib/internal/crypto/keygen.js135
-rw-r--r--lib/internal/crypto/keys.js337
-rw-r--r--lib/internal/crypto/sig.js31
-rw-r--r--lib/internal/errors.js2
6 files changed, 428 insertions, 119 deletions
diff --git a/lib/internal/crypto/cipher.js b/lib/internal/crypto/cipher.js
index 1e5dc91c8d..0e8e5c4cf8 100644
--- a/lib/internal/crypto/cipher.js
+++ b/lib/internal/crypto/cipher.js
@@ -13,6 +13,11 @@ const {
const { validateString } = require('internal/validators');
const {
+ preparePrivateKey,
+ preparePublicOrPrivateKey,
+ prepareSecretKey
+} = require('internal/crypto/keys');
+const {
getDefaultEncoding,
kHandle,
legacyNativeHandle,
@@ -37,19 +42,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);
@@ -104,11 +115,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 f289d11cf8..713ded3d18 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 7222d301f0..7c0c411043 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 0000000000..ad82835080
--- /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 fa2d4998b6..32f7c37ec2 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');
function Sign(algorithm, options) {
@@ -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;
};
@@ -108,7 +109,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
@@ -116,12 +122,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 174281f8a2..8be692ef57 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);