Support anonymous auth and other auth providers

This commit is contained in:
Florian Bach 2021-12-12 11:31:51 +01:00
parent f9fbc4172a
commit cc37506762
7 changed files with 237 additions and 60 deletions

View File

@ -5,19 +5,25 @@ It is a full Python reimplementation of libgourou by Grégory Soutadé (http://i
## Setup ## Setup
1. Download the plugin and import it into Calibre Download the plugin and import it into Calibre, then open the plugin settings. The plugin should display "Not authorized for any ADE ID". You now have multiple options to authorize the plugin:
2. Open the plugin settings, it should say "Not authorized for any ADE ID"
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. - You can click on "Link to ADE account" and enter your AdobeID credentials to link your Calibre installation to your AdobeID account. This uses up one of your available activations.
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. - You can click on "Create anonymous authorization" to create an anonymous authorization. Make sure to create backups of that authorization. Also, if you do end up emulating ADE3+ and you receive a file with the new Adobe DRM, you might not be able to access it ...
5. The settings window should now say "Authorized with ADE ID X on device Y, emulating ADE version Z". - If you have ADE installed and activated on your machine, you can click "Import activation from ADE" to clone the existing activation from your ADE installation.
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. - If you have used this plugin before, you can click on "Import existing activation backup" to import a previously created activation backup (ZIP) file to restore an activation. This functionality can also be used to clone one activation to multiple computers.
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 During authorization, the plugin may ask you for the ADE version to emulate. Usually you can leave this setting as it is (ADE 2.0.1).
After you've activated the plugin, make a backup of the activation using the "Export account activation data". Then click "Export account encryption key" and import the resulting file into the DeDRM plugin for DRM removal. If you're using noDRM's fork of the DeDRM plugin, this step will happen automatically.
Once that's done, 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
IMPORTANT: 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. - 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. - 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.
- If you use an anonymous authorization, make sure you make backups of the activation data.
- If you use an anonymous authorization and you end up getting an eBook with the new Adobe DRM (version 3 or higher), there might be no way for you to access that book at all, as right now there's no way to export an existing authorization from the plugin into ADE.
- 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. - 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 ## Returning books
@ -41,10 +47,6 @@ Though, generally it's recommended to use the Calibre plugin instead of these st
## To-Do list for the future? ## To-Do list for the future?
There's a bunch of features that could still be added, but most of them aren't implemented in libgourou either, so I don't know if or when I'll be able to add these:
- Support for anonymous Adobe IDs
- Support for un-authorizing a machine
- Support to copy an authorization from the plugin to an ADE install - Support to copy an authorization from the plugin to an ADE install
- Support for Adobe's "auth" download method instead of the "simple" method. - Support for Adobe's "auth" download method instead of the "simple" method.
- ... - ...

View File

@ -29,6 +29,9 @@
# 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, add auto-delete ACSM setting, # add detailed logging toggle setting, add auto-delete ACSM setting,
# add useful error message for ACSMs with nonstandard download type. # add useful error message for ACSMs with nonstandard download type.
# Currently in development:
# Add support for anonymous authorizations, add support for other ID providers,
# fix ACSM files from Google Play books (no metadata node).
PLUGIN_NAME = "DeACSM" PLUGIN_NAME = "DeACSM"
PLUGIN_VERSION_TUPLE = (0, 0, 14) PLUGIN_VERSION_TUPLE = (0, 0, 14)

View File

@ -22,7 +22,8 @@ from PyQt5 import Qt as QtGui
from zipfile import ZipFile from zipfile import ZipFile
# calibre modules and constants. # calibre modules and constants.
from calibre.gui2 import (question_dialog, error_dialog, info_dialog, choose_save_file, choose_files) # type: ignore from calibre.gui2 import (question_dialog, error_dialog, info_dialog, # type: ignore
warning_dialog, choose_save_file, choose_files)
# modules from this plugin's zipfile. # modules from this plugin's zipfile.
from calibre_plugins.deacsm.__init__ import PLUGIN_NAME, PLUGIN_VERSION # type: ignore from calibre_plugins.deacsm.__init__ import PLUGIN_NAME, PLUGIN_VERSION # type: ignore
import calibre_plugins.deacsm.prefs as prefs # type: ignore import calibre_plugins.deacsm.prefs as prefs # type: ignore
@ -72,6 +73,11 @@ class ConfigWidget(QWidget):
self.button_link_account.clicked.connect(self.link_account) self.button_link_account.clicked.connect(self.link_account)
ua_group_box_layout.addWidget(self.button_link_account) ua_group_box_layout.addWidget(self.button_link_account)
self.button_anon_auth = QtGui.QPushButton(self)
self.button_anon_auth.setText(_("Create anonymous authorization"))
self.button_anon_auth.clicked.connect(self.create_anon_auth)
ua_group_box_layout.addWidget(self.button_anon_auth)
if isosx: if isosx:
self.button_import_MacADE = QtGui.QPushButton(self) self.button_import_MacADE = QtGui.QPushButton(self)
self.button_import_MacADE.setText(_("Import activation from ADE (MacOS)")) self.button_import_MacADE.setText(_("Import activation from ADE (MacOS)"))
@ -169,6 +175,7 @@ class ConfigWidget(QWidget):
# Internal error, this should never happen # Internal error, this should never happen
if not activated: if not activated:
self.button_link_account.setEnabled(False) self.button_link_account.setEnabled(False)
self.button_anon_auth.setEnabled(False)
self.button_import_activation.setEnabled(False) self.button_import_activation.setEnabled(False)
if isosx: if isosx:
self.button_import_MacADE.setEnabled(activated) self.button_import_MacADE.setEnabled(activated)
@ -196,7 +203,7 @@ class ConfigWidget(QWidget):
msg += "This will cause various data to be included in the logfiles, like encryption keys, account keys and other confidential data.\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." 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) warning_dialog(None, "Warning", msg, show=True, show_copy_button=False)
def toggle_acsm_delete(self): def toggle_acsm_delete(self):
if not self.chkDeleteAfterFulfill.isChecked(): if not self.chkDeleteAfterFulfill.isChecked():
@ -207,7 +214,7 @@ class ConfigWidget(QWidget):
msg += "As this feature is experimental, it's possible that ACSMs will also sometimes get deleted even when the import failed.\n\n" msg += "As this feature is experimental, it's possible that ACSMs will also sometimes get deleted even when the import failed.\n\n"
msg += "If you're importing an ACSM that you cannot re-download in case of issues, do not enable this option!" msg += "If you're importing an ACSM that you cannot re-download in case of issues, do not enable this option!"
info_dialog(None, "Warning", msg, show=True, show_copy_button=False) warning_dialog(None, "Warning", msg, show=True, show_copy_button=False)
@ -453,6 +460,7 @@ class ConfigWidget(QWidget):
self.lblAccInfo.setText(info_string) self.lblAccInfo.setText(info_string)
self.button_link_account.setEnabled(not activated) self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated)
self.button_import_activation.setEnabled(not activated) self.button_import_activation.setEnabled(not activated)
self.button_import_LinuxWineADE.setEnabled(not activated) self.button_import_LinuxWineADE.setEnabled(not activated)
self.button_export_key.setEnabled(activated) self.button_export_key.setEnabled(activated)
@ -485,6 +493,7 @@ class ConfigWidget(QWidget):
self.lblAccInfo.setText(info_string) self.lblAccInfo.setText(info_string)
self.button_link_account.setEnabled(not activated) self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated)
self.button_import_activation.setEnabled(not activated) self.button_import_activation.setEnabled(not activated)
self.button_import_WinADE.setEnabled(not activated) self.button_import_WinADE.setEnabled(not activated)
self.button_export_key.setEnabled(activated) self.button_export_key.setEnabled(activated)
@ -522,6 +531,7 @@ class ConfigWidget(QWidget):
self.lblAccInfo.setText(info_string) self.lblAccInfo.setText(info_string)
self.button_link_account.setEnabled(not activated) self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated)
self.button_import_activation.setEnabled(not activated) self.button_import_activation.setEnabled(not activated)
self.button_import_MacADE.setEnabled(not activated) self.button_import_MacADE.setEnabled(not activated)
self.button_export_key.setEnabled(activated) self.button_export_key.setEnabled(activated)
@ -586,6 +596,7 @@ class ConfigWidget(QWidget):
self.lblAccInfo.setText(info_string) self.lblAccInfo.setText(info_string)
self.button_link_account.setEnabled(not activated) self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated)
self.button_import_activation.setEnabled(not activated) self.button_import_activation.setEnabled(not activated)
self.button_export_key.setEnabled(activated) self.button_export_key.setEnabled(activated)
self.button_export_activation.setEnabled(activated) self.button_export_activation.setEnabled(activated)
@ -705,8 +716,7 @@ class ConfigWidget(QWidget):
return error_dialog(None, "Failed", "Error while changing ADE version.", show=True, det_msg=traceback.format_exc(), 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): def create_anon_auth(self):
try: try:
from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path, VAR_VER_SUPP_CONFIG_NAMES 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.libadobe import VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE, VAR_VER_BUILD_IDS, VAR_VER_DEFAULT_BUILD_ID
@ -722,15 +732,13 @@ class ConfigWidget(QWidget):
update_account_path(self.deacsmprefs["path_to_account_data"]) update_account_path(self.deacsmprefs["path_to_account_data"])
mail, ok = QInputDialog.getText(self, "Authorizing ADE account", "Please enter mail address") msg = "You are about to create an anonymous authorization.\n"
msg += "If you lose access to this authorization, all books linked to it will be lost / inaccessible. "
msg += "Make sure to create backups of the authorization data! "
msg += "Also, if you do end up emulating ADE3 or newer, and you receive an eBook with the new DRM, "
msg += "you might not be able to read / access that book at all."
if (not ok or mail is None or len(mail) == 0): warning_dialog(None, "Warning", msg, show=True, show_copy_button=False)
return
passwd, ok = QInputDialog.getText(self, "Authorizing ADE account", "Please enter password", QLineEdit.Password)
if (not ok or passwd is None or len(passwd) == 0):
return
# Build a list of allowed strings: # Build a list of allowed strings:
allowed_strings = [] allowed_strings = []
@ -765,11 +773,11 @@ class ConfigWidget(QWidget):
createDeviceKeyFile() createDeviceKeyFile()
createDeviceFile(False, idx) createDeviceFile(False, idx)
success, resp = createUser(idx) success, resp = createUser(idx, None)
if (success is False): 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) return error_dialog(None, "ADE activation failed", "Couldn't create user", det_msg=str(resp), show=True, show_copy_button=True)
success, resp = signIn(mail, passwd) success, resp = signIn("anonymous", "", "")
if (success is False): if (success is False):
return error_dialog(None, "ADE activation failed", "Login unsuccessful", det_msg=str(resp), show=True, show_copy_button=True) return error_dialog(None, "ADE activation failed", "Login unsuccessful", det_msg=str(resp), show=True, show_copy_button=True)
@ -777,27 +785,140 @@ class ConfigWidget(QWidget):
if (success is False): 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) return error_dialog(None, "ADE activation failed", "Couldn't activate device", det_msg=str(resp), show=True, show_copy_button=True)
print("Authorized to account " + mail) print("Authorized to anonymous account")
# update display # update display
info_string, activated, mail = self.get_account_info() info_string, activated, mail = self.get_account_info()
self.lblAccInfo.setText(info_string) self.lblAccInfo.setText(info_string)
self.button_link_account.setEnabled(False) self.button_link_account.setEnabled(not activated)
self.button_import_activation.setEnabled(False) self.button_anon_auth.setEnabled(not activated)
self.button_export_key.setEnabled(True) self.button_import_activation.setEnabled(not activated)
self.button_export_activation.setEnabled(True) self.button_export_key.setEnabled(activated)
self.button_export_activation.setEnabled(activated)
if isosx: if isosx:
self.button_import_MacADE.setEnabled(False) self.button_import_MacADE.setEnabled(not activated)
if iswindows: if iswindows:
self.button_import_WinADE.setEnabled(False) self.button_import_WinADE.setEnabled(not activated)
if islinux: if islinux:
self.button_import_LinuxWineADE.setEnabled(False) self.button_import_LinuxWineADE.setEnabled(not activated)
self.resize(self.sizeHint()) self.resize(self.sizeHint())
info_dialog(None, "Done", "Authorized to account " + mail, show=True, show_copy_button=False) info_dialog(None, "Done", "Authorized to anonymous account.", show=True, show_copy_button=False)
def link_account(self):
try:
from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path, VAR_VER_SUPP_CONFIG_NAMES
from calibre_plugins.deacsm.libadobe import VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE, VAR_VER_BUILD_IDS, VAR_VER_DEFAULT_BUILD_ID
from calibre_plugins.deacsm.libadobeAccount import createDeviceFile, getAuthMethodsAndCert, createUser, signIn, activateDevice
except:
try:
from libadobe import createDeviceKeyFile, update_account_path, VAR_VER_SUPP_CONFIG_NAMES
from libadobe import VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE, VAR_VER_BUILD_IDS, VAR_VER_DEFAULT_BUILD_ID
from libadobeAccount import createDeviceFile, getAuthMethodsAndCert, createUser, signIn, activateDevice
except:
print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION))
traceback.print_exc()
update_account_path(self.deacsmprefs["path_to_account_data"])
# Get account types
types, authCert = getAuthMethodsAndCert()
msg = "Please select your AdobeID provider. Usually, \"Adobe ID\" is the correct choice."
acc_item, ok = QInputDialog.getItem(self, "Authorizing ADE account", msg, types[1], 0, False)
acc_type = "AdobeID"
try:
acc_idx = types[1].index(acc_item)
acc_type = types[0][acc_idx]
print("User has selected account type " + acc_type)
except ValueError:
return error_dialog(None, "ADE activation failed", "Invalid provider", show=True, show_copy_button=True)
mail, ok = QInputDialog.getText(self, "Authorizing ADE account", "Please enter mail address")
if (not ok or mail is None or len(mail) == 0):
return
passwd, ok = QInputDialog.getText(self, "Authorizing ADE account", "Please enter password", QLineEdit.Password)
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
vers_idx = 0
try:
vers_idx = VAR_VER_SUPP_CONFIG_NAMES.index(item)
print("User selected ({0}) -> {1}".format(vers_idx, VAR_VER_SUPP_CONFIG_NAMES[vers_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(False, vers_idx)
success, resp = createUser(vers_idx, authCert)
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)
success, resp = signIn(acc_type, mail, passwd)
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(vers_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)
print("Authorized to '" + acc_type + "' account " + mail)
# update display
info_string, activated, mail = self.get_account_info()
self.lblAccInfo.setText(info_string)
self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated)
self.button_import_activation.setEnabled(not activated)
self.button_export_key.setEnabled(activated)
self.button_export_activation.setEnabled(activated)
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)
self.resize(self.sizeHint())
info_dialog(None, "Done", "Authorized to '" + acc_item + "' account " + mail, show=True, show_copy_button=False)

View File

@ -68,14 +68,14 @@ def main():
exit(1) exit(1)
success, resp = createUser(VAR_VER) success, resp = createUser(VAR_VER, None)
if (success is False): if (success is False):
print("Error, couldn't create user: %s" % resp) print("Error, couldn't create user: %s" % resp)
exit(1) exit(1)
print("Logging in ...") print("Logging in ...")
success, resp = signIn(VAR_MAIL, VAR_PASS) success, resp = signIn("AdobeID", VAR_MAIL, VAR_PASS)
if (success is False): if (success is False):
print("Login unsuccessful: " + resp) print("Login unsuccessful: " + resp)
exit(1) exit(1)

View File

@ -350,6 +350,11 @@ def decrypt_with_device_key(data):
def addNonce(): def addNonce():
# TODO: Update nonce calculation
# Currently, the plugin always uses the current time, and the counter (tmp) is always 0.
# What Adobe does instead is save the current time on program start, then increase tmp
# every time a Nonce is needed.
dt = datetime.utcnow() dt = datetime.utcnow()
usec = dt.microsecond usec = dt.microsecond
sec = (dt - datetime(1970,1,1)).total_seconds() sec = (dt - datetime(1970,1,1)).total_seconds()

View File

@ -95,11 +95,57 @@ def createDeviceFile(randomSerial: bool, useVersionIndex: int = 0):
return True return True
def getAuthMethodsAndCert():
# Queries the /AuthenticationServiceInfo endpoint to get a list
# of available ID providers.
# Returns a list of providers, and the login certificate.
def createUser(useVersionIndex: int = 0): # The login certificate stuff would usually be handled elsewhere,
# but that would require another request to Adobe's servers
# which is not what we want (as ADE only performs one request, too),
# so we need to store this cert.
# If you DO call this method before calling createUser,
# it is your responsibility to pass the authCert returned by this function
# to the createUser function call.
# Otherwise the plugin will not look 100% like ADE to Adobe.
authenticationURL = VAR_ACS_SERVER_HTTP + "/AuthenticationServiceInfo"
response2 = sendHTTPRequest(authenticationURL)
adobe_response_xml2 = etree.fromstring(response2)
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
try:
authCert = None
authCert = adobe_response_xml2.find("./%s" % (adNS("certificate"))).text
except:
pass
# Get sign-in methods.
sign_in_methods = adobe_response_xml2.findall("./%s/%s" % (adNS("signInMethods"), adNS("signInMethod")))
aid_ids = []
aid_names = []
for method in sign_in_methods:
mid = method.get("method", None)
txt = method.text
if mid != "anonymous":
aid_ids.append(mid)
aid_names.append(txt)
return [aid_ids, aid_names], authCert
def createUser(useVersionIndex: int = 0, authCert = None):
if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES): if useVersionIndex >= len(VAR_VER_SUPP_CONFIG_NAMES):
return False, "Invalid Version index" return False, "Invalid Version index", [[], []]
NSMAP = { "adept" : "http://ns.adobe.com/adept" } NSMAP = { "adept" : "http://ns.adobe.com/adept" }
@ -138,7 +184,7 @@ def createUser(useVersionIndex: int = 0):
certificate = adobe_response_xml.find("./%s" % (adNS("certificate"))).text certificate = adobe_response_xml.find("./%s" % (adNS("certificate"))).text
if (authURL is None or userInfoURL is None or certificate is None): if (authURL is None or userInfoURL is None or certificate is None):
return False, "Error: Unexpected reply from Adobe." return False, "Error: Unexpected reply from Adobe.", [[], []]
etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "authURL")).text = authURL 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"], "userInfoURL")).text = userInfoURL
@ -150,17 +196,15 @@ def createUser(useVersionIndex: int = 0):
etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "certificate")).text = certificate etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "certificate")).text = certificate
if authCert is None:
# This is not supposed to happen, but if it does, then just query it again from Adobe.
authenticationURL = authURL + "/AuthenticationServiceInfo" authenticationURL = authURL + "/AuthenticationServiceInfo"
response2 = sendHTTPRequest(authenticationURL) response2 = sendHTTPRequest(authenticationURL)
#print("======================================================")
#print("Sending request to " + authenticationURL)
#print("got response:")
#print(response2)
#print("======================================================")
adobe_response_xml2 = etree.fromstring(response2) adobe_response_xml2 = etree.fromstring(response2)
authCert = adobe_response_xml2.find("./%s" % (adNS("certificate"))).text authCert = adobe_response_xml2.find("./%s" % (adNS("certificate"))).text
etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "authenticationCertificate")).text = authCert etree.SubElement(activationServiceInfo, etree.QName(NSMAP["adept"], "authenticationCertificate")).text = authCert
@ -168,17 +212,16 @@ def createUser(useVersionIndex: int = 0):
f.write("<?xml version=\"1.0\"?>\n") f.write("<?xml version=\"1.0\"?>\n")
f.write(etree.tostring(root, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1")) f.write(etree.tostring(root, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1"))
f.close() f.close()
return True, "Done" return True, "Done"
def buildSignInRequest(type: str, username: str, password: str, authenticationCertificate: str):
def buildSignInRequest(adobeID: str, adobePassword: str, authenticationCertificate: str):
NSMAP = { "adept" : "http://ns.adobe.com/adept" } NSMAP = { "adept" : "http://ns.adobe.com/adept" }
etree.register_namespace("adept", NSMAP["adept"]) etree.register_namespace("adept", NSMAP["adept"])
root = etree.Element(etree.QName(NSMAP["adept"], "signIn")) root = etree.Element(etree.QName(NSMAP["adept"], "signIn"))
root.set("method", "AdobeID") root.set("method", type)
f = open(get_devkey_path(), "rb") f = open(get_devkey_path(), "rb")
devkey_bytes = f.read() devkey_bytes = f.read()
@ -189,10 +232,10 @@ def buildSignInRequest(adobeID: str, adobePassword: str, authenticationCertifica
# Build buffer <devkey_bytes> <len username> <username> <len password> <password> # Build buffer <devkey_bytes> <len username> <username> <len password> <password>
ar = bytearray(devkey_bytes) ar = bytearray(devkey_bytes)
ar.extend(bytearray(len(adobeID).to_bytes(1, 'big'))) ar.extend(bytearray(len(username).to_bytes(1, 'big')))
ar.extend(bytearray(adobeID.encode("latin-1"))) ar.extend(bytearray(username.encode("latin-1")))
ar.extend(bytearray(len(adobePassword).to_bytes(1, 'big'))) ar.extend(bytearray(len(password).to_bytes(1, 'big')))
ar.extend(bytearray(adobePassword.encode("latin-1"))) ar.extend(bytearray(password.encode("latin-1")))
# Crypt code from https://stackoverflow.com/a/12921889/4991648 # Crypt code from https://stackoverflow.com/a/12921889/4991648
cert = DerSequence() cert = DerSequence()
@ -230,7 +273,7 @@ def buildSignInRequest(adobeID: str, adobePassword: str, authenticationCertifica
def signIn(username: str, passwd: str): def signIn(account_type: str, username: str, passwd: str):
# Get authenticationCertificate # Get authenticationCertificate
@ -238,7 +281,9 @@ def signIn(username: str, passwd: str):
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag) adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
authenticationCertificate = activationxml.find("./%s/%s" % (adNS("activationServiceInfo"), adNS("authenticationCertificate"))).text authenticationCertificate = activationxml.find("./%s/%s" % (adNS("activationServiceInfo"), adNS("authenticationCertificate"))).text
signInRequest = buildSignInRequest(username, passwd, authenticationCertificate)
# Type = "AdobeID" or "anonymous". For "anonymous", username and passwd need to be the empty string.
signInRequest = buildSignInRequest(account_type, username, passwd, authenticationCertificate)
signInURL = activationxml.find("./%s/%s" % (adNS("activationServiceInfo"), adNS("authURL"))).text + "/SignInDirect" signInURL = activationxml.find("./%s/%s" % (adNS("activationServiceInfo"), adNS("authURL"))).text + "/SignInDirect"
@ -294,7 +339,8 @@ def signIn(username: str, passwd: str):
f.write("<adept:credentials xmlns:adept=\"http://ns.adobe.com/adept\">\n") f.write("<adept:credentials xmlns:adept=\"http://ns.adobe.com/adept\">\n")
f.write("<adept:user>%s</adept:user>\n" % (credentialsXML.find("./%s" % (adNS("user"))).text)) f.write("<adept:user>%s</adept:user>\n" % (credentialsXML.find("./%s" % (adNS("user"))).text))
f.write("<adept:username method=\"%s\">%s</adept:username>\n" % (credentialsXML.find("./%s" % (adNS("username"))).get("method", "AdobeID"), credentialsXML.find("./%s" % (adNS("username"))).text)) if account_type != "anonymous":
f.write("<adept:username method=\"%s\">%s</adept:username>\n" % (credentialsXML.find("./%s" % (adNS("username"))).get("method", account_type), credentialsXML.find("./%s" % (adNS("username"))).text))
f.write("<adept:pkcs12>%s</adept:pkcs12>\n" % (credentialsXML.find("./%s" % (adNS("pkcs12"))).text)) f.write("<adept:pkcs12>%s</adept:pkcs12>\n" % (credentialsXML.find("./%s" % (adNS("pkcs12"))).text))
f.write("<adept:licenseCertificate>%s</adept:licenseCertificate>\n" % (credentialsXML.find("./%s" % (adNS("licenseCertificate"))).text)) f.write("<adept:licenseCertificate>%s</adept:licenseCertificate>\n" % (credentialsXML.find("./%s" % (adNS("licenseCertificate"))).text))
f.write("<adept:privateLicenseKey>%s</adept:privateLicenseKey>\n" % (base64.b64encode(private_key_data).decode("latin-1"))) f.write("<adept:privateLicenseKey>%s</adept:privateLicenseKey>\n" % (base64.b64encode(private_key_data).decode("latin-1")))

View File

@ -56,12 +56,12 @@ def main():
print("Error, couldn't create device file.") print("Error, couldn't create device file.")
exit(1) exit(1)
success, resp = createUser(VAR_VER) success, resp = createUser(VAR_VER, None)
if (success is False): if (success is False):
print("Error, couldn't create user: %s" % resp) print("Error, couldn't create user: %s" % resp)
exit(1) exit(1)
success, resp = signIn(VAR_MAIL, VAR_PASS) success, resp = signIn("AdobeID", VAR_MAIL, VAR_PASS)
if (success is False): if (success is False):
print("Login unsuccessful: " + resp) print("Login unsuccessful: " + resp)
exit(1) exit(1)