crypto: allow deriving public from private keys
This change allows passing private key objects to crypto.createPublicKey, resulting in a key object that represents a valid public key for the given private key. The returned public key object can be used and exported safely without revealing information about the private key. PR-URL: https://github.com/nodejs/node/pull/26278 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
This commit is contained in:
parent
84ebaaa339
commit
fe7162915e
@ -1813,11 +1813,15 @@ must be an object with the properties described above.
|
|||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v11.6.0
|
added: v11.6.0
|
||||||
changes:
|
changes:
|
||||||
|
- version: REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/26278
|
||||||
|
description: The `key` argument can now be a `KeyObject` with type
|
||||||
|
`private`.
|
||||||
- version: v11.7.0
|
- version: v11.7.0
|
||||||
pr-url: https://github.com/nodejs/node/pull/25217
|
pr-url: https://github.com/nodejs/node/pull/25217
|
||||||
description: The `key` argument can now be a private key.
|
description: The `key` argument can now be a private key.
|
||||||
-->
|
-->
|
||||||
* `key` {Object | string | Buffer}
|
* `key` {Object | string | Buffer | KeyObject}
|
||||||
- `key`: {string | Buffer}
|
- `key`: {string | Buffer}
|
||||||
- `format`: {string} Must be `'pem'` or `'der'`. **Default:** `'pem'`.
|
- `format`: {string} Must be `'pem'` or `'der'`. **Default:** `'pem'`.
|
||||||
- `type`: {string} Must be `'pkcs1'` or `'spki'`. This option is required
|
- `type`: {string} Must be `'pkcs1'` or `'spki'`. This option is required
|
||||||
@ -1825,16 +1829,19 @@ changes:
|
|||||||
* Returns: {KeyObject}
|
* Returns: {KeyObject}
|
||||||
|
|
||||||
Creates and returns a new key object containing a public key. If `key` is a
|
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`
|
string or `Buffer`, `format` is assumed to be `'pem'`; if `key` is a `KeyObject`
|
||||||
must be an object with the properties described above.
|
with type `'private'`, the public key is derived from the given private key;
|
||||||
|
otherwise, `key` must be an object with the properties described above.
|
||||||
|
|
||||||
If the format is `'pem'`, the `'key'` may also be an X.509 certificate.
|
If the format is `'pem'`, the `'key'` may also be an X.509 certificate.
|
||||||
|
|
||||||
Because public keys can be derived from private keys, a private key may be
|
Because public keys can be derived from private keys, a private key may be
|
||||||
passed instead of a public key. In that case, this function behaves as if
|
passed instead of a public key. In that case, this function behaves as if
|
||||||
[`crypto.createPrivateKey()`][] had been called, except that the type of the
|
[`crypto.createPrivateKey()`][] had been called, except that the type of the
|
||||||
returned `KeyObject` will be `public` and that the private key cannot be
|
returned `KeyObject` will be `'public'` and that the private key cannot be
|
||||||
extracted from the returned `KeyObject`.
|
extracted from the returned `KeyObject`. Similarly, if a `KeyObject` with type
|
||||||
|
`'private'` is given, a new `KeyObject` with type `'public'` will be returned
|
||||||
|
and it will be impossible to extract the private key from the returned object.
|
||||||
|
|
||||||
### crypto.createSecretKey(key)
|
### crypto.createSecretKey(key)
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
@ -26,6 +26,12 @@ const { isArrayBufferView } = require('internal/util/types');
|
|||||||
|
|
||||||
const kKeyType = Symbol('kKeyType');
|
const kKeyType = Symbol('kKeyType');
|
||||||
|
|
||||||
|
// Key input contexts.
|
||||||
|
const kConsumePublic = 0;
|
||||||
|
const kConsumePrivate = 1;
|
||||||
|
const kCreatePublic = 2;
|
||||||
|
const kCreatePrivate = 3;
|
||||||
|
|
||||||
const encodingNames = [];
|
const encodingNames = [];
|
||||||
for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'],
|
for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'],
|
||||||
[kKeyEncodingSPKI, 'spki'], [kKeyEncodingSEC1, 'sec1']])
|
[kKeyEncodingSPKI, 'spki'], [kKeyEncodingSEC1, 'sec1']])
|
||||||
@ -203,7 +209,7 @@ function parseKeyEncoding(enc, keyType, isPublic, objName) {
|
|||||||
// when this is used to parse an input encoding and must be a valid key type if
|
// when this is used to parse an input encoding and must be a valid key type if
|
||||||
// used to parse an output encoding.
|
// used to parse an output encoding.
|
||||||
function parsePublicKeyEncoding(enc, keyType, objName) {
|
function parsePublicKeyEncoding(enc, keyType, objName) {
|
||||||
return parseKeyFormatAndType(enc, keyType, true, objName);
|
return parseKeyEncoding(enc, keyType, keyType ? true : undefined, objName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses the private key encoding based on an object. keyType must be undefined
|
// Parses the private key encoding based on an object. keyType must be undefined
|
||||||
@ -213,26 +219,31 @@ function parsePrivateKeyEncoding(enc, keyType, objName) {
|
|||||||
return parseKeyEncoding(enc, keyType, false, objName);
|
return parseKeyEncoding(enc, keyType, false, objName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getKeyObjectHandle(key, isPublic, allowKeyObject) {
|
function getKeyObjectHandle(key, ctx) {
|
||||||
if (!allowKeyObject) {
|
if (ctx === kCreatePrivate) {
|
||||||
throw new ERR_INVALID_ARG_TYPE(
|
throw new ERR_INVALID_ARG_TYPE(
|
||||||
'key',
|
'key',
|
||||||
['string', 'Buffer', 'TypedArray', 'DataView'],
|
['string', 'Buffer', 'TypedArray', 'DataView'],
|
||||||
key
|
key
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isPublic != null) {
|
|
||||||
const expectedType = isPublic ? 'public' : 'private';
|
if (key.type !== 'private') {
|
||||||
if (key.type !== expectedType)
|
if (ctx === kConsumePrivate || ctx === kCreatePublic)
|
||||||
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, expectedType);
|
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'private');
|
||||||
|
if (key.type !== 'public') {
|
||||||
|
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type,
|
||||||
|
'private or public');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return key[kHandle];
|
return key[kHandle];
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareAsymmetricKey(key, isPublic, allowKeyObject = true) {
|
function prepareAsymmetricKey(key, ctx) {
|
||||||
if (isKeyObject(key)) {
|
if (isKeyObject(key)) {
|
||||||
// Best case: A key object, as simple as that.
|
// Best case: A key object, as simple as that.
|
||||||
return { data: getKeyObjectHandle(key, isPublic, allowKeyObject) };
|
return { data: getKeyObjectHandle(key, ctx) };
|
||||||
} else if (typeof key === 'string' || isArrayBufferView(key)) {
|
} else if (typeof key === 'string' || isArrayBufferView(key)) {
|
||||||
// Expect PEM by default, mostly for backward compatibility.
|
// Expect PEM by default, mostly for backward compatibility.
|
||||||
return { format: kKeyFormatPEM, data: key };
|
return { format: kKeyFormatPEM, data: key };
|
||||||
@ -241,32 +252,32 @@ function prepareAsymmetricKey(key, isPublic, allowKeyObject = true) {
|
|||||||
// The 'key' property can be a KeyObject as well to allow specifying
|
// The 'key' property can be a KeyObject as well to allow specifying
|
||||||
// additional options such as padding along with the key.
|
// additional options such as padding along with the key.
|
||||||
if (isKeyObject(data))
|
if (isKeyObject(data))
|
||||||
return { data: getKeyObjectHandle(data, isPublic, allowKeyObject) };
|
return { data: getKeyObjectHandle(data, ctx) };
|
||||||
// Either PEM or DER using PKCS#1 or SPKI.
|
// Either PEM or DER using PKCS#1 or SPKI.
|
||||||
if (!isStringOrBuffer(data)) {
|
if (!isStringOrBuffer(data)) {
|
||||||
throw new ERR_INVALID_ARG_TYPE(
|
throw new ERR_INVALID_ARG_TYPE(
|
||||||
'key',
|
'key',
|
||||||
['string', 'Buffer', 'TypedArray', 'DataView',
|
['string', 'Buffer', 'TypedArray', 'DataView',
|
||||||
...(allowKeyObject ? ['KeyObject'] : [])],
|
...(ctx !== kCreatePrivate ? ['KeyObject'] : [])],
|
||||||
key);
|
key);
|
||||||
}
|
}
|
||||||
return { data, ...parseKeyEncoding(key, undefined, isPublic) };
|
return { data, ...parseKeyEncoding(key, undefined) };
|
||||||
} else {
|
} else {
|
||||||
throw new ERR_INVALID_ARG_TYPE(
|
throw new ERR_INVALID_ARG_TYPE(
|
||||||
'key',
|
'key',
|
||||||
['string', 'Buffer', 'TypedArray', 'DataView',
|
['string', 'Buffer', 'TypedArray', 'DataView',
|
||||||
...(allowKeyObject ? ['KeyObject'] : [])],
|
...(ctx !== kCreatePrivate ? ['KeyObject'] : [])],
|
||||||
key
|
key
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function preparePrivateKey(key, allowKeyObject) {
|
function preparePrivateKey(key) {
|
||||||
return prepareAsymmetricKey(key, false, allowKeyObject);
|
return prepareAsymmetricKey(key, kConsumePrivate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function preparePublicOrPrivateKey(key, allowKeyObject) {
|
function preparePublicOrPrivateKey(key) {
|
||||||
return prepareAsymmetricKey(key, undefined, allowKeyObject);
|
return prepareAsymmetricKey(key, kConsumePublic);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareSecretKey(key, bufferOnly = false) {
|
function prepareSecretKey(key, bufferOnly = false) {
|
||||||
@ -296,14 +307,15 @@ function createSecretKey(key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createPublicKey(key) {
|
function createPublicKey(key) {
|
||||||
const { format, type, data } = preparePublicOrPrivateKey(key, false);
|
const { format, type, data } = prepareAsymmetricKey(key, kCreatePublic);
|
||||||
const handle = new KeyObjectHandle(kKeyTypePublic);
|
const handle = new KeyObjectHandle(kKeyTypePublic);
|
||||||
handle.init(data, format, type);
|
handle.init(data, format, type);
|
||||||
return new PublicKeyObject(handle);
|
return new PublicKeyObject(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPrivateKey(key) {
|
function createPrivateKey(key) {
|
||||||
const { format, type, data, passphrase } = preparePrivateKey(key, false);
|
const { format, type, data, passphrase } =
|
||||||
|
prepareAsymmetricKey(key, kCreatePrivate);
|
||||||
const handle = new KeyObjectHandle(kKeyTypePrivate);
|
const handle = new KeyObjectHandle(kKeyTypePrivate);
|
||||||
handle.init(data, format, type, passphrase);
|
handle.init(data, format, type, passphrase);
|
||||||
return new PrivateKeyObject(handle);
|
return new PrivateKeyObject(handle);
|
||||||
|
@ -3409,7 +3409,7 @@ void KeyObject::Init(const FunctionCallbackInfo<Value>& args) {
|
|||||||
CHECK_EQ(args.Length(), 3);
|
CHECK_EQ(args.Length(), 3);
|
||||||
|
|
||||||
offset = 0;
|
offset = 0;
|
||||||
pkey = GetPublicOrPrivateKeyFromJs(args, &offset, false);
|
pkey = GetPublicOrPrivateKeyFromJs(args, &offset, true);
|
||||||
if (!pkey)
|
if (!pkey)
|
||||||
return;
|
return;
|
||||||
key->InitPublic(pkey);
|
key->InitPublic(pkey);
|
||||||
|
@ -59,9 +59,27 @@ const privatePem = fixtures.readSync('test_rsa_privkey.pem', 'ascii');
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// Passing an existing key object should throw.
|
// Passing an existing public key object to createPublicKey should throw.
|
||||||
const publicKey = createPublicKey(publicPem);
|
const publicKey = createPublicKey(publicPem);
|
||||||
common.expectsError(() => createPublicKey(publicKey), {
|
common.expectsError(() => createPublicKey(publicKey), {
|
||||||
|
type: TypeError,
|
||||||
|
code: 'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',
|
||||||
|
message: 'Invalid key object type public, expected private.'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Constructing a private key from a public key should be impossible, even
|
||||||
|
// if the public key was derived from a private key.
|
||||||
|
common.expectsError(() => createPrivateKey(createPublicKey(privatePem)), {
|
||||||
|
type: TypeError,
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
message: 'The "key" argument must be one of type string, Buffer, ' +
|
||||||
|
'TypedArray, or DataView. Received type object'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Similarly, passing an existing private key object to createPrivateKey
|
||||||
|
// should throw.
|
||||||
|
const privateKey = createPrivateKey(privatePem);
|
||||||
|
common.expectsError(() => createPrivateKey(privateKey), {
|
||||||
type: TypeError,
|
type: TypeError,
|
||||||
code: 'ERR_INVALID_ARG_TYPE',
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
message: 'The "key" argument must be one of type string, Buffer, ' +
|
message: 'The "key" argument must be one of type string, Buffer, ' +
|
||||||
@ -80,6 +98,12 @@ const privatePem = fixtures.readSync('test_rsa_privkey.pem', 'ascii');
|
|||||||
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
|
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
|
||||||
assert.strictEqual(privateKey.symmetricKeySize, undefined);
|
assert.strictEqual(privateKey.symmetricKeySize, undefined);
|
||||||
|
|
||||||
|
// It should be possible to derive a public key from a private key.
|
||||||
|
const derivedPublicKey = createPublicKey(privateKey);
|
||||||
|
assert.strictEqual(derivedPublicKey.type, 'public');
|
||||||
|
assert.strictEqual(derivedPublicKey.asymmetricKeyType, 'rsa');
|
||||||
|
assert.strictEqual(derivedPublicKey.symmetricKeySize, undefined);
|
||||||
|
|
||||||
const publicDER = publicKey.export({
|
const publicDER = publicKey.export({
|
||||||
format: 'der',
|
format: 'der',
|
||||||
type: 'pkcs1'
|
type: 'pkcs1'
|
||||||
@ -95,8 +119,18 @@ const privatePem = fixtures.readSync('test_rsa_privkey.pem', 'ascii');
|
|||||||
|
|
||||||
const plaintext = Buffer.from('Hello world', 'utf8');
|
const plaintext = Buffer.from('Hello world', 'utf8');
|
||||||
const ciphertexts = [
|
const ciphertexts = [
|
||||||
|
// Encrypt using the public key.
|
||||||
publicEncrypt(publicKey, plaintext),
|
publicEncrypt(publicKey, plaintext),
|
||||||
publicEncrypt({ key: publicKey }, plaintext),
|
publicEncrypt({ key: publicKey }, plaintext),
|
||||||
|
|
||||||
|
// Encrypt using the private key.
|
||||||
|
publicEncrypt(privateKey, plaintext),
|
||||||
|
publicEncrypt({ key: privateKey }, plaintext),
|
||||||
|
|
||||||
|
// Encrypt using a public key derived from the private key.
|
||||||
|
publicEncrypt(derivedPublicKey, plaintext),
|
||||||
|
publicEncrypt({ key: derivedPublicKey }, plaintext),
|
||||||
|
|
||||||
// Test distinguishing PKCS#1 public and private keys based on the
|
// Test distinguishing PKCS#1 public and private keys based on the
|
||||||
// DER-encoded data only.
|
// DER-encoded data only.
|
||||||
publicEncrypt({ format: 'der', type: 'pkcs1', key: publicDER }, plaintext),
|
publicEncrypt({ format: 'der', type: 'pkcs1', key: publicDER }, plaintext),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user