-
Notifications
You must be signed in to change notification settings - Fork 30.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
crypto: add API for key pair generation
This adds support for RSA, DSA and EC key pair generation with a variety of possible output formats etc. PR-URL: #22660 Fixes: #15116 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
- Loading branch information
Showing
10 changed files
with
1,457 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
'use strict'; | ||
|
||
const { internalBinding } = require('internal/bootstrap/loaders'); | ||
const { AsyncWrap, Providers } = internalBinding('async_wrap'); | ||
const { | ||
generateKeyPairRSA, | ||
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 | ||
} = internalBinding('crypto'); | ||
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; | ||
|
||
function generateKeyPair(type, options, callback) { | ||
if (typeof options === 'function') { | ||
callback = options; | ||
options = undefined; | ||
} | ||
|
||
const impl = check(type, options); | ||
|
||
if (typeof callback !== 'function') | ||
throw new ERR_INVALID_CALLBACK(); | ||
|
||
const wrap = new AsyncWrap(Providers.KEYPAIRGENREQUEST); | ||
wrap.ondone = (ex, pubkey, privkey) => { | ||
if (ex) return callback.call(wrap, ex); | ||
callback.call(wrap, null, pubkey, privkey); | ||
}; | ||
|
||
handleError(impl, wrap); | ||
} | ||
|
||
function generateKeyPairSync(type, options) { | ||
const impl = check(type, options); | ||
return handleError(impl); | ||
} | ||
|
||
function handleError(impl, wrap) { | ||
const ret = impl(wrap); | ||
if (ret === undefined) | ||
return; // async | ||
|
||
const [err, publicKey, privateKey] = ret; | ||
if (err !== undefined) | ||
throw err; | ||
|
||
return { publicKey, privateKey }; | ||
} | ||
|
||
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; | ||
} else { | ||
throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding.type', strPublicType); | ||
} | ||
|
||
let publicFormat; | ||
if (strPublicFormat === 'der') { | ||
publicFormat = PK_FORMAT_DER; | ||
} else if (strPublicFormat === 'pem') { | ||
publicFormat = PK_FORMAT_PEM; | ||
} 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 (privateType !== PK_ENCODING_PKCS8) { | ||
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 { | ||
cipher, passphrase, publicType, publicFormat, privateType, privateFormat | ||
}; | ||
} | ||
|
||
function check(type, options, callback) { | ||
if (typeof type !== 'string') | ||
throw new ERR_INVALID_ARG_TYPE('type', 'string', type); | ||
if (options == null || typeof options !== 'object') | ||
throw new ERR_INVALID_ARG_TYPE('options', 'object', options); | ||
|
||
// These will be set after parsing the type and type-specific options to make | ||
// the order a bit more intuitive. | ||
let cipher, passphrase, publicType, publicFormat, privateType, privateFormat; | ||
|
||
let impl; | ||
switch (type) { | ||
case 'rsa': | ||
{ | ||
const { modulusLength } = options; | ||
if (!isUint32(modulusLength)) | ||
throw new ERR_INVALID_OPT_VALUE('modulusLength', modulusLength); | ||
|
||
let { publicExponent } = options; | ||
if (publicExponent == null) { | ||
publicExponent = 0x10001; | ||
} else if (!isUint32(publicExponent)) { | ||
throw new ERR_INVALID_OPT_VALUE('publicExponent', publicExponent); | ||
} | ||
|
||
impl = (wrap) => generateKeyPairRSA(modulusLength, publicExponent, | ||
publicType, publicFormat, | ||
privateType, privateFormat, | ||
cipher, passphrase, wrap); | ||
} | ||
break; | ||
case 'dsa': | ||
{ | ||
const { modulusLength } = options; | ||
if (!isUint32(modulusLength)) | ||
throw new ERR_INVALID_OPT_VALUE('modulusLength', modulusLength); | ||
|
||
let { divisorLength } = options; | ||
if (divisorLength == null) { | ||
divisorLength = -1; | ||
} else if (!isUint32(divisorLength)) { | ||
throw new ERR_INVALID_OPT_VALUE('divisorLength', divisorLength); | ||
} | ||
|
||
impl = (wrap) => generateKeyPairDSA(modulusLength, divisorLength, | ||
publicType, publicFormat, | ||
privateType, privateFormat, | ||
cipher, passphrase, wrap); | ||
} | ||
break; | ||
case 'ec': | ||
{ | ||
const { namedCurve } = options; | ||
if (typeof namedCurve !== 'string') | ||
throw new ERR_INVALID_OPT_VALUE('namedCurve', namedCurve); | ||
let { paramEncoding } = options; | ||
if (paramEncoding == null || paramEncoding === 'named') | ||
paramEncoding = OPENSSL_EC_NAMED_CURVE; | ||
else if (paramEncoding === 'explicit') | ||
paramEncoding = OPENSSL_EC_EXPLICIT_CURVE; | ||
else | ||
throw new ERR_INVALID_OPT_VALUE('paramEncoding', paramEncoding); | ||
|
||
impl = (wrap) => generateKeyPairEC(namedCurve, paramEncoding, | ||
publicType, publicFormat, | ||
privateType, privateFormat, | ||
cipher, passphrase, wrap); | ||
} | ||
break; | ||
default: | ||
throw new ERR_INVALID_ARG_VALUE('type', type, | ||
"must be one of 'rsa', 'dsa', 'ec'"); | ||
} | ||
|
||
({ | ||
cipher, | ||
passphrase, | ||
publicType, | ||
publicFormat, | ||
privateType, | ||
privateFormat | ||
} = parseKeyEncoding(type, options)); | ||
|
||
return impl; | ||
} | ||
|
||
module.exports = { generateKeyPair, generateKeyPairSync }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.