Skip to content

Commit

Permalink
src,test: support dynamically linking OpenSSL 3.0
Browse files Browse the repository at this point in the history
This commit enables node to dynamically link against OpenSSL 3.0.

The motivation for opening this PR even though OpenSSL 3.0 has not been
released yet is to allow a nightly CI job to be created. This will
allow us stay on top of changes required for OpenSSL 3.0, and also to
make sure that changes to node crypto do not cause issues when linking
to OpenSSL 3.0.

PR-URL: #37669
Refs: #29817
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <midawson@redhat.com>
  • Loading branch information
danbev committed Mar 16, 2021
1 parent 6527e04 commit 640fe94
Show file tree
Hide file tree
Showing 47 changed files with 513 additions and 297 deletions.
6 changes: 5 additions & 1 deletion node.gypi
Expand Up @@ -361,7 +361,11 @@
],
}],
],
}]]
}, {
# Set 1.0.0 as the API compability level to avoid the
# deprecation warnings when using OpenSSL 3.0.
'defines': ['OPENSSL_API_COMPAT=0x10000000L'],
}]]

}, {
'defines': [ 'HAVE_OPENSSL=0' ]
Expand Down
11 changes: 10 additions & 1 deletion src/crypto/crypto_cipher.cc
Expand Up @@ -342,8 +342,11 @@ void CipherBase::Init(const char* cipher_type,
unsigned int auth_tag_len) {
HandleScope scope(env()->isolate());
MarkPopErrorOnReturn mark_pop_error_on_return;

#if OPENSSL_VERSION_MAJOR >= 3
if (EVP_default_properties_is_fips_enabled(nullptr)) {
#else
if (FIPS_mode()) {
#endif
return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
"crypto.createCipher() is not supported in FIPS mode.");
}
Expand Down Expand Up @@ -527,7 +530,13 @@ bool CipherBase::InitAuthenticated(
}

// TODO(tniessen) Support CCM decryption in FIPS mode

#if OPENSSL_VERSION_MAJOR >= 3
if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher &&
EVP_default_properties_is_fips_enabled(nullptr)) {
#else
if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher && FIPS_mode()) {
#endif
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(),
"CCM encryption not supported in FIPS mode");
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/crypto_dsa.cc
Expand Up @@ -138,7 +138,7 @@ Maybe<bool> GetDsaKeyDetail(
int type = EVP_PKEY_id(m_pkey.get());
CHECK(type == EVP_PKEY_DSA);

DSA* dsa = EVP_PKEY_get0_DSA(m_pkey.get());
const DSA* dsa = EVP_PKEY_get0_DSA(m_pkey.get());
CHECK_NOT_NULL(dsa);

DSA_get0_pqg(dsa, &p, &q, nullptr);
Expand Down
40 changes: 24 additions & 16 deletions src/crypto/crypto_ec.cc
Expand Up @@ -463,19 +463,22 @@ bool ECDHBitsTraits::DeriveBits(

char* data = nullptr;
size_t len = 0;
ManagedEVPPKey m_privkey = params.private_->GetAsymmetricKey();
ManagedEVPPKey m_pubkey = params.public_->GetAsymmetricKey();

switch (params.id_) {
case EVP_PKEY_X25519:
// Fall through
case EVP_PKEY_X448: {
EVPKeyCtxPointer ctx(
EVP_PKEY_CTX_new(
params.private_->GetAsymmetricKey().get(),
nullptr));
EVPKeyCtxPointer ctx = nullptr;
{
ctx.reset(EVP_PKEY_CTX_new(m_privkey.get(), nullptr));
}
Mutex::ScopedLock pub_lock(*m_pubkey.mutex());
if (EVP_PKEY_derive_init(ctx.get()) <= 0 ||
EVP_PKEY_derive_set_peer(
ctx.get(),
params.public_->GetAsymmetricKey().get()) <= 0 ||
m_pubkey.get()) <= 0 ||
EVP_PKEY_derive(ctx.get(), nullptr, &len) <= 0) {
return false;
}
Expand All @@ -492,10 +495,14 @@ bool ECDHBitsTraits::DeriveBits(
break;
}
default: {
const EC_KEY* private_key =
EVP_PKEY_get0_EC_KEY(params.private_->GetAsymmetricKey().get());
const EC_KEY* public_key =
EVP_PKEY_get0_EC_KEY(params.public_->GetAsymmetricKey().get());
const EC_KEY* private_key;
{
Mutex::ScopedLock priv_lock(*m_privkey.mutex());
private_key = EVP_PKEY_get0_EC_KEY(m_privkey.get());
}

Mutex::ScopedLock pub_lock(*m_pubkey.mutex());
const EC_KEY* public_key = EVP_PKEY_get0_EC_KEY(m_pubkey.get());

const EC_GROUP* group = EC_KEY_get0_group(private_key);
if (group == nullptr)
Expand Down Expand Up @@ -607,7 +614,7 @@ WebCryptoKeyExportStatus EC_Raw_Export(
CHECK(m_pkey);
Mutex::ScopedLock lock(*m_pkey.mutex());

EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(m_pkey.get());
const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(m_pkey.get());

unsigned char* data;
size_t len = 0;
Expand All @@ -627,10 +634,10 @@ WebCryptoKeyExportStatus EC_Raw_Export(
}
CHECK_NOT_NULL(fn);
// Get the size of the raw key data
if (fn(key_data->GetAsymmetricKey().get(), nullptr, &len) == 0)
if (fn(m_pkey.get(), nullptr, &len) == 0)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
data = MallocOpenSSL<unsigned char>(len);
if (fn(key_data->GetAsymmetricKey().get(), data, &len) == 0)
if (fn(m_pkey.get(), data, &len) == 0)
return WebCryptoKeyExportStatus::INVALID_KEY_TYPE;
} else {
if (key_data->GetKeyType() != kKeyTypePublic)
Expand Down Expand Up @@ -696,7 +703,7 @@ Maybe<bool> ExportJWKEcKey(
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(m_pkey.get());
const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(m_pkey.get());
CHECK_NOT_NULL(ec);

const EC_POINT* pub = EC_KEY_get0_public_key(ec);
Expand Down Expand Up @@ -751,6 +758,7 @@ Maybe<bool> ExportJWKEdKey(
std::shared_ptr<KeyObjectData> key,
Local<Object> target) {
ManagedEVPPKey pkey = key->GetAsymmetricKey();
Mutex::ScopedLock lock(*pkey.mutex());

const char* curve = nullptr;
switch (EVP_PKEY_id(pkey.get())) {
Expand Down Expand Up @@ -902,7 +910,7 @@ Maybe<bool> GetEcKeyDetail(
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(m_pkey.get());
const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(m_pkey.get());
CHECK_NOT_NULL(ec);

const EC_GROUP* group = EC_KEY_get0_group(ec);
Expand All @@ -919,8 +927,8 @@ Maybe<bool> GetEcKeyDetail(
// implementation here is a adapted from Chromium's impl here:
// https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/ecdsa.cc

size_t GroupOrderSize(ManagedEVPPKey key) {
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(key.get());
size_t GroupOrderSize(const ManagedEVPPKey& key) {
const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(key.get());
CHECK_NOT_NULL(ec);
const EC_GROUP* group = EC_KEY_get0_group(ec);
BignumPointer order(BN_new());
Expand Down
6 changes: 3 additions & 3 deletions src/crypto/crypto_hkdf.cc
Expand Up @@ -110,15 +110,15 @@ bool HKDFTraits::DeriveBits(
!EVP_PKEY_CTX_set_hkdf_md(ctx.get(), params.digest) ||
!EVP_PKEY_CTX_set1_hkdf_salt(
ctx.get(),
params.salt.get(),
reinterpret_cast<const unsigned char*>(params.salt.get()),
params.salt.size()) ||
!EVP_PKEY_CTX_set1_hkdf_key(
ctx.get(),
params.key->GetSymmetricKey(),
reinterpret_cast<const unsigned char*>(params.key->GetSymmetricKey()),
params.key->GetSymmetricKeySize()) ||
!EVP_PKEY_CTX_add1_hkdf_info(
ctx.get(),
params.info.get(),
reinterpret_cast<const unsigned char*>(params.info.get()),
params.info.size())) {
return false;
}
Expand Down
3 changes: 3 additions & 0 deletions src/crypto/crypto_keygen.h
Expand Up @@ -235,6 +235,9 @@ struct KeyPairGenConfig final : public MemoryRetainer {
AlgorithmParams params;

KeyPairGenConfig() = default;
~KeyPairGenConfig() {
Mutex::ScopedLock priv_lock(*key.mutex());
}

explicit KeyPairGenConfig(KeyPairGenConfig&& other) noexcept
: public_key_encoding(other.public_key_encoding),
Expand Down
2 changes: 2 additions & 0 deletions src/crypto/crypto_keys.cc
Expand Up @@ -559,6 +559,8 @@ ManagedEVPPKey::ManagedEVPPKey(const ManagedEVPPKey& that) {
}

ManagedEVPPKey& ManagedEVPPKey::operator=(const ManagedEVPPKey& that) {
Mutex::ScopedLock lock(*that.mutex_);

pkey_.reset(that.get());

if (pkey_)
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/crypto_keys.h
Expand Up @@ -74,7 +74,7 @@ struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig {
// use.
class ManagedEVPPKey : public MemoryRetainer {
public:
ManagedEVPPKey() = default;
ManagedEVPPKey() : mutex_(std::make_shared<Mutex>()) {}
explicit ManagedEVPPKey(EVPKeyPointer&& pkey);
ManagedEVPPKey(const ManagedEVPPKey& that);
ManagedEVPPKey& operator=(const ManagedEVPPKey& that);
Expand Down
8 changes: 4 additions & 4 deletions src/crypto/crypto_rsa.cc
Expand Up @@ -371,11 +371,11 @@ Maybe<bool> ExportJWKRsaKey(

// TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL
// versions older than 1.1.1e via FIPS / dynamic linking.
RSA* rsa;
const RSA* rsa;
if (OpenSSL_version_num() >= 0x1010105fL) {
rsa = EVP_PKEY_get0_RSA(m_pkey.get());
} else {
rsa = static_cast<RSA*>(EVP_PKEY_get0(m_pkey.get()));
rsa = static_cast<const RSA*>(EVP_PKEY_get0(m_pkey.get()));
}
CHECK_NOT_NULL(rsa);

Expand Down Expand Up @@ -520,11 +520,11 @@ Maybe<bool> GetRsaKeyDetail(

// TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL
// versions older than 1.1.1e via FIPS / dynamic linking.
RSA* rsa;
const RSA* rsa;
if (OpenSSL_version_num() >= 0x1010105fL) {
rsa = EVP_PKEY_get0_RSA(m_pkey.get());
} else {
rsa = static_cast<RSA*>(EVP_PKEY_get0(m_pkey.get()));
rsa = static_cast<const RSA*>(EVP_PKEY_get0(m_pkey.get()));
}
CHECK_NOT_NULL(rsa);

Expand Down
18 changes: 10 additions & 8 deletions src/crypto/crypto_sig.cc
Expand Up @@ -28,8 +28,13 @@ namespace crypto {
namespace {
bool ValidateDSAParameters(EVP_PKEY* key) {
/* Validate DSA2 parameters from FIPS 186-4 */
#if OPENSSL_VERSION_MAJOR >= 3
if (EVP_default_properties_is_fips_enabled(nullptr) &&
EVP_PKEY_DSA == EVP_PKEY_base_id(key)) {
#else
if (FIPS_mode() && EVP_PKEY_DSA == EVP_PKEY_base_id(key)) {
DSA* dsa = EVP_PKEY_get0_DSA(key);
#endif
const DSA* dsa = EVP_PKEY_get0_DSA(key);
const BIGNUM* p;
DSA_get0_pqg(dsa, &p, nullptr, nullptr);
size_t L = BN_num_bits(p);
Expand Down Expand Up @@ -103,11 +108,11 @@ unsigned int GetBytesOfRS(const ManagedEVPPKey& pkey) {
int bits, base_id = EVP_PKEY_base_id(pkey.get());

if (base_id == EVP_PKEY_DSA) {
DSA* dsa_key = EVP_PKEY_get0_DSA(pkey.get());
const DSA* dsa_key = EVP_PKEY_get0_DSA(pkey.get());
// Both r and s are computed mod q, so their width is limited by that of q.
bits = BN_num_bits(DSA_get0_q(dsa_key));
} else if (base_id == EVP_PKEY_EC) {
EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey.get());
const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey.get());
const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key);
bits = EC_GROUP_order_bits(ec_group);
} else {
Expand Down Expand Up @@ -873,7 +878,7 @@ bool SignTraits::DeriveBits(
case SignConfiguration::kSign: {
size_t len;
unsigned char* data = nullptr;
if (IsOneShot(params.key->GetAsymmetricKey())) {
if (IsOneShot(m_pkey)) {
EVP_DigestSign(
context.get(),
nullptr,
Expand Down Expand Up @@ -905,10 +910,7 @@ bool SignTraits::DeriveBits(
return false;

if (UseP1363Encoding(m_pkey, params.dsa_encoding)) {
*out = ConvertSignatureToP1363(
env,
params.key->GetAsymmetricKey(),
buf);
*out = ConvertSignatureToP1363(env, m_pkey, buf);
} else {
buf.Resize(len);
*out = std::move(buf);
Expand Down
18 changes: 18 additions & 0 deletions src/crypto/crypto_util.cc
Expand Up @@ -137,7 +137,12 @@ void InitCryptoOnce() {
unsigned long err = 0; // NOLINT(runtime/int)
if (per_process::cli_options->enable_fips_crypto ||
per_process::cli_options->force_fips_crypto) {
#if OPENSSL_VERSION_MAJOR >= 3
if (0 == EVP_default_properties_is_fips_enabled(nullptr) &&
!EVP_default_properties_enable_fips(nullptr, 1)) {
#else
if (0 == FIPS_mode() && !FIPS_mode_set(1)) {
#endif
err = ERR_get_error();
}
}
Expand All @@ -160,18 +165,31 @@ void InitCryptoOnce() {
}

void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
#if OPENSSL_VERSION_MAJOR >= 3
args.GetReturnValue().Set(EVP_default_properties_is_fips_enabled(nullptr) ?
1 : 0);
#else
args.GetReturnValue().Set(FIPS_mode() ? 1 : 0);
#endif
}

void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
CHECK(!per_process::cli_options->force_fips_crypto);
Environment* env = Environment::GetCurrent(args);
bool enable = args[0]->BooleanValue(env->isolate());

#if OPENSSL_VERSION_MAJOR >= 3
if (enable == EVP_default_properties_is_fips_enabled(nullptr))
#else
if (enable == FIPS_mode())
#endif
return; // No action needed.

#if OPENSSL_VERSION_MAJOR >= 3
if (!EVP_default_properties_enable_fips(nullptr, enable)) {
#else
if (!FIPS_mode_set(enable)) {
#endif
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
return ThrowCryptoError(env, err);
}
Expand Down
4 changes: 4 additions & 0 deletions src/node.cc
Expand Up @@ -1015,8 +1015,12 @@ InitializationResult InitializeOncePerProcess(int argc, char** argv) {
}
// In the case of FIPS builds we should make sure
// the random source is properly initialized first.
#if OPENSSL_VERSION_MAJOR >= 3
if (EVP_default_properties_is_fips_enabled(nullptr)) {
#else
if (FIPS_mode()) {
OPENSSL_init();
#endif
}
// V8 on Windows doesn't have a good source of entropy. Seed it from
// OpenSSL's pool.
Expand Down
4 changes: 4 additions & 0 deletions test/common/index.js
Expand Up @@ -51,6 +51,9 @@ const noop = () => {};
const hasCrypto = Boolean(process.versions.openssl) &&
!process.env.NODE_SKIP_CRYPTO;

const hasOpenSSL3 = hasCrypto &&
require('crypto').constants.OPENSSL_VERSION_NUMBER >= 805306368;

// Check for flags. Skip this for workers (both, the `cluster` module and
// `worker_threads`) and child processes.
// If the binary was built without-ssl then the crypto flags are
Expand Down Expand Up @@ -726,6 +729,7 @@ const common = {
getTTYfd,
hasIntl,
hasCrypto,
hasOpenSSL3,
hasMultiLocalhost,
invalidArgTypeHelper,
isAIX,
Expand Down
3 changes: 2 additions & 1 deletion test/parallel/test-crypto-binary-default.js
Expand Up @@ -583,7 +583,8 @@ assert.throws(
// Test Diffie-Hellman with two parties sharing a secret,
// using various encodings as we go along
{
const dh1 = crypto.createDiffieHellman(common.hasFipsCrypto ? 1024 : 256);
const size = common.hasFipsCrypto || common.hasOpenSSL3 ? 1024 : 256;
const dh1 = crypto.createDiffieHellman(size);
const p1 = dh1.getPrime('buffer');
const dh2 = crypto.createDiffieHellman(p1, 'base64');
const key1 = dh1.generateKeys();
Expand Down
4 changes: 3 additions & 1 deletion test/parallel/test-crypto-cipheriv-decipheriv.js
Expand Up @@ -193,7 +193,9 @@ assert.throws(
errMessage);

// But all other IV lengths should be accepted.
for (let n = 1; n < 256; n += 1) {
const minIvLength = common.hasOpenSSL3 ? 8 : 1;
const maxIvLength = common.hasOpenSSL3 ? 64 : 256;
for (let n = minIvLength; n < maxIvLength; n += 1) {
if (common.hasFipsCrypto && n < 12) continue;
crypto.createCipheriv('aes-128-gcm', Buffer.alloc(16), Buffer.alloc(n));
}
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-crypto-classes.js
Expand Up @@ -24,7 +24,7 @@ const TEST_CASES = {
if (!common.hasFipsCrypto) {
TEST_CASES.Cipher = ['aes192', 'secret'];
TEST_CASES.Decipher = ['aes192', 'secret'];
TEST_CASES.DiffieHellman = [256];
TEST_CASES.DiffieHellman = [common.hasOpenSSL3 ? 1024 : 256];
}

for (const [clazz, args] of Object.entries(TEST_CASES)) {
Expand Down
3 changes: 2 additions & 1 deletion test/parallel/test-crypto-dh-leak.js
Expand Up @@ -12,7 +12,8 @@ const crypto = require('crypto');

const before = process.memoryUsage.rss();
{
const dh = crypto.createDiffieHellman(common.hasFipsCrypto ? 1024 : 256);
const size = common.hasFipsCrypto || common.hasOpenSSL3 ? 1024 : 256;
const dh = crypto.createDiffieHellman(size);
const publicKey = dh.generateKeys();
const privateKey = dh.getPrivateKey();
for (let i = 0; i < 5e4; i += 1) {
Expand Down

0 comments on commit 640fe94

Please sign in to comment.