Add B&N PDF DeDRM (untested), match UUID for Adobe PDFs
This commit is contained in:
parent
b4c0e33b8b
commit
9c41716e5e
|
@ -544,7 +544,7 @@ class DeDRM(FileTypePlugin):
|
||||||
|
|
||||||
# If we end up here, we didn't find a key with a matching UUID, so lets just try all of them.
|
# If we end up here, we didn't find a key with a matching UUID, so lets just try all of them.
|
||||||
|
|
||||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
# Attempt to decrypt PDF with each encryption key (generated or provided).
|
||||||
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
||||||
userkey = codecs.decode(userkeyhex,'hex')
|
userkey = codecs.decode(userkeyhex,'hex')
|
||||||
print("{0} v{1}: Trying encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
|
print("{0} v{1}: Trying encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
|
||||||
|
@ -640,6 +640,36 @@ class DeDRM(FileTypePlugin):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Unable to decrypt the PDF with any of the existing keys. Is it a B&N PDF?
|
||||||
|
# Attempt to decrypt PDF with each encryption key (generated or provided).
|
||||||
|
for keyname, userkey in dedrmprefs['bandnkeys'].items():
|
||||||
|
keyname_masked = "".join(("X" if (x.isdigit()) else x) for x in keyname)
|
||||||
|
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked))
|
||||||
|
of = self.temporary_file(".pdf")
|
||||||
|
|
||||||
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||||
|
try:
|
||||||
|
result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name, False)
|
||||||
|
except ineptpdf.ADEPTNewVersionError:
|
||||||
|
print("{0} v{1}: Book uses unsupported (too new) Adobe DRM.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||||
|
return path_to_ebook
|
||||||
|
except:
|
||||||
|
print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||||
|
traceback.print_exc()
|
||||||
|
result = 1
|
||||||
|
|
||||||
|
of.close()
|
||||||
|
|
||||||
|
if result == 0:
|
||||||
|
# Decryption was successful.
|
||||||
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
|
print("{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime))
|
||||||
|
return of.name
|
||||||
|
|
||||||
|
print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime))
|
||||||
|
|
||||||
|
|
||||||
# Something went wrong with decryption.
|
# Something went wrong with decryption.
|
||||||
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||||
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||||
|
|
|
@ -24,6 +24,7 @@ Decrypts Barnes & Noble encrypted PDF files.
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "0.3"
|
__version__ = "0.3"
|
||||||
|
|
||||||
|
import codecs
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -312,7 +313,7 @@ class PSLiteral(PSObject):
|
||||||
Use PSLiteralTable.intern() instead.
|
Use PSLiteralTable.intern() instead.
|
||||||
'''
|
'''
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name.decode('utf-8')
|
||||||
return
|
return
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -1448,7 +1449,7 @@ class PDFDocument(object):
|
||||||
self.decipher = self.decipher_rc4 # XXX may be AES
|
self.decipher = self.decipher_rc4 # XXX may be AES
|
||||||
# aes
|
# aes
|
||||||
elif V == 4 and length == 128:
|
elif V == 4 and length == 128:
|
||||||
elf.decipher = self.decipher_aes
|
self.decipher = self.decipher_aes
|
||||||
elif V == 4 and length == 256:
|
elif V == 4 and length == 256:
|
||||||
raise PDFNotImplementedError('AES256 encryption is currently unsupported')
|
raise PDFNotImplementedError('AES256 encryption is currently unsupported')
|
||||||
self.ready = True
|
self.ready = True
|
||||||
|
@ -1491,7 +1492,7 @@ class PDFDocument(object):
|
||||||
# proper length unknown try with whatever you have
|
# proper length unknown try with whatever you have
|
||||||
print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
|
print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
|
||||||
print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
|
print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
|
||||||
print("bookkey[0] is %d" % ord(bookkey[0]))
|
print("bookkey[0] is %d" % bookkey[0])
|
||||||
if ebx_V == 3:
|
if ebx_V == 3:
|
||||||
V = 3
|
V = 3
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1488,7 +1488,7 @@ class PDFDocument(object):
|
||||||
# Perform the initialization with a given password.
|
# Perform the initialization with a given password.
|
||||||
# This step is mandatory even if there's no password associated
|
# This step is mandatory even if there's no password associated
|
||||||
# with the document.
|
# with the document.
|
||||||
def initialize(self, password=b''):
|
def initialize(self, password=b'', inept=True):
|
||||||
if not self.encryption:
|
if not self.encryption:
|
||||||
self.is_printable = self.is_modifiable = self.is_extractable = True
|
self.is_printable = self.is_modifiable = self.is_extractable = True
|
||||||
self.ready = True
|
self.ready = True
|
||||||
|
@ -1500,8 +1500,11 @@ class PDFDocument(object):
|
||||||
return self.initialize_adobe_ps(password, docid, param)
|
return self.initialize_adobe_ps(password, docid, param)
|
||||||
if type == 'Standard':
|
if type == 'Standard':
|
||||||
return self.initialize_standard(password, docid, param)
|
return self.initialize_standard(password, docid, param)
|
||||||
if type == 'EBX_HANDLER':
|
if type == 'EBX_HANDLER' and inept is True:
|
||||||
return self.initialize_ebx(password, docid, param)
|
return self.initialize_ebx_inept(password, docid, param)
|
||||||
|
if type == 'EBX_HANDLER' and inept is False:
|
||||||
|
return self.initialize_ebx_ignoble(password, docid, param)
|
||||||
|
|
||||||
raise PDFEncryptionError('Unknown filter: param=%r' % param)
|
raise PDFEncryptionError('Unknown filter: param=%r' % param)
|
||||||
|
|
||||||
def initialize_adobe_ps(self, password, docid, param):
|
def initialize_adobe_ps(self, password, docid, param):
|
||||||
|
@ -1642,7 +1645,52 @@ class PDFDocument(object):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def initialize_ebx(self, password, docid, param):
|
def initialize_ebx_ignoble(self, keyb64, docid, param):
|
||||||
|
self.is_printable = self.is_modifiable = self.is_extractable = True
|
||||||
|
key = keyb64.decode('base64')[:16]
|
||||||
|
aes = AES.new(key,AES.MODE_CBC,"\x00" * len(key))
|
||||||
|
length = int_value(param.get('Length', 0)) / 8
|
||||||
|
rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
|
||||||
|
rights = zlib.decompress(rights, -15)
|
||||||
|
rights = etree.fromstring(rights)
|
||||||
|
expr = './/{http://ns.adobe.com/adept}encryptedKey'
|
||||||
|
bookkey = ''.join(rights.findtext(expr)).decode('base64')
|
||||||
|
bookkey = aes.decrypt(bookkey)
|
||||||
|
bookkey = bookkey[:-ord(bookkey[-1])]
|
||||||
|
bookkey = bookkey[-16:]
|
||||||
|
ebx_V = int_value(param.get('V', 4))
|
||||||
|
ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
|
||||||
|
# added because of improper booktype / decryption book session key errors
|
||||||
|
if length > 0:
|
||||||
|
if len(bookkey) == length:
|
||||||
|
if ebx_V == 3:
|
||||||
|
V = 3
|
||||||
|
else:
|
||||||
|
V = 2
|
||||||
|
elif len(bookkey) == length + 1:
|
||||||
|
V = bookkey[0]
|
||||||
|
bookkey = bookkey[1:]
|
||||||
|
else:
|
||||||
|
print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
|
||||||
|
print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
|
||||||
|
print("bookkey[0] is %d" % bookkey[0])
|
||||||
|
raise ADEPTError('error decrypting book session key - mismatched length')
|
||||||
|
else:
|
||||||
|
# proper length unknown try with whatever you have
|
||||||
|
print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
|
||||||
|
print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
|
||||||
|
print("bookkey[0] is %d" % ord(bookkey[0]))
|
||||||
|
if ebx_V == 3:
|
||||||
|
V = 3
|
||||||
|
else:
|
||||||
|
V = 2
|
||||||
|
self.decrypt_key = bookkey
|
||||||
|
self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
|
||||||
|
self.decipher = self.decrypt_rc4
|
||||||
|
self.ready = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def initialize_ebx_inept(self, password, docid, param):
|
||||||
self.is_printable = self.is_modifiable = self.is_extractable = True
|
self.is_printable = self.is_modifiable = self.is_extractable = True
|
||||||
rsa = RSA(password)
|
rsa = RSA(password)
|
||||||
length = int_value(param.get('Length', 0)) // 8
|
length = int_value(param.get('Length', 0)) // 8
|
||||||
|
@ -2042,15 +2090,19 @@ class PDFObjStrmParser(PDFParser):
|
||||||
def adeptGetUserUUID(inf):
|
def adeptGetUserUUID(inf):
|
||||||
try:
|
try:
|
||||||
doc = PDFDocument()
|
doc = PDFDocument()
|
||||||
|
inf = open(inf, 'rb')
|
||||||
pars = PDFParser(doc, inf)
|
pars = PDFParser(doc, inf)
|
||||||
|
|
||||||
(docid, param) = doc.encryption
|
(docid, param) = doc.encryption
|
||||||
type = literal_name(param['Filter'])
|
type = literal_name(param['Filter'])
|
||||||
if type != 'EBX_HANDLER':
|
if type != 'EBX_HANDLER':
|
||||||
# No EBX_HANDLER, no idea which user key can decrypt this.
|
# No EBX_HANDLER, no idea which user key can decrypt this.
|
||||||
|
inf.close()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
rights = codecs.decode(param.get('ADEPT_LICENSE'), 'base64')
|
rights = codecs.decode(param.get('ADEPT_LICENSE'), 'base64')
|
||||||
|
inf.close()
|
||||||
|
|
||||||
rights = zlib.decompress(rights, -15)
|
rights = zlib.decompress(rights, -15)
|
||||||
rights = etree.fromstring(rights)
|
rights = etree.fromstring(rights)
|
||||||
expr = './/{http://ns.adobe.com/adept}user'
|
expr = './/{http://ns.adobe.com/adept}user'
|
||||||
|
@ -2066,14 +2118,14 @@ def adeptGetUserUUID(inf):
|
||||||
### My own code, for which there is none else to blame
|
### My own code, for which there is none else to blame
|
||||||
|
|
||||||
class PDFSerializer(object):
|
class PDFSerializer(object):
|
||||||
def __init__(self, inf, userkey):
|
def __init__(self, inf, userkey, inept=True):
|
||||||
global GEN_XREF_STM, gen_xref_stm
|
global GEN_XREF_STM, gen_xref_stm
|
||||||
gen_xref_stm = GEN_XREF_STM > 1
|
gen_xref_stm = GEN_XREF_STM > 1
|
||||||
self.version = inf.read(8)
|
self.version = inf.read(8)
|
||||||
inf.seek(0)
|
inf.seek(0)
|
||||||
self.doc = doc = PDFDocument()
|
self.doc = doc = PDFDocument()
|
||||||
parser = PDFParser(doc, inf)
|
parser = PDFParser(doc, inf)
|
||||||
doc.initialize(userkey)
|
doc.initialize(userkey, inept)
|
||||||
self.objids = objids = set()
|
self.objids = objids = set()
|
||||||
for xref in reversed(doc.xrefs):
|
for xref in reversed(doc.xrefs):
|
||||||
trailer = xref.trailer
|
trailer = xref.trailer
|
||||||
|
@ -2263,11 +2315,11 @@ class PDFSerializer(object):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def decryptBook(userkey, inpath, outpath):
|
def decryptBook(userkey, inpath, outpath, inept=True):
|
||||||
if RSA is None:
|
if RSA is None:
|
||||||
raise ADEPTError("PyCryptodome or OpenSSL must be installed.")
|
raise ADEPTError("PyCryptodome or OpenSSL must be installed.")
|
||||||
with open(inpath, 'rb') as inf:
|
with open(inpath, 'rb') as inf:
|
||||||
serializer = PDFSerializer(inf, userkey)
|
serializer = PDFSerializer(inf, userkey, inept)
|
||||||
with open(outpath, 'wb') as outf:
|
with open(outpath, 'wb') as outf:
|
||||||
# help construct to make sure the method runs to the end
|
# help construct to make sure the method runs to the end
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in New Issue