From d66913c1aed97148fa9d617a9e15ca83ad120ab6 Mon Sep 17 00:00:00 2001 From: Florian Bach Date: Sat, 14 May 2022 18:56:33 +0200 Subject: [PATCH] Hacky fix for OpenSSL 3 --- bundle_calibre_plugin.sh | 2 +- calibre-plugin/__init__.py | 43 ++++- .../_libcrypto_replacement_file_bugfix.py | 179 ++++++++++++++++++ 3 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 calibre-plugin/_libcrypto_replacement_file_bugfix.py diff --git a/bundle_calibre_plugin.sh b/bundle_calibre_plugin.sh index 3b86f6e..04578b9 100755 --- a/bundle_calibre_plugin.sh +++ b/bundle_calibre_plugin.sh @@ -20,7 +20,7 @@ rm -r __pycache__ rm *.pyc # Set module ID. This needs to be changed if any of the module ZIPs change. -echo -n "2022-05-01-01" > module_id.txt +echo -n "2022-05-14-02" > module_id.txt # Copy LICENSE and README.md so it'll be included in the ZIP. cp ../LICENSE LICENSE diff --git a/calibre-plugin/__init__.py b/calibre-plugin/__init__.py index 9c1607b..dc8e0f3 100644 --- a/calibre-plugin/__init__.py +++ b/calibre-plugin/__init__.py @@ -160,6 +160,36 @@ class DeACSM(FileTypePlugin): traceback.print_exc() pass + + # TEMPORARY + # oscrypto still doesn't support Ubuntu 22.04 + # add a hacky bugfix + # once oscrypto supports 22.04, this can be removed. + + ts_dict = self.load_resources( ["module_id.txt"] ) + id_plugin = ts_dict["module_id.txt"].decode("latin-1").split('\n')[0].strip() + + if islinux: + try: + print("{0} v{1}: Patching oscrypto for OpenSSL3 support".format(PLUGIN_NAME, PLUGIN_VERSION)) + res_dict = self.load_resources( ["_libcrypto_replacement_file_bugfix.py"] ) + bugfix_file = res_dict["_libcrypto_replacement_file_bugfix.py"] + dest_path = os.path.join(rand_path, "oscrypto", "oscrypto", "_openssl", "_libcrypto.py") + backup_path = os.path.join(rand_path, "oscrypto", "oscrypto", "_openssl", "_libcrypto.py.bak") + + shutil.copyfile(dest_path, backup_path) + f = open(dest_path, "wb") + f.write(bugfix_file) + f.close() + print("{0} v{1}: Patch done".format(PLUGIN_NAME, PLUGIN_VERSION)) + except: + print("{0} v{1}: Error while patching oscrypto".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() + + # TEMPORARY END + + + if islinux: # Also extract EXE files needed for WINE ADE key extraction names = [ "keyextract/decrypt_win32.exe", "keyextract/decrypt_win64.exe" ] @@ -189,7 +219,7 @@ class DeACSM(FileTypePlugin): from libadobe import createDeviceKeyFile, update_account_path, sendHTTPRequest from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice - from libadobeFulfill import buildRights, fulfill, getDecryptedCert + from libadobeFulfill import buildRights, fulfill import calibre_plugins.deacsm.prefs as prefs # type: ignore @@ -216,7 +246,6 @@ class DeACSM(FileTypePlugin): deacsmprefs = prefs.DeACSM_Prefs() from libadobe import get_activation_xml_path - from libadobeFulfill import getDecryptedCert container = None try: @@ -236,9 +265,13 @@ class DeACSM(FileTypePlugin): print("ADE sanity check: pkcs12 missing") return False - if getDecryptedCert() is None: - print("ADE sanity check: Can't decrypt pkcs12") - return False + try: + from libadobeFulfill import getDecryptedCert + if getDecryptedCert() is None: + print("ADE sanity check: Can't decrypt pkcs12") + return False + except: + print("Skipping decryption check") return True except: diff --git a/calibre-plugin/_libcrypto_replacement_file_bugfix.py b/calibre-plugin/_libcrypto_replacement_file_bugfix.py new file mode 100644 index 0000000..1aed454 --- /dev/null +++ b/calibre-plugin/_libcrypto_replacement_file_bugfix.py @@ -0,0 +1,179 @@ +# coding: utf-8 +# This file is part of the oscrypto project: +# https://github.com/wbond/oscrypto +# It's been patched to fix OpenSSL3 issues: +# https://github.com/wbond/oscrypto/pull/61 +# +# As soon as this PR is merged and a new version of oscrypto is released, +# this temporary hack will be removed from the ACSM Input plugin. +# +# oscrypto is under the MIT license: +# Copyright (c) 2015-2022 Will Bond +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is furnished to do +# so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import unicode_literals, division, absolute_import, print_function + +from .. import ffi +from .._ffi import buffer_from_bytes, byte_string_from_buffer, null +from .._types import str_cls + +if ffi() == 'cffi': + from ._libcrypto_cffi import ( + libcrypto, + version as libcrypto_version, + version_info as libcrypto_version_info + ) +else: + from ._libcrypto_ctypes import ( + libcrypto, + version as libcrypto_version, + version_info as libcrypto_version_info + ) + + +__all__ = [ + 'handle_openssl_error', + 'libcrypto', + 'libcrypto_legacy_support', + 'libcrypto_version', + 'libcrypto_version_info', + 'LibcryptoConst', + 'peek_openssl_error', +] + + +_encoding = 'utf-8' +_fallback_encodings = ['utf-8', 'cp1252'] + + +if libcrypto_version_info < (1, 1): + libcrypto.ERR_load_crypto_strings() +libcrypto.OPENSSL_config(null()) + + +# This enables legacy algorithms in OpenSSL 3.0, such as RC2, etc +# which are used by various tests and some old protocols and things +# like PKCS12 +libcrypto_legacy_support = True +if libcrypto_version_info >= (3, ): + libcrypto.OSSL_PROVIDER_load(null(), "legacy".encode("ascii")) + libcrypto.OSSL_PROVIDER_load(null(), "default".encode("ascii")) + + if libcrypto.OSSL_PROVIDER_available(null(), "legacy".encode("ascii")) == 0: + libcrypto_legacy_support = False + + +def _try_decode(value): + + try: + return str_cls(value, _encoding) + + # If the "correct" encoding did not work, try some defaults, and then just + # obliterate characters that we can't seen to decode properly + except (UnicodeDecodeError): + for encoding in _fallback_encodings: + try: + return str_cls(value, encoding, errors='strict') + except (UnicodeDecodeError): + pass + + return str_cls(value, errors='replace') + + +def handle_openssl_error(result, exception_class=None): + """ + Checks if an error occurred, and if so throws an OSError containing the + last OpenSSL error message + + :param result: + An integer result code - 1 or greater indicates success + + :param exception_class: + The exception class to use for the exception if an error occurred + + :raises: + OSError - when an OpenSSL error occurs + """ + + if result > 0: + return + + if exception_class is None: + exception_class = OSError + + error_num = libcrypto.ERR_get_error() + buffer = buffer_from_bytes(120) + libcrypto.ERR_error_string(error_num, buffer) + + # Since we are dealing with a string, it is NULL terminated + error_string = byte_string_from_buffer(buffer) + + raise exception_class(_try_decode(error_string)) + + +def peek_openssl_error(): + """ + Peeks into the error stack and pulls out the lib, func and reason + + :return: + A three-element tuple of integers (lib, func, reason) + """ + + error = libcrypto.ERR_peek_error() + if libcrypto_version_info < (3, 0): + lib = int((error >> 24) & 0xff) + func = int((error >> 12) & 0xfff) + reason = int(error & 0xfff) + else: + lib = int((error >> 23) & 0xff) + # OpenSSL 3.0 removed ERR_GET_FUNC() + func = 0 + reason = int(error & 0x7fffff) + + return (lib, func, reason) + + +class LibcryptoConst(): + EVP_CTRL_SET_RC2_KEY_BITS = 3 + + SSLEAY_VERSION = 0 + + RSA_PKCS1_PADDING = 1 + RSA_NO_PADDING = 3 + RSA_PKCS1_OAEP_PADDING = 4 + + # OpenSSL 0.9.x + EVP_MD_CTX_FLAG_PSS_MDLEN = -1 + + # OpenSSL 1.x.x + EVP_PKEY_CTRL_RSA_PADDING = 0x1001 + RSA_PKCS1_PSS_PADDING = 6 + EVP_PKEY_CTRL_RSA_PSS_SALTLEN = 0x1002 + EVP_PKEY_RSA = 6 + EVP_PKEY_OP_SIGN = 1 << 3 + EVP_PKEY_OP_VERIFY = 1 << 4 + + NID_X9_62_prime256v1 = 415 + NID_secp384r1 = 715 + NID_secp521r1 = 716 + + OPENSSL_EC_NAMED_CURVE = 1 + + DH_GENERATOR_2 = 2