src: add mutex to ManagedEVPPKey class
This commit introduces a mutex field on the ManagedEVPPKey class intended to be used when multiple threads require access to an OpenSSL EVP_PKEY object. The motivation for this came from the work being done to upgrade Node.js to OpenSSL 3.0. OpenSSL objects, like EVP_PKEY, are not thread safe (see refs for details). In versions prior to OpenSSL 3.0 this was not noticeable and did not cause any issues (like incorrect logic or crashes), but with OpenSSL 3.0 this does cause issues if access to an EVP_PKEY instance is required from multiple threads without locking. In OpenSSL 3.0 when the evp_pkey_downgrade function is called, which downgrades an EVP_PKEY instance to a legacy version, it will clear all the fields of EVP_PKEY struct except the lock (#13374). But this also means that keymgmt and keydata will also be cleared, which other parts of the code base depends on, and those calls will either fail to export the key (returning null) or crash due to a segment fault. This same code works with OpenSSL 1.1.1 without locking and I think this is because there is no downgrade being done in OpenSSL 1.1.1. But even so, as far as I can tell there are no guarantees that these object are thread safe in 1.1.1 either and should be protected with a lock. PR-URL: https://github.com/nodejs/node/pull/36825 Refs: https://github.com/openssl/openssl/pull/13374 Refs: https://github.com/openssl/openssl/pull/13374 Refs: https://github.com/openssl/openssl/issues/2165) Refs: https://www.openssl.org/blog/blog/2017/02/21/threads Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
857fbdb13f
commit
79d44baae2
@ -133,11 +133,12 @@ Maybe<bool> GetDsaKeyDetail(
|
|||||||
const BIGNUM* p; // Modulus length
|
const BIGNUM* p; // Modulus length
|
||||||
const BIGNUM* q; // Divisor length
|
const BIGNUM* q; // Divisor length
|
||||||
|
|
||||||
ManagedEVPPKey pkey = key->GetAsymmetricKey();
|
ManagedEVPPKey m_pkey = key->GetAsymmetricKey();
|
||||||
int type = EVP_PKEY_id(pkey.get());
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
|
int type = EVP_PKEY_id(m_pkey.get());
|
||||||
CHECK(type == EVP_PKEY_DSA);
|
CHECK(type == EVP_PKEY_DSA);
|
||||||
|
|
||||||
DSA* dsa = EVP_PKEY_get0_DSA(pkey.get());
|
DSA* dsa = EVP_PKEY_get0_DSA(m_pkey.get());
|
||||||
CHECK_NOT_NULL(dsa);
|
CHECK_NOT_NULL(dsa);
|
||||||
|
|
||||||
DSA_get0_pqg(dsa, &p, &q, nullptr);
|
DSA_get0_pqg(dsa, &p, &q, nullptr);
|
||||||
|
@ -601,9 +601,11 @@ WebCryptoKeyExportStatus EC_Raw_Export(
|
|||||||
KeyObjectData* key_data,
|
KeyObjectData* key_data,
|
||||||
const ECKeyExportConfig& params,
|
const ECKeyExportConfig& params,
|
||||||
ByteSource* out) {
|
ByteSource* out) {
|
||||||
CHECK(key_data->GetAsymmetricKey());
|
ManagedEVPPKey m_pkey = key_data->GetAsymmetricKey();
|
||||||
|
CHECK(m_pkey);
|
||||||
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
|
|
||||||
EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(key_data->GetAsymmetricKey().get());
|
EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(m_pkey.get());
|
||||||
|
|
||||||
unsigned char* data;
|
unsigned char* data;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
@ -688,10 +690,11 @@ Maybe<bool> ExportJWKEcKey(
|
|||||||
Environment* env,
|
Environment* env,
|
||||||
std::shared_ptr<KeyObjectData> key,
|
std::shared_ptr<KeyObjectData> key,
|
||||||
Local<Object> target) {
|
Local<Object> target) {
|
||||||
ManagedEVPPKey pkey = key->GetAsymmetricKey();
|
ManagedEVPPKey m_pkey = key->GetAsymmetricKey();
|
||||||
CHECK_EQ(EVP_PKEY_id(pkey.get()), EVP_PKEY_EC);
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
|
CHECK_EQ(EVP_PKEY_id(m_pkey.get()), EVP_PKEY_EC);
|
||||||
|
|
||||||
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey.get());
|
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(m_pkey.get());
|
||||||
CHECK_NOT_NULL(ec);
|
CHECK_NOT_NULL(ec);
|
||||||
|
|
||||||
const EC_POINT* pub = EC_KEY_get0_public_key(ec);
|
const EC_POINT* pub = EC_KEY_get0_public_key(ec);
|
||||||
@ -893,10 +896,11 @@ Maybe<bool> GetEcKeyDetail(
|
|||||||
Environment* env,
|
Environment* env,
|
||||||
std::shared_ptr<KeyObjectData> key,
|
std::shared_ptr<KeyObjectData> key,
|
||||||
Local<Object> target) {
|
Local<Object> target) {
|
||||||
ManagedEVPPKey pkey = key->GetAsymmetricKey();
|
ManagedEVPPKey m_pkey = key->GetAsymmetricKey();
|
||||||
CHECK_EQ(EVP_PKEY_id(pkey.get()), EVP_PKEY_EC);
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
|
CHECK_EQ(EVP_PKEY_id(m_pkey.get()), EVP_PKEY_EC);
|
||||||
|
|
||||||
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey.get());
|
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(m_pkey.get());
|
||||||
CHECK_NOT_NULL(ec);
|
CHECK_NOT_NULL(ec);
|
||||||
|
|
||||||
const EC_GROUP* group = EC_KEY_get0_group(ec);
|
const EC_GROUP* group = EC_KEY_get0_group(ec);
|
||||||
|
@ -552,7 +552,8 @@ Maybe<bool> GetAsymmetricKeyDetail(
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ManagedEVPPKey::ManagedEVPPKey(EVPKeyPointer&& pkey) : pkey_(std::move(pkey)) {}
|
ManagedEVPPKey::ManagedEVPPKey(EVPKeyPointer&& pkey) : pkey_(std::move(pkey)),
|
||||||
|
mutex_(std::make_shared<Mutex>()) {}
|
||||||
|
|
||||||
ManagedEVPPKey::ManagedEVPPKey(const ManagedEVPPKey& that) {
|
ManagedEVPPKey::ManagedEVPPKey(const ManagedEVPPKey& that) {
|
||||||
*this = that;
|
*this = that;
|
||||||
@ -564,6 +565,8 @@ ManagedEVPPKey& ManagedEVPPKey::operator=(const ManagedEVPPKey& that) {
|
|||||||
if (pkey_)
|
if (pkey_)
|
||||||
EVP_PKEY_up_ref(pkey_.get());
|
EVP_PKEY_up_ref(pkey_.get());
|
||||||
|
|
||||||
|
mutex_ = that.mutex_;
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -575,6 +578,10 @@ EVP_PKEY* ManagedEVPPKey::get() const {
|
|||||||
return pkey_.get();
|
return pkey_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mutex* ManagedEVPPKey::mutex() const {
|
||||||
|
return mutex_.get();
|
||||||
|
}
|
||||||
|
|
||||||
void ManagedEVPPKey::MemoryInfo(MemoryTracker* tracker) const {
|
void ManagedEVPPKey::MemoryInfo(MemoryTracker* tracker) const {
|
||||||
tracker->TrackFieldWithSize("pkey",
|
tracker->TrackFieldWithSize("pkey",
|
||||||
!pkey_ ? 0 : kSizeOf_EVP_PKEY +
|
!pkey_ ? 0 : kSizeOf_EVP_PKEY +
|
||||||
@ -1326,8 +1333,10 @@ WebCryptoKeyExportStatus PKEY_SPKI_Export(
|
|||||||
KeyObjectData* key_data,
|
KeyObjectData* key_data,
|
||||||
ByteSource* out) {
|
ByteSource* out) {
|
||||||
CHECK_EQ(key_data->GetKeyType(), kKeyTypePublic);
|
CHECK_EQ(key_data->GetKeyType(), kKeyTypePublic);
|
||||||
|
ManagedEVPPKey m_pkey = key_data->GetAsymmetricKey();
|
||||||
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
BIOPointer bio(BIO_new(BIO_s_mem()));
|
BIOPointer bio(BIO_new(BIO_s_mem()));
|
||||||
if (!i2d_PUBKEY_bio(bio.get(), key_data->GetAsymmetricKey().get()))
|
if (!i2d_PUBKEY_bio(bio.get(), m_pkey.get()))
|
||||||
return WebCryptoKeyExportStatus::FAILED;
|
return WebCryptoKeyExportStatus::FAILED;
|
||||||
|
|
||||||
*out = ByteSource::FromBIO(bio);
|
*out = ByteSource::FromBIO(bio);
|
||||||
@ -1338,8 +1347,11 @@ WebCryptoKeyExportStatus PKEY_PKCS8_Export(
|
|||||||
KeyObjectData* key_data,
|
KeyObjectData* key_data,
|
||||||
ByteSource* out) {
|
ByteSource* out) {
|
||||||
CHECK_EQ(key_data->GetKeyType(), kKeyTypePrivate);
|
CHECK_EQ(key_data->GetKeyType(), kKeyTypePrivate);
|
||||||
|
ManagedEVPPKey m_pkey = key_data->GetAsymmetricKey();
|
||||||
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
|
|
||||||
BIOPointer bio(BIO_new(BIO_s_mem()));
|
BIOPointer bio(BIO_new(BIO_s_mem()));
|
||||||
PKCS8Pointer p8inf(EVP_PKEY2PKCS8(key_data->GetAsymmetricKey().get()));
|
PKCS8Pointer p8inf(EVP_PKEY2PKCS8(m_pkey.get()));
|
||||||
if (!i2d_PKCS8_PRIV_KEY_INFO_bio(bio.get(), p8inf.get()))
|
if (!i2d_PKCS8_PRIV_KEY_INFO_bio(bio.get(), p8inf.get()))
|
||||||
return WebCryptoKeyExportStatus::FAILED;
|
return WebCryptoKeyExportStatus::FAILED;
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ class ManagedEVPPKey : public MemoryRetainer {
|
|||||||
|
|
||||||
operator bool() const;
|
operator bool() const;
|
||||||
EVP_PKEY* get() const;
|
EVP_PKEY* get() const;
|
||||||
|
Mutex* mutex() const;
|
||||||
|
|
||||||
void MemoryInfo(MemoryTracker* tracker) const override;
|
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||||
SET_MEMORY_INFO_NAME(ManagedEVPPKey)
|
SET_MEMORY_INFO_NAME(ManagedEVPPKey)
|
||||||
@ -127,6 +128,7 @@ class ManagedEVPPKey : public MemoryRetainer {
|
|||||||
size_t size_of_public_key() const;
|
size_t size_of_public_key() const;
|
||||||
|
|
||||||
EVPKeyPointer pkey_;
|
EVPKeyPointer pkey_;
|
||||||
|
std::shared_ptr<Mutex> mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Objects of this class can safely be shared among threads.
|
// Objects of this class can safely be shared among threads.
|
||||||
|
@ -191,9 +191,10 @@ WebCryptoCipherStatus RSA_Cipher(
|
|||||||
const ByteSource& in,
|
const ByteSource& in,
|
||||||
ByteSource* out) {
|
ByteSource* out) {
|
||||||
CHECK_NE(key_data->GetKeyType(), kKeyTypeSecret);
|
CHECK_NE(key_data->GetKeyType(), kKeyTypeSecret);
|
||||||
|
ManagedEVPPKey m_pkey = key_data->GetAsymmetricKey();
|
||||||
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
|
|
||||||
EVPKeyCtxPointer ctx(
|
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(m_pkey.get(), nullptr));
|
||||||
EVP_PKEY_CTX_new(key_data->GetAsymmetricKey().get(), nullptr));
|
|
||||||
|
|
||||||
if (!ctx || init(ctx.get()) <= 0)
|
if (!ctx || init(ctx.get()) <= 0)
|
||||||
return WebCryptoCipherStatus::FAILED;
|
return WebCryptoCipherStatus::FAILED;
|
||||||
@ -363,17 +364,18 @@ Maybe<bool> ExportJWKRsaKey(
|
|||||||
Environment* env,
|
Environment* env,
|
||||||
std::shared_ptr<KeyObjectData> key,
|
std::shared_ptr<KeyObjectData> key,
|
||||||
Local<Object> target) {
|
Local<Object> target) {
|
||||||
ManagedEVPPKey pkey = key->GetAsymmetricKey();
|
ManagedEVPPKey m_pkey = key->GetAsymmetricKey();
|
||||||
int type = EVP_PKEY_id(pkey.get());
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
|
int type = EVP_PKEY_id(m_pkey.get());
|
||||||
CHECK(type == EVP_PKEY_RSA || type == EVP_PKEY_RSA_PSS);
|
CHECK(type == EVP_PKEY_RSA || type == EVP_PKEY_RSA_PSS);
|
||||||
|
|
||||||
// TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL
|
// TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL
|
||||||
// versions older than 1.1.1e via FIPS / dynamic linking.
|
// versions older than 1.1.1e via FIPS / dynamic linking.
|
||||||
RSA* rsa;
|
RSA* rsa;
|
||||||
if (OpenSSL_version_num() >= 0x1010105fL) {
|
if (OpenSSL_version_num() >= 0x1010105fL) {
|
||||||
rsa = EVP_PKEY_get0_RSA(pkey.get());
|
rsa = EVP_PKEY_get0_RSA(m_pkey.get());
|
||||||
} else {
|
} else {
|
||||||
rsa = static_cast<RSA*>(EVP_PKEY_get0(pkey.get()));
|
rsa = static_cast<RSA*>(EVP_PKEY_get0(m_pkey.get()));
|
||||||
}
|
}
|
||||||
CHECK_NOT_NULL(rsa);
|
CHECK_NOT_NULL(rsa);
|
||||||
|
|
||||||
@ -511,17 +513,18 @@ Maybe<bool> GetRsaKeyDetail(
|
|||||||
const BIGNUM* e; // Public Exponent
|
const BIGNUM* e; // Public Exponent
|
||||||
const BIGNUM* n; // Modulus
|
const BIGNUM* n; // Modulus
|
||||||
|
|
||||||
ManagedEVPPKey pkey = key->GetAsymmetricKey();
|
ManagedEVPPKey m_pkey = key->GetAsymmetricKey();
|
||||||
int type = EVP_PKEY_id(pkey.get());
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
|
int type = EVP_PKEY_id(m_pkey.get());
|
||||||
CHECK(type == EVP_PKEY_RSA || type == EVP_PKEY_RSA_PSS);
|
CHECK(type == EVP_PKEY_RSA || type == EVP_PKEY_RSA_PSS);
|
||||||
|
|
||||||
// TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL
|
// TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL
|
||||||
// versions older than 1.1.1e via FIPS / dynamic linking.
|
// versions older than 1.1.1e via FIPS / dynamic linking.
|
||||||
RSA* rsa;
|
RSA* rsa;
|
||||||
if (OpenSSL_version_num() >= 0x1010105fL) {
|
if (OpenSSL_version_num() >= 0x1010105fL) {
|
||||||
rsa = EVP_PKEY_get0_RSA(pkey.get());
|
rsa = EVP_PKEY_get0_RSA(m_pkey.get());
|
||||||
} else {
|
} else {
|
||||||
rsa = static_cast<RSA*>(EVP_PKEY_get0(pkey.get()));
|
rsa = static_cast<RSA*>(EVP_PKEY_get0(m_pkey.get()));
|
||||||
}
|
}
|
||||||
CHECK_NOT_NULL(rsa);
|
CHECK_NOT_NULL(rsa);
|
||||||
|
|
||||||
|
@ -96,8 +96,8 @@ AllocatedBuffer Node_SignFinal(Environment* env,
|
|||||||
return AllocatedBuffer();
|
return AllocatedBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetDefaultSignPadding(const ManagedEVPPKey& key) {
|
int GetDefaultSignPadding(const ManagedEVPPKey& m_pkey) {
|
||||||
return EVP_PKEY_id(key.get()) == EVP_PKEY_RSA_PSS ? RSA_PKCS1_PSS_PADDING :
|
return EVP_PKEY_id(m_pkey.get()) == EVP_PKEY_RSA_PSS ? RSA_PKCS1_PSS_PADDING :
|
||||||
RSA_PKCS1_PADDING;
|
RSA_PKCS1_PADDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -752,11 +752,11 @@ Maybe<bool> SignTraits::AdditionalConfig(
|
|||||||
}
|
}
|
||||||
// If this is an EC key (assuming ECDSA) we need to convert the
|
// If this is an EC key (assuming ECDSA) we need to convert the
|
||||||
// the signature from WebCrypto format into DER format...
|
// the signature from WebCrypto format into DER format...
|
||||||
if (EVP_PKEY_id(params->key->GetAsymmetricKey().get()) == EVP_PKEY_EC) {
|
ManagedEVPPKey m_pkey = params->key->GetAsymmetricKey();
|
||||||
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
|
if (EVP_PKEY_id(m_pkey.get()) == EVP_PKEY_EC) {
|
||||||
params->signature =
|
params->signature =
|
||||||
ConvertFromWebCryptoSignature(
|
ConvertFromWebCryptoSignature(m_pkey, signature.ToByteSource());
|
||||||
params->key->GetAsymmetricKey(),
|
|
||||||
signature.ToByteSource());
|
|
||||||
} else {
|
} else {
|
||||||
params->signature = mode == kCryptoJobAsync
|
params->signature = mode == kCryptoJobAsync
|
||||||
? signature.ToCopy()
|
? signature.ToCopy()
|
||||||
@ -774,6 +774,8 @@ bool SignTraits::DeriveBits(
|
|||||||
EVPMDPointer context(EVP_MD_CTX_new());
|
EVPMDPointer context(EVP_MD_CTX_new());
|
||||||
EVP_PKEY_CTX* ctx = nullptr;
|
EVP_PKEY_CTX* ctx = nullptr;
|
||||||
|
|
||||||
|
ManagedEVPPKey m_pkey = params.key->GetAsymmetricKey();
|
||||||
|
Mutex::ScopedLock lock(*m_pkey.mutex());
|
||||||
switch (params.mode) {
|
switch (params.mode) {
|
||||||
case SignConfiguration::kSign:
|
case SignConfiguration::kSign:
|
||||||
CHECK_EQ(params.key->GetKeyType(), kKeyTypePrivate);
|
CHECK_EQ(params.key->GetKeyType(), kKeyTypePrivate);
|
||||||
@ -782,7 +784,7 @@ bool SignTraits::DeriveBits(
|
|||||||
&ctx,
|
&ctx,
|
||||||
params.digest,
|
params.digest,
|
||||||
nullptr,
|
nullptr,
|
||||||
params.key->GetAsymmetricKey().get())) {
|
m_pkey.get())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -793,7 +795,7 @@ bool SignTraits::DeriveBits(
|
|||||||
&ctx,
|
&ctx,
|
||||||
params.digest,
|
params.digest,
|
||||||
nullptr,
|
nullptr,
|
||||||
params.key->GetAsymmetricKey().get())) {
|
m_pkey.get())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -801,13 +803,13 @@ bool SignTraits::DeriveBits(
|
|||||||
|
|
||||||
int padding = params.flags & SignConfiguration::kHasPadding
|
int padding = params.flags & SignConfiguration::kHasPadding
|
||||||
? params.padding
|
? params.padding
|
||||||
: GetDefaultSignPadding(params.key->GetAsymmetricKey());
|
: GetDefaultSignPadding(m_pkey);
|
||||||
|
|
||||||
Maybe<int> salt_length = params.flags & SignConfiguration::kHasSaltLength
|
Maybe<int> salt_length = params.flags & SignConfiguration::kHasSaltLength
|
||||||
? Just<int>(params.salt_length) : Nothing<int>();
|
? Just<int>(params.salt_length) : Nothing<int>();
|
||||||
|
|
||||||
if (!ApplyRSAOptions(
|
if (!ApplyRSAOptions(
|
||||||
params.key->GetAsymmetricKey(),
|
m_pkey,
|
||||||
ctx,
|
ctx,
|
||||||
padding,
|
padding,
|
||||||
salt_length)) {
|
salt_length)) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user