From 6d72506fad533a81a2b9f30a6e47e87f33b578ef Mon Sep 17 00:00:00 2001 From: Florian Bach Date: Sun, 16 Jan 2022 17:43:29 +0100 Subject: [PATCH] Begin work on Python2 support --- .github/workflows/main.yml | 12 +- bundle_calibre_plugin.sh | 22 +++- calibre-plugin/__calibre_compat_code.py | 21 ++++ calibre-plugin/__init__.py | 52 +++----- calibre-plugin/config.py | 146 +++++++++------------- calibre-plugin/customRSA.py | 76 +++++++++-- calibre-plugin/getEncryptionKeyLinux.py | 34 +++-- calibre-plugin/getEncryptionKeyWindows.py | 7 +- calibre-plugin/libadobe.py | 143 +++++++++++++++------ calibre-plugin/libadobeAccount.py | 66 +++++----- calibre-plugin/libadobeFulfill.py | 30 ++--- calibre-plugin/libadobeImportAccount.py | 12 +- tests/main.py | 45 ++++--- 13 files changed, 410 insertions(+), 256 deletions(-) create mode 100644 calibre-plugin/__calibre_compat_code.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b186cca..fb9c205 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,8 +34,16 @@ jobs: run: | # Require cryptography >= 3.1 because in 3.0 and below, the backend param in load_key_and_certificates was still required. pip3 install freezegun lxml pycryptodome rsa oscrypto "cryptography>=3.1" + # Install Python2 stuff + curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py + python2 get-pip.py + pip2 install freezegun mock lxml pycryptodome "rsa<=4.3" oscrypto cryptography==3.1 - - name: Run tests + - name: Run tests (Python 3) run: | - cd tests && ./main.py + cd tests && python3 ./main.py && cd .. + + - name: Run tests (Python 2) + run: | + cd tests && PYTHONWARNINGS=ignore python2 ./main.py && cd .. diff --git a/bundle_calibre_plugin.sh b/bundle_calibre_plugin.sh index faf8cf0..9c1a813 100755 --- a/bundle_calibre_plugin.sh +++ b/bundle_calibre_plugin.sh @@ -3,7 +3,11 @@ [ ! -f calibre-plugin/asn1crypto.zip ] && ./package_modules.sh [ ! -f calibre-plugin/oscrypto.zip ] && ./package_modules.sh -pushd calibre-plugin +rm -rf calibre-plugin-tmp || /bin/true + +cp -r calibre-plugin calibre-plugin-tmp + +pushd calibre-plugin-tmp pushd keyextract # Compile C programs: @@ -21,8 +25,24 @@ echo -n "2021-12-19-03" > module_id.txt cp ../LICENSE LICENSE cp ../README.md README.md +shopt -s globstar +echo "Injecting Python2 compat code ..." +for file in **/*.py; +do + #echo $file + # Inject Python2 compat code: + sed '/#@@CALIBRE_COMPAT_CODE@@/ { + r __calibre_compat_code.py + d + }' -i $file + +done + + + # Create ZIP file from calibre-plugin folder. zip -r ../calibre-plugin.zip * popd +rm -rf calibre-plugin-tmp diff --git a/calibre-plugin/__calibre_compat_code.py b/calibre-plugin/__calibre_compat_code.py new file mode 100644 index 0000000..9ec5039 --- /dev/null +++ b/calibre-plugin/__calibre_compat_code.py @@ -0,0 +1,21 @@ + +#@@CALIBRE_COMPAT_CODE_START@@ +import sys, os + +# Compatibility code taken from noDRM's DeDRM fork. +# This fixes the weird import issues with Calibre 2, +# and it allows me to get rid of a ton of try-except blocks. + +if "calibre" in sys.modules: + + # Explicitly allow importing everything ... + if os.path.dirname(os.path.abspath(__file__)) not in sys.path: + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + + # Bugfix for Calibre < 5: + if sys.version_info[0] == 2: + from calibre.utils.config import config_dir + if os.path.join(config_dir, "plugins", "DeACSM.zip") not in sys.path: + sys.path.insert(0, os.path.join(config_dir, "plugins", "DeACSM.zip")) + +#@@CALIBRE_COMPAT_CODE_END@@ diff --git a/calibre-plugin/__init__.py b/calibre-plugin/__init__.py index d08dbac..1046331 100644 --- a/calibre-plugin/__init__.py +++ b/calibre-plugin/__init__.py @@ -54,13 +54,15 @@ import os, shutil, traceback, sys, time, io, random import zipfile from lxml import etree +#@@CALIBRE_COMPAT_CODE@@ + class DeACSM(FileTypePlugin): name = PLUGIN_NAME description = "ACSM Input Plugin - Takes an Adobe ACSM file and converts that into a useable EPUB or PDF file. Python reimplementation of libgourou by Grégory Soutadé" supported_platforms = ['linux', 'osx', 'windows'] author = "Leseratte10" version = PLUGIN_VERSION_TUPLE - minimum_calibre_version = (5, 0, 0) + minimum_calibre_version = (4, 0, 0) file_types = set(['acsm']) on_import = True on_preprocess = True @@ -184,21 +186,9 @@ class DeACSM(FileTypePlugin): # Okay, now all the modules are available, import the Adobe modules. - # Account: - try: - from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path - from calibre_plugins.deacsm.libadobeAccount import createDeviceFile, createUser, signIn, activateDevice - except: - from libadobe import createDeviceKeyFile, update_account_path - from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice - - # Fulfill: - try: - from calibre_plugins.deacsm.libadobe import sendHTTPRequest - from calibre_plugins.deacsm.libadobeFulfill import buildRights, fulfill - except: - from libadobe import sendHTTPRequest - from libadobeFulfill import buildRights, fulfill + from libadobe import createDeviceKeyFile, update_account_path, sendHTTPRequest + from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice + from libadobeFulfill import buildRights, fulfill import calibre_plugins.deacsm.prefs as prefs # type: ignore @@ -245,20 +235,13 @@ class DeACSM(FileTypePlugin): except: return False - def download(self, replyData: str): + def download(self, replyData): + # type: (str) -> str - try: - from calibre_plugins.deacsm.libadobe import sendHTTPRequest_DL2FILE - from calibre_plugins.deacsm.libadobeFulfill import buildRights, fulfill - except: - from libadobe import sendHTTPRequest_DL2FILE - from libadobeFulfill import buildRights, fulfill - - try: - from calibre_plugins.deacsm.libpdf import patch_drm_into_pdf - except: - from libpdf import patch_drm_into_pdf + from libadobe import sendHTTPRequest_DL2FILE + from libadobeFulfill import buildRights, fulfill + from libpdf import patch_drm_into_pdf adobe_fulfill_response = etree.fromstring(replyData) @@ -344,7 +327,9 @@ class DeACSM(FileTypePlugin): print("{0} v{1}: Error: Unsupported file type ...".format(PLUGIN_NAME, PLUGIN_VERSION)) return None - def run(self, path_to_ebook: str): + def run(self, path_to_ebook): + # type: (str) -> str + # This code gets called by Calibre with a path to the new book file. # We need to check if it's an ACSM file @@ -363,12 +348,9 @@ class DeACSM(FileTypePlugin): print("{0} v{1}: ADE auth is missing or broken ".format(PLUGIN_NAME, PLUGIN_VERSION)) return path_to_ebook - try: - from calibre_plugins.deacsm.libadobe import are_ade_version_lists_valid - from calibre_plugins.deacsm.libadobeFulfill import fulfill - except: - from libadobe import are_ade_version_lists_valid - from libadobeFulfill import fulfill + + from libadobe import are_ade_version_lists_valid + from libadobeFulfill import fulfill if not are_ade_version_lists_valid(): print("{0} v{1}: ADE version list mismatch, please open a bug report.".format(PLUGIN_NAME, PLUGIN_VERSION)) diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py index 0f3fa31..afaed60 100644 --- a/calibre-plugin/config.py +++ b/calibre-plugin/config.py @@ -32,6 +32,13 @@ from calibre.utils.config import config_dir # type: ignore from calibre.constants import isosx, iswindows, islinux # type: ignore +#@@CALIBRE_COMPAT_CODE@@ + +if sys.version_info[0] == 2: + class FileNotFoundError(Exception): + pass + + class ConfigWidget(QWidget): def __init__(self, plugin_path): QWidget.__init__(self) @@ -171,15 +178,11 @@ class ConfigWidget(QWidget): try: - from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path, are_ade_version_lists_valid - from calibre_plugins.deacsm.libadobeAccount import createDeviceFile, createUser, signIn, activateDevice + from libadobe import createDeviceKeyFile, update_account_path, are_ade_version_lists_valid + from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice except: - try: - from libadobe import createDeviceKeyFile, update_account_path, are_ade_version_lists_valid - from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice - except: - print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) - traceback.print_exc() + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() update_account_path(self.deacsmprefs["path_to_account_data"]) @@ -242,15 +245,11 @@ class ConfigWidget(QWidget): # So just open up a folder picker dialog and have the user select the eReader's root folder. try: - from calibre_plugins.deacsm.libadobe import update_account_path, VAR_VER_HOBBES_VERSIONS - from calibre_plugins.deacsm.libadobeAccount import activateDevice, exportProxyAuth + from libadobe import update_account_path, VAR_VER_HOBBES_VERSIONS + from libadobeAccount import activateDevice, exportProxyAuth except: - try: - from libadobe import update_account_path, VAR_VER_HOBBES_VERSIONS - from libadobeAccount import activateDevice, exportProxyAuth - except: - print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) - traceback.print_exc() + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() update_account_path(self.deacsmprefs["path_to_account_data"]) @@ -402,7 +401,7 @@ class ConfigWidget(QWidget): try: containerdev = etree.parse(device_xml_path) - except (FileNotFoundError, OSError) as e: + except (IOError, FileNotFoundError, OSError) as e: return error_dialog(None, "Failed", "Error while reading device.xml", show=True, show_copy_button=False) try: @@ -509,14 +508,12 @@ class ConfigWidget(QWidget): def get_account_info(self): + try: - from calibre_plugins.deacsm.libadobe import VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS + from libadobe import VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS except: - try: - from libadobe import VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS - except: - print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) - traceback.print_exc() + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() activation_xml_path = os.path.join(self.deacsmprefs["path_to_account_data"], "activation.xml") device_xml_path = os.path.join(self.deacsmprefs["path_to_account_data"], "device.xml") @@ -525,7 +522,7 @@ class ConfigWidget(QWidget): try: container = etree.parse(activation_xml_path) containerdev = etree.parse(device_xml_path) - except (FileNotFoundError, OSError) as e: + except (IOError, FileNotFoundError, OSError) as e: return "Not authorized for any ADE ID", False, None try: @@ -579,16 +576,12 @@ class ConfigWidget(QWidget): def export_activation(self): try: - from calibre_plugins.deacsm.libadobe import update_account_path - from calibre_plugins.deacsm.libadobeAccount import getAccountUUID + from libadobe import update_account_path + from libadobeAccount import getAccountUUID except: - try: - from libadobe import update_account_path - from libadobeAccount import getAccountUUID - except: - print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) - traceback.print_exc() - return False + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() + return False update_account_path(self.deacsmprefs["path_to_account_data"]) @@ -678,7 +671,7 @@ class ConfigWidget(QWidget): return error_dialog(None, "Import failed", "The WINEPREFIX you entered doesn't seem to contain an authorized ADE.", show=True, show_copy_button=False) - from calibre_plugins.deacsm.libadobeImportAccount import importADEactivationLinuxWine + from libadobeImportAccount import importADEactivationLinuxWine ret, msg = importADEactivationLinuxWine(text) @@ -715,7 +708,7 @@ class ConfigWidget(QWidget): def import_activation_from_Win(self): # This will try to import the activation from Adobe Digital Editions on Windows ... - from calibre_plugins.deacsm.libadobeImportAccount import importADEactivationWindows + from libadobeImportAccount import importADEactivationWindows ret, msg = importADEactivationWindows() @@ -757,7 +750,7 @@ class ConfigWidget(QWidget): info_dialog(None, "Importing from ADE", msg, show=True, show_copy_button=False) - from calibre_plugins.deacsm.libadobeImportAccount import importADEactivationMac + from libadobeImportAccount import importADEactivationMac ret, msg = importADEactivationMac() @@ -862,24 +855,19 @@ class ConfigWidget(QWidget): def switch_ade_version(self): try: - from calibre_plugins.deacsm.libadobe import VAR_VER_HOBBES_VERSIONS, VAR_VER_SUPP_CONFIG_NAMES - from calibre_plugins.deacsm.libadobe import VAR_VER_BUILD_IDS, VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO - from calibre_plugins.deacsm.libadobeAccount import changeDeviceVersion + from libadobe import VAR_VER_HOBBES_VERSIONS, VAR_VER_SUPP_CONFIG_NAMES + from libadobe import VAR_VER_BUILD_IDS, VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO + from libadobeAccount import changeDeviceVersion except: - try: - from libadobe import VAR_VER_HOBBES_VERSIONS, VAR_VER_SUPP_CONFIG_NAMES - from libadobe import VAR_VER_BUILD_IDS, VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO - from libadobeAccount import changeDeviceVersion - except: - print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) - traceback.print_exc() + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() device_xml_path = os.path.join(self.deacsmprefs["path_to_account_data"], "device.xml") try: containerdev = etree.parse(device_xml_path) - except (FileNotFoundError, OSError) as e: + except (IOError, FileNotFoundError, OSError) as e: return error_dialog(None, "Failed", "Error while reading file", show=True, show_copy_button=False) try: @@ -961,18 +949,14 @@ class ConfigWidget(QWidget): def create_anon_auth(self): + try: - from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path, VAR_VER_SUPP_CONFIG_NAMES - from calibre_plugins.deacsm.libadobe import VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE, VAR_VER_BUILD_IDS, VAR_VER_DEFAULT_BUILD_ID - from calibre_plugins.deacsm.libadobeAccount import createDeviceFile, createUser, signIn, activateDevice + from libadobe import createDeviceKeyFile, update_account_path, VAR_VER_SUPP_CONFIG_NAMES + from libadobe import VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE, VAR_VER_BUILD_IDS, VAR_VER_DEFAULT_BUILD_ID + from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice except: - try: - from libadobe import createDeviceKeyFile, update_account_path, VAR_VER_SUPP_CONFIG_NAMES - from libadobe import VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE, VAR_VER_BUILD_IDS, VAR_VER_DEFAULT_BUILD_ID - from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice - except: - print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) - traceback.print_exc() + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() update_account_path(self.deacsmprefs["path_to_account_data"]) @@ -1059,15 +1043,11 @@ class ConfigWidget(QWidget): def convert_anon_to_account(self): try: - from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path - from calibre_plugins.deacsm.libadobeAccount import convertAnonAuthToAccount + from libadobe import createDeviceKeyFile, update_account_path + from libadobeAccount import convertAnonAuthToAccount except: - try: - from libadobe import createDeviceKeyFile, update_account_path - from libadobeAccount import convertAnonAuthToAccount - except: - print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) - traceback.print_exc() + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() update_account_path(self.deacsmprefs["path_to_account_data"]) @@ -1128,17 +1108,12 @@ class ConfigWidget(QWidget): def link_account(self): try: - from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path, VAR_VER_SUPP_CONFIG_NAMES - from calibre_plugins.deacsm.libadobe import VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE, VAR_VER_BUILD_IDS, VAR_VER_DEFAULT_BUILD_ID - from calibre_plugins.deacsm.libadobeAccount import createDeviceFile, getAuthMethodsAndCert, createUser, signIn, activateDevice + from libadobe import createDeviceKeyFile, update_account_path, VAR_VER_SUPP_CONFIG_NAMES + from libadobe import VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE, VAR_VER_BUILD_IDS, VAR_VER_DEFAULT_BUILD_ID + from libadobeAccount import createDeviceFile, getAuthMethodsAndCert, createUser, signIn, activateDevice except: - try: - from libadobe import createDeviceKeyFile, update_account_path, VAR_VER_SUPP_CONFIG_NAMES - from libadobe import VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE, VAR_VER_BUILD_IDS, VAR_VER_DEFAULT_BUILD_ID - from libadobeAccount import createDeviceFile, getAuthMethodsAndCert, createUser, signIn, activateDevice - except: - print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) - traceback.print_exc() + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() update_account_path(self.deacsmprefs["path_to_account_data"]) @@ -1247,15 +1222,11 @@ class ConfigWidget(QWidget): def export_key(self): try: - from calibre_plugins.deacsm.libadobe import update_account_path - from calibre_plugins.deacsm.libadobeAccount import exportAccountEncryptionKeyDER, getAccountUUID + from libadobe import update_account_path + from libadobeAccount import exportAccountEncryptionKeyDER, getAccountUUID except: - try: - from libadobe import update_account_path - from libadobeAccount import exportAccountEncryptionKeyDER, getAccountUUID - except: - print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) - traceback.print_exc() + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() update_account_path(self.deacsmprefs["path_to_account_data"]) @@ -1421,13 +1392,10 @@ class RentedBooksDialog(QDialog): try: - from calibre_plugins.deacsm.libadobeFulfill import tryReturnBook + from libadobeFulfill import tryReturnBook except: - try: - from libadobeFulfill import tryReturnBook - except: - print("{0} v{1}: Error while importing book return stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) - traceback.print_exc() + print("{0} v{1}: Error while importing book return stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() Ret_book = None for book in self.parent.deacsmprefs["list_of_rented_books"]: diff --git a/calibre-plugin/customRSA.py b/calibre-plugin/customRSA.py index 416d1a5..d394bf0 100644 --- a/calibre-plugin/customRSA.py +++ b/calibre-plugin/customRSA.py @@ -8,6 +8,8 @@ The original code used blinding and this one doesn't, but we don't really care about side-channel attacks ... ''' +import sys + try: from Cryptodome.PublicKey import RSA except ImportError: @@ -16,6 +18,7 @@ except ImportError: class CustomRSA: + @staticmethod def encrypt_for_adobe_signature(signing_key, message): key = RSA.importKey(signing_key) keylen = CustomRSA.byte_size(key.n) @@ -23,12 +26,17 @@ class CustomRSA: payload = CustomRSA.transform_bytes2int(padded) encrypted = CustomRSA.normal_encrypt(key, payload) block = CustomRSA.transform_int2bytes(encrypted, keylen) - return block + return bytearray(block) - def byte_size(number: int): + @staticmethod + def byte_size(number): + # type: (int) -> int return (number.bit_length() + 7) // 8 - def pad_message(message: bytes, target_len: int) -> bytes: + @staticmethod + def pad_message(message, target_len): + # type: (bytes, int) -> bytes + # Padding always uses 0xFF # Returns: 00 01 PADDING 00 MESSAGE @@ -40,9 +48,13 @@ class CustomRSA: padding_len = target_len - message_length - 3 - return b"".join([b"\x00\x01", padding_len * b"\xff", b"\x00", message]) + ret = bytearray(b"".join([b"\x00\x01", padding_len * b"\xff", b"\x00"])) + ret.extend(bytes(message)) - def normal_encrypt(key, message: int): + return ret + + @staticmethod + def normal_encrypt(key, message): if message < 0 or message > key.n: raise ValueError("Invalid message") @@ -50,15 +62,57 @@ class CustomRSA: encrypted = pow(message, key.d, key.n) return encrypted - def transform_bytes2int(raw_bytes: bytes): - return int.from_bytes(raw_bytes, "big", signed=False) + @staticmethod + def py2_int_to_bytes(value, length, big_endian = True): + result = [] + + for i in range(0, length): + result.append(value >> (i * 8) & 0xff) + + if big_endian: + result.reverse() + + return result + + @staticmethod + def py2_bytes_to_int(bytes, big_endian = True): + # type: (bytes, bool) -> int + + my_bytes = bytes + if not big_endian: + my_bytes.reverse() + + result = 0 + for b in my_bytes: + result = result * 256 + int(b) + + return result + + @staticmethod + def transform_bytes2int(raw_bytes): + # type: (bytes) -> int + + if sys.version_info[0] >= 3: + return int.from_bytes(raw_bytes, "big", signed=False) + + return CustomRSA.py2_bytes_to_int(raw_bytes, True) + + + @staticmethod + def transform_int2bytes(number, fill_size = 0): + # type: (int, int) -> bytes - def transform_int2bytes(number: int, fill_size: int = 0): if number < 0: raise ValueError("Negative number") + + size = None if fill_size > 0: - return number.to_bytes(fill_size, "big") + size = fill_size + else: + size = max(1, CustomRSA.byte_size(number)) - bytes_needed = max(1, CustomRSA.byte_size(number)) - return number.to_bytes(bytes_needed, "big") \ No newline at end of file + if sys.version_info[0] >= 3: + return number.to_bytes(size, "big") + + return CustomRSA.py2_int_to_bytes(number, size, True) \ No newline at end of file diff --git a/calibre-plugin/getEncryptionKeyLinux.py b/calibre-plugin/getEncryptionKeyLinux.py index 684f531..48d7aca 100644 --- a/calibre-plugin/getEncryptionKeyLinux.py +++ b/calibre-plugin/getEncryptionKeyLinux.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +#@@CALIBRE_COMPAT_CODE@@ -from re import VERBOSE - +import sys def unfuck(user): # Wine uses a pretty nonstandard encoding in their registry file. @@ -22,10 +22,16 @@ def unfuck(user): while i < len(user): # Convert string of len 1 to a byte char = user[i][0].encode("latin-1")[0] + if sys.version_info[0] == 2: + char = ord(char) + if char == ord('\\'): # Get next char: i += 1 char = user[i][0].encode("latin-1")[0] + if sys.version_info[0] == 2: + char = ord(char) + if (char == ord('a')): user_new.append(0x07) elif (char == ord('b')): @@ -46,6 +52,8 @@ def unfuck(user): # Get next char i += 1 char = user[i][0].encode("latin-1")[0] + if sys.version_info[0] == 2: + char = ord(char) if char not in hex_char_list: user_new.append(ord('x')) # This seems to be fallback code. @@ -57,16 +65,25 @@ def unfuck(user): # Read up to 3 more chars next = user[i + 1][0].encode("latin-1")[0] + if sys.version_info[0] == 2: + next = ord(next) + if next in hex_char_list: ival += chr(next) i += 1 next = user[i + 1][0].encode("latin-1")[0] + if sys.version_info[0] == 2: + next = ord(next) + if next in hex_char_list: ival += chr(next) i += 1 next = user[i + 1][0].encode("latin-1")[0] + if sys.version_info[0] == 2: + next = ord(next) + if next in hex_char_list: ival += chr(next) i += 1 @@ -83,11 +100,17 @@ def unfuck(user): # Read up to 2 more chars next = user[i + 1][0].encode("latin-1")[0] + if sys.version_info[0] == 2: + next = ord(next) + if next >= ord('0') and next <= ord('9'): octal = (octal * 8) + (next - ord('0')) i += 1 next = user[i + 1][0].encode("latin-1")[0] + if sys.version_info[0] == 2: + next = ord(next) + if next >= ord('0') and next <= ord('9'): octal = (octal * 8) + (next - ord('0')) i += 1 @@ -122,11 +145,8 @@ def GetMasterKey(path_to_wine_prefix): except: pass - try: - import cpuid - except: - import calibre_plugins.deacsm.cpuid as cpuid - + + import cpuid import struct try: diff --git a/calibre-plugin/getEncryptionKeyWindows.py b/calibre-plugin/getEncryptionKeyWindows.py index 4d9de7c..5ee41e6 100644 --- a/calibre-plugin/getEncryptionKeyWindows.py +++ b/calibre-plugin/getEncryptionKeyWindows.py @@ -17,6 +17,8 @@ except ImportError: else: raise +#@@CALIBRE_COMPAT_CODE@@ + def GetSystemDirectory(): from ctypes import windll, c_wchar_p, c_uint, create_unicode_buffer MAX_PATH = 255 @@ -126,11 +128,8 @@ def GetMasterKey(): # Get CPU vendor: - try: - import cpuid - except: - import calibre_plugins.deacsm.cpuid as cpuid + import cpuid import struct cpu = cpuid.CPUID() _, b, c, d = cpu(0) diff --git a/calibre-plugin/libadobe.py b/calibre-plugin/libadobe.py index 7ae29e5..d49b24a 100644 --- a/calibre-plugin/libadobe.py +++ b/calibre-plugin/libadobe.py @@ -6,8 +6,15 @@ Helper library with code needed for Adobe stuff. ''' from uuid import getnode -import os, hashlib, base64 -import urllib.request, ssl +import sys, os, hashlib, base64 +import ssl +try: + import urllib.request as ulib + import urllib.error as uliberror +except: + import urllib2 as ulib + import urllib2 as uliberror + from datetime import datetime, timedelta from lxml import etree @@ -23,10 +30,11 @@ except ImportError: from Crypto.Cipher import AES from Crypto.Hash import SHA -try: - from customRSA import CustomRSA -except: - from calibre_plugins.deacsm.customRSA import CustomRSA + +#@@CALIBRE_COMPAT_CODE@@ + + +from customRSA import CustomRSA from oscrypto import keys from oscrypto.asymmetric import dump_certificate, dump_private_key @@ -115,7 +123,9 @@ def get_activation_xml_path(): return FILE_ACTIVATIONXML -def update_account_path(folder_path: str): +def update_account_path(folder_path): + # type: (str) -> None + global FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML FILE_DEVICEKEY = os.path.join(folder_path, "devicesalt") @@ -134,17 +144,40 @@ def createDeviceKeyFile(): f.write(devkey_bytes) f.close() +def int_to_bytes(value, length, big_endian = True): + # Helper function for Python2 only (big endian) + # Python3 uses int.to_bytes() + result = [] + + for i in range(0, length): + result.append(value >> (i * 8) & 0xff) + + if big_endian: + result.reverse() + + return result + def get_mac_address(): mac1 = getnode() mac2 = getnode() if (mac1 != mac2) or ((mac1 >> 40) % 2): - return bytes([1, 2, 3, 4, 5, 0]) + if sys.version_info[0] >= 3: + return bytes([1, 2, 3, 4, 5, 0]) + else: + return bytearray([1, 2, 3, 4, 5, 0]) + + if sys.version_info[0] >= 3: + return mac1.to_bytes(6, byteorder='big') + + return int_to_bytes(mac1, 6) - return mac1.to_bytes(6, byteorder='big') -def makeSerial(random: bool): + +def makeSerial(random): + # type: (bool) -> str + # Original implementation: std::string Device::makeSerial(bool random) # It doesn't look like this implementation results in the same fingerprint Adobe is using in ADE. @@ -165,17 +198,26 @@ def makeSerial(random: bool): mac_address = get_mac_address() - dataToHash = "%d:%s:%02x:%02x:%02x:%02x:%02x:%02x\x00" % (uid, username, - mac_address[0], mac_address[1], mac_address[2], - mac_address[3], mac_address[4], mac_address[5]) + if sys.version_info[0] >= 3: + dataToHash = "%d:%s:%02x:%02x:%02x:%02x:%02x:%02x\x00" % (uid, username, + mac_address[0], mac_address[1], mac_address[2], + mac_address[3], mac_address[4], mac_address[5]) + + else: + dataToHash = "%d:%s:%02x:%02x:%02x:%02x:%02x:%02x\x00" % (uid, username, + mac_address[0], mac_address[1], mac_address[2], + mac_address[3], mac_address[4], mac_address[5]) sha_out = hashlib.sha1(dataToHash.encode('latin-1')).hexdigest().lower() else: - sha_out = Random.get_random_bytes(20).hex().lower() + import binascii + sha_out = binascii.hexlify(Random.get_random_bytes(20)).lower() return sha_out -def makeFingerprint(serial: str): +def makeFingerprint(serial): + # type: (str) -> str + # Original implementation: std::string Device::makeFingerprint(const std::string& serial) # base64(sha1(serial + privateKey)) # Fingerprint must be 20 bytes or less. @@ -195,14 +237,16 @@ def makeFingerprint(serial: str): ############################################## HTTP stuff: -def sendHTTPRequest_DL2FILE(URL: str, outputfile: str): +def sendHTTPRequest_DL2FILE(URL, outputfile): + # type: (str, str) -> int + headers = { "Accept": "*/*", "User-Agent": "book2png", # MacOS uses different User-Agent. Good thing we're emulating a Windows client. } - req = urllib.request.Request(url=URL, headers=headers) - handler = urllib.request.urlopen(req) + req = ulib.Request(url=URL, headers=headers) + handler = ulib.urlopen(req) chunksize = 16 * 1024 @@ -230,7 +274,8 @@ def sendHTTPRequest_DL2FILE(URL: str, outputfile: str): return 200 -def sendHTTPRequest_getSimple(URL: str): +def sendHTTPRequest_getSimple(URL): + # type: (str) -> str headers = { "Accept": "*/*", @@ -246,8 +291,8 @@ def sendHTTPRequest_getSimple(URL: str): ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE - req = urllib.request.Request(url=URL, headers=headers) - handler = urllib.request.urlopen(req, context=ctx) + req = ulib.Request(url=URL, headers=headers) + handler = ulib.urlopen(req, context=ctx) content = handler.read() @@ -262,7 +307,8 @@ def sendHTTPRequest_getSimple(URL: str): return content -def sendPOSTHTTPRequest(URL: str, document: bytes, type: str, returnRC = False): +def sendPOSTHTTPRequest(URL, document, type, returnRC = False): + # type: (str, bytes, str, bool) -> str headers = { "Accept": "*/*", @@ -279,10 +325,10 @@ def sendPOSTHTTPRequest(URL: str, document: bytes, type: str, returnRC = False): ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE - req = urllib.request.Request(url=URL, headers=headers, data=document) + req = ulib.Request(url=URL, headers=headers, data=document) try: - handler = urllib.request.urlopen(req, context=ctx) - except urllib.error.HTTPError as err: + handler = ulib.urlopen(req, context=ctx) + except uliberror.HTTPError as err: # This happens with HTTP 500 and related errors. print("Post request caused HTTPError %d" % (err.code)) return err.code, "Post request caused HTTPException" @@ -311,14 +357,17 @@ def sendPOSTHTTPRequest(URL: str, document: bytes, type: str, returnRC = False): return content -def sendHTTPRequest(URL: str): +def sendHTTPRequest(URL): + # type: (str) -> str return sendHTTPRequest_getSimple(URL) -def sendRequestDocu(document: str, URL: str): +def sendRequestDocu(document, URL): + # type: (str, str) -> str return sendPOSTHTTPRequest(URL, document.encode("utf-8"), "application/vnd.adobe.adept+xml", False) -def sendRequestDocuRC(document: str, URL: str): +def sendRequestDocuRC(document, URL): + # type: (str, str) -> str return sendPOSTHTTPRequest(URL, document.encode("utf-8"), "application/vnd.adobe.adept+xml", True) @@ -328,6 +377,8 @@ def sendRequestDocuRC(document: str, URL: str): def encrypt_with_device_key(data): + data = bytearray(data) + global devkey_bytes if devkey_bytes is None: f = open(FILE_DEVICEKEY, "rb") @@ -338,7 +389,9 @@ def encrypt_with_device_key(data): if (len(data) % 16): remain = 16 - (len(data) % 16) - data += bytes([remain])*remain + for _ in range(remain): + data.append(remain) + iv = Random.get_random_bytes(16) cip = AES.new(devkey_bytes, AES.MODE_CBC, iv) @@ -348,6 +401,11 @@ def encrypt_with_device_key(data): return res def decrypt_with_device_key(data): + + if isinstance(data, str): + # Python2 + data = bytes(data) + global devkey_bytes if devkey_bytes is None: f = open(FILE_DEVICEKEY, "rb") @@ -355,7 +413,7 @@ def decrypt_with_device_key(data): f.close() cip = AES.new(devkey_bytes, AES.MODE_CBC, data[:16]) - decrypted = cip.decrypt(data[16:]) + decrypted = bytearray(cip.decrypt(data[16:])) # Remove padding decrypted = decrypted[:-decrypted[-1]] @@ -380,12 +438,17 @@ def addNonce(): # Unixtime to gregorian timestamp Ntime += 62167219200000 - final = bytearray(Ntime.to_bytes(8, 'little')) - # Something is fishy with this tmp value. It usually is 0 in ADE, but not always. # I haven't yet figured out what it means ... tmp = 0 - final.extend(tmp.to_bytes(4, 'little')) + + if sys.version_info[0] >= 3: + final = bytearray(Ntime.to_bytes(8, 'little')) + final.extend(tmp.to_bytes(4, 'little')) + else: + final = bytearray(int_to_bytes(Ntime, 8, False)) + final.extend(int_to_bytes(tmp, 4, True)) + ret = "" @@ -552,9 +615,13 @@ def hash_node_ctx(node, hash_ctx): -def hash_do_append_string(hash_ctx, string: str): +def hash_do_append_string(hash_ctx, string): + # type: (SHA.SHA1Hash, str) -> None - str_bytes = bytes(string, encoding="utf-8") + if sys.version_info[0] >= 3: + str_bytes = bytes(string, encoding="utf-8") + else: + str_bytes = bytes(string) length = len(str_bytes) len_upper = int(length / 256) @@ -563,12 +630,14 @@ def hash_do_append_string(hash_ctx, string: str): hash_do_append_raw_bytes(hash_ctx, [len_upper, len_lower]) hash_do_append_raw_bytes(hash_ctx, str_bytes) -def hash_do_append_tag(hash_ctx, tag: int): +def hash_do_append_tag(hash_ctx, tag): + # type: (SHA.SHA1Hash, int) -> None if (tag > 5): return hash_do_append_raw_bytes(hash_ctx, [tag]) -def hash_do_append_raw_bytes(hash_ctx, data: bytes): +def hash_do_append_raw_bytes(hash_ctx, data): + # type: (SHA.SHA1Hash, bytes) -> None hash_ctx.update(bytearray(data)) diff --git a/calibre-plugin/libadobeAccount.py b/calibre-plugin/libadobeAccount.py index 81dd6c6..2549a25 100644 --- a/calibre-plugin/libadobeAccount.py +++ b/calibre-plugin/libadobeAccount.py @@ -12,25 +12,20 @@ except ImportError: from Crypto.Util.asn1 import DerSequence from Crypto.Cipher import PKCS1_v1_5 - -try: - from libadobe import addNonce, sign_node, sendRequestDocu, sendHTTPRequest - from libadobe import makeFingerprint, makeSerial, encrypt_with_device_key, decrypt_with_device_key - from libadobe import get_devkey_path, get_device_path, get_activation_xml_path - from libadobe import VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS - from libadobe import VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO, VAR_VER_SUPP_VERSIONS, VAR_ACS_SERVER_HTTP - from libadobe import VAR_ACS_SERVER_HTTPS, VAR_VER_BUILD_IDS, VAR_VER_NEED_HTTPS_BUILD_ID_LIMIT, VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE -except: - from calibre_plugins.deacsm.libadobe import addNonce, sign_node, sendRequestDocu, sendHTTPRequest - from calibre_plugins.deacsm.libadobe import makeFingerprint, makeSerial, encrypt_with_device_key, decrypt_with_device_key - from calibre_plugins.deacsm.libadobe import get_devkey_path, get_device_path, get_activation_xml_path - from calibre_plugins.deacsm.libadobe import VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS - from calibre_plugins.deacsm.libadobe import VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO, VAR_VER_SUPP_VERSIONS, VAR_ACS_SERVER_HTTP - from calibre_plugins.deacsm.libadobe import VAR_ACS_SERVER_HTTPS, VAR_VER_BUILD_IDS, VAR_VER_NEED_HTTPS_BUILD_ID_LIMIT, VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE +#@@CALIBRE_COMPAT_CODE@@ +from libadobe import addNonce, sign_node, sendRequestDocu, sendHTTPRequest +from libadobe import makeFingerprint, makeSerial, encrypt_with_device_key, decrypt_with_device_key +from libadobe import get_devkey_path, get_device_path, get_activation_xml_path +from libadobe import VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS +from libadobe import VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO, VAR_VER_SUPP_VERSIONS, VAR_ACS_SERVER_HTTP +from libadobe import VAR_ACS_SERVER_HTTPS, VAR_VER_BUILD_IDS, VAR_VER_NEED_HTTPS_BUILD_ID_LIMIT, VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE + + +def createDeviceFile(randomSerial, useVersionIndex = 0): + # type: (bool, int) -> bool -def createDeviceFile(randomSerial: bool, useVersionIndex: int = 0): # Original implementation: Device::createDeviceFile(const std::string& hobbes, bool randomSerial) if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES): @@ -142,7 +137,7 @@ def getAuthMethodsAndCert(): -def createUser(useVersionIndex: int = 0, authCert = None): +def createUser(useVersionIndex = 0, authCert = None): if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES): return False, "Invalid Version index", [[], []] @@ -215,12 +210,11 @@ def createUser(useVersionIndex: int = 0, authCert = None): return True, "Done" -def encryptLoginCredentials(username: str, password: str, authenticationCertificate: str): +def encryptLoginCredentials(username, password, authenticationCertificate): + # type: (str, str, str) -> bytes - try: - from calibre_plugins.deacsm.libadobe import devkey_bytes as devkey_adobe - except: - from libadobe import devkey_bytes as devkey_adobe + from libadobe import devkey_bytes as devkey_adobe + import struct if devkey_adobe is not None: devkey_bytes = devkey_adobe @@ -234,9 +228,9 @@ def encryptLoginCredentials(username: str, password: str, authenticationCertific # Build buffer ar = bytearray(devkey_bytes) - ar.extend(bytearray(len(username).to_bytes(1, 'big'))) + ar.extend(bytearray(struct.pack("B", len(username)))) ar.extend(bytearray(username.encode("latin-1"))) - ar.extend(bytearray(len(password).to_bytes(1, 'big'))) + ar.extend(bytearray(struct.pack("B", len(password)))) ar.extend(bytearray(password.encode("latin-1"))) # Crypt code from https://stackoverflow.com/a/12921889/4991648 @@ -253,7 +247,9 @@ def encryptLoginCredentials(username: str, password: str, authenticationCertific return crypted_msg -def buildSignInRequestForAnonAuthConvert(username: str, password: str, authenticationCertificate: str): +def buildSignInRequestForAnonAuthConvert(username, password, authenticationCertificate): + # type: (str, str, str) -> str + NSMAP = { "adept" : "http://ns.adobe.com/adept" } etree.register_namespace("adept", NSMAP["adept"]) @@ -283,7 +279,9 @@ def buildSignInRequestForAnonAuthConvert(username: str, password: str, authentic return "\n" + etree.tostring(root, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1") -def buildSignInRequest(type: str, username: str, password: str, authenticationCertificate: str): +def buildSignInRequest(type, username, password, authenticationCertificate): + # type: (str, str, str, str) -> str + NSMAP = { "adept" : "http://ns.adobe.com/adept" } etree.register_namespace("adept", NSMAP["adept"]) @@ -316,7 +314,8 @@ def buildSignInRequest(type: str, username: str, password: str, authenticationCe return "\n" + etree.tostring(root, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1") -def convertAnonAuthToAccount(username: str, passwd: str): +def convertAnonAuthToAccount(username, passwd): + # If you have an anonymous authorization, you can convert that to an AdobeID. # Important: You can only do this ONCE for each AdobeID. # The AdobeID you are using for this must not be connected to any ADE install. @@ -402,7 +401,7 @@ def convertAnonAuthToAccount(username: str, passwd: str): -def signIn(account_type: str, username: str, passwd: str): +def signIn(account_type, username, passwd): # Get authenticationCertificate @@ -556,7 +555,7 @@ def exportProxyAuth(act_xml_path, activationToken): -def buildActivateReqProxy(useVersionIndex: int = 0, proxyData = None): +def buildActivateReqProxy(useVersionIndex = 0, proxyData = None): if proxyData is None: return False @@ -652,7 +651,7 @@ def buildActivateReqProxy(useVersionIndex: int = 0, proxyData = None): return True, ret -def buildActivateReq(useVersionIndex: int = 0): +def buildActivateReq(useVersionIndex = 0): if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES): return False @@ -727,7 +726,7 @@ def buildActivateReq(useVersionIndex: int = 0): # Call this function to change from ADE2 to ADE3 and vice versa. -def changeDeviceVersion(useVersionIndex: int = 0): +def changeDeviceVersion(useVersionIndex = 0): if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES): return False, "Invalid Version index" @@ -771,7 +770,7 @@ def changeDeviceVersion(useVersionIndex: int = 0): -def activateDevice(useVersionIndex: int = 0, proxyData = None): +def activateDevice(useVersionIndex = 0, proxyData = None): if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES): return False, "Invalid Version index" @@ -881,7 +880,8 @@ def getAccountUUID(): return None -def exportAccountEncryptionKeyDER(output_file: str): +def exportAccountEncryptionKeyDER(output_file): + # type: (str) -> bool try: activationxml = etree.parse(get_activation_xml_path()) adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag) diff --git a/calibre-plugin/libadobeFulfill.py b/calibre-plugin/libadobeFulfill.py index 8a9f4ce..7904fee 100644 --- a/calibre-plugin/libadobeFulfill.py +++ b/calibre-plugin/libadobeFulfill.py @@ -1,16 +1,13 @@ from lxml import etree import base64 -try: - from libadobe import addNonce, sign_node, get_cert_from_pkcs12, sendRequestDocu, sendRequestDocuRC, sendHTTPRequest - from libadobe import get_devkey_path, get_device_path, get_activation_xml_path - from libadobe import VAR_VER_SUPP_VERSIONS, VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS - from libadobe import VAR_VER_BUILD_IDS, VAR_VER_USE_DIFFERENT_NOTIFICATION_XML_ORDER -except: - from calibre_plugins.deacsm.libadobe import addNonce, sign_node, get_cert_from_pkcs12, sendRequestDocu, sendRequestDocuRC, sendHTTPRequest - from calibre_plugins.deacsm.libadobe import get_devkey_path, get_device_path, get_activation_xml_path - from calibre_plugins.deacsm.libadobe import VAR_VER_SUPP_VERSIONS, VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS - from calibre_plugins.deacsm.libadobe import VAR_VER_BUILD_IDS, VAR_VER_USE_DIFFERENT_NOTIFICATION_XML_ORDER +#@@CALIBRE_COMPAT_CODE@@ + +from libadobe import addNonce, sign_node, get_cert_from_pkcs12, sendRequestDocu, sendRequestDocuRC, sendHTTPRequest +from libadobe import get_devkey_path, get_device_path, get_activation_xml_path +from libadobe import VAR_VER_SUPP_VERSIONS, VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS +from libadobe import VAR_VER_BUILD_IDS, VAR_VER_USE_DIFFERENT_NOTIFICATION_XML_ORDER + def buildFulfillRequest(acsm): @@ -108,7 +105,8 @@ def buildFulfillRequest(acsm): -def buildInitLicenseServiceRequest(authURL: str): +def buildInitLicenseServiceRequest(authURL): + # type: (str) -> str adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag) @@ -175,7 +173,9 @@ def buildAuthRequest(): return ret -def doOperatorAuth(operatorURL: str): +def doOperatorAuth(operatorURL): + # type: (str) -> str + auth_req = buildAuthRequest() authURL = operatorURL @@ -212,7 +212,8 @@ def doOperatorAuth(operatorURL: str): -def operatorAuth(operatorURL: str): +def operatorAuth(operatorURL): + # type: (str) -> str adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag) NSMAP = { "adept" : "http://ns.adobe.com/adept" } @@ -749,7 +750,8 @@ def performFulfillmentNotification(fulfillmentResultToken, forceOptional = False -def fetchLicenseServiceCertificate(licenseURL: str, operatorURL: str): +def fetchLicenseServiceCertificate(licenseURL, operatorURL): + # Check if we already have a cert for this URL: adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag) NSMAP = { "adept" : "http://ns.adobe.com/adept" } diff --git a/calibre-plugin/libadobeImportAccount.py b/calibre-plugin/libadobeImportAccount.py index ab1b62b..1dd8f73 100644 --- a/calibre-plugin/libadobeImportAccount.py +++ b/calibre-plugin/libadobeImportAccount.py @@ -8,6 +8,10 @@ except ImportError: # Some distros still ship this as Crypto from Crypto.Cipher import AES as _AES + +#@@CALIBRE_COMPAT_CODE@@ + + class AES(object): def __init__(self, key, iv): self._aes = _AES.new(key, _AES.MODE_CBC, iv) @@ -15,12 +19,8 @@ class AES(object): return self._aes.decrypt(data) -try: - from libadobe import makeSerial, get_devkey_path, get_device_path, get_activation_xml_path - from libadobe import VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS, VAR_VER_DEFAULT_BUILD_ID, VAR_VER_BUILD_IDS -except: - from calibre_plugins.deacsm.libadobe import makeSerial, get_devkey_path, get_device_path, get_activation_xml_path - from calibre_plugins.deacsm.libadobe import VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS, VAR_VER_DEFAULT_BUILD_ID, VAR_VER_BUILD_IDS +from libadobe import makeSerial, get_devkey_path, get_device_path, get_activation_xml_path +from libadobe import VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS, VAR_VER_DEFAULT_BUILD_ID, VAR_VER_BUILD_IDS def importADEactivationLinuxWine(wine_prefix_path, buildIDtoEmulate=VAR_VER_DEFAULT_BUILD_ID): diff --git a/tests/main.py b/tests/main.py index dbcb7dd..8c86794 100755 --- a/tests/main.py +++ b/tests/main.py @@ -7,7 +7,10 @@ sys.path.append("../calibre-plugin") import unittest import base64 from freezegun import freeze_time -from unittest.mock import patch +if sys.version_info[0] >= 3: + from unittest.mock import patch +else: + from mock import patch from lxml import etree @@ -38,9 +41,9 @@ class TestAdobe(unittest.TestCase): def test_checkIfVersionListsAreValid(self): - ''' - Check if version lists are sane + '''Check if version lists are sane''' + ''' These four lists must all have the same amount of elements. Also, the default build ID must be valid, and all the IDs available for authorization or switching must be valid, too. @@ -67,7 +70,7 @@ class TestAdobe(unittest.TestCase): def test_fingerprintGeneration(self): '''Check if fingerprint generation works''' - libadobe.devkey_bytes = bytes([0xf8, 0x7a, 0xfc, 0x8c, 0x75, 0x25, 0xdc, 0x4b, 0x83, 0xec, 0x0c, 0xe2, 0xab, 0x4b, 0xef, 0x51]) + libadobe.devkey_bytes = bytearray([0xf8, 0x7a, 0xfc, 0x8c, 0x75, 0x25, 0xdc, 0x4b, 0x83, 0xec, 0x0c, 0xe2, 0xab, 0x4b, 0xef, 0x51]) self.assertEqual(libadobe.makeFingerprint("f0081bce3f771bdeeb26fcb4b2011fed77edff7b"), b"FgLMNXxv1BZPqMOM6IUnfaG4Qj8=", "Wrong fingerprint") self.assertEqual(libadobe.makeFingerprint("HelloWorld123"), b"hpp223C1kfLDOoyxo8WR7KhcXB8=", "Wrong fingerprint") @@ -79,14 +82,14 @@ class TestAdobe(unittest.TestCase): # Overwrite the get_random_bytes function that's used to get a random IV # Forcing hard-coded IV ... - random.get_random_bytes._mock_side_effect = lambda rndlen: bytes([0xc2, 0x3b, 0x0f, 0xde, 0xf2, 0x4a, 0xc3, 0x03, + random.get_random_bytes._mock_side_effect = lambda rndlen: bytearray([0xc2, 0x3b, 0x0f, 0xde, 0xf2, 0x4a, 0xc3, 0x03, 0xae, 0xc8, 0x70, 0xd4, 0x46, 0x6c, 0x8b, 0xb0]) - libadobe.devkey_bytes = bytes([0xf8, 0x7a, 0xfc, 0x8c, 0x75, 0x25, 0xdc, 0x4b, 0x83, 0xec, 0x0c, 0xe2, 0xab, 0x4b, 0xef, 0x51]) + libadobe.devkey_bytes = bytearray([0xf8, 0x7a, 0xfc, 0x8c, 0x75, 0x25, 0xdc, 0x4b, 0x83, 0xec, 0x0c, 0xe2, 0xab, 0x4b, 0xef, 0x51]) mock_data = b"Test message" mock_result = libadobe.encrypt_with_device_key(mock_data) - expected_result = bytes([0xc2, 0x3b, 0x0f, 0xde, 0xf2, 0x4a, 0xc3, 0x03, 0xae, 0xc8, 0x70, 0xd4, + expected_result = bytearray([0xc2, 0x3b, 0x0f, 0xde, 0xf2, 0x4a, 0xc3, 0x03, 0xae, 0xc8, 0x70, 0xd4, 0x46, 0x6c, 0x8b, 0xb0, 0x23, 0x5a, 0xd3, 0x1b, 0x4e, 0x2b, 0x12, 0x79, 0x85, 0x63, 0x2d, 0x01, 0xa4, 0xe8, 0x29, 0x22]) @@ -95,9 +98,9 @@ class TestAdobe(unittest.TestCase): def test_deviceKeyDecryption(self): '''Check if decryption with the device key works''' - libadobe.devkey_bytes = bytes([0xf8, 0x7a, 0xfc, 0x8c, 0x75, 0x25, 0xdc, 0x4b, 0x83, 0xec, 0x0c, 0xe2, 0xab, 0x4b, 0xef, 0x51]) + libadobe.devkey_bytes = bytearray([0xf8, 0x7a, 0xfc, 0x8c, 0x75, 0x25, 0xdc, 0x4b, 0x83, 0xec, 0x0c, 0xe2, 0xab, 0x4b, 0xef, 0x51]) - mock_data = bytes([0xc2, 0x3b, 0x0f, 0xde, 0xf2, 0x4a, 0xc3, 0x03, 0xae, 0xc8, 0x70, 0xd4, + mock_data = bytearray([0xc2, 0x3b, 0x0f, 0xde, 0xf2, 0x4a, 0xc3, 0x03, 0xae, 0xc8, 0x70, 0xd4, 0x46, 0x6c, 0x8b, 0xb0, 0x23, 0x5a, 0xd3, 0x1b, 0x4e, 0x2b, 0x12, 0x79, 0x85, 0x63, 0x2d, 0x01, 0xa4, 0xe8, 0x29, 0x22]) @@ -175,7 +178,7 @@ class TestAdobe(unittest.TestCase): # in case we need to go back to the old method. mock_signing_key = "MIICdAIBADANBgkqhkiG9w0BAQEFAASCAl4wggJaAgEAAoGBALluuPvdDpr4L0j3eIGy3VxhgRcEKU3++qwbdvLXI99/izW9kfELFFJtq5d4ktIIUIvHsWkW0jblGi+bQ4sQXCeIvtOgqVHMSvRpW78lnGEkdD4Y1qhbcVGw7OGpWlhp8qCJKVCGbrkML7BSwFvQqqvg4vMU8O1uALfJvicKN3YfAgMBAAECf3uEg+Hr+DrstHhZF40zJPHKG3FkFd3HerXbOawMH5Q6CKTuKDGmOYQD+StFIlMArQJh8fxTVM3gSqgPkyyiesw0OuECU985FaLbUWxuCQzBcitnhl+VSv19oEPHTJWu0nYabasfT4oPjf8eiWR/ymJ9DZrjMWWy4Xf/S+/nFYUCQQDIZ1pc9nZsCB4QiBl5agTXoMcKavxFHPKxI/mHfRCHYjNyirziBJ+Dc/N40zKvldNBjO43KjLhUZs/BxdAJo09AkEA7OAdsg6SmviVV8xk0vuTmgLxhD7aZ9vpV4KF5+TH2DbximFoOP3YRObXV862wAjCpa84v43ok7Imtsu3NKQ+iwJAc0mx3GUU/1U0JoKFVSm+m2Ws27tsYT4kB/AQLvetuJSv0CcsPkI2meLsoAev0v84Ry+SIz4tgx31V672mzsSaQJBAJET1rw2Vq5Zr8Y9ZkceVFGQmfGAOW5A71Jsm6zin0+anyc874NwXaQdqiiab61/8A9gGSahOKA1DacJcCTqr28CQGm4mn3rOQFf+nniajIobATjNHaZJ76Xnc6rtoreK6+ZjO9wYF+797X/bhiV11Fpakvyrz6+t7bAd0PPQ2taTDg=" - payload_bytes = bytes([0x34, 0x52, 0xe3, 0xd1, 0x1c, 0xdd, 0x70, 0xeb, 0x90, 0x32, 0x3f, 0x29, 0x1c, 0x06, 0xaf, 0xaf, 0xe1, 0x0e, 0x09, 0x8a]) + payload_bytes = bytearray([0x34, 0x52, 0xe3, 0xd1, 0x1c, 0xdd, 0x70, 0xeb, 0x90, 0x32, 0x3f, 0x29, 0x1c, 0x06, 0xaf, 0xaf, 0xe1, 0x0e, 0x09, 0x8a]) try: import rsa @@ -184,7 +187,10 @@ class TestAdobe(unittest.TestCase): key = rsa.PrivateKey.load_pkcs1(RSA.importKey(base64.b64decode(mock_signing_key)).exportKey()) keylen = rsa.pkcs1.common.byte_size(key.n) - padded = rsa.pkcs1._pad_for_signing(payload_bytes, keylen) + if sys.version_info[0] >= 3: + padded = rsa.pkcs1._pad_for_signing(bytes(payload_bytes), keylen) + else: + padded = rsa.pkcs1._pad_for_signing(bytes(payload_bytes), keylen) payload = rsa.pkcs1.transform.bytes2int(padded) encrypted = key.blinded_encrypt(payload) block = rsa.pkcs1.transform.int2bytes(encrypted, keylen) @@ -200,7 +206,7 @@ class TestAdobe(unittest.TestCase): '''Check if the builtin CustomRSA library signs correctly''' mock_signing_key = "MIICdAIBADANBgkqhkiG9w0BAQEFAASCAl4wggJaAgEAAoGBALluuPvdDpr4L0j3eIGy3VxhgRcEKU3++qwbdvLXI99/izW9kfELFFJtq5d4ktIIUIvHsWkW0jblGi+bQ4sQXCeIvtOgqVHMSvRpW78lnGEkdD4Y1qhbcVGw7OGpWlhp8qCJKVCGbrkML7BSwFvQqqvg4vMU8O1uALfJvicKN3YfAgMBAAECf3uEg+Hr+DrstHhZF40zJPHKG3FkFd3HerXbOawMH5Q6CKTuKDGmOYQD+StFIlMArQJh8fxTVM3gSqgPkyyiesw0OuECU985FaLbUWxuCQzBcitnhl+VSv19oEPHTJWu0nYabasfT4oPjf8eiWR/ymJ9DZrjMWWy4Xf/S+/nFYUCQQDIZ1pc9nZsCB4QiBl5agTXoMcKavxFHPKxI/mHfRCHYjNyirziBJ+Dc/N40zKvldNBjO43KjLhUZs/BxdAJo09AkEA7OAdsg6SmviVV8xk0vuTmgLxhD7aZ9vpV4KF5+TH2DbximFoOP3YRObXV862wAjCpa84v43ok7Imtsu3NKQ+iwJAc0mx3GUU/1U0JoKFVSm+m2Ws27tsYT4kB/AQLvetuJSv0CcsPkI2meLsoAev0v84Ry+SIz4tgx31V672mzsSaQJBAJET1rw2Vq5Zr8Y9ZkceVFGQmfGAOW5A71Jsm6zin0+anyc874NwXaQdqiiab61/8A9gGSahOKA1DacJcCTqr28CQGm4mn3rOQFf+nniajIobATjNHaZJ76Xnc6rtoreK6+ZjO9wYF+797X/bhiV11Fpakvyrz6+t7bAd0PPQ2taTDg=" - payload_bytes = bytes([0x34, 0x52, 0xe3, 0xd1, 0x1c, 0xdd, 0x70, 0xeb, 0x90, 0x32, 0x3f, 0x29, 0x1c, 0x06, 0xaf, 0xaf, 0xe1, 0x0e, 0x09, 0x8a]) + payload_bytes = bytearray([0x34, 0x52, 0xe3, 0xd1, 0x1c, 0xdd, 0x70, 0xeb, 0x90, 0x32, 0x3f, 0x29, 0x1c, 0x06, 0xaf, 0xaf, 0xe1, 0x0e, 0x09, 0x8a]) block = CustomRSA.encrypt_for_adobe_signature(base64.b64decode(mock_signing_key), payload_bytes) signature = base64.b64encode(block).decode() @@ -287,7 +293,7 @@ class TestAdobe(unittest.TestCase): user = "username" passwd = "unit-test-password" - libadobe.devkey_bytes = bytes([0xf8, 0x7a, 0xfc, 0x8c, 0x75, 0x25, 0xdc, 0x4b, 0x83, 0xec, 0x0c, 0xe2, 0xab, 0x4b, 0xef, 0x51]) + libadobe.devkey_bytes = bytearray([0xf8, 0x7a, 0xfc, 0x8c, 0x75, 0x25, 0xdc, 0x4b, 0x83, 0xec, 0x0c, 0xe2, 0xab, 0x4b, 0xef, 0x51]) encrypted = libadobeAccount.encryptLoginCredentials(user, passwd, mock_auth_certificate) # Okay, now try to decrypt this again: @@ -296,13 +302,18 @@ class TestAdobe(unittest.TestCase): cipher_engine = PKCS1_v1_5.new(pkey) msg = cipher_engine.decrypt(encrypted, bytes([0x00] * 16)) + import struct + expected_msg = bytearray(libadobe.devkey_bytes) - expected_msg.extend(bytearray(len(user).to_bytes(1, 'big'))) + expected_msg.extend(bytearray(struct.pack("B", len(user)))) expected_msg.extend(bytearray(user.encode("latin-1"))) - expected_msg.extend(bytearray(len(passwd).to_bytes(1, 'big'))) + expected_msg.extend(bytearray(struct.pack("B", len(passwd)))) expected_msg.extend(bytearray(passwd.encode("latin-1"))) - self.assertEqual(msg.hex(), expected_msg.hex(), "devkey encryption returned invalid result") + if sys.version_info[0] >= 3: + self.assertEqual(msg.hex(), expected_msg.hex(), "devkey encryption returned invalid result") + else: + self.assertEqual(msg, expected_msg, "devkey encryption returned invalid result") @@ -322,7 +333,7 @@ class TestOther(unittest.TestCase): '''Check if Wine username decoder is working properly''' self.assertEqual(fix_wine_username(r'"1234"'), b'1234', "Wine username mismatch") - self.assertEqual(fix_wine_username(r'"a\x00e931"'), b'a\xe931', "Wine username mismatch") + self.assertEqual(fix_wine_username(r'"a\x00e931"'), b'a\xe931', "Wine username mismatch with UTF-8") def test_pdf_trimEncrypt(self): '''Check if PDF encryption string trimming code is working properly'''