From d621df47ac24ba1dc09f19e779b95c53854e7f9d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 4 Dec 2022 17:00:10 +0100 Subject: [PATCH] prc: Fix bf-cbc encryption/decryption regression with OpenSSL 3.0 Loads the legacy provider to continue supporting this algorithm --- dtool/src/prc/encryptStreamBuf.cxx | 71 ++++++++++++++++++++++++++++++ tests/express/test_encrypt.py | 20 +++++++++ 2 files changed, 91 insertions(+) create mode 100644 tests/express/test_encrypt.py diff --git a/dtool/src/prc/encryptStreamBuf.cxx b/dtool/src/prc/encryptStreamBuf.cxx index a8e7c04967..afe8ec1d61 100644 --- a/dtool/src/prc/encryptStreamBuf.cxx +++ b/dtool/src/prc/encryptStreamBuf.cxx @@ -23,6 +23,32 @@ #include #include +#if OPENSSL_VERSION_MAJOR >= 3 +#include + +/** + * Tries to load the legacy provider in OpenSSL. Returns true if the provider + * was just loaded, false if it was already loaded or couldn't be loaded. + */ +static bool load_legacy_provider() { + static bool tried = false; + if (!tried) { + tried = true; + if (OSSL_PROVIDER_try_load(nullptr, "legacy", 1) != nullptr) { + if (prc_cat.is_debug()) { + prc_cat.debug() + << "Loaded legacy OpenSSL provider.\n"; + } + return true; + } else { + prc_cat.warning() + << "Failed to load legacy OpenSSL provider.\n"; + } + } + return false; +} +#endif // OPENSSL_VERSION_MAJOR + // The iteration count is scaled by this factor for writing to the stream. static const int iteration_count_factor = 1000; @@ -122,7 +148,31 @@ open_read(std::istream *source, bool owns_source, const std::string &password) { int key_length = sr.get_uint16(); int count = sr.get_uint16(); +#if OPENSSL_VERSION_MAJOR >= 3 + // First, convert the cipher's nid to its full name. + const char *cipher_name = OBJ_nid2ln(nid); + + const EVP_CIPHER *cipher = nullptr; + if (cipher_name != nullptr) { + // Now, fetch the cipher known by this name. + cipher = EVP_CIPHER_fetch(nullptr, cipher_name, nullptr); + + if (cipher == nullptr && EVP_get_cipherbynid(nid) != nullptr) { + if (load_legacy_provider()) { + cipher = EVP_CIPHER_fetch(nullptr, cipher_name, nullptr); + } + + if (cipher == nullptr) { + prc_cat.error() + << "No implementation available for encryption algorithm in stream: " + << cipher_name << "\n"; + return; + } + } + } +#else const EVP_CIPHER *cipher = EVP_get_cipherbynid(nid); +#endif if (cipher == nullptr) { prc_cat.error() @@ -219,8 +269,29 @@ open_write(std::ostream *dest, bool owns_dest, const std::string &password) { _dest = dest; _owns_dest = owns_dest; +#if OPENSSL_VERSION_MAJOR >= 3 + // This checks that there is actually an implementation available. + const EVP_CIPHER *cipher = + EVP_CIPHER_fetch(nullptr, _algorithm.c_str(), nullptr); + + if (cipher == nullptr && + EVP_get_cipherbyname(_algorithm.c_str()) != nullptr) { + // The cipher does exist, though, do we need to load the legacy provider? + if (load_legacy_provider()) { + cipher = EVP_CIPHER_fetch(nullptr, _algorithm.c_str(), nullptr); + } + + if (cipher == nullptr) { + prc_cat.error() + << "No implementation available for encryption algorithm: " + << _algorithm << "\n"; + return; + } + } +#else const EVP_CIPHER *cipher = EVP_get_cipherbyname(_algorithm.c_str()); +#endif if (cipher == nullptr) { prc_cat.error() diff --git a/tests/express/test_encrypt.py b/tests/express/test_encrypt.py new file mode 100644 index 0000000000..c42398a9df --- /dev/null +++ b/tests/express/test_encrypt.py @@ -0,0 +1,20 @@ +from panda3d import core +import pytest + + +def test_encrypt_string(): + # Test encrypt and then decrypt cycle + for algorithm in ('', 'bf-cbc', 'aes-256-cbc'): + enc = core.encrypt_string('abcdefg', '12345', algorithm) + assert len(enc) > 0 + + dec = core.decrypt_string(enc, '12345') + assert dec == 'abcdefg' + + # Test pre-encrypted bf-cbc string + enc = b'[\x00\x10\x00d\x00\xb5\x7f\xc44Y\xb7\xd9\x15\xe3\xbd\xcf\xb3yK\xfb\xf6' + assert 'test' == core.decrypt_string(enc, '98765') + + # Test pre-encrypted aes-256-cbc string + enc = b'\xab\x01 \x00d\x00\xf1WP\xb0\x96h\xf8\xc5\xf4\x8d\x0b>q0\xf15\x185\xf8+\x1b\xe4\xae8\x88\xf2\x91\x15\xb8\x8fh\x88' + assert 'test' == core.decrypt_string(enc, '98765')