Advertisement
Guest User

aineptepub 5.2

a guest
Oct 2nd, 2010
5,260
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 17.17 KB | None | 0 0
  1. #! /usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # Automated Inept Epub (bulk processing)
  5. #
  6. # aineptepub.pyw, version 5.2
  7. # Copyright © 2009-2010 i♥cabbages, Tetrachroma (bulk automation)
  8. #
  9. # Released under the terms of the GNU General Public Licence, version 3 or
  10. # later.  <http://www.gnu.org/licenses/>
  11.  
  12. # Windows users: Before running this program, you must first install Python 2.6
  13. #   from <http://www.python.org/download/> and PyCrypto from
  14. #   <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
  15. #   install the version for Python 2.6).  Save this script file as
  16. #   ineptepub.pyw and double-click on it to run it.
  17. #
  18. # Mac OS X users: Save this script file as ineptepub.pyw.  You can run this
  19. #   program from the command line (pythonw ineptepub.pyw) or by double-clicking
  20. #   it when it has been associated with PythonLauncher.
  21.  
  22. # Revision history:
  23. #   1 - Initial release
  24. #   2 - Rename to INEPT, fix exit code
  25. #   5 - Version bump to avoid (?) confusion;
  26. #       Improve OS X support by using OpenSSL when available
  27. #   5.1 - Improve OpenSSL error checking
  28. #   5.2 - Fix ctypes error causing segfaults on some systems
  29. #   5.2 Rev. 2 - added automated bulk processing (Tetrachroma)
  30.  
  31. """
  32. Automatically decrypt Adobe ADEPT-encrypted EPUB books in a directory.
  33. """
  34.  
  35. from __future__ import with_statement
  36.  
  37. __license__ = 'GPL v3'
  38.  
  39. import sys
  40. import os
  41. import zlib
  42. import zipfile
  43. from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
  44. from contextlib import closing
  45. import xml.etree.ElementTree as etree
  46. import Tkinter
  47. import Tkconstants
  48. import tkFileDialog
  49. import tkMessageBox
  50. import re
  51.  
  52. class ADEPTError(Exception):
  53.     pass
  54.  
  55. def _load_crypto_libcrypto():
  56.     from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
  57.         Structure, c_ulong, create_string_buffer, cast
  58.     from ctypes.util import find_library
  59.  
  60.     libcrypto = find_library('crypto')
  61.     if libcrypto is None:
  62.         raise ADEPTError('libcrypto not found')
  63.     libcrypto = CDLL(libcrypto)
  64.  
  65.     RSA_NO_PADDING = 3
  66.     AES_MAXNR = 14
  67.    
  68.     c_char_pp = POINTER(c_char_p)
  69.     c_int_p = POINTER(c_int)
  70.  
  71.     class RSA(Structure):
  72.         pass
  73.     RSA_p = POINTER(RSA)
  74.    
  75.     class AES_KEY(Structure):
  76.         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
  77.                     ('rounds', c_int)]
  78.     AES_KEY_p = POINTER(AES_KEY)
  79.    
  80.     def F(restype, name, argtypes):
  81.         func = getattr(libcrypto, name)
  82.         func.restype = restype
  83.         func.argtypes = argtypes
  84.         return func
  85.    
  86.     d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
  87.                           [RSA_p, c_char_pp, c_long])
  88.     RSA_size = F(c_int, 'RSA_size', [RSA_p])
  89.     RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
  90.                             [c_int, c_char_p, c_char_p, RSA_p, c_int])
  91.     RSA_free = F(None, 'RSA_free', [RSA_p])
  92.     AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
  93.                             [c_char_p, c_int, AES_KEY_p])
  94.     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
  95.                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
  96.                          c_int])
  97.    
  98.     class RSA(object):
  99.         def __init__(self, der):
  100.             buf = create_string_buffer(der)
  101.             pp = c_char_pp(cast(buf, c_char_p))
  102.             rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
  103.             if rsa is None:
  104.                 raise ADEPTError('Error parsing ADEPT user key DER')
  105.        
  106.         def decrypt(self, from_):
  107.             rsa = self._rsa
  108.             to = create_string_buffer(RSA_size(rsa))
  109.             dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
  110.                                        RSA_NO_PADDING)
  111.             if dlen < 0:
  112.                 raise ADEPTError('RSA decryption failed')
  113.             return to[:dlen]
  114.    
  115.         def __del__(self):
  116.             if self._rsa is not None:
  117.                 RSA_free(self._rsa)
  118.                 self._rsa = None
  119.  
  120.     class AES(object):
  121.         def __init__(self, userkey):
  122.             self._blocksize = len(userkey)
  123.             key = self._key = AES_KEY()
  124.             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
  125.             if rv < 0:
  126.                 raise ADEPTError('Failed to initialize AES key')
  127.    
  128.         def decrypt(self, data):
  129.             out = create_string_buffer(len(data))
  130.             iv = ("\x00" * self._blocksize)
  131.             rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
  132.             if rv == 0:
  133.                 raise ADEPTError('AES decryption failed')
  134.             return out.raw
  135.  
  136.     return (AES, RSA)
  137.  
  138. def _load_crypto_pycrypto():
  139.     from Crypto.Cipher import AES as _AES
  140.     from Crypto.PublicKey import RSA as _RSA
  141.  
  142.     # ASN.1 parsing code from tlslite
  143.     class ASN1Error(Exception):
  144.         pass
  145.    
  146.     class ASN1Parser(object):
  147.         class Parser(object):
  148.             def __init__(self, bytes):
  149.                 self.bytes = bytes
  150.                 self.index = 0
  151.    
  152.             def get(self, length):
  153.                 if self.index + length > len(self.bytes):
  154.                     raise ASN1Error("Error decoding ASN.1")
  155.                 x = 0
  156.                 for count in range(length):
  157.                     x <<= 8
  158.                     x |= self.bytes[self.index]
  159.                     self.index += 1
  160.                 return x
  161.    
  162.             def getFixBytes(self, lengthBytes):
  163.                 bytes = self.bytes[self.index : self.index+lengthBytes]
  164.                 self.index += lengthBytes
  165.                 return bytes
  166.    
  167.             def getVarBytes(self, lengthLength):
  168.                 lengthBytes = self.get(lengthLength)
  169.                 return self.getFixBytes(lengthBytes)
  170.    
  171.             def getFixList(self, length, lengthList):
  172.                 l = [0] * lengthList
  173.                 for x in range(lengthList):
  174.                     l[x] = self.get(length)
  175.                 return l
  176.    
  177.             def getVarList(self, length, lengthLength):
  178.                 lengthList = self.get(lengthLength)
  179.                 if lengthList % length != 0:
  180.                     raise ASN1Error("Error decoding ASN.1")
  181.                 lengthList = int(lengthList/length)
  182.                 l = [0] * lengthList
  183.                 for x in range(lengthList):
  184.                     l[x] = self.get(length)
  185.                 return l
  186.    
  187.             def startLengthCheck(self, lengthLength):
  188.                 self.lengthCheck = self.get(lengthLength)
  189.                 self.indexCheck = self.index
  190.    
  191.             def setLengthCheck(self, length):
  192.                 self.lengthCheck = length
  193.                 self.indexCheck = self.index
  194.    
  195.             def stopLengthCheck(self):
  196.                 if (self.index - self.indexCheck) != self.lengthCheck:
  197.                     raise ASN1Error("Error decoding ASN.1")
  198.    
  199.             def atLengthCheck(self):
  200.                 if (self.index - self.indexCheck) < self.lengthCheck:
  201.                     return False
  202.                 elif (self.index - self.indexCheck) == self.lengthCheck:
  203.                     return True
  204.                 else:
  205.                     raise ASN1Error("Error decoding ASN.1")
  206.    
  207.         def __init__(self, bytes):
  208.             p = self.Parser(bytes)
  209.             p.get(1)
  210.             self.length = self._getASN1Length(p)
  211.             self.value = p.getFixBytes(self.length)
  212.    
  213.         def getChild(self, which):
  214.             p = self.Parser(self.value)
  215.             for x in range(which+1):
  216.                 markIndex = p.index
  217.                 p.get(1)
  218.                 length = self._getASN1Length(p)
  219.                 p.getFixBytes(length)
  220.             return ASN1Parser(p.bytes[markIndex:p.index])
  221.    
  222.         def _getASN1Length(self, p):
  223.             firstLength = p.get(1)
  224.             if firstLength<=127:
  225.                 return firstLength
  226.             else:
  227.                 lengthLength = firstLength & 0x7F
  228.                 return p.get(lengthLength)
  229.  
  230.     class AES(object):
  231.         def __init__(self, key):
  232.             self._aes = _AES.new(key, _AES.MODE_CBC)
  233.  
  234.         def decrypt(self, data):
  235.             return self._aes.decrypt(data)
  236.  
  237.     class RSA(object):
  238.         def __init__(self, der):
  239.             key = ASN1Parser([ord(x) for x in der])
  240.             key = [key.getChild(x).value for x in xrange(1, 4)]
  241.             key = [self.bytesToNumber(v) for v in key]
  242.             self._rsa = _RSA.construct(key)
  243.  
  244.         def bytesToNumber(self, bytes):
  245.             total = 0L
  246.             for byte in bytes:
  247.                 total = (total << 8) + byte
  248.             return total
  249.    
  250.         def decrypt(self, data):
  251.             return self._rsa.decrypt(data)
  252.  
  253.     return (AES, RSA)
  254.  
  255. def _load_crypto():
  256.     AES = RSA = None
  257.     for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
  258.         try:
  259.             AES, RSA = loader()
  260.             break
  261.         except (ImportError, ADEPTError):
  262.             pass
  263.     return (AES, RSA)
  264. AES, RSA = _load_crypto()
  265.  
  266. META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
  267. NSMAP = {'adept': 'http://ns.adobe.com/adept',
  268.          'enc': 'http://www.w3.org/2001/04/xmlenc#'}
  269.  
  270. class ZipInfo(zipfile.ZipInfo):
  271.     def __init__(self, *args, **kwargs):
  272.         if 'compress_type' in kwargs:
  273.             compress_type = kwargs.pop('compress_type')
  274.         super(ZipInfo, self).__init__(*args, **kwargs)
  275.         self.compress_type = compress_type
  276.  
  277. class Decryptor(object):
  278.     def __init__(self, bookkey, encryption):
  279.         enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
  280.         self._aes = AES(bookkey)
  281.         encryption = etree.fromstring(encryption)
  282.         self._encrypted = encrypted = set()
  283.         expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
  284.                                enc('CipherReference'))
  285.         for elem in encryption.findall(expr):
  286.             path = elem.get('URI', None)
  287.             if path is not None:
  288.                 encrypted.add(path)
  289.  
  290.     def decompress(self, bytes):
  291.         dc = zlib.decompressobj(-15)
  292.         bytes = dc.decompress(bytes)
  293.         ex = dc.decompress('Z') + dc.flush()
  294.         if ex:
  295.             bytes = bytes + ex
  296.         return bytes
  297.  
  298.     def decrypt(self, path, data):
  299.         if path in self._encrypted:
  300.             data = self._aes.decrypt(data)[16:]
  301.             data = data[:-ord(data[-1])]
  302.             data = self.decompress(data)
  303.         return data
  304.  
  305. def cli_main(argv=sys.argv):
  306.     progname = os.path.basename(argv[0])
  307.     if AES is None:
  308.         print "%s: This script requires OpenSSL or PyCrypto, which must be" \
  309.               " installed separately.  Read the top-of-script comment for" \
  310.               " details." % (progname,)
  311.         return 1
  312.     if len(argv) != 4:
  313.         print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
  314.         return 1
  315.     keypath, inpath, outpath = argv[1:]
  316.     with open(keypath, 'rb') as f:
  317.         keyder = f.read()
  318.     rsa = RSA(keyder)
  319.     with closing(ZipFile(open(inpath, 'rb'))) as inf:
  320.         namelist = set(inf.namelist())
  321.         if 'META-INF/rights.xml' not in namelist or \
  322.            'META-INF/encryption.xml' not in namelist:
  323.             raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,))
  324.         for name in META_NAMES:
  325.             namelist.remove(name)
  326.         rights = etree.fromstring(inf.read('META-INF/rights.xml'))
  327.         adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
  328.         expr = './/%s' % (adept('encryptedKey'),)
  329.         bookkey = ''.join(rights.findtext(expr))
  330.         bookkey = rsa.decrypt(bookkey.decode('base64'))
  331.         # Padded as per RSAES-PKCS1-v1_5
  332.         if bookkey[-17] != '\x00':
  333.             raise ADEPTError('problem decrypting session key')
  334.         encryption = inf.read('META-INF/encryption.xml')
  335.         decryptor = Decryptor(bookkey[-16:], encryption)
  336.         kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
  337.         with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
  338.             zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
  339.             outf.writestr(zi, inf.read('mimetype'))
  340.             for path in namelist:
  341.                 data = inf.read(path)
  342.                 outf.writestr(path, decryptor.decrypt(path, data))
  343.     return 0
  344.  
  345. class DecryptionDialog(Tkinter.Frame):
  346.     def __init__(self, root):
  347.         Tkinter.Frame.__init__(self, root, border=5)
  348.         self.status = Tkinter.Label(self, text='Automated Inept epub V5.2 (Tetrachroma)')
  349.         self.status.pack(fill=Tkconstants.X, expand=1)
  350.         body = Tkinter.Frame(self)
  351.         body.pack(fill=Tkconstants.X, expand=1)
  352.         sticky = Tkconstants.E + Tkconstants.W
  353.         body.grid_columnconfigure(1, weight=2)
  354.         Tkinter.Label(body, text='Key file').grid(row=0)
  355.         self.keypath = Tkinter.Entry(body, width=35)
  356.         self.keypath.grid(row=0, column=1, sticky=sticky)
  357.         if os.path.exists('adeptkey.der'):
  358.             self.keypath.insert(0, 'adeptkey.der')
  359.         button = Tkinter.Button(body, text="...", command=self.get_keypath)
  360.         button.grid(row=0, column=2)
  361.         Tkinter.Label(body, text='Input Path').grid(row=1)
  362.         self.inpath = Tkinter.Entry(body, width=35)
  363.         self.inpath.grid(row=1, column=1, sticky=sticky)
  364.         button = Tkinter.Button(body, text="...", command=self.get_inpath)
  365.         button.grid(row=1, column=2)
  366.         Tkinter.Label(body, text='Output Path').grid(row=2)
  367.         self.outpath = Tkinter.Entry(body, width=35)
  368.         self.outpath.grid(row=2, column=1, sticky=sticky)
  369.         button = Tkinter.Button(body, text="...", command=self.get_outpath)
  370.         button.grid(row=2, column=2)
  371.         buttons = Tkinter.Frame(self)
  372.         buttons.pack()
  373.         botton = Tkinter.Button(
  374.             buttons, text="Decrypt whole directory", width=35, command=self.decrypt)
  375.         botton.pack(side=Tkconstants.LEFT)
  376.         Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
  377.         button = Tkinter.Button(
  378.             buttons, text="Quit", width=10, command=self.quit)
  379.         button.pack(side=Tkconstants.RIGHT)
  380.  
  381.     def get_keypath(self):
  382.         keypath = tkFileDialog.askopenfilename(
  383.             parent=None, title='Select ADEPT key file',
  384.             defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
  385.                                                 ('All Files', '.*')])
  386.         if keypath:
  387.             keypath = os.path.normpath(keypath)
  388.             self.keypath.delete(0, Tkconstants.END)
  389.             self.keypath.insert(0, keypath)
  390.         return
  391.  
  392.     def get_inpath(self):
  393.         inpath = tkFileDialog.askdirectory(parent=None, title='Select encrypted ePub source directory')
  394.         if inpath:
  395.             inpath = os.path.normpath(inpath)
  396.             self.inpath.delete(0, Tkconstants.END)
  397.             self.inpath.insert(0, inpath)
  398.         return
  399.  
  400.     def get_outpath(self):
  401.         outpath = tkFileDialog.askdirectory(parent=None, title='Select directory to decrypt to')
  402.         if outpath:
  403.             outpath = os.path.normpath(outpath)
  404.             self.outpath.delete(0, Tkconstants.END)
  405.             self.outpath.insert(0, outpath)
  406.         return
  407.  
  408.     def decrypt(self):
  409.         keypath = self.keypath.get()
  410.         inpath = self.inpath.get()
  411.         outpath = self.outpath.get()
  412.         if not keypath or not os.path.exists(keypath):
  413.             self.status['text'] = 'Specified key file does not exist'
  414.             return
  415.         if not inpath or not os.path.exists(inpath):
  416.             self.status['text'] = 'Specified input directory does not exist'
  417.             return
  418.         if not outpath:
  419.             self.status['text'] = 'Output directory not specified'
  420.             return
  421.         if inpath == outpath:
  422.             self.status['text'] = 'Must have different input and output directory'
  423.             return
  424.         dirlist = os.listdir(inpath)
  425.         #get regular expression match for epubs
  426.         match = r'.epub'
  427.         inpath=inpath+'//'
  428.         outpath=outpath+'//'
  429.         for fname in dirlist:
  430.              root, ext = os.path.splitext(fname)
  431.              if (os.path.isfile(os.path.normpath(inpath+fname)) == True) and re.match(match,ext,re.IGNORECASE):
  432.                 argv = [sys.argv[0], os.path.normpath(keypath), os.path.normpath(inpath+fname), os.path.normpath(outpath+root+".epub")]
  433.                 self.status['text'] = 'Decrypting:'+fname
  434.                 try:
  435.                     cli_main(argv)
  436.                 except Exception, e:
  437.                     self.status['text'] = 'Error in: '+ fname + str(e)
  438.                     return
  439.                 self.status['text'] = 'Decrypted directory '+inpath
  440.  
  441. def gui_main():
  442.     root = Tkinter.Tk()
  443.     if AES is None:
  444.         root.withdraw()
  445.         tkMessageBox.showerror(
  446.             "INEPT EPUB Decrypter",
  447.             "This script requires OpenSSL or PyCrypto, which must be"
  448.             " installed separately.  Read the top-of-script comment for"
  449.             " details.")
  450.         return 1
  451.     root.title('Automated INEPT EPUB Decrypter (Tetrachroma)')
  452.     root.resizable(True, False)
  453.     root.minsize(300, 0)
  454.     DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
  455.     root.mainloop()
  456.     return 0
  457.  
  458. if __name__ == '__main__':
  459.     if len(sys.argv) > 1:
  460.         sys.exit(cli_main())
  461.     sys.exit(gui_main())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement