]> xmof Git - DeDRM.git/commitdiff
Add B&N PDF DeDRM (untested), match UUID for Adobe PDFs
authorNoDRM <no_drm123@protonmail.com>
Tue, 16 Nov 2021 10:48:53 +0000 (11:48 +0100)
committerNoDRM <no_drm123@protonmail.com>
Tue, 16 Nov 2021 10:48:53 +0000 (11:48 +0100)
DeDRM_plugin/__init__.py
DeDRM_plugin/ignoblepdf.py
DeDRM_plugin/ineptpdf.py

index 9ee3a39f7cd5d25d23ab25c21a72fb2242863fb9..6fc4594ff873d8e15038aac88006d60d0412ce66 100644 (file)
@@ -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.
 
-        # 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():
             userkey = codecs.decode(userkeyhex,'hex')
             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:
                 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.
         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))
index 9f2c4dba6d22bf5c9388654617ab107ed6855754..1e6d66ae284e10b0d32dec7c05078e6aa9770479 100644 (file)
@@ -24,6 +24,7 @@ Decrypts Barnes & Noble encrypted PDF files.
 __license__ = 'GPL v3'
 __version__ = "0.3"
 
+import codecs
 import sys
 import os
 import re
@@ -312,7 +313,7 @@ class PSLiteral(PSObject):
     Use PSLiteralTable.intern() instead.
     '''
     def __init__(self, name):
-        self.name = name
+        self.name = name.decode('utf-8')
         return
 
     def __repr__(self):
@@ -1448,7 +1449,7 @@ class PDFDocument(object):
             self.decipher = self.decipher_rc4  # XXX may be AES
         # aes
         elif V == 4 and length == 128:
-            elf.decipher = self.decipher_aes
+            self.decipher = self.decipher_aes
         elif V == 4 and length == 256:
             raise PDFNotImplementedError('AES256 encryption is currently unsupported')
         self.ready = True
@@ -1491,7 +1492,7 @@ class PDFDocument(object):
             # 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]))
+            print("bookkey[0] is %d" % bookkey[0])
             if ebx_V == 3:
                 V = 3
             else:
index f8a5592322d46a37d0d04059d69433a93092d77d..51ca17e39b3566aab767808d204ce46a1d698244 100755 (executable)
@@ -1488,7 +1488,7 @@ class PDFDocument(object):
     #   Perform the initialization with a given password.
     #   This step is mandatory even if there's no password associated
     #   with the document.
-    def initialize(self, password=b''):
+    def initialize(self, password=b'', inept=True):
         if not self.encryption:
             self.is_printable = self.is_modifiable = self.is_extractable = True
             self.ready = True
@@ -1500,8 +1500,11 @@ class PDFDocument(object):
             return self.initialize_adobe_ps(password, docid, param)
         if type == 'Standard':
             return self.initialize_standard(password, docid, param)
-        if type == 'EBX_HANDLER':
-            return self.initialize_ebx(password, docid, param)
+        if type == 'EBX_HANDLER' and inept is True:
+            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)
 
     def initialize_adobe_ps(self, password, docid, param):
@@ -1642,7 +1645,52 @@ class PDFDocument(object):
 
         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
         rsa = RSA(password)
         length = int_value(param.get('Length', 0)) // 8
@@ -2042,15 +2090,19 @@ class PDFObjStrmParser(PDFParser):
 def adeptGetUserUUID(inf):
     try: 
         doc = PDFDocument()
+        inf = open(inf, 'rb')
         pars = PDFParser(doc, inf)
 
         (docid, param) = doc.encryption
         type = literal_name(param['Filter'])
         if type != 'EBX_HANDLER':
             # No EBX_HANDLER, no idea which user key can decrypt this.
+            inf.close()
             return None
 
         rights = codecs.decode(param.get('ADEPT_LICENSE'), 'base64')
+        inf.close()
+
         rights = zlib.decompress(rights, -15)
         rights = etree.fromstring(rights)
         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
 
 class PDFSerializer(object):
-    def __init__(self, inf, userkey):
+    def __init__(self, inf, userkey, inept=True):
         global GEN_XREF_STM, gen_xref_stm
         gen_xref_stm = GEN_XREF_STM > 1
         self.version = inf.read(8)
         inf.seek(0)
         self.doc = doc = PDFDocument()
         parser = PDFParser(doc, inf)
-        doc.initialize(userkey)
+        doc.initialize(userkey, inept)
         self.objids = objids = set()
         for xref in reversed(doc.xrefs):
             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:
         raise ADEPTError("PyCryptodome or OpenSSL must be installed.")
     with open(inpath, 'rb') as inf:
-        serializer = PDFSerializer(inf, userkey)
+        serializer = PDFSerializer(inf, userkey, inept)
         with open(outpath, 'wb') as outf:
             # help construct to make sure the method runs to the end
             try: