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: allow deriving public from private keys #26278
Changes from all commits
File filter...
Jump to…
Diff settings
| @@ -1813,28 +1813,35 @@ must be an object with the properties described above. | |||
| <!-- YAML | |||
| added: v11.6.0 | |||
| 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 | |||
| pr-url: https://github.com/nodejs/node/pull/25217 | |||
| description: The `key` argument can now be a private key. | |||
| --> | |||
| * `key` {Object | string | Buffer} | |||
| * `key` {Object | string | Buffer | KeyObject} | |||
| - `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. | |||
| string or `Buffer`, `format` is assumed to be `'pem'`; if `key` is a `KeyObject` | |||
| 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. | |||
|
|
|||
| 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 | |||
| [`crypto.createPrivateKey()`][] had been called, except that the type of the | |||
| returned `KeyObject` will be `public` and that the private key cannot be | |||
| extracted from the returned `KeyObject`. | |||
| returned `KeyObject` will be `'public'` and that the private key cannot be | |||
| extracted from the returned `KeyObject`. Similarly, if a `KeyObject` with type | |||
|
This conversation was marked as resolved
by tniessen
tniessen
Author
Member
|
|||
| `'private'` is given, a new `KeyObject` with type `'public'` will be returned | |||
|
This conversation was marked as resolved
by sam-github
sam-github
Member
|
|||
| and it will be impossible to extract the private key from the returned object. | |||
|
|
|||
| ### crypto.createSecretKey(key) | |||
| <!-- YAML | |||
| @@ -26,6 +26,12 @@ const { isArrayBufferView } = require('internal/util/types'); | |||
|
|
|||
| const kKeyType = Symbol('kKeyType'); | |||
|
|
|||
| // Key input contexts. | |||
| const kConsumePublic = 0; | |||
| const kConsumePrivate = 1; | |||
| const kCreatePublic = 2; | |||
| const kCreatePrivate = 3; | |||
|
|
|||
| const encodingNames = []; | |||
| for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'], | |||
| [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 | |||
| // used to parse an output encoding. | |||
| function parsePublicKeyEncoding(enc, keyType, objName) { | |||
| return parseKeyFormatAndType(enc, keyType, true, objName); | |||
| return parseKeyEncoding(enc, keyType, keyType ? true : undefined, objName); | |||
|
This conversation was marked as resolved
by sam-github
sam-github
Member
|
|||
| } | |||
|
|
|||
| // 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); | |||
| } | |||
|
|
|||
| function getKeyObjectHandle(key, isPublic, allowKeyObject) { | |||
| if (!allowKeyObject) { | |||
| function getKeyObjectHandle(key, ctx) { | |||
| if (ctx === kCreatePrivate) { | |||
| throw 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); | |||
|
|
|||
| if (key.type !== 'private') { | |||
| if (ctx === kConsumePrivate || ctx === kCreatePublic) | |||
| 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]; | |||
| } | |||
|
|
|||
| function prepareAsymmetricKey(key, isPublic, allowKeyObject = true) { | |||
| function prepareAsymmetricKey(key, ctx) { | |||
| if (isKeyObject(key)) { | |||
| // 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)) { | |||
| // Expect PEM by default, mostly for backward compatibility. | |||
| 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 | |||
| // additional options such as padding along with the key. | |||
| if (isKeyObject(data)) | |||
| return { data: getKeyObjectHandle(data, isPublic, allowKeyObject) }; | |||
| return { data: getKeyObjectHandle(data, ctx) }; | |||
| // 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'] : [])], | |||
| ...(ctx !== kCreatePrivate ? ['KeyObject'] : [])], | |||
| key); | |||
| } | |||
| return { data, ...parseKeyEncoding(key, undefined, isPublic) }; | |||
| return { data, ...parseKeyEncoding(key, undefined) }; | |||
| } else { | |||
| throw new ERR_INVALID_ARG_TYPE( | |||
| 'key', | |||
| ['string', 'Buffer', 'TypedArray', 'DataView', | |||
| ...(allowKeyObject ? ['KeyObject'] : [])], | |||
| ...(ctx !== kCreatePrivate ? ['KeyObject'] : [])], | |||
| key | |||
| ); | |||
| } | |||
| } | |||
|
|
|||
| function preparePrivateKey(key, allowKeyObject) { | |||
| return prepareAsymmetricKey(key, false, allowKeyObject); | |||
| function preparePrivateKey(key) { | |||
| return prepareAsymmetricKey(key, kConsumePrivate); | |||
| } | |||
|
|
|||
| function preparePublicOrPrivateKey(key, allowKeyObject) { | |||
| return prepareAsymmetricKey(key, undefined, allowKeyObject); | |||
| function preparePublicOrPrivateKey(key) { | |||
| return prepareAsymmetricKey(key, kConsumePublic); | |||
| } | |||
|
|
|||
| function prepareSecretKey(key, bufferOnly = false) { | |||
| @@ -296,14 +307,15 @@ function createSecretKey(key) { | |||
| } | |||
|
|
|||
| function createPublicKey(key) { | |||
| const { format, type, data } = preparePublicOrPrivateKey(key, false); | |||
| const { format, type, data } = prepareAsymmetricKey(key, kCreatePublic); | |||
| 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 { format, type, data, passphrase } = | |||
| prepareAsymmetricKey(key, kCreatePrivate); | |||
| const handle = new KeyObjectHandle(kKeyTypePrivate); | |||
| handle.init(data, format, type, passphrase); | |||
| return new PrivateKeyObject(handle); | |||
Is there a cost related to this action? When we pass a
'private'key to functions needing a'public'one, is the key being derived over and over?Should an API be encouraged to prepare and cache the public KeyObject and pass that one instead?