From 2c95633fcdecd5547252e3692cf4c207136492b5 Mon Sep 17 00:00:00 2001 From: Apprentice Alf Date: Wed, 16 May 2012 17:15:43 +0100 Subject: [PATCH] tools v5.1 alfcrypto added to DeDRM plugin --- .gitignore | 3 - .../K4MobiDeDRM_plugin/__init__.py | 7 +- Calibre_Plugins/K4MobiDeDRM_plugin/aescbc.py | 568 +++++++ .../K4MobiDeDRM_plugin/alfcrypto.dll | Bin 0 -> 70144 bytes .../K4MobiDeDRM_plugin/alfcrypto.py | 290 ++++ .../K4MobiDeDRM_plugin/alfcrypto64.dll | Bin 0 -> 52224 bytes .../K4MobiDeDRM_plugin/alfcrypto_src.zip | Bin 0 -> 17393 bytes .../K4MobiDeDRM_plugin/cmbtc_v2.2.py | 899 ++++++++++ .../K4MobiDeDRM_plugin/convert2xml.py | 1400 +++++++++------ .../K4MobiDeDRM_plugin/flatxml2html.py | Bin 70144 -> 29407 bytes .../K4MobiDeDRM_plugin/flatxml2svg.py | 529 +++--- Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py | Bin 52224 -> 25360 bytes .../K4MobiDeDRM_plugin/k4mobidedrm_orig.py | Bin 17393 -> 6469 bytes .../K4MobiDeDRM_plugin/kgenpids.py | 1057 +++--------- .../K4MobiDeDRM_plugin/libalfcrypto.dylib | Bin 0 -> 87160 bytes .../K4MobiDeDRM_plugin/libalfcrypto32.so | Bin 0 -> 23859 bytes .../K4MobiDeDRM_plugin/libalfcrypto64.so | Bin 0 -> 33417 bytes Calibre_Plugins/K4MobiDeDRM_plugin/pbkdf2.py | 68 + .../plugin-import-name-k4mobidedrm.txt | 726 -------- .../K4MobiDeDRM_plugin/scrolltextwidget.py | 27 + .../K4MobiDeDRM_plugin/stylexml2css.py | 900 +++------- .../K4MobiDeDRM_plugin/subasyncio.py | 148 ++ .../K4MobiDeDRM_plugin/topazextract.py | 559 ++++-- Calibre_Plugins/k4mobidedrm_plugin.zip | Bin 215853 -> 217095 bytes .../k4mobidedrm_plugin/k4mutils.py | 1503 ++++++++--------- .../k4mobidedrm_plugin/k4pcutils.py | 1255 +++++--------- .../k4mobidedrm_plugin/mobidedrm.py | 685 +++++--- DeDRM_Macintosh_Application/DeDRM.app.txt | Bin 107246 -> 112132 bytes .../DeDRM.app/Contents/Info.plist | 8 +- .../Contents/Resources/Scripts/main.scpt | Bin 236148 -> 239610 bytes .../Contents/Resources/alfcrypto_src.zip | Bin 0 -> 17393 bytes .../Contents/Resources/convert2xml.py | 52 +- .../Contents/Resources/flatxml2html.py | 49 +- .../Contents/Resources/flatxml2svg.py | 86 +- .../DeDRM.app/Contents/Resources/genbook.py | 217 ++- .../Contents/Resources/k4mobidedrm.py | 15 +- .../DeDRM.app/Contents/Resources/k4mutils.py | 2 +- .../DeDRM.app/Contents/Resources/k4pcutils.py | 31 +- .../DeDRM.app/Contents/Resources/mobidedrm.py | 8 +- .../Contents/Resources/topazextract.py | 15 +- .../ReadMe_DeDRM.app.rtf | 9 +- .../DeDRM_WinApp/DeDRM_lib/DeDRM_app.pyw | 29 +- .../DeDRM_WinApp/DeDRM_lib/lib/cmbtc_v2.2.py | 899 ++++++++++ .../DeDRM_WinApp/DeDRM_lib/lib/convert2xml.py | 1 + .../DeDRM_WinApp/DeDRM_lib/lib/genbook.py | 12 + .../DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py | 4 +- .../DeDRM_WinApp/DeDRM_lib/lib/k4pcutils.py | 7 + .../DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py | 8 +- .../Additional_Tools/FindTopazEbooks.pyw | 1 + Other_Tools/Additional_Tools/KindlePID.pyw | 22 +- Other_Tools/Additional_Tools/MobiDeDRM.pyw | 22 +- Other_Tools/KindleBooks/KindleBooks.pyw | 29 +- Other_Tools/KindleBooks/lib/convert2xml.py | 1 + Other_Tools/KindleBooks/lib/genbook.py | 12 + Other_Tools/KindleBooks/lib/k4mobidedrm.py | 4 +- Other_Tools/KindleBooks/lib/k4pcutils.py | 7 + Other_Tools/KindleBooks/lib/libalfcrypto32.so | Bin 0 -> 23859 bytes Other_Tools/KindleBooks/lib/libalfcrypto64.so | Bin 0 -> 33417 bytes Other_Tools/KindleBooks/lib/mobidedrm.py | 8 +- Other_Tools/ePub_Fixer/ePub_Fixer.pyw | 23 +- Other_Tools/eReader_PDB_Tools/Pml2HTML.pyw | 22 +- .../eReader_PDB_Tools/eReaderPDB2PML.pyw | 22 +- .../eReader_PDB_Tools/eReaderPDB2PMLZ.pyw | 23 +- 63 files changed, 6867 insertions(+), 5405 deletions(-) create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/aescbc.py create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto.dll create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto.py create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto64.dll create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto_src.zip create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/cmbtc_v2.2.py create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto.dylib create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto32.so create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto64.so create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/pbkdf2.py create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/scrolltextwidget.py create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/subasyncio.py create mode 100644 DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto_src.zip create mode 100644 DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/cmbtc_v2.2.py create mode 100644 Other_Tools/KindleBooks/lib/libalfcrypto32.so create mode 100644 Other_Tools/KindleBooks/lib/libalfcrypto64.so diff --git a/.gitignore b/.gitignore index 436624b..67cac1d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,6 @@ __pycache__/ *.pyc -# C extensions -*.so - # Distribution / packaging .Python env/ diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py b/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py index 233b462..a2afd66 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py @@ -19,8 +19,8 @@ class K4DeDRM(FileTypePlugin): description = 'Removes DRM from Mobipocket, Kindle/Mobi, Kindle/Topaz and Kindle/Print Replica files. Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on author = 'DiapDealer, SomeUpdates' # The author of this plugin - version = (0, 4, 1) # The version number of this plugin - file_types = set(['prc','mobi','azw','azw1','azw4','tpz']) # The file types that this plugin will be applied to + version = (0, 4, 2) # The version number of this plugin + file_types = set(['prc','mobi','azw','azw1','azw3','azw4','tpz']) # The file types that this plugin will be applied to on_import = True # Run this plugin during the import priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm minimum_calibre_version = (0, 7, 55) @@ -140,6 +140,9 @@ class K4DeDRM(FileTypePlugin): if mb.getPrintReplica(): of = self.temporary_file(bookname+'.azw4') print 'K4MobiDeDRM v%s: Print Replica format detected.' % plug_ver + elif mb.getMobiVersion() >= 8: + print 'K4MobiDeDRM v%s: Stand-alone KF8 format detected.' % plug_ver + of = self.temporary_file(bookname+'.azw3') else: of = self.temporary_file(bookname+'.mobi') mb.getMobiFile(of.name) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/aescbc.py b/Calibre_Plugins/K4MobiDeDRM_plugin/aescbc.py new file mode 100644 index 0000000..5667511 --- /dev/null +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/aescbc.py @@ -0,0 +1,568 @@ +#! /usr/bin/env python + +""" + Routines for doing AES CBC in one file + + Modified by some_updates to extract + and combine only those parts needed for AES CBC + into one simple to add python file + + Original Version + Copyright (c) 2002 by Paul A. Lambert + Under: + CryptoPy Artisitic License Version 1.0 + See the wonderful pure python package cryptopy-1.2.5 + and read its LICENSE.txt for complete license details. +""" + +class CryptoError(Exception): + """ Base class for crypto exceptions """ + def __init__(self,errorMessage='Error!'): + self.message = errorMessage + def __str__(self): + return self.message + +class InitCryptoError(CryptoError): + """ Crypto errors during algorithm initialization """ +class BadKeySizeError(InitCryptoError): + """ Bad key size error """ +class EncryptError(CryptoError): + """ Error in encryption processing """ +class DecryptError(CryptoError): + """ Error in decryption processing """ +class DecryptNotBlockAlignedError(DecryptError): + """ Error in decryption processing """ + +def xorS(a,b): + """ XOR two strings """ + assert len(a)==len(b) + x = [] + for i in range(len(a)): + x.append( chr(ord(a[i])^ord(b[i]))) + return ''.join(x) + +def xor(a,b): + """ XOR two strings """ + x = [] + for i in range(min(len(a),len(b))): + x.append( chr(ord(a[i])^ord(b[i]))) + return ''.join(x) + +""" + Base 'BlockCipher' and Pad classes for cipher instances. + BlockCipher supports automatic padding and type conversion. The BlockCipher + class was written to make the actual algorithm code more readable and + not for performance. +""" + +class BlockCipher: + """ Block ciphers """ + def __init__(self): + self.reset() + + def reset(self): + self.resetEncrypt() + self.resetDecrypt() + def resetEncrypt(self): + self.encryptBlockCount = 0 + self.bytesToEncrypt = '' + def resetDecrypt(self): + self.decryptBlockCount = 0 + self.bytesToDecrypt = '' + + def encrypt(self, plainText, more = None): + """ Encrypt a string and return a binary string """ + self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt + numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize) + cipherText = '' + for i in range(numBlocks): + bStart = i*self.blockSize + ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize]) + self.encryptBlockCount += 1 + cipherText += ctBlock + if numExtraBytes > 0: # save any bytes that are not block aligned + self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + else: + self.bytesToEncrypt = '' + + if more == None: # no more data expected from caller + finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize) + if len(finalBytes) > 0: + ctBlock = self.encryptBlock(finalBytes) + self.encryptBlockCount += 1 + cipherText += ctBlock + self.resetEncrypt() + return cipherText + + def decrypt(self, cipherText, more = None): + """ Decrypt a string and return a string """ + self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt + + numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) + if more == None: # no more calls to decrypt, should have all the data + if numExtraBytes != 0: + raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt' + + # hold back some bytes in case last decrypt has zero len + if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : + numBlocks -= 1 + numExtraBytes = self.blockSize + + plainText = '' + for i in range(numBlocks): + bStart = i*self.blockSize + ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize]) + self.decryptBlockCount += 1 + plainText += ptBlock + + if numExtraBytes > 0: # save any bytes that are not block aligned + self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + else: + self.bytesToEncrypt = '' + + if more == None: # last decrypt remove padding + plainText = self.padding.removePad(plainText, self.blockSize) + self.resetDecrypt() + return plainText + + +class Pad: + def __init__(self): + pass # eventually could put in calculation of min and max size extension + +class padWithPadLen(Pad): + """ Pad a binary string with the length of the padding """ + + def addPad(self, extraBytes, blockSize): + """ Add padding to a binary string to make it an even multiple + of the block size """ + blocks, numExtraBytes = divmod(len(extraBytes), blockSize) + padLength = blockSize - numExtraBytes + return extraBytes + padLength*chr(padLength) + + def removePad(self, paddedBinaryString, blockSize): + """ Remove padding from a binary string """ + if not(0 6 and i%Nk == 4 : + temp = [ Sbox[byte] for byte in temp ] # SubWord(temp) + w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] ) + return w + +Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!! + 0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6, + 0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91) + +#------------------------------------- +def AddRoundKey(algInstance, keyBlock): + """ XOR the algorithm state with a block of key material """ + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] ^= keyBlock[column][row] +#------------------------------------- + +def SubBytes(algInstance): + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] = Sbox[algInstance.state[column][row]] + +def InvSubBytes(algInstance): + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] = InvSbox[algInstance.state[column][row]] + +Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5, + 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, + 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0, + 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, + 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc, + 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, + 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a, + 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, + 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0, + 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, + 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b, + 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, + 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85, + 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, + 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5, + 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, + 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17, + 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, + 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88, + 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, + 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c, + 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, + 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9, + 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, + 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6, + 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, + 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e, + 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, + 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94, + 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, + 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68, + 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16) + +InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, + 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, + 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, + 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, + 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, + 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, + 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, + 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, + 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, + 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, + 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, + 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, + 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, + 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, + 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, + 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, + 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, + 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, + 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, + 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, + 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, + 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, + 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, + 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, + 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, + 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, + 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, + 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, + 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, + 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, + 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, + 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d) + +#------------------------------------- +""" For each block size (Nb), the ShiftRow operation shifts row i + by the amount Ci. Note that row 0 is not shifted. + Nb C1 C2 C3 + ------------------- """ +shiftOffset = { 4 : ( 0, 1, 2, 3), + 5 : ( 0, 1, 2, 3), + 6 : ( 0, 1, 2, 3), + 7 : ( 0, 1, 2, 4), + 8 : ( 0, 1, 3, 4) } +def ShiftRows(algInstance): + tmp = [0]*algInstance.Nb # list of size Nb + for r in range(1,4): # row 0 reamains unchanged and can be skipped + for c in range(algInstance.Nb): + tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] + for c in range(algInstance.Nb): + algInstance.state[c][r] = tmp[c] +def InvShiftRows(algInstance): + tmp = [0]*algInstance.Nb # list of size Nb + for r in range(1,4): # row 0 reamains unchanged and can be skipped + for c in range(algInstance.Nb): + tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] + for c in range(algInstance.Nb): + algInstance.state[c][r] = tmp[c] +#------------------------------------- +def MixColumns(a): + Sprime = [0,0,0,0] + for j in range(a.Nb): # for each column + Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3]) + Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3]) + Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3]) + Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3]) + for i in range(4): + a.state[j][i] = Sprime[i] + +def InvMixColumns(a): + """ Mix the four bytes of every column in a linear way + This is the opposite operation of Mixcolumn """ + Sprime = [0,0,0,0] + for j in range(a.Nb): # for each column + Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3]) + Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3]) + Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3]) + Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3]) + for i in range(4): + a.state[j][i] = Sprime[i] + +#------------------------------------- +def mul(a, b): + """ Multiply two elements of GF(2^m) + needed for MixColumn and InvMixColumn """ + if (a !=0 and b!=0): + return Alogtable[(Logtable[a] + Logtable[b])%255] + else: + return 0 + +Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, + 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, + 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, + 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, + 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, + 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, + 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, + 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, + 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, + 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, + 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, + 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, + 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, + 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, + 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, + 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7) + +Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, + 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, + 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, + 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, + 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, + 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, + 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, + 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, + 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, + 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, + 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, + 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, + 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, + 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, + 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, + 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1) + + + + +""" + AES Encryption Algorithm + The AES algorithm is just Rijndael algorithm restricted to the default + blockSize of 128 bits. +""" + +class AES(Rijndael): + """ The AES algorithm is the Rijndael block cipher restricted to block + sizes of 128 bits and key sizes of 128, 192 or 256 bits + """ + def __init__(self, key = None, padding = padWithPadLen(), keySize=16): + """ Initialize AES, keySize is in bytes """ + if not (keySize == 16 or keySize == 24 or keySize == 32) : + raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes' + + Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 ) + + self.name = 'AES' + + +""" + CBC mode of encryption for block ciphers. + This algorithm mode wraps any BlockCipher to make a + Cipher Block Chaining mode. +""" +from random import Random # should change to crypto.random!!! + + +class CBC(BlockCipher): + """ The CBC class wraps block ciphers to make cipher block chaining (CBC) mode + algorithms. The initialization (IV) is automatic if set to None. Padding + is also automatic based on the Pad class used to initialize the algorithm + """ + def __init__(self, blockCipherInstance, padding = padWithPadLen()): + """ CBC algorithms are created by initializing with a BlockCipher instance """ + self.baseCipher = blockCipherInstance + self.name = self.baseCipher.name + '_CBC' + self.blockSize = self.baseCipher.blockSize + self.keySize = self.baseCipher.keySize + self.padding = padding + self.baseCipher.padding = noPadding() # baseCipher should NOT pad!! + self.r = Random() # for IV generation, currently uses + # mediocre standard distro version <---------------- + import time + newSeed = time.ctime()+str(self.r) # seed with instance location + self.r.seed(newSeed) # to make unique + self.reset() + + def setKey(self, key): + self.baseCipher.setKey(key) + + # Overload to reset both CBC state and the wrapped baseCipher + def resetEncrypt(self): + BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class) + self.baseCipher.resetEncrypt() # reset base cipher encrypt state + + def resetDecrypt(self): + BlockCipher.resetDecrypt(self) # reset CBC state (super class) + self.baseCipher.resetDecrypt() # reset base cipher decrypt state + + def encrypt(self, plainText, iv=None, more=None): + """ CBC encryption - overloads baseCipher to allow optional explicit IV + when iv=None, iv is auto generated! + """ + if self.encryptBlockCount == 0: + self.iv = iv + else: + assert(iv==None), 'IV used only on first call to encrypt' + + return BlockCipher.encrypt(self,plainText, more=more) + + def decrypt(self, cipherText, iv=None, more=None): + """ CBC decryption - overloads baseCipher to allow optional explicit IV + when iv=None, iv is auto generated! + """ + if self.decryptBlockCount == 0: + self.iv = iv + else: + assert(iv==None), 'IV used only on first call to decrypt' + + return BlockCipher.decrypt(self, cipherText, more=more) + + def encryptBlock(self, plainTextBlock): + """ CBC block encryption, IV is set with 'encrypt' """ + auto_IV = '' + if self.encryptBlockCount == 0: + if self.iv == None: + # generate IV and use + self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)]) + self.prior_encr_CT_block = self.iv + auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic + else: # application provided IV + assert(len(self.iv) == self.blockSize ),'IV must be same length as block' + self.prior_encr_CT_block = self.iv + """ encrypt the prior CT XORed with the PT """ + ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) ) + self.prior_encr_CT_block = ct + return auto_IV+ct + + def decryptBlock(self, encryptedBlock): + """ Decrypt a single block """ + + if self.decryptBlockCount == 0: # first call, process IV + if self.iv == None: # auto decrypt IV? + self.prior_CT_block = encryptedBlock + return '' + else: + assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption" + self.prior_CT_block = self.iv + + dct = self.baseCipher.decryptBlock(encryptedBlock) + """ XOR the prior decrypted CT with the prior CT """ + dct_XOR_priorCT = xor( self.prior_CT_block, dct ) + + self.prior_CT_block = encryptedBlock + + return dct_XOR_priorCT + + +""" + AES_CBC Encryption Algorithm +""" + +class AES_CBC(CBC): + """ AES encryption in CBC feedback mode """ + def __init__(self, key=None, padding=padWithPadLen(), keySize=16): + CBC.__init__( self, AES(key, noPadding(), keySize), padding) + self.name = 'AES_CBC' diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto.dll b/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto.dll new file mode 100644 index 0000000000000000000000000000000000000000..26d740ddb0ed3be82128b3fbe0e45471b0e04f4b GIT binary patch literal 70144 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 G!2bbGSZYrI literal 0 HcmV?d00001 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto.py b/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto.py new file mode 100644 index 0000000..e25a0c8 --- /dev/null +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto.py @@ -0,0 +1,290 @@ +#! /usr/bin/env python + +import sys, os +import hmac +from struct import pack +import hashlib + + +# interface to needed routines libalfcrypto +def _load_libalfcrypto(): + import ctypes + from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ + Structure, c_ulong, create_string_buffer, addressof, string_at, cast, sizeof + + pointer_size = ctypes.sizeof(ctypes.c_voidp) + name_of_lib = None + if sys.platform.startswith('darwin'): + name_of_lib = 'libalfcrypto.dylib' + elif sys.platform.startswith('win'): + if pointer_size == 4: + name_of_lib = 'alfcrypto.dll' + else: + name_of_lib = 'alfcrypto64.dll' + else: + if pointer_size == 4: + name_of_lib = 'libalfcrypto32.so' + else: + name_of_lib = 'libalfcrypto64.so' + + libalfcrypto = sys.path[0] + os.sep + name_of_lib + + if not os.path.isfile(libalfcrypto): + raise Exception('libalfcrypto not found') + + libalfcrypto = CDLL(libalfcrypto) + + c_char_pp = POINTER(c_char_p) + c_int_p = POINTER(c_int) + + + def F(restype, name, argtypes): + func = getattr(libalfcrypto, name) + func.restype = restype + func.argtypes = argtypes + return func + + # aes cbc decryption + # + # struct aes_key_st { + # unsigned long rd_key[4 *(AES_MAXNR + 1)]; + # int rounds; + # }; + # + # typedef struct aes_key_st AES_KEY; + # + # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); + # + # + # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, + # const unsigned long length, const AES_KEY *key, + # unsigned char *ivec, const int enc); + + AES_MAXNR = 14 + + class AES_KEY(Structure): + _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)] + + AES_KEY_p = POINTER(AES_KEY) + AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, c_int]) + AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p]) + + + + # Pukall 1 Cipher + # unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src, + # unsigned char *dest, unsigned int len, int decryption); + + PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong]) + + # Topaz Encryption + # typedef struct _TpzCtx { + # unsigned int v[2]; + # } TpzCtx; + # + # void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen); + # void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len); + + class TPZ_CTX(Structure): + _fields_ = [('v', c_long * 2)] + + TPZ_CTX_p = POINTER(TPZ_CTX) + topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong]) + topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong]) + + + class AES_CBC(object): + def __init__(self): + self._blocksize = 0 + self._keyctx = None + self._iv = 0 + + def set_decrypt_key(self, userkey, iv): + self._blocksize = len(userkey) + if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : + raise Exception('AES CBC improper key used') + return + keyctx = self._keyctx = AES_KEY() + self._iv = iv + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) + if rv < 0: + raise Exception('Failed to initialize AES CBC key') + + def decrypt(self, data): + out = create_string_buffer(len(data)) + mutable_iv = create_string_buffer(self._iv, len(self._iv)) + rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0) + if rv == 0: + raise Exception('AES CBC decryption failed') + return out.raw + + class Pukall_Cipher(object): + def __init__(self): + self.key = None + + def PC1(self, key, src, decryption=True): + self.key = key + out = create_string_buffer(len(src)) + de = 0 + if decryption: + de = 1 + rv = PC1(key, len(key), src, out, len(src), de) + return out.raw + + class Topaz_Cipher(object): + def __init__(self): + self._ctx = None + + def ctx_init(self, key): + tpz_ctx = self._ctx = TPZ_CTX() + topazCryptoInit(tpz_ctx, key, len(key)) + return tpz_ctx + + def decrypt(self, data, ctx=None): + if ctx == None: + ctx = self._ctx + out = create_string_buffer(len(data)) + topazCryptoDecrypt(ctx, data, out, len(data)) + return out.raw + + print "Using Library AlfCrypto DLL/DYLIB/SO" + return (AES_CBC, Pukall_Cipher, Topaz_Cipher) + + +def _load_python_alfcrypto(): + + import aescbc + + class Pukall_Cipher(object): + def __init__(self): + self.key = None + + def PC1(self, key, src, decryption=True): + sum1 = 0; + sum2 = 0; + keyXorVal = 0; + if len(key)!=16: + print "Bad key length!" + return None + wkey = [] + for i in xrange(8): + wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) + dst = "" + for i in xrange(len(src)): + temp1 = 0; + byteXorVal = 0; + for j in xrange(8): + temp1 ^= wkey[j] + sum2 = (sum2+j)*20021 + sum1 + sum1 = (temp1*346)&0xFFFF + sum2 = (sum2+sum1)&0xFFFF + temp1 = (temp1*20021+1)&0xFFFF + byteXorVal ^= temp1 ^ sum2 + curByte = ord(src[i]) + if not decryption: + keyXorVal = curByte * 257; + curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF + if decryption: + keyXorVal = curByte * 257; + for j in xrange(8): + wkey[j] ^= keyXorVal; + dst+=chr(curByte) + return dst + + class Topaz_Cipher(object): + def __init__(self): + self._ctx = None + + def ctx_init(self, key): + ctx1 = 0x0CAFFE19E + for keyChar in key: + keyByte = ord(keyChar) + ctx2 = ctx1 + ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF ) + self._ctx = [ctx1, ctx2] + return [ctx1,ctx2] + + def decrypt(self, data, ctx=None): + if ctx == None: + ctx = self._ctx + ctx1 = ctx[0] + ctx2 = ctx[1] + plainText = "" + for dataChar in data: + dataByte = ord(dataChar) + m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF + ctx2 = ctx1 + ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF) + plainText += chr(m) + return plainText + + class AES_CBC(object): + def __init__(self): + self._key = None + self._iv = None + self.aes = None + + def set_decrypt_key(self, userkey, iv): + self._key = userkey + self._iv = iv + self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey)) + + def decrypt(self, data): + iv = self._iv + cleartext = self.aes.decrypt(iv + data) + return cleartext + + return (AES_CBC, Pukall_Cipher, Topaz_Cipher) + + +def _load_crypto(): + AES_CBC = Pukall_Cipher = Topaz_Cipher = None + cryptolist = (_load_libalfcrypto, _load_python_alfcrypto) + for loader in cryptolist: + try: + AES_CBC, Pukall_Cipher, Topaz_Cipher = loader() + break + except (ImportError, Exception): + pass + return AES_CBC, Pukall_Cipher, Topaz_Cipher + +AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto() + + +class KeyIVGen(object): + # this only exists in openssl so we will use pure python implementation instead + # PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', + # [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) + def pbkdf2(self, passwd, salt, iter, keylen): + + def xorstr( a, b ): + if len(a) != len(b): + raise Exception("xorstr(): lengths differ") + return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b))) + + def prf( h, data ): + hm = h.copy() + hm.update( data ) + return hm.digest() + + def pbkdf2_F( h, salt, itercount, blocknum ): + U = prf( h, salt + pack('>i',blocknum ) ) + T = U + for i in range(2, itercount+1): + U = prf( h, U ) + T = xorstr( T, U ) + return T + + sha = hashlib.sha1 + digest_size = sha().digest_size + # l - number of output blocks to produce + l = keylen / digest_size + if keylen % digest_size != 0: + l += 1 + h = hmac.new( passwd, None, sha ) + T = "" + for i in range(1, l+1): + T += pbkdf2_F( h, salt, iter, i ) + return T[0: keylen] + + diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto64.dll b/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto64.dll new file mode 100644 index 0000000000000000000000000000000000000000..7bef68eac0d0a751b0c7090f5b5427a1d5ab1a59 GIT binary patch literal 52224 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 NdK>>N@IQ70{vUg3cj*8C literal 0 HcmV?d00001 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto_src.zip b/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto_src.zip new file mode 100644 index 0000000000000000000000000000000000000000..269810cfa24541f8be10050e7192fb6211a45a98 GIT binary patch literal 17393 zcma)j2RxPi`@g;89EFB)+$c(vWF#4#LWv}MWoL(s%uu;ykI0CU8Iql>3S}gFg_JET zJNthfeShEYGk)La|Lb|3<2ky|bzh(Bb6xN2eZ4>3S1*&2(GU^-u-ceO@BYuf-sp)~ zi43jHj2-UWx@~9R=wK|UcAc7tR8(7C*@^ISr6nRJTP7wVBHIVwxC(X%KdFeMO-!U6 z9dQLMLPSL8GKh#+{$|I})X~7$$XLL5*C3j%s!VwO#o*`@ok+Hl@TW=j2KHz7et*i{ zAV5TZ{SlwI(Z}aKJwIosYkItX9_6N_VaeoqkvU=8pdPe5D4JE7P0`~i&)#gxVHO`< z|KmN+sqol32DS8!x!jlSjoqaQ+XB&jQmLo6hNi0A*H=G^N|jbL3g&hhcx+A0y3el^ z_xIZjy02GfWqGXIC2iSG{YrOuV(YcJJ*(yB=IQCa-04)GzT{Lc?&<2ZwfVh7 zzoOE8d#OIMeP#J+bE8JL!4v)HsodF0ua~RET3#Ey9LgMCI$q^-E4LyzE}i*epf~sg zpUj}Nv%WMLDN0%CHUCpWsB=>F$*ZbT2KUv0)g-%uo$8&8$ur2F*b>VPe$epJkIrur@x!HQRpCFNb~92dPoI4cpw*@Yoy z7TQIgL)KI_m0>2mexVx!nXiBRsEbfDNI$bDNUV(8z}RNXO!Q6bsr#a>at8A;)(-{0 z+N;D^zBn*urYsR#&(H6qyU71x4AC@CnAg=jS-M%)B>JhE*ZZ;E{7_B~dc!E|b z%qp+;%~%p&Oi^}p+Vh)MwR=w5GAS1aI0@P>L>k0-eV}iAwg2MjJI)0-vV2E#VG9bug=`m9KU-ybP@DFzw#>|^QcuI5W;<1XD^XVtr{7JT`}j}fnz3+cVJYW6IL?2lVT zmybNcB~r}W&ohn)N7!z*%rGda^PB4ZP$+&Bb}L7dC-2fPT>7K8!q4h6B?8t1V`efB zUXO_4?X{7fC^Cz;xw$PAA7z?~ZTgg~EMysjvru66edNUR$V8|ArI3Hjo`8v^#zI9E z)v>b;8b){>?x`!b7G8&)m7jMXfA^73>5$V3*KUS2c~$-$Uit4-4{N+busVNBQ{cTx!%&D zd(;C*sjbjYqS=cpon3}Xh?)Fjec=ef%XJDqCoYpeyW=JD9J^!ZC8Dqyqp<2i%`M1= zJwrG3>fl+vvGOq|OZ%v9gs+b?xF%|vT0gRnxALIeJ(1@xyRQd$jD=TaU{T zlic0NQt{St)0>M;oaFVfni6Z@&UcDM&^!wvbvT@<_i*IFn0drRYJ&8}nTI-;4;nsH z(2n*^Fbk8sp75%+%p~!&S&o#yMPkj9i6hmG+LjlhOv;W&+N?yqJzbBaCvlJaztD-9 zzB%t=jYA1(wQ;Y(F4${A!dxvFKSXjen+m^K+U z-jrwmM#kakriF8>gY4qV6s(rb9(yi{80DK`DfBT5*`v*-VV>8S!&HhaKgdT$bsM&9 zi3F^&v`-^WiPVQ*1mb($D?Z-KXHu9^IlVtdZF{76)-0}?l{x1NOJ2_Wk8C5J$m1*f zR{5_{m3TzQ7(JX`e|&2|g!nmUr9gpezP=`x!OKxr@frSC3J!wf{!6rY$Rg9Mw)H;hO3`r&%*aXRM zKlyT|&a9|5K}0WwYA-7N`AUUp*QbN2G<9k9d`CsN%odk7-i1XLnKVRbtV%mu+#2Lh{Su@UI zzD9aURftidW1P5)49JUVT(TQAnvu$k_Rz)1)NrZHqOK;-*|1Jl#8{nYnV&;~+0YDq z5`Aq%WHDEd!T4MufpWBSle34NTr;DqM0JEkup&o}skYVz2HfRvWGsO6s zd*t{IUmE=MTSk1%g^o+Fbb7!V;#5-fEH8Iqht zjEzq?0RBQ0CWsJ+?_gj_8sF9Ii`I$zqON!{STrT}WQBT@?*85A<>mO1Vp}VH(ft<0 z*w!0F@O=t=${-zL^pF}~)69&?BXz=HEm%=`RZ3)NgdRW5pzeYzMF**z`au#F^o-^zIrQ=y6D#GR{b+iV8`tAjWE~62Zcu zj@R0K(0(>)JU(6;KXwY%^phAt2)p@%2tgFcu!A({3MbB1nL!laahDjYh6B4yc>nGrdH71XVhyq_Da0<<6 zB}ImQLH)lK@kQe{WboG=AX8Y;p0`#M*pnBCu%Xsv8jbw{09kf`C|ht5MjC(IP`XC_ z#7lrEh$e3d4hfuu4V4G5D}fqNfF#Ak{#?e)^Imtx*JyE}bu@c$MyuQye9h7^)RjAe z4STxG54ADYlg1ai;1CCO40g{&0G)AZeA`x^=#0HULxpoSFfT7be}Uj^g*= zrfV$?(w2J{y;*q|wIL0_A)JZQ_<`dx_>0+)hg6UO$B#lh&!H&AF+d$h9I|MNQCYo) zQK6;BPft)HLKh%UeW3W3AoyMs_>q^SSgdxlZCHUXI zwR7e!>LbnvAVRFtH6numYHOrOz6m+LMjrCaOVSs8Z9;*KH3Imr@j(YOF7; zKbld5LF;f>{4~u)XhD`dQP3c`6=2yoS_BCu#cF*bLn6YDqdiRDaLC~$sNI85F?LX| zUVBmbl~@`45+V5;aYo0i0V4@HG(ZbjNsIPu0yN#^kj4+)B*S9Q5F)jA7oY3)!Lr&; zf!DCCrFby+&JWdvrc>)qf{jJ)qQ80pSn`Mv5%BIHAp0SPRd z4n<+d&~ZQs30~Mr8w#w&89$WHNV?|mI0>K}tbm#p;sS_2bdwx=py)0*3aIZ0&Agu( zP{I|1?T&*;LFaR6Awh=BDexwh(COHqk4gR{#)cohi`D>$YOTWdP1!;k8$gKy$}Xxw z8jC{v7xhK?muYs_S;>SMFqS66285F#4kvw3UC2{P6o**8#33xu6FQ+M5F?*M;>wv* zisPj5*?GPw8xFodOorrJLJA}h{!`mmlvjx&PxkPaobhkx)z z#hQtbku&uD!`B3MCZ);@ZkS!V%9fRzI&+;h5 zhR+NWj{Vo)GefYKuwaepx3x+-h!4aA)=h}d0Vm>r!vbM{RJk$Xm<({H`S*UgfHN&_ z&VJy~t}_oIJZp4Fvd{0YYvmImLuax0nn}9d+t+0PWqR)eX$Rm);A_xsWIy`=YarRZ zT`M4JUL(~mNbhcEGi*gYxE~HlMp({eaGlU^WdM(fkv}5>;qQw8)boKlpipas&j1K@ zMo?0+p}+n8BOVYO$cAPpfc%C7LVCQ^rZxpqpa1|v__{DX-sB_{{l((nU;iT-5W-V` z4gdg9q0Zv~ewx@`4nG2LF{Lk!zo-8@{R$ayh`lf{A-3NOE+oWo9|n8*U-8?5^*H^6 z^aOH9*vy9zFLPK!Ey-^O^av;mpS^+nL3$BTSDp&Cl;vNW53#Ak0RV0N>-x?@OEWeS zwC)MCP)J{)Bmjv|5W|Jv_G>Jm#{rtuIQ~ut^yVp4zWo5+1Vg%e{hwd}0#KI$jA@Vz z8A|%s^?YYh)CxFG#C1#_Wepp4{uC)9G)TY?2mmYqA4h~RdNTten*S?XfnbQAf&l$a z5PTIZG80H^AUOsrefB)M*6NQj-HGA3x}j(){O(vRdit;-P5PudZwmha7cce9t@29K z9}#l7f<7OwKTj&!PyLoQ_iqtZ9wm?d;4O?}~WPR*^lMGP}HL zXC0hLFLyw&;|RIWEZLOv)vs(SdxT0m=+~(lEFV9n&3) zTW5p2Celt`qG9;h=jn)z=x_?9jO6r`)g|bf^j`})m`QOVI3u0EOy(}W+FQKd-aW~)l)z>RUTsJa9Q-@j= z`A$}TNS3+QXgzeR{CGZHfnFKcbBp~vXjG)x0sAaV;X5OYZP>-+8&Ap@3~QVsX8PE} zBriREL~6&~kR^3)e$-P}0@2HP)s?+gPov9|HEBp+AIP+P96z3BI4cxzJ$=!`BeCv{ znuo6Pz7%Vx@2-Jq)@aAv?SgxO(@yIPzl4uZWtawb6bU8T#u!sy&dZbVT@>${cU{6h z*b}wHh75nW5o#Q!vhumjTgn)lb2T0fUlwTo@t(tNy-KRqYcZ>~8n zmsR`qgiTpI^IE){SEsf~#Kf^w_j{ZQLVHxElK6t1Ge&y^##^J7F#dDO6e;R7>xG)d zpMx#Ojr&gJ>%B;*mz)1uytjitI#*5VEKlE~_+@l(lvm*Cc&3twO84AIpTaw5ikBM^ z$|rgAj_YJPBuvYb|%SY7C*N`||QzZ_8~u+go)k9*T8;MLDU^Yn=MHv#CB#&3PocIpw;xN&E+a z&d*kHA%>}Cno87W&@$cQBrJk%z@)0!xN`3iO`;((LWh$B1P3W zkIk%C&&8h#F?6SRF}`w*PjRdMb9u;T0XN=Abv`@UbBQt0x}gOnP6F?n2K@OKxp^+O z(Rf5j+O2$~EwYWxmSi6dx%N74yIRyM$T@-joW4f=ptgF{y(3NX1NqOpypQG? z?AUPmfp3xdexuIuXEE#C0UlLdjqlc{9%JaYA3s@Q7wcK^=PQjvud z=Fd6Cg7p(pS(ULSQ;!2B^IOm{fr-()lToTD4WM-Iqv7@17kzeA6Q@;c^yC7?)x&Ke>!Zd z{xX{fYe=WIZ=%@cl*5^c!m%`U)Wn5OVP~XQ&hiA=6gbFmFv=%%8MYRjJN9TH%0a_B zPOPCaXZ%8|nmwJ9sTOM5QD8X!oXO65TRhlUt9?GanR_L~%_EZ^$tg;qJeKpnh?Vr$p7k0^T z*^T$84BNOCLpqP~Vbx`o&DTsP(%jWd>MP~-vc?fJrTllN41x=|^?A&(#%xzy9C-3L zC$Fy=hbP2_G9*V`5Lp_2#N7KfHDpTZZcNStQ8#2H?dAhvwz&anfsl3X3omYsxApJU ziJF;oEt7sAADH;6ekJCi;ChW*vHIkfAE_5ug*EOAe?M!eA^AX9=aH93&>aU02UQow z2|cDAzN6YqZh_ms?l21YzeqZplv4b8H;~++$;hXZ+S3Vh3Q>qEPn99nr-w~nDu^7 zd9%q)ZB1+TYI}s+ggt~MzU2M#FGJ>`Zv{y!k(~vhn8oJ|oa-PI+@i)~8mN=*X&7pt_emTj!=9WXLw+O@ zBQG=v>eMS-ax@n%xr+vuY{mr_ERbSj-}|8l;disLFod~6C)cAyEN#iKMe9V^GP%RJ z4vDS5l(!DIpW zEjbgK5dfow7zHML4G5ae2rl_B@K-K1cwNBc^+`+PFT$+Pm`IMD=LQ!7Xz?8+L@=O% zOH!nWNEtt{853Y*G`o^k&t|(Hs`woyIvCEcyaJgA^b;eXa2qiVsp7mj0hP} zV4XC87)22{3k{Gi5CZLBi3PtEw+wP37`fQ-HHk0_F$KX4s7O#xfG7g#WCE$|3y?Zl zcpZb$(*_80axJi47!YCw{m=ofy^uLT8&w$5401{sjeJS4^ABMv2_sAsn(Cb^}U}O2Sw=Rsg!wCmbT9 z3p{fL))YpIL=-XZ&Os>U8OXi2lrY@+pr|_RA7SwQMgznQ$e9+9wINs}Wi$?X4lLRW z*y|XiKP3lDegtal!~v@WABd`a(d?JR*wX!A0mgH)7*MIeAtPBy5hD;5CEpB1{<6;2xSqDK?xi;B$!py#>m7SQ``_xL{HPln7u8E9 z!=63~4$4c{aM{nGJ=26yUj@PjLQfM&<7=QD8_mEAR%$s3h3ajOLoVCM;7JNW%LN5- z&j}C>taN@$KrWJyGW8VL1D$>lNE!TL&@M@5K?CUmw;u7M@?C{Eqrm5|T?Qa_ix6W& zk6Osp%yRF+*U(BrrGPkO1R3B9DyIuu^RTFv5SlSi+Ue zOb-As0t3}m$OHi#at8)NDn03%v2NyF@%@J_fEvg?5>JV-Dw)2!X*_$Dq=pF47E~f< zN^B`$OFJaly?$6D3j^X{PK+&+rQ5Bk4p|U@-$PQ*gS1!&fq8raAbUUPdo?g8b-*sL zK|T=R%{`e2X@ewEJhv-#fHV$RXbb3SY7RL8=ra%7kljWG5JrbEUWajf0Z)Vt5Mn@# z)J^Y2F|~5N!&A!@HlSG-U8`^q}k* z$>5o5A?fSIQTgkT&{OC9&>j$&%VhV0_CSLq8^Fm299R&fmvEj_IOHg3-H-HSYAS#B zVZ*&ZO?piZLpu%BvNfDiXkx-^6>&%d2&27VHVBj*et=8lHsDPHjKd!EAZ@~!HvsT4 z0TTFtx}E#fV#s39Z~>>_0M#>hYaCPfOnURL17F7 z)%XaYEg#hPO~SMdq23~Z4@~*gOF;pF(90ZP;&$Oe<zl!Bmh*e#QmV>lVie{FGI`Ri~drV_E_S?>!0Y%Ph?R{ zAGQ!j(T8v+rCX3sr(uo`0$6~vm%Jz6VA-^+yDboTX$Be|sL6zs?BFGWLq=M}`3MM$ z4+3#W@&!190X(n?gY-~<*5`%6`a6K?oaokVbj}zJlvJW2SGoW^+0%nL4{cY76fe@ac~2RrU1=xaQ<+g5__812W8XrMdh^exORhF54G^b z#1Flh_gf_7hs3a919eH20kIq+!7AK`qyUh|=7N$};*bts zI7g6sff#%S>0f~u5eWkw_R6Q-ELL#=`Jk^3+6gDXTs5eUT5xnV3$278{_yBX%? zd;%C2biN4ae2Tk=PLzO(007>x()g$%*as;}B)JW=y9jdZBwR2M$pG9W8yFolP(uCF z5RrmhMu}B{6PZ#~nHpOz04FOMyetm1&{hbm#~2()!ui3?99~vj>OxK`rgv z1|S4wx>Ujs>O%&9_7vBmgo~#^xA7rpv9JMzbE$VKzYjVIa(}oP1!$neyY1>Y z1F#J*Knd7tEjUOlUI#hfb&C!}9fEQM!VUd7SOv7@^$QRv4T2~Qx7a!gbOg8yHyi-m zaRHJu1&1pJpd6dP$rOR)fV5~*58=NL2~0{3XOhLB1y3H>yLA|o=Pgvig?t5lIhy~s z2rUAnY!c8h4TZ?U-mJKgLi++4w1XNy5<-cMuL9K!aHg&n1YnLFfL08gHE4r49s#j% zfp18@hIUa8bmSd@jgbNv12}8%hf_bo=@{Iz$sEi>dT(6>K{XaoX%3VNIaPpVkyMv{LMa&V|(vL+|#!G0&82m6qD? z;1QxYkUlE(F(5?YVML7;$AZ}dta;!Fp!GT6ItYNbhhOiFr*fm zT=ke@|2$Fs#xJ)t_aM96nYt9K!QLNYGZ$~~A>p|!S=sH^I$RP^*mKUS^^oesV$ra_ z_Nk}mygSZzJ}H?nd(jl)$iCfn~6PdEO%N?|GHx(|4b%#(b-Px zg-=mk*Qa!BW_faBHha(3zR}_FbHb#|p#iI^mT@-aD@{ zRNQ>iG$U7uT}qg>v*SFIUeU$buZTtg>&98t4A+BlA9eitr`V}(P7Qk!rFA#yPwEV- z9t)XOlpfyr75>0AgZXO1$p*TQ?tV7jXA_x|gAR8Ov#jFY98@4JaPPay(6O{nZS_*> zK}m+TE0-e8SHDgDn(XC$URGnDUH9li_~DsZMTLU3c|N(9A*8(898%=|;R`zSx9moe zk_sfHdc7a?ZC`jZu`TJmlej#nLun)R&3k6hcy;d1gv93gvhVNb=M21DU*y^eA zbWQg;tRAuXb;M#>F~|9j|CE(ic3`u3^Ovs8n`I^@+a!*v{hK98Tk}ibSIXYjzE@7N zKD_gDtdFJd%(>n#`T>*vEd8&K4SEV5oUZcOG(R&tnd>dNWj!S@JP=jZE!v$P+C=-s zdNILkWzwve@NZS$ROwoK^Nnf@_S&B6_c+$xkioIDe%S5>TY;_TxvlFT`rK~qbWTlq z9Ls4sJi1*rT=pPPvc1K!`lHLmv2BkY*4ZlWl?^Gejp-K0jW1r}rTrr2w%)hT1iSSm zR!L6TxU3ZCaV)8C+%7p-HN4eqU{JxIZhp~wc%zu+$N?h`%bh1b7INolE4d;daX zd)xz)_J3PyO;4ZS?rs^>S)Gp{*HiIcDQ08O$#^}iqj8&Mt)ozkjV5tBD`#?2^J#nD zi}V)T&mWtN4O7>)nnqvW$<7h>e*Jo}tZT*b6zln*kC?`-UmrZ|tLXcq#suRu?unKo z2?i@%dlbj*ni(pL8olS{A5I3{*>1d3yHIy*toTGVSFTuz}Ml+ zA{c_t&$7ytnRE85d{b{Utl?DE(-@z#HIwW7tXOuXSHCMDd7=B)_kx#n>)-B2c_bxN z_HKV!d6(1laY6KZyX>gt>L$xflE+5;%8XrzopFVzMC0t8+h4cu+^*bN5&c-w>{6Ul zFmtA_tGBYE!0L%!cW=?>ms6YVw*9}hRt3HLn@Z!gUVPedT8kWY9U^Gp%q*3Ys}(+Aqa=qZ4=QnCV6MKg>VJ8@Rc?P})}_L81G;V-#Fp+yzqy+i zM408yJYTXC@hn&E7cF9lJNHRWPqkZAGBRkyj_FGR_KU7SVX5v-$I_NDN!h+rvVD#B zi(_bQ1X;dR%H4Fic+;h{W!B-@z^H7Q3)PluUysY4h*7>o+uWB`5m9f9SDnXyxG|(^ zFzPC_%^eGEc^AnQ|)VePTH5njhfEoMgPHwcf~fcE+GZ(7bG5uVeVADeuQ_k z8j(7-Q#zh5QCb-}1#gapeIn8EEOdMAcoqjG_wwBH6=fR{%-?ZdRdgP#tcb-|o;&Nw zQYDom_4Sczd1-)@QRMOYi4SjV_Fjm$6?rJ$IHKXAk=J;+D*2t@YL%_Odiq1f?ADmK z+{i$jsP=S5R_EK+_vfrnTG6@}-eSs`^O`WFLFa6XvZq<*NCn% z`}T|4nFdKdsdSz9Irpizau#2)5jbO&TZo^e{Qh&)@8VEV@~WS-YmfX78I>&=sf#PBC#8ED%3dP>MHm5BN0880){DBUED>Gc52y7 z8{3TK==T@dg8|uTf`I~pfgXYZR3a3TDFh_@f9vO!=2~(tckO@#Un!wddoFmLFZZ zCw9MnO=9e_<&uZi%&%E627h;1lXv$L&58fH(W|Sw_seVX)(#eUE-6l8l67OjRY3UM znn~5>La=(M(Rpo~7`tM(eXMPkgQo^>E}u9Pl3CPQc5G3Pa#7k}?wNzSzg!;S?H#=N zw=QVhsus;(}x-n-hAPW;rVzT{anU-7q)&56HAFq?%=r5E0#{_@@VCZvN8)GC68<%JEnCZSi~8UWpUCfq&+7Wp78y z1)(eHR@zQyhNkOQsG~Ao2IwdCNiR z={>Zk{M%J4PWb~`)huL)kM~fppj`SPB~ra|37i za6evrFC#B$Gf6C?`~rU?2X7hiT9`4tPm*DcV_qupTU?AqChihNLE0&W8s^7+dq_WS z1xB6}lh||X=V+EF-C?;w&s%S_EoOvMN$_zjTDqkQ7OZln<``?x%Q+EZ!xnv3nSjbuZ%8m}OCgIcRM)00D<2CX*_ zZpHMCVe6>M!Y8LLbp{%}fALc?;D^;ifv8hC7qp_7PWtF^8pqkCZd~Dg-mVrjeE-^h zU+U{FNSEJm087G#F{y~Cj6w3d)(g2-ZxjQKjHr2W6mqCKYa8V z(B`7V%CG2xQViefKYGb!@%m?Sk^JF0Hzvi-M*R5-gWm2}WwbWdE0i5K((gT)rRf(i z?7U1mtGm;lv5-zP zX3yN;8$SHb>5bj3(N=!9*#)V)#?}+xm)iQ0-t_f&`|U4hte^;SWa#klDq5xdPvWaT zHj%dOoi3Gu=ft$&wf}EVhF#FSs(kIbfQhNuZswGbIHcg%GBDz*2z`C@pn0P3aS5ZPF+@3Qg7L#{Hb20 z!tC5iVG&QEVmi!NH{<1cc*135-wGLeL2G}_Czq)B-Xl&>BZ$Fo` z)RlMO^_S=BQo|Z)-k-o+SlxO^YkU3X{DtGTdZie0>_GkPFP0}nIInc9FEewXhmx{l z6~@>fs8*j(?LS7Wk;rtJooHicyAk3z~b<|Q2m3*i{+T~@|Ist3oqxUzMb!j zpI~@Xe(FDIB@GW8F4}!5T7YK}Uy&0LG5;+%x16jDt*!t0dBdFR^|pN{FcnkBKG0vi zT8!0EiXqacr*2WbI?{$nXAwQbYekt}!toW#PI=E=-7tIZzi^}7j@|t0A$rg1*N0Ns z@8?`+547{Euy22WtaN=O8}?_9u1{@XJnH}IdPv(%Qsu~{34E)~iPJ%&5lg@qLg;6Qj zyU+wv@(&)kFXD>MH$1ZoCr6LCIyD?|&AE<}D{`vtHu_cV|6(T!etlOKHadXX3jjk>2uImTyv%9Q-)<&nXzW8 zYTJ)_+;%kIm$|XK`-bkN3=wDU;nZMczV!aRaZl$n4V7&tjs5g9SB_}w3)NSRW8%In zS{4=m^iK>X){)@6@cBT0#m9Je$}EE)l)3^MIk3h{&Tnt<1T&7gm5$Tmunv`|?C za~iBBD%8FO2pV|9U*NNh1B-j1?xhI&tyOgvp7pfV+TjD#msnd$SDYnJ2bbN|D&+On zxq;t0fgR>#iFiE|LKJCshj;J#Y~ro zsEI|Uy%l%gTQnJ*uZ!|-03+7Qw+efd(*!Q;=*5f_Coosj>L3(Xu;)v!i)&uwLqDe!! zG!@Rcu89vTIw$OZ@KpHd^yR|Itm&b7MhapKgB5H>WqfHWP0aUUZ(3QZJ%>4`PN~+2EWOFYmYY-Rp~0IC&k;oi%?HX2~brg zkFKOrWc`jNobxXXxVl05=baQ1zrd9RiHU>V{BJw?kGXk2_$2LnX}bHXn-8T~j@P~` z8oEX~z?!g6yg~1UO#LJMqw4cjR+1_$@A%4pR?hM020W(;);iv8MqkhMszu`bd!x`C zt^_itX}Jx)yUwSVuePKWJv37@pphJA!sI=AE5Yf{|1iDDHNJX}F(;+i9p{Wn0l^FH zoj+LKpK<-3+3EP8>vBtI?QA1OJE>Ezs;gK8-6BU*khM1@c6-jazS5wwQw+<3s z@T*{7rib*It-hD?kK<+9lhWo}7|z@4saU*Ms5tiQRZ1Xt!{G&_szAhKfXA;a-FbB- zUyAiY@g2d*XL1EeAvq~pYq>o6Eu-}to4^@}N!UsM+Y|l!A(DhY7@`>&!q2}x-%tLp z=llQqY(K%~e|x(Bw@ug{q8T6gzuEl5v;JWGe|gUTx5NK2{^!Z)kH#%d{ng?BF%GH! z|2O`3_W4KSMk0ST{zonf)(KqnFI4J}uK%4#{>OTX&+^}_|Cv^TeFClg2m60#fd8>S z;*%iySMLe-|G)>~1_VC%4>$Nbi~7$EW_(J;{(6J|aH{`*=n1y}++Y7_d*jT1vHgGA zR)Xz6H@ZLC{w(oV+y8ER|2Mb)jJ$s|{aEsUH~oL|`tQ*8N7E7K{%ZQ4few~J0J^{J VE;*= 0x80: + datax = (data & 0x7F) + while data >= 0x80 : + data = ord(bookFile.read(1)) + datax = (datax <<7) + (data & 0x7F) + data = datax + + if flag: + data = -data + return data + +# +# Encode a number in 7 bit format +# + +def encodeNumber(number): + result = "" + negative = False + flag = 0 + + if number < 0 : + number = -number + 1 + negative = True + + while True: + byte = number & 0x7F + number = number >> 7 + byte += flag + result += chr(byte) + flag = 0x80 + if number == 0 : + if (byte == 0xFF and negative == False) : + result += chr(0x80) + break + + if negative: + result += chr(0xFF) + + return result[::-1] + +# +# Get a length prefixed string from the file +# + +def bookReadString(): + stringLength = bookReadEncodedNumber() + return unpack(str(stringLength)+"s",bookFile.read(stringLength))[0] + +# +# Returns a length prefixed string +# + +def lengthPrefixString(data): + return encodeNumber(len(data))+data + + +# +# Read and return the data of one header record at the current book file position [[offset,compressedLength,decompressedLength],...] +# + +def bookReadHeaderRecordData(): + nbValues = bookReadEncodedNumber() + values = [] + for i in range (0,nbValues): + values.append([bookReadEncodedNumber(),bookReadEncodedNumber(),bookReadEncodedNumber()]) + return values + +# +# Read and parse one header record at the current book file position and return the associated data [[offset,compressedLength,decompressedLength],...] +# + +def parseTopazHeaderRecord(): + if ord(bookFile.read(1)) != 0x63: + raise CMBDTCFatal("Parse Error : Invalid Header") + + tag = bookReadString() + record = bookReadHeaderRecordData() + return [tag,record] + +# +# Parse the header of a Topaz file, get all the header records and the offset for the payload +# + +def parseTopazHeader(): + global bookHeaderRecords + global bookPayloadOffset + magic = unpack("4s",bookFile.read(4))[0] + + if magic != 'TPZ0': + raise CMBDTCFatal("Parse Error : Invalid Header, not a Topaz file") + + nbRecords = bookReadEncodedNumber() + bookHeaderRecords = {} + + for i in range (0,nbRecords): + result = parseTopazHeaderRecord() + bookHeaderRecords[result[0]] = result[1] + + if ord(bookFile.read(1)) != 0x64 : + raise CMBDTCFatal("Parse Error : Invalid Header") + + bookPayloadOffset = bookFile.tell() + +# +# Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed +# + +def getBookPayloadRecord(name, index): + encrypted = False + + try: + recordOffset = bookHeaderRecords[name][index][0] + except: + raise CMBDTCFatal("Parse Error : Invalid Record, record not found") + + bookFile.seek(bookPayloadOffset + recordOffset) + + tag = bookReadString() + if tag != name : + raise CMBDTCFatal("Parse Error : Invalid Record, record name doesn't match") + + recordIndex = bookReadEncodedNumber() + + if recordIndex < 0 : + encrypted = True + recordIndex = -recordIndex -1 + + if recordIndex != index : + raise CMBDTCFatal("Parse Error : Invalid Record, index doesn't match") + + if bookHeaderRecords[name][index][2] != 0 : + record = bookFile.read(bookHeaderRecords[name][index][2]) + else: + record = bookFile.read(bookHeaderRecords[name][index][1]) + + if encrypted: + ctx = topazCryptoInit(bookKey) + record = topazCryptoDecrypt(record,ctx) + + return record + +# +# Extract, decrypt and decompress a book record indicated by name and index and print it or save it in "filename" +# + +def extractBookPayloadRecord(name, index, filename): + compressed = False + + try: + compressed = bookHeaderRecords[name][index][2] != 0 + record = getBookPayloadRecord(name,index) + except: + print("Could not find record") + + if compressed: + try: + record = zlib.decompress(record) + except: + raise CMBDTCFatal("Could not decompress record") + + if filename != "": + try: + file = open(filename,"wb") + file.write(record) + file.close() + except: + raise CMBDTCFatal("Could not write to destination file") + else: + print(record) + +# +# return next record [key,value] from the book metadata from the current book position +# + +def readMetadataRecord(): + return [bookReadString(),bookReadString()] + +# +# Parse the metadata record from the book payload and return a list of [key,values] +# + +def parseMetadata(): + global bookHeaderRecords + global bookPayloadAddress + global bookMetadata + bookMetadata = {} + bookFile.seek(bookPayloadOffset + bookHeaderRecords["metadata"][0][0]) + tag = bookReadString() + if tag != "metadata" : + raise CMBDTCFatal("Parse Error : Record Names Don't Match") + + flags = ord(bookFile.read(1)) + nbRecords = ord(bookFile.read(1)) + + for i in range (0,nbRecords) : + record =readMetadataRecord() + bookMetadata[record[0]] = record[1] + +# +# Returns two bit at offset from a bit field +# + +def getTwoBitsFromBitField(bitField,offset): + byteNumber = offset // 4 + bitPosition = 6 - 2*(offset % 4) + + return ord(bitField[byteNumber]) >> bitPosition & 3 + +# +# Returns the six bits at offset from a bit field +# + +def getSixBitsFromBitField(bitField,offset): + offset *= 3 + value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2) + return value + +# +# 8 bits to six bits encoding from hash to generate PID string +# + +def encodePID(hash): + global charMap3 + PID = "" + for position in range (0,8): + PID += charMap3[getSixBitsFromBitField(hash,position)] + return PID + +# +# Context initialisation for the Topaz Crypto +# + +def topazCryptoInit(key): + ctx1 = 0x0CAFFE19E + + for keyChar in key: + keyByte = ord(keyChar) + ctx2 = ctx1 + ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF ) + return [ctx1,ctx2] + +# +# decrypt data with the context prepared by topazCryptoInit() +# + +def topazCryptoDecrypt(data, ctx): + ctx1 = ctx[0] + ctx2 = ctx[1] + + plainText = "" + + for dataChar in data: + dataByte = ord(dataChar) + m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF + ctx2 = ctx1 + ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF) + plainText += chr(m) + + return plainText + +# +# Decrypt a payload record with the PID +# + +def decryptRecord(data,PID): + ctx = topazCryptoInit(PID) + return topazCryptoDecrypt(data, ctx) + +# +# Try to decrypt a dkey record (contains the book PID) +# + +def decryptDkeyRecord(data,PID): + record = decryptRecord(data,PID) + fields = unpack("3sB8sB8s3s",record) + + if fields[0] != "PID" or fields[5] != "pid" : + raise CMBDTCError("Didn't find PID magic numbers in record") + elif fields[1] != 8 or fields[3] != 8 : + raise CMBDTCError("Record didn't contain correct length fields") + elif fields[2] != PID : + raise CMBDTCError("Record didn't contain PID") + + return fields[4] + +# +# Decrypt all the book's dkey records (contain the book PID) +# + +def decryptDkeyRecords(data,PID): + nbKeyRecords = ord(data[0]) + records = [] + data = data[1:] + for i in range (0,nbKeyRecords): + length = ord(data[0]) + try: + key = decryptDkeyRecord(data[1:length+1],PID) + records.append(key) + except CMBDTCError: + pass + data = data[1+length:] + + return records + +# +# Encryption table used to generate the device PID +# + +def generatePidEncryptionTable() : + table = [] + for counter1 in range (0,0x100): + value = counter1 + for counter2 in range (0,8): + if (value & 1 == 0) : + value = value >> 1 + else : + value = value >> 1 + value = value ^ 0xEDB88320 + table.append(value) + return table + +# +# Seed value used to generate the device PID +# + +def generatePidSeed(table,dsn) : + value = 0 + for counter in range (0,4) : + index = (ord(dsn[counter]) ^ value) &0xFF + value = (value >> 8) ^ table[index] + return value + +# +# Generate the device PID +# + +def generateDevicePID(table,dsn,nbRoll): + seed = generatePidSeed(table,dsn) + pidAscii = "" + pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF] + index = 0 + + for counter in range (0,nbRoll): + pid[index] = pid[index] ^ ord(dsn[counter]) + index = (index+1) %8 + + for counter in range (0,8): + index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7) + pidAscii += charMap4[index] + return pidAscii + +# +# Create decrypted book payload +# + +def createDecryptedPayload(payload): + + # store data to be able to create the header later + headerData= [] + currentOffset = 0 + + # Add social DRM to decrypted files + + try: + data = getKindleInfoValueForKey("kindle.name.info")+":"+ getKindleInfoValueForKey("login") + if payload!= None: + payload.write(lengthPrefixString("sdrm")) + payload.write(encodeNumber(0)) + payload.write(data) + else: + currentOffset += len(lengthPrefixString("sdrm")) + currentOffset += len(encodeNumber(0)) + currentOffset += len(data) + except: + pass + + for headerRecord in bookHeaderRecords: + name = headerRecord + newRecord = [] + + if name != "dkey" : + + for index in range (0,len(bookHeaderRecords[name])) : + offset = currentOffset + + if payload != None: + # write tag + payload.write(lengthPrefixString(name)) + # write data + payload.write(encodeNumber(index)) + payload.write(getBookPayloadRecord(name, index)) + + else : + currentOffset += len(lengthPrefixString(name)) + currentOffset += len(encodeNumber(index)) + currentOffset += len(getBookPayloadRecord(name, index)) + newRecord.append([offset,bookHeaderRecords[name][index][1],bookHeaderRecords[name][index][2]]) + + headerData.append([name,newRecord]) + + + + return headerData + +# +# Create decrypted book +# + +def createDecryptedBook(outputFile): + outputFile = open(outputFile,"wb") + # Write the payload in a temporary file + headerData = createDecryptedPayload(None) + outputFile.write("TPZ0") + outputFile.write(encodeNumber(len(headerData))) + + for header in headerData : + outputFile.write(chr(0x63)) + outputFile.write(lengthPrefixString(header[0])) + outputFile.write(encodeNumber(len(header[1]))) + for numbers in header[1] : + outputFile.write(encodeNumber(numbers[0])) + outputFile.write(encodeNumber(numbers[1])) + outputFile.write(encodeNumber(numbers[2])) + + outputFile.write(chr(0x64)) + createDecryptedPayload(outputFile) + outputFile.close() + +# +# Set the command to execute by the programm according to cmdLine parameters +# + +def setCommand(name) : + global command + if command != "" : + raise CMBDTCFatal("Invalid command line parameters") + else : + command = name + +# +# Program usage +# + +def usage(): + print("\nUsage:") + print("\nCMBDTC.py [options] bookFileName\n") + print("-p Adds a PID to the list of PIDs that are tried to decrypt the book key (can be used several times)") + print("-d Saves a decrypted copy of the book") + print("-r Prints or writes to disk a record indicated in the form name:index (e.g \"img:0\")") + print("-o Output file name to write records and decrypted books") + print("-v Verbose (can be used several times)") + print("-i Prints kindle.info database") + +# +# Main +# + +def main(argv=sys.argv): + global kindleDatabase + global bookMetadata + global bookKey + global bookFile + global command + + progname = os.path.basename(argv[0]) + + verbose = 0 + recordName = "" + recordIndex = 0 + outputFile = "" + PIDs = [] + kindleDatabase = None + command = "" + + + try: + opts, args = getopt.getopt(sys.argv[1:], "vdir:o:p:") + except getopt.GetoptError, err: + # print help information and exit: + print str(err) # will print something like "option -a not recognized" + usage() + sys.exit(2) + + if len(opts) == 0 and len(args) == 0 : + usage() + sys.exit(2) + + for o, a in opts: + if o == "-v": + verbose+=1 + if o == "-i": + setCommand("printInfo") + if o =="-o": + if a == None : + raise CMBDTCFatal("Invalid parameter for -o") + outputFile = a + if o =="-r": + setCommand("printRecord") + try: + recordName,recordIndex = a.split(':') + except: + raise CMBDTCFatal("Invalid parameter for -r") + if o =="-p": + PIDs.append(a) + if o =="-d": + setCommand("doit") + + if command == "" : + raise CMBDTCFatal("No action supplied on command line") + + # + # Read the encrypted database + # + + try: + kindleDatabase = parseKindleInfo() + except Exception, message: + if verbose>0: + print(message) + + if kindleDatabase != None : + if command == "printInfo" : + printKindleInfo() + + # + # Compute the DSN + # + + # Get the Mazama Random number + MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") + + # Get the HDD serial + encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1) + + # Get the current user name + encodedUsername = encodeHash(GetUserName(),charMap1) + + # concat, hash and encode + DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1) + + if verbose >1: + print("DSN: " + DSN) + + # + # Compute the device PID + # + + table = generatePidEncryptionTable() + devicePID = generateDevicePID(table,DSN,4) + PIDs.append(devicePID) + + if verbose > 0: + print("Device PID: " + devicePID) + + # + # Open book and parse metadata + # + + if len(args) == 1: + + bookFile = openBook(args[0]) + parseTopazHeader() + parseMetadata() + + # + # Compute book PID + # + + # Get the account token + + if kindleDatabase != None: + kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens") + + if verbose >1: + print("Account Token: " + kindleAccountToken) + + keysRecord = bookMetadata["keys"] + keysRecordRecord = bookMetadata[keysRecord] + + pidHash = SHA1(DSN+kindleAccountToken+keysRecord+keysRecordRecord) + + bookPID = encodePID(pidHash) + PIDs.append(bookPID) + + if verbose > 0: + print ("Book PID: " + bookPID ) + + # + # Decrypt book key + # + + dkey = getBookPayloadRecord('dkey', 0) + + bookKeys = [] + for PID in PIDs : + bookKeys+=decryptDkeyRecords(dkey,PID) + + if len(bookKeys) == 0 : + if verbose > 0 : + print ("Book key could not be found. Maybe this book is not registered with this device.") + else : + bookKey = bookKeys[0] + if verbose > 0: + print("Book key: " + bookKey.encode('hex')) + + + + if command == "printRecord" : + extractBookPayloadRecord(recordName,int(recordIndex),outputFile) + if outputFile != "" and verbose>0 : + print("Wrote record to file: "+outputFile) + elif command == "doit" : + if outputFile!="" : + createDecryptedBook(outputFile) + if verbose >0 : + print ("Decrypted book saved. Don't pirate!") + elif verbose > 0: + print("Output file name was not supplied.") + + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/convert2xml.py b/Calibre_Plugins/K4MobiDeDRM_plugin/convert2xml.py index 5667511..5312a38 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/convert2xml.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/convert2xml.py @@ -1,568 +1,844 @@ -#! /usr/bin/env python - -""" - Routines for doing AES CBC in one file - - Modified by some_updates to extract - and combine only those parts needed for AES CBC - into one simple to add python file - - Original Version - Copyright (c) 2002 by Paul A. Lambert - Under: - CryptoPy Artisitic License Version 1.0 - See the wonderful pure python package cryptopy-1.2.5 - and read its LICENSE.txt for complete license details. -""" - -class CryptoError(Exception): - """ Base class for crypto exceptions """ - def __init__(self,errorMessage='Error!'): - self.message = errorMessage - def __str__(self): - return self.message - -class InitCryptoError(CryptoError): - """ Crypto errors during algorithm initialization """ -class BadKeySizeError(InitCryptoError): - """ Bad key size error """ -class EncryptError(CryptoError): - """ Error in encryption processing """ -class DecryptError(CryptoError): - """ Error in decryption processing """ -class DecryptNotBlockAlignedError(DecryptError): - """ Error in decryption processing """ - -def xorS(a,b): - """ XOR two strings """ - assert len(a)==len(b) - x = [] - for i in range(len(a)): - x.append( chr(ord(a[i])^ord(b[i]))) - return ''.join(x) - -def xor(a,b): - """ XOR two strings """ - x = [] - for i in range(min(len(a),len(b))): - x.append( chr(ord(a[i])^ord(b[i]))) - return ''.join(x) - -""" - Base 'BlockCipher' and Pad classes for cipher instances. - BlockCipher supports automatic padding and type conversion. The BlockCipher - class was written to make the actual algorithm code more readable and - not for performance. -""" - -class BlockCipher: - """ Block ciphers """ - def __init__(self): - self.reset() - - def reset(self): - self.resetEncrypt() - self.resetDecrypt() - def resetEncrypt(self): - self.encryptBlockCount = 0 - self.bytesToEncrypt = '' - def resetDecrypt(self): - self.decryptBlockCount = 0 - self.bytesToDecrypt = '' - - def encrypt(self, plainText, more = None): - """ Encrypt a string and return a binary string """ - self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt - numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize) - cipherText = '' - for i in range(numBlocks): - bStart = i*self.blockSize - ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize]) - self.encryptBlockCount += 1 - cipherText += ctBlock - if numExtraBytes > 0: # save any bytes that are not block aligned - self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] +#! /usr/bin/python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab +# For use with Topaz Scripts Version 2.6 + +class Unbuffered: + def __init__(self, stream): + self.stream = stream + def write(self, data): + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +import sys +sys.stdout=Unbuffered(sys.stdout) + +import csv +import os +import getopt +from struct import pack +from struct import unpack + +class TpzDRMError(Exception): + pass + +# Get a 7 bit encoded number from string. The most +# significant byte comes first and has the high bit (8th) set + +def readEncodedNumber(file): + flag = False + c = file.read(1) + if (len(c) == 0): + return None + data = ord(c) + + if data == 0xFF: + flag = True + c = file.read(1) + if (len(c) == 0): + return None + data = ord(c) + + if data >= 0x80: + datax = (data & 0x7F) + while data >= 0x80 : + c = file.read(1) + if (len(c) == 0): + return None + data = ord(c) + datax = (datax <<7) + (data & 0x7F) + data = datax + + if flag: + data = -data + return data + + +# returns a binary string that encodes a number into 7 bits +# most significant byte first which has the high bit set + +def encodeNumber(number): + result = "" + negative = False + flag = 0 + + if number < 0 : + number = -number + 1 + negative = True + + while True: + byte = number & 0x7F + number = number >> 7 + byte += flag + result += chr(byte) + flag = 0x80 + if number == 0 : + if (byte == 0xFF and negative == False) : + result += chr(0x80) + break + + if negative: + result += chr(0xFF) + + return result[::-1] + + + +# create / read a length prefixed string from the file + +def lengthPrefixString(data): + return encodeNumber(len(data))+data + +def readString(file): + stringLength = readEncodedNumber(file) + if (stringLength == None): + return "" + sv = file.read(stringLength) + if (len(sv) != stringLength): + return "" + return unpack(str(stringLength)+"s",sv)[0] + + +# convert a binary string generated by encodeNumber (7 bit encoded number) +# to the value you would find inside the page*.dat files to be processed + +def convert(i): + result = '' + val = encodeNumber(i) + for j in xrange(len(val)): + c = ord(val[j:j+1]) + result += '%02x' % c + return result + + + +# the complete string table used to store all book text content +# as well as the xml tokens and values that make sense out of it + +class Dictionary(object): + def __init__(self, dictFile): + self.filename = dictFile + self.size = 0 + self.fo = file(dictFile,'rb') + self.stable = [] + self.size = readEncodedNumber(self.fo) + for i in xrange(self.size): + self.stable.append(self.escapestr(readString(self.fo))) + self.pos = 0 + + def escapestr(self, str): + str = str.replace('&','&') + str = str.replace('<','<') + str = str.replace('>','>') + str = str.replace('=','=') + return str + + def lookup(self,val): + if ((val >= 0) and (val < self.size)) : + self.pos = val + return self.stable[self.pos] else: - self.bytesToEncrypt = '' - - if more == None: # no more data expected from caller - finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize) - if len(finalBytes) > 0: - ctBlock = self.encryptBlock(finalBytes) - self.encryptBlockCount += 1 - cipherText += ctBlock - self.resetEncrypt() - return cipherText - - def decrypt(self, cipherText, more = None): - """ Decrypt a string and return a string """ - self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt - - numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) - if more == None: # no more calls to decrypt, should have all the data - if numExtraBytes != 0: - raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt' - - # hold back some bytes in case last decrypt has zero len - if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : - numBlocks -= 1 - numExtraBytes = self.blockSize - - plainText = '' - for i in range(numBlocks): - bStart = i*self.blockSize - ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize]) - self.decryptBlockCount += 1 - plainText += ptBlock - - if numExtraBytes > 0: # save any bytes that are not block aligned - self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + print "Error - %d outside of string table limits" % val + raise TpzDRMError('outside of string table limits') + # sys.exit(-1) + + def getSize(self): + return self.size + + def getPos(self): + return self.pos + + def dumpDict(self): + for i in xrange(self.size): + print "%d %s %s" % (i, convert(i), self.stable[i]) + return + +# parses the xml snippets that are represented by each page*.dat file. +# also parses the other0.dat file - the main stylesheet +# and information used to inject the xml snippets into page*.dat files + +class PageParser(object): + def __init__(self, filename, dict, debug, flat_xml): + self.fo = file(filename,'rb') + self.id = os.path.basename(filename).replace('.dat','') + self.dict = dict + self.debug = debug + self.flat_xml = flat_xml + self.tagpath = [] + self.doc = [] + self.snippetList = [] + + + # hash table used to enable the decoding process + # This has all been developed by trial and error so it may still have omissions or + # contain errors + # Format: + # tag : (number of arguments, argument type, subtags present, special case of subtags presents when escaped) + + token_tags = { + 'x' : (1, 'scalar_number', 0, 0), + 'y' : (1, 'scalar_number', 0, 0), + 'h' : (1, 'scalar_number', 0, 0), + 'w' : (1, 'scalar_number', 0, 0), + 'firstWord' : (1, 'scalar_number', 0, 0), + 'lastWord' : (1, 'scalar_number', 0, 0), + 'rootID' : (1, 'scalar_number', 0, 0), + 'stemID' : (1, 'scalar_number', 0, 0), + 'type' : (1, 'scalar_text', 0, 0), + + 'info' : (0, 'number', 1, 0), + + 'info.word' : (0, 'number', 1, 1), + 'info.word.ocrText' : (1, 'text', 0, 0), + 'info.word.firstGlyph' : (1, 'raw', 0, 0), + 'info.word.lastGlyph' : (1, 'raw', 0, 0), + 'info.word.bl' : (1, 'raw', 0, 0), + 'info.word.link_id' : (1, 'number', 0, 0), + + 'glyph' : (0, 'number', 1, 1), + 'glyph.x' : (1, 'number', 0, 0), + 'glyph.y' : (1, 'number', 0, 0), + 'glyph.glyphID' : (1, 'number', 0, 0), + + 'dehyphen' : (0, 'number', 1, 1), + 'dehyphen.rootID' : (1, 'number', 0, 0), + 'dehyphen.stemID' : (1, 'number', 0, 0), + 'dehyphen.stemPage' : (1, 'number', 0, 0), + 'dehyphen.sh' : (1, 'number', 0, 0), + + 'links' : (0, 'number', 1, 1), + 'links.page' : (1, 'number', 0, 0), + 'links.rel' : (1, 'number', 0, 0), + 'links.row' : (1, 'number', 0, 0), + 'links.title' : (1, 'text', 0, 0), + 'links.href' : (1, 'text', 0, 0), + 'links.type' : (1, 'text', 0, 0), + + 'paraCont' : (0, 'number', 1, 1), + 'paraCont.rootID' : (1, 'number', 0, 0), + 'paraCont.stemID' : (1, 'number', 0, 0), + 'paraCont.stemPage' : (1, 'number', 0, 0), + + 'paraStems' : (0, 'number', 1, 1), + 'paraStems.stemID' : (1, 'number', 0, 0), + + 'wordStems' : (0, 'number', 1, 1), + 'wordStems.stemID' : (1, 'number', 0, 0), + + 'empty' : (1, 'snippets', 1, 0), + + 'page' : (1, 'snippets', 1, 0), + 'page.pageid' : (1, 'scalar_text', 0, 0), + 'page.pagelabel' : (1, 'scalar_text', 0, 0), + 'page.type' : (1, 'scalar_text', 0, 0), + 'page.h' : (1, 'scalar_number', 0, 0), + 'page.w' : (1, 'scalar_number', 0, 0), + 'page.startID' : (1, 'scalar_number', 0, 0), + + 'group' : (1, 'snippets', 1, 0), + 'group.type' : (1, 'scalar_text', 0, 0), + 'group._tag' : (1, 'scalar_text', 0, 0), + + 'region' : (1, 'snippets', 1, 0), + 'region.type' : (1, 'scalar_text', 0, 0), + 'region.x' : (1, 'scalar_number', 0, 0), + 'region.y' : (1, 'scalar_number', 0, 0), + 'region.h' : (1, 'scalar_number', 0, 0), + 'region.w' : (1, 'scalar_number', 0, 0), + 'region.orientation' : (1, 'scalar_number', 0, 0), + + 'empty_text_region' : (1, 'snippets', 1, 0), + + 'img' : (1, 'snippets', 1, 0), + 'img.x' : (1, 'scalar_number', 0, 0), + 'img.y' : (1, 'scalar_number', 0, 0), + 'img.h' : (1, 'scalar_number', 0, 0), + 'img.w' : (1, 'scalar_number', 0, 0), + 'img.src' : (1, 'scalar_number', 0, 0), + 'img.color_src' : (1, 'scalar_number', 0, 0), + + 'paragraph' : (1, 'snippets', 1, 0), + 'paragraph.class' : (1, 'scalar_text', 0, 0), + 'paragraph.firstWord' : (1, 'scalar_number', 0, 0), + 'paragraph.lastWord' : (1, 'scalar_number', 0, 0), + 'paragraph.lastWord' : (1, 'scalar_number', 0, 0), + 'paragraph.gridSize' : (1, 'scalar_number', 0, 0), + 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0), + + + 'word_semantic' : (1, 'snippets', 1, 1), + 'word_semantic.type' : (1, 'scalar_text', 0, 0), + 'word_semantic.firstWord' : (1, 'scalar_number', 0, 0), + 'word_semantic.lastWord' : (1, 'scalar_number', 0, 0), + + 'word' : (1, 'snippets', 1, 0), + 'word.type' : (1, 'scalar_text', 0, 0), + 'word.class' : (1, 'scalar_text', 0, 0), + 'word.firstGlyph' : (1, 'scalar_number', 0, 0), + 'word.lastGlyph' : (1, 'scalar_number', 0, 0), + + '_span' : (1, 'snippets', 1, 0), + '_span.firstWord' : (1, 'scalar_number', 0, 0), + '_span.lastWord' : (1, 'scalar_number', 0, 0), + '_span.gridSize' : (1, 'scalar_number', 0, 0), + '_span.gridBottomCenter' : (1, 'scalar_number', 0, 0), + '_span.gridTopCenter' : (1, 'scalar_number', 0, 0), + '_span.gridBeginCenter' : (1, 'scalar_number', 0, 0), + '_span.gridEndCenter' : (1, 'scalar_number', 0, 0), + + 'span' : (1, 'snippets', 1, 0), + 'span.firstWord' : (1, 'scalar_number', 0, 0), + 'span.lastWord' : (1, 'scalar_number', 0, 0), + 'span.gridSize' : (1, 'scalar_number', 0, 0), + 'span.gridBottomCenter' : (1, 'scalar_number', 0, 0), + 'span.gridTopCenter' : (1, 'scalar_number', 0, 0), + 'span.gridBeginCenter' : (1, 'scalar_number', 0, 0), + 'span.gridEndCenter' : (1, 'scalar_number', 0, 0), + + 'extratokens' : (1, 'snippets', 1, 0), + 'extratokens.type' : (1, 'scalar_text', 0, 0), + 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0), + 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0), + + 'glyph.h' : (1, 'number', 0, 0), + 'glyph.w' : (1, 'number', 0, 0), + 'glyph.use' : (1, 'number', 0, 0), + 'glyph.vtx' : (1, 'number', 0, 1), + 'glyph.len' : (1, 'number', 0, 1), + 'glyph.dpi' : (1, 'number', 0, 0), + 'vtx' : (0, 'number', 1, 1), + 'vtx.x' : (1, 'number', 0, 0), + 'vtx.y' : (1, 'number', 0, 0), + 'len' : (0, 'number', 1, 1), + 'len.n' : (1, 'number', 0, 0), + + 'book' : (1, 'snippets', 1, 0), + 'version' : (1, 'snippets', 1, 0), + 'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0), + 'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0), + 'version.Schema_id' : (1, 'scalar_text', 0, 0), + 'version.Schema_version' : (1, 'scalar_text', 0, 0), + 'version.Topaz_version' : (1, 'scalar_text', 0, 0), + 'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0), + 'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0), + 'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0), + 'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0), + 'version.chapterheaders' : (1, 'scalar_text', 0, 0), + 'version.creation_date' : (1, 'scalar_text', 0, 0), + 'version.header_footer' : (1, 'scalar_text', 0, 0), + 'version.init_from_ocr' : (1, 'scalar_text', 0, 0), + 'version.letter_insertion' : (1, 'scalar_text', 0, 0), + 'version.xmlinj_convert' : (1, 'scalar_text', 0, 0), + 'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0), + 'version.xmlinj_transform' : (1, 'scalar_text', 0, 0), + 'version.findlists' : (1, 'scalar_text', 0, 0), + 'version.page_num' : (1, 'scalar_text', 0, 0), + 'version.page_type' : (1, 'scalar_text', 0, 0), + 'version.bad_text' : (1, 'scalar_text', 0, 0), + 'version.glyph_mismatch' : (1, 'scalar_text', 0, 0), + 'version.margins' : (1, 'scalar_text', 0, 0), + 'version.staggered_lines' : (1, 'scalar_text', 0, 0), + 'version.paragraph_continuation' : (1, 'scalar_text', 0, 0), + 'version.toc' : (1, 'scalar_text', 0, 0), + + 'stylesheet' : (1, 'snippets', 1, 0), + 'style' : (1, 'snippets', 1, 0), + 'style._tag' : (1, 'scalar_text', 0, 0), + 'style.type' : (1, 'scalar_text', 0, 0), + 'style._parent_type' : (1, 'scalar_text', 0, 0), + 'style.class' : (1, 'scalar_text', 0, 0), + 'style._after_class' : (1, 'scalar_text', 0, 0), + 'rule' : (1, 'snippets', 1, 0), + 'rule.attr' : (1, 'scalar_text', 0, 0), + 'rule.value' : (1, 'scalar_text', 0, 0), + + 'original' : (0, 'number', 1, 1), + 'original.pnum' : (1, 'number', 0, 0), + 'original.pid' : (1, 'text', 0, 0), + 'pages' : (0, 'number', 1, 1), + 'pages.ref' : (1, 'number', 0, 0), + 'pages.id' : (1, 'number', 0, 0), + 'startID' : (0, 'number', 1, 1), + 'startID.page' : (1, 'number', 0, 0), + 'startID.id' : (1, 'number', 0, 0), + + } + + + # full tag path record keeping routines + def tag_push(self, token): + self.tagpath.append(token) + def tag_pop(self): + if len(self.tagpath) > 0 : + self.tagpath.pop() + def tagpath_len(self): + return len(self.tagpath) + def get_tagpath(self, i): + cnt = len(self.tagpath) + if i < cnt : result = self.tagpath[i] + for j in xrange(i+1, cnt) : + result += '.' + self.tagpath[j] + return result + + + # list of absolute command byte values values that indicate + # various types of loop meachanisms typically used to generate vectors + + cmd_list = (0x76, 0x76) + + # peek at and return 1 byte that is ahead by i bytes + def peek(self, aheadi): + c = self.fo.read(aheadi) + if (len(c) == 0): + return None + self.fo.seek(-aheadi,1) + c = c[-1:] + return ord(c) + + + # get the next value from the file being processed + def getNext(self): + nbyte = self.peek(1); + if (nbyte == None): + return None + val = readEncodedNumber(self.fo) + return val + + + # format an arg by argtype + def formatArg(self, arg, argtype): + if (argtype == 'text') or (argtype == 'scalar_text') : + result = self.dict.lookup(arg) + elif (argtype == 'raw') or (argtype == 'number') or (argtype == 'scalar_number') : + result = arg + elif (argtype == 'snippets') : + result = arg + else : + print "Error Unknown argtype %s" % argtype + sys.exit(-2) + return result + + + # process the next tag token, recursively handling subtags, + # arguments, and commands + def procToken(self, token): + + known_token = False + self.tag_push(token) + + if self.debug : print 'Processing: ', self.get_tagpath(0) + cnt = self.tagpath_len() + for j in xrange(cnt): + tkn = self.get_tagpath(j) + if tkn in self.token_tags : + num_args = self.token_tags[tkn][0] + argtype = self.token_tags[tkn][1] + subtags = self.token_tags[tkn][2] + splcase = self.token_tags[tkn][3] + ntags = -1 + known_token = True + break + + if known_token : + + # handle subtags if present + subtagres = [] + if (splcase == 1): + # this type of tag uses of escape marker 0x74 indicate subtag count + if self.peek(1) == 0x74: + skip = readEncodedNumber(self.fo) + subtags = 1 + num_args = 0 + + if (subtags == 1): + ntags = readEncodedNumber(self.fo) + if self.debug : print 'subtags: ' + token + ' has ' + str(ntags) + for j in xrange(ntags): + val = readEncodedNumber(self.fo) + subtagres.append(self.procToken(self.dict.lookup(val))) + + # arguments can be scalars or vectors of text or numbers + argres = [] + if num_args > 0 : + firstarg = self.peek(1) + if (firstarg in self.cmd_list) and (argtype != 'scalar_number') and (argtype != 'scalar_text'): + # single argument is a variable length vector of data + arg = readEncodedNumber(self.fo) + argres = self.decodeCMD(arg,argtype) + else : + # num_arg scalar arguments + for i in xrange(num_args): + argres.append(self.formatArg(readEncodedNumber(self.fo), argtype)) + + # build the return tag + result = [] + tkn = self.get_tagpath(0) + result.append(tkn) + result.append(subtagres) + result.append(argtype) + result.append(argres) + self.tag_pop() + return result + + # all tokens that need to be processed should be in the hash + # table if it may indicate a problem, either new token + # or an out of sync condition else: - self.bytesToEncrypt = '' - - if more == None: # last decrypt remove padding - plainText = self.padding.removePad(plainText, self.blockSize) - self.resetDecrypt() - return plainText - - -class Pad: - def __init__(self): - pass # eventually could put in calculation of min and max size extension - -class padWithPadLen(Pad): - """ Pad a binary string with the length of the padding """ - - def addPad(self, extraBytes, blockSize): - """ Add padding to a binary string to make it an even multiple - of the block size """ - blocks, numExtraBytes = divmod(len(extraBytes), blockSize) - padLength = blockSize - numExtraBytes - return extraBytes + padLength*chr(padLength) - - def removePad(self, paddedBinaryString, blockSize): - """ Remove padding from a binary string """ - if not(0 6 and i%Nk == 4 : - temp = [ Sbox[byte] for byte in temp ] # SubWord(temp) - w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] ) - return w - -Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!! - 0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6, - 0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91) - -#------------------------------------- -def AddRoundKey(algInstance, keyBlock): - """ XOR the algorithm state with a block of key material """ - for column in range(algInstance.Nb): - for row in range(4): - algInstance.state[column][row] ^= keyBlock[column][row] -#------------------------------------- - -def SubBytes(algInstance): - for column in range(algInstance.Nb): - for row in range(4): - algInstance.state[column][row] = Sbox[algInstance.state[column][row]] - -def InvSubBytes(algInstance): - for column in range(algInstance.Nb): - for row in range(4): - algInstance.state[column][row] = InvSbox[algInstance.state[column][row]] - -Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5, - 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, - 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0, - 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, - 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc, - 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, - 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a, - 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, - 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0, - 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, - 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b, - 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, - 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85, - 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, - 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5, - 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, - 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17, - 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, - 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88, - 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, - 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c, - 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, - 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9, - 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, - 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6, - 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, - 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e, - 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, - 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94, - 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, - 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68, - 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16) - -InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, - 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, - 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, - 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, - 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, - 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, - 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, - 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, - 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, - 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, - 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, - 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, - 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, - 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, - 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, - 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, - 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, - 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, - 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, - 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, - 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, - 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, - 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, - 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, - 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, - 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, - 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, - 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, - 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, - 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, - 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, - 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d) - -#------------------------------------- -""" For each block size (Nb), the ShiftRow operation shifts row i - by the amount Ci. Note that row 0 is not shifted. - Nb C1 C2 C3 - ------------------- """ -shiftOffset = { 4 : ( 0, 1, 2, 3), - 5 : ( 0, 1, 2, 3), - 6 : ( 0, 1, 2, 3), - 7 : ( 0, 1, 2, 4), - 8 : ( 0, 1, 3, 4) } -def ShiftRows(algInstance): - tmp = [0]*algInstance.Nb # list of size Nb - for r in range(1,4): # row 0 reamains unchanged and can be skipped - for c in range(algInstance.Nb): - tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] - for c in range(algInstance.Nb): - algInstance.state[c][r] = tmp[c] -def InvShiftRows(algInstance): - tmp = [0]*algInstance.Nb # list of size Nb - for r in range(1,4): # row 0 reamains unchanged and can be skipped - for c in range(algInstance.Nb): - tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] - for c in range(algInstance.Nb): - algInstance.state[c][r] = tmp[c] -#------------------------------------- -def MixColumns(a): - Sprime = [0,0,0,0] - for j in range(a.Nb): # for each column - Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3]) - Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3]) - Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3]) - Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3]) - for i in range(4): - a.state[j][i] = Sprime[i] - -def InvMixColumns(a): - """ Mix the four bytes of every column in a linear way - This is the opposite operation of Mixcolumn """ - Sprime = [0,0,0,0] - for j in range(a.Nb): # for each column - Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3]) - Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3]) - Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3]) - Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3]) - for i in range(4): - a.state[j][i] = Sprime[i] - -#------------------------------------- -def mul(a, b): - """ Multiply two elements of GF(2^m) - needed for MixColumn and InvMixColumn """ - if (a !=0 and b!=0): - return Alogtable[(Logtable[a] + Logtable[b])%255] - else: - return 0 - -Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, - 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, - 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, - 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, - 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, - 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, - 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, - 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, - 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, - 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, - 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, - 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, - 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, - 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, - 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, - 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7) - -Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, - 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, - 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, - 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, - 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, - 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, - 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, - 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, - 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, - 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, - 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, - 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, - 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, - 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, - 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, - 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1) - - - - -""" - AES Encryption Algorithm - The AES algorithm is just Rijndael algorithm restricted to the default - blockSize of 128 bits. -""" - -class AES(Rijndael): - """ The AES algorithm is the Rijndael block cipher restricted to block - sizes of 128 bits and key sizes of 128, 192 or 256 bits - """ - def __init__(self, key = None, padding = padWithPadLen(), keySize=16): - """ Initialize AES, keySize is in bytes """ - if not (keySize == 16 or keySize == 24 or keySize == 32) : - raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes' - - Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 ) - - self.name = 'AES' - - -""" - CBC mode of encryption for block ciphers. - This algorithm mode wraps any BlockCipher to make a - Cipher Block Chaining mode. -""" -from random import Random # should change to crypto.random!!! - - -class CBC(BlockCipher): - """ The CBC class wraps block ciphers to make cipher block chaining (CBC) mode - algorithms. The initialization (IV) is automatic if set to None. Padding - is also automatic based on the Pad class used to initialize the algorithm - """ - def __init__(self, blockCipherInstance, padding = padWithPadLen()): - """ CBC algorithms are created by initializing with a BlockCipher instance """ - self.baseCipher = blockCipherInstance - self.name = self.baseCipher.name + '_CBC' - self.blockSize = self.baseCipher.blockSize - self.keySize = self.baseCipher.keySize - self.padding = padding - self.baseCipher.padding = noPadding() # baseCipher should NOT pad!! - self.r = Random() # for IV generation, currently uses - # mediocre standard distro version <---------------- - import time - newSeed = time.ctime()+str(self.r) # seed with instance location - self.r.seed(newSeed) # to make unique - self.reset() - - def setKey(self, key): - self.baseCipher.setKey(key) - - # Overload to reset both CBC state and the wrapped baseCipher - def resetEncrypt(self): - BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class) - self.baseCipher.resetEncrypt() # reset base cipher encrypt state - - def resetDecrypt(self): - BlockCipher.resetDecrypt(self) # reset CBC state (super class) - self.baseCipher.resetDecrypt() # reset base cipher decrypt state - - def encrypt(self, plainText, iv=None, more=None): - """ CBC encryption - overloads baseCipher to allow optional explicit IV - when iv=None, iv is auto generated! - """ - if self.encryptBlockCount == 0: - self.iv = iv - else: - assert(iv==None), 'IV used only on first call to encrypt' - - return BlockCipher.encrypt(self,plainText, more=more) - - def decrypt(self, cipherText, iv=None, more=None): - """ CBC decryption - overloads baseCipher to allow optional explicit IV - when iv=None, iv is auto generated! - """ - if self.decryptBlockCount == 0: - self.iv = iv + result = [] + if (self.debug): + print 'Unknown Token:', token + self.tag_pop() + return result + + + # special loop used to process code snippets + # it is NEVER used to format arguments. + # builds the snippetList + def doLoop72(self, argtype): + cnt = readEncodedNumber(self.fo) + if self.debug : + result = 'Set of '+ str(cnt) + ' xml snippets. The overall structure \n' + result += 'of the document is indicated by snippet number sets at the\n' + result += 'end of each snippet. \n' + print result + for i in xrange(cnt): + if self.debug: print 'Snippet:',str(i) + snippet = [] + snippet.append(i) + val = readEncodedNumber(self.fo) + snippet.append(self.procToken(self.dict.lookup(val))) + self.snippetList.append(snippet) + return + + + + # general loop code gracisouly submitted by "skindle" - thank you! + def doLoop76Mode(self, argtype, cnt, mode): + result = [] + adj = 0 + if mode & 1: + adj = readEncodedNumber(self.fo) + mode = mode >> 1 + x = [] + for i in xrange(cnt): + x.append(readEncodedNumber(self.fo) - adj) + for i in xrange(mode): + for j in xrange(1, cnt): + x[j] = x[j] + x[j - 1] + for i in xrange(cnt): + result.append(self.formatArg(x[i],argtype)) + return result + + + # dispatches loop commands bytes with various modes + # The 0x76 style loops are used to build vectors + + # This was all derived by trial and error and + # new loop types may exist that are not handled here + # since they did not appear in the test cases + + def decodeCMD(self, cmd, argtype): + if (cmd == 0x76): + + # loop with cnt, and mode to control loop styles + cnt = readEncodedNumber(self.fo) + mode = readEncodedNumber(self.fo) + + if self.debug : print 'Loop for', cnt, 'with mode', mode, ': ' + return self.doLoop76Mode(argtype, cnt, mode) + + if self.dbug: print "Unknown command", cmd + result = [] + return result + + + + # add full tag path to injected snippets + def updateName(self, tag, prefix): + name = tag[0] + subtagList = tag[1] + argtype = tag[2] + argList = tag[3] + nname = prefix + '.' + name + nsubtaglist = [] + for j in subtagList: + nsubtaglist.append(self.updateName(j,prefix)) + ntag = [] + ntag.append(nname) + ntag.append(nsubtaglist) + ntag.append(argtype) + ntag.append(argList) + return ntag + + + + # perform depth first injection of specified snippets into this one + def injectSnippets(self, snippet): + snipno, tag = snippet + name = tag[0] + subtagList = tag[1] + argtype = tag[2] + argList = tag[3] + nsubtagList = [] + if len(argList) > 0 : + for j in argList: + asnip = self.snippetList[j] + aso, atag = self.injectSnippets(asnip) + atag = self.updateName(atag, name) + nsubtagList.append(atag) + argtype='number' + argList=[] + if len(nsubtagList) > 0 : + subtagList.extend(nsubtagList) + tag = [] + tag.append(name) + tag.append(subtagList) + tag.append(argtype) + tag.append(argList) + snippet = [] + snippet.append(snipno) + snippet.append(tag) + return snippet + + + + # format the tag for output + def formatTag(self, node): + name = node[0] + subtagList = node[1] + argtype = node[2] + argList = node[3] + fullpathname = name.split('.') + nodename = fullpathname.pop() + ilvl = len(fullpathname) + indent = ' ' * (3 * ilvl) + rlst = [] + rlst.append(indent + '<' + nodename + '>') + if len(argList) > 0: + alst = [] + for j in argList: + if (argtype == 'text') or (argtype == 'scalar_text') : + alst.append(j + '|') + else : + alst.append(str(j) + ',') + argres = "".join(alst) + argres = argres[0:-1] + if argtype == 'snippets' : + rlst.append('snippets:' + argres) + else : + rlst.append(argres) + if len(subtagList) > 0 : + rlst.append('\n') + for j in subtagList: + if len(j) > 0 : + rlst.append(self.formatTag(j)) + rlst.append(indent + '\n') else: - assert(iv==None), 'IV used only on first call to decrypt' - - return BlockCipher.decrypt(self, cipherText, more=more) - - def encryptBlock(self, plainTextBlock): - """ CBC block encryption, IV is set with 'encrypt' """ - auto_IV = '' - if self.encryptBlockCount == 0: - if self.iv == None: - # generate IV and use - self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)]) - self.prior_encr_CT_block = self.iv - auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic - else: # application provided IV - assert(len(self.iv) == self.blockSize ),'IV must be same length as block' - self.prior_encr_CT_block = self.iv - """ encrypt the prior CT XORed with the PT """ - ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) ) - self.prior_encr_CT_block = ct - return auto_IV+ct - - def decryptBlock(self, encryptedBlock): - """ Decrypt a single block """ - - if self.decryptBlockCount == 0: # first call, process IV - if self.iv == None: # auto decrypt IV? - self.prior_CT_block = encryptedBlock - return '' + rlst.append('\n') + return "".join(rlst) + + + # flatten tag + def flattenTag(self, node): + name = node[0] + subtagList = node[1] + argtype = node[2] + argList = node[3] + rlst = [] + rlst.append(name) + if (len(argList) > 0): + alst = [] + for j in argList: + if (argtype == 'text') or (argtype == 'scalar_text') : + alst.append(j + '|') + else : + alst.append(str(j) + '|') + argres = "".join(alst) + argres = argres[0:-1] + if argtype == 'snippets' : + rlst.append('.snippets=' + argres) + else : + rlst.append('=' + argres) + rlst.append('\n') + for j in subtagList: + if len(j) > 0 : + rlst.append(self.flattenTag(j)) + return "".join(rlst) + + + # reduce create xml output + def formatDoc(self, flat_xml): + rlst = [] + for j in self.doc : + if len(j) > 0: + if flat_xml: + rlst.append(self.flattenTag(j)) + else: + rlst.append(self.formatTag(j)) + result = "".join(rlst) + if self.debug : print result + return result + + + + # main loop - parse the page.dat files + # to create structured document and snippets + + # FIXME: value at end of magic appears to be a subtags count + # but for what? For now, inject an 'info" tag as it is in + # every dictionary and seems close to what is meant + # The alternative is to special case the last _ "0x5f" to mean something + + def process(self): + + # peek at the first bytes to see what type of file it is + magic = self.fo.read(9) + if (magic[0:1] == 'p') and (magic[2:9] == 'marker_'): + first_token = 'info' + elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'): + skip = self.fo.read(2) + first_token = 'info' + elif (magic[0:1] == 'p') and (magic[2:8] == '_PAGE_'): + first_token = 'info' + elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'): + skip = self.fo.read(3) + first_token = 'info' + else : + # other0.dat file + first_token = None + self.fo.seek(-9,1) + + + # main loop to read and build the document tree + while True: + + if first_token != None : + # use "inserted" first token 'info' for page and glyph files + tag = self.procToken(first_token) + if len(tag) > 0 : + self.doc.append(tag) + first_token = None + + v = self.getNext() + if (v == None): + break + + if (v == 0x72): + self.doLoop72('number') + elif (v > 0) and (v < self.dict.getSize()) : + tag = self.procToken(self.dict.lookup(v)) + if len(tag) > 0 : + self.doc.append(tag) else: - assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption" - self.prior_CT_block = self.iv - - dct = self.baseCipher.decryptBlock(encryptedBlock) - """ XOR the prior decrypted CT with the prior CT """ - dct_XOR_priorCT = xor( self.prior_CT_block, dct ) - - self.prior_CT_block = encryptedBlock - - return dct_XOR_priorCT - + if self.debug: + print "Main Loop: Unknown value: %x" % v + if (v == 0): + if (self.peek(1) == 0x5f): + skip = self.fo.read(1) + first_token = 'info' + + # now do snippet injection + if len(self.snippetList) > 0 : + if self.debug : print 'Injecting Snippets:' + snippet = self.injectSnippets(self.snippetList[0]) + snipno = snippet[0] + tag_add = snippet[1] + if self.debug : print self.formatTag(tag_add) + if len(tag_add) > 0: + self.doc.append(tag_add) + + # handle generation of xml output + xmlpage = self.formatDoc(self.flat_xml) + + return xmlpage + + +def fromData(dict, fname): + flat_xml = True + debug = False + pp = PageParser(fname, dict, debug, flat_xml) + xmlpage = pp.process() + return xmlpage + +def getXML(dict, fname): + flat_xml = False + debug = False + pp = PageParser(fname, dict, debug, flat_xml) + xmlpage = pp.process() + return xmlpage + +def usage(): + print 'Usage: ' + print ' convert2xml.py dict0000.dat infile.dat ' + print ' ' + print ' Options:' + print ' -h print this usage help message ' + print ' -d turn on debug output to check for potential errors ' + print ' --flat-xml output the flattened xml page description only ' + print ' ' + print ' This program will attempt to convert a page*.dat file or ' + print ' glyphs*.dat file, using the dict0000.dat file, to its xml description. ' + print ' ' + print ' Use "cmbtc_dump.py" first to unencrypt, uncompress, and dump ' + print ' the *.dat files from a Topaz format e-book.' + +# +# Main +# + +def main(argv): + dictFile = "" + pageFile = "" + debug = False + flat_xml = False + printOutput = False + if len(argv) == 0: + printOutput = True + argv = sys.argv + + try: + opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"]) + + except getopt.GetoptError, err: + + # print help information and exit: + print str(err) # will print something like "option -a not recognized" + usage() + sys.exit(2) + + if len(opts) == 0 and len(args) == 0 : + usage() + sys.exit(2) + + for o, a in opts: + if o =="-d": + debug=True + if o =="-h": + usage() + sys.exit(0) + if o =="--flat-xml": + flat_xml = True + + dictFile, pageFile = args[0], args[1] + + # read in the string table dictionary + dict = Dictionary(dictFile) + # dict.dumpDict() + + # create a page parser + pp = PageParser(pageFile, dict, debug, flat_xml) + + xmlpage = pp.process() + + if printOutput: + print xmlpage + return 0 -""" - AES_CBC Encryption Algorithm -""" + return xmlpage -class AES_CBC(CBC): - """ AES encryption in CBC feedback mode """ - def __init__(self, key=None, padding=padWithPadLen(), keySize=16): - CBC.__init__( self, AES(key, noPadding(), keySize), padding) - self.name = 'AES_CBC' +if __name__ == '__main__': + sys.exit(main('')) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2html.py b/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2html.py index 26d740ddb0ed3be82128b3fbe0e45471b0e04f4b..e5647f4bc3842abb1546088d41b2c899fa88c36a 100644 GIT binary patch literal 29407 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 literal 70144 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 G!2bbGSZYrI diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2svg.py b/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2svg.py index e25a0c8..4dfd6c7 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2svg.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2svg.py @@ -1,290 +1,249 @@ -#! /usr/bin/env python +#! /usr/bin/python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -import sys, os -import hmac +import sys +import csv +import os +import getopt from struct import pack -import hashlib - - -# interface to needed routines libalfcrypto -def _load_libalfcrypto(): - import ctypes - from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, addressof, string_at, cast, sizeof - - pointer_size = ctypes.sizeof(ctypes.c_voidp) - name_of_lib = None - if sys.platform.startswith('darwin'): - name_of_lib = 'libalfcrypto.dylib' - elif sys.platform.startswith('win'): - if pointer_size == 4: - name_of_lib = 'alfcrypto.dll' +from struct import unpack + + +class PParser(object): + def __init__(self, gd, flatxml, meta_array): + self.gd = gd + self.flatdoc = flatxml.split('\n') + self.docSize = len(self.flatdoc) + self.temp = [] + + self.ph = -1 + self.pw = -1 + startpos = self.posinDoc('page.h') or self.posinDoc('book.h') + for p in startpos: + (name, argres) = self.lineinDoc(p) + self.ph = max(self.ph, int(argres)) + startpos = self.posinDoc('page.w') or self.posinDoc('book.w') + for p in startpos: + (name, argres) = self.lineinDoc(p) + self.pw = max(self.pw, int(argres)) + + if self.ph <= 0: + self.ph = int(meta_array.get('pageHeight', '11000')) + if self.pw <= 0: + self.pw = int(meta_array.get('pageWidth', '8500')) + + res = [] + startpos = self.posinDoc('info.glyph.x') + for p in startpos: + argres = self.getDataatPos('info.glyph.x', p) + res.extend(argres) + self.gx = res + + res = [] + startpos = self.posinDoc('info.glyph.y') + for p in startpos: + argres = self.getDataatPos('info.glyph.y', p) + res.extend(argres) + self.gy = res + + res = [] + startpos = self.posinDoc('info.glyph.glyphID') + for p in startpos: + argres = self.getDataatPos('info.glyph.glyphID', p) + res.extend(argres) + self.gid = res + + + # return tag at line pos in document + def lineinDoc(self, pos) : + if (pos >= 0) and (pos < self.docSize) : + item = self.flatdoc[pos] + if item.find('=') >= 0: + (name, argres) = item.split('=',1) + else : + name = item + argres = '' + return name, argres + + # find tag in doc if within pos to end inclusive + def findinDoc(self, tagpath, pos, end) : + result = None + if end == -1 : + end = self.docSize + else: + end = min(self.docSize, end) + foundat = -1 + for j in xrange(pos, end): + item = self.flatdoc[j] + if item.find('=') >= 0: + (name, argres) = item.split('=',1) + else : + name = item + argres = '' + if name.endswith(tagpath) : + result = argres + foundat = j + break + return foundat, result + + # return list of start positions for the tagpath + def posinDoc(self, tagpath): + startpos = [] + pos = 0 + res = "" + while res != None : + (foundpos, res) = self.findinDoc(tagpath, pos, -1) + if res != None : + startpos.append(foundpos) + pos = foundpos + 1 + return startpos + + def getData(self, path): + result = None + cnt = len(self.flatdoc) + for j in xrange(cnt): + item = self.flatdoc[j] + if item.find('=') >= 0: + (name, argt) = item.split('=') + argres = argt.split('|') + else: + name = item + argres = [] + if (name.endswith(path)): + result = argres + break + if (len(argres) > 0) : + for j in xrange(0,len(argres)): + argres[j] = int(argres[j]) + return result + + def getDataatPos(self, path, pos): + result = None + item = self.flatdoc[pos] + if item.find('=') >= 0: + (name, argt) = item.split('=') + argres = argt.split('|') else: - name_of_lib = 'alfcrypto64.dll' + name = item + argres = [] + if (len(argres) > 0) : + for j in xrange(0,len(argres)): + argres[j] = int(argres[j]) + if (name.endswith(path)): + result = argres + return result + + def getDataTemp(self, path): + result = None + cnt = len(self.temp) + for j in xrange(cnt): + item = self.temp[j] + if item.find('=') >= 0: + (name, argt) = item.split('=') + argres = argt.split('|') + else: + name = item + argres = [] + if (name.endswith(path)): + result = argres + self.temp.pop(j) + break + if (len(argres) > 0) : + for j in xrange(0,len(argres)): + argres[j] = int(argres[j]) + return result + + def getImages(self): + result = [] + self.temp = self.flatdoc + while (self.getDataTemp('img') != None): + h = self.getDataTemp('img.h')[0] + w = self.getDataTemp('img.w')[0] + x = self.getDataTemp('img.x')[0] + y = self.getDataTemp('img.y')[0] + src = self.getDataTemp('img.src')[0] + result.append('\n' % (src, x, y, w, h)) + return result + + def getGlyphs(self): + result = [] + if (self.gid != None) and (len(self.gid) > 0): + glyphs = [] + for j in set(self.gid): + glyphs.append(j) + glyphs.sort() + for gid in glyphs: + id='id="gl%d"' % gid + path = self.gd.lookup(id) + if path: + result.append(id + ' ' + path) + return result + + +def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi): + mlst = [] + pp = PParser(gdict, flat_xml, meta_array) + mlst.append('\n') + if (raw): + mlst.append('\n') + mlst.append('\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1)) + mlst.append('Page %d - %s by %s\n' % (pageid, meta_array['Title'],meta_array['Authors'])) else: - if pointer_size == 4: - name_of_lib = 'libalfcrypto32.so' + mlst.append('\n') + mlst.append('\n') + mlst.append('Page %d - %s by %s\n' % (pageid, meta_array['Title'],meta_array['Authors'])) + mlst.append('\n') + mlst.append('\n') + mlst.append('\n') + mlst.append('

\n') + if previd == None: + mlst.append('\n') else: - name_of_lib = 'libalfcrypto64.so' - - libalfcrypto = sys.path[0] + os.sep + name_of_lib - - if not os.path.isfile(libalfcrypto): - raise Exception('libalfcrypto not found') - - libalfcrypto = CDLL(libalfcrypto) - - c_char_pp = POINTER(c_char_p) - c_int_p = POINTER(c_int) - - - def F(restype, name, argtypes): - func = getattr(libalfcrypto, name) - func.restype = restype - func.argtypes = argtypes - return func - - # aes cbc decryption - # - # struct aes_key_st { - # unsigned long rd_key[4 *(AES_MAXNR + 1)]; - # int rounds; - # }; - # - # typedef struct aes_key_st AES_KEY; - # - # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); - # - # - # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, - # const unsigned long length, const AES_KEY *key, - # unsigned char *ivec, const int enc); - - AES_MAXNR = 14 - - class AES_KEY(Structure): - _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)] - - AES_KEY_p = POINTER(AES_KEY) - AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, c_int]) - AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p]) - - - - # Pukall 1 Cipher - # unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src, - # unsigned char *dest, unsigned int len, int decryption); - - PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong]) - - # Topaz Encryption - # typedef struct _TpzCtx { - # unsigned int v[2]; - # } TpzCtx; - # - # void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen); - # void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len); - - class TPZ_CTX(Structure): - _fields_ = [('v', c_long * 2)] - - TPZ_CTX_p = POINTER(TPZ_CTX) - topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong]) - topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong]) - - - class AES_CBC(object): - def __init__(self): - self._blocksize = 0 - self._keyctx = None - self._iv = 0 - - def set_decrypt_key(self, userkey, iv): - self._blocksize = len(userkey) - if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : - raise Exception('AES CBC improper key used') - return - keyctx = self._keyctx = AES_KEY() - self._iv = iv - rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) - if rv < 0: - raise Exception('Failed to initialize AES CBC key') - - def decrypt(self, data): - out = create_string_buffer(len(data)) - mutable_iv = create_string_buffer(self._iv, len(self._iv)) - rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0) - if rv == 0: - raise Exception('AES CBC decryption failed') - return out.raw - - class Pukall_Cipher(object): - def __init__(self): - self.key = None - - def PC1(self, key, src, decryption=True): - self.key = key - out = create_string_buffer(len(src)) - de = 0 - if decryption: - de = 1 - rv = PC1(key, len(key), src, out, len(src), de) - return out.raw - - class Topaz_Cipher(object): - def __init__(self): - self._ctx = None - - def ctx_init(self, key): - tpz_ctx = self._ctx = TPZ_CTX() - topazCryptoInit(tpz_ctx, key, len(key)) - return tpz_ctx - - def decrypt(self, data, ctx=None): - if ctx == None: - ctx = self._ctx - out = create_string_buffer(len(data)) - topazCryptoDecrypt(ctx, data, out, len(data)) - return out.raw - - print "Using Library AlfCrypto DLL/DYLIB/SO" - return (AES_CBC, Pukall_Cipher, Topaz_Cipher) - - -def _load_python_alfcrypto(): - - import aescbc - - class Pukall_Cipher(object): - def __init__(self): - self.key = None - - def PC1(self, key, src, decryption=True): - sum1 = 0; - sum2 = 0; - keyXorVal = 0; - if len(key)!=16: - print "Bad key length!" - return None - wkey = [] - for i in xrange(8): - wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) - dst = "" - for i in xrange(len(src)): - temp1 = 0; - byteXorVal = 0; - for j in xrange(8): - temp1 ^= wkey[j] - sum2 = (sum2+j)*20021 + sum1 - sum1 = (temp1*346)&0xFFFF - sum2 = (sum2+sum1)&0xFFFF - temp1 = (temp1*20021+1)&0xFFFF - byteXorVal ^= temp1 ^ sum2 - curByte = ord(src[i]) - if not decryption: - keyXorVal = curByte * 257; - curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF - if decryption: - keyXorVal = curByte * 257; - for j in xrange(8): - wkey[j] ^= keyXorVal; - dst+=chr(curByte) - return dst - - class Topaz_Cipher(object): - def __init__(self): - self._ctx = None - - def ctx_init(self, key): - ctx1 = 0x0CAFFE19E - for keyChar in key: - keyByte = ord(keyChar) - ctx2 = ctx1 - ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF ) - self._ctx = [ctx1, ctx2] - return [ctx1,ctx2] - - def decrypt(self, data, ctx=None): - if ctx == None: - ctx = self._ctx - ctx1 = ctx[0] - ctx2 = ctx[1] - plainText = "" - for dataChar in data: - dataByte = ord(dataChar) - m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF - ctx2 = ctx1 - ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF) - plainText += chr(m) - return plainText - - class AES_CBC(object): - def __init__(self): - self._key = None - self._iv = None - self.aes = None - - def set_decrypt_key(self, userkey, iv): - self._key = userkey - self._iv = iv - self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey)) - - def decrypt(self, data): - iv = self._iv - cleartext = self.aes.decrypt(iv + data) - return cleartext - - return (AES_CBC, Pukall_Cipher, Topaz_Cipher) - - -def _load_crypto(): - AES_CBC = Pukall_Cipher = Topaz_Cipher = None - cryptolist = (_load_libalfcrypto, _load_python_alfcrypto) - for loader in cryptolist: - try: - AES_CBC, Pukall_Cipher, Topaz_Cipher = loader() - break - except (ImportError, Exception): - pass - return AES_CBC, Pukall_Cipher, Topaz_Cipher - -AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto() - - -class KeyIVGen(object): - # this only exists in openssl so we will use pure python implementation instead - # PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', - # [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) - def pbkdf2(self, passwd, salt, iter, keylen): - - def xorstr( a, b ): - if len(a) != len(b): - raise Exception("xorstr(): lengths differ") - return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b))) - - def prf( h, data ): - hm = h.copy() - hm.update( data ) - return hm.digest() - - def pbkdf2_F( h, salt, itercount, blocknum ): - U = prf( h, salt + pack('>i',blocknum ) ) - T = U - for i in range(2, itercount+1): - U = prf( h, U ) - T = xorstr( T, U ) - return T - - sha = hashlib.sha1 - digest_size = sha().digest_size - # l - number of output blocks to produce - l = keylen / digest_size - if keylen % digest_size != 0: - l += 1 - h = hmac.new( passwd, None, sha ) - T = "" - for i in range(1, l+1): - T += pbkdf2_F( h, salt, iter, i ) - return T[0: keylen] - - + mlst.append('\n') + + mlst.append('' % (pp.pw, pp.ph)) + if (pp.gid != None): + mlst.append('\n') + gdefs = pp.getGlyphs() + for j in xrange(0,len(gdefs)): + mlst.append(gdefs[j]) + mlst.append('\n') + img = pp.getImages() + if (img != None): + for j in xrange(0,len(img)): + mlst.append(img[j]) + if (pp.gid != None): + for j in xrange(0,len(pp.gid)): + mlst.append('\n' % (pp.gid[j], pp.gx[j], pp.gy[j])) + if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0): + xpos = "%d" % (pp.pw // 3) + ypos = "%d" % (pp.ph // 3) + mlst.append('This page intentionally left blank.\n') + if (raw) : + mlst.append('') + else : + mlst.append('\n') + if nextid == None: + mlst.append('\n') + else : + mlst.append('\n') + mlst.append('
\n') + mlst.append('\n') + mlst.append('\n') + mlst.append('\n') + return "".join(mlst) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py b/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py index 7bef68eac0d0a751b0c7090f5b5427a1d5ab1a59..97338872be00259f25164d5efa82a6970f24758b 100644 GIT binary patch literal 25360 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 NdK>>N@IQ70{vUg3cj*8C diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py b/Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py index 269810cfa24541f8be10050e7192fb6211a45a98..a889564b8a4b2c8658243bf5eb5e03d2610ea0d2 100644 GIT binary patch literal 6469 zcmd5=e{b7368+zwV%EJv^3_$EHU}(%B!@OhcY`)B;{sFZuL7==7pr0HgqbcB+8t6LuZGS zX}z!z!80mptS&`CjrB`}na%4iFay0Pw)rL2L_n(DbEE0(Z;!Vg_b`nj<%6zi-tBxI6A20J(liJU%uI`=y# zUDEX&ro_%Dmn$^|)D|s_g#`;KD~K>sF(#hhsYVQ3_KZiYyaX5+<+#8z1ctCw!%*J%2M0F%Z!Jn*N%CxC+Y={pPe|+LgQQrC=4NDn27!1;sr72y)t@bI} zNnS>S!L$(8(x)S?nxzrgF(-5e%^&a8~}-RWOvelQ(RO`Zq0HT*ruH$H~DRTtllv!`U z(H6FlGUk|ng0rOUMp7#fj_OXr9gIw-dRbwMv5CG+ZiJk?m`wgV`RC;Cd&Av>$+NAm zkq;W%WVTjyQ_KA*jod+LWDSIWl>Dq!88dp92;2j<$+`*N)V_OrCT_dTgk2J46fZ+0pvWxL=zj0DI)yv431G%g+TYR*E9b!NuK?UC#PX@Zx@)F7+DptY8)jA zCUm+zP$VJ{DwA`rr7fcxS!5xp67?LX0>>*st2hzl1Ekd4C0bBDGLF)@(*#?v8hivd z*K_EJdW7S_;`ht$^X;$CI`wYtlXV9ERH_?5SOef=b+!(=Gl}?Ywa*$PO{B!C1pr@; zp<_e`^ujqBD}3>l_HmF&)N!&$(}8%#^R{=t{*uXHf0)%!E|hu!60&{ic|>T9W3k zOL3*9uQ1XBJB~~ijo_>zk6(F%V{h$D9OLEL=iWgEk%8PLHXHuu-ouvLL9dsD}) zc`_K%5r{vp(Fi``A+j!@>^x$s-}U{ZS3ij0O{0E-ZP*E)B=1;1u-*p` zeuEmf_Hk8C7c$LCtDGbX3>a`FQER$tnPGWK`6UA!Z2#lMHzB~h;5f*hT;p(HgE_6w z^~-#FOw0V``jF2~j!sdSF(b6ctnvco_&&9XntkC+zF?O?{)$1*IJQZ7bbd$>)@hq4WTP0nKybewd?wc9!B1~nCNFOBet zgeZpRwyf&vBn-RE*9&r-c9fPHrF6uPiMYGo3AxlAEpvj`$L#u(zk9{V;rrC1n?J1c z)rQXYiojy|@Aq$qZPyds?c^<|;2_u2Fe?fv@kN{GolE<;4U~^bhsb}fX#Su%N`AaN zegBzWyN-kO5pt)hds*&V%|EY~#a#gG!8-9MD}&6y5|5y{_~{4wd~)`OkU)la18Cbo zfe)V_fPibXg;B?Ck09f_rFc8x<3ohp{cwFHgo-rWAwus7y(obe z2O>dE(XqMZ%aE%AkRK#S*~J}%hJ1}&;6^&%aFu0a(ff!&<5zstA^yVV9dV-QV)wv| z8hUVt4vnpyv%{^^ap-1^iNnfO!yba~O?fv8(mt`K-HjpM_ESq=o-b(6_g)fIOZwm| zhAP@aLMfZ2b+5LaapB6_56v<|5aZLHudfbh&(p#I(Gb;SyCYmlapyY~s>B0d?v{9r zg}GYobAiea>rHzb^w?%y!|^oKZ^49%9nA6NAn%|{k{JHt>-$iIcLSVQ{hK=lZ}bUmB6qO3V&`@ipSkNUsd8F0IahG~cGLyhQJ;J_>S4F5?Sfr|uGg=3 zxnw=1WV1|vq#{7a2IT{+#e4M~9IiDm9JkiyE6|vRi`{D1?hZq7>x+f3;0L#$&AGzz<;J!1>Uq0}CaPt#?HbSA<+b@pd;Yo>d1vOqJ4AN#=Aj`%*e4ts&tVv0 z-{gxTPxXA(4sQFwK;OT=A%NyH_K>>f7B`=9-Az*_#WW3fUfv%r?ohwq^`c$;`cZ$V M>)>*PIvNfB2Tj+{wg3PC literal 17393 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 VE;*\n') - ifile.write('\n') - ifile.write('\n' % (math.floor(maxw/10), math.floor(maxh/10), maxw, maxh)) - ifile.write('\n') - for j in xrange(0,len(gdefs)): - ifile.write(gdefs[j]) - ifile.write('\n') - for j in xrange(0,len(gids)): - ifile.write('\n' % (gids[j], xs[j], ys[j])) - ifile.write('') - ifile.close() - - return 0 - - - - # return tag at line pos in document - def lineinDoc(self, pos) : - if (pos >= 0) and (pos < self.docSize) : - item = self.docList[pos] - if item.find('=') >= 0: - (name, argres) = item.split('=',1) +import os, csv +import binascii +import zlib +import re +from struct import pack, unpack, unpack_from + +class DrmException(Exception): + pass + +global charMap1 +global charMap3 +global charMap4 + +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre: + if sys.platform.startswith('win'): + from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString + + if sys.platform.startswith('darwin'): + from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString +else: + if sys.platform.startswith('win'): + from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString + + if sys.platform.startswith('darwin'): + from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString + + +charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" +charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" + +# crypto digestroutines +import hashlib + +def MD5(message): + ctx = hashlib.md5() + ctx.update(message) + return ctx.digest() + +def SHA1(message): + ctx = hashlib.sha1() + ctx.update(message) + return ctx.digest() + + +# Encode the bytes in data with the characters in map +def encode(data, map): + result = "" + for char in data: + value = ord(char) + Q = (value ^ 0x80) // len(map) + R = value % len(map) + result += map[Q] + result += map[R] + return result + +# Hash the bytes in data and then encode the digest with the characters in map +def encodeHash(data,map): + return encode(MD5(data),map) + +# Decode the string in data with the characters in map. Returns the decoded bytes +def decode(data,map): + result = "" + for i in range (0,len(data)-1,2): + high = map.find(data[i]) + low = map.find(data[i+1]) + if (high == -1) or (low == -1) : + break + value = (((high * len(map)) ^ 0x80) & 0xFF) + low + result += pack("B",value) + return result + +# +# PID generation routines +# + +# Returns two bit at offset from a bit field +def getTwoBitsFromBitField(bitField,offset): + byteNumber = offset // 4 + bitPosition = 6 - 2*(offset % 4) + return ord(bitField[byteNumber]) >> bitPosition & 3 + +# Returns the six bits at offset from a bit field +def getSixBitsFromBitField(bitField,offset): + offset *= 3 + value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2) + return value + +# 8 bits to six bits encoding from hash to generate PID string +def encodePID(hash): + global charMap3 + PID = "" + for position in range (0,8): + PID += charMap3[getSixBitsFromBitField(hash,position)] + return PID + +# Encryption table used to generate the device PID +def generatePidEncryptionTable() : + table = [] + for counter1 in range (0,0x100): + value = counter1 + for counter2 in range (0,8): + if (value & 1 == 0) : + value = value >> 1 else : - name = item - argres = '' - return name, argres - - - # find tag in doc if within pos to end inclusive - def findinDoc(self, tagpath, pos, end) : - result = None - if end == -1 : - end = self.docSize - else: - end = min(self.docSize, end) - foundat = -1 - for j in xrange(pos, end): - item = self.docList[j] - if item.find('=') >= 0: - (name, argres) = item.split('=',1) - else : - name = item - argres = '' - if name.endswith(tagpath) : - result = argres - foundat = j - break - return foundat, result - - - # return list of start positions for the tagpath - def posinDoc(self, tagpath): - startpos = [] - pos = 0 - res = "" - while res != None : - (foundpos, res) = self.findinDoc(tagpath, pos, -1) - if res != None : - startpos.append(foundpos) - pos = foundpos + 1 - return startpos - - - # returns a vector of integers for the tagpath - def getData(self, tagpath, pos, end): - argres=[] - (foundat, argt) = self.findinDoc(tagpath, pos, end) - if (argt != None) and (len(argt) > 0) : - argList = argt.split('|') - argres = [ int(strval) for strval in argList] - return argres - - - # get the class - def getClass(self, pclass): - nclass = pclass - - # class names are an issue given topaz may start them with numerals (not allowed), - # use a mix of cases (which cause some browsers problems), and actually - # attach numbers after "_reclustered*" to the end to deal classeses that inherit - # from a base class (but then not actually provide all of these _reclustereed - # classes in the stylesheet! - - # so we clean this up by lowercasing, prepend 'cl-', and getting any baseclass - # that exists in the stylesheet first, and then adding this specific class - # after - - # also some class names have spaces in them so need to convert to dashes - if nclass != None : - nclass = nclass.replace(' ','-') - classres = '' - nclass = nclass.lower() - nclass = 'cl-' + nclass - baseclass = '' - # graphic is the base class for captions - if nclass.find('cl-cap-') >=0 : - classres = 'graphic' + ' ' - else : - # strip to find baseclass - p = nclass.find('_') - if p > 0 : - baseclass = nclass[0:p] - if baseclass in self.classList: - classres += baseclass + ' ' - classres += nclass - nclass = classres - return nclass - - - # develop a sorted description of the starting positions of - # groups and regions on the page, as well as the page type - def PageDescription(self): - - def compare(x, y): - (xtype, xval) = x - (ytype, yval) = y - if xval > yval: - return 1 - if xval == yval: - return 0 - return -1 - - result = [] - (pos, pagetype) = self.findinDoc('page.type',0,-1) - - groupList = self.posinDoc('page.group') - groupregionList = self.posinDoc('page.group.region') - pageregionList = self.posinDoc('page.region') - # integrate into one list - for j in groupList: - result.append(('grpbeg',j)) - for j in groupregionList: - result.append(('gregion',j)) - for j in pageregionList: - result.append(('pregion',j)) - result.sort(compare) - - # insert group end and page end indicators - inGroup = False - j = 0 - while True: - if j == len(result): break - rtype = result[j][0] - rval = result[j][1] - if not inGroup and (rtype == 'grpbeg') : - inGroup = True - j = j + 1 - elif inGroup and (rtype in ('grpbeg', 'pregion')): - result.insert(j,('grpend',rval)) - inGroup = False - else: - j = j + 1 - if inGroup: - result.append(('grpend',-1)) - result.append(('pageend', -1)) - return pagetype, result - - - - # build a description of the paragraph - def getParaDescription(self, start, end, regtype): - - result = [] - - # paragraph - (pos, pclass) = self.findinDoc('paragraph.class',start,end) - - pclass = self.getClass(pclass) - - # if paragraph uses extratokens (extra glyphs) then make it fixed - (pos, extraglyphs) = self.findinDoc('paragraph.extratokens',start,end) - - # build up a description of the paragraph in result and return it - # first check for the basic - all words paragraph - (pos, sfirst) = self.findinDoc('paragraph.firstWord',start,end) - (pos, slast) = self.findinDoc('paragraph.lastWord',start,end) - if (sfirst != None) and (slast != None) : - first = int(sfirst) - last = int(slast) - - makeImage = (regtype == 'vertical') or (regtype == 'table') - makeImage = makeImage or (extraglyphs != None) - if self.fixedimage: - makeImage = makeImage or (regtype == 'fixed') - - if (pclass != None): - makeImage = makeImage or (pclass.find('.inverted') >= 0) - if self.fixedimage : - makeImage = makeImage or (pclass.find('cl-f-') >= 0) - - # before creating an image make sure glyph info exists - gidList = self.getData('info.glyph.glyphID',0,-1) - - makeImage = makeImage & (len(gidList) > 0) - - if not makeImage : - # standard all word paragraph - for wordnum in xrange(first, last): - result.append(('ocr', wordnum)) - return pclass, result - - # convert paragraph to svg image - # translate first and last word into first and last glyphs - # and generate inline image and include it - glyphList = [] - firstglyphList = self.getData('word.firstGlyph',0,-1) - gidList = self.getData('info.glyph.glyphID',0,-1) - firstGlyph = firstglyphList[first] - if last < len(firstglyphList): - lastGlyph = firstglyphList[last] - else : - lastGlyph = len(gidList) - - # handle case of white sapce paragraphs with no actual glyphs in them - # by reverting to text based paragraph - if firstGlyph >= lastGlyph: - # revert to standard text based paragraph - for wordnum in xrange(first, last): - result.append(('ocr', wordnum)) - return pclass, result - - for glyphnum in xrange(firstGlyph, lastGlyph): - glyphList.append(glyphnum) - # include any extratokens if they exist - (pos, sfg) = self.findinDoc('extratokens.firstGlyph',start,end) - (pos, slg) = self.findinDoc('extratokens.lastGlyph',start,end) - if (sfg != None) and (slg != None): - for glyphnum in xrange(int(sfg), int(slg)): - glyphList.append(glyphnum) - num = self.svgcount - self.glyphs_to_image(glyphList) - self.svgcount += 1 - result.append(('svg', num)) - return pclass, result - - # this type of paragraph may be made up of multiple spans, inline - # word monograms (images), and words with semantic meaning, - # plus glyphs used to form starting letter of first word - - # need to parse this type line by line - line = start + 1 - word_class = '' - - # if end is -1 then we must search to end of document - if end == -1 : - end = self.docSize - - # seems some xml has last* coming before first* so we have to - # handle any order - sp_first = -1 - sp_last = -1 - - gl_first = -1 - gl_last = -1 - - ws_first = -1 - ws_last = -1 - - word_class = '' - - word_semantic_type = '' - - while (line < end) : - - (name, argres) = self.lineinDoc(line) - - if name.endswith('span.firstWord') : - sp_first = int(argres) - - elif name.endswith('span.lastWord') : - sp_last = int(argres) - - elif name.endswith('word.firstGlyph') : - gl_first = int(argres) - - elif name.endswith('word.lastGlyph') : - gl_last = int(argres) - - elif name.endswith('word_semantic.firstWord'): - ws_first = int(argres) - - elif name.endswith('word_semantic.lastWord'): - ws_last = int(argres) - - elif name.endswith('word.class'): - (cname, space) = argres.split('-',1) - if space == '' : space = '0' - if (cname == 'spaceafter') and (int(space) > 0) : - word_class = 'sa' - - elif name.endswith('word.img.src'): - result.append(('img' + word_class, int(argres))) - word_class = '' - - elif name.endswith('region.img.src'): - result.append(('img' + word_class, int(argres))) - - if (sp_first != -1) and (sp_last != -1): - for wordnum in xrange(sp_first, sp_last): - result.append(('ocr', wordnum)) - sp_first = -1 - sp_last = -1 - - if (gl_first != -1) and (gl_last != -1): - glyphList = [] - for glyphnum in xrange(gl_first, gl_last): - glyphList.append(glyphnum) - num = self.svgcount - self.glyphs_to_image(glyphList) - self.svgcount += 1 - result.append(('svg', num)) - gl_first = -1 - gl_last = -1 - - if (ws_first != -1) and (ws_last != -1): - for wordnum in xrange(ws_first, ws_last): - result.append(('ocr', wordnum)) - ws_first = -1 - ws_last = -1 - - line += 1 - - return pclass, result - - - def buildParagraph(self, pclass, pdesc, type, regtype) : - parares = '' - sep ='' - - classres = '' - if pclass : - classres = ' class="' + pclass + '"' - - br_lb = (regtype == 'fixed') or (regtype == 'chapterheading') or (regtype == 'vertical') - - handle_links = len(self.link_id) > 0 - - if (type == 'full') or (type == 'begin') : - parares += '' - - if (type == 'end'): - parares += ' ' - - lstart = len(parares) - - cnt = len(pdesc) - - for j in xrange( 0, cnt) : - - (wtype, num) = pdesc[j] - - if wtype == 'ocr' : - word = self.ocrtext[num] - sep = ' ' - - if handle_links: - link = self.link_id[num] - if (link > 0): - linktype = self.link_type[link-1] - title = self.link_title[link-1] - if (title == "") or (parares.rfind(title) < 0): - title=parares[lstart:] - if linktype == 'external' : - linkhref = self.link_href[link-1] - linkhtml = '' % linkhref - else : - if len(self.link_page) >= link : - ptarget = self.link_page[link-1] - 1 - linkhtml = '' % ptarget - else : - # just link to the current page - linkhtml = '' - linkhtml += title + '' - pos = parares.rfind(title) - if pos >= 0: - parares = parares[0:pos] + linkhtml + parares[pos+len(title):] - else : - parares += linkhtml - lstart = len(parares) - if word == '_link_' : word = '' - elif (link < 0) : - if word == '_link_' : word = '' - - if word == '_lb_': - if ((num-1) in self.dehyphen_rootid ) or handle_links: - word = '' - sep = '' - elif br_lb : - word = '
\n' - sep = '' - else : - word = '\n' - sep = '' - - if num in self.dehyphen_rootid : - word = word[0:-1] - sep = '' - - parares += word + sep - - elif wtype == 'img' : - sep = '' - parares += '' % num - parares += sep - - elif wtype == 'imgsa' : - sep = ' ' - parares += '' % num - parares += sep - - elif wtype == 'svg' : - sep = '' - parares += '' % num - parares += sep - - if len(sep) > 0 : parares = parares[0:-1] - if (type == 'full') or (type == 'end') : - parares += '

' - return parares - - - def buildTOCEntry(self, pdesc) : - parares = '' - sep ='' - tocentry = '' - handle_links = len(self.link_id) > 0 - - lstart = 0 - - cnt = len(pdesc) - for j in xrange( 0, cnt) : - - (wtype, num) = pdesc[j] - - if wtype == 'ocr' : - word = self.ocrtext[num] - sep = ' ' - - if handle_links: - link = self.link_id[num] - if (link > 0): - linktype = self.link_type[link-1] - title = self.link_title[link-1] - title = title.rstrip('. ') - alt_title = parares[lstart:] - alt_title = alt_title.strip() - # now strip off the actual printed page number - alt_title = alt_title.rstrip('01234567890ivxldIVXLD-.') - alt_title = alt_title.rstrip('. ') - # skip over any external links - can't have them in a books toc - if linktype == 'external' : - title = '' - alt_title = '' - linkpage = '' - else : - if len(self.link_page) >= link : - ptarget = self.link_page[link-1] - 1 - linkpage = '%04d' % ptarget - else : - # just link to the current page - linkpage = self.id[4:] - if len(alt_title) >= len(title): - title = alt_title - if title != '' and linkpage != '': - tocentry += title + '|' + linkpage + '\n' - lstart = len(parares) - if word == '_link_' : word = '' - elif (link < 0) : - if word == '_link_' : word = '' - - if word == '_lb_': - word = '' - sep = '' - - if num in self.dehyphen_rootid : - word = word[0:-1] - sep = '' - - parares += word + sep - - else : - continue - - return tocentry - - - - - # walk the document tree collecting the information needed - # to build an html page using the ocrText - - def process(self): - - tocinfo = '' - hlst = [] - - # get the ocr text - (pos, argres) = self.findinDoc('info.word.ocrText',0,-1) - if argres : self.ocrtext = argres.split('|') - - # get information to dehyphenate the text - self.dehyphen_rootid = self.getData('info.dehyphen.rootID',0,-1) - - # determine if first paragraph is continued from previous page - (pos, self.parastems_stemid) = self.findinDoc('info.paraStems.stemID',0,-1) - first_para_continued = (self.parastems_stemid != None) - - # determine if last paragraph is continued onto the next page - (pos, self.paracont_stemid) = self.findinDoc('info.paraCont.stemID',0,-1) - last_para_continued = (self.paracont_stemid != None) - - # collect link ids - self.link_id = self.getData('info.word.link_id',0,-1) - - # collect link destination page numbers - self.link_page = self.getData('info.links.page',0,-1) - - # collect link types (container versus external) - (pos, argres) = self.findinDoc('info.links.type',0,-1) - if argres : self.link_type = argres.split('|') - - # collect link destinations - (pos, argres) = self.findinDoc('info.links.href',0,-1) - if argres : self.link_href = argres.split('|') - - # collect link titles - (pos, argres) = self.findinDoc('info.links.title',0,-1) - if argres : - self.link_title = argres.split('|') - else: - self.link_title.append('') - - # get a descriptions of the starting points of the regions - # and groups on the page - (pagetype, pageDesc) = self.PageDescription() - regcnt = len(pageDesc) - 1 - - anchorSet = False - breakSet = False - inGroup = False - - # process each region on the page and convert what you can to html - - for j in xrange(regcnt): - - (etype, start) = pageDesc[j] - (ntype, end) = pageDesc[j+1] - - - # set anchor for link target on this page - if not anchorSet and not first_para_continued: - hlst.append('\n') - anchorSet = True - - # handle groups of graphics with text captions - if (etype == 'grpbeg'): - (pos, grptype) = self.findinDoc('group.type', start, end) - if grptype != None: - if grptype == 'graphic': - gcstr = ' class="' + grptype + '"' - hlst.append('') - inGroup = True - - elif (etype == 'grpend'): - if inGroup: - hlst.append('\n') - inGroup = False - - else: - (pos, regtype) = self.findinDoc('region.type',start,end) - - if regtype == 'graphic' : - (pos, simgsrc) = self.findinDoc('img.src',start,end) - if simgsrc: - if inGroup: - hlst.append('' % int(simgsrc)) - else: - hlst.append('
' % int(simgsrc)) - - elif regtype == 'chapterheading' : - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - if not breakSet: - hlst.append('
 
\n') - breakSet = True - tag = 'h1' - if pclass and (len(pclass) >= 7): - if pclass[3:7] == 'ch1-' : tag = 'h1' - if pclass[3:7] == 'ch2-' : tag = 'h2' - if pclass[3:7] == 'ch3-' : tag = 'h3' - hlst.append('<' + tag + ' class="' + pclass + '">') - else: - hlst.append('<' + tag + '>') - hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype)) - hlst.append('') - - elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'): - ptype = 'full' - # check to see if this is a continution from the previous page - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - if pclass and (len(pclass) >= 6) and (ptype == 'full'): - tag = 'p' - if pclass[3:6] == 'h1-' : tag = 'h4' - if pclass[3:6] == 'h2-' : tag = 'h5' - if pclass[3:6] == 'h3-' : tag = 'h6' - hlst.append('<' + tag + ' class="' + pclass + '">') - hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype)) - hlst.append('') - else : - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - - elif (regtype == 'tocentry') : - ptype = 'full' - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - tocinfo += self.buildTOCEntry(pdesc) - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - - elif (regtype == 'vertical') or (regtype == 'table') : - ptype = 'full' - if inGroup: - ptype = 'middle' - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start, end, regtype) - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - - - elif (regtype == 'synth_fcvr.center'): - (pos, simgsrc) = self.findinDoc('img.src',start,end) - if simgsrc: - hlst.append('
' % int(simgsrc)) - - else : - print ' Making region type', regtype, - (pos, temp) = self.findinDoc('paragraph',start,end) - (pos2, temp) = self.findinDoc('span',start,end) - if pos != -1 or pos2 != -1: - print ' a "text" region' - orig_regtype = regtype - regtype = 'fixed' - ptype = 'full' - # check to see if this is a continution from the previous page - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - if not pclass: - if orig_regtype.endswith('.right') : pclass = 'cl-right' - elif orig_regtype.endswith('.center') : pclass = 'cl-center' - elif orig_regtype.endswith('.left') : pclass = 'cl-left' - elif orig_regtype.endswith('.justify') : pclass = 'cl-justify' - if pclass and (ptype == 'full') and (len(pclass) >= 6): - tag = 'p' - if pclass[3:6] == 'h1-' : tag = 'h4' - if pclass[3:6] == 'h2-' : tag = 'h5' - if pclass[3:6] == 'h3-' : tag = 'h6' - hlst.append('<' + tag + ' class="' + pclass + '">') - hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype)) - hlst.append('') - else : - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - else : - print ' a "graphic" region' - (pos, simgsrc) = self.findinDoc('img.src',start,end) - if simgsrc: - hlst.append('
' % int(simgsrc)) - - - htmlpage = "".join(hlst) - if last_para_continued : - if htmlpage[-4:] == '

': - htmlpage = htmlpage[0:-4] - last_para_continued = False - - return htmlpage, tocinfo - - -def convert2HTML(flatxml, classlst, fileid, bookDir, gdict, fixedimage): - # create a document parser - dp = DocParser(flatxml, classlst, fileid, bookDir, gdict, fixedimage) - htmlpage, tocinfo = dp.process() - return htmlpage, tocinfo + value = value >> 1 + value = value ^ 0xEDB88320 + table.append(value) + return table + +# Seed value used to generate the device PID +def generatePidSeed(table,dsn) : + value = 0 + for counter in range (0,4) : + index = (ord(dsn[counter]) ^ value) &0xFF + value = (value >> 8) ^ table[index] + return value + +# Generate the device PID +def generateDevicePID(table,dsn,nbRoll): + global charMap4 + seed = generatePidSeed(table,dsn) + pidAscii = "" + pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF] + index = 0 + for counter in range (0,nbRoll): + pid[index] = pid[index] ^ ord(dsn[counter]) + index = (index+1) %8 + for counter in range (0,8): + index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7) + pidAscii += charMap4[index] + return pidAscii + +def crc32(s): + return (~binascii.crc32(s,-1))&0xFFFFFFFF + +# convert from 8 digit PID to 10 digit PID with checksum +def checksumPid(s): + global charMap4 + crc = crc32(s) + crc = crc ^ (crc >> 16) + res = s + l = len(charMap4) + for i in (0,1): + b = crc & 0xff + pos = (b // l) ^ (b % l) + res += charMap4[pos%l] + crc >>= 8 + return res + + +# old kindle serial number to fixed pid +def pidFromSerial(s, l): + global charMap4 + crc = crc32(s) + arr1 = [0]*l + for i in xrange(len(s)): + arr1[i%l] ^= ord(s[i]) + crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff] + for i in xrange(l): + arr1[i] ^= crc_bytes[i&3] + pid = "" + for i in xrange(l): + b = arr1[i] & 0xff + pid+=charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))] + return pid + + +# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. +def getKindlePid(pidlst, rec209, token, serialnum): + # Compute book PID + pidHash = SHA1(serialnum+rec209+token) + bookPID = encodePID(pidHash) + bookPID = checksumPid(bookPID) + pidlst.append(bookPID) + + # compute fixed pid for old pre 2.5 firmware update pid as well + bookPID = pidFromSerial(serialnum, 7) + "*" + bookPID = checksumPid(bookPID) + pidlst.append(bookPID) + + return pidlst + + +# parse the Kindleinfo file to calculate the book pid. + +keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] + +def getK4Pids(pidlst, rec209, token, kInfoFile): + global charMap1 + kindleDatabase = None + try: + kindleDatabase = getDBfromFile(kInfoFile) + except Exception, message: + print(message) + kindleDatabase = None + pass + + if kindleDatabase == None : + return pidlst + + try: + # Get the Mazama Random number + MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"] + + # Get the kindle account token + kindleAccountToken = kindleDatabase["kindle.account.tokens"] + except KeyError: + print "Keys not found in " + kInfoFile + return pidlst + + # Get the ID string used + encodedIDString = encodeHash(GetIDString(),charMap1) + + # Get the current user name + encodedUsername = encodeHash(GetUserName(),charMap1) + + # concat, hash and encode to calculate the DSN + DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1) + + # Compute the device PID (for which I can tell, is used for nothing). + table = generatePidEncryptionTable() + devicePID = generateDevicePID(table,DSN,4) + devicePID = checksumPid(devicePID) + pidlst.append(devicePID) + + # Compute book PIDs + + # book pid + pidHash = SHA1(DSN+kindleAccountToken+rec209+token) + bookPID = encodePID(pidHash) + bookPID = checksumPid(bookPID) + pidlst.append(bookPID) + + # variant 1 + pidHash = SHA1(kindleAccountToken+rec209+token) + bookPID = encodePID(pidHash) + bookPID = checksumPid(bookPID) + pidlst.append(bookPID) + + # variant 2 + pidHash = SHA1(DSN+rec209+token) + bookPID = encodePID(pidHash) + bookPID = checksumPid(bookPID) + pidlst.append(bookPID) + + return pidlst + +def getPidList(md1, md2, k4, pids, serials, kInfoFiles): + pidlst = [] + if kInfoFiles is None: + kInfoFiles = [] + if k4: + kInfoFiles = getKindleInfoFiles(kInfoFiles) + for infoFile in kInfoFiles: + pidlst = getK4Pids(pidlst, md1, md2, infoFile) + for serialnum in serials: + pidlst = getKindlePid(pidlst, md1, md2, serialnum) + for pid in pids: + pidlst.append(pid) + return pidlst diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto.dylib b/Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto.dylib new file mode 100644 index 0000000000000000000000000000000000000000..01c348cc8a638e243754aea2cd2681ad30e629a4 GIT binary patch literal 87160 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 literal 0 HcmV?d00001 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto32.so b/Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto32.so new file mode 100644 index 0000000000000000000000000000000000000000..9a5a442617a2046fb9b7050d339b18bca8993e7b GIT binary patch literal 23859 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 literal 0 HcmV?d00001 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto64.so b/Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto64.so new file mode 100644 index 0000000000000000000000000000000000000000..a08ac28930fdd54e0eb4e8973a398e5e951d3825 GIT binary patch literal 33417 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 Ko^Mvd(mwtG literal 0 HcmV?d00001 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/pbkdf2.py b/Calibre_Plugins/K4MobiDeDRM_plugin/pbkdf2.py new file mode 100644 index 0000000..65220a9 --- /dev/null +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/pbkdf2.py @@ -0,0 +1,68 @@ +# A simple implementation of pbkdf2 using stock python modules. See RFC2898 +# for details. Basically, it derives a key from a password and salt. + +# Copyright 2004 Matt Johnston +# Copyright 2009 Daniel Holth +# This code may be freely used and modified for any purpose. + +# Revision history +# v0.1 October 2004 - Initial release +# v0.2 8 March 2007 - Make usable with hashlib in Python 2.5 and use +# v0.3 "" the correct digest_size rather than always 20 +# v0.4 Oct 2009 - Rescue from chandler svn, test and optimize. + +import sys +import hmac +from struct import pack +try: + # only in python 2.5 + import hashlib + sha = hashlib.sha1 + md5 = hashlib.md5 + sha256 = hashlib.sha256 +except ImportError: # pragma: NO COVERAGE + # fallback + import sha + import md5 + +# this is what you want to call. +def pbkdf2( password, salt, itercount, keylen, hashfn = sha ): + try: + # depending whether the hashfn is from hashlib or sha/md5 + digest_size = hashfn().digest_size + except TypeError: # pragma: NO COVERAGE + digest_size = hashfn.digest_size + # l - number of output blocks to produce + l = keylen / digest_size + if keylen % digest_size != 0: + l += 1 + + h = hmac.new( password, None, hashfn ) + + T = "" + for i in range(1, l+1): + T += pbkdf2_F( h, salt, itercount, i ) + + return T[0: keylen] + +def xorstr( a, b ): + if len(a) != len(b): + raise ValueError("xorstr(): lengths differ") + return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b))) + +def prf( h, data ): + hm = h.copy() + hm.update( data ) + return hm.digest() + +# Helper as per the spec. h is a hmac which has been created seeded with the +# password, it will be copy()ed and not modified. +def pbkdf2_F( h, salt, itercount, blocknum ): + U = prf( h, salt + pack('>i',blocknum ) ) + T = U + + for i in range(2, itercount+1): + U = prf( h, U ) + T = xorstr( T, U ) + + return T diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/plugin-import-name-k4mobidedrm.txt b/Calibre_Plugins/K4MobiDeDRM_plugin/plugin-import-name-k4mobidedrm.txt index e66e9f3..e69de29 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/plugin-import-name-k4mobidedrm.txt +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/plugin-import-name-k4mobidedrm.txt @@ -1,726 +0,0 @@ -# standlone set of Mac OSX specific routines needed for KindleBooks - -from __future__ import with_statement - -import sys -import os -import os.path -import re -import copy -import subprocess -from struct import pack, unpack, unpack_from - -class DrmException(Exception): - pass - - -# interface to needed routines in openssl's libcrypto -def _load_crypto_libcrypto(): - from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \ - Structure, c_ulong, create_string_buffer, addressof, string_at, cast - from ctypes.util import find_library - - libcrypto = find_library('crypto') - if libcrypto is None: - raise DrmException('libcrypto not found') - libcrypto = CDLL(libcrypto) - - # From OpenSSL's crypto aes header - # - # AES_ENCRYPT 1 - # AES_DECRYPT 0 - # AES_MAXNR 14 (in bytes) - # AES_BLOCK_SIZE 16 (in bytes) - # - # struct aes_key_st { - # unsigned long rd_key[4 *(AES_MAXNR + 1)]; - # int rounds; - # }; - # typedef struct aes_key_st AES_KEY; - # - # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); - # - # note: the ivec string, and output buffer are both mutable - # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, - # const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc); - - AES_MAXNR = 14 - c_char_pp = POINTER(c_char_p) - c_int_p = POINTER(c_int) - - class AES_KEY(Structure): - _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)] - AES_KEY_p = POINTER(AES_KEY) - - def F(restype, name, argtypes): - func = getattr(libcrypto, name) - func.restype = restype - func.argtypes = argtypes - return func - - AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int]) - - AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p]) - - # From OpenSSL's Crypto evp/p5_crpt2.c - # - # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen, - # const unsigned char *salt, int saltlen, int iter, - # int keylen, unsigned char *out); - - PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', - [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) - - class LibCrypto(object): - def __init__(self): - self._blocksize = 0 - self._keyctx = None - self._iv = 0 - - def set_decrypt_key(self, userkey, iv): - self._blocksize = len(userkey) - if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : - raise DrmException('AES improper key used') - return - keyctx = self._keyctx = AES_KEY() - self._iv = iv - self._userkey = userkey - rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) - if rv < 0: - raise DrmException('Failed to initialize AES key') - - def decrypt(self, data): - out = create_string_buffer(len(data)) - mutable_iv = create_string_buffer(self._iv, len(self._iv)) - keyctx = self._keyctx - rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0) - if rv == 0: - raise DrmException('AES decryption failed') - return out.raw - - def keyivgen(self, passwd, salt, iter, keylen): - saltlen = len(salt) - passlen = len(passwd) - out = create_string_buffer(keylen) - rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out) - return out.raw - return LibCrypto - -def _load_crypto(): - LibCrypto = None - try: - LibCrypto = _load_crypto_libcrypto() - except (ImportError, DrmException): - pass - return LibCrypto - -LibCrypto = _load_crypto() - -# -# Utility Routines -# - -# crypto digestroutines -import hashlib - -def MD5(message): - ctx = hashlib.md5() - ctx.update(message) - return ctx.digest() - -def SHA1(message): - ctx = hashlib.sha1() - ctx.update(message) - return ctx.digest() - -def SHA256(message): - ctx = hashlib.sha256() - ctx.update(message) - return ctx.digest() - -# Various character maps used to decrypt books. Probably supposed to act as obfuscation -charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" -charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" - -# For kinf approach of K4Mac 1.6.X or later -# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE" -# For Mac they seem to re-use charMap2 here -charMap5 = charMap2 - -# new in K4M 1.9.X -testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD" - - -def encode(data, map): - result = "" - for char in data: - value = ord(char) - Q = (value ^ 0x80) // len(map) - R = value % len(map) - result += map[Q] - result += map[R] - return result - -# Hash the bytes in data and then encode the digest with the characters in map -def encodeHash(data,map): - return encode(MD5(data),map) - -# Decode the string in data with the characters in map. Returns the decoded bytes -def decode(data,map): - result = "" - for i in range (0,len(data)-1,2): - high = map.find(data[i]) - low = map.find(data[i+1]) - if (high == -1) or (low == -1) : - break - value = (((high * len(map)) ^ 0x80) & 0xFF) + low - result += pack("B",value) - return result - -# For K4M 1.6.X and later -# generate table of prime number less than or equal to int n -def primes(n): - if n==2: return [2] - elif n<2: return [] - s=range(3,n+1,2) - mroot = n ** 0.5 - half=(n+1)/2-1 - i=0 - m=3 - while m <= mroot: - if s[i]: - j=(m*m-3)/2 - s[j]=0 - while j 7: - return mungedmac - sernum = GetVolumeSerialNumber() - if len(sernum) > 7: - return sernum - diskpart = GetUserHomeAppSupKindleDirParitionName() - uuidnum = GetDiskPartitionUUID(diskpart) - if len(uuidnum) > 7: - return uuidnum - mungedmac = GetMACAddressMunged() - if len(mungedmac) > 7: - return mungedmac - return '9999999999' - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used by Kindle for Mac versions < 1.6.0 -class CryptUnprotectData(object): - def __init__(self): - sernum = GetVolumeSerialNumber() - if sernum == '': - sernum = '9999999999' - sp = sernum + '!@#' + GetUserName() - passwdData = encode(SHA256(sp),charMap1) - salt = '16743' - self.crp = LibCrypto() - iter = 0x3e8 - keylen = 0x80 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext,charMap1) - return cleartext - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used for Kindle for Mac Versions >= 1.6.0 -class CryptUnprotectDataV2(object): - def __init__(self): - sp = GetUserName() + ':&%:' + GetIDString() - passwdData = encode(SHA256(sp),charMap5) - # salt generation as per the code - salt = 0x0512981d * 2 * 1 * 1 - salt = str(salt) + GetUserName() - salt = encode(salt,charMap5) - self.crp = LibCrypto() - iter = 0x800 - keylen = 0x400 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext, charMap5) - return cleartext - - -# unprotect the new header blob in .kinf2011 -# used in Kindle for Mac Version >= 1.9.0 -def UnprotectHeaderData(encryptedData): - passwdData = 'header_key_data' - salt = 'HEADER.2011' - iter = 0x80 - keylen = 0x100 - crp = LibCrypto() - key_iv = crp.keyivgen(passwdData, salt, iter, keylen) - key = key_iv[0:32] - iv = key_iv[32:48] - crp.set_decrypt_key(key,iv) - cleartext = crp.decrypt(encryptedData) - return cleartext - - -# implements an Pseudo Mac Version of Windows built-in Crypto routine -# used for Kindle for Mac Versions >= 1.9.0 -class CryptUnprotectDataV3(object): - def __init__(self, entropy): - sp = GetUserName() + '+@#$%+' + GetIDString() - passwdData = encode(SHA256(sp),charMap2) - salt = entropy - self.crp = LibCrypto() - iter = 0x800 - keylen = 0x400 - key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen) - self.key = key_iv[0:32] - self.iv = key_iv[32:48] - self.crp.set_decrypt_key(self.key, self.iv) - - def decrypt(self, encryptedData): - cleartext = self.crp.decrypt(encryptedData) - cleartext = decode(cleartext, charMap2) - return cleartext - - -# Locate the .kindle-info files -def getKindleInfoFiles(kInfoFiles): - # first search for current .kindle-info files - home = os.getenv('HOME') - cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' - cmdline = cmdline.encode(sys.getfilesystemencoding()) - p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) - out1, out2 = p1.communicate() - reslst = out1.split('\n') - kinfopath = 'NONE' - found = False - for resline in reslst: - if os.path.isfile(resline): - kInfoFiles.append(resline) - found = True - # add any .rainier*-kinf files - cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"' - cmdline = cmdline.encode(sys.getfilesystemencoding()) - p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) - out1, out2 = p1.communicate() - reslst = out1.split('\n') - for resline in reslst: - if os.path.isfile(resline): - kInfoFiles.append(resline) - found = True - # add any .kinf2011 files - cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"' - cmdline = cmdline.encode(sys.getfilesystemencoding()) - p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) - out1, out2 = p1.communicate() - reslst = out1.split('\n') - for resline in reslst: - if os.path.isfile(resline): - kInfoFiles.append(resline) - found = True - if not found: - print('No kindle-info files have been found.') - return kInfoFiles - -# determine type of kindle info provided and return a -# database of keynames and values -def getDBfromFile(kInfoFile): - names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"] - DB = {} - cnt = 0 - infoReader = open(kInfoFile, 'r') - hdr = infoReader.read(1) - data = infoReader.read() - - if data.find('[') != -1 : - - # older style kindle-info file - cud = CryptUnprotectData() - items = data.split('[') - for item in items: - if item != '': - keyhash, rawdata = item.split(':') - keyname = "unknown" - for name in names: - if encodeHash(name,charMap2) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - encryptedValue = decode(rawdata,charMap2) - cleartext = cud.decrypt(encryptedValue) - DB[keyname] = cleartext - cnt = cnt + 1 - if cnt == 0: - DB = None - return DB - - if hdr == '/': - - # else newer style .kinf file used by K4Mac >= 1.6.0 - # the .kinf file uses "/" to separate it into records - # so remove the trailing "/" to make it easy to use split - data = data[:-1] - items = data.split('/') - cud = CryptUnprotectDataV2() - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - keyname = "unknown" - - # the raw keyhash string is also used to create entropy for the actual - # CryptProtectData Blob that represents that keys contents - # "entropy" not used for K4Mac only K4PC - # entropy = SHA1(keyhash) - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,charMap5) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - - # the charMap5 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using charMap5 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the charMap5 encoded contents seems to be: - # len(contents) - largest prime number less than or equal to int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by charMap5 - encdata = "".join(edlst) - contlen = len(encdata) - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using charMap5 to get the CryptProtect Data - encryptedValue = decode(encdata,charMap5) - cleartext = cud.decrypt(encryptedValue) - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB - - # the latest .kinf2011 version for K4M 1.9.1 - # put back the hdr char, it is needed - data = hdr + data - data = data[:-1] - items = data.split('/') - - # the headerblob is the encrypted information needed to build the entropy string - headerblob = items.pop(0) - encryptedValue = decode(headerblob, charMap1) - cleartext = UnprotectHeaderData(encryptedValue) - - # now extract the pieces in the same way - # this version is different from K4PC it scales the build number by multipying by 735 - pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE) - for m in re.finditer(pattern, cleartext): - entropy = str(int(m.group(2)) * 0x2df) + m.group(4) - - cud = CryptUnprotectDataV3(entropy) - - # loop through the item records until all are processed - while len(items) > 0: - - # get the first item record - item = items.pop(0) - - # the first 32 chars of the first record of a group - # is the MD5 hash of the key name encoded by charMap5 - keyhash = item[0:32] - keyname = "unknown" - - # unlike K4PC the keyhash is not used in generating entropy - # entropy = SHA1(keyhash) + added_entropy - # entropy = added_entropy - - # the remainder of the first record when decoded with charMap5 - # has the ':' split char followed by the string representation - # of the number of records that follow - # and make up the contents - srcnt = decode(item[34:],charMap5) - rcnt = int(srcnt) - - # read and store in rcnt records of data - # that make up the contents value - edlst = [] - for i in xrange(rcnt): - item = items.pop(0) - edlst.append(item) - - keyname = "unknown" - for name in names: - if encodeHash(name,testMap8) == keyhash: - keyname = name - break - if keyname == "unknown": - keyname = keyhash - - # the testMap8 encoded contents data has had a length - # of chars (always odd) cut off of the front and moved - # to the end to prevent decoding using testMap8 from - # working properly, and thereby preventing the ensuing - # CryptUnprotectData call from succeeding. - - # The offset into the testMap8 encoded contents seems to be: - # len(contents) - largest prime number less than or equal to int(len(content)/3) - # (in other words split "about" 2/3rds of the way through) - - # move first offsets chars to end to align for decode by testMap8 - encdata = "".join(edlst) - contlen = len(encdata) - - # now properly split and recombine - # by moving noffset chars from the start of the - # string to the end of the string - noffset = contlen - primes(int(contlen/3))[-1] - pfx = encdata[0:noffset] - encdata = encdata[noffset:] - encdata = encdata + pfx - - # decode using testMap8 to get the CryptProtect Data - encryptedValue = decode(encdata,testMap8) - cleartext = cud.decrypt(encryptedValue) - # print keyname - # print cleartext - DB[keyname] = cleartext - cnt = cnt + 1 - - if cnt == 0: - DB = None - return DB diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/scrolltextwidget.py b/Calibre_Plugins/K4MobiDeDRM_plugin/scrolltextwidget.py new file mode 100644 index 0000000..98b4147 --- /dev/null +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/scrolltextwidget.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +import Tkinter +import Tkconstants + +# basic scrolled text widget +class ScrolledText(Tkinter.Text): + def __init__(self, master=None, **kw): + self.frame = Tkinter.Frame(master) + self.vbar = Tkinter.Scrollbar(self.frame) + self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y) + kw.update({'yscrollcommand': self.vbar.set}) + Tkinter.Text.__init__(self, self.frame, **kw) + self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True) + self.vbar['command'] = self.yview + # Copy geometry methods of self.frame without overriding Text + # methods = hack! + text_meths = vars(Tkinter.Text).keys() + methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys() + methods = set(methods).difference(text_meths) + for m in methods: + if m[0] != '_' and m != 'config' and m != 'configure': + setattr(self, m, getattr(self.frame, m)) + + def __str__(self): + return str(self.frame) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/stylexml2css.py b/Calibre_Plugins/K4MobiDeDRM_plugin/stylexml2css.py index a412a7b..adbac49 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/stylexml2css.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/stylexml2css.py @@ -1,134 +1,51 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -sys.stdout=Unbuffered(sys.stdout) +# For use with Topaz Scripts Version 2.6 import csv +import sys import os import getopt +import re from struct import pack from struct import unpack -class TpzDRMError(Exception): - pass - -# local support routines -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -if inCalibre : - from calibre_plugins.k4mobidedrm import convert2xml - from calibre_plugins.k4mobidedrm import flatxml2html - from calibre_plugins.k4mobidedrm import flatxml2svg - from calibre_plugins.k4mobidedrm import stylexml2css -else : - import convert2xml - import flatxml2html - import flatxml2svg - import stylexml2css - -# global switch -buildXML = False - -# Get a 7 bit encoded number from a file -def readEncodedNumber(file): - flag = False - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - if data == 0xFF: - flag = True - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - if data >= 0x80: - datax = (data & 0x7F) - while data >= 0x80 : - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - datax = (datax <<7) + (data & 0x7F) - data = datax - if flag: - data = -data - return data - -# Get a length prefixed string from the file -def lengthPrefixString(data): - return encodeNumber(len(data))+data - -def readString(file): - stringLength = readEncodedNumber(file) - if (stringLength == None): - return None - sv = file.read(stringLength) - if (len(sv) != stringLength): - return "" - return unpack(str(stringLength)+"s",sv)[0] - -def getMetaArray(metaFile): - # parse the meta file - result = {} - fo = file(metaFile,'rb') - size = readEncodedNumber(fo) - for i in xrange(size): - tag = readString(fo) - value = readString(fo) - result[tag] = value - # print tag, value - fo.close() - return result - - -# dictionary of all text strings by index value -class Dictionary(object): - def __init__(self, dictFile): - self.filename = dictFile - self.size = 0 - self.fo = file(dictFile,'rb') - self.stable = [] - self.size = readEncodedNumber(self.fo) - for i in xrange(self.size): - self.stable.append(self.escapestr(readString(self.fo))) - self.pos = 0 - def escapestr(self, str): - str = str.replace('&','&') - str = str.replace('<','<') - str = str.replace('>','>') - str = str.replace('=','=') - return str - def lookup(self,val): - if ((val >= 0) and (val < self.size)) : - self.pos = val - return self.stable[self.pos] - else: - print "Error - %d outside of string table limits" % val - raise TpzDRMError('outside or string table limits') - # sys.exit(-1) - def getSize(self): - return self.size - def getPos(self): - return self.pos - -class PageDimParser(object): - def __init__(self, flatxml): +class DocParser(object): + def __init__(self, flatxml, fontsize, ph, pw): self.flatdoc = flatxml.split('\n') + self.fontsize = int(fontsize) + self.ph = int(ph) * 1.0 + self.pw = int(pw) * 1.0 + + stags = { + 'paragraph' : 'p', + 'graphic' : '.graphic' + } + + attr_val_map = { + 'hang' : 'text-indent: ', + 'indent' : 'text-indent: ', + 'line-space' : 'line-height: ', + 'margin-bottom' : 'margin-bottom: ', + 'margin-left' : 'margin-left: ', + 'margin-right' : 'margin-right: ', + 'margin-top' : 'margin-top: ', + 'space-after' : 'padding-bottom: ', + } + + attr_str_map = { + 'align-center' : 'text-align: center; margin-left: auto; margin-right: auto;', + 'align-left' : 'text-align: left;', + 'align-right' : 'text-align: right;', + 'align-justify' : 'text-align: justify;', + 'display-inline' : 'display: inline;', + 'pos-left' : 'text-align: left;', + 'pos-right' : 'text-align: right;', + 'pos-center' : 'text-align: center; margin-left: auto; margin-right: auto;', + } + + # find tag if within pos to end inclusive def findinDoc(self, tagpath, pos, end) : result = None @@ -142,7 +59,7 @@ class PageDimParser(object): for j in xrange(pos, end): item = docList[j] if item.find('=') >= 0: - (name, argres) = item.split('=') + (name, argres) = item.split('=',1) else : name = item argres = '' @@ -151,559 +68,196 @@ class PageDimParser(object): foundat = j break return foundat, result + + + # return list of start positions for the tagpath + def posinDoc(self, tagpath): + startpos = [] + pos = 0 + res = "" + while res != None : + (foundpos, res) = self.findinDoc(tagpath, pos, -1) + if res != None : + startpos.append(foundpos) + pos = foundpos + 1 + return startpos + + # returns a vector of integers for the tagpath + def getData(self, tagpath, pos, end, clean=False): + if clean: + digits_only = re.compile(r'''([0-9]+)''') + argres=[] + (foundat, argt) = self.findinDoc(tagpath, pos, end) + if (argt != None) and (len(argt) > 0) : + argList = argt.split('|') + for strval in argList: + if clean: + m = re.search(digits_only, strval) + if m != None: + strval = m.group() + argres.append(int(strval)) + return argres + def process(self): - (pos, sph) = self.findinDoc('page.h',0,-1) - (pos, spw) = self.findinDoc('page.w',0,-1) - if (sph == None): sph = '-1' - if (spw == None): spw = '-1' - return sph, spw -def getPageDim(flatxml): + classlst = '' + csspage = '.cl-center { text-align: center; margin-left: auto; margin-right: auto; }\n' + csspage += '.cl-right { text-align: right; }\n' + csspage += '.cl-left { text-align: left; }\n' + csspage += '.cl-justify { text-align: justify; }\n' + + # generate a list of each