''' Copyright (c) 2021-2023 Leseratte10 This file is part of the ACSM Input Plugin by Leseratte10 ACSM Input Plugin for Calibre / acsm-calibre-plugin For more information, see: https://github.com/Leseratte10/acsm-calibre-plugin ''' from lxml import etree import base64 import os, locale, platform try: from Cryptodome.Cipher import AES as _AES 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) def decrypt(self, data): return self._aes.decrypt(data) 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): # 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 from 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. try: from calibre.constants import iswindows except: import sys iswindows = sys.platform.startswith('win') if not iswindows: print("This function is for Windows only!") return False, "Windows only!" # Get encryption key: from getEncryptionKeyWindows import GetMasterKey master_key = GetMasterKey() if master_key is None: return False, "Could not access ADE encryption key" PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation' # Dump data from registry: try: import winreg except ImportError: import _winreg as winreg try: activation_root = winreg.OpenKey(winreg.HKEY_CURRENT_USER, PRIVATE_LICENCE_KEY_PATH) except: return False, "Could not locate ADE activation" i = 0 while True: try: activation_parent = winreg.OpenKey(activation_root, "%04d" % (i)) except: break ParentKeyType = winreg.QueryValueEx(activation_parent, None)[0] j = 0 while True: try: activation_child = winreg.OpenKey(activation_parent, "%04d" % (j)) except: break SubKeyType = winreg.QueryValueEx(activation_child, None)[0] try: value = winreg.QueryValueEx(activation_child, 'value')[0] try: method = winreg.QueryValueEx(activation_child, 'method')[0] except: method = None handle_subkey(ParentKeyType, SubKeyType, value, master_key, method, None) except: pass j = j + 1 i = i + 1 return handle_subkey(None, None, None, master_key, None, buildIDtoEmulate) persistent_data = dict() account_method = None def finalize_write_config_Windows(data, key, buildID): # Okay, finally got all the data that's needed - recreate all the files. # Create devicekey file: f = open(get_devkey_path(), "wb") f.write(key) f.close() # Create activation.xml f = open(get_activation_xml_path(), "w") content = '\n' content += "\n" content += '\n' content += "%s" % (data["activationServiceInfo"]["authURL"]) content += "%s" % (data["activationServiceInfo"]["userInfoURL"]) content += "%s" % (data["activationServiceInfo"]["activationURL"]) content += "%s" % (data["activationServiceInfo"]["certificate"]) content += "%s" % (data["activationServiceInfo"]["authenticationCertificate"]) content += '\n' content += '' content += "%s" % (data["credentials"]["user"]) global account_method if "username" in data["credentials"]: if account_method is None: account_method = "AdobeID" content += "%s" % (account_method, data["credentials"]["username"]) content += "%s" % (data["credentials"]["pkcs12"]) content += "%s" % (data["credentials"]["licenseCertificate"]) content += "%s" % (data["credentials"]["privateLicenseKey"]) content += "%s" % (data["credentials"]["authenticationCertificate"]) content += '\n' content += '' for x in ["device", "fingerprint", "deviceType", "activationURL", "user", "signature"]: content += "<%s>%s" % (x, data["activationToken"][x], x) content += '\n' content += '\n' f.write(content) f.close() # Re-create device.xml from scratch: content = '\n' content += '\n' content += "%s\n" % (data["activationToken"]["deviceType"]) content += "%s\n" % ("Desktop") content += "%s\n" % (makeSerial(False)) content += "%s\n" % (platform.uname()[1]) version_idx = VAR_VER_BUILD_IDS.index(buildID) hobbes_ver = VAR_VER_HOBBES_VERSIONS[version_idx] clientOS = VAR_VER_OS_IDENTIFIERS[version_idx] content += "\n" % (hobbes_ver) content += "\n" % (clientOS) language = None try: language = locale.getdefaultlocale()[0].split('_')[0] except: pass if language is None or language == "": # Can sometimes happen on MacOS with default English language language = "en" content += "\n" % (language) content += "%s\n" % (data["activationToken"]["fingerprint"]) content += "" # Write device.xml f = open(get_device_path(), "w") f.write(content) f.close() return True, "Done" def handle_subkey(parent, subkey, value, encryption_key, method, buildID): if parent is None: # We're done collecting sub keys - decrypt the private key, then finalize config. # 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 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 iv = persistent_data["activationToken"]["fingerprint"] iv = base64.b64decode(iv)[:16] aes = AES(encryption_key, iv) value = base64.b64decode(persistent_data["credentials"]["privateLicenseKey"]) persistent_data["credentials"]["privateLicenseKey"] = base64.b64encode(aes.decrypt(value)).decode("latin-1") return finalize_write_config_Windows(persistent_data, encryption_key, buildID) else: # collecting a single sub key by storing it in the global list "persistent_data" global account_method if method: account_method = method # Not encrypted try: persistent_data[parent][subkey] = value except KeyError: persistent_data[parent] = dict() persistent_data[parent][subkey] = value return None def getMacCredential(type): import os, re # Just calling a native binary. # The python modules were all A) pretty complicated and B) are only intended # to read your own credentials, so they require the user to manually add # Python to the list of applications allowed to read ADE credentials. # By calling this "security" binary, it will instead pop up a password input # dialog, having the user verify that they do want to export the ADE keys. cmd = ' '.join([ "/usr/bin/security", "find-generic-password", "-g -s '%s' -a '%s'" % ("Digital Editions", type), "2>&1 >/dev/null" ]) p = os.popen(cmd) s = p.read() p.close() m = re.match(r"password: (?:0x([0-9A-F]+)\s*)?\"(.*)\"$", s) if m: hexform, stringform = m.groups() if hexform: return bytes.fromhex(hexform) else: return bytes(stringform, 'latin-1') return None def importADEactivationMac(buildIDtoEmulate=VAR_VER_DEFAULT_BUILD_ID): # Tries to import the system activation from Adobe Digital Editions on a Mac into the plugin # This can be used to "clone" the ADE activation so you don't need to waste an additional activation. import sys try: from calibre.constants import isosx except: isosx = sys.platform.startswith('darwin') if not isosx: print("This function is for MacOS only!") return False, "MacOS only!" import subprocess import warnings warnings.filterwarnings('ignore', category=FutureWarning) home = os.getenv('HOME') cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"' cmdline = cmdline.encode(sys.getfilesystemencoding()) p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) out1, out2 = p2.communicate() reslst = out1.split(b'\n') cnt = len(reslst) ActDatPath = b"activation.dat" for j in range(cnt): resline = reslst[j] pp = resline.find(b'activation.dat') if pp >= 0: ActDatPath = resline break if not os.path.exists(ActDatPath): print("Activation file does not exist ...") return False, "Activation file not found" # activation.xml is at ActDatPath. # Now get the password(s) ... DeviceKey = getMacCredential("DeviceKey") DeviceFingerprint = getMacCredential("DeviceFingerprint") if (DeviceKey is None or DeviceFingerprint is None): print("There was an error exporting the keys from the MacOS keychain.") return False, "Error while exporting keys" return recreateMacActivationFiles(ActDatPath, DeviceKey, DeviceFingerprint, buildIDtoEmulate) def recreateMacActivationFiles(path_to_activation, deviceKey, deviceFingerprint, buildIDtoEmulate): if (len(deviceKey) != 16): print("Looks like the device key is invalid ...") return False, "Invalid device key" # Create activation.xml file: import shutil shutil.copyfile(path_to_activation, get_activation_xml_path()) # Create devicekey file: f = open(get_devkey_path(), "wb") f.write(deviceKey) f.close() # Read and parse activation.xml activationxml = etree.parse(get_activation_xml_path()) adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag) devtype = activationxml.find("./%s/%s" % (adNS("activationToken"), adNS("deviceType"))).text # Re-create device.xml from scratch: content = '\n' content += '\n' content += "%s\n" % (devtype) content += "%s\n" % ("Desktop") content += "%s\n" % (makeSerial(False)) content += "%s\n" % (platform.uname()[1]) version_idx = VAR_VER_BUILD_IDS.index(buildIDtoEmulate) hobbes_ver = VAR_VER_HOBBES_VERSIONS[version_idx] clientOS = VAR_VER_OS_IDENTIFIERS[version_idx] content += "\n" % (hobbes_ver) content += "\n" % (clientOS) language = None try: language = locale.getdefaultlocale()[0].split('_')[0] except: pass if language is None or language == "": # Can sometimes happen on MacOS with default English language language = "en" content += "\n" % (language) content += "%s\n" % (base64.b64encode(deviceFingerprint)) content += "" # Write device.xml f = open(get_device_path(), "w") f.write(content) f.close() return True, "Success"