@@ -29,7 +29,8 @@ KeyObjectData TryParsePrivateKey(std::shared_ptr<ArrayBuffer> key, std::optional
2929
3030 if (passphrase.has_value ()) {
3131 auto & passphrase_ptr = passphrase.value ();
32- config.passphrase = std::make_optional (ncrypto::DataPointer (passphrase_ptr->data (), passphrase_ptr->size ()));
32+ config.passphrase =
33+ std::make_optional (ncrypto::DataPointer::Copy (ncrypto::Buffer<const void >{passphrase_ptr->data (), passphrase_ptr->size ()}));
3334 }
3435
3536 auto buffer = ncrypto::Buffer<const unsigned char >{key->data (), key->size ()};
@@ -44,13 +45,21 @@ KeyObjectData TryParsePrivateKey(std::shared_ptr<ArrayBuffer> key, std::optional
4445
4546 if (res.error .has_value () && res.error .value () == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE ) {
4647 throw std::runtime_error (" Passphrase required for encrypted key" );
47- } else {
48- // Get OpenSSL error details
49- unsigned long err = ERR_get_error ();
50- char err_buf[256 ];
51- ERR_error_string_n (err, err_buf, sizeof (err_buf));
52- throw std::runtime_error (" Failed to read private key: " + std::string (err_buf));
5348 }
49+
50+ // ncrypto only maps ERR_LIB_PEM/PEM_R_BAD_PASSWORD_READ to NEED_PASSPHRASE. On OpenSSL 3.6+
51+ // PEM_read_bio_PrivateKey surfaces a missing-passphrase callback as
52+ // ERR_R_INTERRUPTED_OR_CANCELLED on ERR_LIB_CRYPTO instead.
53+ if (!passphrase.has_value () && res.openssl_error .has_value () &&
54+ ERR_GET_REASON (res.openssl_error .value ()) == ERR_R_INTERRUPTED_OR_CANCELLED ) {
55+ throw std::runtime_error (" Passphrase required for encrypted key" );
56+ }
57+
58+ // Get OpenSSL error details
59+ unsigned long err = ERR_get_error ();
60+ char err_buf[256 ];
61+ ERR_error_string_n (err, err_buf, sizeof (err_buf));
62+ throw std::runtime_error (" Failed to read private key: " + std::string (err_buf));
5463}
5564
5665KeyObjectData::KeyObjectData (std::nullptr_t ) : key_type_(KeyType::SECRET ) {}
@@ -133,7 +142,8 @@ KeyObjectData KeyObjectData::GetPublicOrPrivateKey(std::shared_ptr<ArrayBuffer>
133142 auto config = GetPrivateKeyEncodingConfig (actualFormat, type.value ());
134143 if (passphrase.has_value ()) {
135144 auto & passphrase_ptr = passphrase.value ();
136- config.passphrase = std::make_optional (ncrypto::DataPointer (passphrase_ptr->data (), passphrase_ptr->size ()));
145+ config.passphrase =
146+ std::make_optional (ncrypto::DataPointer::Copy (ncrypto::Buffer<const void >{passphrase_ptr->data (), passphrase_ptr->size ()}));
137147 }
138148 ERR_clear_error ();
139149 auto private_res = ncrypto::EVPKeyPointer::TryParsePrivateKey (config, buffer);
@@ -155,7 +165,8 @@ KeyObjectData KeyObjectData::GetPublicOrPrivateKey(std::shared_ptr<ArrayBuffer>
155165 auto config = GetPrivateKeyEncodingConfig (actualFormat, actualType);
156166 if (passphrase.has_value ()) {
157167 auto & passphrase_ptr = passphrase.value ();
158- config.passphrase = std::make_optional (ncrypto::DataPointer (passphrase_ptr->data (), passphrase_ptr->size ()));
168+ config.passphrase =
169+ std::make_optional (ncrypto::DataPointer::Copy (ncrypto::Buffer<const void >{passphrase_ptr->data (), passphrase_ptr->size ()}));
159170 }
160171
161172 ERR_clear_error ();
@@ -181,25 +192,42 @@ KeyObjectData KeyObjectData::GetPublicOrPrivateKey(std::shared_ptr<ArrayBuffer>
181192 auto private_config = GetPrivateKeyEncodingConfig (actualFormat, type.value ());
182193 if (passphrase.has_value ()) {
183194 auto & passphrase_ptr = passphrase.value ();
184- private_config.passphrase = std::make_optional (ncrypto::DataPointer (passphrase_ptr->data (), passphrase_ptr->size ()));
195+ private_config.passphrase =
196+ std::make_optional (ncrypto::DataPointer::Copy (ncrypto::Buffer<const void >{passphrase_ptr->data (), passphrase_ptr->size ()}));
185197 }
186198 auto res = ncrypto::EVPKeyPointer::TryParsePrivateKey (private_config, buffer);
187199 if (res) {
188200 return CreateAsymmetric (KeyType::PRIVATE , std::move (res.value ));
189201 }
190202 } else {
191- // If no encoding type specified, try both SPKI and PKCS8
203+ // If no encoding type specified, try both SPKI and PKCS8. Clear the OpenSSL error
204+ // queue between attempts so a failed first parse doesn't taint ncrypto's
205+ // post-parse ERR_peek_error() check on the second.
206+ ERR_clear_error ();
192207 auto public_config = GetPublicKeyEncodingConfig (actualFormat, KeyEncoding::SPKI );
193208 auto public_res = ncrypto::EVPKeyPointer::TryParsePublicKey (public_config, buffer);
194209 if (public_res) {
195210 return CreateAsymmetric (KeyType::PUBLIC , std::move (public_res.value ));
196211 }
197212
213+ ERR_clear_error ();
198214 auto private_config = GetPrivateKeyEncodingConfig (actualFormat, KeyEncoding::PKCS8 );
215+ if (passphrase.has_value ()) {
216+ auto & passphrase_ptr = passphrase.value ();
217+ private_config.passphrase =
218+ std::make_optional (ncrypto::DataPointer::Copy (ncrypto::Buffer<const void >{passphrase_ptr->data (), passphrase_ptr->size ()}));
219+ }
199220 auto private_res = ncrypto::EVPKeyPointer::TryParsePrivateKey (private_config, buffer);
200221 if (private_res) {
201222 return CreateAsymmetric (KeyType::PRIVATE , std::move (private_res.value ));
202223 }
224+ if (private_res.error .has_value () && private_res.error .value () == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE ) {
225+ throw std::runtime_error (" Passphrase required for encrypted key" );
226+ }
227+ if (!passphrase.has_value () && private_res.openssl_error .has_value () &&
228+ ERR_GET_REASON (private_res.openssl_error .value ()) == ERR_R_INTERRUPTED_OR_CANCELLED ) {
229+ throw std::runtime_error (" Passphrase required for encrypted key" );
230+ }
203231 }
204232 throw std::runtime_error (" Failed to read DER asymmetric key" );
205233 }
@@ -232,7 +260,8 @@ KeyObjectData KeyObjectData::GetPrivateKey(std::shared_ptr<ArrayBuffer> key, std
232260 auto private_config = GetPrivateKeyEncodingConfig (actualFormat, primaryEncoding);
233261 if (passphrase.has_value ()) {
234262 auto & passphrase_ptr = passphrase.value ();
235- private_config.passphrase = std::make_optional (ncrypto::DataPointer (passphrase_ptr->data (), passphrase_ptr->size ()));
263+ private_config.passphrase =
264+ std::make_optional (ncrypto::DataPointer::Copy (ncrypto::Buffer<const void >{passphrase_ptr->data (), passphrase_ptr->size ()}));
236265 }
237266
238267 // Clear any existing OpenSSL errors before parsing
@@ -242,22 +271,23 @@ KeyObjectData KeyObjectData::GetPrivateKey(std::shared_ptr<ArrayBuffer> key, std
242271 if (res) {
243272 return CreateAsymmetric (KeyType::PRIVATE , std::move (res.value ));
244273 }
274+ if (res.error .has_value () && res.error .value () == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE ) {
275+ throw std::runtime_error (" Passphrase required for encrypted key" );
276+ }
245277
246- // If no specific encoding was provided, try other encodings as fallback
278+ // If no specific encoding was provided, try other encodings as fallback.
279+ // SEC1/PKCS1 DER are never encrypted, so passphrase is irrelevant here.
247280 if (!type.has_value ()) {
248281 std::vector<KeyEncoding> fallbackEncodings = {KeyEncoding::SEC1 , KeyEncoding::PKCS1 };
249282 for (auto encoding : fallbackEncodings) {
250283 auto config = GetPrivateKeyEncodingConfig (actualFormat, encoding);
251- if (passphrase.has_value ()) {
252- auto & passphrase_ptr = passphrase.value ();
253- config.passphrase = std::make_optional (ncrypto::DataPointer (passphrase_ptr->data (), passphrase_ptr->size ()));
254- }
255284 auto fallback_res = ncrypto::EVPKeyPointer::TryParsePrivateKey (config, buffer);
256285 if (fallback_res) {
257286 return CreateAsymmetric (KeyType::PRIVATE , std::move (fallback_res.value ));
258287 }
259288 }
260289 }
290+
261291 throw std::runtime_error (" Failed to read DER private key" );
262292 }
263293 }
0 commit comments