Support converting anonymous auth to new AdobeID

This commit is contained in:
Florian Bach 2021-12-14 19:35:07 +01:00
parent cc37506762
commit 732ed1cf3e
4 changed files with 218 additions and 11 deletions

View File

@ -21,9 +21,9 @@ Once that's done, download an ACSM file from Adobe's test library and see if you
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 using 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, 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. - If you use an anonymous authorization, you have the ability to copy that authorization into an AdobeID account at a later time (by clicking "Connect anonymous auth to ADE account"). This is useful if you have books linked to your authorization that you want to read elsewhere. Same restrictions as with ADE apply - you can only do this ONCE per AdobeID, and only if the AdobeID hasn't been in use elsewhere yet.
- 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

View File

@ -31,7 +31,8 @@
# add useful error message for ACSMs with nonstandard download type. # add useful error message for ACSMs with nonstandard download type.
# Currently in development: # Currently in development:
# Add support for anonymous authorizations, add support for other ID providers, # Add support for anonymous authorizations, add support for other ID providers,
# fix ACSM files from Google Play books (no metadata node). # fix ACSM files from Google Play books (no metadata node),
# allow converting an anonymous auth to an AdobeID auth.
PLUGIN_NAME = "DeACSM" PLUGIN_NAME = "DeACSM"
PLUGIN_VERSION_TUPLE = (0, 0, 14) PLUGIN_VERSION_TUPLE = (0, 0, 14)

View File

@ -102,6 +102,15 @@ class ConfigWidget(QWidget):
ua_group_box_layout.addWidget(self.button_import_activation) ua_group_box_layout.addWidget(self.button_import_activation)
else: else:
if mail is None:
# Current auth is anon auth. Offer to link to account.
self.button_convert_anon_to_account = QtGui.QPushButton(self)
self.button_convert_anon_to_account.setText(_("Connect anonymous auth to ADE account"))
self.button_convert_anon_to_account.clicked.connect(self.convert_anon_to_account)
ua_group_box_layout.addWidget(self.button_convert_anon_to_account)
self.button_switch_ade_version = QtGui.QPushButton(self) self.button_switch_ade_version = QtGui.QPushButton(self)
self.button_switch_ade_version.setText(_("Change ADE version")) self.button_switch_ade_version.setText(_("Change ADE version"))
self.button_switch_ade_version.clicked.connect(self.switch_ade_version) self.button_switch_ade_version.clicked.connect(self.switch_ade_version)
@ -185,6 +194,10 @@ class ConfigWidget(QWidget):
self.button_import_LinuxWineADE.setEnabled(activated) self.button_import_LinuxWineADE.setEnabled(activated)
else: else:
self.button_switch_ade_version.setEnabled(False) self.button_switch_ade_version.setEnabled(False)
try:
self.button_convert_anon_to_account.setEnabled(False)
except:
pass
self.button_export_key.setEnabled(False) self.button_export_key.setEnabled(False)
self.button_export_activation.setEnabled(False) self.button_export_activation.setEnabled(False)
self.button_rented_books.setEnabled(False) self.button_rented_books.setEnabled(False)
@ -274,6 +287,8 @@ class ConfigWidget(QWidget):
self.button_switch_ade_version.setEnabled(False) self.button_switch_ade_version.setEnabled(False)
except: except:
pass pass
if ade_mail is None:
self.button_convert_anon_to_account.setEnabled(False)
self.button_export_activation.setEnabled(False) self.button_export_activation.setEnabled(False)
self.button_export_key.setEnabled(False) self.button_export_key.setEnabled(False)
self.lblAccInfo.setText("Authorization deleted.\nClose and re-open this window to add a new authorization.") self.lblAccInfo.setText("Authorization deleted.\nClose and re-open this window to add a new authorization.")
@ -461,6 +476,10 @@ class ConfigWidget(QWidget):
self.button_link_account.setEnabled(not activated) self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated) self.button_anon_auth.setEnabled(not activated)
try:
self.button_convert_anon_to_account.setEnabled(ade_mail is None)
except:
pass
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)
@ -494,6 +513,10 @@ class ConfigWidget(QWidget):
self.button_link_account.setEnabled(not activated) self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated) self.button_anon_auth.setEnabled(not activated)
try:
self.button_convert_anon_to_account.setEnabled(ade_mail is None)
except:
pass
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)
@ -532,6 +555,10 @@ class ConfigWidget(QWidget):
self.button_link_account.setEnabled(not activated) self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated) self.button_anon_auth.setEnabled(not activated)
try:
self.button_convert_anon_to_account.setEnabled(ade_mail is None)
except:
pass
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)
@ -597,6 +624,10 @@ class ConfigWidget(QWidget):
self.button_link_account.setEnabled(not activated) self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated) self.button_anon_auth.setEnabled(not activated)
try:
self.button_convert_anon_to_account.setEnabled(ade_mail is None)
except:
pass
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)
@ -794,6 +825,10 @@ class ConfigWidget(QWidget):
self.button_link_account.setEnabled(not activated) self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated) self.button_anon_auth.setEnabled(not activated)
try:
self.button_convert_anon_to_account.setEnabled(mail is None)
except:
pass
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)
@ -808,6 +843,75 @@ class ConfigWidget(QWidget):
info_dialog(None, "Done", "Authorized to anonymous account.", show=True, show_copy_button=False) info_dialog(None, "Done", "Authorized to anonymous account.", show=True, show_copy_button=False)
def convert_anon_to_account(self):
try:
from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path
from calibre_plugins.deacsm.libadobeAccount import convertAnonAuthToAccount
except:
try:
from libadobe import createDeviceKeyFile, update_account_path
from libadobeAccount import convertAnonAuthToAccount
except:
print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION))
traceback.print_exc()
update_account_path(self.deacsmprefs["path_to_account_data"])
# This MUST only be called on anonymous accounts.
# The button should be disabled if that's not the case, but just to make sure ...
info_string, activated, mail = self.get_account_info()
if (not activated):
return
if (mail is not None):
return
msg = "You are about to link your anonymous authorization to an AdobeID. "
msg += "This only works ONCE for each AdobeID. The anonymous authorization will then "
msg += "permanently be connected to your AdobeID. This is intended for cases where the user "
msg += "has started with an anonymous authorization, and then creates a fresh AdobeID later "
msg += "and doesn't want to lose his books.\n\n"
msg += "Only continue if you fully understand this."
warning_dialog(None, "Warning", msg, show=True, show_copy_button=False)
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
success, message = convertAnonAuthToAccount(mail, passwd)
if (success):
# update display
info_string, activated, mail = self.get_account_info()
self.lblAccInfo.setText(info_string)
try:
self.button_convert_anon_to_account.setEnabled(mail is None)
except:
pass
self.resize(self.sizeHint())
return info_dialog(None, "Done", "Successfully converted anonynmous authentication to AdobeID", show=True, show_copy_button=False)
else:
err_msg = "Could not link anonymous authentication to AdobeID.\n"
err_msg += "This only works with a fresh AdobeID that has never been linked to any ADE install."
return error_dialog(None, "ADE activation failed", err_msg, det_msg=str(message), show=True, show_copy_button=True)
def link_account(self): def link_account(self):
try: try:
@ -906,6 +1010,10 @@ class ConfigWidget(QWidget):
self.button_link_account.setEnabled(not activated) self.button_link_account.setEnabled(not activated)
self.button_anon_auth.setEnabled(not activated) self.button_anon_auth.setEnabled(not activated)
try:
self.button_convert_anon_to_account.setEnabled(mail is None)
except:
pass
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)

View File

@ -215,14 +215,7 @@ def createUser(useVersionIndex: int = 0, authCert = None):
return True, "Done" return True, "Done"
def encryptLoginCredentials(username: str, password: str, authenticationCertificate: str):
def buildSignInRequest(type: str, username: str, password: str, authenticationCertificate: str):
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
etree.register_namespace("adept", NSMAP["adept"])
root = etree.Element(etree.QName(NSMAP["adept"], "signIn"))
root.set("method", type)
f = open(get_devkey_path(), "rb") f = open(get_devkey_path(), "rb")
devkey_bytes = f.read() devkey_bytes = f.read()
f.close() f.close()
@ -248,6 +241,43 @@ def buildSignInRequest(type: str, username: str, password: str, authenticationCe
cipherAC = PKCS1_v1_5.new(rsakey) cipherAC = PKCS1_v1_5.new(rsakey)
crypted_msg = cipherAC.encrypt(bytes(ar)) crypted_msg = cipherAC.encrypt(bytes(ar))
return crypted_msg
def buildSignInRequestForAnonAuthConvert(username: str, password: str, authenticationCertificate: str):
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
etree.register_namespace("adept", NSMAP["adept"])
root = etree.Element(etree.QName(NSMAP["adept"], "signIn"))
root.set("method", "AdobeID")
crypted_msg = encryptLoginCredentials(username, password, authenticationCertificate)
etree.SubElement(root, etree.QName(NSMAP["adept"], "signInData")).text = base64.b64encode(crypted_msg)
try:
activationxml = etree.parse(get_activation_xml_path())
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
user_uuid = activationxml.find("./%s/%s" % (adNS("credentials"), adNS("user"))).text
except:
return None
etree.SubElement(root, etree.QName(NSMAP["adept"], "user")).text = user_uuid
signature = sign_node(root)
etree.SubElement(root, etree.QName(NSMAP["adept"], "signature")).text = signature
return "<?xml version=\"1.0\"?>\n" + etree.tostring(root, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1")
def buildSignInRequest(type: str, username: str, password: str, authenticationCertificate: str):
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
etree.register_namespace("adept", NSMAP["adept"])
root = etree.Element(etree.QName(NSMAP["adept"], "signIn"))
root.set("method", type)
crypted_msg = encryptLoginCredentials(username, password, authenticationCertificate)
etree.SubElement(root, etree.QName(NSMAP["adept"], "signInData")).text = base64.b64encode(crypted_msg) etree.SubElement(root, etree.QName(NSMAP["adept"], "signInData")).text = base64.b64encode(crypted_msg)
# Generate Auth key and License Key # Generate Auth key and License Key
@ -272,6 +302,74 @@ def buildSignInRequest(type: str, username: str, password: str, authenticationCe
return "<?xml version=\"1.0\"?>\n" + etree.tostring(root, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1") return "<?xml version=\"1.0\"?>\n" + etree.tostring(root, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1")
def convertAnonAuthToAccount(username: str, passwd: str):
# If you have an anonymous authorization, you can convert that to an AdobeID.
# Important: You can only do this ONCE for each AdobeID.
# The AdobeID you are using for this must not be connected to any ADE install.
# This is intended for cases where people install ADE, use an anonymous auth,
# buy a couple books, and then decide to get a fresh AdobeID.
# Get authenticationCertificate
try:
activationxml = etree.parse(get_activation_xml_path())
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
authenticationCertificate = activationxml.find("./%s/%s" % (adNS("activationServiceInfo"), adNS("authenticationCertificate"))).text
except:
return False, "Missing authenticationCertificate"
if authenticationCertificate == "":
return False, "Empty authenticationCertificate"
linkRequest = buildSignInRequestForAnonAuthConvert(username, passwd, authenticationCertificate)
signInURL = activationxml.find("./%s/%s" % (adNS("activationServiceInfo"), adNS("authURL"))).text + "/AddSignInDirect"
linkResponse = sendRequestDocu(linkRequest, signInURL)
try:
credentialsXML = etree.fromstring(linkResponse)
if (credentialsXML.tag == adNS("error")):
err = credentialsXML.get("data")
err_parts = err.split(' ')
if err_parts[0] == "E_AUTH_USER_ALREADY_REGISTERED":
try:
return False, "Can't link anon auth " + err_parts[2] + " to account, account already has user ID " + err_parts[3]
except:
pass
return False, "Can't link anon auth to account: " + err
elif (credentialsXML.tag != adNS("success")):
return False, "Invalid main tag " + credentialsXML.tag
except:
return False, "Invalid response to login request"
# If we end up here, the account linking was successful. Now we just need to update the activation.xml accordingly.
activationxml = etree.parse(get_activation_xml_path())
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
cred_node = activationxml.find("./%s" % (adNS("credentials")))
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
tmp_node = etree.SubElement(cred_node, etree.QName(NSMAP["adept"], "username"))
# Adobe / ADE only supports this account linking for AdobeID accounts, not for any Vendor IDs.
tmp_node.set("method", "AdobeID")
tmp_node.text = username
# Write to file
f = open(get_activation_xml_path(), "w")
f.write("<?xml version=\"1.0\"?>\n")
f.write(etree.tostring(activationxml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1"))
f.close()
return True, "Account linking successful"
def signIn(account_type: str, username: str, passwd: str): def signIn(account_type: str, username: str, passwd: str):