mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-24 14:38:56 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85e3db8f7c | ||
|
|
29338db228 | ||
|
|
608159d71b | ||
|
|
20e0850001 | ||
|
|
ffd7d41bcd | ||
|
|
75cad40804 | ||
|
|
18d6413467 | ||
|
|
7619ee4e0f | ||
|
|
a390d7a207 | ||
|
|
f04f9eca04 | ||
|
|
1ece09023c | ||
|
|
8d9f384492 | ||
|
|
c3fbb83dbc | ||
|
|
0f23eac3b8 | ||
|
|
bcdcb23d0d | ||
|
|
a252dd0da6 | ||
|
|
2042354788 | ||
|
|
9bea20c7ab | ||
|
|
415df655a1 | ||
|
|
346b3e312c | ||
|
|
f981a548a3 | ||
|
|
4084a49872 | ||
|
|
347ad3cc05 | ||
|
|
e4b702e241 | ||
|
|
691a3d6955 | ||
|
|
fa317dc1cf | ||
|
|
6f0c36b67a | ||
|
|
ceacdbbb1b | ||
|
|
ff03c68674 | ||
|
|
84d4e4e0c8 | ||
|
|
a553df50d7 | ||
|
|
0b244b5781 | ||
|
|
ab4597dfd7 | ||
|
|
82e9927ace | ||
|
|
528092c05d | ||
|
|
faa19cc19b | ||
|
|
e6592841b6 | ||
|
|
482ac15055 | ||
|
|
cb74bd8cef |
Binary file not shown.
@@ -24,7 +24,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>droplet</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>DeDRM AppleScript 6.5.3 Written 2010–2017 by Apprentice Alf et al.</string>
|
||||
<string>DeDRM AppleScript 6.6.0 Written 2010–2018 by Apprentice Alf et al.</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>DeDRM</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -36,13 +36,13 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>6.5.3</string>
|
||||
<string>6.6.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>dplt</string>
|
||||
<key>LSRequiresCarbon</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2010–2017 Apprentice Alf</string>
|
||||
<string>Copyright © 2010–2018 Apprentice Alf</string>
|
||||
<key>WindowState</key>
|
||||
<dict>
|
||||
<key>bundleDividerCollapsed</key>
|
||||
|
||||
Binary file not shown.
@@ -2,6 +2,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# __init__.py for DeDRM_plugin
|
||||
# Copyright © 2008-2018 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -57,6 +61,10 @@ __docformat__ = 'restructuredtext en'
|
||||
# 6.5.1 - Updated version number, added PDF check for DRM-free documents
|
||||
# 6.5.2 - Another Topaz fix
|
||||
# 6.5.3 - Warn about KFX files explicitly
|
||||
# 6.5.4 - Mac App Fix, improve PDF decryption, handle latest tcl changes in ActivePython
|
||||
# 6.5.5 - Finally a fix for the Windows non-ASCII user names.
|
||||
# 6.6.0 - Add kfx and kfx-zip as supported file types (also invoke this plugin if the original
|
||||
# imported format was azw8 since that may be converted to kfx)
|
||||
|
||||
|
||||
"""
|
||||
@@ -64,7 +72,7 @@ Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 5, 3)
|
||||
PLUGIN_VERSION_TUPLE = (6, 6, 0)
|
||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||
@@ -112,7 +120,7 @@ class DeDRM(FileTypePlugin):
|
||||
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','azw8','tpz','kfx','kfx-zip'])
|
||||
on_import = True
|
||||
priority = 600
|
||||
|
||||
@@ -288,8 +296,8 @@ class DeDRM(FileTypePlugin):
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
|
||||
# import the Adobe Adept ePub handler
|
||||
import calibre_plugins.dedrm.ineptepub as ineptepub
|
||||
@@ -379,17 +387,19 @@ class DeDRM(FileTypePlugin):
|
||||
except:
|
||||
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
traceback.print_exc()
|
||||
print u"{0} v{1}: Decrypted with new default key after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Decrypted with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
print u"{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
traceback.print_exc()
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
|
||||
# Not a Barnes & Noble nor an Adobe Adept
|
||||
# Import the fixed epub.
|
||||
@@ -488,8 +498,8 @@ class DeDRM(FileTypePlugin):
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
|
||||
|
||||
def KindleMobiDecrypt(self,path_to_ebook):
|
||||
@@ -556,8 +566,8 @@ class DeDRM(FileTypePlugin):
|
||||
pass
|
||||
if not decoded:
|
||||
#if you reached here then no luck raise and exception
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
|
||||
of = self.temporary_file(book.getBookExtension())
|
||||
book.getFile(of.name)
|
||||
@@ -591,8 +601,8 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
|
||||
|
||||
def run(self, path_to_ebook):
|
||||
@@ -605,7 +615,7 @@ class DeDRM(FileTypePlugin):
|
||||
self.starttime = time.time()
|
||||
|
||||
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
|
||||
if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz']:
|
||||
if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz','kfx-zip']:
|
||||
# Kindle/Mobipocket
|
||||
decrypted_ebook = self.KindleMobiDecrypt(path_to_ebook)
|
||||
elif booktype == 'pdb':
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignobleepub.pyw, version 3.8
|
||||
# ignobleepub.pyw, version 4.1
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015–2017 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.6
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
@@ -35,13 +36,14 @@ from __future__ import with_statement
|
||||
# 3.8 - Fixed to retain zip file metadata (e.g. file modification date)
|
||||
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 4.0 - Work if TkInter is missing
|
||||
# 4.1 - Import tkFileDialog, don't assume something else will import it.
|
||||
|
||||
"""
|
||||
Decrypt Barnes & Noble encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "4.0"
|
||||
__version__ = "4.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -337,6 +339,7 @@ def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 6.5
|
||||
# ineptepub.pyw, version 6.6
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015–2016 by Apprentice Harper
|
||||
# Modified 2015–2017 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
@@ -42,13 +42,14 @@ from __future__ import with_statement
|
||||
# 6.3 - Add additional check on DER file sanity
|
||||
# 6.4 - Remove erroneous check on DER file sanity
|
||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||
# 6.6 - Import tkFileDialog, don't assume something else will import it.
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "6.5"
|
||||
__version__ = "6.6"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -484,6 +485,7 @@ def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptpdf.pyw, version 8.0.4
|
||||
# ineptpdf.pyw, version 8.0.6
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015-2016 by Apprentice Harper
|
||||
# Modified 2015-2017 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
@@ -58,6 +58,7 @@ from __future__ import with_statement
|
||||
# 8.0.3 - Remove erroneous check on DER file sanity
|
||||
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
||||
# 8.0.5 - Do not process DRM-free documents
|
||||
# 8.0.6 - Replace use of float by Decimal for greater precision, and import tkFileDialog
|
||||
|
||||
|
||||
"""
|
||||
@@ -65,7 +66,7 @@ Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0.5"
|
||||
__version__ = "8.0.6"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -73,6 +74,7 @@ import re
|
||||
import zlib
|
||||
import struct
|
||||
import hashlib
|
||||
from decimal import *
|
||||
from itertools import chain, islice
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
@@ -653,7 +655,7 @@ class PSBaseParser(object):
|
||||
return (self.parse_number, j+1)
|
||||
if c == '.':
|
||||
self.token = c
|
||||
return (self.parse_float, j+1)
|
||||
return (self.parse_decimal, j+1)
|
||||
if c.isalpha():
|
||||
self.token = c
|
||||
return (self.parse_keyword, j+1)
|
||||
@@ -718,20 +720,21 @@ class PSBaseParser(object):
|
||||
c = s[j]
|
||||
if c == '.':
|
||||
self.token += c
|
||||
return (self.parse_float, j+1)
|
||||
return (self.parse_decimal, j+1)
|
||||
try:
|
||||
self.add_token(int(self.token))
|
||||
except ValueError:
|
||||
pass
|
||||
return (self.parse_main, j)
|
||||
def parse_float(self, s, i):
|
||||
|
||||
def parse_decimal(self, s, i):
|
||||
m = END_NUMBER.search(s, i)
|
||||
if not m:
|
||||
self.token += s[i:]
|
||||
return (self.parse_float, len(s))
|
||||
return (self.parse_decimal, len(s))
|
||||
j = m.start(0)
|
||||
self.token += s[i:j]
|
||||
self.add_token(float(self.token))
|
||||
self.add_token(Decimal(self.token))
|
||||
return (self.parse_main, j)
|
||||
|
||||
def parse_keyword(self, s, i):
|
||||
@@ -933,7 +936,7 @@ class PSStackParser(PSBaseParser):
|
||||
(pos, token) = self.nexttoken()
|
||||
##print (pos,token), (self.curtype, self.curstack)
|
||||
if (isinstance(token, int) or
|
||||
isinstance(token, float) or
|
||||
isinstance(token, Decimal) or
|
||||
isinstance(token, bool) or
|
||||
isinstance(token, str) or
|
||||
isinstance(token, PSLiteral)):
|
||||
@@ -1062,17 +1065,17 @@ def int_value(x):
|
||||
return 0
|
||||
return x
|
||||
|
||||
def float_value(x):
|
||||
def decimal_value(x):
|
||||
x = resolve1(x)
|
||||
if not isinstance(x, float):
|
||||
if not isinstance(x, Decimal):
|
||||
if STRICT:
|
||||
raise PDFTypeError('Float required: %r' % x)
|
||||
raise PDFTypeError('Decimal required: %r' % x)
|
||||
return 0.0
|
||||
return x
|
||||
|
||||
def num_value(x):
|
||||
x = resolve1(x)
|
||||
if not (isinstance(x, int) or isinstance(x, float)):
|
||||
if not (isinstance(x, int) or isinstance(x, Decimal)):
|
||||
if STRICT:
|
||||
raise PDFTypeError('Int or Float required: %r' % x)
|
||||
return 0
|
||||
@@ -2142,7 +2145,11 @@ class PDFSerializer(object):
|
||||
if self.last.isalnum():
|
||||
self.write(' ')
|
||||
self.write(str(obj).lower())
|
||||
elif isinstance(obj, (int, long, float)):
|
||||
elif isinstance(obj, (int, long)):
|
||||
if self.last.isalnum():
|
||||
self.write(' ')
|
||||
self.write(str(obj))
|
||||
elif isinstance(obj, Decimal):
|
||||
if self.last.isalnum():
|
||||
self.write(' ')
|
||||
self.write(str(obj))
|
||||
@@ -2218,6 +2225,7 @@ def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
|
||||
981
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ion.py
Normal file
981
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ion.py
Normal file
@@ -0,0 +1,981 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Pascal implementation by lulzkabulz. Python translation by apprenticenaomi. DeDRM integration by anon.
|
||||
# BinaryIon.pas + DrmIon.pas + IonSymbols.pas
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import collections
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import os.path
|
||||
import struct
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.py3compat import bchr, bord
|
||||
|
||||
try:
|
||||
# lzma library from calibre 2.35.0 or later
|
||||
import lzma.lzma1 as calibre_lzma
|
||||
except:
|
||||
calibre_lzma = None
|
||||
try:
|
||||
import lzma
|
||||
except:
|
||||
# Need pip backports.lzma on Python <3.3
|
||||
from backports import lzma
|
||||
|
||||
|
||||
TID_NULL = 0
|
||||
TID_BOOLEAN = 1
|
||||
TID_POSINT = 2
|
||||
TID_NEGINT = 3
|
||||
TID_FLOAT = 4
|
||||
TID_DECIMAL = 5
|
||||
TID_TIMESTAMP = 6
|
||||
TID_SYMBOL = 7
|
||||
TID_STRING = 8
|
||||
TID_CLOB = 9
|
||||
TID_BLOB = 0xA
|
||||
TID_LIST = 0xB
|
||||
TID_SEXP = 0xC
|
||||
TID_STRUCT = 0xD
|
||||
TID_TYPEDECL = 0xE
|
||||
TID_UNUSED = 0xF
|
||||
|
||||
|
||||
SID_UNKNOWN = -1
|
||||
SID_ION = 1
|
||||
SID_ION_1_0 = 2
|
||||
SID_ION_SYMBOL_TABLE = 3
|
||||
SID_NAME = 4
|
||||
SID_VERSION = 5
|
||||
SID_IMPORTS = 6
|
||||
SID_SYMBOLS = 7
|
||||
SID_MAX_ID = 8
|
||||
SID_ION_SHARED_SYMBOL_TABLE = 9
|
||||
SID_ION_1_0_MAX = 10
|
||||
|
||||
|
||||
LEN_IS_VAR_LEN = 0xE
|
||||
LEN_IS_NULL = 0xF
|
||||
|
||||
|
||||
VERSION_MARKER = b"\x01\x00\xEA"
|
||||
|
||||
|
||||
# asserts must always raise exceptions for proper functioning
|
||||
def _assert(test, msg="Exception"):
|
||||
if not test:
|
||||
raise Exception(msg)
|
||||
|
||||
|
||||
class SystemSymbols(object):
|
||||
ION = '$ion'
|
||||
ION_1_0 = '$ion_1_0'
|
||||
ION_SYMBOL_TABLE = '$ion_symbol_table'
|
||||
NAME = 'name'
|
||||
VERSION = 'version'
|
||||
IMPORTS = 'imports'
|
||||
SYMBOLS = 'symbols'
|
||||
MAX_ID = 'max_id'
|
||||
ION_SHARED_SYMBOL_TABLE = '$ion_shared_symbol_table'
|
||||
|
||||
|
||||
class IonCatalogItem(object):
|
||||
name = ""
|
||||
version = 0
|
||||
symnames = []
|
||||
|
||||
def __init__(self, name, version, symnames):
|
||||
self.name = name
|
||||
self.version = version
|
||||
self.symnames = symnames
|
||||
|
||||
|
||||
class SymbolToken(object):
|
||||
text = ""
|
||||
sid = 0
|
||||
|
||||
def __init__(self, text, sid):
|
||||
if text == "" and sid == 0:
|
||||
raise ValueError("Symbol token must have Text or SID")
|
||||
|
||||
self.text = text
|
||||
self.sid = sid
|
||||
|
||||
|
||||
class SymbolTable(object):
|
||||
table = None
|
||||
|
||||
def __init__(self):
|
||||
self.table = [None] * SID_ION_1_0_MAX
|
||||
self.table[SID_ION] = SystemSymbols.ION
|
||||
self.table[SID_ION_1_0] = SystemSymbols.ION_1_0
|
||||
self.table[SID_ION_SYMBOL_TABLE] = SystemSymbols.ION_SYMBOL_TABLE
|
||||
self.table[SID_NAME] = SystemSymbols.NAME
|
||||
self.table[SID_VERSION] = SystemSymbols.VERSION
|
||||
self.table[SID_IMPORTS] = SystemSymbols.IMPORTS
|
||||
self.table[SID_SYMBOLS] = SystemSymbols.SYMBOLS
|
||||
self.table[SID_MAX_ID] = SystemSymbols.MAX_ID
|
||||
self.table[SID_ION_SHARED_SYMBOL_TABLE] = SystemSymbols.ION_SHARED_SYMBOL_TABLE
|
||||
|
||||
def findbyid(self, sid):
|
||||
if sid < 1:
|
||||
raise ValueError("Invalid symbol id")
|
||||
|
||||
if sid < len(self.table):
|
||||
return self.table[sid]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def import_(self, table, maxid):
|
||||
for i in range(maxid):
|
||||
self.table.append(table.symnames[i])
|
||||
|
||||
def importunknown(self, name, maxid):
|
||||
for i in range(maxid):
|
||||
self.table.append("%s#%d" % (name, i + 1))
|
||||
|
||||
|
||||
class ParserState:
|
||||
Invalid,BeforeField,BeforeTID,BeforeValue,AfterValue,EOF = 1,2,3,4,5,6
|
||||
|
||||
ContainerRec = collections.namedtuple("ContainerRec", "nextpos, tid, remaining")
|
||||
|
||||
|
||||
class BinaryIonParser(object):
|
||||
eof = False
|
||||
state = None
|
||||
localremaining = 0
|
||||
needhasnext = False
|
||||
isinstruct = False
|
||||
valuetid = 0
|
||||
valuefieldid = 0
|
||||
parenttid = 0
|
||||
valuelen = 0
|
||||
valueisnull = False
|
||||
valueistrue = False
|
||||
value = None
|
||||
didimports = False
|
||||
|
||||
def __init__(self, stream):
|
||||
self.annotations = []
|
||||
self.catalog = []
|
||||
|
||||
self.stream = stream
|
||||
self.initpos = stream.tell()
|
||||
self.reset()
|
||||
self.symbols = SymbolTable()
|
||||
|
||||
def reset(self):
|
||||
self.state = ParserState.BeforeTID
|
||||
self.needhasnext = True
|
||||
self.localremaining = -1
|
||||
self.eof = False
|
||||
self.isinstruct = False
|
||||
self.containerstack = []
|
||||
self.stream.seek(self.initpos)
|
||||
|
||||
def addtocatalog(self, name, version, symbols):
|
||||
self.catalog.append(IonCatalogItem(name, version, symbols))
|
||||
|
||||
def hasnext(self):
|
||||
while self.needhasnext and not self.eof:
|
||||
self.hasnextraw()
|
||||
if len(self.containerstack) == 0 and not self.valueisnull:
|
||||
if self.valuetid == TID_SYMBOL:
|
||||
if self.value == SID_ION_1_0:
|
||||
self.needhasnext = True
|
||||
elif self.valuetid == TID_STRUCT:
|
||||
for a in self.annotations:
|
||||
if a == SID_ION_SYMBOL_TABLE:
|
||||
self.parsesymboltable()
|
||||
self.needhasnext = True
|
||||
break
|
||||
return not self.eof
|
||||
|
||||
def hasnextraw(self):
|
||||
self.clearvalue()
|
||||
while self.valuetid == -1 and not self.eof:
|
||||
self.needhasnext = False
|
||||
if self.state == ParserState.BeforeField:
|
||||
_assert(self.valuefieldid == SID_UNKNOWN)
|
||||
|
||||
self.valuefieldid = self.readfieldid()
|
||||
if self.valuefieldid != SID_UNKNOWN:
|
||||
self.state = ParserState.BeforeTID
|
||||
else:
|
||||
self.eof = True
|
||||
|
||||
elif self.state == ParserState.BeforeTID:
|
||||
self.state = ParserState.BeforeValue
|
||||
self.valuetid = self.readtypeid()
|
||||
if self.valuetid == -1:
|
||||
self.state = ParserState.EOF
|
||||
self.eof = True
|
||||
break
|
||||
|
||||
if self.valuetid == TID_TYPEDECL:
|
||||
if self.valuelen == 0:
|
||||
self.checkversionmarker()
|
||||
else:
|
||||
self.loadannotations()
|
||||
|
||||
elif self.state == ParserState.BeforeValue:
|
||||
self.skip(self.valuelen)
|
||||
self.state = ParserState.AfterValue
|
||||
|
||||
elif self.state == ParserState.AfterValue:
|
||||
if self.isinstruct:
|
||||
self.state = ParserState.BeforeField
|
||||
else:
|
||||
self.state = ParserState.BeforeTID
|
||||
|
||||
else:
|
||||
_assert(self.state == ParserState.EOF)
|
||||
|
||||
def next(self):
|
||||
if self.hasnext():
|
||||
self.needhasnext = True
|
||||
return self.valuetid
|
||||
else:
|
||||
return -1
|
||||
|
||||
def push(self, typeid, nextposition, nextremaining):
|
||||
self.containerstack.append(ContainerRec(nextpos=nextposition, tid=typeid, remaining=nextremaining))
|
||||
|
||||
def stepin(self):
|
||||
_assert(self.valuetid in [TID_STRUCT, TID_LIST, TID_SEXP] and not self.eof,
|
||||
"valuetid=%s eof=%s" % (self.valuetid, self.eof))
|
||||
_assert((not self.valueisnull or self.state == ParserState.AfterValue) and
|
||||
(self.valueisnull or self.state == ParserState.BeforeValue))
|
||||
|
||||
nextrem = self.localremaining
|
||||
if nextrem != -1:
|
||||
nextrem -= self.valuelen
|
||||
if nextrem < 0:
|
||||
nextrem = 0
|
||||
self.push(self.parenttid, self.stream.tell() + self.valuelen, nextrem)
|
||||
|
||||
self.isinstruct = (self.valuetid == TID_STRUCT)
|
||||
if self.isinstruct:
|
||||
self.state = ParserState.BeforeField
|
||||
else:
|
||||
self.state = ParserState.BeforeTID
|
||||
|
||||
self.localremaining = self.valuelen
|
||||
self.parenttid = self.valuetid
|
||||
self.clearvalue()
|
||||
self.needhasnext = True
|
||||
|
||||
def stepout(self):
|
||||
rec = self.containerstack.pop()
|
||||
|
||||
self.eof = False
|
||||
self.parenttid = rec.tid
|
||||
if self.parenttid == TID_STRUCT:
|
||||
self.isinstruct = True
|
||||
self.state = ParserState.BeforeField
|
||||
else:
|
||||
self.isinstruct = False
|
||||
self.state = ParserState.BeforeTID
|
||||
self.needhasnext = True
|
||||
|
||||
self.clearvalue()
|
||||
curpos = self.stream.tell()
|
||||
if rec.nextpos > curpos:
|
||||
self.skip(rec.nextpos - curpos)
|
||||
else:
|
||||
_assert(rec.nextpos == curpos)
|
||||
|
||||
self.localremaining = rec.remaining
|
||||
|
||||
def read(self, count=1):
|
||||
if self.localremaining != -1:
|
||||
self.localremaining -= count
|
||||
_assert(self.localremaining >= 0)
|
||||
|
||||
result = self.stream.read(count)
|
||||
if len(result) == 0:
|
||||
raise EOFError()
|
||||
return result
|
||||
|
||||
def readfieldid(self):
|
||||
if self.localremaining != -1 and self.localremaining < 1:
|
||||
return -1
|
||||
|
||||
try:
|
||||
return self.readvaruint()
|
||||
except EOFError:
|
||||
return -1
|
||||
|
||||
def readtypeid(self):
|
||||
if self.localremaining != -1:
|
||||
if self.localremaining < 1:
|
||||
return -1
|
||||
self.localremaining -= 1
|
||||
|
||||
b = self.stream.read(1)
|
||||
if len(b) < 1:
|
||||
return -1
|
||||
b = bord(b)
|
||||
result = b >> 4
|
||||
ln = b & 0xF
|
||||
|
||||
if ln == LEN_IS_VAR_LEN:
|
||||
ln = self.readvaruint()
|
||||
elif ln == LEN_IS_NULL:
|
||||
ln = 0
|
||||
self.state = ParserState.AfterValue
|
||||
elif result == TID_NULL:
|
||||
# Must have LEN_IS_NULL
|
||||
_assert(False)
|
||||
elif result == TID_BOOLEAN:
|
||||
_assert(ln <= 1)
|
||||
self.valueistrue = (ln == 1)
|
||||
ln = 0
|
||||
self.state = ParserState.AfterValue
|
||||
elif result == TID_STRUCT:
|
||||
if ln == 1:
|
||||
ln = self.readvaruint()
|
||||
|
||||
self.valuelen = ln
|
||||
return result
|
||||
|
||||
def readvarint(self):
|
||||
b = bord(self.read())
|
||||
negative = ((b & 0x40) != 0)
|
||||
result = (b & 0x3F)
|
||||
|
||||
i = 0
|
||||
while (b & 0x80) == 0 and i < 4:
|
||||
b = bord(self.read())
|
||||
result = (result << 7) | (b & 0x7F)
|
||||
i += 1
|
||||
|
||||
_assert(i < 4 or (b & 0x80) != 0, "int overflow")
|
||||
|
||||
if negative:
|
||||
return -result
|
||||
return result
|
||||
|
||||
def readvaruint(self):
|
||||
b = bord(self.read())
|
||||
result = (b & 0x7F)
|
||||
|
||||
i = 0
|
||||
while (b & 0x80) == 0 and i < 4:
|
||||
b = bord(self.read())
|
||||
result = (result << 7) | (b & 0x7F)
|
||||
i += 1
|
||||
|
||||
_assert(i < 4 or (b & 0x80) != 0, "int overflow")
|
||||
|
||||
return result
|
||||
|
||||
def readdecimal(self):
|
||||
if self.valuelen == 0:
|
||||
return 0.
|
||||
|
||||
rem = self.localremaining - self.valuelen
|
||||
self.localremaining = self.valuelen
|
||||
exponent = self.readvarint()
|
||||
|
||||
_assert(self.localremaining > 0, "Only exponent in ReadDecimal")
|
||||
_assert(self.localremaining <= 8, "Decimal overflow")
|
||||
|
||||
signed = False
|
||||
b = [bord(x) for x in self.read(self.localremaining)]
|
||||
if (b[0] & 0x80) != 0:
|
||||
b[0] = b[0] & 0x7F
|
||||
signed = True
|
||||
|
||||
# Convert variably sized network order integer into 64-bit little endian
|
||||
j = 0
|
||||
vb = [0] * 8
|
||||
for i in range(len(b), -1, -1):
|
||||
vb[i] = b[j]
|
||||
j += 1
|
||||
|
||||
v = struct.unpack("<Q", b"".join(bchr(x) for x in vb))[0]
|
||||
|
||||
result = v * (10 ** exponent)
|
||||
if signed:
|
||||
result = -result
|
||||
|
||||
self.localremaining = rem
|
||||
return result
|
||||
|
||||
def skip(self, count):
|
||||
if self.localremaining != -1:
|
||||
self.localremaining -= count
|
||||
if self.localremaining < 0:
|
||||
raise EOFError()
|
||||
|
||||
self.stream.seek(count, os.SEEK_CUR)
|
||||
|
||||
def parsesymboltable(self):
|
||||
self.next() # shouldn't do anything?
|
||||
|
||||
_assert(self.valuetid == TID_STRUCT)
|
||||
|
||||
if self.didimports:
|
||||
return
|
||||
|
||||
self.stepin()
|
||||
|
||||
fieldtype = self.next()
|
||||
while fieldtype != -1:
|
||||
if not self.valueisnull:
|
||||
_assert(self.valuefieldid == SID_IMPORTS, "Unsupported symbol table field id")
|
||||
|
||||
if fieldtype == TID_LIST:
|
||||
self.gatherimports()
|
||||
|
||||
fieldtype = self.next()
|
||||
|
||||
self.stepout()
|
||||
self.didimports = True
|
||||
|
||||
def gatherimports(self):
|
||||
self.stepin()
|
||||
|
||||
t = self.next()
|
||||
while t != -1:
|
||||
if not self.valueisnull and t == TID_STRUCT:
|
||||
self.readimport()
|
||||
|
||||
t = self.next()
|
||||
|
||||
self.stepout()
|
||||
|
||||
def readimport(self):
|
||||
version = -1
|
||||
maxid = -1
|
||||
name = ""
|
||||
|
||||
self.stepin()
|
||||
|
||||
t = self.next()
|
||||
while t != -1:
|
||||
if not self.valueisnull and self.valuefieldid != SID_UNKNOWN:
|
||||
if self.valuefieldid == SID_NAME:
|
||||
name = self.stringvalue()
|
||||
elif self.valuefieldid == SID_VERSION:
|
||||
version = self.intvalue()
|
||||
elif self.valuefieldid == SID_MAX_ID:
|
||||
maxid = self.intvalue()
|
||||
|
||||
t = self.next()
|
||||
|
||||
self.stepout()
|
||||
|
||||
if name == "" or name == SystemSymbols.ION:
|
||||
return
|
||||
|
||||
if version < 1:
|
||||
version = 1
|
||||
|
||||
table = self.findcatalogitem(name)
|
||||
if maxid < 0:
|
||||
_assert(table is not None and version == table.version, "Import %s lacks maxid" % name)
|
||||
maxid = len(table.symnames)
|
||||
|
||||
if table is not None:
|
||||
self.symbols.import_(table, min(maxid, len(table.symnames)))
|
||||
else:
|
||||
self.symbols.importunknown(name, maxid)
|
||||
|
||||
def intvalue(self):
|
||||
_assert(self.valuetid in [TID_POSINT, TID_NEGINT], "Not an int")
|
||||
|
||||
self.preparevalue()
|
||||
return self.value
|
||||
|
||||
def stringvalue(self):
|
||||
_assert(self.valuetid == TID_STRING, "Not a string")
|
||||
|
||||
if self.valueisnull:
|
||||
return ""
|
||||
|
||||
self.preparevalue()
|
||||
return self.value
|
||||
|
||||
def symbolvalue(self):
|
||||
_assert(self.valuetid == TID_SYMBOL, "Not a symbol")
|
||||
|
||||
self.preparevalue()
|
||||
result = self.symbols.findbyid(self.value)
|
||||
if result == "":
|
||||
result = "SYMBOL#%d" % self.value
|
||||
return result
|
||||
|
||||
def lobvalue(self):
|
||||
_assert(self.valuetid in [TID_CLOB, TID_BLOB], "Not a LOB type: %s" % self.getfieldname())
|
||||
|
||||
if self.valueisnull:
|
||||
return None
|
||||
|
||||
result = self.read(self.valuelen)
|
||||
self.state = ParserState.AfterValue
|
||||
return result
|
||||
|
||||
def decimalvalue(self):
|
||||
_assert(self.valuetid == TID_DECIMAL, "Not a decimal")
|
||||
|
||||
self.preparevalue()
|
||||
return self.value
|
||||
|
||||
def preparevalue(self):
|
||||
if self.value is None:
|
||||
self.loadscalarvalue()
|
||||
|
||||
def loadscalarvalue(self):
|
||||
if self.valuetid not in [TID_NULL, TID_BOOLEAN, TID_POSINT, TID_NEGINT,
|
||||
TID_FLOAT, TID_DECIMAL, TID_TIMESTAMP,
|
||||
TID_SYMBOL, TID_STRING]:
|
||||
return
|
||||
|
||||
if self.valueisnull:
|
||||
self.value = None
|
||||
return
|
||||
|
||||
if self.valuetid == TID_STRING:
|
||||
self.value = self.read(self.valuelen).decode("UTF-8")
|
||||
|
||||
elif self.valuetid in (TID_POSINT, TID_NEGINT, TID_SYMBOL):
|
||||
if self.valuelen == 0:
|
||||
self.value = 0
|
||||
else:
|
||||
_assert(self.valuelen <= 4, "int too long: %d" % self.valuelen)
|
||||
v = 0
|
||||
for i in range(self.valuelen - 1, -1, -1):
|
||||
v = (v | (bord(self.read()) << (i * 8)))
|
||||
|
||||
if self.valuetid == TID_NEGINT:
|
||||
self.value = -v
|
||||
else:
|
||||
self.value = v
|
||||
|
||||
elif self.valuetid == TID_DECIMAL:
|
||||
self.value = self.readdecimal()
|
||||
|
||||
#else:
|
||||
# _assert(False, "Unhandled scalar type %d" % self.valuetid)
|
||||
|
||||
self.state = ParserState.AfterValue
|
||||
|
||||
def clearvalue(self):
|
||||
self.valuetid = -1
|
||||
self.value = None
|
||||
self.valueisnull = False
|
||||
self.valuefieldid = SID_UNKNOWN
|
||||
self.annotations = []
|
||||
|
||||
def loadannotations(self):
|
||||
ln = self.readvaruint()
|
||||
maxpos = self.stream.tell() + ln
|
||||
while self.stream.tell() < maxpos:
|
||||
self.annotations.append(self.readvaruint())
|
||||
self.valuetid = self.readtypeid()
|
||||
|
||||
def checkversionmarker(self):
|
||||
for i in VERSION_MARKER:
|
||||
_assert(self.read() == i, "Unknown version marker")
|
||||
|
||||
self.valuelen = 0
|
||||
self.valuetid = TID_SYMBOL
|
||||
self.value = SID_ION_1_0
|
||||
self.valueisnull = False
|
||||
self.valuefieldid = SID_UNKNOWN
|
||||
self.state = ParserState.AfterValue
|
||||
|
||||
def findcatalogitem(self, name):
|
||||
for result in self.catalog:
|
||||
if result.name == name:
|
||||
return result
|
||||
|
||||
def forceimport(self, symbols):
|
||||
item = IonCatalogItem("Forced", 1, symbols)
|
||||
self.symbols.import_(item, len(symbols))
|
||||
|
||||
def getfieldname(self):
|
||||
if self.valuefieldid == SID_UNKNOWN:
|
||||
return ""
|
||||
return self.symbols.findbyid(self.valuefieldid)
|
||||
|
||||
def getfieldnamesymbol(self):
|
||||
return SymbolToken(self.getfieldname(), self.valuefieldid)
|
||||
|
||||
def gettypename(self):
|
||||
if len(self.annotations) == 0:
|
||||
return ""
|
||||
|
||||
return self.symbols.findbyid(self.annotations[0])
|
||||
|
||||
@staticmethod
|
||||
def printlob(b):
|
||||
if b is None:
|
||||
return "null"
|
||||
|
||||
result = ""
|
||||
for i in b:
|
||||
result += ("%02x " % bord(i))
|
||||
|
||||
if len(result) > 0:
|
||||
result = result[:-1]
|
||||
return result
|
||||
|
||||
def ionwalk(self, supert, indent, lst):
|
||||
while self.hasnext():
|
||||
if supert == TID_STRUCT:
|
||||
L = self.getfieldname() + ":"
|
||||
else:
|
||||
L = ""
|
||||
|
||||
t = self.next()
|
||||
if t in [TID_STRUCT, TID_LIST]:
|
||||
if L != "":
|
||||
lst.append(indent + L)
|
||||
L = self.gettypename()
|
||||
if L != "":
|
||||
lst.append(indent + L + "::")
|
||||
if t == TID_STRUCT:
|
||||
lst.append(indent + "{")
|
||||
else:
|
||||
lst.append(indent + "[")
|
||||
|
||||
self.stepin()
|
||||
self.ionwalk(t, indent + " ", lst)
|
||||
self.stepout()
|
||||
|
||||
if t == TID_STRUCT:
|
||||
lst.append(indent + "}")
|
||||
else:
|
||||
lst.append(indent + "]")
|
||||
|
||||
else:
|
||||
if t == TID_STRING:
|
||||
L += ('"%s"' % self.stringvalue())
|
||||
elif t in [TID_CLOB, TID_BLOB]:
|
||||
L += ("{%s}" % self.printlob(self.lobvalue()))
|
||||
elif t == TID_POSINT:
|
||||
L += str(self.intvalue())
|
||||
elif t == TID_SYMBOL:
|
||||
tn = self.gettypename()
|
||||
if tn != "":
|
||||
tn += "::"
|
||||
L += tn + self.symbolvalue()
|
||||
elif t == TID_DECIMAL:
|
||||
L += str(self.decimalvalue())
|
||||
else:
|
||||
L += ("TID %d" % t)
|
||||
lst.append(indent + L)
|
||||
|
||||
def print_(self, lst):
|
||||
self.reset()
|
||||
self.ionwalk(-1, "", lst)
|
||||
|
||||
|
||||
SYM_NAMES = [ 'com.amazon.drm.Envelope@1.0',
|
||||
'com.amazon.drm.EnvelopeMetadata@1.0', 'size', 'page_size',
|
||||
'encryption_key', 'encryption_transformation',
|
||||
'encryption_voucher', 'signing_key', 'signing_algorithm',
|
||||
'signing_voucher', 'com.amazon.drm.EncryptedPage@1.0',
|
||||
'cipher_text', 'cipher_iv', 'com.amazon.drm.Signature@1.0',
|
||||
'data', 'com.amazon.drm.EnvelopeIndexTable@1.0', 'length',
|
||||
'offset', 'algorithm', 'encoded', 'encryption_algorithm',
|
||||
'hashing_algorithm', 'expires', 'format', 'id',
|
||||
'lock_parameters', 'strategy', 'com.amazon.drm.Key@1.0',
|
||||
'com.amazon.drm.KeySet@1.0', 'com.amazon.drm.PIDv3@1.0',
|
||||
'com.amazon.drm.PlainTextPage@1.0',
|
||||
'com.amazon.drm.PlainText@1.0', 'com.amazon.drm.PrivateKey@1.0',
|
||||
'com.amazon.drm.PublicKey@1.0', 'com.amazon.drm.SecretKey@1.0',
|
||||
'com.amazon.drm.Voucher@1.0', 'public_key', 'private_key',
|
||||
'com.amazon.drm.KeyPair@1.0', 'com.amazon.drm.ProtectedData@1.0',
|
||||
'doctype', 'com.amazon.drm.EnvelopeIndexTableOffset@1.0',
|
||||
'enddoc', 'license_type', 'license', 'watermark', 'key', 'value',
|
||||
'com.amazon.drm.License@1.0', 'category', 'metadata',
|
||||
'categorized_metadata', 'com.amazon.drm.CategorizedMetadata@1.0',
|
||||
'com.amazon.drm.VoucherEnvelope@1.0', 'mac', 'voucher',
|
||||
'com.amazon.drm.ProtectedData@2.0',
|
||||
'com.amazon.drm.Envelope@2.0',
|
||||
'com.amazon.drm.EnvelopeMetadata@2.0',
|
||||
'com.amazon.drm.EncryptedPage@2.0',
|
||||
'com.amazon.drm.PlainText@2.0', 'compression_algorithm',
|
||||
'com.amazon.drm.Compressed@1.0', 'priority', 'refines']
|
||||
|
||||
def addprottable(ion):
|
||||
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
||||
|
||||
|
||||
def pkcs7pad(msg, blocklen):
|
||||
paddinglen = blocklen - len(msg) % blocklen
|
||||
padding = bchr(paddinglen) * paddinglen
|
||||
return msg + padding
|
||||
|
||||
|
||||
def pkcs7unpad(msg, blocklen):
|
||||
_assert(len(msg) % blocklen == 0)
|
||||
|
||||
paddinglen = bord(msg[-1])
|
||||
_assert(paddinglen > 0 and paddinglen <= blocklen, "Incorrect padding - Wrong key")
|
||||
_assert(msg[-paddinglen:] == bchr(paddinglen) * paddinglen, "Incorrect padding - Wrong key")
|
||||
|
||||
return msg[:-paddinglen]
|
||||
|
||||
|
||||
class DrmIonVoucher(object):
|
||||
envelope = None
|
||||
voucher = None
|
||||
drmkey = None
|
||||
license_type = "Unknown"
|
||||
|
||||
encalgorithm = ""
|
||||
enctransformation = ""
|
||||
hashalgorithm = ""
|
||||
|
||||
lockparams = None
|
||||
|
||||
ciphertext = b""
|
||||
cipheriv = b""
|
||||
secretkey = b""
|
||||
|
||||
def __init__(self, voucherenv, dsn, secret):
|
||||
self.dsn,self.secret = dsn,secret
|
||||
|
||||
self.lockparams = []
|
||||
|
||||
self.envelope = BinaryIonParser(voucherenv)
|
||||
addprottable(self.envelope)
|
||||
|
||||
def decryptvoucher(self):
|
||||
shared = "PIDv3" + self.encalgorithm + self.enctransformation + self.hashalgorithm
|
||||
|
||||
self.lockparams.sort()
|
||||
for param in self.lockparams:
|
||||
if param == "ACCOUNT_SECRET":
|
||||
shared += param + self.secret
|
||||
elif param == "CLIENT_ID":
|
||||
shared += param + self.dsn
|
||||
else:
|
||||
_assert(False, "Unknown lock parameter: %s" % param)
|
||||
|
||||
sharedsecret = shared.encode("UTF-8")
|
||||
|
||||
key = hmac.new(sharedsecret, sharedsecret[:5], digestmod=hashlib.sha256).digest()
|
||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||
b = aes.decrypt(self.ciphertext)
|
||||
b = pkcs7unpad(b, 16)
|
||||
|
||||
self.drmkey = BinaryIonParser(StringIO(b))
|
||||
addprottable(self.drmkey)
|
||||
|
||||
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
||||
"Expected KeySet, got %s" % self.drmkey.gettypename())
|
||||
|
||||
self.drmkey.stepin()
|
||||
while self.drmkey.hasnext():
|
||||
self.drmkey.next()
|
||||
if self.drmkey.gettypename() != "com.amazon.drm.SecretKey@1.0":
|
||||
continue
|
||||
|
||||
self.drmkey.stepin()
|
||||
while self.drmkey.hasnext():
|
||||
self.drmkey.next()
|
||||
if self.drmkey.getfieldname() == "algorithm":
|
||||
_assert(self.drmkey.stringvalue() == "AES", "Unknown cipher algorithm: %s" % self.drmkey.stringvalue())
|
||||
elif self.drmkey.getfieldname() == "format":
|
||||
_assert(self.drmkey.stringvalue() == "RAW", "Unknown key format: %s" % self.drmkey.stringvalue())
|
||||
elif self.drmkey.getfieldname() == "encoded":
|
||||
self.secretkey = self.drmkey.lobvalue()
|
||||
|
||||
self.drmkey.stepout()
|
||||
break
|
||||
|
||||
self.drmkey.stepout()
|
||||
|
||||
def parse(self):
|
||||
self.envelope.reset()
|
||||
_assert(self.envelope.hasnext(), "Envelope is empty")
|
||||
_assert(self.envelope.next() == TID_STRUCT and self.envelope.gettypename() == "com.amazon.drm.VoucherEnvelope@1.0",
|
||||
"Unknown type encountered in envelope, expected VoucherEnvelope")
|
||||
|
||||
self.envelope.stepin()
|
||||
while self.envelope.hasnext():
|
||||
self.envelope.next()
|
||||
field = self.envelope.getfieldname()
|
||||
if field == "voucher":
|
||||
self.voucher = BinaryIonParser(StringIO(self.envelope.lobvalue()))
|
||||
addprottable(self.voucher)
|
||||
continue
|
||||
elif field != "strategy":
|
||||
continue
|
||||
|
||||
_assert(self.envelope.gettypename() == "com.amazon.drm.PIDv3@1.0", "Unknown strategy: %s" % self.envelope.gettypename())
|
||||
|
||||
self.envelope.stepin()
|
||||
while self.envelope.hasnext():
|
||||
self.envelope.next()
|
||||
field = self.envelope.getfieldname()
|
||||
if field == "encryption_algorithm":
|
||||
self.encalgorithm = self.envelope.stringvalue()
|
||||
elif field == "encryption_transformation":
|
||||
self.enctransformation = self.envelope.stringvalue()
|
||||
elif field == "hashing_algorithm":
|
||||
self.hashalgorithm = self.envelope.stringvalue()
|
||||
elif field == "lock_parameters":
|
||||
self.envelope.stepin()
|
||||
while self.envelope.hasnext():
|
||||
_assert(self.envelope.next() == TID_STRING, "Expected string list for lock_parameters")
|
||||
self.lockparams.append(self.envelope.stringvalue())
|
||||
self.envelope.stepout()
|
||||
|
||||
self.envelope.stepout()
|
||||
|
||||
self.parsevoucher()
|
||||
|
||||
def parsevoucher(self):
|
||||
_assert(self.voucher.hasnext(), "Voucher is empty")
|
||||
_assert(self.voucher.next() == TID_STRUCT and self.voucher.gettypename() == "com.amazon.drm.Voucher@1.0",
|
||||
"Unknown type, expected Voucher")
|
||||
|
||||
self.voucher.stepin()
|
||||
while self.voucher.hasnext():
|
||||
self.voucher.next()
|
||||
|
||||
if self.voucher.getfieldname() == "cipher_iv":
|
||||
self.cipheriv = self.voucher.lobvalue()
|
||||
elif self.voucher.getfieldname() == "cipher_text":
|
||||
self.ciphertext = self.voucher.lobvalue()
|
||||
elif self.voucher.getfieldname() == "license":
|
||||
_assert(self.voucher.gettypename() == "com.amazon.drm.License@1.0",
|
||||
"Unknown license: %s" % self.voucher.gettypename())
|
||||
self.voucher.stepin()
|
||||
while self.voucher.hasnext():
|
||||
self.voucher.next()
|
||||
if self.voucher.getfieldname() == "license_type":
|
||||
self.license_type = self.voucher.stringvalue()
|
||||
self.voucher.stepout()
|
||||
|
||||
def printenvelope(self, lst):
|
||||
self.envelope.print_(lst)
|
||||
|
||||
def printkey(self, lst):
|
||||
if self.voucher is None:
|
||||
self.parse()
|
||||
if self.drmkey is None:
|
||||
self.decryptvoucher()
|
||||
|
||||
self.drmkey.print_(lst)
|
||||
|
||||
def printvoucher(self, lst):
|
||||
if self.voucher is None:
|
||||
self.parse()
|
||||
|
||||
self.voucher.print_(lst)
|
||||
|
||||
def getlicensetype(self):
|
||||
return self.license_type
|
||||
|
||||
|
||||
class DrmIon(object):
|
||||
ion = None
|
||||
voucher = None
|
||||
vouchername = ""
|
||||
key = b""
|
||||
onvoucherrequired = None
|
||||
|
||||
def __init__(self, ionstream, onvoucherrequired):
|
||||
self.ion = BinaryIonParser(ionstream)
|
||||
addprottable(self.ion)
|
||||
self.onvoucherrequired = onvoucherrequired
|
||||
|
||||
def parse(self, outpages):
|
||||
self.ion.reset()
|
||||
|
||||
_assert(self.ion.hasnext(), "DRMION envelope is empty")
|
||||
_assert(self.ion.next() == TID_SYMBOL and self.ion.gettypename() == "doctype", "Expected doctype symbol")
|
||||
_assert(self.ion.next() == TID_LIST and self.ion.gettypename() in ["com.amazon.drm.Envelope@1.0", "com.amazon.drm.Envelope@2.0"],
|
||||
"Unknown type encountered in DRMION envelope, expected Envelope, got %s" % self.ion.gettypename())
|
||||
|
||||
while True:
|
||||
if self.ion.gettypename() == "enddoc":
|
||||
break
|
||||
|
||||
self.ion.stepin()
|
||||
while self.ion.hasnext():
|
||||
self.ion.next()
|
||||
|
||||
if self.ion.gettypename() in ["com.amazon.drm.EnvelopeMetadata@1.0", "com.amazon.drm.EnvelopeMetadata@2.0"]:
|
||||
self.ion.stepin()
|
||||
while self.ion.hasnext():
|
||||
self.ion.next()
|
||||
if self.ion.getfieldname() != "encryption_voucher":
|
||||
continue
|
||||
|
||||
if self.vouchername == "":
|
||||
self.vouchername = self.ion.stringvalue()
|
||||
self.voucher = self.onvoucherrequired(self.vouchername)
|
||||
self.key = self.voucher.secretkey
|
||||
_assert(self.key is not None, "Unable to obtain secret key from voucher")
|
||||
else:
|
||||
_assert(self.vouchername == self.ion.stringvalue(),
|
||||
"Unexpected: Different vouchers required for same file?")
|
||||
|
||||
self.ion.stepout()
|
||||
|
||||
elif self.ion.gettypename() in ["com.amazon.drm.EncryptedPage@1.0", "com.amazon.drm.EncryptedPage@2.0"]:
|
||||
decompress = False
|
||||
ct = None
|
||||
civ = None
|
||||
self.ion.stepin()
|
||||
while self.ion.hasnext():
|
||||
self.ion.next()
|
||||
if self.ion.gettypename() == "com.amazon.drm.Compressed@1.0":
|
||||
decompress = True
|
||||
if self.ion.getfieldname() == "cipher_text":
|
||||
ct = self.ion.lobvalue()
|
||||
elif self.ion.getfieldname() == "cipher_iv":
|
||||
civ = self.ion.lobvalue()
|
||||
|
||||
if ct is not None and civ is not None:
|
||||
self.processpage(ct, civ, outpages, decompress)
|
||||
self.ion.stepout()
|
||||
|
||||
self.ion.stepout()
|
||||
if not self.ion.hasnext():
|
||||
break
|
||||
self.ion.next()
|
||||
|
||||
def print_(self, lst):
|
||||
self.ion.print_(lst)
|
||||
|
||||
def processpage(self, ct, civ, outpages, decompress):
|
||||
aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
|
||||
msg = pkcs7unpad(aes.decrypt(ct), 16)
|
||||
|
||||
if not decompress:
|
||||
outpages.write(msg)
|
||||
return
|
||||
|
||||
_assert(msg[0] == b"\x00", "LZMA UseFilter not supported")
|
||||
|
||||
if calibre_lzma is not None:
|
||||
with calibre_lzma.decompress(msg[1:], bufsize=0x1000000) as f:
|
||||
f.seek(0)
|
||||
outpages.write(f.read())
|
||||
return
|
||||
|
||||
decomp = lzma.LZMADecompressor(format=lzma.FORMAT_ALONE)
|
||||
while not decomp.eof:
|
||||
segment = decomp.decompress(msg[1:])
|
||||
msg = b"" # Contents were internally buffered after the first call
|
||||
outpages.write(segment)
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# k4mobidedrm.py, version 5.3
|
||||
# Copyright © 2009-2015 by ApprenticeHarper et al.
|
||||
# k4mobidedrm.py
|
||||
# Copyright © 2008-2017 by Apprentice Harper et al.
|
||||
|
||||
# engine to remove drm from Kindle and Mobipocket ebooks
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '5.5'
|
||||
|
||||
# Engine to remove drm from Kindle and Mobipocket ebooks
|
||||
# for personal use for archiving and converting your ebooks
|
||||
|
||||
# PLEASE DO NOT PIRATE EBOOKS!
|
||||
@@ -17,12 +20,11 @@ from __future__ import with_statement
|
||||
# readable for a long, long time
|
||||
|
||||
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
|
||||
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
|
||||
# and many many others
|
||||
# unswindle, DarkReverser, ApprenticeAlf, and many many others
|
||||
|
||||
# Special thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump
|
||||
# from which this script borrows most unashamedly.
|
||||
|
||||
|
||||
# Changelog
|
||||
# 1.0 - Name change to k4mobidedrm. Adds Mac support, Adds plugin code
|
||||
# 1.1 - Adds support for additional kindle.info files
|
||||
@@ -57,9 +59,8 @@ from __future__ import with_statement
|
||||
# 5.2 - Fixed error in command line processing of unicode arguments
|
||||
# 5.3 - Changed Android support to allow passing of backup .ab files
|
||||
# 5.4 - Recognise KFX files masquerading as azw, even if we can't decrypt them yet.
|
||||
|
||||
__version__ = '5.4'
|
||||
|
||||
# 5.5 - Added GPL v3 licence explicitly.
|
||||
# 5.x - Invoke KFXZipBook to handle zipped KFX files
|
||||
|
||||
import sys, os, re
|
||||
import csv
|
||||
@@ -83,11 +84,13 @@ if inCalibre:
|
||||
from calibre_plugins.dedrm import topazextract
|
||||
from calibre_plugins.dedrm import kgenpids
|
||||
from calibre_plugins.dedrm import androidkindlekey
|
||||
from calibre_plugins.dedrm import kfxdedrm
|
||||
else:
|
||||
import mobidedrm
|
||||
import topazextract
|
||||
import kgenpids
|
||||
import androidkindlekey
|
||||
import kfxdedrm
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -197,13 +200,15 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
|
||||
mobi = True
|
||||
magic8 = open(infile,'rb').read(8)
|
||||
if magic8 == '\xeaDRMION\xee':
|
||||
raise DrmException(u"KFX format detected. This format cannot be decrypted yet.")
|
||||
|
||||
raise DrmException(u"The .kfx DRMION file cannot be decrypted by itself. A .kfx-zip archive containing a DRM voucher is required.")
|
||||
|
||||
magic3 = magic8[:3]
|
||||
if magic3 == 'TPZ':
|
||||
mobi = False
|
||||
|
||||
if mobi:
|
||||
if magic8[:4] == 'PK\x03\x04':
|
||||
mb = kfxdedrm.KFXZipBook(infile)
|
||||
elif mobi:
|
||||
mb = mobidedrm.MobiBook(infile)
|
||||
else:
|
||||
mb = topazextract.TopazBook(infile)
|
||||
@@ -295,7 +300,7 @@ def usage(progname):
|
||||
def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2013 The Dark Reverser et al.".format(__version__)
|
||||
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2017 Apprentice Harper et al.".format(__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# Engine to remove drm from Kindle KFX ebooks
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
import ion
|
||||
except:
|
||||
from calibre_plugins.dedrm import ion
|
||||
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.0'
|
||||
|
||||
|
||||
class KFXZipBook:
|
||||
def __init__(self, infile):
|
||||
self.infile = infile
|
||||
self.voucher = None
|
||||
self.decrypted = {}
|
||||
|
||||
def getPIDMetaInfo(self):
|
||||
return (None, None)
|
||||
|
||||
def processBook(self, totalpids):
|
||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
||||
for filename in zf.namelist():
|
||||
data = zf.read(filename)
|
||||
if data.startswith('\xeaDRMION\xee'):
|
||||
if self.voucher is None:
|
||||
self.decrypt_voucher(totalpids)
|
||||
print u'Decrypting KFX DRMION: {0}'.format(filename)
|
||||
outfile = StringIO()
|
||||
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
||||
self.decrypted[filename] = outfile.getvalue()
|
||||
|
||||
if not self.decrypted:
|
||||
print(u'The .kfx-zip archive does not contain an encrypted DRMION file')
|
||||
|
||||
def decrypt_voucher(self, totalpids):
|
||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
||||
for info in zf.infolist():
|
||||
if info.file_size < 0x10000:
|
||||
data = zf.read(info.filename)
|
||||
if data.startswith('\xe0\x01\x00\xea') and 'ProtectedData' in data:
|
||||
break # found DRM voucher
|
||||
else:
|
||||
raise Exception(u'The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher')
|
||||
|
||||
print u'Decrypting KFX DRM voucher: {0}'.format(info.filename)
|
||||
|
||||
for pid in [''] + totalpids:
|
||||
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,40)]:
|
||||
if len(pid) == dsn_len + secret_len:
|
||||
break # split pid into DSN and account secret
|
||||
else:
|
||||
continue
|
||||
|
||||
try:
|
||||
voucher = ion.DrmIonVoucher(StringIO(data), pid[:dsn_len], pid[dsn_len:])
|
||||
voucher.parse()
|
||||
voucher.decryptvoucher()
|
||||
break
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
raise Exception(u'Failed to decrypt KFX DRM voucher with any key')
|
||||
|
||||
print u'KFX DRM voucher successfully decrypted'
|
||||
|
||||
license_type = voucher.getlicensetype()
|
||||
if license_type != "Purchase":
|
||||
raise Exception((u'This book is licensed as {0}. '
|
||||
'These tools are intended for use on purchased books.').format(license_type))
|
||||
|
||||
self.voucher = voucher
|
||||
|
||||
def getBookTitle(self):
|
||||
return os.path.splitext(os.path.split(self.infile)[1])[0]
|
||||
|
||||
def getBookExtension(self):
|
||||
return '.kfx-zip'
|
||||
|
||||
def getBookType(self):
|
||||
return 'KFX-ZIP'
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
||||
|
||||
def getFile(self, outpath):
|
||||
if not self.decrypted:
|
||||
shutil.copyfile(self.infile, outpath)
|
||||
else:
|
||||
with zipfile.ZipFile(self.infile, 'r') as zif:
|
||||
with zipfile.ZipFile(outpath, 'w') as zof:
|
||||
for info in zif.infolist():
|
||||
zof.writestr(info, self.decrypted.get(info.filename, zif.read(info.filename)))
|
||||
@@ -4,10 +4,15 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kgenpids.py
|
||||
# Copyright © 2010-2015 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.1'
|
||||
|
||||
# Revision history:
|
||||
# 2.0 - Fix for non-ascii Windows user names
|
||||
# 2.1 - Actual fix for non-ascii WIndows user names.
|
||||
# x.x - Return information needed for KFX decryption
|
||||
|
||||
import sys
|
||||
import os, csv
|
||||
@@ -168,6 +173,9 @@ def pidFromSerial(s, l):
|
||||
|
||||
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
|
||||
def getKindlePids(rec209, token, serialnum):
|
||||
if rec209 is None:
|
||||
return [serialnum]
|
||||
|
||||
pids=[]
|
||||
|
||||
if isinstance(serialnum,unicode):
|
||||
@@ -195,20 +203,6 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
global charMap1
|
||||
pids = []
|
||||
|
||||
try:
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
|
||||
# Get the IDString used to decode the Kindle Info file
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
# Get the UserName stored when the Kindle Info file was decoded
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||
|
||||
except KeyError:
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
return pids
|
||||
|
||||
try:
|
||||
# Get the kindle account token, if present
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
@@ -217,14 +211,51 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
kindleAccountToken=""
|
||||
pass
|
||||
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(IDString,charMap1)
|
||||
try:
|
||||
# Get the DSN token, if present
|
||||
DSN = (kindleDatabase[1])['DSN'].decode('hex')
|
||||
print u"Got DSN key from database {0}".format(kindleDatabase[0])
|
||||
except KeyError:
|
||||
# See if we have the info to generate the DSN
|
||||
try:
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
#print u"Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
|
||||
|
||||
# Get the current user name
|
||||
encodedUsername = encodeHash(UserName,charMap1)
|
||||
try:
|
||||
# Get the SerialNumber token, if present
|
||||
IDString = (kindleDatabase[1])['SerialNumber'].decode('hex')
|
||||
print u"Got SerialNumber from database {0}".format(kindleDatabase[0])
|
||||
except KeyError:
|
||||
# Get the IDString we added
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
try:
|
||||
# Get the UsernameHash token, if present
|
||||
encodedUsername = (kindleDatabase[1])['UsernameHash'].decode('hex')
|
||||
print u"Got UsernameHash from database {0}".format(kindleDatabase[0])
|
||||
except KeyError:
|
||||
# Get the UserName we added
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||
# encode it
|
||||
encodedUsername = encodeHash(UserName,charMap1)
|
||||
#print u"encodedUsername",encodedUsername.encode('hex')
|
||||
except KeyError:
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
return pids
|
||||
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(IDString,charMap1)
|
||||
#print u"encodedIDString",encodedIDString.encode('hex')
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
#print u"DSN",DSN.encode('hex')
|
||||
pass
|
||||
|
||||
if rec209 is None:
|
||||
pids.append(DSN+kindleAccountToken)
|
||||
return pids
|
||||
|
||||
# Compute the device PID (for which I can tell, is used for nothing).
|
||||
table = generatePidEncryptionTable()
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindlekey.py
|
||||
# Copyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.5'
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||
@@ -22,15 +25,15 @@ from __future__ import with_statement
|
||||
# 2.1 - Fixed Kindle for PC encryption changes March 2016
|
||||
# 2.2 - Fixes for Macs with bonded ethernet ports
|
||||
# Also removed old .kinfo file support (pre-2011)
|
||||
# 2.3 - Added more field names thanks to concavegit's KFX code.
|
||||
# 2.4 - Fix for complex Mac disk setups, thanks to Tibs
|
||||
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
|
||||
|
||||
|
||||
"""
|
||||
Retrieve Kindle for PC/Mac user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.2'
|
||||
|
||||
import sys, os, re
|
||||
from struct import pack, unpack, unpack_from
|
||||
import json
|
||||
@@ -885,10 +888,18 @@ if iswindows:
|
||||
if errcd == 234:
|
||||
# bad wine implementation up through wine 1.3.21
|
||||
return "AlternateUserName"
|
||||
# double the buffer size
|
||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||
size.value = len(buffer)
|
||||
# return low byte of the unicode value of each character of the username
|
||||
return buffer.value.encode('utf-16-le')[::2]
|
||||
|
||||
# replace any non-ASCII values with 0xfffd
|
||||
for i in xrange(0,len(buffer)):
|
||||
if buffer[i]>u"\u007f":
|
||||
#print u"swapping char "+str(i)+" ("+buffer[i]+")"
|
||||
buffer[i] = u"\ufffd"
|
||||
# return utf-8 encoding of modified username
|
||||
#print u"modified username:"+buffer.value
|
||||
return buffer.value.encode('utf-8')
|
||||
return GetUserName
|
||||
GetUserName = GetUserName()
|
||||
|
||||
@@ -1010,8 +1021,16 @@ if iswindows:
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
'SerialNumber',\
|
||||
'UsernameHash',\
|
||||
'kindle.directedid.info',\
|
||||
'DSN',\
|
||||
'kindle.accounttype.info',\
|
||||
'krx.flashcardsplugin.data.encryption_key',\
|
||||
'krx.notebookexportplugin.data.encryption_key',\
|
||||
'proxy.http.password',\
|
||||
'proxy.http.username'
|
||||
]
|
||||
|
||||
DB = {}
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
data = infoReader.read()
|
||||
@@ -1263,7 +1282,7 @@ elif isosx:
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.startswith('/dev'):
|
||||
(devpart, mpath) = resline.split(' on ')
|
||||
(devpart, mpath) = resline.split(' on ')[:2]
|
||||
dpart = devpart[5:]
|
||||
names.append(dpart)
|
||||
return names
|
||||
@@ -1447,6 +1466,10 @@ elif isosx:
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
'SerialNumber',\
|
||||
'UsernameHash',\
|
||||
'kindle.directedid.info',\
|
||||
'DSN'
|
||||
]
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
filedata = infoReader.read()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# mobidedrm.py, version 0.38
|
||||
# mobidedrm.py
|
||||
# Copyright © 2008 The Dark Reverser
|
||||
#
|
||||
# Modified 2008–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Portions © 2008–2017 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = u"0.42"
|
||||
|
||||
# This is a python script. You need a Python interpreter to run it.
|
||||
# For example, ActiveState Python, which exists for windows.
|
||||
@@ -69,9 +71,7 @@
|
||||
# 0.39 - Fixed problem with TEXtREAd and getBookType interface
|
||||
# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 0.41 - Fixed potential unicode problem in command line calls
|
||||
|
||||
|
||||
__version__ = u"0.41"
|
||||
# 0.42 - Added GPL v3 licence. updated/removed some print statements
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -244,7 +244,7 @@ class MobiBook:
|
||||
pass
|
||||
|
||||
def __init__(self, infile):
|
||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
|
||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)
|
||||
|
||||
try:
|
||||
from alfcrypto import Pukall_Cipher
|
||||
@@ -288,10 +288,10 @@ class MobiBook:
|
||||
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
|
||||
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
|
||||
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
|
||||
print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
|
||||
#print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
|
||||
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
|
||||
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
|
||||
print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
|
||||
#print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
|
||||
if (self.compression != 17480):
|
||||
# multibyte utf8 data is included in the encryption for PalmDoc compression
|
||||
# so clear that byte so that we leave it to be decrypted.
|
||||
@@ -516,7 +516,7 @@ def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv)<3 or len(argv)>4:
|
||||
print u"MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
|
||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)
|
||||
print u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
|
||||
print u"Usage:"
|
||||
print u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname)
|
||||
|
||||
@@ -29,8 +29,11 @@
|
||||
# 6.5.1 - Version bump to match plugin & Mac app
|
||||
# 6.5.2 - Fix for a new tag in Topaz ebooks
|
||||
# 6.5.3 - Explicitly warn about KFX files
|
||||
# 6.5.4 - PDF float fix.
|
||||
# 6.5.5 - Kindle for PC/Accented characters in username fix.
|
||||
# 6.6.0 - Initial KFX support from TomThumb
|
||||
|
||||
__version__ = '6.5.3'
|
||||
__version__ = '6.6.0'
|
||||
|
||||
import sys
|
||||
import os, os.path
|
||||
@@ -381,6 +384,9 @@ class PrefsDialog(Toplevel):
|
||||
('Kindle','.azw3'),
|
||||
('Kindle','.azw4'),
|
||||
('Kindle','.tpz'),
|
||||
('Kindle','.azw8'),
|
||||
('Kindle','.kfx'),
|
||||
('Kindle','.kfx-zip'),
|
||||
('Kindle','.mobi'),
|
||||
('Kindle','.prc'),
|
||||
('eReader','.pdb'),
|
||||
@@ -596,7 +602,7 @@ class ConvDialog(Toplevel):
|
||||
self.p2 = Process(target=processPDB, args=(q, infile, outdir, rscpath))
|
||||
self.p2.start()
|
||||
return 0
|
||||
if ext in ['.azw', '.azw1', '.azw3', '.azw4', '.prc', '.mobi', '.pobi', '.tpz']:
|
||||
if ext in ['.azw', '.azw1', '.azw3', '.azw4', '.prc', '.mobi', '.pobi', '.tpz', '.azw8', '.kfx', '.kfx-zip']:
|
||||
self.p2 = Process(target=processK4MOBI,args=(q, infile, outdir, rscpath))
|
||||
self.p2.start()
|
||||
return 0
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# __init__.py for DeDRM_plugin
|
||||
# Copyright © 2008-2018 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -57,6 +61,10 @@ __docformat__ = 'restructuredtext en'
|
||||
# 6.5.1 - Updated version number, added PDF check for DRM-free documents
|
||||
# 6.5.2 - Another Topaz fix
|
||||
# 6.5.3 - Warn about KFX files explicitly
|
||||
# 6.5.4 - Mac App Fix, improve PDF decryption, handle latest tcl changes in ActivePython
|
||||
# 6.5.5 - Finally a fix for the Windows non-ASCII user names.
|
||||
# 6.6.0 - Add kfx and kfx-zip as supported file types (also invoke this plugin if the original
|
||||
# imported format was azw8 since that may be converted to kfx)
|
||||
|
||||
|
||||
"""
|
||||
@@ -64,7 +72,7 @@ Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 5, 3)
|
||||
PLUGIN_VERSION_TUPLE = (6, 6, 0)
|
||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||
@@ -112,7 +120,7 @@ class DeDRM(FileTypePlugin):
|
||||
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','azw8','tpz','kfx','kfx-zip'])
|
||||
on_import = True
|
||||
priority = 600
|
||||
|
||||
@@ -288,8 +296,8 @@ class DeDRM(FileTypePlugin):
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
|
||||
# import the Adobe Adept ePub handler
|
||||
import calibre_plugins.dedrm.ineptepub as ineptepub
|
||||
@@ -379,17 +387,19 @@ class DeDRM(FileTypePlugin):
|
||||
except:
|
||||
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
traceback.print_exc()
|
||||
print u"{0} v{1}: Decrypted with new default key after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Decrypted with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
print u"{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
traceback.print_exc()
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
|
||||
# Not a Barnes & Noble nor an Adobe Adept
|
||||
# Import the fixed epub.
|
||||
@@ -488,8 +498,8 @@ class DeDRM(FileTypePlugin):
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
|
||||
|
||||
def KindleMobiDecrypt(self,path_to_ebook):
|
||||
@@ -556,8 +566,8 @@ class DeDRM(FileTypePlugin):
|
||||
pass
|
||||
if not decoded:
|
||||
#if you reached here then no luck raise and exception
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
|
||||
of = self.temporary_file(book.getBookExtension())
|
||||
book.getFile(of.name)
|
||||
@@ -591,8 +601,8 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
|
||||
|
||||
def run(self, path_to_ebook):
|
||||
@@ -605,7 +615,7 @@ class DeDRM(FileTypePlugin):
|
||||
self.starttime = time.time()
|
||||
|
||||
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
|
||||
if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz']:
|
||||
if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz','kfx-zip']:
|
||||
# Kindle/Mobipocket
|
||||
decrypted_ebook = self.KindleMobiDecrypt(path_to_ebook)
|
||||
elif booktype == 'pdb':
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignobleepub.pyw, version 3.8
|
||||
# ignobleepub.pyw, version 4.1
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015–2017 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.6
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
@@ -35,13 +36,14 @@ from __future__ import with_statement
|
||||
# 3.8 - Fixed to retain zip file metadata (e.g. file modification date)
|
||||
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 4.0 - Work if TkInter is missing
|
||||
# 4.1 - Import tkFileDialog, don't assume something else will import it.
|
||||
|
||||
"""
|
||||
Decrypt Barnes & Noble encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "4.0"
|
||||
__version__ = "4.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -337,6 +339,7 @@ def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 6.5
|
||||
# ineptepub.pyw, version 6.6
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015–2016 by Apprentice Harper
|
||||
# Modified 2015–2017 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
@@ -42,13 +42,14 @@ from __future__ import with_statement
|
||||
# 6.3 - Add additional check on DER file sanity
|
||||
# 6.4 - Remove erroneous check on DER file sanity
|
||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||
# 6.6 - Import tkFileDialog, don't assume something else will import it.
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "6.5"
|
||||
__version__ = "6.6"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -484,6 +485,7 @@ def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptpdf.pyw, version 8.0.4
|
||||
# ineptpdf.pyw, version 8.0.6
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015-2016 by Apprentice Harper
|
||||
# Modified 2015-2017 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
@@ -58,6 +58,7 @@ from __future__ import with_statement
|
||||
# 8.0.3 - Remove erroneous check on DER file sanity
|
||||
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
||||
# 8.0.5 - Do not process DRM-free documents
|
||||
# 8.0.6 - Replace use of float by Decimal for greater precision, and import tkFileDialog
|
||||
|
||||
|
||||
"""
|
||||
@@ -65,7 +66,7 @@ Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0.5"
|
||||
__version__ = "8.0.6"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -73,6 +74,7 @@ import re
|
||||
import zlib
|
||||
import struct
|
||||
import hashlib
|
||||
from decimal import *
|
||||
from itertools import chain, islice
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
@@ -653,7 +655,7 @@ class PSBaseParser(object):
|
||||
return (self.parse_number, j+1)
|
||||
if c == '.':
|
||||
self.token = c
|
||||
return (self.parse_float, j+1)
|
||||
return (self.parse_decimal, j+1)
|
||||
if c.isalpha():
|
||||
self.token = c
|
||||
return (self.parse_keyword, j+1)
|
||||
@@ -718,20 +720,21 @@ class PSBaseParser(object):
|
||||
c = s[j]
|
||||
if c == '.':
|
||||
self.token += c
|
||||
return (self.parse_float, j+1)
|
||||
return (self.parse_decimal, j+1)
|
||||
try:
|
||||
self.add_token(int(self.token))
|
||||
except ValueError:
|
||||
pass
|
||||
return (self.parse_main, j)
|
||||
def parse_float(self, s, i):
|
||||
|
||||
def parse_decimal(self, s, i):
|
||||
m = END_NUMBER.search(s, i)
|
||||
if not m:
|
||||
self.token += s[i:]
|
||||
return (self.parse_float, len(s))
|
||||
return (self.parse_decimal, len(s))
|
||||
j = m.start(0)
|
||||
self.token += s[i:j]
|
||||
self.add_token(float(self.token))
|
||||
self.add_token(Decimal(self.token))
|
||||
return (self.parse_main, j)
|
||||
|
||||
def parse_keyword(self, s, i):
|
||||
@@ -933,7 +936,7 @@ class PSStackParser(PSBaseParser):
|
||||
(pos, token) = self.nexttoken()
|
||||
##print (pos,token), (self.curtype, self.curstack)
|
||||
if (isinstance(token, int) or
|
||||
isinstance(token, float) or
|
||||
isinstance(token, Decimal) or
|
||||
isinstance(token, bool) or
|
||||
isinstance(token, str) or
|
||||
isinstance(token, PSLiteral)):
|
||||
@@ -1062,17 +1065,17 @@ def int_value(x):
|
||||
return 0
|
||||
return x
|
||||
|
||||
def float_value(x):
|
||||
def decimal_value(x):
|
||||
x = resolve1(x)
|
||||
if not isinstance(x, float):
|
||||
if not isinstance(x, Decimal):
|
||||
if STRICT:
|
||||
raise PDFTypeError('Float required: %r' % x)
|
||||
raise PDFTypeError('Decimal required: %r' % x)
|
||||
return 0.0
|
||||
return x
|
||||
|
||||
def num_value(x):
|
||||
x = resolve1(x)
|
||||
if not (isinstance(x, int) or isinstance(x, float)):
|
||||
if not (isinstance(x, int) or isinstance(x, Decimal)):
|
||||
if STRICT:
|
||||
raise PDFTypeError('Int or Float required: %r' % x)
|
||||
return 0
|
||||
@@ -2142,7 +2145,11 @@ class PDFSerializer(object):
|
||||
if self.last.isalnum():
|
||||
self.write(' ')
|
||||
self.write(str(obj).lower())
|
||||
elif isinstance(obj, (int, long, float)):
|
||||
elif isinstance(obj, (int, long)):
|
||||
if self.last.isalnum():
|
||||
self.write(' ')
|
||||
self.write(str(obj))
|
||||
elif isinstance(obj, Decimal):
|
||||
if self.last.isalnum():
|
||||
self.write(' ')
|
||||
self.write(str(obj))
|
||||
@@ -2218,6 +2225,7 @@ def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
|
||||
981
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ion.py
Normal file
981
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ion.py
Normal file
@@ -0,0 +1,981 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Pascal implementation by lulzkabulz. Python translation by apprenticenaomi. DeDRM integration by anon.
|
||||
# BinaryIon.pas + DrmIon.pas + IonSymbols.pas
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import collections
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import os.path
|
||||
import struct
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.py3compat import bchr, bord
|
||||
|
||||
try:
|
||||
# lzma library from calibre 2.35.0 or later
|
||||
import lzma.lzma1 as calibre_lzma
|
||||
except:
|
||||
calibre_lzma = None
|
||||
try:
|
||||
import lzma
|
||||
except:
|
||||
# Need pip backports.lzma on Python <3.3
|
||||
from backports import lzma
|
||||
|
||||
|
||||
TID_NULL = 0
|
||||
TID_BOOLEAN = 1
|
||||
TID_POSINT = 2
|
||||
TID_NEGINT = 3
|
||||
TID_FLOAT = 4
|
||||
TID_DECIMAL = 5
|
||||
TID_TIMESTAMP = 6
|
||||
TID_SYMBOL = 7
|
||||
TID_STRING = 8
|
||||
TID_CLOB = 9
|
||||
TID_BLOB = 0xA
|
||||
TID_LIST = 0xB
|
||||
TID_SEXP = 0xC
|
||||
TID_STRUCT = 0xD
|
||||
TID_TYPEDECL = 0xE
|
||||
TID_UNUSED = 0xF
|
||||
|
||||
|
||||
SID_UNKNOWN = -1
|
||||
SID_ION = 1
|
||||
SID_ION_1_0 = 2
|
||||
SID_ION_SYMBOL_TABLE = 3
|
||||
SID_NAME = 4
|
||||
SID_VERSION = 5
|
||||
SID_IMPORTS = 6
|
||||
SID_SYMBOLS = 7
|
||||
SID_MAX_ID = 8
|
||||
SID_ION_SHARED_SYMBOL_TABLE = 9
|
||||
SID_ION_1_0_MAX = 10
|
||||
|
||||
|
||||
LEN_IS_VAR_LEN = 0xE
|
||||
LEN_IS_NULL = 0xF
|
||||
|
||||
|
||||
VERSION_MARKER = b"\x01\x00\xEA"
|
||||
|
||||
|
||||
# asserts must always raise exceptions for proper functioning
|
||||
def _assert(test, msg="Exception"):
|
||||
if not test:
|
||||
raise Exception(msg)
|
||||
|
||||
|
||||
class SystemSymbols(object):
|
||||
ION = '$ion'
|
||||
ION_1_0 = '$ion_1_0'
|
||||
ION_SYMBOL_TABLE = '$ion_symbol_table'
|
||||
NAME = 'name'
|
||||
VERSION = 'version'
|
||||
IMPORTS = 'imports'
|
||||
SYMBOLS = 'symbols'
|
||||
MAX_ID = 'max_id'
|
||||
ION_SHARED_SYMBOL_TABLE = '$ion_shared_symbol_table'
|
||||
|
||||
|
||||
class IonCatalogItem(object):
|
||||
name = ""
|
||||
version = 0
|
||||
symnames = []
|
||||
|
||||
def __init__(self, name, version, symnames):
|
||||
self.name = name
|
||||
self.version = version
|
||||
self.symnames = symnames
|
||||
|
||||
|
||||
class SymbolToken(object):
|
||||
text = ""
|
||||
sid = 0
|
||||
|
||||
def __init__(self, text, sid):
|
||||
if text == "" and sid == 0:
|
||||
raise ValueError("Symbol token must have Text or SID")
|
||||
|
||||
self.text = text
|
||||
self.sid = sid
|
||||
|
||||
|
||||
class SymbolTable(object):
|
||||
table = None
|
||||
|
||||
def __init__(self):
|
||||
self.table = [None] * SID_ION_1_0_MAX
|
||||
self.table[SID_ION] = SystemSymbols.ION
|
||||
self.table[SID_ION_1_0] = SystemSymbols.ION_1_0
|
||||
self.table[SID_ION_SYMBOL_TABLE] = SystemSymbols.ION_SYMBOL_TABLE
|
||||
self.table[SID_NAME] = SystemSymbols.NAME
|
||||
self.table[SID_VERSION] = SystemSymbols.VERSION
|
||||
self.table[SID_IMPORTS] = SystemSymbols.IMPORTS
|
||||
self.table[SID_SYMBOLS] = SystemSymbols.SYMBOLS
|
||||
self.table[SID_MAX_ID] = SystemSymbols.MAX_ID
|
||||
self.table[SID_ION_SHARED_SYMBOL_TABLE] = SystemSymbols.ION_SHARED_SYMBOL_TABLE
|
||||
|
||||
def findbyid(self, sid):
|
||||
if sid < 1:
|
||||
raise ValueError("Invalid symbol id")
|
||||
|
||||
if sid < len(self.table):
|
||||
return self.table[sid]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def import_(self, table, maxid):
|
||||
for i in range(maxid):
|
||||
self.table.append(table.symnames[i])
|
||||
|
||||
def importunknown(self, name, maxid):
|
||||
for i in range(maxid):
|
||||
self.table.append("%s#%d" % (name, i + 1))
|
||||
|
||||
|
||||
class ParserState:
|
||||
Invalid,BeforeField,BeforeTID,BeforeValue,AfterValue,EOF = 1,2,3,4,5,6
|
||||
|
||||
ContainerRec = collections.namedtuple("ContainerRec", "nextpos, tid, remaining")
|
||||
|
||||
|
||||
class BinaryIonParser(object):
|
||||
eof = False
|
||||
state = None
|
||||
localremaining = 0
|
||||
needhasnext = False
|
||||
isinstruct = False
|
||||
valuetid = 0
|
||||
valuefieldid = 0
|
||||
parenttid = 0
|
||||
valuelen = 0
|
||||
valueisnull = False
|
||||
valueistrue = False
|
||||
value = None
|
||||
didimports = False
|
||||
|
||||
def __init__(self, stream):
|
||||
self.annotations = []
|
||||
self.catalog = []
|
||||
|
||||
self.stream = stream
|
||||
self.initpos = stream.tell()
|
||||
self.reset()
|
||||
self.symbols = SymbolTable()
|
||||
|
||||
def reset(self):
|
||||
self.state = ParserState.BeforeTID
|
||||
self.needhasnext = True
|
||||
self.localremaining = -1
|
||||
self.eof = False
|
||||
self.isinstruct = False
|
||||
self.containerstack = []
|
||||
self.stream.seek(self.initpos)
|
||||
|
||||
def addtocatalog(self, name, version, symbols):
|
||||
self.catalog.append(IonCatalogItem(name, version, symbols))
|
||||
|
||||
def hasnext(self):
|
||||
while self.needhasnext and not self.eof:
|
||||
self.hasnextraw()
|
||||
if len(self.containerstack) == 0 and not self.valueisnull:
|
||||
if self.valuetid == TID_SYMBOL:
|
||||
if self.value == SID_ION_1_0:
|
||||
self.needhasnext = True
|
||||
elif self.valuetid == TID_STRUCT:
|
||||
for a in self.annotations:
|
||||
if a == SID_ION_SYMBOL_TABLE:
|
||||
self.parsesymboltable()
|
||||
self.needhasnext = True
|
||||
break
|
||||
return not self.eof
|
||||
|
||||
def hasnextraw(self):
|
||||
self.clearvalue()
|
||||
while self.valuetid == -1 and not self.eof:
|
||||
self.needhasnext = False
|
||||
if self.state == ParserState.BeforeField:
|
||||
_assert(self.valuefieldid == SID_UNKNOWN)
|
||||
|
||||
self.valuefieldid = self.readfieldid()
|
||||
if self.valuefieldid != SID_UNKNOWN:
|
||||
self.state = ParserState.BeforeTID
|
||||
else:
|
||||
self.eof = True
|
||||
|
||||
elif self.state == ParserState.BeforeTID:
|
||||
self.state = ParserState.BeforeValue
|
||||
self.valuetid = self.readtypeid()
|
||||
if self.valuetid == -1:
|
||||
self.state = ParserState.EOF
|
||||
self.eof = True
|
||||
break
|
||||
|
||||
if self.valuetid == TID_TYPEDECL:
|
||||
if self.valuelen == 0:
|
||||
self.checkversionmarker()
|
||||
else:
|
||||
self.loadannotations()
|
||||
|
||||
elif self.state == ParserState.BeforeValue:
|
||||
self.skip(self.valuelen)
|
||||
self.state = ParserState.AfterValue
|
||||
|
||||
elif self.state == ParserState.AfterValue:
|
||||
if self.isinstruct:
|
||||
self.state = ParserState.BeforeField
|
||||
else:
|
||||
self.state = ParserState.BeforeTID
|
||||
|
||||
else:
|
||||
_assert(self.state == ParserState.EOF)
|
||||
|
||||
def next(self):
|
||||
if self.hasnext():
|
||||
self.needhasnext = True
|
||||
return self.valuetid
|
||||
else:
|
||||
return -1
|
||||
|
||||
def push(self, typeid, nextposition, nextremaining):
|
||||
self.containerstack.append(ContainerRec(nextpos=nextposition, tid=typeid, remaining=nextremaining))
|
||||
|
||||
def stepin(self):
|
||||
_assert(self.valuetid in [TID_STRUCT, TID_LIST, TID_SEXP] and not self.eof,
|
||||
"valuetid=%s eof=%s" % (self.valuetid, self.eof))
|
||||
_assert((not self.valueisnull or self.state == ParserState.AfterValue) and
|
||||
(self.valueisnull or self.state == ParserState.BeforeValue))
|
||||
|
||||
nextrem = self.localremaining
|
||||
if nextrem != -1:
|
||||
nextrem -= self.valuelen
|
||||
if nextrem < 0:
|
||||
nextrem = 0
|
||||
self.push(self.parenttid, self.stream.tell() + self.valuelen, nextrem)
|
||||
|
||||
self.isinstruct = (self.valuetid == TID_STRUCT)
|
||||
if self.isinstruct:
|
||||
self.state = ParserState.BeforeField
|
||||
else:
|
||||
self.state = ParserState.BeforeTID
|
||||
|
||||
self.localremaining = self.valuelen
|
||||
self.parenttid = self.valuetid
|
||||
self.clearvalue()
|
||||
self.needhasnext = True
|
||||
|
||||
def stepout(self):
|
||||
rec = self.containerstack.pop()
|
||||
|
||||
self.eof = False
|
||||
self.parenttid = rec.tid
|
||||
if self.parenttid == TID_STRUCT:
|
||||
self.isinstruct = True
|
||||
self.state = ParserState.BeforeField
|
||||
else:
|
||||
self.isinstruct = False
|
||||
self.state = ParserState.BeforeTID
|
||||
self.needhasnext = True
|
||||
|
||||
self.clearvalue()
|
||||
curpos = self.stream.tell()
|
||||
if rec.nextpos > curpos:
|
||||
self.skip(rec.nextpos - curpos)
|
||||
else:
|
||||
_assert(rec.nextpos == curpos)
|
||||
|
||||
self.localremaining = rec.remaining
|
||||
|
||||
def read(self, count=1):
|
||||
if self.localremaining != -1:
|
||||
self.localremaining -= count
|
||||
_assert(self.localremaining >= 0)
|
||||
|
||||
result = self.stream.read(count)
|
||||
if len(result) == 0:
|
||||
raise EOFError()
|
||||
return result
|
||||
|
||||
def readfieldid(self):
|
||||
if self.localremaining != -1 and self.localremaining < 1:
|
||||
return -1
|
||||
|
||||
try:
|
||||
return self.readvaruint()
|
||||
except EOFError:
|
||||
return -1
|
||||
|
||||
def readtypeid(self):
|
||||
if self.localremaining != -1:
|
||||
if self.localremaining < 1:
|
||||
return -1
|
||||
self.localremaining -= 1
|
||||
|
||||
b = self.stream.read(1)
|
||||
if len(b) < 1:
|
||||
return -1
|
||||
b = bord(b)
|
||||
result = b >> 4
|
||||
ln = b & 0xF
|
||||
|
||||
if ln == LEN_IS_VAR_LEN:
|
||||
ln = self.readvaruint()
|
||||
elif ln == LEN_IS_NULL:
|
||||
ln = 0
|
||||
self.state = ParserState.AfterValue
|
||||
elif result == TID_NULL:
|
||||
# Must have LEN_IS_NULL
|
||||
_assert(False)
|
||||
elif result == TID_BOOLEAN:
|
||||
_assert(ln <= 1)
|
||||
self.valueistrue = (ln == 1)
|
||||
ln = 0
|
||||
self.state = ParserState.AfterValue
|
||||
elif result == TID_STRUCT:
|
||||
if ln == 1:
|
||||
ln = self.readvaruint()
|
||||
|
||||
self.valuelen = ln
|
||||
return result
|
||||
|
||||
def readvarint(self):
|
||||
b = bord(self.read())
|
||||
negative = ((b & 0x40) != 0)
|
||||
result = (b & 0x3F)
|
||||
|
||||
i = 0
|
||||
while (b & 0x80) == 0 and i < 4:
|
||||
b = bord(self.read())
|
||||
result = (result << 7) | (b & 0x7F)
|
||||
i += 1
|
||||
|
||||
_assert(i < 4 or (b & 0x80) != 0, "int overflow")
|
||||
|
||||
if negative:
|
||||
return -result
|
||||
return result
|
||||
|
||||
def readvaruint(self):
|
||||
b = bord(self.read())
|
||||
result = (b & 0x7F)
|
||||
|
||||
i = 0
|
||||
while (b & 0x80) == 0 and i < 4:
|
||||
b = bord(self.read())
|
||||
result = (result << 7) | (b & 0x7F)
|
||||
i += 1
|
||||
|
||||
_assert(i < 4 or (b & 0x80) != 0, "int overflow")
|
||||
|
||||
return result
|
||||
|
||||
def readdecimal(self):
|
||||
if self.valuelen == 0:
|
||||
return 0.
|
||||
|
||||
rem = self.localremaining - self.valuelen
|
||||
self.localremaining = self.valuelen
|
||||
exponent = self.readvarint()
|
||||
|
||||
_assert(self.localremaining > 0, "Only exponent in ReadDecimal")
|
||||
_assert(self.localremaining <= 8, "Decimal overflow")
|
||||
|
||||
signed = False
|
||||
b = [bord(x) for x in self.read(self.localremaining)]
|
||||
if (b[0] & 0x80) != 0:
|
||||
b[0] = b[0] & 0x7F
|
||||
signed = True
|
||||
|
||||
# Convert variably sized network order integer into 64-bit little endian
|
||||
j = 0
|
||||
vb = [0] * 8
|
||||
for i in range(len(b), -1, -1):
|
||||
vb[i] = b[j]
|
||||
j += 1
|
||||
|
||||
v = struct.unpack("<Q", b"".join(bchr(x) for x in vb))[0]
|
||||
|
||||
result = v * (10 ** exponent)
|
||||
if signed:
|
||||
result = -result
|
||||
|
||||
self.localremaining = rem
|
||||
return result
|
||||
|
||||
def skip(self, count):
|
||||
if self.localremaining != -1:
|
||||
self.localremaining -= count
|
||||
if self.localremaining < 0:
|
||||
raise EOFError()
|
||||
|
||||
self.stream.seek(count, os.SEEK_CUR)
|
||||
|
||||
def parsesymboltable(self):
|
||||
self.next() # shouldn't do anything?
|
||||
|
||||
_assert(self.valuetid == TID_STRUCT)
|
||||
|
||||
if self.didimports:
|
||||
return
|
||||
|
||||
self.stepin()
|
||||
|
||||
fieldtype = self.next()
|
||||
while fieldtype != -1:
|
||||
if not self.valueisnull:
|
||||
_assert(self.valuefieldid == SID_IMPORTS, "Unsupported symbol table field id")
|
||||
|
||||
if fieldtype == TID_LIST:
|
||||
self.gatherimports()
|
||||
|
||||
fieldtype = self.next()
|
||||
|
||||
self.stepout()
|
||||
self.didimports = True
|
||||
|
||||
def gatherimports(self):
|
||||
self.stepin()
|
||||
|
||||
t = self.next()
|
||||
while t != -1:
|
||||
if not self.valueisnull and t == TID_STRUCT:
|
||||
self.readimport()
|
||||
|
||||
t = self.next()
|
||||
|
||||
self.stepout()
|
||||
|
||||
def readimport(self):
|
||||
version = -1
|
||||
maxid = -1
|
||||
name = ""
|
||||
|
||||
self.stepin()
|
||||
|
||||
t = self.next()
|
||||
while t != -1:
|
||||
if not self.valueisnull and self.valuefieldid != SID_UNKNOWN:
|
||||
if self.valuefieldid == SID_NAME:
|
||||
name = self.stringvalue()
|
||||
elif self.valuefieldid == SID_VERSION:
|
||||
version = self.intvalue()
|
||||
elif self.valuefieldid == SID_MAX_ID:
|
||||
maxid = self.intvalue()
|
||||
|
||||
t = self.next()
|
||||
|
||||
self.stepout()
|
||||
|
||||
if name == "" or name == SystemSymbols.ION:
|
||||
return
|
||||
|
||||
if version < 1:
|
||||
version = 1
|
||||
|
||||
table = self.findcatalogitem(name)
|
||||
if maxid < 0:
|
||||
_assert(table is not None and version == table.version, "Import %s lacks maxid" % name)
|
||||
maxid = len(table.symnames)
|
||||
|
||||
if table is not None:
|
||||
self.symbols.import_(table, min(maxid, len(table.symnames)))
|
||||
else:
|
||||
self.symbols.importunknown(name, maxid)
|
||||
|
||||
def intvalue(self):
|
||||
_assert(self.valuetid in [TID_POSINT, TID_NEGINT], "Not an int")
|
||||
|
||||
self.preparevalue()
|
||||
return self.value
|
||||
|
||||
def stringvalue(self):
|
||||
_assert(self.valuetid == TID_STRING, "Not a string")
|
||||
|
||||
if self.valueisnull:
|
||||
return ""
|
||||
|
||||
self.preparevalue()
|
||||
return self.value
|
||||
|
||||
def symbolvalue(self):
|
||||
_assert(self.valuetid == TID_SYMBOL, "Not a symbol")
|
||||
|
||||
self.preparevalue()
|
||||
result = self.symbols.findbyid(self.value)
|
||||
if result == "":
|
||||
result = "SYMBOL#%d" % self.value
|
||||
return result
|
||||
|
||||
def lobvalue(self):
|
||||
_assert(self.valuetid in [TID_CLOB, TID_BLOB], "Not a LOB type: %s" % self.getfieldname())
|
||||
|
||||
if self.valueisnull:
|
||||
return None
|
||||
|
||||
result = self.read(self.valuelen)
|
||||
self.state = ParserState.AfterValue
|
||||
return result
|
||||
|
||||
def decimalvalue(self):
|
||||
_assert(self.valuetid == TID_DECIMAL, "Not a decimal")
|
||||
|
||||
self.preparevalue()
|
||||
return self.value
|
||||
|
||||
def preparevalue(self):
|
||||
if self.value is None:
|
||||
self.loadscalarvalue()
|
||||
|
||||
def loadscalarvalue(self):
|
||||
if self.valuetid not in [TID_NULL, TID_BOOLEAN, TID_POSINT, TID_NEGINT,
|
||||
TID_FLOAT, TID_DECIMAL, TID_TIMESTAMP,
|
||||
TID_SYMBOL, TID_STRING]:
|
||||
return
|
||||
|
||||
if self.valueisnull:
|
||||
self.value = None
|
||||
return
|
||||
|
||||
if self.valuetid == TID_STRING:
|
||||
self.value = self.read(self.valuelen).decode("UTF-8")
|
||||
|
||||
elif self.valuetid in (TID_POSINT, TID_NEGINT, TID_SYMBOL):
|
||||
if self.valuelen == 0:
|
||||
self.value = 0
|
||||
else:
|
||||
_assert(self.valuelen <= 4, "int too long: %d" % self.valuelen)
|
||||
v = 0
|
||||
for i in range(self.valuelen - 1, -1, -1):
|
||||
v = (v | (bord(self.read()) << (i * 8)))
|
||||
|
||||
if self.valuetid == TID_NEGINT:
|
||||
self.value = -v
|
||||
else:
|
||||
self.value = v
|
||||
|
||||
elif self.valuetid == TID_DECIMAL:
|
||||
self.value = self.readdecimal()
|
||||
|
||||
#else:
|
||||
# _assert(False, "Unhandled scalar type %d" % self.valuetid)
|
||||
|
||||
self.state = ParserState.AfterValue
|
||||
|
||||
def clearvalue(self):
|
||||
self.valuetid = -1
|
||||
self.value = None
|
||||
self.valueisnull = False
|
||||
self.valuefieldid = SID_UNKNOWN
|
||||
self.annotations = []
|
||||
|
||||
def loadannotations(self):
|
||||
ln = self.readvaruint()
|
||||
maxpos = self.stream.tell() + ln
|
||||
while self.stream.tell() < maxpos:
|
||||
self.annotations.append(self.readvaruint())
|
||||
self.valuetid = self.readtypeid()
|
||||
|
||||
def checkversionmarker(self):
|
||||
for i in VERSION_MARKER:
|
||||
_assert(self.read() == i, "Unknown version marker")
|
||||
|
||||
self.valuelen = 0
|
||||
self.valuetid = TID_SYMBOL
|
||||
self.value = SID_ION_1_0
|
||||
self.valueisnull = False
|
||||
self.valuefieldid = SID_UNKNOWN
|
||||
self.state = ParserState.AfterValue
|
||||
|
||||
def findcatalogitem(self, name):
|
||||
for result in self.catalog:
|
||||
if result.name == name:
|
||||
return result
|
||||
|
||||
def forceimport(self, symbols):
|
||||
item = IonCatalogItem("Forced", 1, symbols)
|
||||
self.symbols.import_(item, len(symbols))
|
||||
|
||||
def getfieldname(self):
|
||||
if self.valuefieldid == SID_UNKNOWN:
|
||||
return ""
|
||||
return self.symbols.findbyid(self.valuefieldid)
|
||||
|
||||
def getfieldnamesymbol(self):
|
||||
return SymbolToken(self.getfieldname(), self.valuefieldid)
|
||||
|
||||
def gettypename(self):
|
||||
if len(self.annotations) == 0:
|
||||
return ""
|
||||
|
||||
return self.symbols.findbyid(self.annotations[0])
|
||||
|
||||
@staticmethod
|
||||
def printlob(b):
|
||||
if b is None:
|
||||
return "null"
|
||||
|
||||
result = ""
|
||||
for i in b:
|
||||
result += ("%02x " % bord(i))
|
||||
|
||||
if len(result) > 0:
|
||||
result = result[:-1]
|
||||
return result
|
||||
|
||||
def ionwalk(self, supert, indent, lst):
|
||||
while self.hasnext():
|
||||
if supert == TID_STRUCT:
|
||||
L = self.getfieldname() + ":"
|
||||
else:
|
||||
L = ""
|
||||
|
||||
t = self.next()
|
||||
if t in [TID_STRUCT, TID_LIST]:
|
||||
if L != "":
|
||||
lst.append(indent + L)
|
||||
L = self.gettypename()
|
||||
if L != "":
|
||||
lst.append(indent + L + "::")
|
||||
if t == TID_STRUCT:
|
||||
lst.append(indent + "{")
|
||||
else:
|
||||
lst.append(indent + "[")
|
||||
|
||||
self.stepin()
|
||||
self.ionwalk(t, indent + " ", lst)
|
||||
self.stepout()
|
||||
|
||||
if t == TID_STRUCT:
|
||||
lst.append(indent + "}")
|
||||
else:
|
||||
lst.append(indent + "]")
|
||||
|
||||
else:
|
||||
if t == TID_STRING:
|
||||
L += ('"%s"' % self.stringvalue())
|
||||
elif t in [TID_CLOB, TID_BLOB]:
|
||||
L += ("{%s}" % self.printlob(self.lobvalue()))
|
||||
elif t == TID_POSINT:
|
||||
L += str(self.intvalue())
|
||||
elif t == TID_SYMBOL:
|
||||
tn = self.gettypename()
|
||||
if tn != "":
|
||||
tn += "::"
|
||||
L += tn + self.symbolvalue()
|
||||
elif t == TID_DECIMAL:
|
||||
L += str(self.decimalvalue())
|
||||
else:
|
||||
L += ("TID %d" % t)
|
||||
lst.append(indent + L)
|
||||
|
||||
def print_(self, lst):
|
||||
self.reset()
|
||||
self.ionwalk(-1, "", lst)
|
||||
|
||||
|
||||
SYM_NAMES = [ 'com.amazon.drm.Envelope@1.0',
|
||||
'com.amazon.drm.EnvelopeMetadata@1.0', 'size', 'page_size',
|
||||
'encryption_key', 'encryption_transformation',
|
||||
'encryption_voucher', 'signing_key', 'signing_algorithm',
|
||||
'signing_voucher', 'com.amazon.drm.EncryptedPage@1.0',
|
||||
'cipher_text', 'cipher_iv', 'com.amazon.drm.Signature@1.0',
|
||||
'data', 'com.amazon.drm.EnvelopeIndexTable@1.0', 'length',
|
||||
'offset', 'algorithm', 'encoded', 'encryption_algorithm',
|
||||
'hashing_algorithm', 'expires', 'format', 'id',
|
||||
'lock_parameters', 'strategy', 'com.amazon.drm.Key@1.0',
|
||||
'com.amazon.drm.KeySet@1.0', 'com.amazon.drm.PIDv3@1.0',
|
||||
'com.amazon.drm.PlainTextPage@1.0',
|
||||
'com.amazon.drm.PlainText@1.0', 'com.amazon.drm.PrivateKey@1.0',
|
||||
'com.amazon.drm.PublicKey@1.0', 'com.amazon.drm.SecretKey@1.0',
|
||||
'com.amazon.drm.Voucher@1.0', 'public_key', 'private_key',
|
||||
'com.amazon.drm.KeyPair@1.0', 'com.amazon.drm.ProtectedData@1.0',
|
||||
'doctype', 'com.amazon.drm.EnvelopeIndexTableOffset@1.0',
|
||||
'enddoc', 'license_type', 'license', 'watermark', 'key', 'value',
|
||||
'com.amazon.drm.License@1.0', 'category', 'metadata',
|
||||
'categorized_metadata', 'com.amazon.drm.CategorizedMetadata@1.0',
|
||||
'com.amazon.drm.VoucherEnvelope@1.0', 'mac', 'voucher',
|
||||
'com.amazon.drm.ProtectedData@2.0',
|
||||
'com.amazon.drm.Envelope@2.0',
|
||||
'com.amazon.drm.EnvelopeMetadata@2.0',
|
||||
'com.amazon.drm.EncryptedPage@2.0',
|
||||
'com.amazon.drm.PlainText@2.0', 'compression_algorithm',
|
||||
'com.amazon.drm.Compressed@1.0', 'priority', 'refines']
|
||||
|
||||
def addprottable(ion):
|
||||
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
||||
|
||||
|
||||
def pkcs7pad(msg, blocklen):
|
||||
paddinglen = blocklen - len(msg) % blocklen
|
||||
padding = bchr(paddinglen) * paddinglen
|
||||
return msg + padding
|
||||
|
||||
|
||||
def pkcs7unpad(msg, blocklen):
|
||||
_assert(len(msg) % blocklen == 0)
|
||||
|
||||
paddinglen = bord(msg[-1])
|
||||
_assert(paddinglen > 0 and paddinglen <= blocklen, "Incorrect padding - Wrong key")
|
||||
_assert(msg[-paddinglen:] == bchr(paddinglen) * paddinglen, "Incorrect padding - Wrong key")
|
||||
|
||||
return msg[:-paddinglen]
|
||||
|
||||
|
||||
class DrmIonVoucher(object):
|
||||
envelope = None
|
||||
voucher = None
|
||||
drmkey = None
|
||||
license_type = "Unknown"
|
||||
|
||||
encalgorithm = ""
|
||||
enctransformation = ""
|
||||
hashalgorithm = ""
|
||||
|
||||
lockparams = None
|
||||
|
||||
ciphertext = b""
|
||||
cipheriv = b""
|
||||
secretkey = b""
|
||||
|
||||
def __init__(self, voucherenv, dsn, secret):
|
||||
self.dsn,self.secret = dsn,secret
|
||||
|
||||
self.lockparams = []
|
||||
|
||||
self.envelope = BinaryIonParser(voucherenv)
|
||||
addprottable(self.envelope)
|
||||
|
||||
def decryptvoucher(self):
|
||||
shared = "PIDv3" + self.encalgorithm + self.enctransformation + self.hashalgorithm
|
||||
|
||||
self.lockparams.sort()
|
||||
for param in self.lockparams:
|
||||
if param == "ACCOUNT_SECRET":
|
||||
shared += param + self.secret
|
||||
elif param == "CLIENT_ID":
|
||||
shared += param + self.dsn
|
||||
else:
|
||||
_assert(False, "Unknown lock parameter: %s" % param)
|
||||
|
||||
sharedsecret = shared.encode("UTF-8")
|
||||
|
||||
key = hmac.new(sharedsecret, sharedsecret[:5], digestmod=hashlib.sha256).digest()
|
||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||
b = aes.decrypt(self.ciphertext)
|
||||
b = pkcs7unpad(b, 16)
|
||||
|
||||
self.drmkey = BinaryIonParser(StringIO(b))
|
||||
addprottable(self.drmkey)
|
||||
|
||||
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
||||
"Expected KeySet, got %s" % self.drmkey.gettypename())
|
||||
|
||||
self.drmkey.stepin()
|
||||
while self.drmkey.hasnext():
|
||||
self.drmkey.next()
|
||||
if self.drmkey.gettypename() != "com.amazon.drm.SecretKey@1.0":
|
||||
continue
|
||||
|
||||
self.drmkey.stepin()
|
||||
while self.drmkey.hasnext():
|
||||
self.drmkey.next()
|
||||
if self.drmkey.getfieldname() == "algorithm":
|
||||
_assert(self.drmkey.stringvalue() == "AES", "Unknown cipher algorithm: %s" % self.drmkey.stringvalue())
|
||||
elif self.drmkey.getfieldname() == "format":
|
||||
_assert(self.drmkey.stringvalue() == "RAW", "Unknown key format: %s" % self.drmkey.stringvalue())
|
||||
elif self.drmkey.getfieldname() == "encoded":
|
||||
self.secretkey = self.drmkey.lobvalue()
|
||||
|
||||
self.drmkey.stepout()
|
||||
break
|
||||
|
||||
self.drmkey.stepout()
|
||||
|
||||
def parse(self):
|
||||
self.envelope.reset()
|
||||
_assert(self.envelope.hasnext(), "Envelope is empty")
|
||||
_assert(self.envelope.next() == TID_STRUCT and self.envelope.gettypename() == "com.amazon.drm.VoucherEnvelope@1.0",
|
||||
"Unknown type encountered in envelope, expected VoucherEnvelope")
|
||||
|
||||
self.envelope.stepin()
|
||||
while self.envelope.hasnext():
|
||||
self.envelope.next()
|
||||
field = self.envelope.getfieldname()
|
||||
if field == "voucher":
|
||||
self.voucher = BinaryIonParser(StringIO(self.envelope.lobvalue()))
|
||||
addprottable(self.voucher)
|
||||
continue
|
||||
elif field != "strategy":
|
||||
continue
|
||||
|
||||
_assert(self.envelope.gettypename() == "com.amazon.drm.PIDv3@1.0", "Unknown strategy: %s" % self.envelope.gettypename())
|
||||
|
||||
self.envelope.stepin()
|
||||
while self.envelope.hasnext():
|
||||
self.envelope.next()
|
||||
field = self.envelope.getfieldname()
|
||||
if field == "encryption_algorithm":
|
||||
self.encalgorithm = self.envelope.stringvalue()
|
||||
elif field == "encryption_transformation":
|
||||
self.enctransformation = self.envelope.stringvalue()
|
||||
elif field == "hashing_algorithm":
|
||||
self.hashalgorithm = self.envelope.stringvalue()
|
||||
elif field == "lock_parameters":
|
||||
self.envelope.stepin()
|
||||
while self.envelope.hasnext():
|
||||
_assert(self.envelope.next() == TID_STRING, "Expected string list for lock_parameters")
|
||||
self.lockparams.append(self.envelope.stringvalue())
|
||||
self.envelope.stepout()
|
||||
|
||||
self.envelope.stepout()
|
||||
|
||||
self.parsevoucher()
|
||||
|
||||
def parsevoucher(self):
|
||||
_assert(self.voucher.hasnext(), "Voucher is empty")
|
||||
_assert(self.voucher.next() == TID_STRUCT and self.voucher.gettypename() == "com.amazon.drm.Voucher@1.0",
|
||||
"Unknown type, expected Voucher")
|
||||
|
||||
self.voucher.stepin()
|
||||
while self.voucher.hasnext():
|
||||
self.voucher.next()
|
||||
|
||||
if self.voucher.getfieldname() == "cipher_iv":
|
||||
self.cipheriv = self.voucher.lobvalue()
|
||||
elif self.voucher.getfieldname() == "cipher_text":
|
||||
self.ciphertext = self.voucher.lobvalue()
|
||||
elif self.voucher.getfieldname() == "license":
|
||||
_assert(self.voucher.gettypename() == "com.amazon.drm.License@1.0",
|
||||
"Unknown license: %s" % self.voucher.gettypename())
|
||||
self.voucher.stepin()
|
||||
while self.voucher.hasnext():
|
||||
self.voucher.next()
|
||||
if self.voucher.getfieldname() == "license_type":
|
||||
self.license_type = self.voucher.stringvalue()
|
||||
self.voucher.stepout()
|
||||
|
||||
def printenvelope(self, lst):
|
||||
self.envelope.print_(lst)
|
||||
|
||||
def printkey(self, lst):
|
||||
if self.voucher is None:
|
||||
self.parse()
|
||||
if self.drmkey is None:
|
||||
self.decryptvoucher()
|
||||
|
||||
self.drmkey.print_(lst)
|
||||
|
||||
def printvoucher(self, lst):
|
||||
if self.voucher is None:
|
||||
self.parse()
|
||||
|
||||
self.voucher.print_(lst)
|
||||
|
||||
def getlicensetype(self):
|
||||
return self.license_type
|
||||
|
||||
|
||||
class DrmIon(object):
|
||||
ion = None
|
||||
voucher = None
|
||||
vouchername = ""
|
||||
key = b""
|
||||
onvoucherrequired = None
|
||||
|
||||
def __init__(self, ionstream, onvoucherrequired):
|
||||
self.ion = BinaryIonParser(ionstream)
|
||||
addprottable(self.ion)
|
||||
self.onvoucherrequired = onvoucherrequired
|
||||
|
||||
def parse(self, outpages):
|
||||
self.ion.reset()
|
||||
|
||||
_assert(self.ion.hasnext(), "DRMION envelope is empty")
|
||||
_assert(self.ion.next() == TID_SYMBOL and self.ion.gettypename() == "doctype", "Expected doctype symbol")
|
||||
_assert(self.ion.next() == TID_LIST and self.ion.gettypename() in ["com.amazon.drm.Envelope@1.0", "com.amazon.drm.Envelope@2.0"],
|
||||
"Unknown type encountered in DRMION envelope, expected Envelope, got %s" % self.ion.gettypename())
|
||||
|
||||
while True:
|
||||
if self.ion.gettypename() == "enddoc":
|
||||
break
|
||||
|
||||
self.ion.stepin()
|
||||
while self.ion.hasnext():
|
||||
self.ion.next()
|
||||
|
||||
if self.ion.gettypename() in ["com.amazon.drm.EnvelopeMetadata@1.0", "com.amazon.drm.EnvelopeMetadata@2.0"]:
|
||||
self.ion.stepin()
|
||||
while self.ion.hasnext():
|
||||
self.ion.next()
|
||||
if self.ion.getfieldname() != "encryption_voucher":
|
||||
continue
|
||||
|
||||
if self.vouchername == "":
|
||||
self.vouchername = self.ion.stringvalue()
|
||||
self.voucher = self.onvoucherrequired(self.vouchername)
|
||||
self.key = self.voucher.secretkey
|
||||
_assert(self.key is not None, "Unable to obtain secret key from voucher")
|
||||
else:
|
||||
_assert(self.vouchername == self.ion.stringvalue(),
|
||||
"Unexpected: Different vouchers required for same file?")
|
||||
|
||||
self.ion.stepout()
|
||||
|
||||
elif self.ion.gettypename() in ["com.amazon.drm.EncryptedPage@1.0", "com.amazon.drm.EncryptedPage@2.0"]:
|
||||
decompress = False
|
||||
ct = None
|
||||
civ = None
|
||||
self.ion.stepin()
|
||||
while self.ion.hasnext():
|
||||
self.ion.next()
|
||||
if self.ion.gettypename() == "com.amazon.drm.Compressed@1.0":
|
||||
decompress = True
|
||||
if self.ion.getfieldname() == "cipher_text":
|
||||
ct = self.ion.lobvalue()
|
||||
elif self.ion.getfieldname() == "cipher_iv":
|
||||
civ = self.ion.lobvalue()
|
||||
|
||||
if ct is not None and civ is not None:
|
||||
self.processpage(ct, civ, outpages, decompress)
|
||||
self.ion.stepout()
|
||||
|
||||
self.ion.stepout()
|
||||
if not self.ion.hasnext():
|
||||
break
|
||||
self.ion.next()
|
||||
|
||||
def print_(self, lst):
|
||||
self.ion.print_(lst)
|
||||
|
||||
def processpage(self, ct, civ, outpages, decompress):
|
||||
aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
|
||||
msg = pkcs7unpad(aes.decrypt(ct), 16)
|
||||
|
||||
if not decompress:
|
||||
outpages.write(msg)
|
||||
return
|
||||
|
||||
_assert(msg[0] == b"\x00", "LZMA UseFilter not supported")
|
||||
|
||||
if calibre_lzma is not None:
|
||||
with calibre_lzma.decompress(msg[1:], bufsize=0x1000000) as f:
|
||||
f.seek(0)
|
||||
outpages.write(f.read())
|
||||
return
|
||||
|
||||
decomp = lzma.LZMADecompressor(format=lzma.FORMAT_ALONE)
|
||||
while not decomp.eof:
|
||||
segment = decomp.decompress(msg[1:])
|
||||
msg = b"" # Contents were internally buffered after the first call
|
||||
outpages.write(segment)
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# k4mobidedrm.py, version 5.3
|
||||
# Copyright © 2009-2015 by ApprenticeHarper et al.
|
||||
# k4mobidedrm.py
|
||||
# Copyright © 2008-2017 by Apprentice Harper et al.
|
||||
|
||||
# engine to remove drm from Kindle and Mobipocket ebooks
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '5.5'
|
||||
|
||||
# Engine to remove drm from Kindle and Mobipocket ebooks
|
||||
# for personal use for archiving and converting your ebooks
|
||||
|
||||
# PLEASE DO NOT PIRATE EBOOKS!
|
||||
@@ -17,12 +20,11 @@ from __future__ import with_statement
|
||||
# readable for a long, long time
|
||||
|
||||
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
|
||||
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
|
||||
# and many many others
|
||||
# unswindle, DarkReverser, ApprenticeAlf, and many many others
|
||||
|
||||
# Special thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump
|
||||
# from which this script borrows most unashamedly.
|
||||
|
||||
|
||||
# Changelog
|
||||
# 1.0 - Name change to k4mobidedrm. Adds Mac support, Adds plugin code
|
||||
# 1.1 - Adds support for additional kindle.info files
|
||||
@@ -57,9 +59,8 @@ from __future__ import with_statement
|
||||
# 5.2 - Fixed error in command line processing of unicode arguments
|
||||
# 5.3 - Changed Android support to allow passing of backup .ab files
|
||||
# 5.4 - Recognise KFX files masquerading as azw, even if we can't decrypt them yet.
|
||||
|
||||
__version__ = '5.4'
|
||||
|
||||
# 5.5 - Added GPL v3 licence explicitly.
|
||||
# 5.x - Invoke KFXZipBook to handle zipped KFX files
|
||||
|
||||
import sys, os, re
|
||||
import csv
|
||||
@@ -83,11 +84,13 @@ if inCalibre:
|
||||
from calibre_plugins.dedrm import topazextract
|
||||
from calibre_plugins.dedrm import kgenpids
|
||||
from calibre_plugins.dedrm import androidkindlekey
|
||||
from calibre_plugins.dedrm import kfxdedrm
|
||||
else:
|
||||
import mobidedrm
|
||||
import topazextract
|
||||
import kgenpids
|
||||
import androidkindlekey
|
||||
import kfxdedrm
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -197,13 +200,15 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
|
||||
mobi = True
|
||||
magic8 = open(infile,'rb').read(8)
|
||||
if magic8 == '\xeaDRMION\xee':
|
||||
raise DrmException(u"KFX format detected. This format cannot be decrypted yet.")
|
||||
|
||||
raise DrmException(u"The .kfx DRMION file cannot be decrypted by itself. A .kfx-zip archive containing a DRM voucher is required.")
|
||||
|
||||
magic3 = magic8[:3]
|
||||
if magic3 == 'TPZ':
|
||||
mobi = False
|
||||
|
||||
if mobi:
|
||||
if magic8[:4] == 'PK\x03\x04':
|
||||
mb = kfxdedrm.KFXZipBook(infile)
|
||||
elif mobi:
|
||||
mb = mobidedrm.MobiBook(infile)
|
||||
else:
|
||||
mb = topazextract.TopazBook(infile)
|
||||
@@ -295,7 +300,7 @@ def usage(progname):
|
||||
def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2013 The Dark Reverser et al.".format(__version__)
|
||||
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2017 Apprentice Harper et al.".format(__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
|
||||
|
||||
108
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kfxdedrm.py
Normal file
108
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kfxdedrm.py
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# Engine to remove drm from Kindle KFX ebooks
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
import ion
|
||||
except:
|
||||
from calibre_plugins.dedrm import ion
|
||||
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.0'
|
||||
|
||||
|
||||
class KFXZipBook:
|
||||
def __init__(self, infile):
|
||||
self.infile = infile
|
||||
self.voucher = None
|
||||
self.decrypted = {}
|
||||
|
||||
def getPIDMetaInfo(self):
|
||||
return (None, None)
|
||||
|
||||
def processBook(self, totalpids):
|
||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
||||
for filename in zf.namelist():
|
||||
data = zf.read(filename)
|
||||
if data.startswith('\xeaDRMION\xee'):
|
||||
if self.voucher is None:
|
||||
self.decrypt_voucher(totalpids)
|
||||
print u'Decrypting KFX DRMION: {0}'.format(filename)
|
||||
outfile = StringIO()
|
||||
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
||||
self.decrypted[filename] = outfile.getvalue()
|
||||
|
||||
if not self.decrypted:
|
||||
print(u'The .kfx-zip archive does not contain an encrypted DRMION file')
|
||||
|
||||
def decrypt_voucher(self, totalpids):
|
||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
||||
for info in zf.infolist():
|
||||
if info.file_size < 0x10000:
|
||||
data = zf.read(info.filename)
|
||||
if data.startswith('\xe0\x01\x00\xea') and 'ProtectedData' in data:
|
||||
break # found DRM voucher
|
||||
else:
|
||||
raise Exception(u'The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher')
|
||||
|
||||
print u'Decrypting KFX DRM voucher: {0}'.format(info.filename)
|
||||
|
||||
for pid in [''] + totalpids:
|
||||
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,40)]:
|
||||
if len(pid) == dsn_len + secret_len:
|
||||
break # split pid into DSN and account secret
|
||||
else:
|
||||
continue
|
||||
|
||||
try:
|
||||
voucher = ion.DrmIonVoucher(StringIO(data), pid[:dsn_len], pid[dsn_len:])
|
||||
voucher.parse()
|
||||
voucher.decryptvoucher()
|
||||
break
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
raise Exception(u'Failed to decrypt KFX DRM voucher with any key')
|
||||
|
||||
print u'KFX DRM voucher successfully decrypted'
|
||||
|
||||
license_type = voucher.getlicensetype()
|
||||
if license_type != "Purchase":
|
||||
raise Exception((u'This book is licensed as {0}. '
|
||||
'These tools are intended for use on purchased books.').format(license_type))
|
||||
|
||||
self.voucher = voucher
|
||||
|
||||
def getBookTitle(self):
|
||||
return os.path.splitext(os.path.split(self.infile)[1])[0]
|
||||
|
||||
def getBookExtension(self):
|
||||
return '.kfx-zip'
|
||||
|
||||
def getBookType(self):
|
||||
return 'KFX-ZIP'
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
||||
|
||||
def getFile(self, outpath):
|
||||
if not self.decrypted:
|
||||
shutil.copyfile(self.infile, outpath)
|
||||
else:
|
||||
with zipfile.ZipFile(self.infile, 'r') as zif:
|
||||
with zipfile.ZipFile(outpath, 'w') as zof:
|
||||
for info in zif.infolist():
|
||||
zof.writestr(info, self.decrypted.get(info.filename, zif.read(info.filename)))
|
||||
@@ -4,10 +4,15 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kgenpids.py
|
||||
# Copyright © 2010-2015 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.1'
|
||||
|
||||
# Revision history:
|
||||
# 2.0 - Fix for non-ascii Windows user names
|
||||
# 2.1 - Actual fix for non-ascii WIndows user names.
|
||||
# x.x - Return information needed for KFX decryption
|
||||
|
||||
import sys
|
||||
import os, csv
|
||||
@@ -168,6 +173,9 @@ def pidFromSerial(s, l):
|
||||
|
||||
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
|
||||
def getKindlePids(rec209, token, serialnum):
|
||||
if rec209 is None:
|
||||
return [serialnum]
|
||||
|
||||
pids=[]
|
||||
|
||||
if isinstance(serialnum,unicode):
|
||||
@@ -195,20 +203,6 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
global charMap1
|
||||
pids = []
|
||||
|
||||
try:
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
|
||||
# Get the IDString used to decode the Kindle Info file
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
# Get the UserName stored when the Kindle Info file was decoded
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||
|
||||
except KeyError:
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
return pids
|
||||
|
||||
try:
|
||||
# Get the kindle account token, if present
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
@@ -217,14 +211,51 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
kindleAccountToken=""
|
||||
pass
|
||||
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(IDString,charMap1)
|
||||
try:
|
||||
# Get the DSN token, if present
|
||||
DSN = (kindleDatabase[1])['DSN'].decode('hex')
|
||||
print u"Got DSN key from database {0}".format(kindleDatabase[0])
|
||||
except KeyError:
|
||||
# See if we have the info to generate the DSN
|
||||
try:
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
#print u"Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
|
||||
|
||||
# Get the current user name
|
||||
encodedUsername = encodeHash(UserName,charMap1)
|
||||
try:
|
||||
# Get the SerialNumber token, if present
|
||||
IDString = (kindleDatabase[1])['SerialNumber'].decode('hex')
|
||||
print u"Got SerialNumber from database {0}".format(kindleDatabase[0])
|
||||
except KeyError:
|
||||
# Get the IDString we added
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
try:
|
||||
# Get the UsernameHash token, if present
|
||||
encodedUsername = (kindleDatabase[1])['UsernameHash'].decode('hex')
|
||||
print u"Got UsernameHash from database {0}".format(kindleDatabase[0])
|
||||
except KeyError:
|
||||
# Get the UserName we added
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||
# encode it
|
||||
encodedUsername = encodeHash(UserName,charMap1)
|
||||
#print u"encodedUsername",encodedUsername.encode('hex')
|
||||
except KeyError:
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
return pids
|
||||
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(IDString,charMap1)
|
||||
#print u"encodedIDString",encodedIDString.encode('hex')
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
#print u"DSN",DSN.encode('hex')
|
||||
pass
|
||||
|
||||
if rec209 is None:
|
||||
pids.append(DSN+kindleAccountToken)
|
||||
return pids
|
||||
|
||||
# Compute the device PID (for which I can tell, is used for nothing).
|
||||
table = generatePidEncryptionTable()
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindlekey.py
|
||||
# Copyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.5'
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||
@@ -22,15 +25,15 @@ from __future__ import with_statement
|
||||
# 2.1 - Fixed Kindle for PC encryption changes March 2016
|
||||
# 2.2 - Fixes for Macs with bonded ethernet ports
|
||||
# Also removed old .kinfo file support (pre-2011)
|
||||
# 2.3 - Added more field names thanks to concavegit's KFX code.
|
||||
# 2.4 - Fix for complex Mac disk setups, thanks to Tibs
|
||||
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
|
||||
|
||||
|
||||
"""
|
||||
Retrieve Kindle for PC/Mac user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.2'
|
||||
|
||||
import sys, os, re
|
||||
from struct import pack, unpack, unpack_from
|
||||
import json
|
||||
@@ -885,10 +888,18 @@ if iswindows:
|
||||
if errcd == 234:
|
||||
# bad wine implementation up through wine 1.3.21
|
||||
return "AlternateUserName"
|
||||
# double the buffer size
|
||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||
size.value = len(buffer)
|
||||
# return low byte of the unicode value of each character of the username
|
||||
return buffer.value.encode('utf-16-le')[::2]
|
||||
|
||||
# replace any non-ASCII values with 0xfffd
|
||||
for i in xrange(0,len(buffer)):
|
||||
if buffer[i]>u"\u007f":
|
||||
#print u"swapping char "+str(i)+" ("+buffer[i]+")"
|
||||
buffer[i] = u"\ufffd"
|
||||
# return utf-8 encoding of modified username
|
||||
#print u"modified username:"+buffer.value
|
||||
return buffer.value.encode('utf-8')
|
||||
return GetUserName
|
||||
GetUserName = GetUserName()
|
||||
|
||||
@@ -1010,8 +1021,16 @@ if iswindows:
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
'SerialNumber',\
|
||||
'UsernameHash',\
|
||||
'kindle.directedid.info',\
|
||||
'DSN',\
|
||||
'kindle.accounttype.info',\
|
||||
'krx.flashcardsplugin.data.encryption_key',\
|
||||
'krx.notebookexportplugin.data.encryption_key',\
|
||||
'proxy.http.password',\
|
||||
'proxy.http.username'
|
||||
]
|
||||
|
||||
DB = {}
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
data = infoReader.read()
|
||||
@@ -1263,7 +1282,7 @@ elif isosx:
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.startswith('/dev'):
|
||||
(devpart, mpath) = resline.split(' on ')
|
||||
(devpart, mpath) = resline.split(' on ')[:2]
|
||||
dpart = devpart[5:]
|
||||
names.append(dpart)
|
||||
return names
|
||||
@@ -1447,6 +1466,10 @@ elif isosx:
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
'SerialNumber',\
|
||||
'UsernameHash',\
|
||||
'kindle.directedid.info',\
|
||||
'DSN'
|
||||
]
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
filedata = infoReader.read()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# mobidedrm.py, version 0.38
|
||||
# mobidedrm.py
|
||||
# Copyright © 2008 The Dark Reverser
|
||||
#
|
||||
# Modified 2008–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Portions © 2008–2017 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = u"0.42"
|
||||
|
||||
# This is a python script. You need a Python interpreter to run it.
|
||||
# For example, ActiveState Python, which exists for windows.
|
||||
@@ -69,9 +71,7 @@
|
||||
# 0.39 - Fixed problem with TEXtREAd and getBookType interface
|
||||
# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 0.41 - Fixed potential unicode problem in command line calls
|
||||
|
||||
|
||||
__version__ = u"0.41"
|
||||
# 0.42 - Added GPL v3 licence. updated/removed some print statements
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -244,7 +244,7 @@ class MobiBook:
|
||||
pass
|
||||
|
||||
def __init__(self, infile):
|
||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
|
||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)
|
||||
|
||||
try:
|
||||
from alfcrypto import Pukall_Cipher
|
||||
@@ -288,10 +288,10 @@ class MobiBook:
|
||||
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
|
||||
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
|
||||
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
|
||||
print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
|
||||
#print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
|
||||
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
|
||||
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
|
||||
print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
|
||||
#print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
|
||||
if (self.compression != 17480):
|
||||
# multibyte utf8 data is included in the encryption for PalmDoc compression
|
||||
# so clear that byte so that we leave it to be decrypted.
|
||||
@@ -516,7 +516,7 @@ def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv)<3 or len(argv)>4:
|
||||
print u"MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
|
||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)
|
||||
print u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
|
||||
print u"Usage:"
|
||||
print u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname)
|
||||
|
||||
Binary file not shown.
@@ -2,6 +2,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# __init__.py for DeDRM_plugin
|
||||
# Copyright © 2008-2018 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -57,6 +61,10 @@ __docformat__ = 'restructuredtext en'
|
||||
# 6.5.1 - Updated version number, added PDF check for DRM-free documents
|
||||
# 6.5.2 - Another Topaz fix
|
||||
# 6.5.3 - Warn about KFX files explicitly
|
||||
# 6.5.4 - Mac App Fix, improve PDF decryption, handle latest tcl changes in ActivePython
|
||||
# 6.5.5 - Finally a fix for the Windows non-ASCII user names.
|
||||
# 6.6.0 - Add kfx and kfx-zip as supported file types (also invoke this plugin if the original
|
||||
# imported format was azw8 since that may be converted to kfx)
|
||||
|
||||
|
||||
"""
|
||||
@@ -64,7 +72,7 @@ Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 5, 3)
|
||||
PLUGIN_VERSION_TUPLE = (6, 6, 0)
|
||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||
@@ -112,7 +120,7 @@ class DeDRM(FileTypePlugin):
|
||||
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','azw8','tpz','kfx','kfx-zip'])
|
||||
on_import = True
|
||||
priority = 600
|
||||
|
||||
@@ -288,8 +296,8 @@ class DeDRM(FileTypePlugin):
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
|
||||
# import the Adobe Adept ePub handler
|
||||
import calibre_plugins.dedrm.ineptepub as ineptepub
|
||||
@@ -379,17 +387,19 @@ class DeDRM(FileTypePlugin):
|
||||
except:
|
||||
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
traceback.print_exc()
|
||||
print u"{0} v{1}: Decrypted with new default key after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Decrypted with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
print u"{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
traceback.print_exc()
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
|
||||
# Not a Barnes & Noble nor an Adobe Adept
|
||||
# Import the fixed epub.
|
||||
@@ -488,8 +498,8 @@ class DeDRM(FileTypePlugin):
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
|
||||
|
||||
def KindleMobiDecrypt(self,path_to_ebook):
|
||||
@@ -556,8 +566,8 @@ class DeDRM(FileTypePlugin):
|
||||
pass
|
||||
if not decoded:
|
||||
#if you reached here then no luck raise and exception
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
|
||||
of = self.temporary_file(book.getBookExtension())
|
||||
book.getFile(of.name)
|
||||
@@ -591,8 +601,8 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
|
||||
|
||||
def run(self, path_to_ebook):
|
||||
@@ -605,7 +615,7 @@ class DeDRM(FileTypePlugin):
|
||||
self.starttime = time.time()
|
||||
|
||||
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
|
||||
if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz']:
|
||||
if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz','kfx-zip']:
|
||||
# Kindle/Mobipocket
|
||||
decrypted_ebook = self.KindleMobiDecrypt(path_to_ebook)
|
||||
elif booktype == 'pdb':
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignobleepub.pyw, version 3.8
|
||||
# ignobleepub.pyw, version 4.1
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015–2017 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.6
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
@@ -35,13 +36,14 @@ from __future__ import with_statement
|
||||
# 3.8 - Fixed to retain zip file metadata (e.g. file modification date)
|
||||
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 4.0 - Work if TkInter is missing
|
||||
# 4.1 - Import tkFileDialog, don't assume something else will import it.
|
||||
|
||||
"""
|
||||
Decrypt Barnes & Noble encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "4.0"
|
||||
__version__ = "4.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -337,6 +339,7 @@ def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 6.5
|
||||
# ineptepub.pyw, version 6.6
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015–2016 by Apprentice Harper
|
||||
# Modified 2015–2017 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
@@ -42,13 +42,14 @@ from __future__ import with_statement
|
||||
# 6.3 - Add additional check on DER file sanity
|
||||
# 6.4 - Remove erroneous check on DER file sanity
|
||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||
# 6.6 - Import tkFileDialog, don't assume something else will import it.
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "6.5"
|
||||
__version__ = "6.6"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -484,6 +485,7 @@ def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptpdf.pyw, version 8.0.4
|
||||
# ineptpdf.pyw, version 8.0.6
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015-2016 by Apprentice Harper
|
||||
# Modified 2015-2017 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
@@ -58,6 +58,7 @@ from __future__ import with_statement
|
||||
# 8.0.3 - Remove erroneous check on DER file sanity
|
||||
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
||||
# 8.0.5 - Do not process DRM-free documents
|
||||
# 8.0.6 - Replace use of float by Decimal for greater precision, and import tkFileDialog
|
||||
|
||||
|
||||
"""
|
||||
@@ -65,7 +66,7 @@ Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0.5"
|
||||
__version__ = "8.0.6"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -73,6 +74,7 @@ import re
|
||||
import zlib
|
||||
import struct
|
||||
import hashlib
|
||||
from decimal import *
|
||||
from itertools import chain, islice
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
@@ -653,7 +655,7 @@ class PSBaseParser(object):
|
||||
return (self.parse_number, j+1)
|
||||
if c == '.':
|
||||
self.token = c
|
||||
return (self.parse_float, j+1)
|
||||
return (self.parse_decimal, j+1)
|
||||
if c.isalpha():
|
||||
self.token = c
|
||||
return (self.parse_keyword, j+1)
|
||||
@@ -718,20 +720,21 @@ class PSBaseParser(object):
|
||||
c = s[j]
|
||||
if c == '.':
|
||||
self.token += c
|
||||
return (self.parse_float, j+1)
|
||||
return (self.parse_decimal, j+1)
|
||||
try:
|
||||
self.add_token(int(self.token))
|
||||
except ValueError:
|
||||
pass
|
||||
return (self.parse_main, j)
|
||||
def parse_float(self, s, i):
|
||||
|
||||
def parse_decimal(self, s, i):
|
||||
m = END_NUMBER.search(s, i)
|
||||
if not m:
|
||||
self.token += s[i:]
|
||||
return (self.parse_float, len(s))
|
||||
return (self.parse_decimal, len(s))
|
||||
j = m.start(0)
|
||||
self.token += s[i:j]
|
||||
self.add_token(float(self.token))
|
||||
self.add_token(Decimal(self.token))
|
||||
return (self.parse_main, j)
|
||||
|
||||
def parse_keyword(self, s, i):
|
||||
@@ -933,7 +936,7 @@ class PSStackParser(PSBaseParser):
|
||||
(pos, token) = self.nexttoken()
|
||||
##print (pos,token), (self.curtype, self.curstack)
|
||||
if (isinstance(token, int) or
|
||||
isinstance(token, float) or
|
||||
isinstance(token, Decimal) or
|
||||
isinstance(token, bool) or
|
||||
isinstance(token, str) or
|
||||
isinstance(token, PSLiteral)):
|
||||
@@ -1062,17 +1065,17 @@ def int_value(x):
|
||||
return 0
|
||||
return x
|
||||
|
||||
def float_value(x):
|
||||
def decimal_value(x):
|
||||
x = resolve1(x)
|
||||
if not isinstance(x, float):
|
||||
if not isinstance(x, Decimal):
|
||||
if STRICT:
|
||||
raise PDFTypeError('Float required: %r' % x)
|
||||
raise PDFTypeError('Decimal required: %r' % x)
|
||||
return 0.0
|
||||
return x
|
||||
|
||||
def num_value(x):
|
||||
x = resolve1(x)
|
||||
if not (isinstance(x, int) or isinstance(x, float)):
|
||||
if not (isinstance(x, int) or isinstance(x, Decimal)):
|
||||
if STRICT:
|
||||
raise PDFTypeError('Int or Float required: %r' % x)
|
||||
return 0
|
||||
@@ -2142,7 +2145,11 @@ class PDFSerializer(object):
|
||||
if self.last.isalnum():
|
||||
self.write(' ')
|
||||
self.write(str(obj).lower())
|
||||
elif isinstance(obj, (int, long, float)):
|
||||
elif isinstance(obj, (int, long)):
|
||||
if self.last.isalnum():
|
||||
self.write(' ')
|
||||
self.write(str(obj))
|
||||
elif isinstance(obj, Decimal):
|
||||
if self.last.isalnum():
|
||||
self.write(' ')
|
||||
self.write(str(obj))
|
||||
@@ -2218,6 +2225,7 @@ def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
|
||||
981
DeDRM_calibre_plugin/DeDRM_plugin/ion.py
Normal file
981
DeDRM_calibre_plugin/DeDRM_plugin/ion.py
Normal file
@@ -0,0 +1,981 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Pascal implementation by lulzkabulz. Python translation by apprenticenaomi. DeDRM integration by anon.
|
||||
# BinaryIon.pas + DrmIon.pas + IonSymbols.pas
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import collections
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import os.path
|
||||
import struct
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.py3compat import bchr, bord
|
||||
|
||||
try:
|
||||
# lzma library from calibre 2.35.0 or later
|
||||
import lzma.lzma1 as calibre_lzma
|
||||
except:
|
||||
calibre_lzma = None
|
||||
try:
|
||||
import lzma
|
||||
except:
|
||||
# Need pip backports.lzma on Python <3.3
|
||||
from backports import lzma
|
||||
|
||||
|
||||
TID_NULL = 0
|
||||
TID_BOOLEAN = 1
|
||||
TID_POSINT = 2
|
||||
TID_NEGINT = 3
|
||||
TID_FLOAT = 4
|
||||
TID_DECIMAL = 5
|
||||
TID_TIMESTAMP = 6
|
||||
TID_SYMBOL = 7
|
||||
TID_STRING = 8
|
||||
TID_CLOB = 9
|
||||
TID_BLOB = 0xA
|
||||
TID_LIST = 0xB
|
||||
TID_SEXP = 0xC
|
||||
TID_STRUCT = 0xD
|
||||
TID_TYPEDECL = 0xE
|
||||
TID_UNUSED = 0xF
|
||||
|
||||
|
||||
SID_UNKNOWN = -1
|
||||
SID_ION = 1
|
||||
SID_ION_1_0 = 2
|
||||
SID_ION_SYMBOL_TABLE = 3
|
||||
SID_NAME = 4
|
||||
SID_VERSION = 5
|
||||
SID_IMPORTS = 6
|
||||
SID_SYMBOLS = 7
|
||||
SID_MAX_ID = 8
|
||||
SID_ION_SHARED_SYMBOL_TABLE = 9
|
||||
SID_ION_1_0_MAX = 10
|
||||
|
||||
|
||||
LEN_IS_VAR_LEN = 0xE
|
||||
LEN_IS_NULL = 0xF
|
||||
|
||||
|
||||
VERSION_MARKER = b"\x01\x00\xEA"
|
||||
|
||||
|
||||
# asserts must always raise exceptions for proper functioning
|
||||
def _assert(test, msg="Exception"):
|
||||
if not test:
|
||||
raise Exception(msg)
|
||||
|
||||
|
||||
class SystemSymbols(object):
|
||||
ION = '$ion'
|
||||
ION_1_0 = '$ion_1_0'
|
||||
ION_SYMBOL_TABLE = '$ion_symbol_table'
|
||||
NAME = 'name'
|
||||
VERSION = 'version'
|
||||
IMPORTS = 'imports'
|
||||
SYMBOLS = 'symbols'
|
||||
MAX_ID = 'max_id'
|
||||
ION_SHARED_SYMBOL_TABLE = '$ion_shared_symbol_table'
|
||||
|
||||
|
||||
class IonCatalogItem(object):
|
||||
name = ""
|
||||
version = 0
|
||||
symnames = []
|
||||
|
||||
def __init__(self, name, version, symnames):
|
||||
self.name = name
|
||||
self.version = version
|
||||
self.symnames = symnames
|
||||
|
||||
|
||||
class SymbolToken(object):
|
||||
text = ""
|
||||
sid = 0
|
||||
|
||||
def __init__(self, text, sid):
|
||||
if text == "" and sid == 0:
|
||||
raise ValueError("Symbol token must have Text or SID")
|
||||
|
||||
self.text = text
|
||||
self.sid = sid
|
||||
|
||||
|
||||
class SymbolTable(object):
|
||||
table = None
|
||||
|
||||
def __init__(self):
|
||||
self.table = [None] * SID_ION_1_0_MAX
|
||||
self.table[SID_ION] = SystemSymbols.ION
|
||||
self.table[SID_ION_1_0] = SystemSymbols.ION_1_0
|
||||
self.table[SID_ION_SYMBOL_TABLE] = SystemSymbols.ION_SYMBOL_TABLE
|
||||
self.table[SID_NAME] = SystemSymbols.NAME
|
||||
self.table[SID_VERSION] = SystemSymbols.VERSION
|
||||
self.table[SID_IMPORTS] = SystemSymbols.IMPORTS
|
||||
self.table[SID_SYMBOLS] = SystemSymbols.SYMBOLS
|
||||
self.table[SID_MAX_ID] = SystemSymbols.MAX_ID
|
||||
self.table[SID_ION_SHARED_SYMBOL_TABLE] = SystemSymbols.ION_SHARED_SYMBOL_TABLE
|
||||
|
||||
def findbyid(self, sid):
|
||||
if sid < 1:
|
||||
raise ValueError("Invalid symbol id")
|
||||
|
||||
if sid < len(self.table):
|
||||
return self.table[sid]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def import_(self, table, maxid):
|
||||
for i in range(maxid):
|
||||
self.table.append(table.symnames[i])
|
||||
|
||||
def importunknown(self, name, maxid):
|
||||
for i in range(maxid):
|
||||
self.table.append("%s#%d" % (name, i + 1))
|
||||
|
||||
|
||||
class ParserState:
|
||||
Invalid,BeforeField,BeforeTID,BeforeValue,AfterValue,EOF = 1,2,3,4,5,6
|
||||
|
||||
ContainerRec = collections.namedtuple("ContainerRec", "nextpos, tid, remaining")
|
||||
|
||||
|
||||
class BinaryIonParser(object):
|
||||
eof = False
|
||||
state = None
|
||||
localremaining = 0
|
||||
needhasnext = False
|
||||
isinstruct = False
|
||||
valuetid = 0
|
||||
valuefieldid = 0
|
||||
parenttid = 0
|
||||
valuelen = 0
|
||||
valueisnull = False
|
||||
valueistrue = False
|
||||
value = None
|
||||
didimports = False
|
||||
|
||||
def __init__(self, stream):
|
||||
self.annotations = []
|
||||
self.catalog = []
|
||||
|
||||
self.stream = stream
|
||||
self.initpos = stream.tell()
|
||||
self.reset()
|
||||
self.symbols = SymbolTable()
|
||||
|
||||
def reset(self):
|
||||
self.state = ParserState.BeforeTID
|
||||
self.needhasnext = True
|
||||
self.localremaining = -1
|
||||
self.eof = False
|
||||
self.isinstruct = False
|
||||
self.containerstack = []
|
||||
self.stream.seek(self.initpos)
|
||||
|
||||
def addtocatalog(self, name, version, symbols):
|
||||
self.catalog.append(IonCatalogItem(name, version, symbols))
|
||||
|
||||
def hasnext(self):
|
||||
while self.needhasnext and not self.eof:
|
||||
self.hasnextraw()
|
||||
if len(self.containerstack) == 0 and not self.valueisnull:
|
||||
if self.valuetid == TID_SYMBOL:
|
||||
if self.value == SID_ION_1_0:
|
||||
self.needhasnext = True
|
||||
elif self.valuetid == TID_STRUCT:
|
||||
for a in self.annotations:
|
||||
if a == SID_ION_SYMBOL_TABLE:
|
||||
self.parsesymboltable()
|
||||
self.needhasnext = True
|
||||
break
|
||||
return not self.eof
|
||||
|
||||
def hasnextraw(self):
|
||||
self.clearvalue()
|
||||
while self.valuetid == -1 and not self.eof:
|
||||
self.needhasnext = False
|
||||
if self.state == ParserState.BeforeField:
|
||||
_assert(self.valuefieldid == SID_UNKNOWN)
|
||||
|
||||
self.valuefieldid = self.readfieldid()
|
||||
if self.valuefieldid != SID_UNKNOWN:
|
||||
self.state = ParserState.BeforeTID
|
||||
else:
|
||||
self.eof = True
|
||||
|
||||
elif self.state == ParserState.BeforeTID:
|
||||
self.state = ParserState.BeforeValue
|
||||
self.valuetid = self.readtypeid()
|
||||
if self.valuetid == -1:
|
||||
self.state = ParserState.EOF
|
||||
self.eof = True
|
||||
break
|
||||
|
||||
if self.valuetid == TID_TYPEDECL:
|
||||
if self.valuelen == 0:
|
||||
self.checkversionmarker()
|
||||
else:
|
||||
self.loadannotations()
|
||||
|
||||
elif self.state == ParserState.BeforeValue:
|
||||
self.skip(self.valuelen)
|
||||
self.state = ParserState.AfterValue
|
||||
|
||||
elif self.state == ParserState.AfterValue:
|
||||
if self.isinstruct:
|
||||
self.state = ParserState.BeforeField
|
||||
else:
|
||||
self.state = ParserState.BeforeTID
|
||||
|
||||
else:
|
||||
_assert(self.state == ParserState.EOF)
|
||||
|
||||
def next(self):
|
||||
if self.hasnext():
|
||||
self.needhasnext = True
|
||||
return self.valuetid
|
||||
else:
|
||||
return -1
|
||||
|
||||
def push(self, typeid, nextposition, nextremaining):
|
||||
self.containerstack.append(ContainerRec(nextpos=nextposition, tid=typeid, remaining=nextremaining))
|
||||
|
||||
def stepin(self):
|
||||
_assert(self.valuetid in [TID_STRUCT, TID_LIST, TID_SEXP] and not self.eof,
|
||||
"valuetid=%s eof=%s" % (self.valuetid, self.eof))
|
||||
_assert((not self.valueisnull or self.state == ParserState.AfterValue) and
|
||||
(self.valueisnull or self.state == ParserState.BeforeValue))
|
||||
|
||||
nextrem = self.localremaining
|
||||
if nextrem != -1:
|
||||
nextrem -= self.valuelen
|
||||
if nextrem < 0:
|
||||
nextrem = 0
|
||||
self.push(self.parenttid, self.stream.tell() + self.valuelen, nextrem)
|
||||
|
||||
self.isinstruct = (self.valuetid == TID_STRUCT)
|
||||
if self.isinstruct:
|
||||
self.state = ParserState.BeforeField
|
||||
else:
|
||||
self.state = ParserState.BeforeTID
|
||||
|
||||
self.localremaining = self.valuelen
|
||||
self.parenttid = self.valuetid
|
||||
self.clearvalue()
|
||||
self.needhasnext = True
|
||||
|
||||
def stepout(self):
|
||||
rec = self.containerstack.pop()
|
||||
|
||||
self.eof = False
|
||||
self.parenttid = rec.tid
|
||||
if self.parenttid == TID_STRUCT:
|
||||
self.isinstruct = True
|
||||
self.state = ParserState.BeforeField
|
||||
else:
|
||||
self.isinstruct = False
|
||||
self.state = ParserState.BeforeTID
|
||||
self.needhasnext = True
|
||||
|
||||
self.clearvalue()
|
||||
curpos = self.stream.tell()
|
||||
if rec.nextpos > curpos:
|
||||
self.skip(rec.nextpos - curpos)
|
||||
else:
|
||||
_assert(rec.nextpos == curpos)
|
||||
|
||||
self.localremaining = rec.remaining
|
||||
|
||||
def read(self, count=1):
|
||||
if self.localremaining != -1:
|
||||
self.localremaining -= count
|
||||
_assert(self.localremaining >= 0)
|
||||
|
||||
result = self.stream.read(count)
|
||||
if len(result) == 0:
|
||||
raise EOFError()
|
||||
return result
|
||||
|
||||
def readfieldid(self):
|
||||
if self.localremaining != -1 and self.localremaining < 1:
|
||||
return -1
|
||||
|
||||
try:
|
||||
return self.readvaruint()
|
||||
except EOFError:
|
||||
return -1
|
||||
|
||||
def readtypeid(self):
|
||||
if self.localremaining != -1:
|
||||
if self.localremaining < 1:
|
||||
return -1
|
||||
self.localremaining -= 1
|
||||
|
||||
b = self.stream.read(1)
|
||||
if len(b) < 1:
|
||||
return -1
|
||||
b = bord(b)
|
||||
result = b >> 4
|
||||
ln = b & 0xF
|
||||
|
||||
if ln == LEN_IS_VAR_LEN:
|
||||
ln = self.readvaruint()
|
||||
elif ln == LEN_IS_NULL:
|
||||
ln = 0
|
||||
self.state = ParserState.AfterValue
|
||||
elif result == TID_NULL:
|
||||
# Must have LEN_IS_NULL
|
||||
_assert(False)
|
||||
elif result == TID_BOOLEAN:
|
||||
_assert(ln <= 1)
|
||||
self.valueistrue = (ln == 1)
|
||||
ln = 0
|
||||
self.state = ParserState.AfterValue
|
||||
elif result == TID_STRUCT:
|
||||
if ln == 1:
|
||||
ln = self.readvaruint()
|
||||
|
||||
self.valuelen = ln
|
||||
return result
|
||||
|
||||
def readvarint(self):
|
||||
b = bord(self.read())
|
||||
negative = ((b & 0x40) != 0)
|
||||
result = (b & 0x3F)
|
||||
|
||||
i = 0
|
||||
while (b & 0x80) == 0 and i < 4:
|
||||
b = bord(self.read())
|
||||
result = (result << 7) | (b & 0x7F)
|
||||
i += 1
|
||||
|
||||
_assert(i < 4 or (b & 0x80) != 0, "int overflow")
|
||||
|
||||
if negative:
|
||||
return -result
|
||||
return result
|
||||
|
||||
def readvaruint(self):
|
||||
b = bord(self.read())
|
||||
result = (b & 0x7F)
|
||||
|
||||
i = 0
|
||||
while (b & 0x80) == 0 and i < 4:
|
||||
b = bord(self.read())
|
||||
result = (result << 7) | (b & 0x7F)
|
||||
i += 1
|
||||
|
||||
_assert(i < 4 or (b & 0x80) != 0, "int overflow")
|
||||
|
||||
return result
|
||||
|
||||
def readdecimal(self):
|
||||
if self.valuelen == 0:
|
||||
return 0.
|
||||
|
||||
rem = self.localremaining - self.valuelen
|
||||
self.localremaining = self.valuelen
|
||||
exponent = self.readvarint()
|
||||
|
||||
_assert(self.localremaining > 0, "Only exponent in ReadDecimal")
|
||||
_assert(self.localremaining <= 8, "Decimal overflow")
|
||||
|
||||
signed = False
|
||||
b = [bord(x) for x in self.read(self.localremaining)]
|
||||
if (b[0] & 0x80) != 0:
|
||||
b[0] = b[0] & 0x7F
|
||||
signed = True
|
||||
|
||||
# Convert variably sized network order integer into 64-bit little endian
|
||||
j = 0
|
||||
vb = [0] * 8
|
||||
for i in range(len(b), -1, -1):
|
||||
vb[i] = b[j]
|
||||
j += 1
|
||||
|
||||
v = struct.unpack("<Q", b"".join(bchr(x) for x in vb))[0]
|
||||
|
||||
result = v * (10 ** exponent)
|
||||
if signed:
|
||||
result = -result
|
||||
|
||||
self.localremaining = rem
|
||||
return result
|
||||
|
||||
def skip(self, count):
|
||||
if self.localremaining != -1:
|
||||
self.localremaining -= count
|
||||
if self.localremaining < 0:
|
||||
raise EOFError()
|
||||
|
||||
self.stream.seek(count, os.SEEK_CUR)
|
||||
|
||||
def parsesymboltable(self):
|
||||
self.next() # shouldn't do anything?
|
||||
|
||||
_assert(self.valuetid == TID_STRUCT)
|
||||
|
||||
if self.didimports:
|
||||
return
|
||||
|
||||
self.stepin()
|
||||
|
||||
fieldtype = self.next()
|
||||
while fieldtype != -1:
|
||||
if not self.valueisnull:
|
||||
_assert(self.valuefieldid == SID_IMPORTS, "Unsupported symbol table field id")
|
||||
|
||||
if fieldtype == TID_LIST:
|
||||
self.gatherimports()
|
||||
|
||||
fieldtype = self.next()
|
||||
|
||||
self.stepout()
|
||||
self.didimports = True
|
||||
|
||||
def gatherimports(self):
|
||||
self.stepin()
|
||||
|
||||
t = self.next()
|
||||
while t != -1:
|
||||
if not self.valueisnull and t == TID_STRUCT:
|
||||
self.readimport()
|
||||
|
||||
t = self.next()
|
||||
|
||||
self.stepout()
|
||||
|
||||
def readimport(self):
|
||||
version = -1
|
||||
maxid = -1
|
||||
name = ""
|
||||
|
||||
self.stepin()
|
||||
|
||||
t = self.next()
|
||||
while t != -1:
|
||||
if not self.valueisnull and self.valuefieldid != SID_UNKNOWN:
|
||||
if self.valuefieldid == SID_NAME:
|
||||
name = self.stringvalue()
|
||||
elif self.valuefieldid == SID_VERSION:
|
||||
version = self.intvalue()
|
||||
elif self.valuefieldid == SID_MAX_ID:
|
||||
maxid = self.intvalue()
|
||||
|
||||
t = self.next()
|
||||
|
||||
self.stepout()
|
||||
|
||||
if name == "" or name == SystemSymbols.ION:
|
||||
return
|
||||
|
||||
if version < 1:
|
||||
version = 1
|
||||
|
||||
table = self.findcatalogitem(name)
|
||||
if maxid < 0:
|
||||
_assert(table is not None and version == table.version, "Import %s lacks maxid" % name)
|
||||
maxid = len(table.symnames)
|
||||
|
||||
if table is not None:
|
||||
self.symbols.import_(table, min(maxid, len(table.symnames)))
|
||||
else:
|
||||
self.symbols.importunknown(name, maxid)
|
||||
|
||||
def intvalue(self):
|
||||
_assert(self.valuetid in [TID_POSINT, TID_NEGINT], "Not an int")
|
||||
|
||||
self.preparevalue()
|
||||
return self.value
|
||||
|
||||
def stringvalue(self):
|
||||
_assert(self.valuetid == TID_STRING, "Not a string")
|
||||
|
||||
if self.valueisnull:
|
||||
return ""
|
||||
|
||||
self.preparevalue()
|
||||
return self.value
|
||||
|
||||
def symbolvalue(self):
|
||||
_assert(self.valuetid == TID_SYMBOL, "Not a symbol")
|
||||
|
||||
self.preparevalue()
|
||||
result = self.symbols.findbyid(self.value)
|
||||
if result == "":
|
||||
result = "SYMBOL#%d" % self.value
|
||||
return result
|
||||
|
||||
def lobvalue(self):
|
||||
_assert(self.valuetid in [TID_CLOB, TID_BLOB], "Not a LOB type: %s" % self.getfieldname())
|
||||
|
||||
if self.valueisnull:
|
||||
return None
|
||||
|
||||
result = self.read(self.valuelen)
|
||||
self.state = ParserState.AfterValue
|
||||
return result
|
||||
|
||||
def decimalvalue(self):
|
||||
_assert(self.valuetid == TID_DECIMAL, "Not a decimal")
|
||||
|
||||
self.preparevalue()
|
||||
return self.value
|
||||
|
||||
def preparevalue(self):
|
||||
if self.value is None:
|
||||
self.loadscalarvalue()
|
||||
|
||||
def loadscalarvalue(self):
|
||||
if self.valuetid not in [TID_NULL, TID_BOOLEAN, TID_POSINT, TID_NEGINT,
|
||||
TID_FLOAT, TID_DECIMAL, TID_TIMESTAMP,
|
||||
TID_SYMBOL, TID_STRING]:
|
||||
return
|
||||
|
||||
if self.valueisnull:
|
||||
self.value = None
|
||||
return
|
||||
|
||||
if self.valuetid == TID_STRING:
|
||||
self.value = self.read(self.valuelen).decode("UTF-8")
|
||||
|
||||
elif self.valuetid in (TID_POSINT, TID_NEGINT, TID_SYMBOL):
|
||||
if self.valuelen == 0:
|
||||
self.value = 0
|
||||
else:
|
||||
_assert(self.valuelen <= 4, "int too long: %d" % self.valuelen)
|
||||
v = 0
|
||||
for i in range(self.valuelen - 1, -1, -1):
|
||||
v = (v | (bord(self.read()) << (i * 8)))
|
||||
|
||||
if self.valuetid == TID_NEGINT:
|
||||
self.value = -v
|
||||
else:
|
||||
self.value = v
|
||||
|
||||
elif self.valuetid == TID_DECIMAL:
|
||||
self.value = self.readdecimal()
|
||||
|
||||
#else:
|
||||
# _assert(False, "Unhandled scalar type %d" % self.valuetid)
|
||||
|
||||
self.state = ParserState.AfterValue
|
||||
|
||||
def clearvalue(self):
|
||||
self.valuetid = -1
|
||||
self.value = None
|
||||
self.valueisnull = False
|
||||
self.valuefieldid = SID_UNKNOWN
|
||||
self.annotations = []
|
||||
|
||||
def loadannotations(self):
|
||||
ln = self.readvaruint()
|
||||
maxpos = self.stream.tell() + ln
|
||||
while self.stream.tell() < maxpos:
|
||||
self.annotations.append(self.readvaruint())
|
||||
self.valuetid = self.readtypeid()
|
||||
|
||||
def checkversionmarker(self):
|
||||
for i in VERSION_MARKER:
|
||||
_assert(self.read() == i, "Unknown version marker")
|
||||
|
||||
self.valuelen = 0
|
||||
self.valuetid = TID_SYMBOL
|
||||
self.value = SID_ION_1_0
|
||||
self.valueisnull = False
|
||||
self.valuefieldid = SID_UNKNOWN
|
||||
self.state = ParserState.AfterValue
|
||||
|
||||
def findcatalogitem(self, name):
|
||||
for result in self.catalog:
|
||||
if result.name == name:
|
||||
return result
|
||||
|
||||
def forceimport(self, symbols):
|
||||
item = IonCatalogItem("Forced", 1, symbols)
|
||||
self.symbols.import_(item, len(symbols))
|
||||
|
||||
def getfieldname(self):
|
||||
if self.valuefieldid == SID_UNKNOWN:
|
||||
return ""
|
||||
return self.symbols.findbyid(self.valuefieldid)
|
||||
|
||||
def getfieldnamesymbol(self):
|
||||
return SymbolToken(self.getfieldname(), self.valuefieldid)
|
||||
|
||||
def gettypename(self):
|
||||
if len(self.annotations) == 0:
|
||||
return ""
|
||||
|
||||
return self.symbols.findbyid(self.annotations[0])
|
||||
|
||||
@staticmethod
|
||||
def printlob(b):
|
||||
if b is None:
|
||||
return "null"
|
||||
|
||||
result = ""
|
||||
for i in b:
|
||||
result += ("%02x " % bord(i))
|
||||
|
||||
if len(result) > 0:
|
||||
result = result[:-1]
|
||||
return result
|
||||
|
||||
def ionwalk(self, supert, indent, lst):
|
||||
while self.hasnext():
|
||||
if supert == TID_STRUCT:
|
||||
L = self.getfieldname() + ":"
|
||||
else:
|
||||
L = ""
|
||||
|
||||
t = self.next()
|
||||
if t in [TID_STRUCT, TID_LIST]:
|
||||
if L != "":
|
||||
lst.append(indent + L)
|
||||
L = self.gettypename()
|
||||
if L != "":
|
||||
lst.append(indent + L + "::")
|
||||
if t == TID_STRUCT:
|
||||
lst.append(indent + "{")
|
||||
else:
|
||||
lst.append(indent + "[")
|
||||
|
||||
self.stepin()
|
||||
self.ionwalk(t, indent + " ", lst)
|
||||
self.stepout()
|
||||
|
||||
if t == TID_STRUCT:
|
||||
lst.append(indent + "}")
|
||||
else:
|
||||
lst.append(indent + "]")
|
||||
|
||||
else:
|
||||
if t == TID_STRING:
|
||||
L += ('"%s"' % self.stringvalue())
|
||||
elif t in [TID_CLOB, TID_BLOB]:
|
||||
L += ("{%s}" % self.printlob(self.lobvalue()))
|
||||
elif t == TID_POSINT:
|
||||
L += str(self.intvalue())
|
||||
elif t == TID_SYMBOL:
|
||||
tn = self.gettypename()
|
||||
if tn != "":
|
||||
tn += "::"
|
||||
L += tn + self.symbolvalue()
|
||||
elif t == TID_DECIMAL:
|
||||
L += str(self.decimalvalue())
|
||||
else:
|
||||
L += ("TID %d" % t)
|
||||
lst.append(indent + L)
|
||||
|
||||
def print_(self, lst):
|
||||
self.reset()
|
||||
self.ionwalk(-1, "", lst)
|
||||
|
||||
|
||||
SYM_NAMES = [ 'com.amazon.drm.Envelope@1.0',
|
||||
'com.amazon.drm.EnvelopeMetadata@1.0', 'size', 'page_size',
|
||||
'encryption_key', 'encryption_transformation',
|
||||
'encryption_voucher', 'signing_key', 'signing_algorithm',
|
||||
'signing_voucher', 'com.amazon.drm.EncryptedPage@1.0',
|
||||
'cipher_text', 'cipher_iv', 'com.amazon.drm.Signature@1.0',
|
||||
'data', 'com.amazon.drm.EnvelopeIndexTable@1.0', 'length',
|
||||
'offset', 'algorithm', 'encoded', 'encryption_algorithm',
|
||||
'hashing_algorithm', 'expires', 'format', 'id',
|
||||
'lock_parameters', 'strategy', 'com.amazon.drm.Key@1.0',
|
||||
'com.amazon.drm.KeySet@1.0', 'com.amazon.drm.PIDv3@1.0',
|
||||
'com.amazon.drm.PlainTextPage@1.0',
|
||||
'com.amazon.drm.PlainText@1.0', 'com.amazon.drm.PrivateKey@1.0',
|
||||
'com.amazon.drm.PublicKey@1.0', 'com.amazon.drm.SecretKey@1.0',
|
||||
'com.amazon.drm.Voucher@1.0', 'public_key', 'private_key',
|
||||
'com.amazon.drm.KeyPair@1.0', 'com.amazon.drm.ProtectedData@1.0',
|
||||
'doctype', 'com.amazon.drm.EnvelopeIndexTableOffset@1.0',
|
||||
'enddoc', 'license_type', 'license', 'watermark', 'key', 'value',
|
||||
'com.amazon.drm.License@1.0', 'category', 'metadata',
|
||||
'categorized_metadata', 'com.amazon.drm.CategorizedMetadata@1.0',
|
||||
'com.amazon.drm.VoucherEnvelope@1.0', 'mac', 'voucher',
|
||||
'com.amazon.drm.ProtectedData@2.0',
|
||||
'com.amazon.drm.Envelope@2.0',
|
||||
'com.amazon.drm.EnvelopeMetadata@2.0',
|
||||
'com.amazon.drm.EncryptedPage@2.0',
|
||||
'com.amazon.drm.PlainText@2.0', 'compression_algorithm',
|
||||
'com.amazon.drm.Compressed@1.0', 'priority', 'refines']
|
||||
|
||||
def addprottable(ion):
|
||||
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
||||
|
||||
|
||||
def pkcs7pad(msg, blocklen):
|
||||
paddinglen = blocklen - len(msg) % blocklen
|
||||
padding = bchr(paddinglen) * paddinglen
|
||||
return msg + padding
|
||||
|
||||
|
||||
def pkcs7unpad(msg, blocklen):
|
||||
_assert(len(msg) % blocklen == 0)
|
||||
|
||||
paddinglen = bord(msg[-1])
|
||||
_assert(paddinglen > 0 and paddinglen <= blocklen, "Incorrect padding - Wrong key")
|
||||
_assert(msg[-paddinglen:] == bchr(paddinglen) * paddinglen, "Incorrect padding - Wrong key")
|
||||
|
||||
return msg[:-paddinglen]
|
||||
|
||||
|
||||
class DrmIonVoucher(object):
|
||||
envelope = None
|
||||
voucher = None
|
||||
drmkey = None
|
||||
license_type = "Unknown"
|
||||
|
||||
encalgorithm = ""
|
||||
enctransformation = ""
|
||||
hashalgorithm = ""
|
||||
|
||||
lockparams = None
|
||||
|
||||
ciphertext = b""
|
||||
cipheriv = b""
|
||||
secretkey = b""
|
||||
|
||||
def __init__(self, voucherenv, dsn, secret):
|
||||
self.dsn,self.secret = dsn,secret
|
||||
|
||||
self.lockparams = []
|
||||
|
||||
self.envelope = BinaryIonParser(voucherenv)
|
||||
addprottable(self.envelope)
|
||||
|
||||
def decryptvoucher(self):
|
||||
shared = "PIDv3" + self.encalgorithm + self.enctransformation + self.hashalgorithm
|
||||
|
||||
self.lockparams.sort()
|
||||
for param in self.lockparams:
|
||||
if param == "ACCOUNT_SECRET":
|
||||
shared += param + self.secret
|
||||
elif param == "CLIENT_ID":
|
||||
shared += param + self.dsn
|
||||
else:
|
||||
_assert(False, "Unknown lock parameter: %s" % param)
|
||||
|
||||
sharedsecret = shared.encode("UTF-8")
|
||||
|
||||
key = hmac.new(sharedsecret, sharedsecret[:5], digestmod=hashlib.sha256).digest()
|
||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||
b = aes.decrypt(self.ciphertext)
|
||||
b = pkcs7unpad(b, 16)
|
||||
|
||||
self.drmkey = BinaryIonParser(StringIO(b))
|
||||
addprottable(self.drmkey)
|
||||
|
||||
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
||||
"Expected KeySet, got %s" % self.drmkey.gettypename())
|
||||
|
||||
self.drmkey.stepin()
|
||||
while self.drmkey.hasnext():
|
||||
self.drmkey.next()
|
||||
if self.drmkey.gettypename() != "com.amazon.drm.SecretKey@1.0":
|
||||
continue
|
||||
|
||||
self.drmkey.stepin()
|
||||
while self.drmkey.hasnext():
|
||||
self.drmkey.next()
|
||||
if self.drmkey.getfieldname() == "algorithm":
|
||||
_assert(self.drmkey.stringvalue() == "AES", "Unknown cipher algorithm: %s" % self.drmkey.stringvalue())
|
||||
elif self.drmkey.getfieldname() == "format":
|
||||
_assert(self.drmkey.stringvalue() == "RAW", "Unknown key format: %s" % self.drmkey.stringvalue())
|
||||
elif self.drmkey.getfieldname() == "encoded":
|
||||
self.secretkey = self.drmkey.lobvalue()
|
||||
|
||||
self.drmkey.stepout()
|
||||
break
|
||||
|
||||
self.drmkey.stepout()
|
||||
|
||||
def parse(self):
|
||||
self.envelope.reset()
|
||||
_assert(self.envelope.hasnext(), "Envelope is empty")
|
||||
_assert(self.envelope.next() == TID_STRUCT and self.envelope.gettypename() == "com.amazon.drm.VoucherEnvelope@1.0",
|
||||
"Unknown type encountered in envelope, expected VoucherEnvelope")
|
||||
|
||||
self.envelope.stepin()
|
||||
while self.envelope.hasnext():
|
||||
self.envelope.next()
|
||||
field = self.envelope.getfieldname()
|
||||
if field == "voucher":
|
||||
self.voucher = BinaryIonParser(StringIO(self.envelope.lobvalue()))
|
||||
addprottable(self.voucher)
|
||||
continue
|
||||
elif field != "strategy":
|
||||
continue
|
||||
|
||||
_assert(self.envelope.gettypename() == "com.amazon.drm.PIDv3@1.0", "Unknown strategy: %s" % self.envelope.gettypename())
|
||||
|
||||
self.envelope.stepin()
|
||||
while self.envelope.hasnext():
|
||||
self.envelope.next()
|
||||
field = self.envelope.getfieldname()
|
||||
if field == "encryption_algorithm":
|
||||
self.encalgorithm = self.envelope.stringvalue()
|
||||
elif field == "encryption_transformation":
|
||||
self.enctransformation = self.envelope.stringvalue()
|
||||
elif field == "hashing_algorithm":
|
||||
self.hashalgorithm = self.envelope.stringvalue()
|
||||
elif field == "lock_parameters":
|
||||
self.envelope.stepin()
|
||||
while self.envelope.hasnext():
|
||||
_assert(self.envelope.next() == TID_STRING, "Expected string list for lock_parameters")
|
||||
self.lockparams.append(self.envelope.stringvalue())
|
||||
self.envelope.stepout()
|
||||
|
||||
self.envelope.stepout()
|
||||
|
||||
self.parsevoucher()
|
||||
|
||||
def parsevoucher(self):
|
||||
_assert(self.voucher.hasnext(), "Voucher is empty")
|
||||
_assert(self.voucher.next() == TID_STRUCT and self.voucher.gettypename() == "com.amazon.drm.Voucher@1.0",
|
||||
"Unknown type, expected Voucher")
|
||||
|
||||
self.voucher.stepin()
|
||||
while self.voucher.hasnext():
|
||||
self.voucher.next()
|
||||
|
||||
if self.voucher.getfieldname() == "cipher_iv":
|
||||
self.cipheriv = self.voucher.lobvalue()
|
||||
elif self.voucher.getfieldname() == "cipher_text":
|
||||
self.ciphertext = self.voucher.lobvalue()
|
||||
elif self.voucher.getfieldname() == "license":
|
||||
_assert(self.voucher.gettypename() == "com.amazon.drm.License@1.0",
|
||||
"Unknown license: %s" % self.voucher.gettypename())
|
||||
self.voucher.stepin()
|
||||
while self.voucher.hasnext():
|
||||
self.voucher.next()
|
||||
if self.voucher.getfieldname() == "license_type":
|
||||
self.license_type = self.voucher.stringvalue()
|
||||
self.voucher.stepout()
|
||||
|
||||
def printenvelope(self, lst):
|
||||
self.envelope.print_(lst)
|
||||
|
||||
def printkey(self, lst):
|
||||
if self.voucher is None:
|
||||
self.parse()
|
||||
if self.drmkey is None:
|
||||
self.decryptvoucher()
|
||||
|
||||
self.drmkey.print_(lst)
|
||||
|
||||
def printvoucher(self, lst):
|
||||
if self.voucher is None:
|
||||
self.parse()
|
||||
|
||||
self.voucher.print_(lst)
|
||||
|
||||
def getlicensetype(self):
|
||||
return self.license_type
|
||||
|
||||
|
||||
class DrmIon(object):
|
||||
ion = None
|
||||
voucher = None
|
||||
vouchername = ""
|
||||
key = b""
|
||||
onvoucherrequired = None
|
||||
|
||||
def __init__(self, ionstream, onvoucherrequired):
|
||||
self.ion = BinaryIonParser(ionstream)
|
||||
addprottable(self.ion)
|
||||
self.onvoucherrequired = onvoucherrequired
|
||||
|
||||
def parse(self, outpages):
|
||||
self.ion.reset()
|
||||
|
||||
_assert(self.ion.hasnext(), "DRMION envelope is empty")
|
||||
_assert(self.ion.next() == TID_SYMBOL and self.ion.gettypename() == "doctype", "Expected doctype symbol")
|
||||
_assert(self.ion.next() == TID_LIST and self.ion.gettypename() in ["com.amazon.drm.Envelope@1.0", "com.amazon.drm.Envelope@2.0"],
|
||||
"Unknown type encountered in DRMION envelope, expected Envelope, got %s" % self.ion.gettypename())
|
||||
|
||||
while True:
|
||||
if self.ion.gettypename() == "enddoc":
|
||||
break
|
||||
|
||||
self.ion.stepin()
|
||||
while self.ion.hasnext():
|
||||
self.ion.next()
|
||||
|
||||
if self.ion.gettypename() in ["com.amazon.drm.EnvelopeMetadata@1.0", "com.amazon.drm.EnvelopeMetadata@2.0"]:
|
||||
self.ion.stepin()
|
||||
while self.ion.hasnext():
|
||||
self.ion.next()
|
||||
if self.ion.getfieldname() != "encryption_voucher":
|
||||
continue
|
||||
|
||||
if self.vouchername == "":
|
||||
self.vouchername = self.ion.stringvalue()
|
||||
self.voucher = self.onvoucherrequired(self.vouchername)
|
||||
self.key = self.voucher.secretkey
|
||||
_assert(self.key is not None, "Unable to obtain secret key from voucher")
|
||||
else:
|
||||
_assert(self.vouchername == self.ion.stringvalue(),
|
||||
"Unexpected: Different vouchers required for same file?")
|
||||
|
||||
self.ion.stepout()
|
||||
|
||||
elif self.ion.gettypename() in ["com.amazon.drm.EncryptedPage@1.0", "com.amazon.drm.EncryptedPage@2.0"]:
|
||||
decompress = False
|
||||
ct = None
|
||||
civ = None
|
||||
self.ion.stepin()
|
||||
while self.ion.hasnext():
|
||||
self.ion.next()
|
||||
if self.ion.gettypename() == "com.amazon.drm.Compressed@1.0":
|
||||
decompress = True
|
||||
if self.ion.getfieldname() == "cipher_text":
|
||||
ct = self.ion.lobvalue()
|
||||
elif self.ion.getfieldname() == "cipher_iv":
|
||||
civ = self.ion.lobvalue()
|
||||
|
||||
if ct is not None and civ is not None:
|
||||
self.processpage(ct, civ, outpages, decompress)
|
||||
self.ion.stepout()
|
||||
|
||||
self.ion.stepout()
|
||||
if not self.ion.hasnext():
|
||||
break
|
||||
self.ion.next()
|
||||
|
||||
def print_(self, lst):
|
||||
self.ion.print_(lst)
|
||||
|
||||
def processpage(self, ct, civ, outpages, decompress):
|
||||
aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
|
||||
msg = pkcs7unpad(aes.decrypt(ct), 16)
|
||||
|
||||
if not decompress:
|
||||
outpages.write(msg)
|
||||
return
|
||||
|
||||
_assert(msg[0] == b"\x00", "LZMA UseFilter not supported")
|
||||
|
||||
if calibre_lzma is not None:
|
||||
with calibre_lzma.decompress(msg[1:], bufsize=0x1000000) as f:
|
||||
f.seek(0)
|
||||
outpages.write(f.read())
|
||||
return
|
||||
|
||||
decomp = lzma.LZMADecompressor(format=lzma.FORMAT_ALONE)
|
||||
while not decomp.eof:
|
||||
segment = decomp.decompress(msg[1:])
|
||||
msg = b"" # Contents were internally buffered after the first call
|
||||
outpages.write(segment)
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# k4mobidedrm.py, version 5.3
|
||||
# Copyright © 2009-2015 by ApprenticeHarper et al.
|
||||
# k4mobidedrm.py
|
||||
# Copyright © 2008-2017 by Apprentice Harper et al.
|
||||
|
||||
# engine to remove drm from Kindle and Mobipocket ebooks
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '5.5'
|
||||
|
||||
# Engine to remove drm from Kindle and Mobipocket ebooks
|
||||
# for personal use for archiving and converting your ebooks
|
||||
|
||||
# PLEASE DO NOT PIRATE EBOOKS!
|
||||
@@ -17,12 +20,11 @@ from __future__ import with_statement
|
||||
# readable for a long, long time
|
||||
|
||||
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
|
||||
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
|
||||
# and many many others
|
||||
# unswindle, DarkReverser, ApprenticeAlf, and many many others
|
||||
|
||||
# Special thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump
|
||||
# from which this script borrows most unashamedly.
|
||||
|
||||
|
||||
# Changelog
|
||||
# 1.0 - Name change to k4mobidedrm. Adds Mac support, Adds plugin code
|
||||
# 1.1 - Adds support for additional kindle.info files
|
||||
@@ -57,9 +59,8 @@ from __future__ import with_statement
|
||||
# 5.2 - Fixed error in command line processing of unicode arguments
|
||||
# 5.3 - Changed Android support to allow passing of backup .ab files
|
||||
# 5.4 - Recognise KFX files masquerading as azw, even if we can't decrypt them yet.
|
||||
|
||||
__version__ = '5.4'
|
||||
|
||||
# 5.5 - Added GPL v3 licence explicitly.
|
||||
# 5.x - Invoke KFXZipBook to handle zipped KFX files
|
||||
|
||||
import sys, os, re
|
||||
import csv
|
||||
@@ -83,11 +84,13 @@ if inCalibre:
|
||||
from calibre_plugins.dedrm import topazextract
|
||||
from calibre_plugins.dedrm import kgenpids
|
||||
from calibre_plugins.dedrm import androidkindlekey
|
||||
from calibre_plugins.dedrm import kfxdedrm
|
||||
else:
|
||||
import mobidedrm
|
||||
import topazextract
|
||||
import kgenpids
|
||||
import androidkindlekey
|
||||
import kfxdedrm
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -197,13 +200,15 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
|
||||
mobi = True
|
||||
magic8 = open(infile,'rb').read(8)
|
||||
if magic8 == '\xeaDRMION\xee':
|
||||
raise DrmException(u"KFX format detected. This format cannot be decrypted yet.")
|
||||
|
||||
raise DrmException(u"The .kfx DRMION file cannot be decrypted by itself. A .kfx-zip archive containing a DRM voucher is required.")
|
||||
|
||||
magic3 = magic8[:3]
|
||||
if magic3 == 'TPZ':
|
||||
mobi = False
|
||||
|
||||
if mobi:
|
||||
if magic8[:4] == 'PK\x03\x04':
|
||||
mb = kfxdedrm.KFXZipBook(infile)
|
||||
elif mobi:
|
||||
mb = mobidedrm.MobiBook(infile)
|
||||
else:
|
||||
mb = topazextract.TopazBook(infile)
|
||||
@@ -295,7 +300,7 @@ def usage(progname):
|
||||
def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2013 The Dark Reverser et al.".format(__version__)
|
||||
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2017 Apprentice Harper et al.".format(__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
|
||||
|
||||
108
DeDRM_calibre_plugin/DeDRM_plugin/kfxdedrm.py
Normal file
108
DeDRM_calibre_plugin/DeDRM_plugin/kfxdedrm.py
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# Engine to remove drm from Kindle KFX ebooks
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
import ion
|
||||
except:
|
||||
from calibre_plugins.dedrm import ion
|
||||
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.0'
|
||||
|
||||
|
||||
class KFXZipBook:
|
||||
def __init__(self, infile):
|
||||
self.infile = infile
|
||||
self.voucher = None
|
||||
self.decrypted = {}
|
||||
|
||||
def getPIDMetaInfo(self):
|
||||
return (None, None)
|
||||
|
||||
def processBook(self, totalpids):
|
||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
||||
for filename in zf.namelist():
|
||||
data = zf.read(filename)
|
||||
if data.startswith('\xeaDRMION\xee'):
|
||||
if self.voucher is None:
|
||||
self.decrypt_voucher(totalpids)
|
||||
print u'Decrypting KFX DRMION: {0}'.format(filename)
|
||||
outfile = StringIO()
|
||||
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
||||
self.decrypted[filename] = outfile.getvalue()
|
||||
|
||||
if not self.decrypted:
|
||||
print(u'The .kfx-zip archive does not contain an encrypted DRMION file')
|
||||
|
||||
def decrypt_voucher(self, totalpids):
|
||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
||||
for info in zf.infolist():
|
||||
if info.file_size < 0x10000:
|
||||
data = zf.read(info.filename)
|
||||
if data.startswith('\xe0\x01\x00\xea') and 'ProtectedData' in data:
|
||||
break # found DRM voucher
|
||||
else:
|
||||
raise Exception(u'The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher')
|
||||
|
||||
print u'Decrypting KFX DRM voucher: {0}'.format(info.filename)
|
||||
|
||||
for pid in [''] + totalpids:
|
||||
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,40)]:
|
||||
if len(pid) == dsn_len + secret_len:
|
||||
break # split pid into DSN and account secret
|
||||
else:
|
||||
continue
|
||||
|
||||
try:
|
||||
voucher = ion.DrmIonVoucher(StringIO(data), pid[:dsn_len], pid[dsn_len:])
|
||||
voucher.parse()
|
||||
voucher.decryptvoucher()
|
||||
break
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
raise Exception(u'Failed to decrypt KFX DRM voucher with any key')
|
||||
|
||||
print u'KFX DRM voucher successfully decrypted'
|
||||
|
||||
license_type = voucher.getlicensetype()
|
||||
if license_type != "Purchase":
|
||||
raise Exception((u'This book is licensed as {0}. '
|
||||
'These tools are intended for use on purchased books.').format(license_type))
|
||||
|
||||
self.voucher = voucher
|
||||
|
||||
def getBookTitle(self):
|
||||
return os.path.splitext(os.path.split(self.infile)[1])[0]
|
||||
|
||||
def getBookExtension(self):
|
||||
return '.kfx-zip'
|
||||
|
||||
def getBookType(self):
|
||||
return 'KFX-ZIP'
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
||||
|
||||
def getFile(self, outpath):
|
||||
if not self.decrypted:
|
||||
shutil.copyfile(self.infile, outpath)
|
||||
else:
|
||||
with zipfile.ZipFile(self.infile, 'r') as zif:
|
||||
with zipfile.ZipFile(outpath, 'w') as zof:
|
||||
for info in zif.infolist():
|
||||
zof.writestr(info, self.decrypted.get(info.filename, zif.read(info.filename)))
|
||||
@@ -4,10 +4,15 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kgenpids.py
|
||||
# Copyright © 2010-2015 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.1'
|
||||
|
||||
# Revision history:
|
||||
# 2.0 - Fix for non-ascii Windows user names
|
||||
# 2.1 - Actual fix for non-ascii WIndows user names.
|
||||
# x.x - Return information needed for KFX decryption
|
||||
|
||||
import sys
|
||||
import os, csv
|
||||
@@ -168,6 +173,9 @@ def pidFromSerial(s, l):
|
||||
|
||||
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
|
||||
def getKindlePids(rec209, token, serialnum):
|
||||
if rec209 is None:
|
||||
return [serialnum]
|
||||
|
||||
pids=[]
|
||||
|
||||
if isinstance(serialnum,unicode):
|
||||
@@ -195,20 +203,6 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
global charMap1
|
||||
pids = []
|
||||
|
||||
try:
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
|
||||
# Get the IDString used to decode the Kindle Info file
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
# Get the UserName stored when the Kindle Info file was decoded
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||
|
||||
except KeyError:
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
return pids
|
||||
|
||||
try:
|
||||
# Get the kindle account token, if present
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
@@ -217,14 +211,51 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
kindleAccountToken=""
|
||||
pass
|
||||
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(IDString,charMap1)
|
||||
try:
|
||||
# Get the DSN token, if present
|
||||
DSN = (kindleDatabase[1])['DSN'].decode('hex')
|
||||
print u"Got DSN key from database {0}".format(kindleDatabase[0])
|
||||
except KeyError:
|
||||
# See if we have the info to generate the DSN
|
||||
try:
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
#print u"Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
|
||||
|
||||
# Get the current user name
|
||||
encodedUsername = encodeHash(UserName,charMap1)
|
||||
try:
|
||||
# Get the SerialNumber token, if present
|
||||
IDString = (kindleDatabase[1])['SerialNumber'].decode('hex')
|
||||
print u"Got SerialNumber from database {0}".format(kindleDatabase[0])
|
||||
except KeyError:
|
||||
# Get the IDString we added
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
try:
|
||||
# Get the UsernameHash token, if present
|
||||
encodedUsername = (kindleDatabase[1])['UsernameHash'].decode('hex')
|
||||
print u"Got UsernameHash from database {0}".format(kindleDatabase[0])
|
||||
except KeyError:
|
||||
# Get the UserName we added
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||
# encode it
|
||||
encodedUsername = encodeHash(UserName,charMap1)
|
||||
#print u"encodedUsername",encodedUsername.encode('hex')
|
||||
except KeyError:
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
return pids
|
||||
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(IDString,charMap1)
|
||||
#print u"encodedIDString",encodedIDString.encode('hex')
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
#print u"DSN",DSN.encode('hex')
|
||||
pass
|
||||
|
||||
if rec209 is None:
|
||||
pids.append(DSN+kindleAccountToken)
|
||||
return pids
|
||||
|
||||
# Compute the device PID (for which I can tell, is used for nothing).
|
||||
table = generatePidEncryptionTable()
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindlekey.py
|
||||
# Copyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.5'
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||
@@ -22,15 +25,15 @@ from __future__ import with_statement
|
||||
# 2.1 - Fixed Kindle for PC encryption changes March 2016
|
||||
# 2.2 - Fixes for Macs with bonded ethernet ports
|
||||
# Also removed old .kinfo file support (pre-2011)
|
||||
# 2.3 - Added more field names thanks to concavegit's KFX code.
|
||||
# 2.4 - Fix for complex Mac disk setups, thanks to Tibs
|
||||
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
|
||||
|
||||
|
||||
"""
|
||||
Retrieve Kindle for PC/Mac user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.2'
|
||||
|
||||
import sys, os, re
|
||||
from struct import pack, unpack, unpack_from
|
||||
import json
|
||||
@@ -885,10 +888,18 @@ if iswindows:
|
||||
if errcd == 234:
|
||||
# bad wine implementation up through wine 1.3.21
|
||||
return "AlternateUserName"
|
||||
# double the buffer size
|
||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||
size.value = len(buffer)
|
||||
# return low byte of the unicode value of each character of the username
|
||||
return buffer.value.encode('utf-16-le')[::2]
|
||||
|
||||
# replace any non-ASCII values with 0xfffd
|
||||
for i in xrange(0,len(buffer)):
|
||||
if buffer[i]>u"\u007f":
|
||||
#print u"swapping char "+str(i)+" ("+buffer[i]+")"
|
||||
buffer[i] = u"\ufffd"
|
||||
# return utf-8 encoding of modified username
|
||||
#print u"modified username:"+buffer.value
|
||||
return buffer.value.encode('utf-8')
|
||||
return GetUserName
|
||||
GetUserName = GetUserName()
|
||||
|
||||
@@ -1010,8 +1021,16 @@ if iswindows:
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
'SerialNumber',\
|
||||
'UsernameHash',\
|
||||
'kindle.directedid.info',\
|
||||
'DSN',\
|
||||
'kindle.accounttype.info',\
|
||||
'krx.flashcardsplugin.data.encryption_key',\
|
||||
'krx.notebookexportplugin.data.encryption_key',\
|
||||
'proxy.http.password',\
|
||||
'proxy.http.username'
|
||||
]
|
||||
|
||||
DB = {}
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
data = infoReader.read()
|
||||
@@ -1263,7 +1282,7 @@ elif isosx:
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.startswith('/dev'):
|
||||
(devpart, mpath) = resline.split(' on ')
|
||||
(devpart, mpath) = resline.split(' on ')[:2]
|
||||
dpart = devpart[5:]
|
||||
names.append(dpart)
|
||||
return names
|
||||
@@ -1447,6 +1466,10 @@ elif isosx:
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
'SerialNumber',\
|
||||
'UsernameHash',\
|
||||
'kindle.directedid.info',\
|
||||
'DSN'
|
||||
]
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
filedata = infoReader.read()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# mobidedrm.py, version 0.38
|
||||
# mobidedrm.py
|
||||
# Copyright © 2008 The Dark Reverser
|
||||
#
|
||||
# Modified 2008–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Portions © 2008–2017 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = u"0.42"
|
||||
|
||||
# This is a python script. You need a Python interpreter to run it.
|
||||
# For example, ActiveState Python, which exists for windows.
|
||||
@@ -69,9 +71,7 @@
|
||||
# 0.39 - Fixed problem with TEXtREAd and getBookType interface
|
||||
# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 0.41 - Fixed potential unicode problem in command line calls
|
||||
|
||||
|
||||
__version__ = u"0.41"
|
||||
# 0.42 - Added GPL v3 licence. updated/removed some print statements
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -244,7 +244,7 @@ class MobiBook:
|
||||
pass
|
||||
|
||||
def __init__(self, infile):
|
||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
|
||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)
|
||||
|
||||
try:
|
||||
from alfcrypto import Pukall_Cipher
|
||||
@@ -288,10 +288,10 @@ class MobiBook:
|
||||
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
|
||||
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
|
||||
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
|
||||
print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
|
||||
#print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
|
||||
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
|
||||
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
|
||||
print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
|
||||
#print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
|
||||
if (self.compression != 17480):
|
||||
# multibyte utf8 data is included in the encryption for PalmDoc compression
|
||||
# so clear that byte so that we leave it to be decrypted.
|
||||
@@ -516,7 +516,7 @@ def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv)<3 or len(argv)>4:
|
||||
print u"MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
|
||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)
|
||||
print u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
|
||||
print u"Usage:"
|
||||
print u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname)
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
DeDRM_plugin.zip
|
||||
DeDRM_plugin.zip
|
||||
================
|
||||
|
||||
This calibre plugin replaces many previously separate DRM removal plugins. Before you install this plugin, you should uninstall any older individual DRM removal plugins, e.g. K4MobiDeDRM. The exception is the obok plugin, which should not be removed.
|
||||
|
||||
This plugin will remove the DRM from
|
||||
- Kindle ebooks (from Kindle for Mac/PC and eInk Kindles).
|
||||
- Kindle ebooks (files from Kindle for Mac/PC* and eInk Kindles**).
|
||||
- Barnes and Noble ePubs
|
||||
- Adobe Digital Editions (v2.0.1*) ePubs (including Kobo and Google ePubs downloaded to ADE)
|
||||
- Adobe Digital Editions (v2.0.1***) ePubs (including Kobo and Google ePubs downloaded to ADE)
|
||||
- Adobe Digital Editions (v2.0.1) PDFs
|
||||
- Mobipocket ebooks
|
||||
- eReader PDB books
|
||||
|
||||
These tools do NOT work with kepubs downloaded using Kobo's desktop app (see the separate obok plugin) nor Apple's iBooks FairPlay DRM (see details about Requiem at the end of this file.)
|
||||
|
||||
* With Adobe Digital Editions 3.0 and later, Adobe have introduced a new, optional, DRM scheme. To avoid this new scheme, you should use Adobe Digital Editions 2.0.1. Some books are required to use the new DRM scheme and so will not download with ADE 2.0.1. If you still want such a book, you will need to use ADE 3.0 or later to download it, but you should remember that no tools to remove Adobe's new DRM scheme exist as of June 2016.
|
||||
* With Kindle for PC/Mac 1.19 and later, Amazon included support for their new KFX format. While the tools now include a first attempt at supporting drm removal for KFX format, we recommend using Kindle for PC/Mac 1.17 or earlier which prevents downloads of the new format, as conversions from the olde KF8 format are likely to be more successful.
|
||||
|
||||
** Some later Kindles support Amazon's new KFX format. And some books download in a split azw3/azw6 format. For best results, instead of using files downloaded directly to your Kindle, download from Amazon's web site 'for transfer via USB'. This will give you an single file to import. See also the FAQ entry about this.
|
||||
|
||||
*** With Adobe Digital Editions 3.0 and later, Adobe have introduced a new, optional, DRM scheme. To avoid this new scheme, you should use Adobe Digital Editions 2.0.1. Some books are required to use the new DRM scheme and so will not download with ADE 2.0.1. If you still want such a book, you will need to use ADE 3.0 or later to download it, but you should remember that no tools to remove Adobe's new DRM scheme exist as of October 2017.
|
||||
|
||||
|
||||
Installation
|
||||
@@ -63,6 +67,7 @@ The original mobidedrm and erdr2pml scripts were by The Dark Reverser
|
||||
The original topaz DRM removal script was by CMBDTC
|
||||
The original topaz format conversion scripts were by some_updates, clarknova and Bart Simpson
|
||||
The original obok script was by Physisticated
|
||||
The original KFX format decryption was by lulzkabulz, converted to python by Apprentice Naomi and integrated into the tools by tomthumb1997
|
||||
|
||||
The alfcrypto library is by some_updates
|
||||
The ePub encryption detection script is by Apprentice Alf, adapted from a script by Paul Durrant
|
||||
@@ -78,23 +83,24 @@ Linux Systems Only
|
||||
Instructions for installing Wine, Kindle for PC, Adobe Digital Editions, Python and PyCrypto
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
These instructions have been tested with Wine 1.4 on Ubuntu.
|
||||
These instructions have been tested with Wine 1.4 on Ubuntu but are now very out of date.
|
||||
|
||||
1. First download the software you're going to to have to install.
|
||||
a. Kindle for PC from http://www.amazon.co.uk/gp/kindle/pc/
|
||||
b. Adobe Digital Editions 1.7.x from http://helpx.adobe.com/digital-editions/kb/cant-install-digital-editions.html
|
||||
a. Adobe Digital Editions 1.7.x from http://helpx.adobe.com/digital-editions/kb/cant-install-digital-editions.html
|
||||
(Adobe Digital Editions 2.x doesn't work with Wine.)
|
||||
c. ActivePython 2.7.X for Windows (x86) from http://www.activestate.com/activepython/downloads
|
||||
d. PyCrypto 2.1 for 32bit Windows and Python 2.7 from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||
b. Python 2.7.X for Windows (x86) from https://www.python.org/ftp/python/2.7.13/python-2.7.13.msi
|
||||
c. PyCrypto 2.1 for 32bit Windows and Python 2.7 from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||
(PyCrypto downloads as a zip file. You will need to unzip it.)
|
||||
2. Install Wine for 32-bit x86. (e.g. on Ubuntu, Open the Ubuntu Software Center, search for Wine, and install "Wine Windows Program Loader".)
|
||||
2a. [update] Kindle for PC now requires Windows 7, so in the following setups, choose any option for Windows 7, not Windows XP.
|
||||
3. Run "Configure Wine", which will set up the default 'wineprefix'
|
||||
4. Run winetricks, select the default wineprefix and install component vcrun2008
|
||||
5. Run the mis-named "Uninstall Wine Software", which also allows installation of software.
|
||||
6. Install Kindle for PC. Accept all defaults and register with your Amazon Account.
|
||||
7. Install Adobe Digital Editions. Accept all defaults and register with your Adobe ID.
|
||||
8. Install ActiveState Python 2.7.x. Accept all defaults.
|
||||
9. Install PyCrypto 2.1. Accept all defaults.
|
||||
4. Navigate to "Install an application" and install Kindle. Alternatively, run `winetricks kindle`
|
||||
5. Install Adobe Digital Editions. Accept all defaults and register with your Adobe ID.
|
||||
6. Install Python 2.7.x using `msiexec /i python-2.7.8.msi`. Accept all defaults.
|
||||
7. Install PyCrypto 2.1. Accept all defaults.
|
||||
8. Unzip DeDRM_plugin.zip and move kindlekey.py to somewhere in drive_c, such as ~/.wine/drive_c/DeDRM/libraryfiles/kindlekey.py.
|
||||
9. Run `wine 'C:\Python27/python.exe' 'C:\DeDRM/libraryfiles/kindlekey.py'`, or wherever you copied kindlekey.py to.
|
||||
10. Import the resulting key file to the Calibre plugin through the Kindle for Mac/PC ebooks option.
|
||||
|
||||
|
||||
Instructions for getting Kindle for PC and Adobe Digital Editions default decryption keys
|
||||
|
||||
56
FAQs.md
56
FAQs.md
@@ -10,6 +10,7 @@ When your ebooks have DRM you are unable to convert the ebook from one format to
|
||||
|
||||
## So how can I remove DRM from my ebooks?
|
||||
Just download and use these tools, that's all! Uh, almost. There are a few, uh, provisos, a, a couple of quid pro quos.
|
||||
|
||||
* The tools don't work on all ebooks. For example, they don't work on any ebooks from Apple's iBooks store.
|
||||
* You must own the ebook - the tools won't work on library ebooks or rented ebooks or books from a friend.
|
||||
* You must not use these tools to give your ebooks to a hundred of your closest friends. Or to a million strangers. Authors need to sell books to be able to write more books. Don't be mean to the authors.
|
||||
@@ -18,7 +19,40 @@ Just download and use these tools, that's all! Uh, almost. There are a few, uh,
|
||||
But otherwise, if your ebook is from Amazon, Kobo, Barnes & Noble or any of the ebook stores selling ebooks compatible with Adobe Digital Editions 2.0.1, you should be able to remove the DRM that's been applied to your ebooks.
|
||||
|
||||
### A Recent Change to Kindle for PC/Kindle for Mac
|
||||
Starting with version 1.19, Kindle for PC/Mac uses Amazon's new KFX format which these tools can't handle. Stick with version 1.17 or earlier. Kindle for PC 1.17 can be downloaded from https://s3.amazonaws.com/kindleforpc/44183/KindleForPC-installer-1.17.44183.exe and Kindle for Mac 1.17 can be downloaded from https://s3.amazonaws.com/kindleformac/44182/KindleForMac-44182.dmg
|
||||
Starting with version 1.19, Kindle for PC/Mac uses Amazon's new KFX format which these tools can't handle. There are two options to get the older formats that the tools can decrypt. Either stick with version 1.17 or earlier, or modify the executable by changing a file name.
|
||||
|
||||
Version 1.17 of Kindle is are no longer available directly from Amazon, so you will need to search for the proper file name and find it on a third party site. The name is "KindleForPC-installer-1.17.44170.exe" for PC and "KindleForMac-44182.dmg" for Mac.
|
||||
Verify the one of the following cryptographic hash values, using software of your choice, before installing the downloaded file in order to avoid viruses. If the hash does not match, delete the downloaded file and try again from another site.
|
||||
Kindle for PC:
|
||||
MD-5: 53F793B562F4823721AA47D7DE099869
|
||||
SHA-1: 73C404D719F0DD8D4AE1C2C96612B095D6C86255
|
||||
SHA-256: 14E0F0053F1276C0C7C446892DC170344F707FBFE99B695176 2C120144163200
|
||||
Kindle for Mac:
|
||||
MD-5: E7E36D5369E1F3CF1D28E5D9115DF15F
|
||||
SHA-1: 7AB9A86B954CB23D622BD79E3257F8E2182D791C
|
||||
SHA-256: 28DC21246A9C7CDEDD2D6F0F4082E6BF7EF9DB9CE9D485548E 8A9E1D19EAE2AC.
|
||||
|
||||
You will need to go to the preferences and uncheck the auto update checkbox. Then download and install 1.17 over the top of the 1.19 installation. You'll also need to delete the KFX folders from your My Kindle Content folder.
|
||||
|
||||
A second possible solution is to use 1.19 or later, but disable KFX by renaming or disabling a necessary component of the application. This may or may not work on versions after 1.20. In a command window, enter the following commands when Kindle for PC/Mac is not running:
|
||||
|
||||
#### Windows
|
||||
ren %localappdata%\Amazon\Kindle\application\renderer-test.exe renderer-test.xxx
|
||||
|
||||
PC Note: The renderer-test program may be in a different location in some Kindle for PC installations. If the rename command fails look in other folders, such as C:\Program Files\Amazon\Kindle.
|
||||
|
||||
#### Macintosh
|
||||
chmod -x /Applications/Kindle.app/Contents/MacOS/renderer-test
|
||||
|
||||
Mac Note: If the chmod command fails with a permission error try again using sudo before chmod - sudo chmod [...]
|
||||
|
||||
After restarting the Kindle program any books previously downloaded in KFX format will no longer open. You will need to remove them from your device and re-download them. All future downloads will use the older Kindle formats instead of KFX although they will continue to be placed in one individual subdirectory per book.
|
||||
|
||||
#### Another Note on KFX
|
||||
It now possible, but not easy, to convert books from KFX to other formats in calibre by installing the optional KFX Input plugin. The lack of automatic DRM removal makes this process difficult so it is not recommended unless there is no other alternative, such as for Indic language books only available in KFX. There is a windows-only KFX DRM rmeoval program in the repository, but not yet integrated into the tools.
|
||||
|
||||
#### Thanks
|
||||
Thanks to jhowell for his investigations into KFX format and workarounds. Some of these instructions are from [his thread on the subject](https://www.mobileread.com/forums/showthread.php?t=283371) at MobileRead.
|
||||
|
||||
## Where can I get the latest version of these free DRM removal tools?
|
||||
Right here at github. Just go to the [releases page](https://github.com/apprenticeharper/DeDRM_tools/releases) and download the latest zip archive of the tools, named DeDRM\_tools\_X.X.X.zip, where X.X.X is the version number. You do not need to download the source code archive.
|
||||
@@ -43,7 +77,7 @@ We strongly recommend renaming the DeDRM\_tools\_X.X.X.zip archive (after extrac
|
||||
|
||||
## The Windows Application
|
||||
### I've installed ActiveState Python and PyCrypto, but the Windows application won't run. What have I done wrong?
|
||||
Nothing. There's a bug in the current ActiveState Python Windows installer that puts the Tcl code in the wrong place. See [this comment of mine at ActiveState community](https://community.activestate.com/node/19090). Just move the Tcl code to the correct place manually and the Windows app should run.
|
||||
Nothing. There's a bug in the some older ActiveState Python Windows installers that puts the Tcl code in the wrong place. See [this comment of mine at ActiveState community](https://community.activestate.com/node/19090). Just move the Tcl code to the correct place manually and the Windows app should run.
|
||||
|
||||
## The Macintosh Application
|
||||
### I can't open the Macintosh Application. Some message about it not being signed or something.
|
||||
@@ -52,6 +86,9 @@ Try right-clicking and select open. That might give you the option to open it an
|
||||
### I can't open the Macintosh Application at all. I get 'The aplication "DeDRM" can't be opened'
|
||||
Some unzip applications do not respect the execution bit setting. Try unzipping the main tools archive using the built-in Mac unzip utility.
|
||||
|
||||
### I can't open the Macintosh Application at all. I get 'spawn_via_launchd() failed, errno=111'
|
||||
There seems to be a bug in Apple's launch services. Try using the free [Maintenance utility](https://www.titanium-software.fr/en/maintenance.html) from Titanium Software to clear the launch cache and database.
|
||||
|
||||
# Using the Tools
|
||||
## I can’t get the tools to work on my rented or library ebooks.
|
||||
The tools are not designed to remove DRM from rented or library ebooks.
|
||||
@@ -73,7 +110,7 @@ Kindle for Mac ebooks are in either Library/Application Support/Kindle/My Kindle
|
||||
Adobe Digital Editions ebooks are in Documents/Digital Editions
|
||||
|
||||
### Windows
|
||||
Navigating from your My Documents folder
|
||||
Navigating from your "Documents" folder ("My Documents" folder, pre-Windows 7)
|
||||
|
||||
Kindle for PC ebooks are in My Kindle Content
|
||||
|
||||
@@ -87,12 +124,12 @@ If you cannot read the ebook on your current device or installed software, the t
|
||||
## I have installed the calibre plugin, and the book is not already in calibre, but the DRM does not get removed. It is a Kindle book.
|
||||
If you are on Windows 8 and using the Windows 8 AppStore Kindle app, you must download and install the Kindle for PC application directly from the Amazon website. The tools do not work with the Windows 8 AppStore Kindle app.
|
||||
|
||||
If you are on Windows, using the Kindle for PC application, and your windows user name has accented or other non-ASCII characters in it, this will cause the plugin to fail. Create a new user account with an ASCII-only username. Install and register Kindle for PC there, and see if things work running calibre and the plugin in that user account.
|
||||
|
||||
If this book is from an eInk Kindle, you must enter the serial number into the configuration dialog. The serial number is sixteen characters long, and is case-sensitive.
|
||||
If this book is from an eInk Kindle (e.g. Paperwhite), you must enter the serial number into the configuration dialog. The serial number is sixteen characters long, and is case-sensitive.
|
||||
|
||||
If this book is from Kindle for Mac or Kindle for PC, you must have the Kindle Software installed on the same computer and user account as your copy of calibre.
|
||||
|
||||
If this book is from Kindle for Mac or Kindle for PC, you must be using version 1.17 or below, see note at top of this file.
|
||||
|
||||
If the book is from Kindle for PC or Kindle for Mac and you think you are doing everything right, and you are getting this message, it is possible that the files containing the encryption key aren’t quite in the format the tools expect. To try to fix this:
|
||||
|
||||
1. Deregister Kindle for PC(Mac) from your Amazon account.
|
||||
@@ -100,14 +137,13 @@ If the book is from Kindle for PC or Kindle for Mac and you think you are doing
|
||||
1. Delete the Kindle for PC(Mac) preferences
|
||||
* PC: Delete the directory [home folder]\AppData\Local\Amazon (it might be hidden) and [home folder]\My Documents\My Kindle Content
|
||||
* Mac: Delete the directory [home folder]/Library/Application Support/Kindle/and/or [home folder]/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/ (one or both may be present and should be deleted)
|
||||
1. Reinstall Kindle for PC(Mac)
|
||||
1. Reinstall Kindle for PC(Mac) version 1.17 or earlier (see above for download links).
|
||||
1. Re-register Kindle for PC(Mac) with your Amazon account
|
||||
1. Download the ebook again. Do not use the files you have downloaded previously.
|
||||
|
||||
## Some of my books had their DRM removed, but some still say that they have DRM and will not convert.
|
||||
There are several possible reasons why only some books get their DRM removed.
|
||||
* You still don’t have the DRM removal tools working correctly, but some of your books didn’t have DRM in the first place.
|
||||
* You added your books to calibre in large batches. For some unknown reason, sometimes calibre stops using the plugin after a while on a single import. Please see [this issue](https://github.com/apprenticeharper/DeDRM_tools/issues/??/) at Apprentice Harper's github repository. As a work-around, delete the books that still have DRM and import them into calibre in batches of twenty.
|
||||
* Kindle only: It is a Topaz format book and contains some coding that the tools do not understand. You will need to get a log of the DeDRM attempt, and then create a [new issue at Apprentice Harper's github repository](https://github.com/apprenticeharper/DeDRM_tools/issues/), attaching the book and the log, so that the tools can be updated.
|
||||
|
||||
If you are still having problems with particular books, you will need to create a log of the DRM removal attempt for one of the problem books, and post that in a comment at Apprentice Alf's blog or in a new issue at Apprentice Harper's github repository.
|
||||
@@ -149,7 +185,9 @@ The tools only remove the DRM. No attempt is made to remove any personally ident
|
||||
Most Amazon Kindle ebooks are Mobipocket format ebooks, or the new KF8 format. However, some are in a format known as Topaz. The Topaz format is only used by Amazon. A Topaz ebook is a collections of glyphs and their positions on each page tagged with some additional information from that page including OCRed text (Optical Character Recognition generated Text) to allow searching, and some additional layout information. Each page of a Topaz ebook is effectively a description of an image of that page. To convert a Topaz ebook to another format is not easy as there is not a one-to-one mapping between glyphs and characters/fonts. To account for this, two different formats are generated by the DRM removal software. The first is an html description built from the OCRtext and images stored in the Topaz file (HTMLZ). This format is easily reflowed but may suffer from typical OCRtext errors including typos, garbled text, missing italics, missing bolds, etc. The second format uses the glyph and position information to create an accurate scalable vector graphics (SVG) image of each page of the book that can be viewed in web browsers that support svg images (Safari, Firefox 4 or later, etc). Additional conversion software can be used to convert these SVG images to an image only PDF file. The DeDRM calibre plugin only imports the HTMLZ versions of the Topaz ebook. The html version can be manually cleaned up and spell checked and then converted using Sigil/calibre to epubs, mobi ebooks, and etc.
|
||||
|
||||
## Are the tools open source? How can I be sure they are safe and not a trojan horse?
|
||||
All the DRM removal tools hosted here are almost entirely scripts of one kind or another: Python, Applescript or Windows Batch files. So they are inherently open source, and open to inspection by everyone who downloads them. The source for any compiled pieces are provided right inside the tools themselves.
|
||||
All the DRM removal tools hosted here are almost entirely scripts of one kind or another: Python, Applescript or Windows Batch files. So they are inherently open source, and open to inspection by everyone who downloads them.
|
||||
|
||||
There are some optional shared libraries (`*.dll`, `*.dylib`, and `*.so`) included for performance. The source for any compiled pieces are provided within `alfcrypto_src.zip`. If this is a concern either delete the binary files or manually rebuild them.
|
||||
|
||||
## What ebooks do these tools work on?
|
||||
The tools linked from this blog remove DRM from PDF, ePub, kePub (Kobo), eReader, Kindle (Mobipocket, KF8, Print Replica and Topaz) format ebooks using Adobe Adept, Barnes & Noble, Amazon, Kobo and eReader DRM schemes.
|
||||
|
||||
Binary file not shown.
@@ -19,7 +19,7 @@ except NameError:
|
||||
PLUGIN_NAME = 'Obok DeDRM'
|
||||
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
||||
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
||||
PLUGIN_VERSION_TUPLE = (6, 5, 3)
|
||||
PLUGIN_VERSION_TUPLE = (6, 5, 4)
|
||||
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
||||
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
||||
PLUGIN_AUTHORS = 'Anon'
|
||||
|
||||
@@ -665,41 +665,16 @@ class KoboFile(object):
|
||||
contents = contents[:-padding]
|
||||
return contents
|
||||
|
||||
def cli_main():
|
||||
description = __about__
|
||||
epilog = u"Parsing of arguments failed."
|
||||
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||
args = vars(parser.parse_args())
|
||||
serials = []
|
||||
devicedir = u""
|
||||
if args['devicedir']:
|
||||
devicedir = args['devicedir']
|
||||
|
||||
lib = KoboLibrary(serials, devicedir)
|
||||
|
||||
for i, book in enumerate(lib.books):
|
||||
print u"{0}: {1}".format(i + 1, book.title)
|
||||
|
||||
num_string = raw_input(u"Convert book number... ")
|
||||
try:
|
||||
num = int(num_string)
|
||||
book = lib.books[num - 1]
|
||||
except (ValueError, IndexError):
|
||||
exit()
|
||||
|
||||
def decrypt_book(book, lib):
|
||||
print u"Converting {0}".format(book.title)
|
||||
|
||||
zin = zipfile.ZipFile(book.filename, "r")
|
||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||
|
||||
if (book.type == 'drm-free'):
|
||||
print u"DRM-free book, conversion is not needed"
|
||||
shutil.copyfile(book.filename, outname)
|
||||
print u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))
|
||||
exit(0)
|
||||
|
||||
return 0
|
||||
result = 1
|
||||
for userkey in lib.userkeys:
|
||||
print u"Trying key: {0}".format(userkey.encode('hex_codec'))
|
||||
@@ -722,13 +697,50 @@ def cli_main():
|
||||
print u"Decryption failed."
|
||||
zout.close()
|
||||
os.remove(outname)
|
||||
|
||||
zin.close()
|
||||
lib.close()
|
||||
if result != 0:
|
||||
print u"Could not decrypt book with any of the keys found."
|
||||
return result
|
||||
|
||||
|
||||
def cli_main():
|
||||
description = __about__
|
||||
epilog = u"Parsing of arguments failed."
|
||||
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||
parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
|
||||
args = vars(parser.parse_args())
|
||||
serials = []
|
||||
devicedir = u""
|
||||
if args['devicedir']:
|
||||
devicedir = args['devicedir']
|
||||
|
||||
lib = KoboLibrary(serials, devicedir)
|
||||
|
||||
if args['all']:
|
||||
books = lib.books
|
||||
else:
|
||||
for i, book in enumerate(lib.books):
|
||||
print u"{0}: {1}".format(i + 1, book.title)
|
||||
print u"Or 'all'"
|
||||
|
||||
choice = raw_input(u"Convert book number... ")
|
||||
if choice == u'all':
|
||||
books = list(lib.books)
|
||||
else:
|
||||
try:
|
||||
num = int(choice)
|
||||
books = [lib.books[num - 1]]
|
||||
except (ValueError, IndexError):
|
||||
print u"Invalid choice. Exiting..."
|
||||
exit()
|
||||
|
||||
results = [decrypt_book(book, lib) for book in books]
|
||||
lib.close()
|
||||
overall_result = all(result != 0 for result in results)
|
||||
if overall_result != 0:
|
||||
print u"Could not decrypt book with any of the keys found."
|
||||
return overall_result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindlekey.py
|
||||
# Copyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.5'
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||
@@ -22,15 +25,15 @@ from __future__ import with_statement
|
||||
# 2.1 - Fixed Kindle for PC encryption changes March 2016
|
||||
# 2.2 - Fixes for Macs with bonded ethernet ports
|
||||
# Also removed old .kinfo file support (pre-2011)
|
||||
# 2.3 - Added more field names thanks to concavegit's KFX code.
|
||||
# 2.4 - Fix for complex Mac disk setups, thanks to Tibs
|
||||
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
|
||||
|
||||
|
||||
"""
|
||||
Retrieve Kindle for PC/Mac user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.2'
|
||||
|
||||
import sys, os, re
|
||||
from struct import pack, unpack, unpack_from
|
||||
import json
|
||||
@@ -885,10 +888,18 @@ if iswindows:
|
||||
if errcd == 234:
|
||||
# bad wine implementation up through wine 1.3.21
|
||||
return "AlternateUserName"
|
||||
# double the buffer size
|
||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||
size.value = len(buffer)
|
||||
# return low byte of the unicode value of each character of the username
|
||||
return buffer.value.encode('utf-16-le')[::2]
|
||||
|
||||
# replace any non-ASCII values with 0xfffd
|
||||
for i in xrange(0,len(buffer)):
|
||||
if buffer[i]>u"\u007f":
|
||||
#print u"swapping char "+str(i)+" ("+buffer[i]+")"
|
||||
buffer[i] = u"\ufffd"
|
||||
# return utf-8 encoding of modified username
|
||||
#print u"modified username:"+buffer.value
|
||||
return buffer.value.encode('utf-8')
|
||||
return GetUserName
|
||||
GetUserName = GetUserName()
|
||||
|
||||
@@ -1010,8 +1021,16 @@ if iswindows:
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
'SerialNumber',\
|
||||
'UsernameHash',\
|
||||
'kindle.directedid.info',\
|
||||
'DSN',\
|
||||
'kindle.accounttype.info',\
|
||||
'krx.flashcardsplugin.data.encryption_key',\
|
||||
'krx.notebookexportplugin.data.encryption_key',\
|
||||
'proxy.http.password',\
|
||||
'proxy.http.username'
|
||||
]
|
||||
|
||||
DB = {}
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
data = infoReader.read()
|
||||
@@ -1263,7 +1282,7 @@ elif isosx:
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.startswith('/dev'):
|
||||
(devpart, mpath) = resline.split(' on ')
|
||||
(devpart, mpath) = resline.split(' on ')[:2]
|
||||
dpart = devpart[5:]
|
||||
names.append(dpart)
|
||||
return names
|
||||
@@ -1447,6 +1466,10 @@ elif isosx:
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
'SerialNumber',\
|
||||
'UsernameHash',\
|
||||
'kindle.directedid.info',\
|
||||
'DSN'
|
||||
]
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
filedata = infoReader.read()
|
||||
|
||||
74
Other_Tools/Kobo/obok.py
Normal file → Executable file
74
Other_Tools/Kobo/obok.py
Normal file → Executable file
@@ -665,41 +665,16 @@ class KoboFile(object):
|
||||
contents = contents[:-padding]
|
||||
return contents
|
||||
|
||||
def cli_main():
|
||||
description = __about__
|
||||
epilog = u"Parsing of arguments failed."
|
||||
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||
args = vars(parser.parse_args())
|
||||
serials = []
|
||||
devicedir = u""
|
||||
if args['devicedir']:
|
||||
devicedir = args['devicedir']
|
||||
|
||||
lib = KoboLibrary(serials, devicedir)
|
||||
|
||||
for i, book in enumerate(lib.books):
|
||||
print u"{0}: {1}".format(i + 1, book.title)
|
||||
|
||||
num_string = raw_input(u"Convert book number... ")
|
||||
try:
|
||||
num = int(num_string)
|
||||
book = lib.books[num - 1]
|
||||
except (ValueError, IndexError):
|
||||
exit()
|
||||
|
||||
def decrypt_book(book, lib):
|
||||
print u"Converting {0}".format(book.title)
|
||||
|
||||
zin = zipfile.ZipFile(book.filename, "r")
|
||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||
|
||||
if (book.type == 'drm-free'):
|
||||
print u"DRM-free book, conversion is not needed"
|
||||
shutil.copyfile(book.filename, outname)
|
||||
print u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))
|
||||
exit(0)
|
||||
|
||||
return 0
|
||||
result = 1
|
||||
for userkey in lib.userkeys:
|
||||
print u"Trying key: {0}".format(userkey.encode('hex_codec'))
|
||||
@@ -722,13 +697,50 @@ def cli_main():
|
||||
print u"Decryption failed."
|
||||
zout.close()
|
||||
os.remove(outname)
|
||||
|
||||
zin.close()
|
||||
lib.close()
|
||||
if result != 0:
|
||||
print u"Could not decrypt book with any of the keys found."
|
||||
return result
|
||||
|
||||
|
||||
def cli_main():
|
||||
description = __about__
|
||||
epilog = u"Parsing of arguments failed."
|
||||
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||
parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
|
||||
args = vars(parser.parse_args())
|
||||
serials = []
|
||||
devicedir = u""
|
||||
if args['devicedir']:
|
||||
devicedir = args['devicedir']
|
||||
|
||||
lib = KoboLibrary(serials, devicedir)
|
||||
|
||||
if args['all']:
|
||||
books = lib.books
|
||||
else:
|
||||
for i, book in enumerate(lib.books):
|
||||
print u"{0}: {1}".format(i + 1, book.title)
|
||||
print u"Or 'all'"
|
||||
|
||||
choice = raw_input(u"Convert book number... ")
|
||||
if choice == u'all':
|
||||
books = list(lib.books)
|
||||
else:
|
||||
try:
|
||||
num = int(choice)
|
||||
books = [lib.books[num - 1]]
|
||||
except (ValueError, IndexError):
|
||||
print u"Invalid choice. Exiting..."
|
||||
exit()
|
||||
|
||||
results = [decrypt_book(book, lib) for book in books]
|
||||
lib.close()
|
||||
overall_result = all(result != 0 for result in results)
|
||||
if overall_result != 0:
|
||||
print u"Could not decrypt book with any of the keys found."
|
||||
return overall_result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Welcome to the tools!
|
||||
=====================
|
||||
|
||||
This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v6.5.3 archive from Apprentice Harper's github repository: https://github.com/apprenticeharper/DeDRM_tools/
|
||||
This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v6.6.0 archive from Apprentice Harper's github repository: https://github.com/apprenticeharper/DeDRM_tools/
|
||||
|
||||
The is archive includes tools to remove DRM from:
|
||||
|
||||
- Kindle ebooks (files from Kindle for Mac/PC and eInk Kindles).
|
||||
- Adobe Digital Editions (v2.0.1*) ePubs (including Kobo and Google ePubs downloaded to ADE)
|
||||
- Kindle ebooks (files from Kindle for Mac/PC* and eInk Kindles**).
|
||||
- Adobe Digital Editions (v2.0.1***) ePubs (including Kobo and Google ePubs downloaded to ADE)
|
||||
- Kobo kePubs from the Kobo Desktop application
|
||||
- Barnes and Noble ePubs
|
||||
- Adobe Digital Editions (v2.0.1) PDFs
|
||||
@@ -17,11 +17,15 @@ The is archive includes tools to remove DRM from:
|
||||
|
||||
These tools do NOT work with Apple's iBooks FairPlay DRM (see end of this file.)
|
||||
|
||||
* With Adobe Digital Editions 3.0 and later, Adobe have introduced a new, optional, DRM scheme. To avoid this new scheme, you should use Adobe Digital Editions 2.0.1. Some books are required to use the new DRM scheme and so will not download with ADE 2.0.1. If you still want such a book, you will need to use ADE 3.0 or later to download it, but you should remember that no tools to remove Adobe's new DRM scheme exist as of October 2016.
|
||||
* With Kindle for PC/Mac 1.19 and later, Amazon included support for their new KFX format. While the tools now include a first attempt at supporting drm removal for KFX format, we recommend using Kindle for PC/Mac 1.17 or earlier which prevents downloads of the new format, as conversions from the olde KF8 format are likely to be more successful.
|
||||
|
||||
** Some later Kindles support Amazon's new KFX format. And some books download in a split azw3/azw6 format. For best results, instead of using files downloaded directly to your Kindle, download from Amazon's web site 'for transfer via USB'. This will give you an single file to import. See also the FAQ entry about this.
|
||||
|
||||
*** With Adobe Digital Editions 3.0 and later, Adobe have introduced a new, optional, DRM scheme. To avoid this new scheme, you should use Adobe Digital Editions 2.0.1. Some books are required to use the new DRM scheme and so will not download with ADE 2.0.1. If you still want such a book, you will need to use ADE 3.0 or later to download it, but you should remember that no tools to remove Adobe's new DRM scheme exist as of June 2017.
|
||||
|
||||
About the tools
|
||||
---------------
|
||||
These tools are updated and maintained by Apprentice Alf and Apprentice Harper. You can find the latest updates at Apprentice Harper's github repository https://github.com/apprenticeharper/DeDRM_tools/ and get support by creating an issue at the repository (github account required) or by posting a comment at Apprentice Alf's blog: http://www.apprenticealf.wordpress.com/
|
||||
These tools are updated and maintained by Apprentice Harper and many others. You can find the latest updates at Apprentice Harper's github repository https://github.com/apprenticeharper/DeDRM_tools/ and get support by creating an issue at the repository (github account required) or by posting a comment at Apprentice Alf's blog: http://www.apprenticealf.wordpress.com/
|
||||
|
||||
If you re-post these tools, a link to the repository and/or the blog would be appreciated.
|
||||
|
||||
@@ -44,7 +48,7 @@ For instructions, see the obok_plugin_ReadMe.txt file in the Obok_calibre_plugin
|
||||
|
||||
DeDRM application for Mac OS X users: (Mac OS X 10.4 and above)
|
||||
---------------------------------------------------------------
|
||||
This application is a stand-alone DRM removal application for Mac OS X users. It is only needed for people who cannot or will not use the calibre plugin.
|
||||
This application is a stand-alone DRM removal application for Mac OS X users. It is only needed for people who cannot or will not use the calibre plugin. KFX support has not been tested yet.
|
||||
|
||||
For instructions, see the "DeDRM ReadMe.rtf" file in the DeDRM_Macintosh_Application folder.
|
||||
|
||||
@@ -56,7 +60,7 @@ DeDRM application for Windows users: (Windows XP through Windows 10)
|
||||
***This program requires that Python and PyCrypto be properly installed.***
|
||||
***See below for details on recommended versions and how to install them.***
|
||||
|
||||
This application is a stand-alone application for Windows users. It is only needed for people who cannot or will not use the calibre plugin.
|
||||
This application is a stand-alone application for Windows users. It is only needed for people who cannot or will not use the calibre plugin. KFX support has not been tested yet.
|
||||
|
||||
For instructions, see the DeDRM_App_ReadMe.txt file in the DeDRM_Windows_Applications folder.
|
||||
|
||||
@@ -86,14 +90,13 @@ A link to the tool for removing DRM from ScuolaBooks PDFs, created by "Hex".
|
||||
|
||||
Windows and Python
|
||||
------------------
|
||||
We **strongly** recommend ActiveState's Active Python 2.7 Community Edition for Windows. This can be downloaded for free from:
|
||||
We **strongly** recommend using calibre and the plugin.
|
||||
|
||||
If you really want to use the WIndows app or the individual scripts, you'll need to install python.
|
||||
ActiveState's Active Python 2.7 Community Edition for Windowscan be downloaded for free from:
|
||||
|
||||
http://www.activestate.com/activepython/downloads
|
||||
|
||||
We do **NOT** recommend the version of Python from python.org as it is missing various Windows specific libraries, does not install the Tk Widget kit (for graphical user interfaces) by default, and does not properly update the system PATH environment variable. Therefore using the default python.org build on Windows is simply an exercise in frustration for most Windows users.
|
||||
|
||||
Note that currently (October 2016) ActiveState Python puts the tcl library in the wrong place, and it needs to be manually moved. See this thread at activestate.com for the latest information: https://community.activestate.com/node/19090
|
||||
|
||||
In addition, Windows Users need PyCrypto:
|
||||
|
||||
There are many places to get PyCrypto installers for Windows. One such place is:
|
||||
@@ -135,6 +138,8 @@ The original inept and ignoble scripts were by i♥cabbages
|
||||
The original mobidedrm and erdr2pml scripts were by The Dark Reverser
|
||||
The original topaz DRM removal script was by CMBDTC
|
||||
The original topaz format conversion scripts were by some_updates, clarknova and Bart Simpson
|
||||
The original KFX format decryption was by lulzkabulz, converted to python by Apprentice Naomi and integrated into the tools by tomthumb1997
|
||||
|
||||
The original obok script was by Physisticated
|
||||
|
||||
The alfcrypto library is by some_updates
|
||||
|
||||
Reference in New Issue
Block a user