self.tempdedrmprefs['remove_watermarks'] = self.dedrmprefs['remove_watermarks']
self.tempdedrmprefs['lcp_passphrases'] = list(self.dedrmprefs['lcp_passphrases'])
self.tempdedrmprefs['adobe_pdf_passphrases'] = list(self.dedrmprefs['adobe_pdf_passphrases'])
-
+ self.tempdedrmprefs['kindleextrakeyfile']=self.dedrmprefs['kindleextrakeyfile']
# Start Qt Gui dialog layout
layout = QVBoxLayout(self)
self.setLayout(layout)
button_layout.addWidget(self.kindle_serial_button)
button_layout.addWidget(self.kindle_android_button)
button_layout.addWidget(self.kindle_key_button)
+ ## mine
+ self._hbox = QWidget ()
+ #self._hbox.setFlat(True)
+ hlayout = QHBoxLayout()
+ self._extrakeyfile_ledit = QLineEdit(self.tempdedrmprefs['kindleextrakeyfile'], self)
+ self._extrakeyfile_ledit.setReadOnly(True)
+ hlayout.addWidget(self._extrakeyfile_ledit)
+
+ self._select_extrakeyfile_button = QPushButton("")
+ self._select_extrakeyfile_button.setIcon(QIcon(I('document_open.png')))
+ self._select_extrakeyfile_button.setToolTip("Select a file with extra key candidates for kindle books")
+ self._select_extrakeyfile_button.clicked.connect(self.select_kindleextrakeyfile_dialog)
+
+ hlayout.addWidget(self._select_extrakeyfile_button)
+ self._hbox.setLayout(hlayout)
+ self._hbox.resize(self._hbox.sizeHint())
+ button_layout.addWidget(self._hbox)
+ ##
button_layout.addSpacing(15)
button_layout.addWidget(self.adept_button)
button_layout.addWidget(self.bandn_button)
button_layout.addWidget(self.chkRemoveWatermarks)
self.resize(self.sizeHint())
-
+ def select_kindleextrakeyfile_dialog(self):
+ unique_dlg_name = PLUGIN_NAME + "import Kindle book key candidates".replace(' ', '_') #takes care of automatically remembering last directory
+ caption = "Select file generated by key extractor to import"
+ filters = []
+ files = choose_files(self, unique_dlg_name, caption, filters, all_files=False,select_only_single_file=True)
+ counter = 0
+ skipped = 0
+ if files:
+ for filename in files:
+ #print(filename)
+ fpath = os.path.join(config_dir, filename)
+ filename = os.path.basename(filename)
+ new_key_name = os.path.splitext(os.path.basename(filename))[0]
+ goodenough=False
+ print(fpath)
+ with open(fpath,'r') as keyfile:
+ for line in keyfile: #just check one
+ sline=line.strip()
+ ls=sline.split("$")
+ #print(ls)
+ if len(ls)>1:
+ goodenough=True
+ break
+ if goodenough:
+ self.tempdedrmprefs['kindleextrakeyfile']=fpath
+ self._extrakeyfile_ledit.setText(fpath)
+ counter=1
+ return counter > 0
def kindle_serials(self):
d = ManageKeysDialog(self,"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
d.exec_()
self.dedrmprefs.set('remove_watermarks', self.chkRemoveWatermarks.isChecked())
self.dedrmprefs.set('lcp_passphrases', self.tempdedrmprefs['lcp_passphrases'])
self.dedrmprefs.set('adobe_pdf_passphrases', self.tempdedrmprefs['adobe_pdf_passphrases'])
+ self.dedrmprefs.set('kindleextrakeyfile', self.tempdedrmprefs['kindleextrakeyfile'])
self.dedrmprefs.writeprefs()
def load_resource(self, name):
except ImportError:
# Windows-friendly choice: pylzma wheels
import pylzma as lzma
-
-from .kfxtables import *
+try:
+ from .kfxtables import *
+except:
+ from kfxtables import *
TID_NULL = 0
TID_BOOLEAN = 1
] + ['com.amazon.drm.VoucherEnvelope@%d.0' % n
for n in list(range(2, 29)) + [
9708, 1031, 2069, 9041, 3646,
- 6052, 9479, 9888, 4648, 5683]]
+ 6052, 9479, 9888, 4648, 5683,7384,2746,3332]+list(range(10001,11111))] #this assumes there are no new types added aside from voucher envelopes. So far it was largely correct
+
+
def addprottable(ion):
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
if version == 1: # v1 does not use obfuscation
return secret
- magic, word = OBFUSCATION_TABLE["V%d" % version]
+ magic, word = OBFUSCATION_TABLE.get("V%d" % version,(1,b"unknown"))
# extend secret so that its length is divisible by the magic number
if len(secret) % magic != 0:
def obfuscate2(secret, version):
if version == 1: # v1 does not use obfuscation
return secret
- magic, word = OBFUSCATION_TABLE["V%d" % version]
+ magic, word = OBFUSCATION_TABLE.get("V%d" % version,(1,b"unknown"))
# extend secret so that its length is divisible by the magic number
if len(secret) % magic != 0:
secret = secret + b'\x00' * (magic - len(secret) % magic)
def obfuscate3(secret, version):
if version == 1: # v1 does not use obfuscation
return secret
- magic, word = OBFUSCATION_TABLE["V%d" % version]
+ magic, word = OBFUSCATION_TABLE.get("V%d" % version,(1,b"unknown"))
# extend secret so that its length is divisible by the magic number
if len(secret) % magic != 0:
secret = secret + b'\x00' * (magic - len(secret) % magic)
obfuscated[i] = shuffled[i] ^ wordhash[i % 16]
return obfuscated
+class SKeyList(object):
+ def __init__(self, skeyfile):
+ self.keycandidates={}
+ self.secretkeys={} #let us hope there is one key per voucher...
+ if skeyfile is None: return
+ if os.path.isfile(skeyfile):
+ with open(skeyfile,"r",encoding="utf8") as fl:
+ for line in fl:
+ sline=line.strip()
+ if len(sline)<32: continue
+ lst=sline.split("$")
+ if len(lst)<2: continue
+ voucherid=lst[0]
+ for key in lst[1:]:
+ skey=key.split(":")
+ if skey[0]=="secret_key":
+ self.secretkeys[voucherid]=bytes.fromhex(skey[1])
+ elif skey[0]=="shared_key":
+ curlist=self.keycandidates.get(voucherid,[])
+ curlist.append(bytes.fromhex(skey[1]))
+ self.keycandidates[voucherid]=curlist
+
class DrmIonVoucher(object):
envelope = None
version = None
cipheriv = b""
secretkey = b""
- def __init__(self, voucherenv, dsn, secret):
+ def __init__(self, voucherenv, dsn, secret,skeylist=None):
self.dsn, self.secret = dsn, secret
if isinstance(dsn, str):
self.secret = secret.encode('ASCII')
self.lockparams = []
-
+ self.keycandidates=[]
+ self.secretkeycandidate=None
+ self.skeylist=skeylist
+ self.voucher_id=""
self.envelope = BinaryIonParser(voucherenv)
addprottable(self.envelope)
decrypted=False
lastexception = None # type: Exception | None
- for sharedsecret in sharedsecrets:
- key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
+ keycandidates=self.keycandidates+[hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest() for sharedsecret in sharedsecrets]
+ for key in keycandidates:
+ print(f"{key.hex()} {self.cipheriv[:16].hex()}")
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
try:
b = aes.decrypt(self.ciphertext)
lastexception = ex
print("Decryption failed, trying next fallback ")
if not decrypted:
- raise lastexception
+ if self.secretkeycandidate is None:
+ print("Failed all decryption attempts and no key candidate available")
+ raise lastexception
+ else:
+ print("Failed all decryption attempts but we have a key candidate")
+ self.secretkey =self.secretkeycandidate
+ self.drmkey=None
+ return
+
self.drmkey.stepin()
while self.drmkey.hasnext():
def parse(self):
self.envelope.reset()
_assert(self.envelope.hasnext(), "Envelope is empty")
- _assert(self.envelope.next() == TID_STRUCT and str.startswith(self.envelope.gettypename(), "com.amazon.drm.VoucherEnvelope@"),
+ tn=self.envelope.gettypename()
+ _assert(self.envelope.next() == TID_STRUCT and str.startswith(tn, "com.amazon.drm.VoucherEnvelope@"),
"Unknown type encountered in envelope, expected VoucherEnvelope")
+ print(f"Envelope version {tn}")
self.version = int(self.envelope.gettypename().split('@')[1][:-2])
self.envelope.stepin()
self.envelope.stepout()
self.parsevoucher()
-
+ if self.skeylist is not None:
+ self.keycandidates=self.skeylist.keycandidates.get(self.voucher_id,[])
+ print(f"Got {len(self.keycandidates)} shared key candidates {self.skeylist.keycandidates} {self.voucher_id}")
+ self.secretkeycandidate=self.skeylist.secretkeys.get(self.voucher_id,None)
+ if self.secretkeycandidate is not None:
+ print(f"Got secret key candidate from file: {self.secretkeycandidate.hex()}")
+
def parsevoucher(self):
_assert(self.voucher.hasnext(), "Voucher is empty")
_assert(self.voucher.next() == TID_STRUCT and self.voucher.gettypename() == "com.amazon.drm.Voucher@1.0",
self.cipheriv = self.voucher.lobvalue()
elif self.voucher.getfieldname() == "cipher_text":
self.ciphertext = self.voucher.lobvalue()
+ elif self.voucher.getfieldname() == "id":
+ self.voucher_id = self.voucher.stringvalue()
elif self.voucher.getfieldname() == "license":
_assert(self.voucher.gettypename() == "com.amazon.drm.License@1.0",
"Unknown license: %s" % self.voucher.gettypename())