From 590dd833c35431bdb70dcd7bb219caca940d8ba9 Mon Sep 17 00:00:00 2001 From: 8go <8go@localhost.localdomain> Date: Fri, 26 May 2017 22:31:58 +0200 Subject: [PATCH] port to Py3.4 --- README.md | 18 +++++++--- TrezorHash.py | 10 +++--- basics.py | 2 +- dialogs.py | 6 ++-- encoding.py | 38 ++++++++++++++------ enter_pin_dialog.ui | 3 ++ trezor_app_generic.py | 69 +++++++++++++++++++++++-------------- trezor_app_specific.py | 22 ++++++++++++ trezor_gui.py | 2 +- trezor_passphrase_dialog.ui | 3 ++ utils.py | 23 +++++++++---- 11 files changed, 140 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 3ccb0e8..e94af51 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Below a sample screenshot. More screenshots [here](screenshots). # Build and runtime requirements * [Trezor](https://www.trezor.io) device - * [Python](https://www.python.org/) v2.7 + * [Python](https://www.python.org/) v2.7 or 3.4+ * PyCrypto * PyQt4 * [trezorlib from python-trezor](https://github.com/trezor/python-trezor) @@ -70,6 +70,8 @@ like `pyqt4-dev-tools` or `PyQt4-devel`). Run: python TrezorHash.py +or + python3 TrezorHash.py Run-time command line options are @@ -142,8 +144,8 @@ Apply, Hash: Control-A, Control-S Cancel, Quit: Esc, Control-Q Version, About: Control-T -Requires: python 2.7 and PyQt4 and trezorlib library. -Tested on Linux on Python 2.7. +Requires: python 2.7 or 3.4+ and PyQt4 and trezorlib library. +Tested on Linux on Python 2.7 and 3.4. BTW, for testing 'xsel -bi', 'xsel -bo' and 'xsel -bc' set, write and clear the clipboard on Linux. ``` @@ -184,7 +186,7 @@ single-file-executablefile. - - - **Question:** In which language is TrezorHash written? -**Answer:** [Python](https://www.python.org/) 2.7. It will currently not run on Python 3. +**Answer:** [Python](https://www.python.org/). It will currently run on Python 2.7 and 3.4+. - - - **Question:** Do I need to have a [Trezor](https://www.trezor.io/) in order to use TrezorHash? @@ -274,4 +276,12 @@ Testing has only been done on Linux. **Answer:** Let us know. - - - +# To-do List + +[ ] There is a bug in Qt4 not allowing foreign characters to be entered +via the Alt-Gr keys from +the keyboard in the password field (used to read Trezor passphrase). +A work around could be written. Immediate work around is copy-paste. + + on :octocat: with :heart: diff --git a/TrezorHash.py b/TrezorHash.py index ed458b3..04d9387 100755 --- a/TrezorHash.py +++ b/TrezorHash.py @@ -53,13 +53,13 @@ def useTerminal(teh, settings): for ii in range(settings.inputArgs.count('')): settings.inputArgs.remove('') # get rid of all empty strings if len(settings.inputArgs) == 0: - settings.input = raw_input("Please provide an input string to be hashed: " + settings.input = raw_input(u"Please provide an input string to be hashed: " "(Carriage return to quit) ") # convert all input as possible to unicode UTF-8 settings.input = settings.input.decode('utf-8') if settings.input == "": - settings.mlogger.log("User decided to abandon.", logging.DEBUG, - "Trezor IO") + settings.mlogger.log(u"User decided to abandon.", logging.DEBUG, + u"Trezor IO") sys.exit(3) settings.inputArgs.append(settings.input) for item in settings.inputArgs: @@ -82,8 +82,8 @@ def main(): trezor.prefillPassphrase(u'') if settings.TArg: - settings.mlogger.log("Terminal mode --terminal was set. Avoiding GUI.", - logging.INFO, "Arguments") + settings.mlogger.log(u"Terminal mode --terminal was set. Avoiding GUI.", + logging.INFO, u"Arguments") dialog = None else: dialog = Dialog(trezor, settings) diff --git a/basics.py b/basics.py index 486573a..6c0139d 100644 --- a/basics.py +++ b/basics.py @@ -5,7 +5,7 @@ import logging # Name of software version -THVERSION = u'v0.2.0-alpha' +THVERSION = u'v0.3.0-alpha' # Date of software version, only used in GUI THVERSIONTEXT = u'May 2017' diff --git a/dialogs.py b/dialogs.py index 85782ca..46416f0 100644 --- a/dialogs.py +++ b/dialogs.py @@ -3,6 +3,7 @@ from __future__ import print_function import logging +import sys from PyQt4 import QtGui from PyQt4.QtGui import QPixmap @@ -77,8 +78,9 @@ def printAbout(self): "computes the encrypted hash (digest) of an input string " "(message) using a Trezor hardware " "device for safety and security.

" + - "Version: " + basics.THVERSION + - " from " + basics.THVERSIONTEXT) + "TrezorHash Version: " + basics.THVERSION + + " from " + basics.THVERSIONTEXT + + "

Python Version: " + sys.version.replace(" \n", "; ")) msgBox.setIconPixmap(QPixmap("icons/TrezorHash.92x128.png")) msgBox.exec_() diff --git a/encoding.py b/encoding.py index f477249..956cc24 100644 --- a/encoding.py +++ b/encoding.py @@ -11,21 +11,27 @@ def q2s(q): """Convert QString to UTF-8 string object""" - return str(q.toUtf8()).decode('utf-8') + if sys.version_info[0] > 2: + return q + else: + return str(q.toUtf8()).decode('utf-8') def s2q(s): """Convert UTF-8 encoded string to QString""" - return QtCore.QString.fromUtf8(s) + if sys.version_info[0] > 2: + return s + else: + return QtCore.QString.fromUtf8(s) -def u(fmt, s): +def unpack(fmt, s): # u = lambda fmt, s: struct.unpack(fmt, s)[0] return(struct.unpack(fmt, s)[0]) -def p(fmt, s): - # u = lambda fmt, s: struct.unpack(fmt, s)[0] +def pack(fmt, s): + # p = lambda fmt, s: struct.pack(fmt, s)[0] return(struct.pack(fmt, s)) @@ -49,11 +55,11 @@ class Magic(object): """ headerStr = b'TRZR' - hdr = u("!I", headerStr) + hdr = unpack("!I", headerStr) # to encrypt hash - hashNode = [hdr, u("!I", b'HASH')] - hashKey = "Allow HASH encryption?" # string to encrypt hash + hashNode = [hdr, unpack("!I", b'HASH')] + hashKey = b"Allow HASH encryption?" # string to encrypt hash class Padding(object): @@ -65,8 +71,20 @@ def __init__(self, blocksize): self.blocksize = blocksize def pad(self, s): + """ + In Python 2 input s is a string, a char list. + Python 2 returns a string. + In Python 3 input s is bytes. + Python 3 returns bytes. + """ BS = self.blocksize - return s + (BS - len(s) % BS) * chr(BS - len(s) % BS) + if sys.version_info[0] > 2: + return s + (BS - len(s) % BS) * bytes([BS - len(s) % BS]) + else: + return s + (BS - len(s) % BS) * chr(BS - len(s) % BS) def unpad(self, s): - return s[0:-ord(s[-1])] + if sys.version_info[0] > 2: + return s[0:-s[-1]] + else: + return s[0:-ord(s[-1])] diff --git a/enter_pin_dialog.ui b/enter_pin_dialog.ui index ab95da5..b53a684 100644 --- a/enter_pin_dialog.ui +++ b/enter_pin_dialog.ui @@ -87,6 +87,9 @@ + + Qt::ImhDigitsOnly|Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhPreferNumbers + QLineEdit::Password diff --git a/trezor_app_generic.py b/trezor_app_generic.py index 6620205..0079ce3 100644 --- a/trezor_app_generic.py +++ b/trezor_app_generic.py @@ -14,6 +14,17 @@ from trezor_gui import TrezorPassphraseDialog, EnterPinDialog, TrezorChooserDialog +""" +This code is written specifically such that both terminal-only as well as +GUI mode are supported for all 3 operations: Trezor choser, PIN entry, +Passphrase entry. +Each of the windows can be turned on or off individually with the 3 flags: +readpinfromstdin, readpassphrasefromstdin, and readdevicestringfromstdin. + +Code should work on both Python 2.7 as well as 3.4. +Requires PyQt4. +""" + class QtTrezorMixin(object): """ @@ -37,37 +48,36 @@ def callback_PassphraseRequest(self, msg): if self.readpassphrasefromstdin: # read passphrase from stdin try: - passphrase = getpass.getpass("Please enter passphrase: ") + passphrase = getpass.getpass(u"Please enter passphrase: ") passphrase = str(passphrase) except KeyboardInterrupt: - sys.stderr.write("\nKeyboard interrupt: passphrase not read. Aborting.\n") + sys.stderr.write(u"\nKeyboard interrupt: passphrase not read. Aborting.\n") sys.exit(3) except Exception as e: - sys.stderr.write("Critical error: Passphrase not read. Aborting. (%s)" % e) + sys.stderr.write(u"Critical error: Passphrase not read. Aborting. (%s)" % e) sys.exit(3) else: dialog = TrezorPassphraseDialog() if not dialog.exec_(): sys.exit(3) else: - passphrase = dialog.passphraseEdit.text() - passphrase = str(passphrase) + passphrase = dialog.passphrase() return proto.PassphraseAck(passphrase=passphrase) def callback_PinMatrixRequest(self, msg): if self.readpinfromstdin: # read PIN from stdin - print(" 7 8 9") - print(" 4 5 6") - print(" 1 2 3") + print(u" 7 8 9") + print(u" 4 5 6") + print(u" 1 2 3") try: - pin = getpass.getpass("Please enter PIN: ") + pin = getpass.getpass(u"Please enter PIN: ") except KeyboardInterrupt: - sys.stderr.write("\nKeyboard interrupt: PIN not read. Aborting.\n") + sys.stderr.write(u"\nKeyboard interrupt: PIN not read. Aborting.\n") sys.exit(7) except Exception as e: - sys.stderr.write("Critical error: PIN not read. Aborting. (%s)" % e) + sys.stderr.write(u"Critical error: PIN not read. Aborting. (%s)" % e) sys.exit(7) else: dialog = EnterPinDialog() @@ -82,7 +92,10 @@ def prefillPassphrase(self, passphrase): Instead of asking for passphrase, use this one """ if passphrase is not None: - self.passphrase = passphrase.decode("utf-8") + if sys.version_info[0] > 2: + self.passphrase = passphrase + else: + self.passphrase = passphrase.decode("utf-8") else: self.passphrase = None @@ -146,13 +159,13 @@ def chooseDevice(self, devices): @returns HidTransport object of selected device """ if not len(devices): - raise RuntimeError("No Trezor connected!") + raise RuntimeError(u"No Trezor connected!") if len(devices) == 1: try: return HidTransport(devices[0]) except IOError: - raise RuntimeError("Trezor is currently in use") + raise RuntimeError(u"Trezor is currently in use") # maps deviceId string to device label deviceMap = {} @@ -169,33 +182,37 @@ def chooseDevice(self, devices): continue if not deviceMap: - raise RuntimeError("All connected Trezors are in use!") + raise RuntimeError(u"All connected Trezors are in use!") if self.readdevicestringfromstdin: - print('Chose your Trezor device please. Devices currently in use are not listed:') + print(u'Chose your Trezor device please. ' + 'Devices currently in use are not listed:') ii = 0 for device in deviceMap: print('%d %s' % (ii, deviceMap[device])) ii += 1 ii -= 1 while True: - inputstr = raw_input("Please provide the number of the device chosen: (%d-%d, Carriage return to quit) " % (0, ii)) + inputstr = raw_input(u"Please provide the number of the device " + "chosen: (%d-%d, Carriage return to quit) " % (0, ii)) if inputstr == '': - raise RuntimeError("No Trezors device chosen! Quitting.") + raise RuntimeError(u"No Trezors device chosen! Quitting.") try: inputint = int(inputstr) except Exception: - print('Wrong input. You must enter a number between %d and %d. Try again.' % (0, ii)) + print(u'Wrong input. You must enter a number ' + 'between %d and %d. Try again.' % (0, ii)) continue if inputint < 0 or inputint > ii: - print('Wrong input. You must enter a number between %d and %d. Try again.' % (0, ii)) + print(u'Wrong input. You must enter a number ' + 'between %d and %d. Try again.' % (0, ii)) continue break deviceStr = deviceMap.keys()[ii] else: dialog = TrezorChooserDialog(deviceMap) if not dialog.exec_(): - raise RuntimeError("No Trezors device chosen! Quitting.") + raise RuntimeError(u"No Trezors device chosen! Quitting.") deviceStr = dialog.chosenDeviceStr() return HidTransport([deviceStr, None]) @@ -208,18 +225,18 @@ def setupTrezor(readdevicestringfromstdin=False, mlogger=None): """ try: if mlogger is not None: - mlogger.log("Starting Trezor initialization", logging.DEBUG, "Trezor Info") + mlogger.log(u"Starting Trezor initialization", logging.DEBUG, u"Trezor Info") trezorChooser = TrezorChooser(readdevicestringfromstdin) trezor = trezorChooser.getDevice() except (ConnectionError, RuntimeError) as e: if mlogger is not None: - mlogger.log("Connection to Trezor failed: %s" % e.message, - logging.CRITICAL, "Trezor Error") + mlogger.log(u"Connection to Trezor failed: %s" % e.message, + logging.CRITICAL, u"Trezor Error") sys.exit(1) if trezor is None: if mlogger is not None: - mlogger.log("No available Trezor found, quitting.", - logging.CRITICAL, "Trezor Error") + mlogger.log(u"No available Trezor found, quitting.", + logging.CRITICAL, u"Trezor Error") sys.exit(1) return trezor diff --git a/trezor_app_specific.py b/trezor_app_specific.py index 37e3f13..b6e9a42 100644 --- a/trezor_app_specific.py +++ b/trezor_app_specific.py @@ -12,6 +12,28 @@ class TrezorEncryptedHash(object): """ class that does the Trezor hash encryption + + Methods setAddress() and setIV() are usually called only at initialization. + They create an address and an AES-CBS initialization vector. + Address and IV are stored internally in the class to make the + trezorEncryptHash() call simpler and with fewer parameters. + + In summary the encrypted hash is computed as follows: + The 999th public receiving BTC address of account 0 of the Trezor wallet without + passphrase is computed. From this address the MD5 hash is derived. This is a + 16-byte digest which will later be used as initial vector IV in the AES + encryption. + The input is a message in unicode UTF-8. + A SHA256 hash is computed from this message. + The resulting 32-byte digest is encrypted on the Trezor with AES CBC using + the IV mentioned before. This encryption uses some static magic numbers + as keys. The encryption is also influenced by the `confirm-on-Trezor-button` + flags. This is why the output for running the program is different + depending whether a `confirm-button` press is required or not. + This encrypted byte array is also 32-bytes. It will be again hashed with + SHA256 resulting in the final 32-byte, i.e. 256-bit, digest which is the + final output. This outbut is converted into a hex string of 64 letters + in the alphabet [0-9a-f]. This 64-letter string is returned to the caller. """ def __init__(self, trezor, settings): diff --git a/trezor_gui.py b/trezor_gui.py index 266161a..f22c8fa 100644 --- a/trezor_gui.py +++ b/trezor_gui.py @@ -18,7 +18,7 @@ def __init__(self): self.setupUi(self) def passphrase(self): - return self.passphraseEdit.text() + return q2s(self.passphraseEdit.text()) class EnterPinDialog(QtGui.QDialog, Ui_EnterPinDialog): diff --git a/trezor_passphrase_dialog.ui b/trezor_passphrase_dialog.ui index e0cd183..18dc56c 100644 --- a/trezor_passphrase_dialog.ui +++ b/trezor_passphrase_dialog.ui @@ -55,6 +55,9 @@ + + Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText + QLineEdit::Password diff --git a/utils.py b/utils.py index e2db780..3dbf685 100644 --- a/utils.py +++ b/utils.py @@ -375,8 +375,8 @@ def printUsage(self): Cancel, Quit: Esc, Control-Q Version, About: Control-T - Requires: python 2.7 and PyQt4 and trezorlib library. - Tested on Linux on Python 2.7. + Requires: python 2.7 or 3.4+ and PyQt4 and trezorlib library. + Tested on Linux on Python 2.7 and 3.4. BTW, for testing 'xsel -bi', 'xsel -bo' and 'xsel -bc' set, write and clear the clipboard on Linux. @@ -443,12 +443,13 @@ def parseArgs(self, argv, settings=None, logger=None): sys.exit(19) settings.LArg = loglevel * 10 # https://docs.python.org/2/library/logging.html#levels logger.setLevel(settings.LArg) - logger.info(u'Logging level set to %s (%d).', - logging.getLevelName(settings.LArg), settings.LArg) - for arg in args: - # convert all input as possible to unicode UTF-8 - settings.inputArgs.append(arg.decode('utf-8')) + if sys.version_info[0] > 2: + settings.inputArgs = args + else: + for arg in args: + # convert all input as possible to unicode UTF-8 + settings.inputArgs.append(arg.decode('utf-8')) if settings.MArg and not settings.TArg: self.settings.mlogger.log(u"Multiple inputs can only be used " "in terminal mode. Add '-t' or remove '-m'.", @@ -462,4 +463,12 @@ def parseArgs(self, argv, settings=None, logger=None): logging.CRITICAL, "Wrong arguments", True, logger) sys.exit(2) settings.mlogger.setTerminalMode(settings.TArg) + self.settings.mlogger.log(u"Version: %s (%s)" % + (basics.THVERSION, basics.THVERSIONTEXT), + logging.INFO, "Wrong arguments", True, logger) + self.settings.mlogger.log(u"Python: %s" % sys.version.replace(" \n", "; "), + logging.INFO, "Wrong arguments", True, logger) + self.settings.mlogger.log(u'Logging level set to %s (%d).' % + (logging.getLevelName(settings.LArg), settings.LArg), + logging.INFO, "Wrong arguments", True, logger) settings.printSettings()