You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DeDRM_tools/DeDRM_plugin/ignoblekey.py

335 lines
12 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ignoblekey.py
# Copyright © 2015-2020 Apprentice Alf, Apprentice Harper et al.
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Revision history:
# 1.0 - Initial release
# 1.1 - remove duplicates and return last key as single key
# 2.0 - Python 3 for calibre 5.0
"""
Get Barnes & Noble EPUB user key from nook Studio log file
"""
__license__ = 'GPL v3'
__version__ = "2.0"
import sys
import os
import hashlib
import getopt
import re
# Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get
# encoded using "replace" before writing them.
class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
self.encoding = stream.encoding
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data, str):
data = data.encode(self.encoding,"replace")
self.stream.buffer.write(data)
self.stream.buffer.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
try:
from calibre.constants import iswindows, isosx
except:
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv():
if iswindows:
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
# strings.
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
# as a list of Unicode strings and encode them as utf-8
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return ["ignoblekey.py"]
else:
argvencoding = sys.stdin.encoding or "utf-8"
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
class DrmException(Exception):
pass
# Locate all of the nookStudy/nook for PC/Mac log file and return as list
def getNookLogFiles():
logFiles = []
found = False
if iswindows:
import winreg
# some 64 bit machines do not have the proper registry key for some reason
# or the python interface to the 32 vs 64 bit registry is broken
paths = set()
if 'LOCALAPPDATA' in os.environ.keys():
# Python 2.x does not return unicode env. Use Python 3.x
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
if os.path.isdir(path):
paths.add(path)
if 'USERPROFILE' in os.environ.keys():
# Python 2.x does not return unicode env. Use Python 3.x
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Local"
if os.path.isdir(path):
paths.add(path)
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming"
if os.path.isdir(path):
paths.add(path)
# User Shell Folders show take precedent over Shell Folders if present
try:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
if os.path.isdir(path):
paths.add(path)
except WindowsError:
pass
try:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'AppData')[0]
if os.path.isdir(path):
paths.add(path)
except WindowsError:
pass
try:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
if os.path.isdir(path):
paths.add(path)
except WindowsError:
pass
try:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'AppData')[0]
if os.path.isdir(path):
paths.add(path)
except WindowsError:
pass
for path in paths:
# look for nookStudy log file
logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
if os.path.isfile(logpath):
found = True
print('Found nookStudy log file: ' + logpath.encode('ascii','ignore'))
logFiles.append(logpath)
else:
home = os.getenv('HOME')
# check for BNClientLog.txt in various locations
testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/logs/BNClientLog.txt'
if os.path.isfile(testpath):
logFiles.append(testpath)
print('Found nookStudy log file: ' + testpath)
found = True
testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/indices/BNClientLog.txt'
if os.path.isfile(testpath):
logFiles.append(testpath)
print('Found nookStudy log file: ' + testpath)
found = True
testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/logs/BNClientLog.txt'
if os.path.isfile(testpath):
logFiles.append(testpath)
print('Found nookStudy log file: ' + testpath)
found = True
testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/indices/BNClientLog.txt'
if os.path.isfile(testpath):
logFiles.append(testpath)
print('Found nookStudy log file: ' + testpath)
found = True
if not found:
print('No nook Study log files have been found.')
return logFiles
# Extract CCHash key(s) from log file
def getKeysFromLog(kLogFile):
keys = []
regex = re.compile("ccHash: \"(.{28})\"");
for line in open(kLogFile):
for m in regex.findall(line):
keys.append(m)
return keys
# interface for calibre plugin
def nookkeys(files = []):
keys = []
if files == []:
files = getNookLogFiles()
for file in files:
fileKeys = getKeysFromLog(file)
if fileKeys:
print("Found {0} keys in the Nook Study log files".format(len(fileKeys)))
keys.extend(fileKeys)
return list(set(keys))
# interface for Python DeDRM
# returns single key or multiple keys, depending on path or file passed in
def getkey(outpath, files=[]):
keys = nookkeys(files)
if len(keys) > 0:
if not os.path.isdir(outpath):
outfile = outpath
with open(outfile, 'w') as keyfileout:
keyfileout.write(keys[-1])
print("Saved a key to {0}".format(outfile))
else:
keycount = 0
for key in keys:
while True:
keycount += 1
outfile = os.path.join(outpath,"nookkey{0:d}.b64".format(keycount))
if not os.path.exists(outfile):
break
with open(outfile, 'w') as keyfileout:
keyfileout.write(key)
print("Saved a key to {0}".format(outfile))
return True
return False
def usage(progname):
print("Finds the nook Study encryption keys.")
print("Keys are saved to the current directory, or a specified output directory.")
print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
print("Usage:")
print(" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname))
def cli_main():
sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr)
argv=unicode_argv()
progname = os.path.basename(argv[0])
print("{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__))
try:
opts, args = getopt.getopt(argv[1:], "hk:")
except getopt.GetoptError as err:
print("Error in options or arguments: {0}".format(err.args[0]))
usage(progname)
sys.exit(2)
files = []
for o, a in opts:
if o == "-h":
usage(progname)
sys.exit(0)
if o == "-k":
files = [a]
if len(args) > 1:
usage(progname)
sys.exit(2)
if len(args) == 1:
# save to the specified file or directory
outpath = args[0]
if not os.path.isabs(outpath):
outpath = os.path.abspath(outpath)
else:
# save to the same directory as the script
outpath = os.path.dirname(argv[0])
# make sure the outpath is the
outpath = os.path.realpath(os.path.normpath(outpath))
if not getkey(outpath, files):
print("Could not retrieve nook Study key.")
return 0
def gui_main():
try:
import tkinter
import tkinter.constants
import tkinter.messagebox
import traceback
except:
return cli_main()
class ExceptionDialog(tkinter.Frame):
def __init__(self, root, text):
tkinter.Frame.__init__(self, root, border=5)
label = tkinter.Label(self, text="Unexpected error:",
anchor=tkinter.constants.W, justify=tkinter.constants.LEFT)
label.pack(fill=tkinter.constants.X, expand=0)
self.text = tkinter.Text(self)
self.text.pack(fill=tkinter.constants.BOTH, expand=1)
self.text.insert(tkinter.constants.END, text)
argv=unicode_argv()
root = tkinter.Tk()
root.withdraw()
progpath, progname = os.path.split(argv[0])
success = False
try:
keys = nookkeys()
keycount = 0
for key in keys:
print(key)
while True:
keycount += 1
outfile = os.path.join(progpath,"nookkey{0:d}.b64".format(keycount))
if not os.path.exists(outfile):
break
with open(outfile, 'w') as keyfileout:
keyfileout.write(key)
success = True
tkinter.messagebox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
except DrmException as e:
tkinter.messagebox.showerror(progname, "Error: {0}".format(str(e)))
except Exception:
root.wm_state('normal')
root.title(progname)
text = traceback.format_exc()
ExceptionDialog(root, text).pack(fill=tkinter.constants.BOTH, expand=1)
root.mainloop()
if not success:
return 1
return 0
if __name__ == '__main__':
if len(sys.argv) > 1:
sys.exit(cli_main())
sys.exit(gui_main())