| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 2868f52 commit 0ebf44b
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -522,13 +522,19 @@ void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) { | |||
| 522 | 522 | if (!bio) | |
| 523 | 523 | return; | |
| 524 | 524 | ||
| 525 | - node::Utf8Value passphrase(env->isolate(), args[1]); | ||
| 525 | + ByteSource passphrase; | ||
| 526 | + if (args[1]->IsString()) | ||
| 527 | + passphrase = ByteSource::FromString(env, args[1].As<String>()); | ||
| 528 | + // This redirection is necessary because the PasswordCallback expects a | ||
| 529 | + // pointer to a pointer to the passphrase ByteSource to allow passing in | ||
| 530 | + // const ByteSources. | ||
| 531 | + const ByteSource* pass_ptr = &passphrase; | ||
| 526 | 532 | ||
| 527 | 533 | EVPKeyPointer key( | |
| 528 | 534 | PEM_read_bio_PrivateKey(bio.get(), | |
| 529 | 535 | nullptr, | |
| 530 | 536 | PasswordCallback, | |
| 531 | - *passphrase)); | ||
| 537 | + &pass_ptr)); | ||
| 532 | 538 | ||
| 533 | 539 | if (!key) { | |
| 534 | 540 | unsigned long err = ERR_get_error(); // NOLINT(runtime/int) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -251,8 +251,10 @@ struct KeyPairGenConfig final : public MemoryRetainer { | |||
| 251 | 251 | ||
| 252 | 252 | void MemoryInfo(MemoryTracker* tracker) const override { | |
| 253 | 253 | tracker->TrackField("key", key); | |
| 254 | - tracker->TrackFieldWithSize("private_key_encoding.passphrase", | ||
| 255 | - private_key_encoding.passphrase_.size()); | ||
| 254 | + if (!private_key_encoding.passphrase_.IsEmpty()) { | ||
| 255 | + tracker->TrackFieldWithSize("private_key_encoding.passphrase", | ||
| 256 | + private_key_encoding.passphrase_->size()); | ||
| 257 | + } | ||
| 256 | 258 | tracker->TrackField("params", params); | |
| 257 | 259 | } | |
| 258 | 260 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -210,8 +210,7 @@ ParseKeyResult ParsePrivateKey(EVPKeyPointer* pkey, | |||
| 210 | 210 | const PrivateKeyEncodingConfig& config, | |
| 211 | 211 | const char* key, | |
| 212 | 212 | size_t key_len) { | |
| 213 | - // OpenSSL needs a non-const pointer, that's why the const_cast is required. | ||
| 214 | - char* const passphrase = const_cast<char*>(config.passphrase_.get()); | ||
| 213 | + const ByteSource* passphrase = config.passphrase_.get(); | ||
| 215 | 214 | ||
| 216 | 215 | if (config.format_ == kKeyFormatPEM) { | |
| 217 | 216 | BIOPointer bio(BIO_new_mem_buf(key, key_len)); | |
@@ -221,7 +220,7 @@ ParseKeyResult ParsePrivateKey(EVPKeyPointer* pkey, | |||
| 221 | 220 | pkey->reset(PEM_read_bio_PrivateKey(bio.get(), | |
| 222 | 221 | nullptr, | |
| 223 | 222 | PasswordCallback, | |
| 224 | - passphrase)); | ||
| 223 | + &passphrase)); | ||
| 225 | 224 | } else { | |
| 226 | 225 | CHECK_EQ(config.format_, kKeyFormatDER); | |
| 227 | 226 | ||
@@ -238,7 +237,7 @@ ParseKeyResult ParsePrivateKey(EVPKeyPointer* pkey, | |||
| 238 | 237 | pkey->reset(d2i_PKCS8PrivateKey_bio(bio.get(), | |
| 239 | 238 | nullptr, | |
| 240 | 239 | PasswordCallback, | |
| 241 | - passphrase)); | ||
| 240 | + &passphrase)); | ||
| 242 | 241 | } else { | |
| 243 | 242 | PKCS8Pointer p8inf(d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr)); | |
| 244 | 243 | if (p8inf) | |
@@ -260,7 +259,7 @@ ParseKeyResult ParsePrivateKey(EVPKeyPointer* pkey, | |||
| 260 | 259 | return ParseKeyResult::kParseKeyOk; | |
| 261 | 260 | if (ERR_GET_LIB(err) == ERR_LIB_PEM && | |
| 262 | 261 | ERR_GET_REASON(err) == PEM_R_BAD_PASSWORD_READ) { | |
| 263 | - if (config.passphrase_.get() == nullptr) | ||
| 262 | + if (config.passphrase_.IsEmpty()) | ||
| 264 | 263 | return ParseKeyResult::kParseKeyNeedPassphrase; | |
| 265 | 264 | } | |
| 266 | 265 | return ParseKeyResult::kParseKeyFailed; | |
@@ -293,6 +292,28 @@ MaybeLocal<Value> WritePrivateKey( | |||
| 293 | 292 | BIOPointer bio(BIO_new(BIO_s_mem())); | |
| 294 | 293 | CHECK(bio); | |
| 295 | 294 | ||
| 295 | + // If an empty string was passed as the passphrase, the ByteSource might | ||
| 296 | + // contain a null pointer, which OpenSSL will ignore, causing it to invoke its | ||
| 297 | + // default passphrase callback, which would block the thread until the user | ||
| 298 | + // manually enters a passphrase. We could supply our own passphrase callback | ||
| 299 | + // to handle this special case, but it is easier to avoid passing a null | ||
| 300 | + // pointer to OpenSSL. | ||
| 301 | + char* pass = nullptr; | ||
| 302 | + size_t pass_len = 0; | ||
| 303 | + if (!config.passphrase_.IsEmpty()) { | ||
| 304 | + pass = const_cast<char*>(config.passphrase_->get()); | ||
| 305 | + pass_len = config.passphrase_->size(); | ||
| 306 | + if (pass == nullptr) { | ||
| 307 | + // OpenSSL will not actually dereference this pointer, so it can be any | ||
| 308 | + // non-null pointer. We cannot assert that directly, which is why we | ||
| 309 | + // intentionally use a pointer that will likely cause a segmentation fault | ||
| 310 | + // when dereferenced. | ||
| 311 | + CHECK_EQ(pass_len, 0); | ||
| 312 | + pass = reinterpret_cast<char*>(-1); | ||
| 313 | + CHECK_NE(pass, nullptr); | ||
| 314 | + } | ||
| 315 | + } | ||
| 316 | + | ||
| 296 | 317 | bool err; | |
| 297 | 318 | ||
| 298 | 319 | PKEncodingType encoding_type = config.type_.ToChecked(); | |
@@ -303,12 +324,11 @@ MaybeLocal<Value> WritePrivateKey( | |||
| 303 | 324 | RSAPointer rsa(EVP_PKEY_get1_RSA(pkey)); | |
| 304 | 325 | if (config.format_ == kKeyFormatPEM) { | |
| 305 | 326 | // Encode PKCS#1 as PEM. | |
| 306 | - const char* pass = config.passphrase_.get(); | ||
| 307 | 327 | err = PEM_write_bio_RSAPrivateKey( | |
| 308 | 328 | bio.get(), rsa.get(), | |
| 309 | 329 | config.cipher_, | |
| 310 | - reinterpret_cast<unsigned char*>(const_cast<char*>(pass)), | ||
| 311 | - config.passphrase_.size(), | ||
| 330 | + reinterpret_cast<unsigned char*>(pass), | ||
| 331 | + pass_len, | ||
| 312 | 332 | nullptr, nullptr) != 1; | |
| 313 | 333 | } else { | |
| 314 | 334 | // Encode PKCS#1 as DER. This does not permit encryption. | |
@@ -322,17 +342,17 @@ MaybeLocal<Value> WritePrivateKey( | |||
| 322 | 342 | err = PEM_write_bio_PKCS8PrivateKey( | |
| 323 | 343 | bio.get(), pkey, | |
| 324 | 344 | config.cipher_, | |
| 325 | - const_cast<char*>(config.passphrase_.get()), | ||
| 326 | - config.passphrase_.size(), | ||
| 345 | + pass, | ||
| 346 | + pass_len, | ||
| 327 | 347 | nullptr, nullptr) != 1; | |
| 328 | 348 | } else { | |
| 329 | 349 | // Encode PKCS#8 as DER. | |
| 330 | 350 | CHECK_EQ(config.format_, kKeyFormatDER); | |
| 331 | 351 | err = i2d_PKCS8PrivateKey_bio( | |
| 332 | 352 | bio.get(), pkey, | |
| 333 | 353 | config.cipher_, | |
| 334 | - const_cast<char*>(config.passphrase_.get()), | ||
| 335 | - config.passphrase_.size(), | ||
| 354 | + pass, | ||
| 355 | + pass_len, | ||
| 336 | 356 | nullptr, nullptr) != 1; | |
| 337 | 357 | } | |
| 338 | 358 | } else { | |
@@ -344,12 +364,11 @@ MaybeLocal<Value> WritePrivateKey( | |||
| 344 | 364 | ECKeyPointer ec_key(EVP_PKEY_get1_EC_KEY(pkey)); | |
| 345 | 365 | if (config.format_ == kKeyFormatPEM) { | |
| 346 | 366 | // Encode SEC1 as PEM. | |
| 347 | - const char* pass = config.passphrase_.get(); | ||
| 348 | 367 | err = PEM_write_bio_ECPrivateKey( | |
| 349 | 368 | bio.get(), ec_key.get(), | |
| 350 | 369 | config.cipher_, | |
| 351 | - reinterpret_cast<unsigned char*>(const_cast<char*>(pass)), | ||
| 352 | - config.passphrase_.size(), | ||
| 370 | + reinterpret_cast<unsigned char*>(pass), | ||
| 371 | + pass_len, | ||
| 353 | 372 | nullptr, nullptr) != 1; | |
| 354 | 373 | } else { | |
| 355 | 374 | // Encode SEC1 as DER. This does not permit encryption. | |
@@ -640,7 +659,8 @@ ManagedEVPPKey::GetPrivateKeyEncodingFromJs( | |||
| 640 | 659 | THROW_ERR_OUT_OF_RANGE(env, "passphrase is too big"); | |
| 641 | 660 | return NonCopyableMaybe<PrivateKeyEncodingConfig>(); | |
| 642 | 661 | } | |
| 643 | - result.passphrase_ = passphrase.ToNullTerminatedCopy(); | ||
| 662 | + result.passphrase_ = NonCopyableMaybe<ByteSource>( | ||
| 663 | + passphrase.ToNullTerminatedCopy()); | ||
| 644 | 664 | } else { | |
| 645 | 665 | CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase); | |
| 646 | 666 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -63,7 +63,10 @@ using PublicKeyEncodingConfig = AsymmetricKeyEncodingConfig; | |||
| 63 | 63 | ||
| 64 | 64 | struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig { | |
| 65 | 65 | const EVP_CIPHER* cipher_; | |
| 66 | - ByteSource passphrase_; | ||
| 66 | + // The ByteSource alone is not enough to distinguish between "no passphrase" | ||
| 67 | + // and a zero-length passphrase (which can be a null pointer), therefore, we | ||
| 68 | + // use a NonCopyableMaybe. | ||
| 69 | + NonCopyableMaybe<ByteSource> passphrase_; | ||
| 67 | 70 | }; | |
| 68 | 71 | ||
| 69 | 72 | // This uses the built-in reference counter of OpenSSL to manage an EVP_PKEY | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -74,13 +74,13 @@ bool EntropySource(unsigned char* buffer, size_t length) { | |||
| 74 | 74 | } | |
| 75 | 75 | ||
| 76 | 76 | int PasswordCallback(char* buf, int size, int rwflag, void* u) { | |
| 77 | - const char* passphrase = static_cast<char*>(u); | ||
| 77 | + const ByteSource* passphrase = *static_cast<const ByteSource**>(u); | ||
| 78 | 78 | if (passphrase != nullptr) { | |
| 79 | 79 | size_t buflen = static_cast<size_t>(size); | |
| 80 | - size_t len = strlen(passphrase); | ||
| 80 | + size_t len = passphrase->size(); | ||
| 81 | 81 | if (buflen < len) | |
| 82 | 82 | return -1; | |
| 83 | - memcpy(buf, passphrase, len); | ||
| 83 | + memcpy(buf, passphrase->get(), len); | ||
| 84 | 84 | return len; | |
| 85 | 85 | } | |
| 86 | 86 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -602,6 +602,15 @@ class NonCopyableMaybe { | |||
| 602 | 602 | return empty_; | |
| 603 | 603 | } | |
| 604 | 604 | ||
| 605 | + const T* get() const { | ||
| 606 | + return empty_ ? nullptr : &value_; | ||
| 607 | + } | ||
| 608 | + | ||
| 609 | + const T* operator->() const { | ||
| 610 | + CHECK(!empty_); | ||
| 611 | + return &value_; | ||
| 612 | + } | ||
| 613 | + | ||
| 605 | 614 | T&& Release() { | |
| 606 | 615 | CHECK_EQ(empty_, false); | |
| 607 | 616 | empty_ = true; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,6 +7,7 @@ if (!common.hasCrypto) | |||
| 7 | 7 | const assert = require('assert'); | |
| 8 | 8 | const { | |
| 9 | 9 | constants, | |
| 10 | + createPrivateKey, | ||
| 10 | 11 | createSign, | |
| 11 | 12 | createVerify, | |
| 12 | 13 | generateKeyPair, | |
@@ -1171,3 +1172,41 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher); | |||
| 1171 | 1172 | ); | |
| 1172 | 1173 | } | |
| 1173 | 1174 | } | |
| 1175 | + | ||
| 1176 | + { | ||
| 1177 | + // Passing an empty passphrase string should not cause OpenSSL's default | ||
| 1178 | + // passphrase prompt in the terminal. | ||
| 1179 | + // See https://github.com/nodejs/node/issues/35898. | ||
| 1180 | + | ||
| 1181 | + for (const type of ['pkcs1', 'pkcs8']) { | ||
| 1182 | + generateKeyPair('rsa', { | ||
| 1183 | + modulusLength: 1024, | ||
| 1184 | + privateKeyEncoding: { | ||
| 1185 | + type, | ||
| 1186 | + format: 'pem', | ||
| 1187 | + cipher: 'aes-256-cbc', | ||
| 1188 | + passphrase: '' | ||
| 1189 | + } | ||
| 1190 | + }, common.mustSucceed((publicKey, privateKey) => { | ||
| 1191 | + assert.strictEqual(publicKey.type, 'public'); | ||
| 1192 | + | ||
| 1193 | + for (const passphrase of ['', Buffer.alloc(0)]) { | ||
| 1194 | + const privateKeyObject = createPrivateKey({ | ||
| 1195 | + passphrase, | ||
| 1196 | + key: privateKey | ||
| 1197 | + }); | ||
| 1198 | + assert.strictEqual(privateKeyObject.asymmetricKeyType, 'rsa'); | ||
| 1199 | + } | ||
| 1200 | + | ||
| 1201 | + // Encrypting with an empty passphrase is not the same as not encrypting | ||
| 1202 | + // the key, and not specifying a passphrase should fail when decoding it. | ||
| 1203 | + assert.throws(() => { | ||
| 1204 | + return testSignVerify(publicKey, privateKey); | ||
| 1205 | + }, { | ||
| 1206 | + name: 'TypeError', | ||
| 1207 | + code: 'ERR_MISSING_PASSPHRASE', | ||
| 1208 | + message: 'Passphrase required for encrypted key' | ||
| 1209 | + }); | ||
| 1210 | + })); | ||
| 1211 | + } | ||
| 1212 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments