Skip to content

Commit 1333dcc

Browse files
tniessenrvagg
authored andcommitted
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 70e463c commit 1333dcc

File tree

3 files changed

+134
-39
lines changed

3 files changed

+134
-39
lines changed

src/node_crypto.cc

+66-39
Original file line numberDiff line numberDiff line change
@@ -2821,6 +2821,59 @@ static MaybeLocal<Value> WritePublicKey(Environment* env,
28212821
return BIOToStringOrBuffer(env, bio.get(), config.format_);
28222822
}
28232823

2824+
static bool IsASN1Sequence(const unsigned char* data, size_t size,
2825+
size_t* data_offset, size_t* data_size) {
2826+
if (size < 2 || data[0] != 0x30)
2827+
return false;
2828+
2829+
if (data[1] & 0x80) {
2830+
// Long form.
2831+
size_t n_bytes = data[1] & ~0x80;
2832+
if (n_bytes + 2 > size || n_bytes > sizeof(size_t))
2833+
return false;
2834+
size_t length = 0;
2835+
for (size_t i = 0; i < n_bytes; i++)
2836+
length = (length << 8) | data[i + 2];
2837+
*data_offset = 2 + n_bytes;
2838+
*data_size = std::min(size - 2 - n_bytes, length);
2839+
} else {
2840+
// Short form.
2841+
*data_offset = 2;
2842+
*data_size = std::min<size_t>(size - 2, data[1]);
2843+
}
2844+
2845+
return true;
2846+
}
2847+
2848+
static bool IsRSAPrivateKey(const unsigned char* data, size_t size) {
2849+
// Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE.
2850+
size_t offset, len;
2851+
if (!IsASN1Sequence(data, size, &offset, &len))
2852+
return false;
2853+
2854+
// An RSAPrivateKey sequence always starts with a single-byte integer whose
2855+
// value is either 0 or 1, whereas an RSAPublicKey starts with the modulus
2856+
// (which is the product of two primes and therefore at least 4), so we can
2857+
// decide the type of the structure based on the first three bytes of the
2858+
// sequence.
2859+
return len >= 3 &&
2860+
data[offset] == 2 &&
2861+
data[offset + 1] == 1 &&
2862+
!(data[offset + 2] & 0xfe);
2863+
}
2864+
2865+
static bool IsEncryptedPrivateKeyInfo(const unsigned char* data, size_t size) {
2866+
// Both PrivateKeyInfo and EncryptedPrivateKeyInfo start with a SEQUENCE.
2867+
size_t offset, len;
2868+
if (!IsASN1Sequence(data, size, &offset, &len))
2869+
return false;
2870+
2871+
// A PrivateKeyInfo sequence always starts with an integer whereas an
2872+
// EncryptedPrivateKeyInfo starts with an AlgorithmIdentifier.
2873+
return len >= 1 &&
2874+
data[offset] != 2;
2875+
}
2876+
28242877
static EVPKeyPointer ParsePrivateKey(const PrivateKeyEncodingConfig& config,
28252878
const char* key,
28262879
size_t key_len) {
@@ -2846,11 +2899,19 @@ static EVPKeyPointer ParsePrivateKey(const PrivateKeyEncodingConfig& config,
28462899
BIOPointer bio(BIO_new_mem_buf(key, key_len));
28472900
if (!bio)
28482901
return pkey;
2849-
char* pass = const_cast<char*>(config.passphrase_.get());
2850-
pkey.reset(d2i_PKCS8PrivateKey_bio(bio.get(),
2851-
nullptr,
2852-
PasswordCallback,
2853-
pass));
2902+
2903+
if (IsEncryptedPrivateKeyInfo(
2904+
reinterpret_cast<const unsigned char*>(key), key_len)) {
2905+
char* pass = const_cast<char*>(config.passphrase_.get());
2906+
pkey.reset(d2i_PKCS8PrivateKey_bio(bio.get(),
2907+
nullptr,
2908+
PasswordCallback,
2909+
pass));
2910+
} else {
2911+
PKCS8Pointer p8inf(d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr));
2912+
if (p8inf)
2913+
pkey.reset(EVP_PKCS82PKEY(p8inf.get()));
2914+
}
28542915
} else {
28552916
CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSEC1);
28562917
const unsigned char* p = reinterpret_cast<const unsigned char*>(key);
@@ -3066,40 +3127,6 @@ static ManagedEVPPKey GetPrivateKeyFromJs(
30663127
}
30673128
}
30683129

3069-
static bool IsRSAPrivateKey(const unsigned char* data, size_t size) {
3070-
// Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE.
3071-
if (size >= 2 && data[0] == 0x30) {
3072-
size_t offset;
3073-
if (data[1] & 0x80) {
3074-
// Long form.
3075-
size_t n_bytes = data[1] & ~0x80;
3076-
if (n_bytes + 2 > size || n_bytes > sizeof(size_t))
3077-
return false;
3078-
size_t i, length = 0;
3079-
for (i = 0; i < n_bytes; i++)
3080-
length = (length << 8) | data[i + 2];
3081-
offset = 2 + n_bytes;
3082-
size = std::min(size, length + 2);
3083-
} else {
3084-
// Short form.
3085-
offset = 2;
3086-
size = std::min<size_t>(size, data[1] + 2);
3087-
}
3088-
3089-
// An RSAPrivateKey sequence always starts with a single-byte integer whose
3090-
// value is either 0 or 1, whereas an RSAPublicKey starts with the modulus
3091-
// (which is the product of two primes and therefore at least 4), so we can
3092-
// decide the type of the structure based on the first three bytes of the
3093-
// sequence.
3094-
return size - offset >= 3 &&
3095-
data[offset] == 2 &&
3096-
data[offset + 1] == 1 &&
3097-
!(data[offset + 2] & 0xfe);
3098-
}
3099-
3100-
return false;
3101-
}
3102-
31033130
static ManagedEVPPKey GetPublicOrPrivateKeyFromJs(
31043131
const FunctionCallbackInfo<Value>& args,
31053132
unsigned int* offset,

src/node_crypto.h

+1
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

+67
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)