This is an automated email from the ASF dual-hosted git repository. szaszm pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
commit a77c85727086a5e38aa14aaef22143acf70235ae Author: Ferenc Gerlits <fgerl...@gmail.com> AuthorDate: Mon Oct 2 13:55:02 2023 +0200 MINIFICPP-2191 Replace deprecated OpenSSL API calls Closes #1656 Signed-off-by: Marton Szasz <sza...@apache.org> --- libminifi/include/utils/tls/CertificateUtils.h | 3 + libminifi/src/utils/tls/CertificateUtils.cpp | 119 ++++++++++++++-------- libminifi/test/unit/CertificateUtilsTests.cpp | 132 +++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 41 deletions(-) diff --git a/libminifi/include/utils/tls/CertificateUtils.h b/libminifi/include/utils/tls/CertificateUtils.h index 67ca46ee9..3c4a8ff6f 100644 --- a/libminifi/include/utils/tls/CertificateUtils.h +++ b/libminifi/include/utils/tls/CertificateUtils.h @@ -91,6 +91,9 @@ class WindowsCertStore { // Returns nullptr on errors X509_unique_ptr convertWindowsCertificate(PCCERT_CONTEXT certificate); +// Returns nullptr on errors, or if the input is not an RSA key pair +EVP_PKEY_unique_ptr convertWindowsRsaKeyPair(std::span<BYTE> data); + // Returns nullptr if the certificate has no associated private key, or the private key could not be extracted EVP_PKEY_unique_ptr extractPrivateKey(PCCERT_CONTEXT certificate); #endif // WIN32 diff --git a/libminifi/src/utils/tls/CertificateUtils.cpp b/libminifi/src/utils/tls/CertificateUtils.cpp index fb508a10a..5b8763a3a 100644 --- a/libminifi/src/utils/tls/CertificateUtils.cpp +++ b/libminifi/src/utils/tls/CertificateUtils.cpp @@ -18,14 +18,19 @@ #include "utils/tls/CertificateUtils.h" -#include <openssl/rsa.h> -#include <openssl/err.h> +#include "openssl/rsa.h" +#include "openssl/err.h" #ifdef WIN32 #include <winsock2.h> #pragma comment(lib, "ncrypt.lib") #pragma comment(lib, "Ws2_32.lib") + +#include "openssl/core_names.h" +#include "openssl/evp.h" +#include "openssl/param_build.h" +#include "openssl/ssl.h" #endif // WIN32 #include "utils/StringUtils.h" @@ -87,6 +92,76 @@ X509_unique_ptr convertWindowsCertificate(const PCCERT_CONTEXT certificate) { return X509_unique_ptr{d2i_X509(nullptr, &certificate_binary, certificate_length)}; } +struct OSSL_PARAM_BLD_deleter { + void operator()(OSSL_PARAM_BLD* param_builder) const { OSSL_PARAM_BLD_free(param_builder); } +}; +using OSSL_PARAM_BLD_unique_ptr = std::unique_ptr<OSSL_PARAM_BLD, OSSL_PARAM_BLD_deleter>; + +struct OSSL_PARAM_deleter { + void operator()(OSSL_PARAM* params) const { OSSL_PARAM_free(params); } +}; +using OSSL_PARAM_unique_ptr = std::unique_ptr<OSSL_PARAM, OSSL_PARAM_deleter>; + +struct EVP_PKEY_CTX_deleter { + void operator()(EVP_PKEY_CTX* pkey_context) const { EVP_PKEY_CTX_free(pkey_context); } +}; +using EVP_PKEY_CTX_unique_ptr = std::unique_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_deleter>; + +EVP_PKEY_unique_ptr convertWindowsRsaKeyPair(std::span<BYTE> data) { + // https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_rsakey_blob + auto const blob = reinterpret_cast<BCRYPT_RSAKEY_BLOB *>(data.data()); + + if (blob->Magic == BCRYPT_RSAFULLPRIVATE_MAGIC) { + OSSL_PARAM_BLD_unique_ptr param_builder{OSSL_PARAM_BLD_new()}; + if (!param_builder) { return nullptr; } + + // n is the modulus common to both public and private key + const auto* const n = BN_bin2bn(data.data() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp, blob->cbModulus, nullptr); + // e is the public exponent + const auto* const e = BN_bin2bn(data.data() + sizeof(BCRYPT_RSAKEY_BLOB), blob->cbPublicExp, nullptr); + // d is the private exponent + const auto* const d = BN_bin2bn(data.data() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + + blob->cbPrime2 + blob->cbPrime1 + blob->cbPrime2 + blob->cbPrime1, blob->cbModulus, nullptr); + + if (OSSL_PARAM_BLD_push_BN(param_builder.get(), OSSL_PKEY_PARAM_RSA_N, n) == 0 || + OSSL_PARAM_BLD_push_BN(param_builder.get(), OSSL_PKEY_PARAM_RSA_E, e) == 0 || + OSSL_PARAM_BLD_push_BN(param_builder.get(), OSSL_PKEY_PARAM_RSA_D, d) == 0) { return nullptr; } + + // p and q are the first and second factor of n + const auto* const p = BN_bin2bn(data.data() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus, + blob->cbPrime1, nullptr); + const auto* const q = BN_bin2bn(data.data() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1, + blob->cbPrime2, nullptr); + + if (OSSL_PARAM_BLD_push_BN(param_builder.get(), OSSL_PKEY_PARAM_RSA_FACTOR1, p) == 0 || + OSSL_PARAM_BLD_push_BN(param_builder.get(), OSSL_PKEY_PARAM_RSA_FACTOR2, q) == 0) { return nullptr; } + + // dmp1, dmq1 and iqmp are the exponents and coefficient for CRT calculations + const auto* const dmp1 = BN_bin2bn(data.data() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + + blob->cbPrime2, blob->cbPrime1, nullptr); + const auto* const dmq1 = BN_bin2bn(data.data() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + + blob->cbPrime2 + blob->cbPrime1, blob->cbPrime2, nullptr); + const auto* const iqmp = BN_bin2bn(data.data() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + + blob->cbPrime2 + blob->cbPrime1 + blob->cbPrime2, blob->cbPrime1, nullptr); + + if (OSSL_PARAM_BLD_push_BN(param_builder.get(), OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1) == 0 || + OSSL_PARAM_BLD_push_BN(param_builder.get(), OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1) == 0 || + OSSL_PARAM_BLD_push_BN(param_builder.get(), OSSL_PKEY_PARAM_RSA_COEFFICIENT1, iqmp) == 0) { return nullptr; } + + OSSL_PARAM_unique_ptr params{OSSL_PARAM_BLD_to_param(param_builder.get())}; + if (!params) { return nullptr; } + + EVP_PKEY_CTX_unique_ptr context{EVP_PKEY_CTX_new_from_name(NULL, SSL_TXT_RSA, NULL)}; + if (!context || EVP_PKEY_fromdata_init(context.get()) <= 0) { return nullptr; } + + EVP_PKEY* keypair_raw = nullptr; + if (EVP_PKEY_fromdata(context.get(), &keypair_raw, EVP_PKEY_KEYPAIR, params.get()) <= 0) { return nullptr; } + return EVP_PKEY_unique_ptr{keypair_raw}; + } + + return nullptr; +} + // from Shane Powell's answer at https://stackoverflow.com/questions/60180688, used with permission EVP_PKEY_unique_ptr extractPrivateKey(const PCCERT_CONTEXT certificate) { HCRYPTPROV_OR_NCRYPT_KEY_HANDLE key_handle; @@ -114,45 +189,7 @@ EVP_PKEY_unique_ptr extractPrivateKey(const PCCERT_CONTEXT certificate) { length, &length, 0))) { - // https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_rsakey_blob - auto const blob = reinterpret_cast<BCRYPT_RSAKEY_BLOB *>(data.get()); - - if (blob->Magic == BCRYPT_RSAFULLPRIVATE_MAGIC) { - auto rsa = RSA_new(); - - // n is the modulus common to both public and private key - auto const n = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp, blob->cbModulus, nullptr); - // e is the public exponent - auto const e = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB), blob->cbPublicExp, nullptr); - // d is the private exponent - auto const d = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 - + blob->cbPrime2 + blob->cbPrime1 + blob->cbPrime2 + blob->cbPrime1, blob->cbModulus, nullptr); - - RSA_set0_key(rsa, n, e, d); - - // p and q are the first and second factor of n - auto const p = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus, - blob->cbPrime1, nullptr); - auto const q = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1, - blob->cbPrime2, nullptr); - - RSA_set0_factors(rsa, p, q); - - // dmp1, dmq1 and iqmp are the exponents and coefficient for CRT calculations - auto const dmp1 = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 - + blob->cbPrime2, blob->cbPrime1, nullptr); - auto const dmq1 = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 - + blob->cbPrime2 + blob->cbPrime1, blob->cbPrime2, nullptr); - auto const iqmp = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 - + blob->cbPrime2 + blob->cbPrime1 + blob->cbPrime2, blob->cbPrime1, nullptr); - - RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp); - - pkey.reset(EVP_PKEY_new()); - - // ownership of rsa transferred to pkey - EVP_PKEY_assign_RSA(pkey.get(), rsa); - } + pkey = convertWindowsRsaKeyPair(std::span(data.get(), length)); } } diff --git a/libminifi/test/unit/CertificateUtilsTests.cpp b/libminifi/test/unit/CertificateUtilsTests.cpp new file mode 100644 index 000000000..0c4380315 --- /dev/null +++ b/libminifi/test/unit/CertificateUtilsTests.cpp @@ -0,0 +1,132 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../Catch.h" +#include "date/date.h" +#include "openssl/core_names.h" +#include "utils/file/FileUtils.h" +#include "utils/span.h" +#include "utils/StringUtils.h" +#include "utils/tls/CertificateUtils.h" + +namespace utils = org::apache::nifi::minifi::utils; + +TEST_CASE("getCertificateExpiration() works correctly") { + using namespace date::literals; + using namespace std::literals::chrono_literals; + + const auto executable_dir = utils::file::get_executable_dir(); + const auto cert_location = executable_dir / "resources" / "goodCA.crt"; + + size_t num_times_called = 0; + utils::tls::processPEMCertificate(cert_location, {}, {[&](utils::tls::X509_unique_ptr cert) { + ++num_times_called; + CHECK(utils::tls::getCertificateExpiration(cert) == date::sys_days(date::year_month_day(2053_y / 4 / 30)) + 9h + 3min); + return std::error_code{}; + }, {}, {}}); + CHECK(num_times_called == 1); +} + +#ifdef WIN32 + +constexpr std::string_view BCRYPT_RSAFULLPRIVATE_BLOB_example = + "52534133000800000300000000010000800000008000000001000184CAF5B447EB8AC0C9FA70942B1DBDA43E1E52B58DCF9F" + "25F44F3962FCFDAF9DE160303E54604721C7F7E6FE5D7E07FE915FF24616FEF4F74E59DEE8BB24C8F711591B9BC92E201130" + "1CB8EE0109D6FE2FD83B11433D4E9AB53BF5BA816E07729ED59DD17036FD7242D214BFA3A35F8668E5F52DF8C8DDE9331136" + "9C8455C1568F9DBC4F012E35B5246A7A6253D2F1423911CB1639995319F51AA3FA59F17A250895AD7F771F841536CD420BE1" + "A7EC5B94FF459A071F353CCC6ED0D6DBDB0F8DF7651C168AF32633272B045CD6EB1E4C29C0441DCE596F33BFA0154F702B8C" + "D000E318E7670053120612B94C7825D0FA28F2D4CBF1A0C467BFD67F719C75CCBDCD930FD9DE2675D2F01A19E449CA6A22F8" + "3CFDB676E05E116939E3F454F23D00620C0D3B6216450391F840A16E982BE07B1EE282235E1A017B504CF2392525702C3F5E" + "22C907A34589D4796C33058DD780EB3B02C27028C0A68F53B54B745530141E43E82B8C2604A86DB9B223C89679C8268DF84D" + "56E34B36A6DF782598AD5BA55D9CFC26CFC82A2BF05D7096965E15DDAD3EC1999B8B41882C01F85A0DEDCEEC28DB304D8880" + "C9A1C286E98F0C9561EB7E66A8CAC4DD80368DE3A71331D07CEF5C56A790EC27C3F17F02ED729A3BD6AB69BCBE6C1EDA381D" + "76FADFEB56457F01657C66D9CA681D5B1FD02ED59843FC534418548F78D8DE04C70DDE8D0C11C77CFA72418EF82CDB8FD31E" + "78968FF394CCFC2F76D5B0B41FBBAC5134C2CBFE60143BA573BBBF3437E66F59C38AFC0278D8013FCDC478BA30BA6A684D3E" + "86A1CB1F6B6AEC94F10AC704F362DA6FEE697C61C920B81DC39852FF2FD5C13B5D3F491A8207E0C750CDF03901744AA3CC98" + "06F3B2D4CEE71DF2D35027856682DCDE4F283680936CD8C06348F615B2E85BBE9B70A1CD8E04CDA76A9333A872FD6B3FB63F" + "059C6FBD9B013E8720A7F02A02345D7087F76CFA8A2F171BAD1B7C2895C78E6A22B94BDC59213E6BFEA7F69814DB925372CE" + "1C3C6CB0424F1FFCD7F101B4BAB2DB3C0A42D20EC97CBC5AC2D8DC43441BF786372200903445532693451777476E201BB98D" + "5674360DACC054D1FB201F5F95BEBEE775B57EDE21B5C46147B02DF8ADD6B06900FC66AB4B52D7B42C65FD27D8986A476EBB" + "FB9CF90E01EA1A7EF228869E0472FB58542B49BA6D3509651E477FCD3ADF06F0F13C98B03733FFCAF0800A250D31FE9F07A8" + "CCF56D820E6A3F4E620A9B40EC64805EF935F0A2CC608C14780C83AB8A5D2AF6774A1F2CBE3ADB34500C43BC0642EBE0CACD" + "77BAB387FC781F1190AA04E53209D6E69E52DEF8707F0C211638B9381D5ED06F91C437195B2C2B661C0F58B2CCB373D9F5A5" + "E754627E1180995A99FAA1D249D9C8D3E697F34D14746D3234E7C053187AE6475D097E7870E9E81A2F2C35A40F85317F1C90" + "DE83BB11657ECB49BDEE5C8E5BE66DB471A3FD16F8BE31B07A62D1DB71D3E5BA6AE67DC0231A5F77F535B4057A14AB805B33" + "511B4E2CB5C4F6305416BF267E204D3153CC3E6C5710404E22BCEE710CC19050DDD73977D005FAD9898C9E6BF5CB9CD36B31" + "06DC17052A51D921C41A18563F68AE932C3793390819F4DC80958609E9"; + +constexpr std::string_view expected_n = + "84CAF5B447EB8AC0C9FA70942B1DBDA43E1E52B58DCF9F25F44F3962FCFDAF9DE160303E54604721C7F7E6FE5D7E07FE915F" + "F24616FEF4F74E59DEE8BB24C8F711591B9BC92E2011301CB8EE0109D6FE2FD83B11433D4E9AB53BF5BA816E07729ED59DD1" + "7036FD7242D214BFA3A35F8668E5F52DF8C8DDE93311369C8455C1568F9DBC4F012E35B5246A7A6253D2F1423911CB163999" + "5319F51AA3FA59F17A250895AD7F771F841536CD420BE1A7EC5B94FF459A071F353CCC6ED0D6DBDB0F8DF7651C168AF32633" + "272B045CD6EB1E4C29C0441DCE596F33BFA0154F702B8CD000E318E7670053120612B94C7825D0FA28F2D4CBF1A0C467BFD6" + "7F719C75CCBD"; +constexpr std::string_view expected_e = "010001"; +constexpr std::string_view expected_d = + "14780C83AB8A5D2AF6774A1F2CBE3ADB34500C43BC0642EBE0CACD77BAB387FC781F1190AA04E53209D6E69E52DEF8707F0C" + "211638B9381D5ED06F91C437195B2C2B661C0F58B2CCB373D9F5A5E754627E1180995A99FAA1D249D9C8D3E697F34D14746D" + "3234E7C053187AE6475D097E7870E9E81A2F2C35A40F85317F1C90DE83BB11657ECB49BDEE5C8E5BE66DB471A3FD16F8BE31" + "B07A62D1DB71D3E5BA6AE67DC0231A5F77F535B4057A14AB805B33511B4E2CB5C4F6305416BF267E204D3153CC3E6C571040" + "4E22BCEE710CC19050DDD73977D005FAD9898C9E6BF5CB9CD36B3106DC17052A51D921C41A18563F68AE932C3793390819F4" + "DC80958609E9"; +constexpr std::string_view expected_p = + "CD930FD9DE2675D2F01A19E449CA6A22F83CFDB676E05E116939E3F454F23D00620C0D3B6216450391F840A16E982BE07B1E" + "E282235E1A017B504CF2392525702C3F5E22C907A34589D4796C33058DD780EB3B02C27028C0A68F53B54B745530141E43E8" + "2B8C2604A86DB9B223C89679C8268DF84D56E34B36A6DF782598AD5B"; +constexpr std::string_view expected_q = + "A55D9CFC26CFC82A2BF05D7096965E15DDAD3EC1999B8B41882C01F85A0DEDCEEC28DB304D8880C9A1C286E98F0C9561EB7E" + "66A8CAC4DD80368DE3A71331D07CEF5C56A790EC27C3F17F02ED729A3BD6AB69BCBE6C1EDA381D76FADFEB56457F01657C66" + "D9CA681D5B1FD02ED59843FC534418548F78D8DE04C70DDE8D0C11C7"; +constexpr std::string_view expected_dmp1 = + "7CFA72418EF82CDB8FD31E78968FF394CCFC2F76D5B0B41FBBAC5134C2CBFE60143BA573BBBF3437E66F59C38AFC0278D801" + "3FCDC478BA30BA6A684D3E86A1CB1F6B6AEC94F10AC704F362DA6FEE697C61C920B81DC39852FF2FD5C13B5D3F491A8207E0" + "C750CDF03901744AA3CC9806F3B2D4CEE71DF2D35027856682DCDE4F"; +constexpr std::string_view expected_dmq1 = + "283680936CD8C06348F615B2E85BBE9B70A1CD8E04CDA76A9333A872FD6B3FB63F059C6FBD9B013E8720A7F02A02345D7087" + "F76CFA8A2F171BAD1B7C2895C78E6A22B94BDC59213E6BFEA7F69814DB925372CE1C3C6CB0424F1FFCD7F101B4BAB2DB3C0A" + "42D20EC97CBC5AC2D8DC43441BF78637220090344553269345177747"; +constexpr std::string_view expected_iqmp = + "6E201BB98D5674360DACC054D1FB201F5F95BEBEE775B57EDE21B5C46147B02DF8ADD6B06900FC66AB4B52D7B42C65FD27D8" + "986A476EBBFB9CF90E01EA1A7EF228869E0472FB58542B49BA6D3509651E477FCD3ADF06F0F13C98B03733FFCAF0800A250D" + "31FE9F07A8CCF56D820E6A3F4E620A9B40EC64805EF935F0A2CC608C"; + +TEST_CASE("convertWindowsRsaKeyPair() works correctly") { + std::vector<std::byte> windows_private_key_blob = utils::StringUtils::from_hex(BCRYPT_RSAFULLPRIVATE_BLOB_example); + utils::tls::EVP_PKEY_unique_ptr openssl_private_key = utils::tls::convertWindowsRsaKeyPair(utils::as_span<BYTE>(std::span{windows_private_key_blob})); + REQUIRE(openssl_private_key); + + const auto checkParam = [&openssl_private_key](const char* param_name, std::string_view expected_value) { + BIGNUM* actual_value = nullptr; + REQUIRE(EVP_PKEY_get_bn_param(openssl_private_key.get(), param_name, &actual_value) == 1); + char* param_hex = BN_bn2hex(actual_value); + CHECK(param_hex == expected_value); + OPENSSL_free(param_hex); + BN_free(actual_value); + }; + + checkParam(OSSL_PKEY_PARAM_RSA_N, expected_n); + checkParam(OSSL_PKEY_PARAM_RSA_E, expected_e); + checkParam(OSSL_PKEY_PARAM_RSA_D, expected_d); + checkParam(OSSL_PKEY_PARAM_RSA_FACTOR1, expected_p); + checkParam(OSSL_PKEY_PARAM_RSA_FACTOR2, expected_q); + checkParam(OSSL_PKEY_PARAM_RSA_EXPONENT1, expected_dmp1); + checkParam(OSSL_PKEY_PARAM_RSA_EXPONENT2, expected_dmq1); + checkParam(OSSL_PKEY_PARAM_RSA_COEFFICIENT1, expected_iqmp); +} + +#endif // WIN32