From f7eb9e5d79a31a0941dce0f64180e1f04060032a Mon Sep 17 00:00:00 2001 From: Florian Bach Date: Thu, 25 Nov 2021 09:15:37 +0100 Subject: [PATCH] Beta 3: Support for importing from Wine --- .gitignore | 2 + README.md | 3 +- bundle_calibre_plugin.sh | 2 +- calibre-plugin/__init__.py | 17 +- calibre-plugin/config.py | 256 +++++++++++++++--- calibre-plugin/{key-wine => }/cpuid.py | 0 .../{key-wine => }/getEncryptionKeyLinux.py | 107 ++++++-- .../{key-wine => }/getEncryptionKeyWindows.py | 79 ++++-- .../{key-wine => keyextract}/Makefile | 0 .../{key-wine => keyextract}/main.c | 26 +- calibre-plugin/libadobeAccount.py | 18 +- calibre-plugin/libadobeEncryptionWindows.py | 225 --------------- calibre-plugin/libadobeFulfill.py | 20 +- calibre-plugin/libadobeImportAccount.py | 90 +++++- calibre-plugin/prefs.py | 1 + 15 files changed, 509 insertions(+), 337 deletions(-) create mode 100644 .gitignore rename calibre-plugin/{key-wine => }/cpuid.py (100%) rename calibre-plugin/{key-wine => }/getEncryptionKeyLinux.py (77%) rename calibre-plugin/{key-wine => }/getEncryptionKeyWindows.py (76%) rename calibre-plugin/{key-wine => keyextract}/Makefile (100%) rename calibre-plugin/{key-wine => keyextract}/main.c (81%) delete mode 100644 calibre-plugin/libadobeEncryptionWindows.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7753923 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/calibre-plugin/*.zip +/calibre-plugin/keyextract/*.exe \ No newline at end of file diff --git a/README.md b/README.md index d06f859..8b6071d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ It is a full Python reimplementation of libgourou by Grégory Soutadé (http://i 1. Download the plugin and import it into Calibre 2. Open the plugin settings, it should say "Not authorized for any ADE ID" -3. If you have ADE installed on your machine (Windows+Mac only, no Linux/Wine), there will be a button "Import activation from ADE". Clicking that will automatically copy your account information from ADE over to the Calibre plugin without using up an activation. +3. If you have ADE installed on your machine, there will be a button "Import activation from ADE". Clicking that will automatically copy your account information from ADE over to the Calibre plugin without using up an activation. 4. If you don't have ADE installed, or you want to authorize a different account, or the automatic retrieval from ADE failed, click the "Link to ADE account" button to make a new clean authorization. You will then be asked to enter your AdobeID and password and to select an ADE version (ADE 2.0.1 recommended). A couple seconds later a success message should be displayed. 5. The settings window should now say "Authorized with ADE ID X on device Y, emulating ADE version Z". 6. Click the "Export account activation data" and "Export account encryption key" buttons to export / backup your keys. Do not skip this step. The first file (ZIP) can be used to re-authorize Calibre after a reset / reinstall without using up one of your Adobe authorizations. The second file (DER) can be imported into DeDRM. @@ -45,4 +45,5 @@ There's a bunch of features that could still be added, but most of them aren't i - Support for anonymous Adobe IDs - Support for un-authorizing a machine +- Support to copy an authorization from the plugin to an ADE install - ... diff --git a/bundle_calibre_plugin.sh b/bundle_calibre_plugin.sh index 9a22bfd..61d61ad 100755 --- a/bundle_calibre_plugin.sh +++ b/bundle_calibre_plugin.sh @@ -7,7 +7,7 @@ [ ! -f calibre-plugin/pyasn1.zip ] && ./package_modules.sh pushd calibre-plugin -pushd key-wine +pushd keyextract # Compile: make diff --git a/calibre-plugin/__init__.py b/calibre-plugin/__init__.py index f3feddc..80dc95b 100644 --- a/calibre-plugin/__init__.py +++ b/calibre-plugin/__init__.py @@ -20,9 +20,11 @@ # fix bug that would block other FileTypePlugins # v0.0.12: Fix Calibre Plugin index / updater # v0.0.13: Add support for emulating multiple ADE versions (1.7.2, 2.0.1, 3.0.1, 4.0.3, 4.5.11), -# add code to import existing activation from ADE (Windows+Mac only), +# add code to import existing activation from ADE (Windows, MacOS or Linux/Wine), +# add code to remove an existing activation from the plugin (Ctrl+Shift+D), # fix race condition when importing multiple ACSMs simultaneously, -# fix authorization failing with certain non-ASCII characters in username. +# fix authorization failing with certain non-ASCII characters in username, +# add detailed logging toggle setting. PLUGIN_NAME = "DeACSM" PLUGIN_VERSION_TUPLE = (0, 0, 13) @@ -33,6 +35,7 @@ __version__ = PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE]) from calibre.utils.config import config_dir # type: ignore +from calibre.constants import isosx, iswindows, islinux # type: ignore import os, shutil, traceback, sys, time, io import zipfile @@ -100,6 +103,16 @@ class DeACSM(FileTypePlugin): traceback.print_exc() pass + if islinux: + # Also extract EXE files needed for WINE ADE key extraction + names = [ "keyextract/decrypt_win32.exe", "keyextract/decrypt_win64.exe" ] + lib_dict = self.load_resources(names) + for entry, data in lib_dict.items(): + file_path = os.path.join(self.moddir, entry.split('/')[1]) + f = open(file_path, "wb") + f.write(data) + f.close() + sys.path.insert(0, os.path.join(self.moddir, "cryptography")) sys.path.insert(0, os.path.join(self.moddir, "rsa")) sys.path.insert(0, os.path.join(self.moddir, "oscrypto")) diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py index 588e163..772d6ee 100644 --- a/calibre-plugin/config.py +++ b/calibre-plugin/config.py @@ -4,6 +4,7 @@ # pyright: reportUndefinedVariable=false import os, base64, traceback +from PyQt5.QtGui import QKeySequence from lxml import etree @@ -13,6 +14,7 @@ import time, datetime from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, QGroupBox, QPushButton, QListWidget, QListWidgetItem, QInputDialog, QLineEdit, QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl) +from PyQt5.QtWidgets import QShortcut from PyQt5 import QtCore @@ -25,7 +27,7 @@ from calibre.gui2 import (question_dialog, error_dialog, info_dialog, choose_sav from calibre_plugins.deacsm.__init__ import PLUGIN_NAME, PLUGIN_VERSION # type: ignore import calibre_plugins.deacsm.prefs as prefs # type: ignore from calibre.utils.config import config_dir # type: ignore -from calibre.constants import isosx, iswindows # type: ignore +from calibre.constants import isosx, iswindows, islinux # type: ignore class ConfigWidget(QWidget): @@ -42,6 +44,7 @@ class ConfigWidget(QWidget): self.tempdeacsmprefs['path_to_account_data'] = self.deacsmprefs['path_to_account_data'] self.tempdeacsmprefs['notify_fulfillment'] = self.deacsmprefs['notify_fulfillment'] + self.tempdeacsmprefs['detailed_logging'] = self.deacsmprefs['detailed_logging'] self.tempdeacsmprefs['list_of_rented_books'] = self.deacsmprefs['list_of_rented_books'] @@ -80,6 +83,12 @@ class ConfigWidget(QWidget): self.button_import_WinADE.clicked.connect(self.import_activation_from_Win) ua_group_box_layout.addWidget(self.button_import_WinADE) + if islinux: + self.button_import_LinuxWineADE = QtGui.QPushButton(self) + self.button_import_LinuxWineADE.setText(_("Import activation from ADE (Wine)")) + self.button_import_LinuxWineADE.clicked.connect(self.import_activation_from_LinuxWine) + ua_group_box_layout.addWidget(self.button_import_LinuxWineADE) + self.button_import_activation = QtGui.QPushButton(self) self.button_import_activation.setText(_("Import existing activation backup (ZIP)")) self.button_import_activation.clicked.connect(self.import_activation_from_ZIP) @@ -119,6 +128,17 @@ class ConfigWidget(QWidget): self.chkNotifyFulfillment.setChecked(self.tempdeacsmprefs["notify_fulfillment"]) layout.addWidget(self.chkNotifyFulfillment) + self.chkDetailedLogging = QtGui.QCheckBox("Enable detailed debug logging") + self.chkDetailedLogging.setToolTip("Default: False\n\nIf this is enabled, the plugin debug logs will be more verbose which might be helpful in case of errors.\nHowever, it will also mean that private data like encryption keys or account credentials might end up in the logfiles.") + self.chkDetailedLogging.setChecked(self.tempdeacsmprefs["detailed_logging"]) + self.chkDetailedLogging.toggled.connect(self.toggle_logging) + layout.addWidget(self.chkDetailedLogging) + + # Key shortcut Ctrl+Shift+D to remove authorization, just like in ADE. + self.deauthShortcut = QShortcut(QKeySequence("Ctrl+Shift+D"), self) + self.deauthShortcut.activated.connect(self.delete_ade_auth) + + try: from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path, are_ade_version_lists_valid @@ -135,25 +155,104 @@ class ConfigWidget(QWidget): update_account_path(self.deacsmprefs["path_to_account_data"]) self.resize(self.sizeHint()) - if not are_ade_version_lists_valid(): - # Internal error, this should never happen - if not activated: - self.button_link_account.setEnabled(False) - self.button_import_activation.setEnabled(False) - if isosx: - self.button_import_MacADE.setEnabled(activated) - if iswindows: - self.button_import_WinADE.setEnabled(activated) - else: - self.button_switch_ade_version.setEnabled(False) - self.button_export_key.setEnabled(False) - self.button_export_activation.setEnabled(False) - self.button_rented_books.setEnabled(False) - self.chkNotifyFulfillment.setEnabled(False) + try: + # Someone reported getting this error after upgrading the plugin. + # No idea why that happens - put a try/catch around just to be safe. + if not are_ade_version_lists_valid(): + # Internal error, this should never happen + if not activated: + self.button_link_account.setEnabled(False) + self.button_import_activation.setEnabled(False) + if isosx: + self.button_import_MacADE.setEnabled(activated) + if iswindows: + self.button_import_WinADE.setEnabled(activated) + if islinux: + self.button_import_LinuxWineADE.setEnabled(activated) + else: + self.button_switch_ade_version.setEnabled(False) + self.button_export_key.setEnabled(False) + self.button_export_activation.setEnabled(False) + self.button_rented_books.setEnabled(False) + self.chkNotifyFulfillment.setEnabled(False) - error_dialog(None, "Internal error", "Version list mismatch. Please open a bug report.", show=True, show_copy_button=False) + error_dialog(None, "Internal error", "Version list mismatch. Please open a bug report.", show=True, show_copy_button=False) + except UnboundLocalError: + print("Verify function are_ade_version_lists_valid() not found - why?") + + + def toggle_logging(self): + if not self.chkDetailedLogging.isChecked(): + return + + msg = "You have enabled detailed logging.\n" + msg += "This will cause various data to be included in the logfiles, like encryption keys, account keys and other confidential data.\n" + msg += "With this setting enabled, only share log files privately with the developer and don't make them publicly available." + + info_dialog(None, "Warning", msg, show=True, show_copy_button=False) + + def delete_ade_auth(self): + # This function can only be triggered with the key combination Ctrl+Shift+D. + # There is no easy-to-access button to trigger that to prevent people from + # accidentally deleting their authorization. + + info_string, activated, ade_mail = self.get_account_info() + + if not activated: + # If there is no authorization, there's nothing to delete + return + + msg = "Are you sure you want to remove the ADE authorization?\n" + + if ade_mail is None: + msg += "The current authorization is an anonymous login. It will be permanently lost if you proceed.\n\n" + else: + msg += "You will use up one of your six activations if you want to authorize your account again in the future.\n\n" + + msg += "Click 'Yes' to delete the authorization or 'No' to cancel." + + ok = question_dialog(None, "Remove ADE account", msg) + + if (not ok): + return + + msg = "Do you want to create a backup of the current authorization?\n" + msg += "This backup can be imported again without using up one of your authorizations.\n\n" + msg += "Click 'Yes' to create a backup before deleting, click 'No' to delete without backup." + + + ok = question_dialog(None, "Remove ADE account", msg) + + if (ok): + # Create a backup: + backup_success = self.export_activation() + if (not backup_success): + error_dialog(None, "Export failed", "The backup was unsuccessful - authorization will not be deleted.", show=True, show_copy_button=False) + return + + # Okay, once we are here, we can be pretty sure the user actually wants to delete their authorization. + try: + os.remove(os.path.join(self.deacsmprefs["path_to_account_data"], "activation.xml")) + os.remove(os.path.join(self.deacsmprefs["path_to_account_data"], "device.xml")) + os.remove(os.path.join(self.deacsmprefs["path_to_account_data"], "devicesalt")) + except: + error_dialog(None, "Remove ADE account", "There was an error while removing the authorization.", show=True, show_copy_button=False) + + + # Show success, then close: + info_dialog(None, "Remove ADE account", "ADE authorization successfully removed.", show=True, show_copy_button=False) + + + try: + self.button_switch_ade_version.setEnabled(False) + except: + pass + self.button_export_activation.setEnabled(False) + self.button_export_key.setEnabled(False) + self.lblAccInfo.setText("Authorization deleted.\nClose and re-open this window to add a new authorization.") + def get_account_info(self): @@ -236,6 +335,7 @@ class ConfigWidget(QWidget): except: 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"]) @@ -253,7 +353,7 @@ class ConfigWidget(QWidget): filters, all_files=False, initial_filename=export_filename) if (filename is None): - return + return False print("{0} v{1}: Exporting activation data to {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename)) @@ -262,8 +362,97 @@ class ConfigWidget(QWidget): zipfile.write(os.path.join(self.deacsmprefs["path_to_account_data"], "device.xml"), "device.xml") zipfile.write(os.path.join(self.deacsmprefs["path_to_account_data"], "activation.xml"), "activation.xml") zipfile.write(os.path.join(self.deacsmprefs["path_to_account_data"], "devicesalt"), "devicesalt") + + return True except: - return error_dialog(None, "Export failed", "Export failed.", show=True, show_copy_button=False) + error_dialog(None, "Export failed", "Export failed.", show=True, show_copy_button=False) + return False + + def check_ADE_registry(self, wineprefix): + # Gets a path to a WINEPREFIX and returns True if this is useable. + # Checks if the Wine registry contains an ADE activation. + + try: + registry_file = open(os.path.join(wineprefix, "user.reg")) + while True: + line = registry_file.readline() + if not line: + break + + if line.strip().startswith("[Software\\\\Adobe\\\\Adept\\\\Activation\\\\0000"): + return True + + except: + print("Exception while validating WINEPREFIX:") + print(traceback.format_exc()) + + return False + + def import_activation_from_LinuxWine(self): + # This will try to import the activation from Adobe Digital Editions on Linux / Wine ... + + msg = "Trying to import existing activation from Adobe Digital Editions in WINE ...\n" + msg += "Note: Importing the activation can take up to 30 seconds, and Calibre will appear to be \"stuck\" during that time.\n\n" + msg += "Please enter the full, absolute path to your WINEPREFIX." + msg += "If there's already a path in the input box, it is usually (but not always) the correct one." + + default_path = "" + + if (default_path == ""): + # Check WINEPREFIX env variable + env_wineprefix = os.getenv("WINEPREFIX", None) + if (env_wineprefix is not None and os.path.isdir(env_wineprefix)): + if self.check_ADE_registry(env_wineprefix): + default_path = env_wineprefix + + if (default_path == ""): + # Use default path ".wine" in HOME dir + home_wineprefix = os.path.join(os.path.expanduser("~"), ".wine") + if (os.path.isdir(home_wineprefix)): + if self.check_ADE_registry(home_wineprefix): + default_path = home_wineprefix + + + text, ok = QInputDialog.getText(self, "Importing authorization", msg, text=default_path) + + if (not ok): + return + + if (not os.path.isdir(text)): + return error_dialog(None, "Import failed", "The WINEPREFIX path you entered doesn't seem to exist.", show=True, show_copy_button=False) + + if (not self.check_ADE_registry(text)): + 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 + + ret, msg = importADEactivationLinuxWine(text) + + if (ret): + # update display + info_string, activated, ade_mail = self.get_account_info() + self.lblAccInfo.setText(info_string) + + self.button_link_account.setEnabled(not activated) + self.button_import_activation.setEnabled(not activated) + self.button_import_LinuxWineADE.setEnabled(not activated) + self.button_export_key.setEnabled(activated) + self.button_export_activation.setEnabled(activated) + + + self.resize(self.sizeHint()) + + if (activated): + if ade_mail is None: + info_dialog(None, "Done", "Successfully imported an anonymous authorization", show=True, show_copy_button=False) + else: + info_dialog(None, "Done", "Successfully imported authorization for " + ade_mail, show=True, show_copy_button=False) + else: + error_dialog(None, "Import failed", "Import looks like it worked, but the resulting files seem to be corrupted ...", show=True, show_copy_button=False) + else: + error_dialog(None, "Import failed", "That didn't work:\n" + msg, show=True, show_copy_button=False) + def import_activation_from_Win(self): # This will try to import the activation from Adobe Digital Editions on Windows ... @@ -382,16 +571,14 @@ class ConfigWidget(QWidget): self.button_import_activation.setEnabled(not activated) self.button_export_key.setEnabled(activated) self.button_export_activation.setEnabled(activated) - try: - self.button_import_MacADE.setEnabled(activated) - except: - pass + if isosx: + self.button_import_MacADE.setEnabled(not activated) + if iswindows: + self.button_import_WinADE.setEnabled(not activated) + if islinux: + self.button_import_LinuxWineADE.setEnabled(not activated) + - try: - self.button_import_WinADE.setEnabled(activated) - except: - pass - self.resize(self.sizeHint()) if ade_mail is None: @@ -497,7 +684,7 @@ class ConfigWidget(QWidget): return error_dialog(None, "Failed", "Error while changing ADE version: " + msg, show=True, show_copy_button=False) except: - return error_dialog(None, "Failed", "Error while changing ADE version.", show=True, det_msg=err, show_copy_button=False) + return error_dialog(None, "Failed", "Error while changing ADE version.", show=True, det_msg=traceback.format_exc(), show_copy_button=False) def link_account(self): @@ -583,14 +770,12 @@ class ConfigWidget(QWidget): self.button_import_activation.setEnabled(False) self.button_export_key.setEnabled(True) self.button_export_activation.setEnabled(True) - try: + if isosx: self.button_import_MacADE.setEnabled(False) - except: - pass - try: + if iswindows: self.button_import_WinADE.setEnabled(False) - except: - pass + if islinux: + self.button_import_LinuxWineADE.setEnabled(False) self.resize(self.sizeHint()) @@ -644,6 +829,7 @@ class ConfigWidget(QWidget): def save_settings(self): self.deacsmprefs.set('notify_fulfillment', self.chkNotifyFulfillment.isChecked()) + self.deacsmprefs.set('detailed_logging', self.chkDetailedLogging.isChecked()) self.deacsmprefs.writeprefs() def load_resource(self, name): diff --git a/calibre-plugin/key-wine/cpuid.py b/calibre-plugin/cpuid.py similarity index 100% rename from calibre-plugin/key-wine/cpuid.py rename to calibre-plugin/cpuid.py diff --git a/calibre-plugin/key-wine/getEncryptionKeyLinux.py b/calibre-plugin/getEncryptionKeyLinux.py similarity index 77% rename from calibre-plugin/key-wine/getEncryptionKeyLinux.py rename to calibre-plugin/getEncryptionKeyLinux.py index c5e2967..e078a24 100644 --- a/calibre-plugin/key-wine/getEncryptionKeyLinux.py +++ b/calibre-plugin/getEncryptionKeyLinux.py @@ -2,6 +2,9 @@ # -*- coding: utf-8 -*- +from re import VERBOSE + + def unfuck(user): # Wine uses a pretty nonstandard encoding in their registry file. # I haven't found any existing Python implementation for that, @@ -109,7 +112,19 @@ def GetMasterKey(path_to_wine_prefix): print("Hey! This is for Linux!") return - import cpuid + verbose_logging = False + try: + import calibre_plugins.deacsm.prefs as prefs + deacsmprefs = prefs.DeACSM_Prefs() + verbose_logging = deacsmprefs["detailed_logging"] + except: + pass + + try: + import cpuid + except: + import calibre_plugins.deacsm.cpuid as cpuid + import struct try: @@ -119,21 +134,26 @@ def GetMasterKey(path_to_wine_prefix): serial_file.close() serial = int(serial, 16) except: - # If this file is not present, Wine will use a default serial number of "0". + # If this file is not present, Wine will usually use a default serial number of "0". + # There are some edge cases where Wine uses a different serial number even when that + # .windows-serial file is not present. serial = 0 - print("Serial: " + str(serial)) + if (verbose_logging): + print("Serial: " + str(serial)) cpu = cpuid.CPUID() _, b, c, d = cpu(0) vendor = struct.pack("III", b, d, c) - print("Vendor: " + vendor.decode("utf-8")) + if (verbose_logging): + print("Vendor: " + vendor.decode("utf-8")) signature, _, _, _ = cpu(1) signature = struct.pack('>I', signature)[1:] - print("Signature: " + str(signature.hex())) + if (verbose_logging): + print("Signature: " + str(signature.hex())) # Search for the username in the registry: user = None @@ -174,7 +194,8 @@ def GetMasterKey(path_to_wine_prefix): print("Error while determining username ...") exit() - print("Username: " + str(user)) + if verbose_logging: + print("Username: " + str(user)) # Find the value we want to decrypt from the registry. loop through the Wine registry file to find the "key" attribute try: @@ -213,12 +234,12 @@ def GetMasterKey(path_to_wine_prefix): raise pass + if key_line is None: + print("No ADE activation found ...") + return None - print("Encrypted key: " + str(key_line)) - - import hexdump - - hexdump.hexdump(key_line) + if verbose_logging: + print("Encrypted key: " + str(key_line)) # These should all be "bytes" or "bytearray" #print(type(vendor)) @@ -227,29 +248,47 @@ def GetMasterKey(path_to_wine_prefix): entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user) - print("Entropy: " + str(entropy)) + if verbose_logging: + print("Entropy: " + str(entropy)) # We would now call CryptUnprotectData to decrypt the stuff, # but unfortunately there's no working Linux implementation - # for that. This means we have to call a Windows binary through + # for that. + # + # The plan was to handle everything in Python so we don't have + # to interact with Wine - that's why we're doing all the registry + # handling ourselves. + # Unfortunately, that doesn't work for the actual decryption. + # + # This means we have to call a Windows binary through # Wine just for this one single decryption call ... success, data = CryptUnprotectDataExecuteWine(path_to_wine_prefix, key_line, entropy) if (success): keykey = data - print(keykey) + if verbose_logging: + print("Key key: ") + print(keykey) return keykey else: print("Error number: " + str(data)) if data == 13: # WINError ERROR_INVALID_DATA - print("Could not decrypt data with the given key. Did the Wine username change?") + print("Could not decrypt data with the given key. Did the entropy change?") return None def CryptUnprotectDataExecuteWine(wineprefix, data, entropy): import subprocess, os, re + verbose_logging = False + try: + import calibre_plugins.deacsm.prefs as prefs + deacsmprefs = prefs.DeACSM_Prefs() + verbose_logging = deacsmprefs["detailed_logging"] + except: + pass + print("Asking WINE to decrypt encrypted key for us ...") if wineprefix == "" or not os.path.exists(wineprefix): @@ -282,10 +321,10 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy): env_dict = os.environ env_dict["PYTHONPATH"] = "" env_dict["WINEPREFIX"] = wineprefix - env_dict["WINEDEBUG"] = "-all,+crypt" - - import base64 + #env_dict["WINEDEBUG"] = "-all,+crypt" + env_dict["WINEDEBUG"] = "+err,+fixme" + # Use environment variables to get the input data to the application. env_dict["X_DECRYPT_DATA"] = data.hex() env_dict["X_DECRYPT_ENTROPY"] = entropy.hex() @@ -296,7 +335,7 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy): moddir = os.path.join(maindir,"modules") except: import os - moddir = os.path.dirname(os.path.abspath(__file__)) + moddir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "keyextract") proc = subprocess.Popen(["wine", "decrypt_" + winearch + ".exe" ], shell=False, cwd=moddir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) prog_output, prog_stderr = proc.communicate() @@ -305,22 +344,34 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy): if prog_output.decode("utf-8").startswith("PROGOUTPUT:0:"): key_string = prog_output.decode("utf-8").split(':')[2] - print("Successfully got encryption key from WINE: " + key_string) + if verbose_logging: + print("Successfully got encryption key from WINE: " + key_string) + else: + print("Successfully got encryption key from WINE.") master_key = bytes.fromhex(key_string) return True, master_key else: print("Huh. That didn't work. ") - err = int(prog_output.decode("utf-8").split(':')[1]) - if err == -4: - err = int(prog_output.decode("utf-8").split(':')[2]) + try: + err = int(prog_output.decode("utf-8").split(':')[1]) + if err == -4: + err = int(prog_output.decode("utf-8").split(':')[2]) + new_serial = int(prog_output.decode("utf-8").split(':')[3]) + if verbose_logging: + print("New serial: " + str(new_serial)) + except: + pass - print("Program reported: " + prog_output.decode("utf-8")) - print("Debug log: ") - print(prog_stderr.decode("utf-8")) + if verbose_logging: + print("Program reported: " + prog_output.decode("utf-8")) + print("Debug log: ") + print(prog_stderr.decode("utf-8")) + return False, err - -GetMasterKey() +if __name__ == "__main__": + print("Do not execute this directly!") + exit() diff --git a/calibre-plugin/key-wine/getEncryptionKeyWindows.py b/calibre-plugin/getEncryptionKeyWindows.py similarity index 76% rename from calibre-plugin/key-wine/getEncryptionKeyWindows.py rename to calibre-plugin/getEncryptionKeyWindows.py index e1805f8..4d9de7c 100644 --- a/calibre-plugin/key-wine/getEncryptionKeyWindows.py +++ b/calibre-plugin/getEncryptionKeyWindows.py @@ -25,14 +25,13 @@ def GetSystemDirectory(): GetSystemDirectoryW = kernel32.GetSystemDirectoryW GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint] GetSystemDirectoryW.restype = c_uint - def GetSystemDirectory(): - buffer = create_unicode_buffer(MAX_PATH + 1) - GetSystemDirectoryW(buffer, len(buffer)) - return buffer.value - return GetSystemDirectory -GetSystemDirectory = GetSystemDirectory() -def GetVolumeSerialNumber(): + buffer = create_unicode_buffer(MAX_PATH + 1) + GetSystemDirectoryW(buffer, len(buffer)) + return buffer.value + + +def GetVolumeSerialNumber(path): from ctypes import windll, c_wchar_p, c_uint, POINTER, byref kernel32 = windll.kernel32 @@ -41,13 +40,11 @@ def GetVolumeSerialNumber(): POINTER(c_uint), POINTER(c_uint), POINTER(c_uint), c_wchar_p, c_uint] GetVolumeInformationW.restype = c_uint - def GetVolumeSerialNumber(path): - vsn = c_uint(0) - GetVolumeInformationW( - path, None, 0, byref(vsn), None, None, None, 0) - return vsn.value - return GetVolumeSerialNumber -GetVolumeSerialNumber = GetVolumeSerialNumber() + vsn = c_uint(0) + GetVolumeInformationW( + path, None, 0, byref(vsn), None, None, None, 0) + return vsn.value + def GetUserNameWINAPI(): @@ -113,23 +110,40 @@ def GetMasterKey(): if os.name != 'nt': print("This script is for Windows!") + verbose_logging = False + try: + import calibre_plugins.deacsm.prefs as prefs + deacsmprefs = prefs.DeACSM_Prefs() + verbose_logging = deacsmprefs["detailed_logging"] + except: + pass + # Get serial number of root drive root = GetSystemDirectory().split('\\')[0] + '\\' serial = GetVolumeSerialNumber(root) - print("Serial: " + str(serial)) + if verbose_logging: + print("Serial: " + str(serial)) # Get CPU vendor: - import cpuid, struct + try: + import cpuid + except: + import calibre_plugins.deacsm.cpuid as cpuid + + import struct cpu = cpuid.CPUID() _, b, c, d = cpu(0) vendor = struct.pack("III", b, d, c) - print("Vendor: " + vendor.decode("utf-8")) + + if verbose_logging: + print("Vendor: " + vendor.decode("utf-8")) signature, _, _, _ = cpu(1) signature = struct.pack('>I', signature)[1:] - print("Signature: " + str(signature.hex())) + if verbose_logging: + print("Signature: " + str(signature.hex())) # Search for the username in the registry: user = None @@ -143,12 +157,13 @@ def GetMasterKey(): else: user = current_user_name - if (user_from_registry is not None and user_from_registry != current_user_name): - print("Username: {0}/{1} mismatch, using {0}".format(str(user_from_registry), str(current_user_name))) - elif (user_from_registry is not None): - print("Username: {0} (Registry)".format(str(user_from_registry))) - else: - print("Username: {0} (WinAPI)".format(str(current_user_name))) + if verbose_logging: + if (user_from_registry is not None and user_from_registry != current_user_name): + print("Username: {0}/{1} mismatch, using {0}".format(str(user_from_registry), str(current_user_name))) + elif (user_from_registry is not None): + print("Username: {0} (Registry)".format(str(user_from_registry))) + else: + print("Username: {0} (WinAPI)".format(str(current_user_name))) @@ -164,9 +179,10 @@ def GetMasterKey(): device = winreg.QueryValueEx(regkey, 'key')[0] except: print("Can't find encrypted device key.") + return None - - print("Encrypted key: " + str(device)) + if verbose_logging: + print("Encrypted key: " + str(device)) # These three must all be bytes. #print(type(vendor)) @@ -175,16 +191,19 @@ def GetMasterKey(): entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user) - - print("Entropy: " + str(entropy)) + if verbose_logging: + print("Entropy: " + str(entropy)) keykey = CryptUnprotectData(device, entropy) if (keykey is None): print("Couldn't decrypt key!") return None - print("Decrypted key: " + str(keykey)) + if verbose_logging: + print("Decrypted key: " + str(keykey)) return keykey -GetMasterKey() + +if __name__ == "__main__": + GetMasterKey() diff --git a/calibre-plugin/key-wine/Makefile b/calibre-plugin/keyextract/Makefile similarity index 100% rename from calibre-plugin/key-wine/Makefile rename to calibre-plugin/keyextract/Makefile diff --git a/calibre-plugin/key-wine/main.c b/calibre-plugin/keyextract/main.c similarity index 81% rename from calibre-plugin/key-wine/main.c rename to calibre-plugin/keyextract/main.c index 1cbe24d..bbe5767 100644 --- a/calibre-plugin/key-wine/main.c +++ b/calibre-plugin/keyextract/main.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #ifdef DEBUG #undef DEBUG @@ -45,7 +47,7 @@ void hexDump ( // Output description if given. - if (desc != NULL) printf ("%s:\n", desc); + if (desc != NULL) fprintf (stderr, "%s:\n", desc); // Length checks. @@ -66,7 +68,7 @@ void hexDump ( if ((i % perLine) == 0) { // Only print previous-line ASCII buffer for lines beyond first. - if (i != 0) printf (" %s\n", buff); + if (i != 0) fprintf (stderr, " %s\n", buff); // Output the offset of current line. @@ -99,6 +101,15 @@ void hexDump ( } #endif +int get_serial() { + DWORD serial = 0; + int retval = GetVolumeInformation("c:\\\\", NULL, 0, &serial, NULL, NULL, NULL, 0); + if (retval == 0) { + fprintf(stderr, "Error with GetVolumeInformation: %d\n", GetLastError()); + return 0; + } + return serial; +} int main() { char * var_data = "X_DECRYPT_DATA"; @@ -164,7 +175,16 @@ if (ret) { exit(0); } else { - printf("PROGOUTPUT:-4:%d", GetLastError()); + + // Apparently Wine has issues with the volume serial code sometimes + // so the code on the Linux side detects the wrong serial number. + // Thus, if the decryption fails, we read the serial number that Wine + // (and ADE) sees back to the Linux side for another attempt. + + int err = GetLastError(); + + printf("PROGOUTPUT:-4:%d:%08x", err, get_serial()); + exit(-4); } diff --git a/calibre-plugin/libadobeAccount.py b/calibre-plugin/libadobeAccount.py index d435301..33d40d2 100644 --- a/calibre-plugin/libadobeAccount.py +++ b/calibre-plugin/libadobeAccount.py @@ -437,6 +437,14 @@ def activateDevice(useVersionIndex: int = 0): # ADE 1.7.2 or another version that authorization is disabled for return False, "Authorization not supported for this build ID" + verbose_logging = False + try: + import calibre_plugins.deacsm.prefs as prefs + deacsmprefs = prefs.DeACSM_Prefs() + verbose_logging = deacsmprefs["detailed_logging"] + except: + pass + result, activate_req = buildActivateReq(useVersionIndex) if (result is False): @@ -455,8 +463,9 @@ def activateDevice(useVersionIndex: int = 0): etree.SubElement(req_xml, etree.QName(NSMAP["adept"], "signature")).text = signature - #print ("final request:") - #print(etree.tostring(req_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1")) + if verbose_logging: + print ("Activation request:") + print(etree.tostring(req_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1")) data = "\n" + etree.tostring(req_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1") @@ -487,8 +496,9 @@ def activateDevice(useVersionIndex: int = 0): except: return False, "Error parsing Adobe /Activate response" - #print("Response from server: ") - #print(ret) + if verbose_logging: + print("Response from server: ") + print(ret) # Soooo, lets go and append that to the XML: diff --git a/calibre-plugin/libadobeEncryptionWindows.py b/calibre-plugin/libadobeEncryptionWindows.py deleted file mode 100644 index 880c30e..0000000 --- a/calibre-plugin/libadobeEncryptionWindows.py +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# Most of the code in this file has been taken from adobekey.pyw written by i♥cabbages -# adobekey.pyw, version 7.0 -# Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al. -# Released under the terms of the GNU General Public Licence, version 3 -# - - -from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ - create_unicode_buffer, create_string_buffer, CFUNCTYPE, \ - string_at, Structure, c_void_p, cast, c_size_t, memmove - -from ctypes.wintypes import LPVOID, DWORD, BOOL -import struct - -try: - import winreg -except ImportError: - import _winreg as winreg - - -MAX_PATH = 255 - -kernel32 = windll.kernel32 -advapi32 = windll.advapi32 -crypt32 = windll.crypt32 - -def GetSystemDirectory(): - GetSystemDirectoryW = kernel32.GetSystemDirectoryW - GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint] - GetSystemDirectoryW.restype = c_uint - def GetSystemDirectory(): - buffer = create_unicode_buffer(MAX_PATH + 1) - GetSystemDirectoryW(buffer, len(buffer)) - return buffer.value - return GetSystemDirectory -GetSystemDirectory = GetSystemDirectory() - -def GetVolumeSerialNumber(): - GetVolumeInformationW = kernel32.GetVolumeInformationW - GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint, - POINTER(c_uint), POINTER(c_uint), - POINTER(c_uint), c_wchar_p, c_uint] - GetVolumeInformationW.restype = c_uint - def GetVolumeSerialNumber(path): - vsn = c_uint(0) - GetVolumeInformationW( - path, None, 0, byref(vsn), None, None, None, 0) - return vsn.value - return GetVolumeSerialNumber -GetVolumeSerialNumber = GetVolumeSerialNumber() - -def GetUserName(): - GetUserNameW = advapi32.GetUserNameW - GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)] - GetUserNameW.restype = c_uint - def GetUserName(): - buffer = create_unicode_buffer(32) - size = c_uint(len(buffer)) - while not GetUserNameW(buffer, byref(size)): - buffer = create_unicode_buffer(len(buffer) * 2) - size.value = len(buffer) - return buffer.value.encode('utf-16-le')[::2] - return GetUserName -GetUserName = GetUserName() - -PAGE_EXECUTE_READWRITE = 0x40 -MEM_COMMIT = 0x1000 -MEM_RESERVE = 0x2000 - -def VirtualAlloc(): - _VirtualAlloc = kernel32.VirtualAlloc - _VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD] - _VirtualAlloc.restype = LPVOID - def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE), - protect=PAGE_EXECUTE_READWRITE): - return _VirtualAlloc(addr, size, alloctype, protect) - return VirtualAlloc -VirtualAlloc = VirtualAlloc() - -MEM_RELEASE = 0x8000 - -def VirtualFree(): - _VirtualFree = kernel32.VirtualFree - _VirtualFree.argtypes = [LPVOID, c_size_t, DWORD] - _VirtualFree.restype = BOOL - def VirtualFree(addr, size=0, freetype=MEM_RELEASE): - return _VirtualFree(addr, size, freetype) - return VirtualFree -VirtualFree = VirtualFree() - -class NativeFunction(object): - def __init__(self, restype, argtypes, insns): - self._buf = buf = VirtualAlloc(None, len(insns)) - memmove(buf, insns, len(insns)) - ftype = CFUNCTYPE(restype, *argtypes) - self._native = ftype(buf) - - def __call__(self, *args): - return self._native(*args) - - def __del__(self): - if self._buf is not None: - VirtualFree(self._buf) - self._buf = None - -if struct.calcsize("P") == 4: - CPUID0_INSNS = ( - b"\x53" # push %ebx - b"\x31\xc0" # xor %eax,%eax - b"\x0f\xa2" # cpuid - b"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax - b"\x89\x18" # mov %ebx,0x0(%eax) - b"\x89\x50\x04" # mov %edx,0x4(%eax) - b"\x89\x48\x08" # mov %ecx,0x8(%eax) - b"\x5b" # pop %ebx - b"\xc3" # ret - ) - CPUID1_INSNS = ( - b"\x53" # push %ebx - b"\x31\xc0" # xor %eax,%eax - b"\x40" # inc %eax - b"\x0f\xa2" # cpuid - b"\x5b" # pop %ebx - b"\xc3" # ret - ) -else: - CPUID0_INSNS = ( - b"\x49\x89\xd8" # mov %rbx,%r8 - b"\x49\x89\xc9" # mov %rcx,%r9 - b"\x48\x31\xc0" # xor %rax,%rax - b"\x0f\xa2" # cpuid - b"\x4c\x89\xc8" # mov %r9,%rax - b"\x89\x18" # mov %ebx,0x0(%rax) - b"\x89\x50\x04" # mov %edx,0x4(%rax) - b"\x89\x48\x08" # mov %ecx,0x8(%rax) - b"\x4c\x89\xc3" # mov %r8,%rbx - b"\xc3" # retq - ) - CPUID1_INSNS = ( - b"\x53" # push %rbx - b"\x48\x31\xc0" # xor %rax,%rax - b"\x48\xff\xc0" # inc %rax - b"\x0f\xa2" # cpuid - b"\x5b" # pop %rbx - b"\xc3" # retq - ) - -def cpuid0(): - _cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS) - buf = create_string_buffer(12) - def cpuid0(): - _cpuid0(buf) - return buf.raw - return cpuid0 -cpuid0 = cpuid0() - -cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS) - -class DataBlob(Structure): - _fields_ = [('cbData', c_uint), - ('pbData', c_void_p)] -DataBlob_p = POINTER(DataBlob) - -def CryptUnprotectData(): - _CryptUnprotectData = crypt32.CryptUnprotectData - _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p, - c_void_p, c_void_p, c_uint, DataBlob_p] - _CryptUnprotectData.restype = c_uint - def CryptUnprotectData(indata, entropy): - indatab = create_string_buffer(indata) - indata = DataBlob(len(indata), cast(indatab, c_void_p)) - entropyb = create_string_buffer(entropy) - entropy = DataBlob(len(entropy), cast(entropyb, c_void_p)) - outdata = DataBlob() - if not _CryptUnprotectData(byref(indata), None, byref(entropy), - None, None, 0, byref(outdata)): - raise Exception("Failed to decrypt user key key (sic)") - return string_at(outdata.pbData, outdata.cbData) - return CryptUnprotectData -CryptUnprotectData = CryptUnprotectData() - -DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device' - -def GetMasterKey(): - root = GetSystemDirectory().split('\\')[0] + '\\' - serial = GetVolumeSerialNumber(root) - vendor = cpuid0() - signature = struct.pack('>I', cpuid1())[1:] - try: - regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, DEVICE_KEY_PATH) - device = winreg.QueryValueEx(regkey, 'key')[0] - - # ADE puts an "username" attribute into that key which was unused - # in previous versions of this script. This means that this key - # retrieval script would break / not work if the user had ever - # changed their Windows account user name after installing ADE. - # By reading the "username" registry entry if available we won't - # have that problem anymore. - - try: - user = winreg.QueryValueEx(regkey, 'username')[0].encode('utf-16-le')[::2] - # Yes, this actually only uses the lowest byte of each character. - except: - # This value should always be available, but just in case - # it's not, use the old implementation. - user = GetUserName() - - except WindowsError: - return None - - entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user) - try: - keykey = CryptUnprotectData(device, entropy) - except Exception: - # There was an exception, so this thing was unable to decrypt - # the key. Maybe this is due to the new user name handling, so - # let's retry with the old code. - user = GetUserName() - entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user) - keykey = CryptUnprotectData(device, entropy) - - return keykey \ No newline at end of file diff --git a/calibre-plugin/libadobeFulfill.py b/calibre-plugin/libadobeFulfill.py index 765047b..57e5302 100644 --- a/calibre-plugin/libadobeFulfill.py +++ b/calibre-plugin/libadobeFulfill.py @@ -26,7 +26,7 @@ def buildFulfillRequest(acsm): fingerprint = None device_type = None fingerprint = activationxml.find("./%s/%s" % (adNS("activationToken"), adNS("fingerprint"))).text - fingerprint = activationxml.find("./%s/%s" % (adNS("activationToken"), adNS("deviceType"))).text + device_type = activationxml.find("./%s/%s" % (adNS("activationToken"), adNS("deviceType"))).text except: pass @@ -286,6 +286,15 @@ def buildRights(license_token_node): def fulfill(acsm_file, do_notify = False): + + verbose_logging = False + try: + import calibre_plugins.deacsm.prefs as prefs + deacsmprefs = prefs.DeACSM_Prefs() + verbose_logging = deacsmprefs["detailed_logging"] + except: + pass + # Get pkcs12: pkcs12 = None @@ -325,7 +334,9 @@ def fulfill(acsm_file, do_notify = False): fulfill_request, adept_ns = buildFulfillRequest(acsmxml) - #print(fulfill_request) + if verbose_logging: + print("Fulfill request:") + print(fulfill_request) fulfill_request_xml = etree.fromstring(fulfill_request) # Sign the request: @@ -388,8 +399,9 @@ def fulfill(acsm_file, do_notify = False): else: return False, "Looks like there's been an error during Fulfillment: %s" % replyData - # Print fulfillmentResult - #print(replyData) + if verbose_logging: + print("fulfillmentResult:") + print(replyData) adobe_fulfill_response = etree.fromstring(replyData) NSMAP = { "adept" : "http://ns.adobe.com/adept" } diff --git a/calibre-plugin/libadobeImportAccount.py b/calibre-plugin/libadobeImportAccount.py index 9d047e5..4cf7a9e 100644 --- a/calibre-plugin/libadobeImportAccount.py +++ b/calibre-plugin/libadobeImportAccount.py @@ -19,6 +19,88 @@ except: from calibre_plugins.deacsm.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): + # Similar to importADEactivationWindows - extracts the activation data from a Wine prefix + try: + from calibre.constants import islinux + if not islinux: + print("This function is for Linux only!") + return False, "Linux only!" + except: + pass + + # Get encryption key + try: + from getEncryptionKeyLinux import GetMasterKey + except: + from calibre_plugins.deacsm.getEncryptionKeyLinux import GetMasterKey + + master_key = GetMasterKey(wine_prefix_path) + + if master_key is None: + err = "Could not access ADE encryption key. If you have just installed ADE in Wine, " + err += "please reboot your machine then try again. Also, make sure neither ADE nor any other " + err += "software is running in WINE while you're trying to import the authorization. " + err += "If it still doesn't work but ADE in that particular WINEPREFIX is working fine, " + err += "please open a bug report." + + return False, err + + # Loop through the registry: + try: + registry_file = open(os.path.join(wine_prefix_path, "user.reg"), "r") + waiting_for_element = False + current_parent = None + current_name = None + current_value = None + current_method = None + while True: + line = registry_file.readline() + if not line: + break + line = line.strip() + + if waiting_for_element: + if (line.lower().startswith("@=")): + current_name = line.split('=', 1)[1].strip()[1:-1] + continue + + if (line.lower().startswith('"value"=')): + current_value = line.split('=', 1)[1].strip()[1:-1] + continue + + if (line.lower().startswith('"method"=')): + current_method = line.split('=', 1)[1].strip()[1:-1] + continue + + if (len(line) == 0): + # Empty line - finalize this element + if current_value is None: + current_parent = current_name + current_name = None + current_method = None + current_value = None + waiting_for_element = False + continue + handle_subkey(current_parent, current_name, current_value, master_key, current_method, None) + current_name = None + current_value = None + current_method = None + + + else: + if (line.startswith("[Software\\\\Adobe\\\\Adept\\\\Activation\\\\")): + waiting_for_element = True + + + registry_file.close() + return handle_subkey(None, None, None, master_key, None, buildIDtoEmulate) + + except: + # There was an error hunting through the registry. + raise + pass + def importADEactivationWindows(buildIDtoEmulate=VAR_VER_DEFAULT_BUILD_ID): # Tries to import the system activation from Adobe Digital Editions on Windows into the plugin # This can be used to "clone" the ADE activation so you don't need to waste an additional activation. @@ -35,14 +117,14 @@ def importADEactivationWindows(buildIDtoEmulate=VAR_VER_DEFAULT_BUILD_ID): # Get encryption key: try: - from libadobeEncryptionWindows import GetMasterKey + from getEncryptionKeyWindows import GetMasterKey except: - from calibre_plugins.deacsm.libadobeEncryptionWindows import GetMasterKey + from calibre_plugins.deacsm.getEncryptionKeyWindows import GetMasterKey master_key = GetMasterKey() if master_key is None: - return False, "master_key is None ..." + return False, "Could not access ADE encryption key" PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation' @@ -185,7 +267,7 @@ def handle_subkey(parent, subkey, value, encryption_key, method, buildID): # The first 16 bytes of the fingerprint are used as IV for the privateLicenseKey # Older versions of this decryption code, like in the DeDRM plugin, didn't # do that correctly. For DeDRM that doesn't matter as a wrong IV only causes - # the first 16 bytes to be corrupted, and these aren't used for decryption anyways. + # the first 16 bytes to be corrupted, and these aren't used for eBook decryption anyways. # For this plugin I want the exact correct data, so lets use the fingerprint as IV. # See jhowell's post: https://www.mobileread.com/forums/showpost.php?p=4173908 diff --git a/calibre-plugin/prefs.py b/calibre-plugin/prefs.py index a6018cf..7b2dce1 100644 --- a/calibre-plugin/prefs.py +++ b/calibre-plugin/prefs.py @@ -19,6 +19,7 @@ class DeACSM_Prefs(): self.deacsmprefs.defaults['configured'] = False self.deacsmprefs.defaults['notify_fulfillment'] = True + self.deacsmprefs.defaults['detailed_logging'] = False self.deacsmprefs.defaults['list_of_rented_books'] = []