mirror of
https://github.com/Leseratte10/acsm-calibre-plugin.git
synced 2024-12-22 17:29:56 +06:00
Bunch of ADE-related changes
- Support for emulating multiple ADE versions - Support for importing existing Win/Mac ADE activation - Various small fixes and cleanup
This commit is contained in:
parent
a7290b536a
commit
eebb523b6b
@ -7,9 +7,9 @@ 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. Click the "Link to ADE account" button
|
||||
4. Enter your AdobeID and password, then wait a couple seconds for the success message.
|
||||
5. The settings window should now say "Authorized with ADE ID X on device Y".
|
||||
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.
|
||||
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.
|
||||
7. If needed (new AdobeID), import the DER file into the DeDRM plugin.
|
||||
8. Download an ACSM file from Adobe's test library and see if you can import it into Calibre: https://www.adobe.com/de/solutions/ebook/digital-editions/sample-ebook-library.html
|
||||
@ -18,7 +18,6 @@ IMPORTANT:
|
||||
|
||||
- I would suggest creating a new dummy AdobeID to use for Calibre so just in case Adobe detects this and bans you, you don't lose your main AdobeID.
|
||||
- Combined with that I suggest importing the DER file into the DeDRM plugin to make sure that losing your AdobeID doesn't also mean you'll lose access to all your eBooks.
|
||||
- Support for PDFs might be unreliable. You will need to apply pull request #1689 (including my additional bugfix in the comments of that PR) to the DeDRM plugin in order to remove the DRM from PDF files. If you still encounter an issue with a PDF file created by this tool even with these bugfixes, please report a bug (in this repository, not in the DeDRM one) and attach the corrupted PDF.
|
||||
- This software is not approved by Adobe. I am not responsible if Adobe detects that you're using nonstandard software and bans your account. Do not complain to me if Adobe bans your main ADE account - you have been warned.
|
||||
|
||||
## Returning books
|
||||
|
@ -19,9 +19,13 @@
|
||||
# improve PassHash support, include UUID in key export filename,
|
||||
# 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),
|
||||
# fix race condition when importing multiple ACSMs simultaneously,
|
||||
# fix authorization failing with certain non-ASCII characters in username.
|
||||
|
||||
PLUGIN_NAME = "DeACSM"
|
||||
PLUGIN_VERSION_TUPLE = (0, 0, 12)
|
||||
PLUGIN_VERSION_TUPLE = (0, 0, 13)
|
||||
|
||||
from calibre.customize import FileTypePlugin # type: ignore
|
||||
__version__ = PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
|
||||
@ -106,10 +110,10 @@ class DeACSM(FileTypePlugin):
|
||||
|
||||
# Account:
|
||||
try:
|
||||
from calibre_plugins.deacsm.libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile, update_account_path
|
||||
from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path
|
||||
from calibre_plugins.deacsm.libadobeAccount import createDeviceFile, createUser, signIn, activateDevice
|
||||
except:
|
||||
from libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile, update_account_path
|
||||
from libadobe import createDeviceKeyFile, update_account_path
|
||||
from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice
|
||||
|
||||
# Fulfill:
|
||||
@ -120,6 +124,7 @@ class DeACSM(FileTypePlugin):
|
||||
from libadobe import sendHTTPRequest
|
||||
from libadobeFulfill import buildRights, fulfill
|
||||
|
||||
|
||||
import calibre_plugins.deacsm.prefs as prefs # type: ignore
|
||||
deacsmprefs = prefs.DeACSM_Prefs()
|
||||
update_account_path(deacsmprefs["path_to_account_data"])
|
||||
@ -238,6 +243,7 @@ class DeACSM(FileTypePlugin):
|
||||
|
||||
elif filetype == ".pdf":
|
||||
adobe_fulfill_response = etree.fromstring(rights_xml_str)
|
||||
|
||||
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
|
||||
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
|
||||
resource = adobe_fulfill_response.find("./%s/%s" % (adNS("licenseToken"), adNS("resource"))).text
|
||||
@ -274,14 +280,18 @@ class DeACSM(FileTypePlugin):
|
||||
print("{0} v{1}: ADE auth is missing or broken ".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
return path_to_ebook
|
||||
|
||||
print("{0} v{1}: Try to fulfill ...".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
|
||||
try:
|
||||
from calibre_plugins.deacsm.libadobe import sendHTTPRequest
|
||||
from calibre_plugins.deacsm.libadobeFulfill import buildRights, fulfill
|
||||
from calibre_plugins.deacsm.libadobe import are_ade_version_lists_valid
|
||||
from calibre_plugins.deacsm.libadobeFulfill import fulfill
|
||||
except:
|
||||
from libadobe import sendHTTPRequest
|
||||
from libadobeFulfill import buildRights, 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))
|
||||
return path_to_ebook
|
||||
|
||||
print("{0} v{1}: Try to fulfill ...".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
|
||||
import calibre_plugins.deacsm.prefs as prefs # type: ignore
|
||||
deacsmprefs = prefs.DeACSM_Prefs()
|
||||
|
@ -25,6 +25,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
|
||||
|
||||
|
||||
class ConfigWidget(QWidget):
|
||||
@ -67,11 +68,30 @@ class ConfigWidget(QWidget):
|
||||
self.button_link_account.clicked.connect(self.link_account)
|
||||
ua_group_box_layout.addWidget(self.button_link_account)
|
||||
|
||||
if isosx:
|
||||
self.button_import_MacADE = QtGui.QPushButton(self)
|
||||
self.button_import_MacADE.setText(_("Import activation from ADE (MacOS)"))
|
||||
self.button_import_MacADE.clicked.connect(self.import_activation_from_MAC)
|
||||
ua_group_box_layout.addWidget(self.button_import_MacADE)
|
||||
|
||||
if iswindows:
|
||||
self.button_import_WinADE = QtGui.QPushButton(self)
|
||||
self.button_import_WinADE.setText(_("Import activation from ADE (Windows)"))
|
||||
self.button_import_WinADE.clicked.connect(self.import_activation_from_Win)
|
||||
ua_group_box_layout.addWidget(self.button_import_WinADE)
|
||||
|
||||
self.button_import_activation = QtGui.QPushButton(self)
|
||||
self.button_import_activation.setText(_("Import existing activation data (ZIP)"))
|
||||
self.button_import_activation.clicked.connect(self.import_activation)
|
||||
self.button_import_activation.setText(_("Import existing activation backup (ZIP)"))
|
||||
self.button_import_activation.clicked.connect(self.import_activation_from_ZIP)
|
||||
ua_group_box_layout.addWidget(self.button_import_activation)
|
||||
|
||||
else:
|
||||
self.button_switch_ade_version = QtGui.QPushButton(self)
|
||||
self.button_switch_ade_version.setText(_("Change ADE version"))
|
||||
self.button_switch_ade_version.clicked.connect(self.switch_ade_version)
|
||||
ua_group_box_layout.addWidget(self.button_switch_ade_version)
|
||||
|
||||
|
||||
self.button_export_key = QtGui.QPushButton(self)
|
||||
self.button_export_key.setText(_("Export account encryption key"))
|
||||
self.button_export_key.clicked.connect(self.export_key)
|
||||
@ -101,11 +121,11 @@ class ConfigWidget(QWidget):
|
||||
|
||||
|
||||
try:
|
||||
from calibre_plugins.deacsm.libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile, update_account_path
|
||||
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
|
||||
except:
|
||||
try:
|
||||
from libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile, update_account_path
|
||||
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))
|
||||
@ -113,11 +133,39 @@ 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)
|
||||
|
||||
error_dialog(None, "Internal error", "Version list mismatch. Please open a bug report.", show=True, show_copy_button=False)
|
||||
|
||||
|
||||
|
||||
def get_account_info(self):
|
||||
|
||||
try:
|
||||
from calibre_plugins.deacsm.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()
|
||||
|
||||
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")
|
||||
|
||||
@ -130,20 +178,49 @@ class ConfigWidget(QWidget):
|
||||
|
||||
try:
|
||||
adeptNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
|
||||
usernameXML = container.find(adeptNS("credentials")).find(adeptNS("username"))
|
||||
devicenameXML = containerdev.find(adeptNS("deviceName"))
|
||||
ade_type = usernameXML.get('method', "unknown")
|
||||
ade_mail = usernameXML.text
|
||||
ade_device_name = devicenameXML.text
|
||||
|
||||
anon = False
|
||||
try:
|
||||
usernameXML = container.find(adeptNS("credentials")).find(adeptNS("username"))
|
||||
ade_type = usernameXML.get('method', "unknown")
|
||||
ade_mail = usernameXML.text
|
||||
except:
|
||||
anon = True
|
||||
|
||||
|
||||
# Determine the ADE version we're emulating:
|
||||
ver = containerdev.findall("./%s" % (adeptNS("version")))
|
||||
ADE_version = None
|
||||
|
||||
for f in ver:
|
||||
if f.get("name") == "hobbes":
|
||||
hobbes_version = f.get("value")
|
||||
|
||||
if hobbes_version is not None:
|
||||
try:
|
||||
v_idx = VAR_VER_HOBBES_VERSIONS.index(hobbes_version)
|
||||
ADE_version = VAR_VER_SUPP_CONFIG_NAMES[v_idx] + " (" + hobbes_version + ")"
|
||||
|
||||
except:
|
||||
# Version not present, probably the "old" 10.0.4 entry.
|
||||
# As 10.X is in the 3.0 range, assume we're on ADE 3.0
|
||||
ADE_version = "ADE 3.0.1 (" + hobbes_version + ")"
|
||||
|
||||
|
||||
if container.find(adeptNS("activationToken")) == None:
|
||||
return "ADE authorization seems to be corrupted (activationToken missing)", False, None
|
||||
|
||||
if container.find(adeptNS("credentials")).find(adeptNS("pkcs12")) == None:
|
||||
return "ADE authorization seems to be corrupted (pkcs12 missing)", False, None
|
||||
|
||||
return "Authorized with ADE ID ("+ade_type+") " + ade_mail + "\non device " + ade_device_name, True, ade_mail
|
||||
if not anon:
|
||||
return "Authorized with ADE ID ("+ade_type+") " + ade_mail + "\non device " + ade_device_name + ", emulating " + ADE_version + ".", True, ade_mail
|
||||
else:
|
||||
return "Authorized with an anonymous ADE ID\non device " + ade_device_name + ", emulating " + ADE_version + ".", True, None
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return "ADE authorization seems to be corrupted", False, None
|
||||
|
||||
|
||||
@ -188,7 +265,75 @@ class ConfigWidget(QWidget):
|
||||
except:
|
||||
return error_dialog(None, "Export failed", "Export failed.", show=True, show_copy_button=False)
|
||||
|
||||
def import_activation(self):
|
||||
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
|
||||
|
||||
ret, msg = importADEactivationWindows()
|
||||
|
||||
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_export_key.setEnabled(activated)
|
||||
self.button_export_activation.setEnabled(activated)
|
||||
self.button_import_WinADE.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_MAC(self):
|
||||
# This will try to import the activation from Adobe Digital Editions on MacOS ...
|
||||
|
||||
msg = "Trying to import existing activation from Adobe Digital Editions ...\n"
|
||||
msg += "You might get a prompt asking you to unlock your keychain / enter your keychain password.\n"
|
||||
msg += "This is necessary to extract the ADE encryption keys. "
|
||||
|
||||
info_dialog(None, "Importing from ADE", msg, show=True, show_copy_button=False)
|
||||
|
||||
from calibre_plugins.deacsm.libadobeImportAccount import importADEactivationMac
|
||||
|
||||
ret, msg = importADEactivationMac()
|
||||
|
||||
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_export_key.setEnabled(activated)
|
||||
self.button_export_activation.setEnabled(activated)
|
||||
self.button_import_MacADE.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_ZIP(self):
|
||||
|
||||
filters = [("ZIP", ["zip"])]
|
||||
filenames = choose_files(self, "Import ADE activation file (ZIP)", _("Import ADE activation file (ZIP)"),
|
||||
@ -236,21 +381,134 @@ 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
|
||||
|
||||
try:
|
||||
self.button_import_WinADE.setEnabled(activated)
|
||||
except:
|
||||
pass
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
info_dialog(None, "Done", "Successfully imported authorization for " + ade_mail, show=True, show_copy_button=False)
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
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()
|
||||
|
||||
|
||||
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:
|
||||
return error_dialog(None, "Failed", "Error while reading file", show=True, show_copy_button=False)
|
||||
|
||||
try:
|
||||
adeptNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
|
||||
|
||||
# Determine the ADE version we're emulating:
|
||||
ver = containerdev.findall("./%s" % (adeptNS("version")))
|
||||
|
||||
# "Default" entry would be for the old 10.0.4 entry.
|
||||
# As 10.X is in the 3.0 range, assume we're on ADE 3.0.1 with hobbes version 10.0.85385
|
||||
v_idx = VAR_VER_HOBBES_VERSIONS.index("10.0.85385")
|
||||
|
||||
for f in ver:
|
||||
if f.get("name") == "hobbes":
|
||||
hobbes_version = f.get("value")
|
||||
|
||||
|
||||
if hobbes_version is not None:
|
||||
ADE_version = "ADE 3.0.X (RMSDK " + hobbes_version + ")"
|
||||
try:
|
||||
v_idx = VAR_VER_HOBBES_VERSIONS.index(hobbes_version)
|
||||
ADE_version = VAR_VER_SUPP_CONFIG_NAMES[v_idx] + " (RMSDK " + hobbes_version + ")"
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
ADE_version = "ADE 3.0.X"
|
||||
|
||||
|
||||
except:
|
||||
err = traceback.format_exc()
|
||||
return error_dialog(None, "Failed", "Error while determining current ADE version.", show=True, det_msg=err, show_copy_button=False)
|
||||
|
||||
|
||||
# Build a list of allowed strings:
|
||||
allowed_strings = []
|
||||
for allowed_id in VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO:
|
||||
try:
|
||||
idx = VAR_VER_BUILD_IDS.index(allowed_id)
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
allowed_strings.append(VAR_VER_SUPP_CONFIG_NAMES[idx])
|
||||
except:
|
||||
pass
|
||||
|
||||
if len(allowed_strings) == 0:
|
||||
return error_dialog(None, "Failed", "Error determining available versions", show=True, show_copy_button=True)
|
||||
|
||||
|
||||
msg = "You are currently using " + ADE_version + "\n"
|
||||
msg += "You can switch to a different ADE version by using the selection box below.\n"
|
||||
msg += "- ADE 1.7.2 is for debugging only. Do not use this setting, it might get your account banned\n"
|
||||
msg += "- ADE 2.0.1 works with most books, and will always get you the old, removable DRM. Select this if you're unsure\n"
|
||||
msg += "- ADE 3.0.1 works with all books, but may give you unremovable DRM for some retailers\n"
|
||||
msg += "- ADE 4.0.3 and ADE 4.5.11 are available, but aren't really needed for anything\n"
|
||||
msg += "Select ADE 2.0.1 if you are unsure\n\n"
|
||||
msg += "Which ADE version do you want to emulate?"
|
||||
|
||||
item, ok = QInputDialog.getItem(self, "Change ADE version", msg, VAR_VER_SUPP_CONFIG_NAMES, 1, False)
|
||||
|
||||
if (not ok):
|
||||
return
|
||||
|
||||
idx = -1
|
||||
try:
|
||||
idx = VAR_VER_SUPP_CONFIG_NAMES.index(item)
|
||||
ret, msg = changeDeviceVersion(idx)
|
||||
if (ret):
|
||||
# Update info display:
|
||||
info_string, activated, mail = self.get_account_info()
|
||||
self.lblAccInfo.setText(info_string)
|
||||
return info_dialog(None, "Done", "Successfully switched to " + item, show=True, show_copy_button=False)
|
||||
else:
|
||||
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)
|
||||
|
||||
|
||||
def link_account(self):
|
||||
|
||||
try:
|
||||
from calibre_plugins.deacsm.libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile, update_account_path
|
||||
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
|
||||
except:
|
||||
try:
|
||||
from libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile, update_account_path
|
||||
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))
|
||||
@ -268,9 +526,40 @@ class ConfigWidget(QWidget):
|
||||
if (not ok or passwd is None or len(passwd) == 0):
|
||||
return
|
||||
|
||||
# Build a list of allowed strings:
|
||||
allowed_strings = []
|
||||
|
||||
for allowed_id in VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE:
|
||||
idx = VAR_VER_BUILD_IDS.index(allowed_id)
|
||||
allowed_strings.append(VAR_VER_SUPP_CONFIG_NAMES[idx])
|
||||
|
||||
|
||||
if len(allowed_strings) == 0:
|
||||
return error_dialog(None, "ADE activation failed", "Error determining available versions", show=True, show_copy_button=True)
|
||||
|
||||
|
||||
msg = "Which ADE version do you want to emulate?\n"
|
||||
msg += "- ADE 2.0.1 works with most but not all books, but will always give you the old, removable DRM.\n"
|
||||
msg += "- ADE 3.0.1 works with all books, but may give you unremovable DRM for some retailers.\n"
|
||||
msg += "- ADE 4.0.3 and 4.5.11 are only provided for completeness sake, but aren't usually needed.\n"
|
||||
msg += "Select ADE 2.0 if you are unsure."
|
||||
item, ok = QInputDialog.getItem(self, "Authorizing ADE account", msg, allowed_strings,
|
||||
VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE.index(VAR_VER_DEFAULT_BUILD_ID), False)
|
||||
|
||||
if (not ok):
|
||||
return
|
||||
|
||||
idx = 0
|
||||
try:
|
||||
idx = VAR_VER_SUPP_CONFIG_NAMES.index(item)
|
||||
print("User selected ({0}) -> {1}".format(idx, VAR_VER_SUPP_CONFIG_NAMES[idx]))
|
||||
except:
|
||||
resp = traceback.format_exc()
|
||||
return error_dialog(None, "ADE activation failed", "Error determining version", det_msg=str(resp), show=True, show_copy_button=True)
|
||||
|
||||
createDeviceKeyFile()
|
||||
createDeviceFile(VAR_HOBBES_VERSION, False)
|
||||
success, resp = createUser()
|
||||
createDeviceFile(False, idx)
|
||||
success, resp = createUser(idx)
|
||||
if (success is False):
|
||||
return error_dialog(None, "ADE activation failed", "Couldn't create user", det_msg=str(resp), show=True, show_copy_button=True)
|
||||
|
||||
@ -278,7 +567,7 @@ class ConfigWidget(QWidget):
|
||||
if (success is False):
|
||||
return error_dialog(None, "ADE activation failed", "Login unsuccessful", det_msg=str(resp), show=True, show_copy_button=True)
|
||||
|
||||
success, resp = activateDevice()
|
||||
success, resp = activateDevice(idx)
|
||||
if (success is False):
|
||||
return error_dialog(None, "ADE activation failed", "Couldn't activate device", det_msg=str(resp), show=True, show_copy_button=True)
|
||||
|
||||
@ -293,6 +582,14 @@ class ConfigWidget(QWidget):
|
||||
self.button_import_activation.setEnabled(False)
|
||||
self.button_export_key.setEnabled(True)
|
||||
self.button_export_activation.setEnabled(True)
|
||||
try:
|
||||
self.button_import_MacADE.setEnabled(False)
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
self.button_import_WinADE.setEnabled(False)
|
||||
except:
|
||||
pass
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@ -345,7 +642,6 @@ class ConfigWidget(QWidget):
|
||||
|
||||
|
||||
def save_settings(self):
|
||||
#self.deacsmprefs.set('path_to_account_data', self.txtboxUA.text())
|
||||
self.deacsmprefs.set('notify_fulfillment', self.chkNotifyFulfillment.isChecked())
|
||||
self.deacsmprefs.writeprefs()
|
||||
|
||||
|
@ -20,7 +20,7 @@ if sys.version_info[0] < 3:
|
||||
print("This script requires Python 3.")
|
||||
exit(1)
|
||||
|
||||
from libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile, update_account_path
|
||||
from libadobe import createDeviceKeyFile, update_account_path, VAR_VER_SUPP_CONFIG_NAMES
|
||||
from libadobeAccount import createDeviceFile, createUser, signIn, exportAccountEncryptionKeyDER, getAccountUUID
|
||||
|
||||
# These are the only two variables you'll need to change
|
||||
@ -28,12 +28,14 @@ from libadobeAccount import createDeviceFile, createUser, signIn, exportAccountE
|
||||
|
||||
VAR_MAIL = ""
|
||||
VAR_PASS = ""
|
||||
VAR_VER = None # 1 for ADE2.0.1, 2 for ADE3.0.1
|
||||
|
||||
#################################################################
|
||||
|
||||
def main():
|
||||
global VAR_MAIL
|
||||
global VAR_PASS
|
||||
global VAR_VER
|
||||
|
||||
if (VAR_MAIL == ""):
|
||||
VAR_MAIL = input("Please enter your AdobeID: ")
|
||||
@ -41,6 +43,13 @@ def main():
|
||||
if (VAR_PASS == ""):
|
||||
VAR_PASS = getpass.getpass("Please enter the password for your AdobeID: ")
|
||||
|
||||
if (VAR_VER is None):
|
||||
VAR_VER = int(input("Please enter '1' for ADE 2.0 or '2' for ADE 3.0: "))
|
||||
|
||||
if VAR_VER >= len(VAR_VER_SUPP_CONFIG_NAMES):
|
||||
print("Invalid version")
|
||||
exit(1)
|
||||
|
||||
if (VAR_MAIL == "" or VAR_PASS == ""):
|
||||
print("Empty credential, aborting")
|
||||
exit(1)
|
||||
@ -53,8 +62,13 @@ def main():
|
||||
print ("Preparing keys ...")
|
||||
|
||||
createDeviceKeyFile()
|
||||
createDeviceFile(VAR_HOBBES_VERSION, True)
|
||||
success, resp = createUser()
|
||||
success = createDeviceFile(True, VAR_VER)
|
||||
if (success is False):
|
||||
print("Error, couldn't create device file.")
|
||||
exit(1)
|
||||
|
||||
|
||||
success, resp = createUser(VAR_VER)
|
||||
if (success is False):
|
||||
print("Error, couldn't create user: %s" % resp)
|
||||
exit(1)
|
||||
|
@ -31,14 +31,62 @@ from oscrypto.asymmetric import dump_certificate, dump_private_key, dump_public_
|
||||
|
||||
|
||||
|
||||
VAR_AUTH_SERVER = "adeactivate.adobe.com"
|
||||
VAR_ACS_SERVER = "http://adeactivate.adobe.com/adept"
|
||||
VAR_HOBBES_VERSION = "10.0.4"
|
||||
VAR_ACS_SERVER_HTTP = "http://adeactivate.adobe.com/adept"
|
||||
VAR_ACS_SERVER_HTTPS = "https://adeactivate.adobe.com/adept"
|
||||
|
||||
FILE_DEVICEKEY = "devicesalt"
|
||||
FILE_DEVICEXML = "device.xml"
|
||||
FILE_ACTIVATIONXML = "activation.xml"
|
||||
|
||||
|
||||
# Lists of different ADE "versions" we know about
|
||||
VAR_VER_SUPP_CONFIG_NAMES = [ "ADE 1.7.2", "ADE 2.0.1", "ADE 3.0.1", "ADE 4.0.3", "ADE 4.5.10", "ADE 4.5.11" ]
|
||||
VAR_VER_SUPP_VERSIONS = [ "ADE WIN 9,0,1131,27", "2.0.1.78765", "3.0.1.91394", "4.0.3.123281",
|
||||
"com.adobe.adobedigitaleditions.exe v4.5.10.186048",
|
||||
"com.adobe.adobedigitaleditions.exe v4.5.11.187303" ]
|
||||
VAR_VER_HOBBES_VERSIONS = [ "9.0.1131.27", "9.3.58046", "10.0.85385", "12.0.123217", "12.5.4.186049", "12.5.4.187298" ]
|
||||
VAR_VER_OS_IDENTIFIERS = [ "Windows Vista", "Windows Vista", "Windows 8", "Windows 8", "Windows 8", "Windows 8" ]
|
||||
|
||||
# This is a list of ALL versions we know (and can potentially use if present in a config file).
|
||||
# Must have the same length / size as the four lists above.
|
||||
VAR_VER_BUILD_IDS = [ 1131, 78765, 91394, 123281, 186048, 187303 ]
|
||||
|
||||
# This is a list of versions that can be used for new authorizations:
|
||||
VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE = [ 78765, 91394, 123281, 187303 ]
|
||||
|
||||
# This is a list of versions to be displayed in the version changer.
|
||||
VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO = [ 1131, 78765, 91394, 123281, 187303 ]
|
||||
|
||||
# Versions >= this one are using HTTPS
|
||||
VAR_VER_NEED_HTTPS_BUILD_ID_LIMIT = 123281
|
||||
|
||||
# Default build ID to use - ADE 2.0.1
|
||||
VAR_VER_DEFAULT_BUILD_ID = 78765
|
||||
|
||||
|
||||
|
||||
def are_ade_version_lists_valid():
|
||||
# These five lists MUST all have the same amount of elements.
|
||||
# Otherwise that will cause all kinds of issues.
|
||||
|
||||
fail = False
|
||||
if len(VAR_VER_SUPP_CONFIG_NAMES) != len(VAR_VER_SUPP_VERSIONS):
|
||||
fail = True
|
||||
if len(VAR_VER_SUPP_CONFIG_NAMES) != len(VAR_VER_HOBBES_VERSIONS):
|
||||
fail = True
|
||||
if len(VAR_VER_SUPP_CONFIG_NAMES) != len(VAR_VER_OS_IDENTIFIERS):
|
||||
fail = True
|
||||
if len(VAR_VER_SUPP_CONFIG_NAMES) != len(VAR_VER_BUILD_IDS):
|
||||
fail = True
|
||||
|
||||
if fail:
|
||||
print("Internal error in DeACSM: Mismatched version list lenghts.")
|
||||
print("This should never happen, please open a bug report.")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
devkey_bytes = None
|
||||
|
||||
|
||||
@ -93,11 +141,11 @@ def makeSerial(random: bool):
|
||||
# Linux
|
||||
uid = os.getuid()
|
||||
import pwd
|
||||
username = pwd.getpwuid(uid).pw_name
|
||||
username = pwd.getpwuid(uid).pw_name.encode("utf-8").decode("latin-1")
|
||||
except:
|
||||
# Windows
|
||||
uid = 1000
|
||||
username = os.getlogin()
|
||||
username = os.getlogin().encode("utf-8").decode("latin-1")
|
||||
|
||||
mac_address = get_mac_address()
|
||||
|
||||
|
@ -17,19 +17,34 @@ 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
|
||||
|
||||
|
||||
VAR_AUTH_SERVER = "adeactivate.adobe.com"
|
||||
VAR_ACS_SERVER = "http://adeactivate.adobe.com/adept"
|
||||
VAR_HOBBES_VERSION = "10.0.4"
|
||||
|
||||
|
||||
def createDeviceFile(hobbes: str, randomSerial: 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):
|
||||
return False
|
||||
|
||||
try:
|
||||
build_id = VAR_VER_BUILD_IDS.index(useVersionIndex)
|
||||
except:
|
||||
return False
|
||||
|
||||
if build_id not in VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE:
|
||||
# ADE 1.7.2 or another version that authorization is disabled for
|
||||
return False
|
||||
|
||||
serial = makeSerial(randomSerial)
|
||||
fingerprint = makeFingerprint(serial)
|
||||
|
||||
@ -37,27 +52,37 @@ def createDeviceFile(hobbes: str, randomSerial: bool):
|
||||
etree.register_namespace("adept", NSMAP["adept"])
|
||||
|
||||
root = etree.Element(etree.QName(NSMAP["adept"], "deviceInfo"))
|
||||
etree.SubElement(root, etree.QName(NSMAP["adept"], "deviceType")).text = "standalone"
|
||||
|
||||
# These three elements are not supposed to be sent to Adobe:
|
||||
etree.SubElement(root, etree.QName(NSMAP["adept"], "deviceClass")).text = "Desktop"
|
||||
etree.SubElement(root, etree.QName(NSMAP["adept"], "deviceSerial")).text = serial
|
||||
etree.SubElement(root, etree.QName(NSMAP["adept"], "deviceName")).text = platform.uname()[1]
|
||||
etree.SubElement(root, etree.QName(NSMAP["adept"], "deviceType")).text = "standalone"
|
||||
# ##
|
||||
|
||||
atr_ver = etree.SubElement(root, etree.QName(NSMAP["adept"], "version"))
|
||||
atr_ver.set("name", "hobbes")
|
||||
atr_ver.set("value", hobbes)
|
||||
atr_ver.set("value", VAR_VER_HOBBES_VERSIONS[useVersionIndex])
|
||||
|
||||
atr_ver2 = etree.SubElement(root, etree.QName(NSMAP["adept"], "version"))
|
||||
atr_ver2.set("name", "clientOS")
|
||||
atr_ver2.set("value", platform.system() + " " + platform.release())
|
||||
# "Windows Vista"
|
||||
|
||||
# This used to contain code to actually read the user's operating system.
|
||||
# That's probably not a good idea because then Adobe sees a bunch of requests from "Linux"
|
||||
#atr_ver2.set("value", platform.system() + " " + platform.release())
|
||||
atr_ver2.set("value", VAR_VER_OS_IDENTIFIERS[useVersionIndex])
|
||||
|
||||
atr_ver3 = etree.SubElement(root, etree.QName(NSMAP["adept"], "version"))
|
||||
atr_ver3.set("name", "clientLocale")
|
||||
|
||||
language = locale.getdefaultlocale()[0]
|
||||
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_US"
|
||||
language = "en"
|
||||
|
||||
atr_ver3.set("value", language)
|
||||
|
||||
@ -68,8 +93,13 @@ def createDeviceFile(hobbes: str, randomSerial: bool):
|
||||
f.write(etree.tostring(root, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1"))
|
||||
f.close()
|
||||
|
||||
return True
|
||||
|
||||
def createUser():
|
||||
|
||||
def createUser(useVersionIndex: int = 0):
|
||||
|
||||
if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES):
|
||||
return False, "Invalid Version index"
|
||||
|
||||
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
|
||||
|
||||
@ -80,8 +110,17 @@ def createUser():
|
||||
|
||||
activationServiceInfo = etree.SubElement(root, etree.QName(NSMAP["adept"], "activationServiceInfo"))
|
||||
|
||||
useHTTPS = False
|
||||
if VAR_VER_BUILD_IDS[useVersionIndex] >= VAR_VER_NEED_HTTPS_BUILD_ID_LIMIT:
|
||||
useHTTPS = True
|
||||
|
||||
|
||||
if useHTTPS:
|
||||
# ADE 4.X uses HTTPS
|
||||
activationURL = VAR_ACS_SERVER_HTTPS + "/ActivationServiceInfo"
|
||||
else:
|
||||
activationURL = VAR_ACS_SERVER_HTTP + "/ActivationServiceInfo"
|
||||
|
||||
activationURL = VAR_ACS_SERVER + "/ActivationServiceInfo"
|
||||
response = sendHTTPRequest(activationURL)
|
||||
|
||||
#print("======================================================")
|
||||
@ -103,7 +142,11 @@ def createUser():
|
||||
|
||||
etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "authURL")).text = authURL
|
||||
etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "userInfoURL")).text = userInfoURL
|
||||
etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "activationURL")).text = VAR_ACS_SERVER
|
||||
if useHTTPS:
|
||||
# ADE 4.X uses HTTPS
|
||||
etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "activationURL")).text = VAR_ACS_SERVER_HTTPS
|
||||
else:
|
||||
etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "activationURL")).text = VAR_ACS_SERVER_HTTP
|
||||
etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "certificate")).text = certificate
|
||||
|
||||
|
||||
@ -265,7 +308,19 @@ def signIn(username: str, passwd: str):
|
||||
|
||||
|
||||
|
||||
def buildActivateReq():
|
||||
def buildActivateReq(useVersionIndex: int = 0):
|
||||
|
||||
if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES):
|
||||
return False
|
||||
|
||||
try:
|
||||
build_id = VAR_VER_BUILD_IDS.index(useVersionIndex)
|
||||
except:
|
||||
return False
|
||||
|
||||
if build_id not in VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE:
|
||||
# ADE 1.7.2 or another version that authorization is disabled for
|
||||
return False
|
||||
|
||||
devicexml = etree.parse(get_device_path())
|
||||
activationxml = etree.parse(get_activation_xml_path())
|
||||
@ -288,7 +343,7 @@ def buildActivateReq():
|
||||
clientLocale = f.get("value")
|
||||
|
||||
if (version is None or clientOS is None or clientLocale is None):
|
||||
return False, "err"
|
||||
return False, "Required version information missing"
|
||||
|
||||
ret = ""
|
||||
|
||||
@ -298,15 +353,18 @@ def buildActivateReq():
|
||||
ret += "<adept:deviceType>%s</adept:deviceType>" % (devicexml.find("./%s" % (adNS("deviceType"))).text)
|
||||
ret += "<adept:clientOS>%s</adept:clientOS>" % (clientOS)
|
||||
ret += "<adept:clientLocale>%s</adept:clientLocale>" % (clientLocale)
|
||||
ret += "<adept:clientVersion>%s</adept:clientVersion>" % (devicexml.find("./%s" % (adNS("deviceClass"))).text)
|
||||
ret += "<adept:clientVersion>%s</adept:clientVersion>" % (VAR_VER_SUPP_VERSIONS[useVersionIndex])
|
||||
ret += "<adept:targetDevice>"
|
||||
|
||||
|
||||
ret += "<adept:softwareVersion>%s</adept:softwareVersion>" % (version)
|
||||
ret += "<adept:clientOS>%s</adept:clientOS>" % (clientOS)
|
||||
ret += "<adept:clientLocale>%s</adept:clientLocale>" % (clientLocale)
|
||||
ret += "<adept:clientVersion>%s</adept:clientVersion>" % (devicexml.find("./%s" % (adNS("deviceClass"))).text)
|
||||
ret += "<adept:clientVersion>%s</adept:clientVersion>" % (VAR_VER_SUPP_VERSIONS[useVersionIndex])
|
||||
ret += "<adept:deviceType>%s</adept:deviceType>" % (devicexml.find("./%s" % (adNS("deviceType"))).text)
|
||||
ret += "<adept:productName>%s</adept:productName>" % ("ADOBE Digitial Editions")
|
||||
# YES, this typo ("Digitial" instead of "Digital") IS present in ADE!!
|
||||
|
||||
ret += "<adept:fingerprint>%s</adept:fingerprint>" % (devicexml.find("./%s" % (adNS("fingerprint"))).text)
|
||||
|
||||
ret += "</adept:targetDevice>"
|
||||
@ -320,9 +378,67 @@ def buildActivateReq():
|
||||
return True, ret
|
||||
|
||||
|
||||
def activateDevice():
|
||||
# Call this function to change from ADE2 to ADE3 and vice versa.
|
||||
def changeDeviceVersion(useVersionIndex: int = 0):
|
||||
if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES):
|
||||
return False, "Invalid Version index"
|
||||
|
||||
result, activate_req = buildActivateReq()
|
||||
try:
|
||||
build_id = VAR_VER_BUILD_IDS.index(useVersionIndex)
|
||||
except:
|
||||
return False, "Unknown build ID"
|
||||
|
||||
if build_id not in VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO:
|
||||
# A version that we no longer want to allow switching to
|
||||
return False, "BuildID not supported"
|
||||
|
||||
try:
|
||||
devicexml = etree.parse(get_device_path())
|
||||
new_hobbes = VAR_VER_HOBBES_VERSIONS[useVersionIndex]
|
||||
new_os = VAR_VER_OS_IDENTIFIERS[useVersionIndex]
|
||||
except:
|
||||
return False, "Error preparing version change"
|
||||
|
||||
|
||||
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
|
||||
ver = devicexml.findall("./%s" % (adNS("version")))
|
||||
|
||||
for f in ver:
|
||||
if f.get("name") == "hobbes":
|
||||
#print("Changing hobbes from {0} to {1}".format(f.attrib["value"], new_hobbes))
|
||||
f.attrib["value"] = new_hobbes
|
||||
if f.get("name") == "clientOS":
|
||||
#print("Changing OS from {0} to {1}".format(f.attrib["value"], new_os))
|
||||
f.attrib["value"] = new_os
|
||||
|
||||
try:
|
||||
f = open(get_device_path(), "w")
|
||||
f.write("<?xml version=\"1.0\"?>\n")
|
||||
f.write(etree.tostring(devicexml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1"))
|
||||
f.close()
|
||||
except:
|
||||
return False, "Failed to update device file."
|
||||
|
||||
return True, ""
|
||||
|
||||
|
||||
|
||||
def activateDevice(useVersionIndex: int = 0):
|
||||
|
||||
if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES):
|
||||
return False, "Invalid Version index"
|
||||
|
||||
try:
|
||||
build_id = VAR_VER_BUILD_IDS.index(useVersionIndex)
|
||||
except:
|
||||
return False, "error checking build ID"
|
||||
|
||||
if build_id not in VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE:
|
||||
# ADE 1.7.2 or another version that authorization is disabled for
|
||||
return False, "Authorization not supported for this build ID"
|
||||
|
||||
|
||||
result, activate_req = buildActivateReq(useVersionIndex)
|
||||
if (result is False):
|
||||
return False, "Building activation request failed: " + activate_req
|
||||
|
||||
@ -344,7 +460,15 @@ def activateDevice():
|
||||
|
||||
data = "<?xml version=\"1.0\"?>\n" + etree.tostring(req_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1")
|
||||
|
||||
ret = sendRequestDocu(data, VAR_ACS_SERVER + "/Activate")
|
||||
useHTTPS = False
|
||||
if VAR_VER_BUILD_IDS[useVersionIndex] >= VAR_VER_NEED_HTTPS_BUILD_ID_LIMIT:
|
||||
useHTTPS = True
|
||||
|
||||
if useHTTPS:
|
||||
# ADE 4.X uses HTTPS
|
||||
ret = sendRequestDocu(data, VAR_ACS_SERVER_HTTPS + "/Activate")
|
||||
else:
|
||||
ret = sendRequestDocu(data, VAR_ACS_SERVER_HTTP + "/Activate")
|
||||
|
||||
try:
|
||||
credentialsXML = etree.fromstring(ret)
|
||||
@ -409,3 +533,14 @@ def exportAccountEncryptionKeyDER(output_file: str):
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def exportAccountEncryptionKeyBytes():
|
||||
try:
|
||||
activationxml = etree.parse(get_activation_xml_path())
|
||||
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
|
||||
privatekey = activationxml.find("./%s/%s" % (adNS("credentials"), adNS("privateLicenseKey"))).text
|
||||
privatekey = base64.b64decode(privatekey)
|
||||
privatekey = privatekey[26:]
|
||||
return privatekey
|
||||
except:
|
||||
return None
|
225
calibre-plugin/libadobeEncryptionWindows.py
Normal file
225
calibre-plugin/libadobeEncryptionWindows.py
Normal file
@ -0,0 +1,225 @@
|
||||
#!/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
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
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
|
@ -4,9 +4,11 @@ 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
|
||||
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
|
||||
|
||||
|
||||
def buildFulfillRequest(acsm):
|
||||
@ -20,9 +22,19 @@ def buildFulfillRequest(acsm):
|
||||
|
||||
user_uuid = activationxml.find("./%s/%s" % (adNS("credentials"), adNS("user"))).text
|
||||
device_uuid = activationxml.find("./%s/%s" % (adNS("activationToken"), adNS("device"))).text
|
||||
device_type = devicexml.find("./%s" % (adNS("deviceType"))).text
|
||||
device_class = devicexml.find("./%s" % (adNS("deviceClass"))).text
|
||||
fingerprint = devicexml.find("./%s" % (adNS("fingerprint"))).text
|
||||
try:
|
||||
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
|
||||
except:
|
||||
pass
|
||||
|
||||
if (fingerprint is None or fingerprint == "" or device_type is None or device_type == ""):
|
||||
# This should usually never happen with a proper activation, but just in case it does,
|
||||
# I'll leave this code in - it loads the fingerprint from the device data instead.
|
||||
fingerprint = devicexml.find("./%s" % (adNS("fingerprint"))).text
|
||||
device_type = devicexml.find("./%s" % (adNS("deviceType"))).text
|
||||
|
||||
|
||||
|
||||
@ -41,31 +53,56 @@ def buildFulfillRequest(acsm):
|
||||
elif f.get("name") == "clientLocale":
|
||||
clientLocale = f.get("value")
|
||||
|
||||
# Find matching client version depending on the Hobbes version.
|
||||
# This way we don't need to store and re-load it for each fulfillment.
|
||||
|
||||
request = ""
|
||||
request += "<?xml version=\"1.0\"?>\n"
|
||||
request += "<adept:fulfill xmlns:adept=\"http://ns.adobe.com/adept\">\n"
|
||||
request += "<adept:user>%s</adept:user>\n" % (user_uuid)
|
||||
request += "<adept:device>%s</adept:device>\n" % (device_uuid)
|
||||
request += "<adept:deviceType>%s</adept:deviceType>\n" % (device_type)
|
||||
request += etree.tostring(acsm, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
|
||||
request += "<adept:targetDevice>\n"
|
||||
try:
|
||||
v_idx = VAR_VER_HOBBES_VERSIONS.index(version)
|
||||
clientVersion = VAR_VER_SUPP_VERSIONS[v_idx]
|
||||
|
||||
request += "<adept:softwareVersion>%s</adept:softwareVersion>\n" % (version)
|
||||
request += "<adept:clientOS>%s</adept:clientOS>\n" % (clientOS)
|
||||
request += "<adept:clientLocale>%s</adept:clientLocale>\n" % (clientLocale)
|
||||
request += "<adept:clientVersion>%s</adept:clientVersion>\n" % (device_class)
|
||||
request += "<adept:deviceType>%s</adept:deviceType>\n" % (device_type)
|
||||
request += "<adept:fingerprint>%s</adept:fingerprint>\n" % (fingerprint)
|
||||
except:
|
||||
# Version not present, probably the "old" 10.0.4 entry.
|
||||
# As 10.X is in the 3.0 range, assume we're on ADE 3.0
|
||||
clientVersion = "3.0.1.91394"
|
||||
|
||||
if clientVersion == "ADE WIN 9,0,1131,27":
|
||||
# Ancient ADE 1.7.2 does this request differently
|
||||
request = "<fulfill xmlns=\"http://ns.adobe.com/adept\">\n"
|
||||
request += "<user>%s</user>\n" % (user_uuid)
|
||||
request += "<device>%s</device>\n" % (device_uuid)
|
||||
request += "<deviceType>%s</deviceType>\n" % (device_type)
|
||||
request += etree.tostring(acsm, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
|
||||
request += "</fulfill>"
|
||||
return request, False
|
||||
|
||||
else:
|
||||
request = ""
|
||||
request += "<?xml version=\"1.0\"?>"
|
||||
request += "<adept:fulfill xmlns:adept=\"http://ns.adobe.com/adept\">"
|
||||
request += "<adept:user>%s</adept:user>" % (user_uuid)
|
||||
request += "<adept:device>%s</adept:device>" % (device_uuid)
|
||||
request += "<adept:deviceType>%s</adept:deviceType>" % (device_type)
|
||||
request += etree.tostring(acsm, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
|
||||
request += "<adept:targetDevice>"
|
||||
|
||||
request += "<adept:softwareVersion>%s</adept:softwareVersion>" % (version)
|
||||
request += "<adept:clientOS>%s</adept:clientOS>" % (clientOS)
|
||||
request += "<adept:clientLocale>%s</adept:clientLocale>" % (clientLocale)
|
||||
request += "<adept:clientVersion>%s</adept:clientVersion>" % (clientVersion)
|
||||
request += "<adept:deviceType>%s</adept:deviceType>" % (device_type)
|
||||
request += "<adept:productName>%s</adept:productName>" % ("ADOBE Digitial Editions")
|
||||
# YES, this typo ("Digitial" instead of "Digital") IS present in ADE!!
|
||||
request += "<adept:fingerprint>%s</adept:fingerprint>" % (fingerprint)
|
||||
|
||||
request += "<adept:activationToken>"
|
||||
request += "<adept:user>%s</adept:user>" % (user_uuid)
|
||||
request += "<adept:device>%s</adept:device>" % (device_uuid)
|
||||
request += "</adept:activationToken>"
|
||||
request += "</adept:targetDevice>"
|
||||
request += "</adept:fulfill>"
|
||||
return request, True
|
||||
|
||||
request += "<adept:activationToken>\n"
|
||||
request += "<adept:user>%s</adept:user>\n" % (user_uuid)
|
||||
request += "<adept:device>%s</adept:device>\n" % (device_uuid)
|
||||
request += "</adept:activationToken>\n"
|
||||
request += "</adept:targetDevice>\n"
|
||||
request += "</adept:fulfill>\n"
|
||||
|
||||
return request
|
||||
|
||||
|
||||
|
||||
@ -286,7 +323,7 @@ def fulfill(acsm_file, do_notify = False):
|
||||
print("Continuing anyways ...")
|
||||
|
||||
|
||||
fulfill_request = buildFulfillRequest(acsmxml)
|
||||
fulfill_request, adept_ns = buildFulfillRequest(acsmxml)
|
||||
|
||||
#print(fulfill_request)
|
||||
|
||||
@ -298,7 +335,13 @@ def fulfill(acsm_file, do_notify = False):
|
||||
|
||||
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
|
||||
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
|
||||
etree.SubElement(fulfill_request_xml, etree.QName(NSMAP["adept"], "signature")).text = signature
|
||||
|
||||
if adept_ns:
|
||||
# "new" ADE
|
||||
etree.SubElement(fulfill_request_xml, etree.QName(NSMAP["adept"], "signature")).text = signature
|
||||
else:
|
||||
# ADE 1.7.2
|
||||
etree.SubElement(fulfill_request_xml, etree.QName("signature")).text = signature
|
||||
|
||||
# Get operator URL:
|
||||
operatorURL = None
|
||||
@ -316,8 +359,12 @@ def fulfill(acsm_file, do_notify = False):
|
||||
if (ret is not None):
|
||||
return False, "operatorAuth error: %s" % ret
|
||||
|
||||
|
||||
fulfill_req_signed = "<?xml version=\"1.0\"?>\n" + etree.tostring(fulfill_request_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
|
||||
if adept_ns:
|
||||
# "new" ADE
|
||||
fulfill_req_signed = "<?xml version=\"1.0\"?>\n" + etree.tostring(fulfill_request_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
|
||||
else:
|
||||
# ADE 1.7.2
|
||||
fulfill_req_signed = etree.tostring(fulfill_request_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
|
||||
|
||||
#print("will send:\n %s" % fulfill_req_signed)
|
||||
#print("Sending fulfill request to %s" % fulfillURL)
|
||||
@ -350,11 +397,14 @@ def fulfill(acsm_file, do_notify = False):
|
||||
|
||||
licenseURL = adobe_fulfill_response.find("./%s/%s/%s/%s" % (adNS("fulfillmentResult"), adNS("resourceItemInfo"), adNS("licenseToken"), adNS("licenseURL"))).text
|
||||
|
||||
if do_notify:
|
||||
print("Notifying server ...")
|
||||
success, response = performFulfillmentNotification(adobe_fulfill_response)
|
||||
if adept_ns:
|
||||
if do_notify:
|
||||
print("Notifying server ...")
|
||||
success, response = performFulfillmentNotification(adobe_fulfill_response)
|
||||
else:
|
||||
print("Not notifying any server since that was disabled.")
|
||||
else:
|
||||
print("Not notifying any server since that was disabled.")
|
||||
print("Skipping notify, not supported properly with ADE 1.7.2")
|
||||
|
||||
|
||||
is_returnable = False
|
||||
@ -365,9 +415,10 @@ def fulfill(acsm_file, do_notify = False):
|
||||
except:
|
||||
pass
|
||||
|
||||
if (is_returnable and do_notify):
|
||||
if (is_returnable and do_notify and adept_ns):
|
||||
# Only support loan returning if we also notified ACS.
|
||||
# Otherwise the server gets confused and we don't want that.
|
||||
# Also, only do that for new-ish ADE and not for ADE 1.7.2
|
||||
updateLoanReturnData(adobe_fulfill_response)
|
||||
|
||||
success, response = fetchLicenseServiceCertificate(licenseURL, operatorURL)
|
||||
|
356
calibre-plugin/libadobeImportAccount.py
Normal file
356
calibre-plugin/libadobeImportAccount.py
Normal file
@ -0,0 +1,356 @@
|
||||
from lxml import etree
|
||||
import base64
|
||||
import os, locale, platform
|
||||
|
||||
from Crypto.Cipher import AES as _AES
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
try:
|
||||
from libadobeEncryptionWindows import GetMasterKey
|
||||
except:
|
||||
from calibre_plugins.deacsm.libadobeEncryptionWindows import GetMasterKey
|
||||
|
||||
master_key = GetMasterKey()
|
||||
|
||||
if master_key is None:
|
||||
return False, "master_key is None ..."
|
||||
|
||||
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 = '<?xml version="1.0"?>\n'
|
||||
content += "<activationInfo xmlns=\"http://ns.adobe.com/adept\">\n"
|
||||
content += '<adept:activationServiceInfo xmlns:adept="http://ns.adobe.com/adept">\n'
|
||||
|
||||
content += "<adept:authURL>%s</adept:authURL>" % (data["activationServiceInfo"]["authURL"])
|
||||
content += "<adept:userInfoURL>%s</adept:userInfoURL>" % (data["activationServiceInfo"]["userInfoURL"])
|
||||
content += "<adept:activationURL>%s</adept:activationURL>" % (data["activationServiceInfo"]["activationURL"])
|
||||
content += "<adept:certificate>%s</adept:certificate>" % (data["activationServiceInfo"]["certificate"])
|
||||
content += "<adept:authenticationCertificate>%s</adept:authenticationCertificate>" % (data["activationServiceInfo"]["authenticationCertificate"])
|
||||
content += '</adept:activationServiceInfo>\n'
|
||||
|
||||
content += '<adept:credentials xmlns:adept="http://ns.adobe.com/adept">'
|
||||
content += "<adept:user>%s</adept:user>" % (data["credentials"]["user"])
|
||||
global account_method
|
||||
if "username" in data["credentials"]:
|
||||
if account_method is None:
|
||||
account_method = "AdobeID"
|
||||
|
||||
content += "<adept:username method=\"%s\">%s</adept:username>" % (account_method, data["credentials"]["username"])
|
||||
|
||||
content += "<adept:pkcs12>%s</adept:pkcs12>" % (data["credentials"]["pkcs12"])
|
||||
content += "<adept:licenseCertificate>%s</adept:licenseCertificate>" % (data["credentials"]["licenseCertificate"])
|
||||
content += "<adept:privateLicenseKey>%s</adept:privateLicenseKey>" % (data["credentials"]["privateLicenseKey"])
|
||||
content += "<adept:authenticationCertificate>%s</adept:authenticationCertificate>" % (data["credentials"]["authenticationCertificate"])
|
||||
content += '</adept:credentials>\n'
|
||||
|
||||
content += '<activationToken xmlns="http://ns.adobe.com/adept">'
|
||||
|
||||
for x in ["device", "fingerprint", "deviceType", "activationURL", "user", "signature"]:
|
||||
content += "<%s>%s</%s>" % (x, data["activationToken"][x], x)
|
||||
|
||||
content += '</activationToken>\n'
|
||||
content += '</activationInfo>\n'
|
||||
|
||||
f.write(content)
|
||||
f.close()
|
||||
|
||||
# Re-create device.xml from scratch:
|
||||
|
||||
content = '<?xml version="1.0"?>\n'
|
||||
content += '<adept:deviceInfo xmlns:adept="http://ns.adobe.com/adept">\n'
|
||||
content += "<adept:deviceType>%s</adept:deviceType>\n" % (data["activationToken"]["deviceType"])
|
||||
content += "<adept:deviceClass>%s</adept:deviceClass>\n" % ("Desktop")
|
||||
content += "<adept:deviceSerial>%s</adept:deviceSerial>\n" % (makeSerial(False))
|
||||
content += "<adept:deviceName>%s</adept:deviceName>\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 += "<adept:version name=\"hobbes\" value=\"%s\"/>\n" % (hobbes_ver)
|
||||
content += "<adept:version name=\"clientOS\" value=\"%s\"/>\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 += "<adept:version name=\"clientLocale\" value=\"%s\"/>\n" % (language)
|
||||
content += "<adept:fingerprint>%s</adept:fingerprint>\n" % (data["activationToken"]["fingerprint"])
|
||||
content += "</adept:deviceInfo>"
|
||||
|
||||
# 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 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 = '<?xml version="1.0"?>\n'
|
||||
content += '<adept:deviceInfo xmlns:adept="http://ns.adobe.com/adept">\n'
|
||||
content += "<adept:deviceType>%s</adept:deviceType>\n" % (devtype)
|
||||
content += "<adept:deviceClass>%s</adept:deviceClass>\n" % ("Desktop")
|
||||
content += "<adept:deviceSerial>%s</adept:deviceSerial>\n" % (makeSerial(False))
|
||||
content += "<adept:deviceName>%s</adept:deviceName>\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 += "<adept:version name=\"hobbes\" value=\"%s\"/>\n" % (hobbes_ver)
|
||||
content += "<adept:version name=\"clientOS\" value=\"%s\"/>\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 += "<adept:version name=\"clientLocale\" value=\"%s\"/>\n" % (language)
|
||||
content += "<adept:fingerprint>%s</adept:fingerprint>\n" % (base64.b64encode(deviceFingerprint))
|
||||
content += "</adept:deviceInfo>"
|
||||
|
||||
# Write device.xml
|
||||
f = open(get_device_path(), "w")
|
||||
f.write(content)
|
||||
f.close()
|
||||
|
||||
return True, "Success"
|
@ -11,7 +11,7 @@ if sys.version_info[0] < 3:
|
||||
print("This script requires Python 3.")
|
||||
exit(1)
|
||||
|
||||
from libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile
|
||||
from libadobe import createDeviceKeyFile, VAR_VER_SUPP_CONFIG_NAMES
|
||||
from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice
|
||||
|
||||
# These are the only two variables you'll need to change
|
||||
@ -21,12 +21,15 @@ from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice
|
||||
|
||||
VAR_MAIL = ""
|
||||
VAR_PASS = ""
|
||||
VAR_VER = None # 1 for ADE2.0, 2 for ADE3.0
|
||||
#################################################################
|
||||
|
||||
def main():
|
||||
|
||||
global VAR_MAIL
|
||||
global VAR_PASS
|
||||
global VAR_VER
|
||||
|
||||
|
||||
if (VAR_MAIL == ""):
|
||||
VAR_MAIL = input("Please enter your AdobeID: ")
|
||||
@ -34,14 +37,26 @@ def main():
|
||||
if (VAR_PASS == ""):
|
||||
VAR_PASS = getpass.getpass("Please enter the password for your AdobeID: ")
|
||||
|
||||
if (VAR_VER is None):
|
||||
VAR_VER = int(input("Please enter '1' for ADE 2.0 or '2' for ADE 3.0: "))
|
||||
|
||||
if VAR_VER >= len(VAR_VER_SUPP_CONFIG_NAMES):
|
||||
print("Invalid version")
|
||||
exit(1)
|
||||
|
||||
if (VAR_MAIL == "" or VAR_PASS == ""):
|
||||
print("Empty credential, aborting")
|
||||
exit(1)
|
||||
|
||||
|
||||
createDeviceKeyFile()
|
||||
createDeviceFile(VAR_HOBBES_VERSION, False)
|
||||
success, resp = createUser()
|
||||
|
||||
success = createDeviceFile(True, VAR_VER)
|
||||
if (success is False):
|
||||
print("Error, couldn't create device file.")
|
||||
exit(1)
|
||||
|
||||
success, resp = createUser(VAR_VER)
|
||||
if (success is False):
|
||||
print("Error, couldn't create user: %s" % resp)
|
||||
exit(1)
|
||||
@ -51,7 +66,7 @@ def main():
|
||||
print("Login unsuccessful: " + resp)
|
||||
exit(1)
|
||||
|
||||
success, resp = activateDevice()
|
||||
success, resp = activateDevice(VAR_VER)
|
||||
if (success is False):
|
||||
print("Couldn't activate device: " + resp)
|
||||
exit(1)
|
||||
|
Loading…
Reference in New Issue
Block a user