]> xmof Git - DeDRM.git/commitdiff
ineptpdf 7.5
authorApprentice Alf <apprenticealf@gmail.com>
Wed, 1 Dec 2010 07:16:54 +0000 (07:16 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Wed, 4 Mar 2015 07:17:08 +0000 (07:17 +0000)
ineptpdf.pyw

index cc3e6890175a2ed65d61f5ea3aa3d284fcf9541c..05f56a586f835a10ce36647de0a7a465660b0250 100644 (file)
@@ -1,7 +1,6 @@
 #! /usr/bin/python
 
-# ineptpdf74.pyw
-# ineptpdf, version 7.4
+# ineptpdf.pyw, version 7.5
 
 # To run this program install Python 2.6 from http://www.python.org/download/
 # and PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
 #   1 - Initial release
 #   2 - Improved determination of key-generation algorithm
 #   3 - Correctly handle PDF >=1.5 cross-reference streams
-#   4 - Removal of ciando's personal ID (anon)
-#   5 - removing small bug with V3 ebooks (anon)
-#   6 - changed to adeptkey4.der format for 1.7.2 support (anon)
+#   4 - Removal of ciando's personal ID
+#   5 - Automated decryption of a complete directory
 #   6.1 - backward compatibility for 1.7.1 and old adeptkey.der
 #   7 - Get cross reference streams and object streams working for input.
 #       Not yet supported on output but this only effects file size,
-#       not functionality. (by anon2)
+#       not functionality. (anon2)
 #   7.1 - Correct a problem when an old trailer is not followed by startxref
-#   7.2 - Correct malformed Mac OS resource forks for Stanza
+#   7.2 - Correct malformed Mac OS resource forks for Stanza (anon2)
 #       - Support for cross ref streams on output (decreases file size)
 #   7.3 - Correct bug in trailer with cross ref stream that caused the error
-#         "The root object is missing or invalid" in Adobe Reader.
+#         "The root object is missing or invalid" in Adobe Reader. (anon2)
 #   7.4 - Force all generation numbers in output file to be 0, like in v6.
 #         Fallback code for wrong xref improved (search till last trailer
-#         instead of first)
+#         instead of first) (anon2)
+#   7.5 - allow support for OpenSSL to replace pycrypto on all platforms
+#         implemented ARC4 interface to OpenSSL
+#         fixed minor typos
+
 """
 Decrypt Adobe ADEPT-encrypted PDF files.
 """
@@ -48,21 +50,233 @@ import Tkconstants
 import tkFileDialog
 import tkMessageBox
 
-try:
-    from Crypto.Cipher import ARC4
-    from Crypto.PublicKey import RSA
-except ImportError:
-    ARC4 = None
-    RSA = None
+
+class ADEPTError(Exception):
+    pass
+
+def _load_crypto_libcrypto():
+    from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
+        Structure, c_ulong, create_string_buffer, cast
+    from ctypes.util import find_library
+
+    if sys.platform.startswith('win'):
+        libcrypto = find_library('libeay32')
+    else:
+        libcrypto = find_library('crypto')
+
+    if libcrypto is None:
+        raise ADEPTError('libcrypto not found')
+    libcrypto = CDLL(libcrypto)
+
+    RSA_NO_PADDING = 3
+    
+    c_char_pp = POINTER(c_char_p)
+    c_int_p = POINTER(c_int)
+
+    class RC4_KEY(Structure):
+        _fields_ = [('x', c_int), ('y', c_int), ('box', c_int * 256)]
+    RC4_KEY_p = POINTER(RC4_KEY)
+
+    class RSA(Structure):
+        pass
+    RSA_p = POINTER(RSA)
+    
+    def F(restype, name, argtypes):
+        func = getattr(libcrypto, name)
+        func.restype = restype
+        func.argtypes = argtypes
+        return func
+    
+    d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
+                          [RSA_p, c_char_pp, c_long])
+    RSA_size = F(c_int, 'RSA_size', [RSA_p])
+    RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
+                            [c_int, c_char_p, c_char_p, RSA_p, c_int])
+    RSA_free = F(None, 'RSA_free', [RSA_p])
+
+    RC4_set_key = F(None,'RC4_set_key',[RC4_KEY_p, c_int, c_char_p])
+    RC4_crypt = F(None,'RC4',[RC4_KEY_p, c_int, c_char_p, c_char_p])
+
+    class RSA(object):
+        def __init__(self, der):
+            buf = create_string_buffer(der)
+            pp = c_char_pp(cast(buf, c_char_p))
+            rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
+            if rsa is None:
+                raise ADEPTError('Error parsing ADEPT user key DER')
+        
+        def decrypt(self, from_):
+            rsa = self._rsa
+            to = create_string_buffer(RSA_size(rsa))
+            dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
+                                       RSA_NO_PADDING)
+            if dlen < 0:
+                raise ADEPTError('RSA decryption failed')
+            return to[1:dlen]
+    
+        def __del__(self):
+            if self._rsa is not None:
+                RSA_free(self._rsa)
+                self._rsa = None
+
+    class ARC4(object):
+        @classmethod
+        def new(cls, userkey):
+            self = ARC4()
+            self._blocksize = len(userkey)
+            key = self._key = RC4_KEY()
+            RC4_set_key(key, self._blocksize, userkey)
+            return self
+        def __init__(self):
+            self._blocksize = 0
+            self._key = None
+        def decrypt(self, data):
+            out = create_string_buffer(len(data))
+            RC4_crypt(self._key, len(data), data, out)
+            return out.raw
+
+    return (ARC4, RSA)
+
+
+def _load_crypto_pycrypto():
+    from Crypto.PublicKey import RSA as _RSA
+    from Crypto.Cipher import ARC4 as _ARC4    
+
+    # ASN.1 parsing code from tlslite
+    class ASN1Error(Exception):
+        pass
+    
+    class ASN1Parser(object):
+        class Parser(object):
+            def __init__(self, bytes):
+                self.bytes = bytes
+                self.index = 0
+    
+            def get(self, length):
+                if self.index + length > len(self.bytes):
+                    raise ASN1Error("Error decoding ASN.1")
+                x = 0
+                for count in range(length):
+                    x <<= 8
+                    x |= self.bytes[self.index]
+                    self.index += 1
+                return x
+    
+            def getFixBytes(self, lengthBytes):
+                bytes = self.bytes[self.index : self.index+lengthBytes]
+                self.index += lengthBytes
+                return bytes
+    
+            def getVarBytes(self, lengthLength):
+                lengthBytes = self.get(lengthLength)
+                return self.getFixBytes(lengthBytes)
+    
+            def getFixList(self, length, lengthList):
+                l = [0] * lengthList
+                for x in range(lengthList):
+                    l[x] = self.get(length)
+                return l
+    
+            def getVarList(self, length, lengthLength):
+                lengthList = self.get(lengthLength)
+                if lengthList % length != 0:
+                    raise ASN1Error("Error decoding ASN.1")
+                lengthList = int(lengthList/length)
+                l = [0] * lengthList
+                for x in range(lengthList):
+                    l[x] = self.get(length)
+                return l
+    
+            def startLengthCheck(self, lengthLength):
+                self.lengthCheck = self.get(lengthLength)
+                self.indexCheck = self.index
+    
+            def setLengthCheck(self, length):
+                self.lengthCheck = length
+                self.indexCheck = self.index
+    
+            def stopLengthCheck(self):
+                if (self.index - self.indexCheck) != self.lengthCheck:
+                    raise ASN1Error("Error decoding ASN.1")
+    
+            def atLengthCheck(self):
+                if (self.index - self.indexCheck) < self.lengthCheck:
+                    return False
+                elif (self.index - self.indexCheck) == self.lengthCheck:
+                    return True
+                else:
+                    raise ASN1Error("Error decoding ASN.1")
+    
+        def __init__(self, bytes):
+            p = self.Parser(bytes)
+            p.get(1)
+            self.length = self._getASN1Length(p)
+            self.value = p.getFixBytes(self.length)
+    
+        def getChild(self, which):
+            p = self.Parser(self.value)
+            for x in range(which+1):
+                markIndex = p.index
+                p.get(1)
+                length = self._getASN1Length(p)
+                p.getFixBytes(length)
+            return ASN1Parser(p.bytes[markIndex:p.index])
+    
+        def _getASN1Length(self, p):
+            firstLength = p.get(1)
+            if firstLength<=127:
+                return firstLength
+            else:
+                lengthLength = firstLength & 0x7F
+                return p.get(lengthLength)
+
+    class ARC4(object):
+        @classmethod
+        def new(cls, userkey):
+            self = ARC4()
+            self._arc4 = _ARC4.new(userkey)
+            return self
+        def __init__(self):
+            self._arc4 = None
+        def decrypt(self, data):
+            return self._arc4.decrypt(data)
+
+    class RSA(object):
+        def __init__(self, der):
+            key = ASN1Parser([ord(x) for x in der])
+            key = [key.getChild(x).value for x in xrange(1, 4)]
+            key = [self.bytesToNumber(v) for v in key]
+            self._rsa = _RSA.construct(key)
+
+        def bytesToNumber(self, bytes):
+            total = 0L
+            for byte in bytes:
+                total = (total << 8) + byte
+            return total
+    
+        def decrypt(self, data):
+            return self._rsa.decrypt(data)
+
+    return (ARC4, RSA)
+
+def _load_crypto():
+    ARC4 = RSA = None
+    for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
+        try:
+            ARC4, RSA = loader()
+            break
+        except (ImportError, ADEPTError):
+            pass
+    return (ARC4, RSA)
+ARC4, RSA = _load_crypto()
+
+
 try:
     from cStringIO import StringIO
 except ImportError:
     from StringIO import StringIO
 
 
-class ADEPTError(Exception):
-    pass
-
 # Do we generate cross reference streams on output?
 # 0 = never
 # 1 = only if present in input
@@ -1208,9 +1422,7 @@ class PDFDocument(object):
         self.is_printable = self.is_modifiable = self.is_extractable = True
         with open(password, 'rb') as f:
             keyder = f.read()
-        key = ASN1Parser([ord(x) for x in keyder])
-        key = [bytesToNumber(key.getChild(x).value) for x in xrange(1, 4)]
-        rsa = RSA.construct(key)
+        rsa = RSA(keyder)
         length = int_value(param.get('Length', 0)) / 8
         rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
         rights = zlib.decompress(rights, -15)
@@ -1339,7 +1551,7 @@ class PDFDocument(object):
             if stmid:
                 if gen_xref_stm:
                     return PDFObjStmRef(objid, stmid, index)
-# Stuff from pdfminer: extract objects from object stream
+                # Stuff from pdfminer: extract objects from object stream
                 stream = stream_value(self.getobj(stmid))
                 if stream.dic.get('Type') is not LITERAL_OBJSTM:
                     if STRICT:
@@ -1371,7 +1583,6 @@ class PDFDocument(object):
                     raise PDFSyntaxError('Invalid object number: objid=%r' % (objid))
                 if isinstance(obj, PDFStream):
                     obj.set_objid(objid, 0)
-###
             else:
                 self.parser.seek(index)
                 (_,objid1) = self.parser.nexttoken() # objid
@@ -1784,8 +1995,8 @@ class PDFSerializer(object):
 
 def cli_main(argv=sys.argv):
     progname = os.path.basename(argv[0])
-    if RSA is None:
-        print "%s: This script requires PyCrypto, which must be installed " \
+    if RSA is None or ARC4 is None:
+        print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
               "separately.  Read the top-of-script comment for details." % \
               (progname,)
         return 1
@@ -1896,11 +2107,11 @@ class DecryptionDialog(Tkinter.Frame):
 
 def gui_main():
     root = Tkinter.Tk()
-    if RSA is None:
+    if RSA is None or ARC4 is None:
         root.withdraw()
         tkMessageBox.showerror(
             "INEPT PDF Decrypter",
-            "This script requires PyCrypto, which must be installed "
+            "This script requires OpenSSL or PyCrypto, which must be installed "
             "separately.  Read the top-of-script comment for details.")
         return 1
     root.title('INEPT PDF Decrypter')
@@ -1914,4 +2125,4 @@ def gui_main():
 if __name__ == '__main__':
     if len(sys.argv) > 1:
         sys.exit(cli_main())
-    sys.exit(gui_main())
\ No newline at end of file
+    sys.exit(gui_main())