Support for extracting PassHashes from ADE
This commit is contained in:
parent
620c90b695
commit
8986855a47
|
@ -44,4 +44,5 @@ List of changes since the fork of Apprentice Harper's repository:
|
||||||
- Merge ignobleepub into ineptepub so there's no duplicate code.
|
- Merge ignobleepub into ineptepub so there's no duplicate code.
|
||||||
- Support extracting the B&N / Nook key from the NOOK Microsoft Store application (based on [this script](https://github.com/noDRM/DeDRM_tools/discussions/9) by fesiwi).
|
- Support extracting the B&N / Nook key from the NOOK Microsoft Store application (based on [this script](https://github.com/noDRM/DeDRM_tools/discussions/9) by fesiwi).
|
||||||
- Support extracting the B&N / Nook key from a data dump of the NOOK Android application.
|
- Support extracting the B&N / Nook key from a data dump of the NOOK Android application.
|
||||||
- Support adding an existing B&N key base64 string without having to write it to a file first.
|
- Support adding an existing PassHash / B&N key base64 string without having to write it to a file first.
|
||||||
|
- Support extracting PassHash keys from Adobe Digital Editions.
|
||||||
|
|
|
@ -38,6 +38,7 @@ Instead, they started generating a random key on their server and send that to t
|
||||||
<li>B&N: The NOOK Android application supports / accepts user-added CA certificates, so you can set up something like mitmproxy on your computer, tunnel your phone's traffic through that, and extract the ccHash key data from the server response. You can then add that hash through the "Base64-encoded PassHash key string" option.</li>
|
<li>B&N: The NOOK Android application supports / accepts user-added CA certificates, so you can set up something like mitmproxy on your computer, tunnel your phone's traffic through that, and extract the ccHash key data from the server response. You can then add that hash through the "Base64-encoded PassHash key string" option.</li>
|
||||||
<li>If you already have a copy of the Nook ccHash key string (or, more general, the PassHash key string) in base64 encoding, you can either click on "Import existing keyfiles" if it's a file in b64 format, or you click on the "Base64-encoded PassHash key string" option while adding a new PassHash key.</li>
|
<li>If you already have a copy of the Nook ccHash key string (or, more general, the PassHash key string) in base64 encoding, you can either click on "Import existing keyfiles" if it's a file in b64 format, or you click on the "Base64-encoded PassHash key string" option while adding a new PassHash key.</li>
|
||||||
<li>For retailers other than B&N that are using the PassHash algorihm as intended, you can click on "Adobe PassHash username & password" to enter your credentials while adding a key. This is the same algorihm as the original credit card number based key generation for B&N.</li>
|
<li>For retailers other than B&N that are using the PassHash algorihm as intended, you can click on "Adobe PassHash username & password" to enter your credentials while adding a key. This is the same algorihm as the original credit card number based key generation for B&N.</li>
|
||||||
|
<li>Windows only: If you've successfully opened a PassHash-encrypted book in Adobe Digital Editions by entering username and password, you can dump the stored credentials from ADE.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -371,6 +371,19 @@ class DeDRM(FileTypePlugin):
|
||||||
print("{0} v{1}: Exception when getting default NOOK Microsoft App keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print("{0} v{1}: Exception when getting default NOOK Microsoft App keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
###### Add keys from Adobe PassHash ADE activation data (adobekey_get_passhash.py)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if iswindows:
|
||||||
|
# Right now this is only implemented for Windows. MacOS support still needs to be added.
|
||||||
|
from calibre_plugins.dedrm.adobekey_get_passhash import passhash_keys
|
||||||
|
defaultkeys_ade, names = passhash_keys()
|
||||||
|
if isosx:
|
||||||
|
print("{0} v{1}: Dumping ADE PassHash data is not yet supported on MacOS.".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
|
except:
|
||||||
|
print("{0} v{1}: Exception when getting PassHashes from ADE after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
###### Check if one of the new keys decrypts the book:
|
###### Check if one of the new keys decrypts the book:
|
||||||
|
|
||||||
|
@ -384,6 +397,10 @@ class DeDRM(FileTypePlugin):
|
||||||
if keyvalue not in dedrmprefs['bandnkeys'].values() and keyvalue not in newkeys:
|
if keyvalue not in dedrmprefs['bandnkeys'].values() and keyvalue not in newkeys:
|
||||||
newkeys.append(keyvalue)
|
newkeys.append(keyvalue)
|
||||||
|
|
||||||
|
for keyvalue in defaultkeys_ade:
|
||||||
|
if keyvalue not in dedrmprefs['bandnkeys'].values() and keyvalue not in newkeys:
|
||||||
|
newkeys.append(keyvalue)
|
||||||
|
|
||||||
if len(newkeys) > 0:
|
if len(newkeys) > 0:
|
||||||
try:
|
try:
|
||||||
for i,userkey in enumerate(newkeys):
|
for i,userkey in enumerate(newkeys):
|
||||||
|
@ -406,7 +423,10 @@ class DeDRM(FileTypePlugin):
|
||||||
# Store the new successful key in the defaults
|
# Store the new successful key in the defaults
|
||||||
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
try:
|
try:
|
||||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_key_'+time.strftime("%Y-%m-%d"),keyvalue)
|
if userkey in defaultkeys_ade:
|
||||||
|
dedrmprefs.addnamedvaluetoprefs('bandnkeys','ade_passhash_'+str(int(time.time())),keyvalue)
|
||||||
|
else:
|
||||||
|
dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_key_'+str(int(time.time())),keyvalue)
|
||||||
dedrmprefs.writeprefs()
|
dedrmprefs.writeprefs()
|
||||||
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# adobekey_get_passhash.py, version 1
|
||||||
|
# based on adobekey.pyw, version 7.2
|
||||||
|
# Copyright © 2009-2021 i♥cabbages, Apprentice Harper et al.
|
||||||
|
# Copyright © 2021 noDRM
|
||||||
|
|
||||||
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Revision history:
|
||||||
|
# 1 - Initial release
|
||||||
|
|
||||||
|
"""
|
||||||
|
Retrieve Adobe ADEPT user passhash keys
|
||||||
|
"""
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__version__ = '1'
|
||||||
|
|
||||||
|
import sys, os, time
|
||||||
|
import base64, hashlib
|
||||||
|
try:
|
||||||
|
from Cryptodome.Cipher import AES
|
||||||
|
except:
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
|
PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
|
||||||
|
|
||||||
|
def unpad(data):
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
pad_len = ord(data[-1])
|
||||||
|
else:
|
||||||
|
pad_len = data[-1]
|
||||||
|
|
||||||
|
return data[:-pad_len]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from calibre.constants import iswindows, isosx
|
||||||
|
except:
|
||||||
|
iswindows = sys.platform.startswith('win')
|
||||||
|
isosx = sys.platform.startswith('darwin')
|
||||||
|
|
||||||
|
|
||||||
|
class ADEPTError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def decrypt_passhash(passhash, fp):
|
||||||
|
|
||||||
|
serial_number = base64.b64decode(fp).hex()
|
||||||
|
|
||||||
|
hash_key = hashlib.sha1(bytearray.fromhex(serial_number + PASS_HASH_SECRET)).digest()[:16]
|
||||||
|
|
||||||
|
encrypted_cc_hash = base64.b64decode(passhash)
|
||||||
|
cc_hash = unpad(AES.new(hash_key, AES.MODE_CBC, encrypted_cc_hash[:16]).decrypt(encrypted_cc_hash[16:]))
|
||||||
|
return base64.b64encode(cc_hash).decode("ascii")
|
||||||
|
|
||||||
|
|
||||||
|
if iswindows:
|
||||||
|
try:
|
||||||
|
import winreg
|
||||||
|
except ImportError:
|
||||||
|
import _winreg as winreg
|
||||||
|
|
||||||
|
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
||||||
|
|
||||||
|
def passhash_keys():
|
||||||
|
cuser = winreg.HKEY_CURRENT_USER
|
||||||
|
keys = []
|
||||||
|
names = []
|
||||||
|
try:
|
||||||
|
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
|
||||||
|
except WindowsError:
|
||||||
|
raise ADEPTError("Could not locate ADE activation")
|
||||||
|
|
||||||
|
idx = 1
|
||||||
|
|
||||||
|
fp = None
|
||||||
|
|
||||||
|
i = -1
|
||||||
|
while True:
|
||||||
|
i = i + 1 # start with 0
|
||||||
|
try:
|
||||||
|
plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
|
||||||
|
except:
|
||||||
|
# No more keys
|
||||||
|
break
|
||||||
|
|
||||||
|
ktype = winreg.QueryValueEx(plkparent, None)[0]
|
||||||
|
|
||||||
|
if ktype == "activationToken":
|
||||||
|
# find fingerprint for hash decryption
|
||||||
|
j = -1
|
||||||
|
while True:
|
||||||
|
j = j + 1 # start with 0
|
||||||
|
try:
|
||||||
|
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
|
||||||
|
except WindowsError:
|
||||||
|
break
|
||||||
|
ktype = winreg.QueryValueEx(plkkey, None)[0]
|
||||||
|
if ktype == 'fingerprint':
|
||||||
|
fp = winreg.QueryValueEx(plkkey, 'value')[0]
|
||||||
|
#print("Found fingerprint: " + fp)
|
||||||
|
|
||||||
|
if ktype == 'passHashList':
|
||||||
|
|
||||||
|
j = -1
|
||||||
|
lastOperator = "Unknown"
|
||||||
|
while True:
|
||||||
|
j = j + 1 # start with 0
|
||||||
|
try:
|
||||||
|
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
|
||||||
|
except WindowsError:
|
||||||
|
break
|
||||||
|
ktype = winreg.QueryValueEx(plkkey, None)[0]
|
||||||
|
if ktype == 'operatorURL':
|
||||||
|
operatorURL = winreg.QueryValueEx(plkkey, 'value')[0]
|
||||||
|
#print("Found operator URL: " + operatorURL)
|
||||||
|
try:
|
||||||
|
lastOperator = operatorURL.split('//')[1].split('/')[0]
|
||||||
|
except:
|
||||||
|
lastOperator = "Unknown"
|
||||||
|
|
||||||
|
elif ktype == "passHash":
|
||||||
|
passhash_encrypted = winreg.QueryValueEx(plkkey, 'value')[0]
|
||||||
|
names.append("ADE_key_" + lastOperator + "_" + str(int(time.time())) + "_" + str(idx))
|
||||||
|
idx = idx + 1
|
||||||
|
keys.append(passhash_encrypted)
|
||||||
|
|
||||||
|
if fp is None:
|
||||||
|
#print("Didn't find fingerprint for decryption ...")
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
print("Found {0:d} passhashes".format(len(keys)))
|
||||||
|
|
||||||
|
keys_decrypted = []
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
decrypted = decrypt_passhash(key, fp)
|
||||||
|
#print("Input key: " + key)
|
||||||
|
#print("Output key: " + decrypted)
|
||||||
|
keys_decrypted.append(decrypted)
|
||||||
|
|
||||||
|
return keys_decrypted, names
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
def passhash_keys():
|
||||||
|
raise ADEPTError("This script only supports Windows.")
|
||||||
|
#TODO: Add MacOS support by parsing the activation.xml file.
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("This is a python calibre plugin. It can't be directly executed.")
|
|
@ -361,6 +361,41 @@ class ManageKeysDialog(QDialog):
|
||||||
if d.result() != d.Accepted:
|
if d.result() != d.Accepted:
|
||||||
# New key generation cancelled.
|
# New key generation cancelled.
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if d.k_key_list is not None:
|
||||||
|
# importing multiple keys
|
||||||
|
idx = -1
|
||||||
|
dup_key_count = 0
|
||||||
|
added_key_count = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
idx = idx + 1
|
||||||
|
try:
|
||||||
|
new_key_value = d.k_key_list[idx]
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
if type(self.plugin_keys) == dict:
|
||||||
|
if new_key_value in self.plugin_keys.values():
|
||||||
|
dup_key_count = dup_key_count + 1
|
||||||
|
continue
|
||||||
|
print("Setting idx " + str(idx) + ", name " + d.k_name_list[idx] + " to " + new_key_value)
|
||||||
|
self.plugin_keys[d.k_name_list[idx]] = new_key_value
|
||||||
|
added_key_count = added_key_count + 1
|
||||||
|
else:
|
||||||
|
if new_key_value in self.plugin_keys:
|
||||||
|
dup_key_count = dup_key_count + 1
|
||||||
|
continue
|
||||||
|
self.plugin_keys.append(new_key_value)
|
||||||
|
added_key_count = added_key_count + 1
|
||||||
|
|
||||||
|
|
||||||
|
if (added_key_count > 0):
|
||||||
|
info_dialog(None, "{0} {1}: Adding {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
||||||
|
"Successfully added {0} key(s).".format(added_key_count), show=True)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Import single key
|
||||||
new_key_value = d.key_value
|
new_key_value = d.key_value
|
||||||
if type(self.plugin_keys) == dict:
|
if type(self.plugin_keys) == dict:
|
||||||
if new_key_value in self.plugin_keys.values():
|
if new_key_value in self.plugin_keys.values():
|
||||||
|
@ -376,6 +411,7 @@ class ManageKeysDialog(QDialog):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.plugin_keys.append(d.key_value)
|
self.plugin_keys.append(d.key_value)
|
||||||
|
|
||||||
self.listy.clear()
|
self.listy.clear()
|
||||||
self.populate_list()
|
self.populate_list()
|
||||||
|
|
||||||
|
@ -575,11 +611,34 @@ class AddBandNKeyDialog(QDialog):
|
||||||
elif idx == 2:
|
elif idx == 2:
|
||||||
self.add_fields_for_b64_passhash()
|
self.add_fields_for_b64_passhash()
|
||||||
elif idx == 3:
|
elif idx == 3:
|
||||||
self.add_fields_for_windows_nook()
|
self.add_fields_for_ade_passhash()
|
||||||
elif idx == 4:
|
elif idx == 4:
|
||||||
|
self.add_fields_for_windows_nook()
|
||||||
|
elif idx == 5:
|
||||||
self.add_fields_for_android_nook()
|
self.add_fields_for_android_nook()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def add_fields_for_ade_passhash(self):
|
||||||
|
|
||||||
|
self.ade_extr_group_box = QGroupBox("", self)
|
||||||
|
ade_extr_group_box_layout = QVBoxLayout()
|
||||||
|
self.ade_extr_group_box.setLayout(ade_extr_group_box_layout)
|
||||||
|
|
||||||
|
self.layout.addWidget(self.ade_extr_group_box)
|
||||||
|
|
||||||
|
ade_extr_group_box_layout.addWidget(QLabel("Click \"OK\" to try and dump PassHash data \nfrom Adobe Digital Editions. This works if\nyou've opened your PassHash books in ADE before.", self))
|
||||||
|
|
||||||
|
self.button_box.hide()
|
||||||
|
|
||||||
|
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
|
self.button_box.accepted.connect(self.accept_ade_dump_passhash)
|
||||||
|
self.button_box.rejected.connect(self.reject)
|
||||||
|
self.layout.addWidget(self.button_box)
|
||||||
|
|
||||||
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
|
|
||||||
def add_fields_for_android_nook(self):
|
def add_fields_for_android_nook(self):
|
||||||
|
|
||||||
self.andr_nook_group_box = QGroupBox("", self)
|
self.andr_nook_group_box = QGroupBox("", self)
|
||||||
|
@ -725,6 +784,7 @@ class AddBandNKeyDialog(QDialog):
|
||||||
self.cbType.addItem("--- Select key type ---")
|
self.cbType.addItem("--- Select key type ---")
|
||||||
self.cbType.addItem("Adobe PassHash username & password")
|
self.cbType.addItem("Adobe PassHash username & password")
|
||||||
self.cbType.addItem("Base64-encoded PassHash key string")
|
self.cbType.addItem("Base64-encoded PassHash key string")
|
||||||
|
self.cbType.addItem("Extract passhashes from Adobe Digital Editions")
|
||||||
self.cbType.addItem("Extract key from Nook Windows application")
|
self.cbType.addItem("Extract key from Nook Windows application")
|
||||||
self.cbType.addItem("Extract key from Nook Android application")
|
self.cbType.addItem("Extract key from Nook Android application")
|
||||||
self.cbType.currentIndexChanged.connect(self.update_form, self.cbType.currentIndex())
|
self.cbType.currentIndexChanged.connect(self.update_form, self.cbType.currentIndex())
|
||||||
|
@ -738,7 +798,10 @@ class AddBandNKeyDialog(QDialog):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_name(self):
|
def key_name(self):
|
||||||
|
try:
|
||||||
return str(self.key_ledit.text()).strip()
|
return str(self.key_ledit.text()).strip()
|
||||||
|
except:
|
||||||
|
return self.result_data_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_value(self):
|
def key_value(self):
|
||||||
|
@ -752,6 +815,23 @@ class AddBandNKeyDialog(QDialog):
|
||||||
def cc_number(self):
|
def cc_number(self):
|
||||||
return str(self.cc_ledit.text()).strip()
|
return str(self.cc_ledit.text()).strip()
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def k_name_list(self):
|
||||||
|
# If the plugin supports returning multiple keys, return a list of names.
|
||||||
|
if self.k_full_name_list is not None and self.k_full_key_list is not None:
|
||||||
|
return self.k_full_name_list
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def k_key_list(self):
|
||||||
|
# If the plugin supports returning multiple keys, return a list of keys.
|
||||||
|
if self.k_full_name_list is not None and self.k_full_key_list is not None:
|
||||||
|
return self.k_full_key_list
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def accept_android_nook(self):
|
def accept_android_nook(self):
|
||||||
|
|
||||||
if len(self.key_name) < 4:
|
if len(self.key_name) < 4:
|
||||||
|
@ -775,10 +855,47 @@ class AddBandNKeyDialog(QDialog):
|
||||||
errmsg = "Failed to extract keys. Is this the correct folder?"
|
errmsg = "Failed to extract keys. Is this the correct folder?"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
|
|
||||||
|
# Take the first key we found. In the future it might be a good idea to import them all.
|
||||||
|
# See accept_ade_dump_passhash for an example on how to do that.
|
||||||
self.result_data = store_result[0]
|
self.result_data = store_result[0]
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
|
|
||||||
|
def accept_ade_dump_passhash(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
from calibre_plugins.dedrm.adobekey_get_passhash import passhash_keys
|
||||||
|
keys, names = passhash_keys()
|
||||||
|
except:
|
||||||
|
errmsg = "Failed to grab PassHash keys from ADE."
|
||||||
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
|
|
||||||
|
# Take the first new key we found.
|
||||||
|
|
||||||
|
idx = -1
|
||||||
|
new_keys = []
|
||||||
|
new_names = []
|
||||||
|
for key in keys:
|
||||||
|
idx = idx + 1
|
||||||
|
if key in self.parent.plugin_keys.values():
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_keys.append(key)
|
||||||
|
new_names.append(names[idx])
|
||||||
|
|
||||||
|
if len(new_keys) == 0:
|
||||||
|
# Okay, we didn't find anything. How do we get rid of the window?
|
||||||
|
errmsg = "Didn't find any PassHash keys in ADE."
|
||||||
|
error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
|
QDialog.reject(self)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add new keys to list.
|
||||||
|
self.k_full_name_list = new_names
|
||||||
|
self.k_full_key_list = new_keys
|
||||||
|
QDialog.accept(self)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def accept_win_nook(self):
|
def accept_win_nook(self):
|
||||||
|
@ -799,8 +916,8 @@ class AddBandNKeyDialog(QDialog):
|
||||||
from calibre_plugins.dedrm.ignoblekeyNookStudy import nookkeys
|
from calibre_plugins.dedrm.ignoblekeyNookStudy import nookkeys
|
||||||
store_result = nookkeys()
|
store_result = nookkeys()
|
||||||
|
|
||||||
# Take the first key we found. In the future it might be a good idea to import them all,
|
# Take the first key we found. In the future it might be a good idea to import them all.
|
||||||
# but with how the import dialog is currently structured that's not easily possible.
|
# See accept_ade_dump_passhash for an example on how to do that.
|
||||||
if len(store_result) > 0:
|
if len(store_result) > 0:
|
||||||
self.result_data = store_result[0]
|
self.result_data = store_result[0]
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
@ -1012,6 +1129,9 @@ class AddAdeptDialog(QDialog):
|
||||||
# Right now this code only supports adding one key per each invocation,
|
# Right now this code only supports adding one key per each invocation,
|
||||||
# so if the user has multiple keys, he's going to need to add the "plus" button multiple times.
|
# so if the user has multiple keys, he's going to need to add the "plus" button multiple times.
|
||||||
|
|
||||||
|
# In the future it might be a good idea to import them all.
|
||||||
|
# See accept_ade_dump_passhash for an example on how to do that.
|
||||||
|
|
||||||
if len(self.new_keys)>0:
|
if len(self.new_keys)>0:
|
||||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue