Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: add key object API #24234

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
280 changes: 220 additions & 60 deletions doc/api/crypto.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions doc/api/errors.md
Expand Up @@ -763,6 +763,11 @@ The selected public or private key encoding is incompatible with other options.

An invalid [crypto digest algorithm][] was specified.

<a id="ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"></a>
### ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE

The given crypto key object's type is invalid for the attempted operation.

<a id="ERR_CRYPTO_INVALID_STATE"></a>
### ERR_CRYPTO_INVALID_STATE

Expand Down
8 changes: 8 additions & 0 deletions lib/crypto.js
Expand Up @@ -59,6 +59,11 @@ const {
generateKeyPair,
generateKeyPairSync
} = require('internal/crypto/keygen');
const {
createSecretKey,
createPublicKey,
createPrivateKey
} = require('internal/crypto/keys');
const {
DiffieHellman,
DiffieHellmanGroup,
Expand Down Expand Up @@ -149,6 +154,9 @@ module.exports = exports = {
createECDH,
createHash,
createHmac,
createPrivateKey,
createPublicKey,
createSecretKey,
createSign,
createVerify,
getCiphers,
Expand Down
33 changes: 20 additions & 13 deletions lib/internal/crypto/cipher.js
Expand Up @@ -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,
Expand All @@ -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' ?
tniessen marked this conversation as resolved.
Show resolved Hide resolved
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);
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 5 additions & 4 deletions lib/internal/crypto/hash.js
Expand Up @@ -12,6 +12,10 @@ const {
toBuf
} = require('internal/crypto/util');

const {
prepareSecretKey
} = require('internal/crypto/keys');

const { Buffer } = require('buffer');

const {
Expand Down Expand Up @@ -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] = {
Expand Down
135 changes: 46 additions & 89 deletions lib/internal/crypto/keygen.js
Expand Up @@ -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 } = 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;
Expand All @@ -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);
};

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -182,8 +139,8 @@ function check(type, options, callback) {
}

impl = (wrap) => generateKeyPairRSA(modulusLength, publicExponent,
publicType, publicFormat,
privateType, privateFormat,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
Expand All @@ -201,8 +158,8 @@ function check(type, options, callback) {
}

impl = (wrap) => generateKeyPairDSA(modulusLength, divisorLength,
publicType, publicFormat,
privateType, privateFormat,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
Expand All @@ -220,8 +177,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;
Expand Down