浏览代码

prc: Fix bf-cbc encryption/decryption regression with OpenSSL 3.0

Loads the legacy provider to continue supporting this algorithm
rdb 3 年之前
父节点
当前提交
d621df47ac
共有 2 个文件被更改,包括 91 次插入0 次删除
  1. 71 0
      dtool/src/prc/encryptStreamBuf.cxx
  2. 20 0
      tests/express/test_encrypt.py

+ 71 - 0
dtool/src/prc/encryptStreamBuf.cxx

@@ -23,6 +23,32 @@
 #include <openssl/rand.h>
 #include <openssl/evp.h>
 
+#if OPENSSL_VERSION_MAJOR >= 3
+#include <openssl/provider.h>
+
+/**
+ * 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()

+ 20 - 0
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')