Skip to content

Commit 8d69fdd

Browse files
committed
crypto: fix unencrypted DER PKCS8 parsing
The previously used OpenSSL call only supports encrypted PKCS8, this commit adds support for unencrypted PKCS8. PR-URL: #26236 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Sam Roberts <[email protected]> Reviewed-By: Ujjwal Sharma <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 10c3db3 commit 8d69fdd

File tree

3 files changed

+134
-39
lines changed

3 files changed

+134
-39
lines changed

src/node_crypto.cc

Lines changed: 66 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2848,6 +2848,59 @@ static MaybeLocal<Value> WritePublicKey(Environment* env,
28482848
return BIOToStringOrBuffer(env, bio.get(), config.format_);
28492849
}
28502850

2851+
static bool IsASN1Sequence(const unsigned char* data, size_t size,
2852+
size_t* data_offset, size_t* data_size) {
2853+
if (size < 2 || data[0] != 0x30)
2854+
return false;
2855+
2856+
if (data[1] & 0x80) {
2857+
// Long form.
2858+
size_t n_bytes = data[1] & ~0x80;
2859+
if (n_bytes + 2 > size || n_bytes > sizeof(size_t))
2860+
return false;
2861+
size_t length = 0;
2862+
for (size_t i = 0; i < n_bytes; i++)
2863+
length = (length << 8) | data[i + 2];
2864+
*data_offset = 2 + n_bytes;
2865+
*data_size = std::min(size - 2 - n_bytes, length);
2866+
} else {
2867+
// Short form.
2868+
*data_offset = 2;
2869+
*data_size = std::min<size_t>(size - 2, data[1]);
2870+
}
2871+
2872+
return true;
2873+
}
2874+
2875+
static bool IsRSAPrivateKey(const unsigned char* data, size_t size) {
2876+
// Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE.
2877+
size_t offset, len;
2878+
if (!IsASN1Sequence(data, size, &offset, &len))
2879+
return false;
2880+
2881+
// An RSAPrivateKey sequence always starts with a single-byte integer whose
2882+
// value is either 0 or 1, whereas an RSAPublicKey starts with the modulus
2883+
// (which is the product of two primes and therefore at least 4), so we can
2884+
// decide the type of the structure based on the first three bytes of the
2885+
// sequence.
2886+
return len >= 3 &&
2887+
data[offset] == 2 &&
2888+
data[offset + 1] == 1 &&
2889+
!(data[offset + 2] & 0xfe);
2890+
}
2891+
2892+
static bool IsEncryptedPrivateKeyInfo(const unsigned char* data, size_t size) {
2893+
// Both PrivateKeyInfo and EncryptedPrivateKeyInfo start with a SEQUENCE.
2894+
size_t offset, len;
2895+
if (!IsASN1Sequence(data, size, &offset, &len))
2896+
return false;
2897+
2898+
// A PrivateKeyInfo sequence always starts with an integer whereas an
2899+
// EncryptedPrivateKeyInfo starts with an AlgorithmIdentifier.
2900+
return len >= 1 &&
2901+
data[offset] != 2;
2902+
}
2903+
28512904
static EVPKeyPointer ParsePrivateKey(const PrivateKeyEncodingConfig& config,
28522905
const char* key,
28532906
size_t key_len) {
@@ -2873,11 +2926,19 @@ static EVPKeyPointer ParsePrivateKey(const PrivateKeyEncodingConfig& config,
28732926
BIOPointer bio(BIO_new_mem_buf(key, key_len));
28742927
if (!bio)
28752928
return pkey;
2876-
char* pass = const_cast<char*>(config.passphrase_.get());
2877-
pkey.reset(d2i_PKCS8PrivateKey_bio(bio.get(),
2878-
nullptr,
2879-
PasswordCallback,
2880-
pass));
2929+
2930+
if (IsEncryptedPrivateKeyInfo(
2931+
reinterpret_cast<const unsigned char*>(key), key_len)) {
2932+
char* pass = const_cast<char*>(config.passphrase_.get());
2933+
pkey.reset(d2i_PKCS8PrivateKey_bio(bio.get(),
2934+
nullptr,
2935+
PasswordCallback,
2936+
pass));
2937+
} else {
2938+
PKCS8Pointer p8inf(d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr));
2939+
if (p8inf)
2940+
pkey.reset(EVP_PKCS82PKEY(p8inf.get()));
2941+
}
28812942
} else {
28822943
CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSEC1);
28832944
const unsigned char* p = reinterpret_cast<const unsigned char*>(key);
@@ -3093,40 +3154,6 @@ static ManagedEVPPKey GetPrivateKeyFromJs(
30933154
}
30943155
}
30953156

3096-
static bool IsRSAPrivateKey(const unsigned char* data, size_t size) {
3097-
// Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE.
3098-
if (size >= 2 && data[0] == 0x30) {
3099-
size_t offset;
3100-
if (data[1] & 0x80) {
3101-
// Long form.
3102-
size_t n_bytes = data[1] & ~0x80;
3103-
if (n_bytes + 2 > size || n_bytes > sizeof(size_t))
3104-
return false;
3105-
size_t i, length = 0;
3106-
for (i = 0; i < n_bytes; i++)
3107-
length = (length << 8) | data[i + 2];
3108-
offset = 2 + n_bytes;
3109-
size = std::min(size, length + 2);
3110-
} else {
3111-
// Short form.
3112-
offset = 2;
3113-
size = std::min<size_t>(size, data[1] + 2);
3114-
}
3115-
3116-
// An RSAPrivateKey sequence always starts with a single-byte integer whose
3117-
// value is either 0 or 1, whereas an RSAPublicKey starts with the modulus
3118-
// (which is the product of two primes and therefore at least 4), so we can
3119-
// decide the type of the structure based on the first three bytes of the
3120-
// sequence.
3121-
return size - offset >= 3 &&
3122-
data[offset] == 2 &&
3123-
data[offset + 1] == 1 &&
3124-
!(data[offset + 2] & 0xfe);
3125-
}
3126-
3127-
return false;
3128-
}
3129-
31303157
static ManagedEVPPKey GetPublicOrPrivateKeyFromJs(
31313158
const FunctionCallbackInfo<Value>& args,
31323159
unsigned int* offset,

src/node_crypto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ using BIOPointer = DeleteFnPtr<BIO, BIO_free_all>;
7777
using SSLCtxPointer = DeleteFnPtr<SSL_CTX, SSL_CTX_free>;
7878
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>;
7979
using SSLPointer = DeleteFnPtr<SSL, SSL_free>;
80+
using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
8081
using EVPKeyPointer = DeleteFnPtr<EVP_PKEY, EVP_PKEY_free>;
8182
using EVPKeyCtxPointer = DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
8283
using EVPMDPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;

test/parallel/test-crypto-keygen.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,73 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
174174
testEncryptDecrypt(publicKey, key);
175175
testSignVerify(publicKey, key);
176176
}));
177+
178+
// Now do the same with an encrypted private key, but encoded as DER.
179+
generateKeyPair('rsa', {
180+
publicExponent: 0x10001,
181+
modulusLength: 512,
182+
publicKeyEncoding,
183+
privateKeyEncoding: {
184+
type: 'pkcs8',
185+
format: 'der',
186+
cipher: 'aes-256-cbc',
187+
passphrase: 'secret'
188+
}
189+
}, common.mustCall((err, publicKeyDER, privateKeyDER) => {
190+
assert.ifError(err);
191+
192+
assert(Buffer.isBuffer(publicKeyDER));
193+
assertApproximateSize(publicKeyDER, 74);
194+
195+
assert(Buffer.isBuffer(privateKeyDER));
196+
197+
// Since the private key is encrypted, signing shouldn't work anymore.
198+
const publicKey = { key: publicKeyDER, ...publicKeyEncoding };
199+
assert.throws(() => {
200+
testSignVerify(publicKey, {
201+
key: privateKeyDER,
202+
format: 'der',
203+
type: 'pkcs8'
204+
});
205+
}, /bad decrypt|asn1 encoding routines/);
206+
207+
const privateKey = {
208+
key: privateKeyDER,
209+
format: 'der',
210+
type: 'pkcs8',
211+
passphrase: 'secret'
212+
};
213+
testEncryptDecrypt(publicKey, privateKey);
214+
testSignVerify(publicKey, privateKey);
215+
}));
216+
217+
// Now do the same with an encrypted private key, but encoded as DER.
218+
generateKeyPair('rsa', {
219+
publicExponent: 0x10001,
220+
modulusLength: 512,
221+
publicKeyEncoding,
222+
privateKeyEncoding: {
223+
type: 'pkcs8',
224+
format: 'der'
225+
}
226+
}, common.mustCall((err, publicKeyDER, privateKeyDER) => {
227+
assert.ifError(err);
228+
229+
assert(Buffer.isBuffer(publicKeyDER));
230+
assertApproximateSize(publicKeyDER, 74);
231+
232+
assert(Buffer.isBuffer(privateKeyDER));
233+
234+
const publicKey = { key: publicKeyDER, ...publicKeyEncoding };
235+
const privateKey = {
236+
key: privateKeyDER,
237+
format: 'der',
238+
type: 'pkcs8',
239+
passphrase: 'secret'
240+
};
241+
testEncryptDecrypt(publicKey, privateKey);
242+
testSignVerify(publicKey, privateKey);
243+
}));
177244
}
178245

179246
{

0 commit comments

Comments
 (0)