Skip to content

Commit 7c4dd14

Browse files
tniessenMylesBorins
authored andcommitted
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: #24234 Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 9b9060d commit 7c4dd14

21 files changed

Lines changed: 1998 additions & 653 deletions

‎doc/api/crypto.md‎

Lines changed: 218 additions & 60 deletions
Large diffs are not rendered by default.

‎doc/api/errors.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,11 @@ The selected public or private key encoding is incompatible with other options.
763763

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

766+
<aid="ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"></a>
767+
### ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE
768+
769+
The given crypto key object's type is invalid for the attempted operation.
770+
766771
<aid="ERR_CRYPTO_INVALID_STATE"></a>
767772
### ERR_CRYPTO_INVALID_STATE
768773

‎lib/crypto.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ const {
5959
generateKeyPair,
6060
generateKeyPairSync
6161
}=require('internal/crypto/keygen');
62+
const{
63+
createSecretKey,
64+
createPublicKey,
65+
createPrivateKey
66+
}=require('internal/crypto/keys');
6267
const{
6368
DiffieHellman,
6469
DiffieHellmanGroup,
@@ -149,6 +154,9 @@ module.exports = exports = {
149154
createECDH,
150155
createHash,
151156
createHmac,
157+
createPrivateKey,
158+
createPublicKey,
159+
createSecretKey,
152160
createSign,
153161
createVerify,
154162
getCiphers,

‎lib/internal/crypto/cipher.js‎

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ const {
1212
}=require('internal/errors').codes;
1313
const{ validateString }=require('internal/validators');
1414

15+
const{
16+
preparePrivateKey,
17+
preparePublicOrPrivateKey,
18+
prepareSecretKey
19+
}=require('internal/crypto/keys');
1520
const{
1621
getDefaultEncoding,
1722
kHandle,
@@ -38,19 +43,25 @@ const { deprecate, normalizeEncoding } = require('internal/util');
3843
// Lazy loaded for startup performance.
3944
letStringDecoder;
4045

41-
functionrsaFunctionFor(method,defaultPadding){
46+
functionrsaFunctionFor(method,defaultPadding,keyType){
4247
return(options,buffer)=>{
43-
constkey=options.key||options;
48+
const{ format, type, data, passphrase }=
49+
keyType==='private' ?
50+
preparePrivateKey(options) :
51+
preparePublicOrPrivateKey(options);
4452
constpadding=options.padding||defaultPadding;
45-
constpassphrase=options.passphrase||null;
46-
returnmethod(toBuf(key),buffer,padding,passphrase);
53+
returnmethod(data,format,type,passphrase,buffer,padding);
4754
};
4855
}
4956

50-
constpublicEncrypt=rsaFunctionFor(_publicEncrypt,RSA_PKCS1_OAEP_PADDING);
51-
constpublicDecrypt=rsaFunctionFor(_publicDecrypt,RSA_PKCS1_PADDING);
52-
constprivateEncrypt=rsaFunctionFor(_privateEncrypt,RSA_PKCS1_PADDING);
53-
constprivateDecrypt=rsaFunctionFor(_privateDecrypt,RSA_PKCS1_OAEP_PADDING);
57+
constpublicEncrypt=rsaFunctionFor(_publicEncrypt,RSA_PKCS1_OAEP_PADDING,
58+
'public');
59+
constpublicDecrypt=rsaFunctionFor(_publicDecrypt,RSA_PKCS1_PADDING,
60+
'private');
61+
constprivateEncrypt=rsaFunctionFor(_privateEncrypt,RSA_PKCS1_PADDING,
62+
'private');
63+
constprivateDecrypt=rsaFunctionFor(_privateDecrypt,RSA_PKCS1_OAEP_PADDING,
64+
'public');
5465

5566
functiongetDecoder(decoder,encoding){
5667
encoding=normalizeEncoding(encoding);
@@ -105,11 +116,7 @@ function createCipher(cipher, password, options, decipher) {
105116

106117
functioncreateCipherWithIV(cipher,key,options,decipher,iv){
107118
validateString(cipher,'cipher');
108-
key=toBuf(key);
109-
if(!isArrayBufferView(key)){
110-
throwinvalidArrayBufferView('key',key);
111-
}
112-
119+
key=prepareSecretKey(key);
113120
iv=toBuf(iv);
114121
if(iv!==null&&!isArrayBufferView(iv)){
115122
throwinvalidArrayBufferView('iv',iv);

‎lib/internal/crypto/hash.js‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ const {
1212
toBuf
1313
}=require('internal/crypto/util');
1414

15+
const{
16+
prepareSecretKey
17+
}=require('internal/crypto/keys');
18+
1519
const{ Buffer }=require('buffer');
1620

1721
const{
@@ -88,10 +92,7 @@ function Hmac(hmac, key, options) {
8892
if(!(thisinstanceofHmac))
8993
returnnewHmac(hmac,key,options);
9094
validateString(hmac,'hmac');
91-
if(typeofkey!=='string'&&!isArrayBufferView(key)){
92-
thrownewERR_INVALID_ARG_TYPE('key',
93-
['string','TypedArray','DataView'],key);
94-
}
95+
key=prepareSecretKey(key);
9596
this[kHandle]=new_Hmac();
9697
this[kHandle].init(hmac,toBuf(key));
9798
this[kState]={

‎lib/internal/crypto/keygen.js‎

Lines changed: 46 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,32 @@ const {
66
generateKeyPairDSA,
77
generateKeyPairEC,
88
OPENSSL_EC_NAMED_CURVE,
9-
OPENSSL_EC_EXPLICIT_CURVE,
10-
PK_ENCODING_PKCS1,
11-
PK_ENCODING_PKCS8,
12-
PK_ENCODING_SPKI,
13-
PK_ENCODING_SEC1,
14-
PK_FORMAT_DER,
15-
PK_FORMAT_PEM
9+
OPENSSL_EC_EXPLICIT_CURVE
1610
}=internalBinding('crypto');
11+
const{
12+
parsePublicKeyEncoding,
13+
parsePrivateKeyEncoding,
14+
15+
PublicKeyObject,
16+
PrivateKeyObject
17+
}=require('internal/crypto/keys');
1718
const{ customPromisifyArgs }=require('internal/util');
1819
const{ isUint32, validateString }=require('internal/validators');
1920
const{
20-
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
2121
ERR_INVALID_ARG_TYPE,
2222
ERR_INVALID_ARG_VALUE,
2323
ERR_INVALID_CALLBACK,
2424
ERR_INVALID_OPT_VALUE
2525
}=require('internal/errors').codes;
2626

27+
const{ isArrayBufferView }=require('internal/util/types');
28+
29+
functionwrapKey(key,ctor){
30+
if(typeofkey==='string'||isArrayBufferView(key))
31+
returnkey;
32+
returnnewctor(key);
33+
}
34+
2735
functiongenerateKeyPair(type,options,callback){
2836
if(typeofoptions==='function'){
2937
callback=options;
@@ -38,6 +46,9 @@ function generateKeyPair(type, options, callback) {
3846
constwrap=newAsyncWrap(Providers.KEYPAIRGENREQUEST);
3947
wrap.ondone=(ex,pubkey,privkey)=>{
4048
if(ex)returncallback.call(wrap,ex);
49+
// If no encoding was chosen, return key objects instead.
50+
pubkey=wrapKey(pubkey,PublicKeyObject);
51+
privkey=wrapKey(privkey,PrivateKeyObject);
4152
callback.call(wrap,null,pubkey,privkey);
4253
};
4354

@@ -69,86 +80,32 @@ function handleError(impl, wrap) {
6980
functionparseKeyEncoding(keyType,options){
7081
const{ publicKeyEncoding, privateKeyEncoding }=options;
7182

72-
if(publicKeyEncoding==null||typeofpublicKeyEncoding!=='object')
73-
thrownewERR_INVALID_OPT_VALUE('publicKeyEncoding',publicKeyEncoding);
74-
75-
const{format: strPublicFormat,type: strPublicType}=publicKeyEncoding;
76-
77-
letpublicType;
78-
if(strPublicType==='pkcs1'){
79-
if(keyType!=='rsa'){
80-
thrownewERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
81-
strPublicType,'can only be used for RSA keys');
82-
}
83-
publicType=PK_ENCODING_PKCS1;
84-
}elseif(strPublicType==='spki'){
85-
publicType=PK_ENCODING_SPKI;
83+
letpublicFormat,publicType;
84+
if(publicKeyEncoding==null){
85+
publicFormat=publicType=undefined;
86+
}elseif(typeofpublicKeyEncoding==='object'){
87+
({
88+
format: publicFormat,
89+
type: publicType
90+
}=parsePublicKeyEncoding(publicKeyEncoding,keyType,
91+
'publicKeyEncoding'));
8692
}else{
87-
thrownewERR_INVALID_OPT_VALUE('publicKeyEncoding.type',strPublicType);
93+
thrownewERR_INVALID_OPT_VALUE('publicKeyEncoding',publicKeyEncoding);
8894
}
8995

90-
letpublicFormat;
91-
if(strPublicFormat==='der'){
92-
publicFormat=PK_FORMAT_DER;
93-
}elseif(strPublicFormat==='pem'){
94-
publicFormat=PK_FORMAT_PEM;
96+
letprivateFormat,privateType,cipher,passphrase;
97+
if(privateKeyEncoding==null){
98+
privateFormat=privateType=undefined;
99+
}elseif(typeofprivateKeyEncoding==='object'){
100+
({
101+
format: privateFormat,
102+
type: privateType,
103+
cipher,
104+
passphrase
105+
}=parsePrivateKeyEncoding(privateKeyEncoding,keyType,
106+
'privateKeyEncoding'));
95107
}else{
96-
thrownewERR_INVALID_OPT_VALUE('publicKeyEncoding.format',
97-
strPublicFormat);
98-
}
99-
100-
if(privateKeyEncoding==null||typeofprivateKeyEncoding!=='object')
101108
thrownewERR_INVALID_OPT_VALUE('privateKeyEncoding',privateKeyEncoding);
102-
103-
const{
104-
cipher,
105-
passphrase,
106-
format: strPrivateFormat,
107-
type: strPrivateType
108-
}=privateKeyEncoding;
109-
110-
letprivateType;
111-
if(strPrivateType==='pkcs1'){
112-
if(keyType!=='rsa'){
113-
thrownewERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
114-
strPrivateType,'can only be used for RSA keys');
115-
}
116-
privateType=PK_ENCODING_PKCS1;
117-
}elseif(strPrivateType==='pkcs8'){
118-
privateType=PK_ENCODING_PKCS8;
119-
}elseif(strPrivateType==='sec1'){
120-
if(keyType!=='ec'){
121-
thrownewERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
122-
strPrivateType,'can only be used for EC keys');
123-
}
124-
privateType=PK_ENCODING_SEC1;
125-
}else{
126-
thrownewERR_INVALID_OPT_VALUE('privateKeyEncoding.type',strPrivateType);
127-
}
128-
129-
letprivateFormat;
130-
if(strPrivateFormat==='der'){
131-
privateFormat=PK_FORMAT_DER;
132-
}elseif(strPrivateFormat==='pem'){
133-
privateFormat=PK_FORMAT_PEM;
134-
}else{
135-
thrownewERR_INVALID_OPT_VALUE('privateKeyEncoding.format',
136-
strPrivateFormat);
137-
}
138-
139-
if(cipher!=null){
140-
if(typeofcipher!=='string')
141-
thrownewERR_INVALID_OPT_VALUE('privateKeyEncoding.cipher',cipher);
142-
if(privateFormat===PK_FORMAT_DER&&
143-
(privateType===PK_ENCODING_PKCS1||
144-
privateType===PK_ENCODING_SEC1)){
145-
thrownewERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
146-
strPrivateType,'does not support encryption');
147-
}
148-
if(typeofpassphrase!=='string'){
149-
thrownewERR_INVALID_OPT_VALUE('privateKeyEncoding.passphrase',
150-
passphrase);
151-
}
152109
}
153110

154111
return{
@@ -181,8 +138,8 @@ function check(type, options, callback) {
181138
}
182139

183140
impl=(wrap)=>generateKeyPairRSA(modulusLength,publicExponent,
184-
publicType,publicFormat,
185-
privateType,privateFormat,
141+
publicFormat,publicType,
142+
privateFormat,privateType,
186143
cipher,passphrase,wrap);
187144
}
188145
break;
@@ -200,8 +157,8 @@ function check(type, options, callback) {
200157
}
201158

202159
impl=(wrap)=>generateKeyPairDSA(modulusLength,divisorLength,
203-
publicType,publicFormat,
204-
privateType,privateFormat,
160+
publicFormat,publicType,
161+
privateFormat,privateType,
205162
cipher,passphrase,wrap);
206163
}
207164
break;
@@ -219,8 +176,8 @@ function check(type, options, callback) {
219176
thrownewERR_INVALID_OPT_VALUE('paramEncoding',paramEncoding);
220177

221178
impl=(wrap)=>generateKeyPairEC(namedCurve,paramEncoding,
222-
publicType,publicFormat,
223-
privateType,privateFormat,
179+
publicFormat,publicType,
180+
privateFormat,privateType,
224181
cipher,passphrase,wrap);
225182
}
226183
break;

0 commit comments

Comments
 (0)