From 60aec33342ebb196c973d7d73df78a6f0118e50e Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Sun, 18 Sep 2022 16:21:16 -0600 Subject: [PATCH 01/34] WIP otp works --- tests/test_msg_ping.py | 160 ++++++++++++++++++++++++++--------------- 1 file changed, 104 insertions(+), 56 deletions(-) diff --git a/tests/test_msg_ping.py b/tests/test_msg_ping.py index 2522105f..c3ac8fb5 100644 --- a/tests/test_msg_ping.py +++ b/tests/test_msg_ping.py @@ -21,76 +21,124 @@ import time import unittest import common +from datetime import datetime +import binascii from keepkeylib import messages_pb2 as proto from keepkeylib import types_pb2 as proto_types +### Dynamic Truncation +def dynamic_truncate(b_hash): + hash_len=len(b_hash) + int_hash = int.from_bytes(b_hash, byteorder='big') + offset = int_hash & 0xF + # Geterate a mask to get bytes from left to right of the hash + n_shift = 8*(hash_len-offset)-32 + MASK = 0xFFFFFFFF << n_shift + hex_mask = "0x"+("{:0"+str(2*hash_len)+"x}").format(MASK) + P = (int_hash & MASK)>>n_shift # Get rid of left zeros + LSB_31 = P & 0x7FFFFFFF # Return only the lower 31 bits + return LSB_31 + class TestPing(common.KeepKeyTest): + def test_auth_init(self): + self.setup_mnemonic_nopin_nopassphrase() + self.client.ping( + #msg = b'\x15' + bytes("initializeAuth:" + "CGPZQ62R37AHNYKJES3JQL5E", 'utf8') + msg = b'\x15' + bytes("initializeAuth:" + "BASE32SECRET2345AB==", 'utf8') + ) + + interval = 30 # 30 second interval + #T0 = int(time.time()) + T0 = 1535317397 + # T1 = T0 + interval + + #T = bytes(str(int(T0/interval).to_bytes(8, byteorder='big')), 'utf8') + T = int(T0/interval).to_bytes(8, byteorder='big') + # print(T) + # print(T.hex()) + # print([T[i:i+1] for i in range(len(T))]) + # print(''.join('{:02x}'.format(x) for x in T)) + # print(b'\x16' + bytes("generateOTPFrom:", 'utf8') + ''.join('{:02x}'.format(x) for x in T)) + # print(T1) + # print(T) + # print((b'\x16' + bytes("generateOTPFrom:", 'utf8') + T).hex()) + retval = self.client.ping( + #msg = b'\x16' + bytes("generateOTPFrom:", 'utf8') + bytes(''.join('{:02x}'.format(x) for x in T), 'utf8') + msg = b'\x16' + bytes("generateOTPFrom:", 'utf8') + binascii.hexlify(bytearray(T)) + ) + print(retval) + print(binascii.unhexlify(retval)) + Digits = 6 + trc_hash = dynamic_truncate(binascii.unhexlify(retval)) + print(("{:0"+str(Digits)+"}").format(trc_hash % (10**Digits))) + print("should be 280672") + - def test_ping(self): - self.setup_mnemonic_pin_passphrase() - self.client.clear_session() + # def test_ping(self): + # self.setup_mnemonic_pin_passphrase() + # self.client.clear_session() - with self.client: - self.client.set_expected_responses([proto.Success()]) - res = self.client.ping('random data') - self.assertEqual(res, 'random data') + # with self.client: + # self.client.set_expected_responses([proto.Success()]) + # res = self.client.ping('random data') + # self.assertEqual(res, 'random data') - with self.client: - self.client.set_expected_responses([proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), proto.Success()]) - res = self.client.ping('random data', button_protection=True) - self.assertEqual(res, 'random data') + # with self.client: + # self.client.set_expected_responses([proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), proto.Success()]) + # res = self.client.ping('random data', button_protection=True) + # self.assertEqual(res, 'random data') - with self.client: - self.client.set_expected_responses([proto.PinMatrixRequest(), proto.Success()]) - res = self.client.ping('random data', pin_protection=True) - self.assertEqual(res, 'random data') + # with self.client: + # self.client.set_expected_responses([proto.PinMatrixRequest(), proto.Success()]) + # res = self.client.ping('random data', pin_protection=True) + # self.assertEqual(res, 'random data') - with self.client: - self.client.set_expected_responses([ - proto.PassphraseRequest(), - proto.ButtonRequest(code=proto_types.ButtonRequest_Other), - proto.Success() - ]) - res = self.client.ping('random data', passphrase_protection=True) - self.assertEqual(res, 'random data') + # with self.client: + # self.client.set_expected_responses([ + # proto.PassphraseRequest(), + # proto.ButtonRequest(code=proto_types.ButtonRequest_Other), + # proto.Success() + # ]) + # res = self.client.ping('random data', passphrase_protection=True) + # self.assertEqual(res, 'random data') - def test_ping_format_specifier_sanitize(self): - self.setup_mnemonic_pin_passphrase() - self.client.clear_session() - with self.client: - self.client.set_expected_responses([ - proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), - proto.PinMatrixRequest(), - proto.PassphraseRequest(), - proto.ButtonRequest(code=proto_types.ButtonRequest_Other), - proto.Success() - ]) - res = self.client.ping('%s%x%n%p', button_protection=True, pin_protection=True, passphrase_protection=True) - self.assertEqual(res, '%s%x%n%p') + # def test_ping_format_specifier_sanitize(self): + # self.setup_mnemonic_pin_passphrase() + # self.client.clear_session() + # with self.client: + # self.client.set_expected_responses([ + # proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), + # proto.PinMatrixRequest(), + # proto.PassphraseRequest(), + # proto.ButtonRequest(code=proto_types.ButtonRequest_Other), + # proto.Success() + # ]) + # res = self.client.ping('%s%x%n%p', button_protection=True, pin_protection=True, passphrase_protection=True) + # self.assertEqual(res, '%s%x%n%p') - def test_ping_caching(self): - self.setup_mnemonic_pin_passphrase() - self.client.clear_session() + # def test_ping_caching(self): + # self.setup_mnemonic_pin_passphrase() + # self.client.clear_session() - with self.client: - self.client.set_expected_responses([ - proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), - proto.PinMatrixRequest(), - proto.PassphraseRequest(), - proto.ButtonRequest(code=proto_types.ButtonRequest_Other), - proto.Success() - ]) - res = self.client.ping('random data', button_protection=True, pin_protection=True, passphrase_protection=True) - self.assertEqual(res, 'random data') + # with self.client: + # self.client.set_expected_responses([ + # proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), + # proto.PinMatrixRequest(), + # proto.PassphraseRequest(), + # proto.ButtonRequest(code=proto_types.ButtonRequest_Other), + # proto.Success() + # ]) + # res = self.client.ping('random data', button_protection=True, pin_protection=True, passphrase_protection=True) + # self.assertEqual(res, 'random data') - with self.client: - # pin and passphrase are cached - self.client.set_expected_responses([ - proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), - proto.Success()]) - res = self.client.ping('random data', button_protection=True, pin_protection=True, passphrase_protection=True) - self.assertEqual(res, 'random data') + # with self.client: + # # pin and passphrase are cached + # self.client.set_expected_responses([ + # proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), + # proto.Success()]) + # res = self.client.ping('random data', button_protection=True, pin_protection=True, passphrase_protection=True) + # self.assertEqual(res, 'random data') if __name__ == '__main__': unittest.main() From 008663b8ffacb3efa73515390e477130be390786 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Mon, 3 Oct 2022 10:27:21 -0600 Subject: [PATCH 02/34] WIP authenticator messages test --- tests/test_msg_ping.py | 39 +++++---------------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/tests/test_msg_ping.py b/tests/test_msg_ping.py index c3ac8fb5..46a2ee73 100644 --- a/tests/test_msg_ping.py +++ b/tests/test_msg_ping.py @@ -27,52 +27,23 @@ from keepkeylib import messages_pb2 as proto from keepkeylib import types_pb2 as proto_types -### Dynamic Truncation -def dynamic_truncate(b_hash): - hash_len=len(b_hash) - int_hash = int.from_bytes(b_hash, byteorder='big') - offset = int_hash & 0xF - # Geterate a mask to get bytes from left to right of the hash - n_shift = 8*(hash_len-offset)-32 - MASK = 0xFFFFFFFF << n_shift - hex_mask = "0x"+("{:0"+str(2*hash_len)+"x}").format(MASK) - P = (int_hash & MASK)>>n_shift # Get rid of left zeros - LSB_31 = P & 0x7FFFFFFF # Return only the lower 31 bits - return LSB_31 - class TestPing(common.KeepKeyTest): def test_auth_init(self): self.setup_mnemonic_nopin_nopassphrase() self.client.ping( - #msg = b'\x15' + bytes("initializeAuth:" + "CGPZQ62R37AHNYKJES3JQL5E", 'utf8') - msg = b'\x15' + bytes("initializeAuth:" + "BASE32SECRET2345AB==", 'utf8') + msg = b'\x15' + bytes("initializeAuth:" + "python-test:" + "BASE32SECRET2345AB", 'utf8') ) interval = 30 # 30 second interval - #T0 = int(time.time()) - T0 = 1535317397 - # T1 = T0 + interval + #T0 = 1535317397 + T0 = 1536262427 - #T = bytes(str(int(T0/interval).to_bytes(8, byteorder='big')), 'utf8') T = int(T0/interval).to_bytes(8, byteorder='big') - # print(T) - # print(T.hex()) - # print([T[i:i+1] for i in range(len(T))]) - # print(''.join('{:02x}'.format(x) for x in T)) - # print(b'\x16' + bytes("generateOTPFrom:", 'utf8') + ''.join('{:02x}'.format(x) for x in T)) - # print(T1) - # print(T) - # print((b'\x16' + bytes("generateOTPFrom:", 'utf8') + T).hex()) retval = self.client.ping( - #msg = b'\x16' + bytes("generateOTPFrom:", 'utf8') + bytes(''.join('{:02x}'.format(x) for x in T), 'utf8') - msg = b'\x16' + bytes("generateOTPFrom:", 'utf8') + binascii.hexlify(bytearray(T)) + msg = b'\x16' + bytes("generateOTPFrom:" + "python-test:", 'utf8') + binascii.hexlify(bytearray(T)) ) print(retval) - print(binascii.unhexlify(retval)) - Digits = 6 - trc_hash = dynamic_truncate(binascii.unhexlify(retval)) - print(("{:0"+str(Digits)+"}").format(trc_hash % (10**Digits))) - print("should be 280672") + print("should be 007767") # def test_ping(self): From b5f14d7212d92eb5f5b158058bd8f4f5957a81a5 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Fri, 4 Nov 2022 11:01:38 -0600 Subject: [PATCH 03/34] WIP standalone auth app dev --- KeepKeyAuthenticator.py | 162 +++++++++++++++++++++++++++++ authenticatorUI/main.ui | 220 ++++++++++++++++++++++++++++++++++++++++ keepkeyctl | 2 +- setupauth.py | 19 ++++ tests/otp.py | 122 ++++++++++++++++++++++ tests/test_msg_ping.py | 130 ++++++++++-------------- 6 files changed, 580 insertions(+), 75 deletions(-) create mode 100644 KeepKeyAuthenticator.py create mode 100644 authenticatorUI/main.ui create mode 100644 setupauth.py create mode 100644 tests/otp.py diff --git a/KeepKeyAuthenticator.py b/KeepKeyAuthenticator.py new file mode 100644 index 00000000..d6686355 --- /dev/null +++ b/KeepKeyAuthenticator.py @@ -0,0 +1,162 @@ +# This file is part of the TREZOR project. +# +# Copyright (C) 2022 markrypto +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . +# +# The script has been modified for KeepKey Device. + +from PyQt5 import QtWidgets, uic +from PyQt5.QtWidgets import QFileDialog, QMenuBar,QAction,QMessageBox,QPushButton +from PyQt5.QtGui import * +from PyQt5.QtCore import Qt +import sys +import os +from PIL import Image +from pyzbar.pyzbar import decode +import qdarkgraystyle +import qrcode +import time +from urllib.parse import urlparse +from PIL import ImageGrab + +import binascii +import urllib +import time +from datetime import datetime + +from keepkeylib.client import KeepKeyClient +from keepkeylib.transport_webusb import WebUsbTransport + +class Ui(QtWidgets.QMainWindow): + def __init__(self): + super(Ui, self).__init__() + uic.loadUi(r'authenticatorUI/main.ui', self) + self.setFixedSize(500, 600) + self.button = self.findChild(QtWidgets.QPushButton, 'qrscreencap') + self.button.clicked.connect(self.QrScreencap) # Remember to pass the definition/method, not the return value! + self.button2 = self.findChild(QtWidgets.QPushButton, 'otpgen') + self.button2.clicked.connect(self.OtpGen) # Remember to pass the definition/method, not the return value! + self.button3 = self.findChild(QtWidgets.QPushButton, 'connect') + self.button3.clicked.connect(self.ConnectKK) # Remember to pass the definition/method, not the return value! + self.button4 = self.findChild(QtWidgets.QPushButton, 'test') + self.button4.clicked.connect(self.Test) # Remember to pass the definition/method, not the return value! + self.input = self.findChild(QtWidgets.QLineEdit, 'qredit') + self.exitAct = self.findChild(QtWidgets.QMenu,'menuMenu') + self.label1 = self.findChild(QtWidgets.QLabel,'image_1') + self.actionQuit = self.findChild(QtWidgets.QAction,'actionExit') + self.actionQuit.triggered.connect(app.quit) + self.show() + + def QrScreencap(self): + # grab fullscreen + self.im = ImageGrab.grab(include_layered_windows=True) + self.im.save("fullscreen.png") + exit() + + try: + #data = decode(Image.open(file_path)) + data = decode(self.im) + print(data) + data1 = str(data[0][0]).replace("b'",'').replace("'","") + type1 = str(data[0][1]) + self.result1 = self.findChild(QtWidgets.QLineEdit, 'raw_text_result') + print(data1) + secret = urlparse(data1).query.split('=')[1].split('&')[0] + domain = urlparse(data1).path.split('/')[1].split(':')[0] + account = urlparse(data1).path.split('/')[1].split(':')[1] + print(secret) + print(domain) + print(account) + # os.system("python cmdkk.py auth_init %s:%s:%s" % (secret, domain, account)) + # self.result1.setText(data1)#'raw_type_result' + # self.result2 = self.findChild(QtWidgets.QLineEdit, 'raw_type_result') + # self.result2.setText(type1) + except AttributeError: + pass + + def OtpGen(self): + #generate OTP + print("Generate OTP") + os.system("python cmdkk.py auth_gen") + + def Test(self): + #test the keepkey function + print("test function") + if (self.client is not None): + AuthOps.auth_test(self.client) + else: + print("KeepKey not connected") + + def ConnectKK(self): + # look for keepkey connection + # List all connected KeepKeys on USB + self.client = None + devices = WebUsbTransport.enumerate() + + # Check whether we found any + if len(devices) == 0: + print('No KeepKey found') + return + + # Use first connected device + transport = WebUsbTransport(devices[0]) + + # Creates object for manipulating KeepKey + self.client = KeepKeyClient(transport) + +class AuthOps: + def auth_test(client): + client.ping( + msg = b'\x15' + bytes("initializeAuth:" + "python-test:" + "BASE32SECRET2345AB", 'utf8') + ) + + interval = 30 # 30 second interval + #T0 = 1535317397 + T0 = 1536262427 + + T = int(T0/interval).to_bytes(8, byteorder='big') + retval = client.ping( + msg = b'\x16' + bytes("generateOTPFrom:" + "python-test:", 'utf8') + binascii.hexlify(bytearray(T)) + ) + print(retval) + print("should be 007767") + +# def main(): + + +# # List all connected KeepKeys on USB +# devices = WebUsbTransport.enumerate() + +# # Check whether we found any +# if len(devices) == 0: +# print('No KeepKey found') +# return + +# # Use first connected device +# transport = WebUsbTransport(devices[0]) + +# # Creates object for manipulating KeepKey +# client = KeepKeyClient(transport) + +# # Initialize authenticator +# AuthOps.auth_test(client) + +# if __name__ == '__main__': +# main() + +app = QtWidgets.QApplication(sys.argv) +app.setStyleSheet(qdarkgraystyle.load_stylesheet()) +window = Ui() +app.exec_() diff --git a/authenticatorUI/main.ui b/authenticatorUI/main.ui new file mode 100644 index 00000000..0f93066d --- /dev/null +++ b/authenticatorUI/main.ui @@ -0,0 +1,220 @@ + + + MainWindow + + + + 0 + 0 + 500 + 600 + + + + + Times New Roman + + + + SHAPESHIFT KEEPKEY AUTHENTICATOR + + + + ../icons/icon.png../icons/icon.png + + + + + + 20 + 46 + 450 + 20 + + + + + Times New Roman + 20 + + + + border-color: rgb(0, 0, 0); +selection-color: rgb(255, 255, 255); + + + Initialize with QR Code or generate OTP on Keepkey + + + + + + 160 + 110 + 180 + 35 + + + + + Times New Roman + 20 + + + + color: rgb(0, 0, 0); +background-color: rgb(255, 255, 255); +QPushButton::hover +{ +background-color : lightgreen; +} + + + Read QR Code + + + + + + 160 + 200 + 180 + 35 + + + + + Times New Roman + 20 + + + + color: rgb(0, 0, 0); +background-color: rgb(255, 255, 255); +QPushButton::hover +{ +background-color : lightgreen; +} + + + Generate OTP + + + + + + 160 + 290 + 180 + 35 + + + + + Times New Roman + 20 + + + + color: rgb(0, 0, 0); +background-color: rgb(255, 255, 255); +QPushButton::hover +{ +background-color : lightgreen; +} + + + Connect KeepKey + + + + + + 160 + 380 + 180 + 35 + + + + + Times New Roman + 20 + + + + color: rgb(0, 0, 0); +background-color: rgb(255, 255, 255); +QPushButton::hover +{ +background-color : lightgreen; +} + + + test + + + + + + + + 0 + 0 + 500 + 24 + + + + + Menu + + + + + + + + + + Quit + + + + + Quit Program + + + + + Quit Program + + + Ctrl+Q + + + + + Exit + + + Exit Application + + + Ctrl+Q + + + + + About + + + + + Check For Updates + + + + + + diff --git a/keepkeyctl b/keepkeyctl index c500d970..8ae78ef0 100755 --- a/keepkeyctl +++ b/keepkeyctl @@ -2,10 +2,10 @@ # Keepkey python client. # +# Copyright (C) 2022 markrypto # Copyright (C) 2012-2016 Marek Palatinus # Copyright (C) 2012-2016 Pavol Rusnak # Copyright (C) 2016 Jochen Hoenicke -# Copyright (C) 2022 markrypto # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by diff --git a/setupauth.py b/setupauth.py new file mode 100644 index 00000000..d80eba3d --- /dev/null +++ b/setupauth.py @@ -0,0 +1,19 @@ +""" +This is a setup.py script generated by py2applet + +Usage: + python setup.py py2app +""" + +from setuptools import setup + +APP = ['KeepKeyAuthenticator.py'] +DATA_FILES = [] +OPTIONS = {} + +setup( + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], +) diff --git a/tests/otp.py b/tests/otp.py new file mode 100644 index 00000000..15914c58 --- /dev/null +++ b/tests/otp.py @@ -0,0 +1,122 @@ +# This file is part of the KeepKey project. +# +# Copyright (C) markrypto +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . +# +# The script has been modified for KeepKey Device. + +import hashlib +import hmac +import base64 +import time + + +### Define SharedSecret, Block size, hashing algorithm, TOTP length and frequency +hash_algo = "sha1" +B = 64 +# Generate a TOTP every 30 seconds +F = 30 +# Shared Secret +shared_secret = b'BASE32SECRET2345AB' +# OTP Length +Digits = 6 +key=base64.b32decode(shared_secret+b'='* (8 - len(shared_secret)%8)) +# Control excessive output to console +debug = True +def dbg(data): + if (debug): + print(data) + +# Prepare time - convert integer to byte +def i2b_time(int_time): + return int_time.to_bytes(8, byteorder='big') + +### Implement the HMAC Algorithm. For details see the rfc2104.ipynb at +# https://github.com/lordloh/OPT_algorithms/blob/master/rfc2104.ipynb + +def my_hmac(key, message): + #print(key.hex()) + #print(message.hex()) + trans_5C = bytes((x ^ 0x5C) for x in range(256)) + trans_36 = bytes((x ^ 0x36) for x in range(256)) + K_zpad=key.ljust(B,b'\0') + K_ipad=K_zpad.translate(trans_36) + K_opad=K_zpad.translate(trans_5C) + hash1 = hashlib.new(hash_algo, K_ipad+message).digest() + hmac_hash = hashlib.new(hash_algo, K_opad + hash1).digest() + return hmac_hash +### Dynamic Truncation + +def dynamic_truncate(b_hash): + hash_len=len(b_hash) + int_hash = int.from_bytes(b_hash, byteorder='big') + #print("int_hash", int_hash) + offset = int_hash & 0xF + #print("offset", offset) + # Geterate a mask to get bytes from left to right of the hash + n_shift = 8*(hash_len-offset)-32 + MASK = 0xFFFFFFFF << n_shift + hex_mask = "0x"+("{:0"+str(2*hash_len)+"x}").format(MASK) + P = (int_hash & MASK)>>n_shift # Get rid of left zeros + LSB_31 = P & 0x7FFFFFFF # Return only the lower 31 bits + return LSB_31 + +# function wrapper to run the HOTP algorithm multiple times for different counter value +def generate_TOTP(time_val): + # %30 seconds + T = i2b_time(int(time_val/F)) + # Same algorithm as HOTP + hmac_hash = my_hmac(key,T) + #print("hmac_hash", hmac_hash.hex()) + trc_hash = dynamic_truncate(hmac_hash) # Get truncated hash (int) + #print("trc_hash", trc_hash) + # Adjust TOTP length + TOTP = ("{:0"+str(Digits)+"}").format(trc_hash % (10**Digits)) + #DEL dbg("\n***** ADJUST DIGITS *****\n"+str(trc_hash)+" % 10 ^ "+str(Digits)+"\nHOPT : "+HOTP) + return TOTP + +def main(): + + # Google Authenticator Compatibility (BASE-32) + # dbg("Key Base32 Decode :") + # dbg(key) + + # Generate TOTP for current time + # T0 = int(time.time()) + T0 = 1535317397 + T1 = T0 + F # F seconds later... + myTOTP0=generate_TOTP(T0) + # myTOTP1=generate_TOTP(T1) + + # Similarly, lets generate HOTPs for counter = 3..10 without a lot of output messages. + debug=False + # myTOTPs=[(generate_TOTP(x)) for x in range(T1+F,T1+F*5,F)] + # myTOTPs.insert(0,myTOTP0) + # myTOTPs.insert(1,myTOTP1) + + #print(myTOTPs) + print(myTOTP0) + print(" ") + + print(generate_TOTP(1536262427)) + + + # T = 1535317397 + # while True: + # print(T, " ", generate_TOTP(T)) + # T = T + 30 + +if __name__ == '__main__': + main() diff --git a/tests/test_msg_ping.py b/tests/test_msg_ping.py index 46a2ee73..1441ee1b 100644 --- a/tests/test_msg_ping.py +++ b/tests/test_msg_ping.py @@ -28,88 +28,70 @@ from keepkeylib import types_pb2 as proto_types class TestPing(common.KeepKeyTest): - def test_auth_init(self): - self.setup_mnemonic_nopin_nopassphrase() - self.client.ping( - msg = b'\x15' + bytes("initializeAuth:" + "python-test:" + "BASE32SECRET2345AB", 'utf8') - ) - - interval = 30 # 30 second interval - #T0 = 1535317397 - T0 = 1536262427 - - T = int(T0/interval).to_bytes(8, byteorder='big') - retval = self.client.ping( - msg = b'\x16' + bytes("generateOTPFrom:" + "python-test:", 'utf8') + binascii.hexlify(bytearray(T)) - ) - print(retval) - print("should be 007767") + def test_ping(self): + self.setup_mnemonic_pin_passphrase() + self.client.clear_session() + with self.client: + self.client.set_expected_responses([proto.Success()]) + res = self.client.ping('random data') + self.assertEqual(res, 'random data') - # def test_ping(self): - # self.setup_mnemonic_pin_passphrase() - # self.client.clear_session() + with self.client: + self.client.set_expected_responses([proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), proto.Success()]) + res = self.client.ping('random data', button_protection=True) + self.assertEqual(res, 'random data') - # with self.client: - # self.client.set_expected_responses([proto.Success()]) - # res = self.client.ping('random data') - # self.assertEqual(res, 'random data') + with self.client: + self.client.set_expected_responses([proto.PinMatrixRequest(), proto.Success()]) + res = self.client.ping('random data', pin_protection=True) + self.assertEqual(res, 'random data') - # with self.client: - # self.client.set_expected_responses([proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), proto.Success()]) - # res = self.client.ping('random data', button_protection=True) - # self.assertEqual(res, 'random data') + with self.client: + self.client.set_expected_responses([ + proto.PassphraseRequest(), + proto.ButtonRequest(code=proto_types.ButtonRequest_Other), + proto.Success() + ]) + res = self.client.ping('random data', passphrase_protection=True) + self.assertEqual(res, 'random data') - # with self.client: - # self.client.set_expected_responses([proto.PinMatrixRequest(), proto.Success()]) - # res = self.client.ping('random data', pin_protection=True) - # self.assertEqual(res, 'random data') + def test_ping_format_specifier_sanitize(self): + self.setup_mnemonic_pin_passphrase() + self.client.clear_session() + with self.client: + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), + proto.PinMatrixRequest(), + proto.PassphraseRequest(), + proto.ButtonRequest(code=proto_types.ButtonRequest_Other), + proto.Success() + ]) + res = self.client.ping('%s%x%n%p', button_protection=True, pin_protection=True, passphrase_protection=True) + self.assertEqual(res, '%s%x%n%p') - # with self.client: - # self.client.set_expected_responses([ - # proto.PassphraseRequest(), - # proto.ButtonRequest(code=proto_types.ButtonRequest_Other), - # proto.Success() - # ]) - # res = self.client.ping('random data', passphrase_protection=True) - # self.assertEqual(res, 'random data') + def test_ping_caching(self): + self.setup_mnemonic_pin_passphrase() + self.client.clear_session() - # def test_ping_format_specifier_sanitize(self): - # self.setup_mnemonic_pin_passphrase() - # self.client.clear_session() - # with self.client: - # self.client.set_expected_responses([ - # proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), - # proto.PinMatrixRequest(), - # proto.PassphraseRequest(), - # proto.ButtonRequest(code=proto_types.ButtonRequest_Other), - # proto.Success() - # ]) - # res = self.client.ping('%s%x%n%p', button_protection=True, pin_protection=True, passphrase_protection=True) - # self.assertEqual(res, '%s%x%n%p') + with self.client: + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), + proto.PinMatrixRequest(), + proto.PassphraseRequest(), + proto.ButtonRequest(code=proto_types.ButtonRequest_Other), + proto.Success() + ]) + res = self.client.ping('random data', button_protection=True, pin_protection=True, passphrase_protection=True) + self.assertEqual(res, 'random data') - # def test_ping_caching(self): - # self.setup_mnemonic_pin_passphrase() - # self.client.clear_session() - - # with self.client: - # self.client.set_expected_responses([ - # proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), - # proto.PinMatrixRequest(), - # proto.PassphraseRequest(), - # proto.ButtonRequest(code=proto_types.ButtonRequest_Other), - # proto.Success() - # ]) - # res = self.client.ping('random data', button_protection=True, pin_protection=True, passphrase_protection=True) - # self.assertEqual(res, 'random data') - - # with self.client: - # # pin and passphrase are cached - # self.client.set_expected_responses([ - # proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), - # proto.Success()]) - # res = self.client.ping('random data', button_protection=True, pin_protection=True, passphrase_protection=True) - # self.assertEqual(res, 'random data') + with self.client: + # pin and passphrase are cached + self.client.set_expected_responses([ + proto.ButtonRequest(code=proto_types.ButtonRequest_Ping), + proto.Success()]) + res = self.client.ping('random data', button_protection=True, pin_protection=True, passphrase_protection=True) + self.assertEqual(res, 'random data') if __name__ == '__main__': unittest.main() From 5647b3112ae210848ef7d8662e2ad1ab6f547073 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Fri, 11 Nov 2022 09:14:54 -0700 Subject: [PATCH 04/34] WIP mac app starts working --- authenticatorUI/main.ui => Authenticator.ui | 0 KeepKeyAuthenticator.py | 9 ++++----- README.rst | 17 +++++++++++++++++ kkQRtest.png | Bin 0 -> 52008 bytes setupauth.py | 2 +- 5 files changed, 22 insertions(+), 6 deletions(-) rename authenticatorUI/main.ui => Authenticator.ui (100%) create mode 100644 kkQRtest.png diff --git a/authenticatorUI/main.ui b/Authenticator.ui similarity index 100% rename from authenticatorUI/main.ui rename to Authenticator.ui diff --git a/KeepKeyAuthenticator.py b/KeepKeyAuthenticator.py index d6686355..3a9f802b 100644 --- a/KeepKeyAuthenticator.py +++ b/KeepKeyAuthenticator.py @@ -42,7 +42,7 @@ class Ui(QtWidgets.QMainWindow): def __init__(self): super(Ui, self).__init__() - uic.loadUi(r'authenticatorUI/main.ui', self) + uic.loadUi(r'Authenticator.ui', self) self.setFixedSize(500, 600) self.button = self.findChild(QtWidgets.QPushButton, 'qrscreencap') self.button.clicked.connect(self.QrScreencap) # Remember to pass the definition/method, not the return value! @@ -58,15 +58,14 @@ def __init__(self): self.actionQuit = self.findChild(QtWidgets.QAction,'actionExit') self.actionQuit.triggered.connect(app.quit) self.show() + self.client = None def QrScreencap(self): # grab fullscreen self.im = ImageGrab.grab(include_layered_windows=True) - self.im.save("fullscreen.png") - exit() + #self.im.save("fullscreen.png") try: - #data = decode(Image.open(file_path)) data = decode(self.im) print(data) data1 = str(data[0][0]).replace("b'",'').replace("'","") @@ -94,7 +93,7 @@ def OtpGen(self): def Test(self): #test the keepkey function print("test function") - if (self.client is not None): + if (self.client != None): AuthOps.auth_test(self.client) else: print("KeepKey not connected") diff --git a/README.rst b/README.rst index 44faebd4..16e4edac 100644 --- a/README.rst +++ b/README.rst @@ -161,3 +161,20 @@ script (modify version, etc., as needed) This will produce an executable install app ``windows/Output/kkbsetup.exe`` + +KeepKey Authenticator +============== +The KeepKey Authenticator is a standalone app that enables the KeepKey device to perform as a +hardware off-line one-time passcode (OTP) generator for two factor authentication using the +TOPT algorithm. The KeepKey PIN and physical button gives it security features similar to other +products such as the Yubikey. + +Build for MacOS +--------------- +KeepKeyAuthenticator.app is built using py2app. The setup file was created from the command line: + py2applet --make-setup KeepKeyAuthenticator.py + mv setup.py setupAuth.py +and then saved as setupAuth.py. NOTE: Creating the setup.py file in this manner will overwrite +the existing setup.py file for the python client. +Create the app: + python setupAuth.py py2app diff --git a/kkQRtest.png b/kkQRtest.png new file mode 100644 index 0000000000000000000000000000000000000000..4930a29315f0fe9dc899271a5794a8f9e6f42e49 GIT binary patch literal 52008 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 Date: Thu, 8 Dec 2022 10:22:44 -0700 Subject: [PATCH 05/34] WIP Authenticator mvp --- AuthMain.py | 243 +++++++++++++++++ AuthMain.ui | 470 ++++++++++++++++++++++++++++++++ Authenticator.ui | 220 --------------- KeepKeyAuthenticator.py | 471 ++++++++++++++++++++++++++------- PinUi.py | 163 ++++++++++++ PinUi.ui | 354 +++++++++++++++++++++++++ README.rst | 2 +- RemAccDialog.ui | 234 ++++++++++++++++ StatErr.py | 43 +++ StatErr.ui | 68 +++++ authResources/circle-32.png | Bin 0 -> 512 bytes authResources/kk-icon-gold.png | Bin 0 -> 4644 bytes circle-32.png | Bin 0 -> 512 bytes keepkeylib/GUImixin.py | 48 ++++ kk-icon-gold.png | Bin 0 -> 4644 bytes kkQRtest1.png | Bin 0 -> 51594 bytes kkQRtest2.png | Bin 0 -> 51775 bytes remaccdialog.py | 135 ++++++++++ setupauth.py | 2 +- 19 files changed, 2137 insertions(+), 316 deletions(-) create mode 100644 AuthMain.py create mode 100644 AuthMain.ui delete mode 100644 Authenticator.ui create mode 100644 PinUi.py create mode 100644 PinUi.ui create mode 100644 RemAccDialog.ui create mode 100644 StatErr.py create mode 100644 StatErr.ui create mode 100644 authResources/circle-32.png create mode 100644 authResources/kk-icon-gold.png create mode 100644 circle-32.png create mode 100644 keepkeylib/GUImixin.py create mode 100644 kk-icon-gold.png create mode 100644 kkQRtest1.png create mode 100644 kkQRtest2.png create mode 100644 remaccdialog.py diff --git a/AuthMain.py b/AuthMain.py new file mode 100644 index 00000000..7f02eb5b --- /dev/null +++ b/AuthMain.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'AuthMain.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(772, 628) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap("kk-icon-gold.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + MainWindow.setWindowIcon(icon) + MainWindow.setStyleSheet("background-color: rgb(1, 0, 0);") + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.kklogo = QtWidgets.QLabel(self.centralwidget) + self.kklogo.setGeometry(QtCore.QRect(30, 30, 200, 160)) + self.kklogo.setText("") + self.kklogo.setPixmap(QtGui.QPixmap("kk-icon-gold.png")) + self.kklogo.setObjectName("kklogo") + self.TitleDesc = QtWidgets.QLabel(self.centralwidget) + self.TitleDesc.setGeometry(QtCore.QRect(290, 30, 460, 80)) + font = QtGui.QFont() + font.setPointSize(32) + self.TitleDesc.setFont(font) + self.TitleDesc.setStyleSheet("color: rgb(255, 255, 255);") + self.TitleDesc.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.TitleDesc.setWordWrap(True) + self.TitleDesc.setObjectName("TitleDesc") + self.Author = QtWidgets.QLabel(self.centralwidget) + self.Author.setGeometry(QtCore.QRect(290, 120, 150, 50)) + font = QtGui.QFont() + font.setPointSize(24) + self.Author.setFont(font) + self.Author.setStyleSheet("color: rgb(55, 62, 255);") + self.Author.setObjectName("Author") + self.line = QtWidgets.QFrame(self.centralwidget) + self.line.setGeometry(QtCore.QRect(30, 200, 720, 16)) + self.line.setStyleSheet("color: rgb(255, 255, 255)") + self.line.setFrameShadow(QtWidgets.QFrame.Plain) + self.line.setLineWidth(1) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setObjectName("line") + self.ConnectKKButton = QtWidgets.QPushButton(self.centralwidget) + self.ConnectKKButton.setGeometry(QtCore.QRect(610, 120, 140, 70)) + font = QtGui.QFont() + font.setPointSize(20) + self.ConnectKKButton.setFont(font) + self.ConnectKKButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(255, 128, 4);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(255, 255, 255)") + self.ConnectKKButton.setAutoDefault(True) + self.ConnectKKButton.setObjectName("ConnectKKButton") + self.AddAccButton = QtWidgets.QPushButton(self.centralwidget) + self.AddAccButton.setGeometry(QtCore.QRect(30, 220, 200, 50)) + font = QtGui.QFont() + font.setPointSize(24) + self.AddAccButton.setFont(font) + self.AddAccButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(255, 255, 255)") + self.AddAccButton.setAutoDefault(True) + self.AddAccButton.setObjectName("AddAccButton") + self.testButton = QtWidgets.QPushButton(self.centralwidget) + self.testButton.setGeometry(QtCore.QRect(30, 480, 200, 50)) + font = QtGui.QFont() + font.setPointSize(24) + self.testButton.setFont(font) + self.testButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(255, 255, 255);\n" +"QPushButton::pressed { \n" +" background-color: rgb(35, 40, 49);\n" +" border: 2px solid rgb(43, 50, 61);\n" +" }") + self.testButton.setAutoDefault(True) + self.testButton.setObjectName("testButton") + self.AccountList = QtWidgets.QLabel(self.centralwidget) + self.AccountList.setGeometry(QtCore.QRect(380, 220, 130, 45)) + font = QtGui.QFont() + font.setPointSize(32) + self.AccountList.setFont(font) + self.AccountList.setStyleSheet("color: rgb(255, 255, 255);\n" +"") + self.AccountList.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.AccountList.setObjectName("AccountList") + self.RemoveAccButton = QtWidgets.QPushButton(self.centralwidget) + self.RemoveAccButton.setGeometry(QtCore.QRect(30, 300, 200, 50)) + font = QtGui.QFont() + font.setPointSize(24) + self.RemoveAccButton.setFont(font) + self.RemoveAccButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(255, 255, 255)") + self.RemoveAccButton.setAutoDefault(True) + self.RemoveAccButton.setObjectName("RemoveAccButton") + self.OTPAcc_1 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_1.setGeometry(QtCore.QRect(380, 270, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_1.setFont(font) + self.OTPAcc_1.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_1.setText("") + self.OTPAcc_1.setAutoDefault(True) + self.OTPAcc_1.setObjectName("OTPAcc_1") + self.OTPButtonGroup = QtWidgets.QButtonGroup(MainWindow) + self.OTPButtonGroup.setObjectName("OTPButtonGroup") + self.OTPButtonGroup.addButton(self.OTPAcc_1) + self.AccountList_2 = QtWidgets.QLabel(self.centralwidget) + self.AccountList_2.setGeometry(QtCore.QRect(525, 220, 220, 45)) + font = QtGui.QFont() + font.setPointSize(18) + self.AccountList_2.setFont(font) + self.AccountList_2.setStyleSheet("color: rgb(255, 255, 255);\n" +"") + self.AccountList_2.setTextFormat(QtCore.Qt.RichText) + self.AccountList_2.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop) + self.AccountList_2.setWordWrap(True) + self.AccountList_2.setObjectName("AccountList_2") + self.OTPAcc_2 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_2.setGeometry(QtCore.QRect(380, 320, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_2.setFont(font) + self.OTPAcc_2.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_2.setText("") + self.OTPAcc_2.setAutoDefault(True) + self.OTPAcc_2.setObjectName("OTPAcc_2") + self.OTPButtonGroup.addButton(self.OTPAcc_2) + self.OTPAcc_3 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_3.setGeometry(QtCore.QRect(380, 370, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_3.setFont(font) + self.OTPAcc_3.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_3.setText("") + self.OTPAcc_3.setAutoDefault(True) + self.OTPAcc_3.setObjectName("OTPAcc_3") + self.OTPButtonGroup.addButton(self.OTPAcc_3) + self.OTPAcc_4 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_4.setGeometry(QtCore.QRect(380, 420, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_4.setFont(font) + self.OTPAcc_4.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_4.setText("") + self.OTPAcc_4.setAutoDefault(True) + self.OTPAcc_4.setObjectName("OTPAcc_4") + self.OTPButtonGroup.addButton(self.OTPAcc_4) + self.OTPAcc_5 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_5.setGeometry(QtCore.QRect(380, 470, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_5.setFont(font) + self.OTPAcc_5.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_5.setText("") + self.OTPAcc_5.setAutoDefault(True) + self.OTPAcc_5.setObjectName("OTPAcc_5") + self.OTPButtonGroup.addButton(self.OTPAcc_5) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 772, 24)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "KeepKey Authenticator")) + self.TitleDesc.setText(_translate("MainWindow", "Authenticator for the KeepKey Python client")) + self.Author.setText(_translate("MainWindow", "by markrypto")) + self.ConnectKKButton.setText(_translate("MainWindow", "Connect\n" +"KeepKey")) + self.AddAccButton.setText(_translate("MainWindow", "Add Account")) + self.testButton.setText(_translate("MainWindow", "test")) + self.AccountList.setText(_translate("MainWindow", "Accounts")) + self.RemoveAccButton.setText(_translate("MainWindow", "Remove Account")) + self.AccountList_2.setText(_translate("MainWindow", "Click on account to generate OTP on KeepKey")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + MainWindow = QtWidgets.QMainWindow() + ui = Ui_MainWindow() + ui.setupUi(MainWindow) + MainWindow.show() + sys.exit(app.exec_()) diff --git a/AuthMain.ui b/AuthMain.ui new file mode 100644 index 00000000..70f306ce --- /dev/null +++ b/AuthMain.ui @@ -0,0 +1,470 @@ + + + MainWindow + + + + 0 + 0 + 772 + 628 + + + + KeepKey Authenticator + + + + kk-icon-gold.pngkk-icon-gold.png + + + background-color: rgb(1, 0, 0); + + + + + + 30 + 30 + 200 + 160 + + + + + + + kk-icon-gold.png + + + + + + 290 + 30 + 460 + 80 + + + + + 32 + + + + color: rgb(255, 255, 255); + + + Authenticator for the KeepKey Python client + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + 290 + 120 + 150 + 50 + + + + + 24 + + + + color: rgb(55, 62, 255); + + + by markrypto + + + + + + 30 + 200 + 720 + 16 + + + + color: rgb(255, 255, 255) + + + QFrame::Plain + + + 1 + + + Qt::Horizontal + + + + + + 610 + 120 + 140 + 70 + + + + + 20 + + + + border-radius: 10px; +background-color: rgb(255, 128, 4); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(255, 255, 255) + + + Connect +KeepKey + + + true + + + + + + 30 + 220 + 200 + 50 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(255, 255, 255) + + + Add Account + + + true + + + + + + 30 + 480 + 200 + 50 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(255, 255, 255); +QPushButton::pressed { + background-color: rgb(35, 40, 49); + border: 2px solid rgb(43, 50, 61); + } + + + test + + + true + + + + + + 380 + 220 + 130 + 45 + + + + + 32 + + + + color: rgb(255, 255, 255); + + + + Accounts + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + 30 + 300 + 200 + 50 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(255, 255, 255) + + + Remove Account + + + true + + + + + + 380 + 270 + 351 + 41 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + true + + + OTPButtonGroup + + + + + + 525 + 220 + 220 + 45 + + + + + 18 + + + + color: rgb(255, 255, 255); + + + + Click on account to generate OTP on KeepKey + + + Qt::RichText + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + + 380 + 320 + 351 + 41 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + true + + + OTPButtonGroup + + + + + + 380 + 370 + 351 + 41 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + true + + + OTPButtonGroup + + + + + + 380 + 420 + 351 + 41 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + true + + + OTPButtonGroup + + + + + + 380 + 470 + 351 + 41 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + true + + + OTPButtonGroup + + + + + + + 0 + 0 + 772 + 24 + + + + + + + + + + + diff --git a/Authenticator.ui b/Authenticator.ui deleted file mode 100644 index 0f93066d..00000000 --- a/Authenticator.ui +++ /dev/null @@ -1,220 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 500 - 600 - - - - - Times New Roman - - - - SHAPESHIFT KEEPKEY AUTHENTICATOR - - - - ../icons/icon.png../icons/icon.png - - - - - - 20 - 46 - 450 - 20 - - - - - Times New Roman - 20 - - - - border-color: rgb(0, 0, 0); -selection-color: rgb(255, 255, 255); - - - Initialize with QR Code or generate OTP on Keepkey - - - - - - 160 - 110 - 180 - 35 - - - - - Times New Roman - 20 - - - - color: rgb(0, 0, 0); -background-color: rgb(255, 255, 255); -QPushButton::hover -{ -background-color : lightgreen; -} - - - Read QR Code - - - - - - 160 - 200 - 180 - 35 - - - - - Times New Roman - 20 - - - - color: rgb(0, 0, 0); -background-color: rgb(255, 255, 255); -QPushButton::hover -{ -background-color : lightgreen; -} - - - Generate OTP - - - - - - 160 - 290 - 180 - 35 - - - - - Times New Roman - 20 - - - - color: rgb(0, 0, 0); -background-color: rgb(255, 255, 255); -QPushButton::hover -{ -background-color : lightgreen; -} - - - Connect KeepKey - - - - - - 160 - 380 - 180 - 35 - - - - - Times New Roman - 20 - - - - color: rgb(0, 0, 0); -background-color: rgb(255, 255, 255); -QPushButton::hover -{ -background-color : lightgreen; -} - - - test - - - - - - - - 0 - 0 - 500 - 24 - - - - - Menu - - - - - - - - - - Quit - - - - - Quit Program - - - - - Quit Program - - - Ctrl+Q - - - - - Exit - - - Exit Application - - - Ctrl+Q - - - - - About - - - - - Check For Updates - - - - - - diff --git a/KeepKeyAuthenticator.py b/KeepKeyAuthenticator.py index 3a9f802b..b81441b3 100644 --- a/KeepKeyAuthenticator.py +++ b/KeepKeyAuthenticator.py @@ -20,7 +20,7 @@ from PyQt5 import QtWidgets, uic from PyQt5.QtWidgets import QFileDialog, QMenuBar,QAction,QMessageBox,QPushButton from PyQt5.QtGui import * -from PyQt5.QtCore import Qt +from PyQt5 import QtCore import sys import os from PIL import Image @@ -35,70 +35,25 @@ import urllib import time from datetime import datetime +import sys +import semver -from keepkeylib.client import KeepKeyClient from keepkeylib.transport_webusb import WebUsbTransport +from keepkeylib.client import PinException, ProtocolMixin, BaseClient, CallException + +from AuthMain import Ui_MainWindow as Ui +from PinUi import Ui_Dialog as PIN_Dialog +from remaccdialog import Ui_RemAccDialog as RemAcc_Dialog -class Ui(QtWidgets.QMainWindow): +class kkClient: def __init__(self): - super(Ui, self).__init__() - uic.loadUi(r'Authenticator.ui', self) - self.setFixedSize(500, 600) - self.button = self.findChild(QtWidgets.QPushButton, 'qrscreencap') - self.button.clicked.connect(self.QrScreencap) # Remember to pass the definition/method, not the return value! - self.button2 = self.findChild(QtWidgets.QPushButton, 'otpgen') - self.button2.clicked.connect(self.OtpGen) # Remember to pass the definition/method, not the return value! - self.button3 = self.findChild(QtWidgets.QPushButton, 'connect') - self.button3.clicked.connect(self.ConnectKK) # Remember to pass the definition/method, not the return value! - self.button4 = self.findChild(QtWidgets.QPushButton, 'test') - self.button4.clicked.connect(self.Test) # Remember to pass the definition/method, not the return value! - self.input = self.findChild(QtWidgets.QLineEdit, 'qredit') - self.exitAct = self.findChild(QtWidgets.QMenu,'menuMenu') - self.label1 = self.findChild(QtWidgets.QLabel,'image_1') - self.actionQuit = self.findChild(QtWidgets.QAction,'actionExit') - self.actionQuit.triggered.connect(app.quit) - self.show() self.client = None - - def QrScreencap(self): - # grab fullscreen - self.im = ImageGrab.grab(include_layered_windows=True) - #self.im.save("fullscreen.png") - - try: - data = decode(self.im) - print(data) - data1 = str(data[0][0]).replace("b'",'').replace("'","") - type1 = str(data[0][1]) - self.result1 = self.findChild(QtWidgets.QLineEdit, 'raw_text_result') - print(data1) - secret = urlparse(data1).query.split('=')[1].split('&')[0] - domain = urlparse(data1).path.split('/')[1].split(':')[0] - account = urlparse(data1).path.split('/')[1].split(':')[1] - print(secret) - print(domain) - print(account) - # os.system("python cmdkk.py auth_init %s:%s:%s" % (secret, domain, account)) - # self.result1.setText(data1)#'raw_type_result' - # self.result2 = self.findChild(QtWidgets.QLineEdit, 'raw_type_result') - # self.result2.setText(type1) - except AttributeError: - pass - - def OtpGen(self): - #generate OTP - print("Generate OTP") - os.system("python cmdkk.py auth_gen") - - def Test(self): - #test the keepkey function - print("test function") - if (self.client != None): - AuthOps.auth_test(self.client) - else: - print("KeepKey not connected") - + def ConnectKK(self): + from keepkeylib.GUImixin import GuiUIMixin + class KeepKeyClientAuth(ProtocolMixin, GuiUIMixin, BaseClient): + pass + # look for keepkey connection # List all connected KeepKeys on USB self.client = None @@ -106,56 +61,384 @@ def ConnectKK(self): # Check whether we found any if len(devices) == 0: - print('No KeepKey found') + error_popup("No KeepKey found", +"Ensure that\n1) KeepKey is plugged in\n2) There are no browser windows connected to KeepKey\nThen restart KeepKeyAuthenticator") return # Use first connected device - transport = WebUsbTransport(devices[0]) + try: + transport = WebUsbTransport(devices[0]) + except Exception as E: + if str(E) == "LIBUSB_ERROR_ACCESS [-3]": + error_popup("KeepKey already claimed on USB","Make sure all KeepKey apps and websites are closed") + else: + error_popup("Unknown connection error","Make sure all KeepKey apps and websites are closed") + return # Creates object for manipulating KeepKey - self.client = KeepKeyClient(transport) + self.client = KeepKeyClientAuth(transport) + self.requires_firmware("7.7.0") + -class AuthOps: - def auth_test(client): - client.ping( - msg = b'\x15' + bytes("initializeAuth:" + "python-test:" + "BASE32SECRET2345AB", 'utf8') - ) + def requires_firmware(self, ver_required): + ret = self.client.init_device() + features = self.client.features + version = "%s.%s.%s" % (features.major_version, features.minor_version, features.patch_version) + if semver.VersionInfo.parse(version) < semver.VersionInfo.parse(ver_required): + error_popup("Firmware Upgrade Needed", +"Your KeepKey has v"+version+" Authenticator feature requires v"+ver_required) + self.client = None + + def getClient(self): + return self.client + +def error_popup(errmsg, infotext): + # set up error/status message box popup + msg = QMessageBox() + msg.setWindowTitle("ERROR") + msg.setIcon(QMessageBox.Critical) + msg.setDefaultButton(QMessageBox.Ok) + msg.setText(errmsg) + msg.setInformativeText(infotext) + x = msg.exec_() # show message box + +class PIN_Dialog(PIN_Dialog): + def setupUi(self, Dialog): + super(PIN_Dialog, self).setupUi(Dialog) + + self.pushButton_1.clicked.connect(lambda: self.addPinClick('1')) + self.pushButton_2.clicked.connect(lambda: self.addPinClick('2')) + self.pushButton_3.clicked.connect(lambda: self.addPinClick('3')) + self.pushButton_4.clicked.connect(lambda: self.addPinClick('4')) + self.pushButton_5.clicked.connect(lambda: self.addPinClick('5')) + self.pushButton_6.clicked.connect(lambda: self.addPinClick('6')) + self.pushButton_7.clicked.connect(lambda: self.addPinClick('7')) + self.pushButton_8.clicked.connect(lambda: self.addPinClick('8')) + self.pushButton_9.clicked.connect(lambda: self.addPinClick('9')) + self.pbUnlock.clicked.connect(lambda: self.returnPinAndClose(Dialog)) + self.encodedPin = '' + + + def addPinClick(self, clickPosition): + self.encodedPin+=clickPosition + print("encoded pin ", self.encodedPin) + self.lineEdit.setText(self.encodedPin) + return + + def returnPinAndClose(self, Dialog): + print("unlock pressed") + Dialog.close() + + def getEncodedPin(self): + # return encoded PIN when unlock button pressed + return self.encodedPin + +def pingui_popup(): + # set up PIN dialog + PINDialog = QtWidgets.QDialog() + PIN_ui = PIN_Dialog() + PIN_ui.setupUi(PINDialog) + PINDialog.show() + x = PINDialog.exec_() # show pin dialog + pin = PIN_ui.getEncodedPin() + print(pin) + return pin + +class RemAcc_Dialog(RemAcc_Dialog): + def __init__(self, client, authOps): + self.client = client + self.authOps = authOps + self.accounts = None + return + + def setupUi(self, Dialog): + super(RemAcc_Dialog, self).setupUi(Dialog) + + # Remove account button group + # Add IDs to buttons to make for easy access + bNum = 1 + bList = self.RemoveAccButtonGroup.buttons() + for b in bList: + self.RemoveAccButtonGroup.setId(b, bNum) + bNum += 1 + + self.RemoveAccButtonGroup.idClicked.connect(self.RemoveAccount) + self.RemGetAccounts() + + def RemGetAccounts(self): + self.accounts = self.authOps.auth_accGet(self.client) + # reset button list + bList = self.RemoveAccButtonGroup.buttons() + for b in bList: + b.setText('') + + if len(self.accounts) > 0: + # this forms monotonic button list of non-empty slots + self.RemAccButton = list() + bNum = 1 + for acc in self.accounts: + if acc[1] == '': + continue; + else: + self.RemoveAccButtonGroup.button(bNum).setText(acc[1]) + self.RemAccButton.append((str(bNum), acc)) + bNum += 1 + + def RemoveAccount(self, id_): + # First check if this is an active button with an account + if self.RemoveAccButtonGroup.button(id_).text() == '': + return + + if (self.client != None): + for ba in self.RemAccButton: + if ba[0] == str(id_): # button clicked is in button-account list + dom, acc = ba[1][1].split(':') + print(dom+" "+acc) + break + try: + self.authOps.auth_accRem(self.client, dom, acc) + self.RemGetAccounts() + + except PinException as e: + error_popup("Invalid PIN", "") + return + else: + print("KeepKey not connected") + +class Ui(Ui): + def __init__(self): + self.authOps = AuthClass() + + def setupUi(self, MainWindow): + super(Ui, self).setupUi(MainWindow) + + self.clientOps = kkClient() + + self.ConnectKKButton.clicked.connect(self.KKConnect) + self.AddAccButton.clicked.connect(self.QrScreencap) + self.RemoveAccButton.clicked.connect(self.removeAcc) + self.testButton.clicked.connect(self.Test) + + # OTP button group + # Add IDs to buttons to make for easy access + bNum = 1 + bList = self.OTPButtonGroup.buttons() + for b in bList: + self.OTPButtonGroup.setId(b, bNum) + bNum += 1 + + # self.OTPButtonGroup.buttonClicked.connect(self.OtpGen) + self.OTPButtonGroup.idClicked.connect(self.OtpGen) + + MainWindow.show() + + def KKConnect(self): + self.accounts = None + _translate = QtCore.QCoreApplication.translate + self.clientOps.ConnectKK() + client = self.clientOps.getClient() + if (client != None): + self.ConnectKKButton.setStyleSheet("border-radius: 10px;\n"\ + "background-color: rgb(35, 40, 49);\n"\ + "border-width: 2px;\n"\ + "border-style: solid;\n"\ + "border-color: black;\n"\ + "color: rgb(0, 255, 0)") + self.ConnectKKButton.setText(_translate("MainWindow", "KeepKey\nConnected")) + # get accounts if connected + self.getAccounts(client) + + def getAccounts(self, client): + self.accounts = self.authOps.auth_accGet(client) + print(self.accounts) + # reset button list + bList = self.OTPButtonGroup.buttons() + for b in bList: + b.setText('') + if len(self.accounts) > 0: + # this forms monotonic button list of non-empty slots + self.otpButtonAcc = list() + bNum = 1 + for acc in self.accounts: + if acc[1] == '': + continue + else: + self.OTPButtonGroup.button(bNum).setText(acc[1]) + self.otpButtonAcc.append((str(bNum), acc)) + bNum += 1 + + def QrScreencap(self): + # grab fullscreen + self.im = ImageGrab.grab(include_layered_windows=True) + #self.im.save("fullscreen.png") + + data = decode(self.im) + if (data == []): + error_popup("QR Code Error", "Could not read QR code") + return + data1 = str(data[0][0]).replace("b'",'').replace("'","") + type1 = str(data[0][1]) + print(data1) + print(type1) + secret = urlparse(data1).query.split('=')[1].split('&')[0] + domain = urlparse(data1).path.split('/')[1].split(':')[0] + account = urlparse(data1).path.split('/')[1].split(':')[1] + print(secret) + print(domain) + print(account) + client = self.clientOps.getClient() + if (client != None): + try: + self.authOps.auth_accAdd(client, secret, domain, account) + # re-establish otp list + self.getAccounts(client) + + except PinException as e: + error_popup("Invalid PIN", "") + return + else: + print("KeepKey not connected") + + def OtpGen(self, id_): + # First check if this is an active button with an account + if self.OTPButtonGroup.button(id_).text() == '': + return + + #generate OTP + client = self.clientOps.getClient() + + if (client != None): + for ba in self.otpButtonAcc: + if ba[0] == str(id_): # button clicked is in button-account list + dom, acc = ba[1][1].split(':') + print(dom+" "+acc) + break + try: + self.authOps.auth_otp(client, dom, acc) + except PinException as e: + error_popup("Invalid PIN", "") + return + else: + print("KeepKey not connected") + + def removeAcc(self): + # set up remove account dialog + RemAccDialog = QtWidgets.QDialog() + client = self.clientOps.getClient() + RemAcc_ui = RemAcc_Dialog(client, self.authOps) + RemAcc_ui.setupUi(RemAccDialog) + RemAccDialog.show() + x = RemAccDialog.exec_() # show pin dialog + self.getAccounts(client) + + def Test(self): + #test the keepkey function + print("test function") + client = self.clientOps.getClient() + if (client != None): + try: + self.authOps.auth_test(client) + self.getAccounts(client) + except PinException as e: + error_popup("Invalid PIN", "") + return + else: + print("KeepKey not connected") + +class AuthClass: + def auth_accAdd(self, client, secret, domain, account): + # try: + # ret = client.ping(msg = b'\x15' + bytes("initializeAuth:" + "python-test:" + "BASE32SECRET2345AB", 'utf8')) + # except PinException as e: + # print("Got exception in authenticator", e.args[0], " ", e.args[1]) + # exit() + + try: + retval = client.ping(msg = b'\x15' + bytes("initializeAuth:"+domain+":"+account+":"+secret, 'utf8')) + except CallException as E: + if E.args[1] == 'Authenticator secret storage full': + error_popup(E.args[1], "Need to remove an account to add a new one to this KeepKey") + else: + error_popup(E.args[1], "") + + def auth_otp(self, client, domain, account): interval = 30 # 30 second interval #T0 = 1535317397 - T0 = 1536262427 - - T = int(T0/interval).to_bytes(8, byteorder='big') + #T0 = 1536262427 + T0 = datetime.now().timestamp() + Tslice = int(T0/interval) + Tremain = int((int(T0) - Tslice*30)) + print(Tremain) + T = Tslice.to_bytes(8, byteorder='big') retval = client.ping( - msg = b'\x16' + bytes("generateOTPFrom:" + "python-test:", 'utf8') + binascii.hexlify(bytearray(T)) + msg = b'\x16' + bytes("generateOTPFrom:"+domain+":"+account+":", 'utf8') + + binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8') ) - print(retval) - print("should be 007767") + # print(retval) + # print("should be 007767") -# def main(): - - -# # List all connected KeepKeys on USB -# devices = WebUsbTransport.enumerate() + def auth_accGet(self, client): + ctr=0 + accounts = list() + while True: + try: + retval = client.ping(msg = b'\x17' + bytes("getAccount:"+str(ctr), 'utf8')) + accounts.append([ctr, retval]) + except CallException as E: + if E.args[1] == 'Account not found': + accounts.append([ctr, '']) + if E.args[1] == 'Slot request out of range': + break + if E.args[1] == 'Device not initialized': + error_popup('Device not initialized', 'Initialize KeepKey prior to using authentication feature') + break; + ctr+=1 + + return accounts -# # Check whether we found any -# if len(devices) == 0: -# print('No KeepKey found') -# return + def auth_accRem(self, client, domain, account): + try: + client.ping(msg = b'\x18' + bytes("removeAccount:"+domain+":"+account, 'utf8')) + except CallException as E: + error_popup(E.args[1], domain+":"+account) + return -# # Use first connected device -# transport = WebUsbTransport(devices[0]) + def auth_test(self, client): + try: + retval = client.ping(msg = b'\x15' + bytes("initializeAuth:"+"GitHub"+":"+"mooneytestgithub"+":"+"ZKLHM3W3XAHG4CBN", 'utf8')) + retval = client.ping(msg = b'\x15' + bytes("initializeAuth:"+"Shapeshift"+":"+"markrypto"+":"+"BASE32SECRET2345AC", 'utf8')) + retval = client.ping(msg = b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto2"+":"+"BASE32SECRET2345AD", 'utf8')) + except CallException as E: + if E.args[1] == 'Authenticator secret storage full': + error_popup(E.args[1], "Need to remove an account to add a new one to this KeepKey") + else: + error_popup(E.args[1], "") + + + # ret = client.ping(msg = b'\x15' + bytes("initializeAuth:" + "python-test:" + "BASE32SECRET2345AB", 'utf8')) + -# # Creates object for manipulating KeepKey -# client = KeepKeyClient(transport) + + # interval = 30 # 30 second interval + # #T0 = 1535317397 + # T0 = 1536262427 + + # T = int(T0/interval).to_bytes(8, byteorder='big') + # retval = client.ping( + # msg = b'\x16' + bytes("generateOTPFrom:" + "python-test:", 'utf8') + binascii.hexlify(bytearray(T)) + # ) + # print(retval) + # print("should be 007767") -# # Initialize authenticator -# AuthOps.auth_test(client) +def main(): + app = QtWidgets.QApplication(sys.argv) + MainWindow = QtWidgets.QMainWindow() + ui = Ui() + ui.setupUi(MainWindow) + sys.exit(app.exec_()) + +if __name__ == '__main__': + main() -# if __name__ == '__main__': -# main() -app = QtWidgets.QApplication(sys.argv) -app.setStyleSheet(qdarkgraystyle.load_stylesheet()) -window = Ui() -app.exec_() diff --git a/PinUi.py b/PinUi.py new file mode 100644 index 00000000..89818477 --- /dev/null +++ b/PinUi.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'PinUi.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(576, 634) + Dialog.setStyleSheet("background-color: rgb(1, 0, 0);") + Dialog.setModal(True) + self.pushButton_7 = QtWidgets.QPushButton(Dialog) + self.pushButton_7.setGeometry(QtCore.QRect(110, 140, 101, 91)) + self.pushButton_7.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;") + self.pushButton_7.setText("") + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap("circle-32.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.pushButton_7.setIcon(icon) + self.pushButton_7.setIconSize(QtCore.QSize(32, 32)) + self.pushButton_7.setObjectName("pushButton_7") + self.pushButton_4 = QtWidgets.QPushButton(Dialog) + self.pushButton_4.setGeometry(QtCore.QRect(110, 250, 101, 91)) + self.pushButton_4.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;") + self.pushButton_4.setIcon(icon) + self.pushButton_4.setIconSize(QtCore.QSize(32, 32)) + self.pushButton_4.setObjectName("pushButton_4") + self.pushButton_1 = QtWidgets.QPushButton(Dialog) + self.pushButton_1.setGeometry(QtCore.QRect(110, 360, 101, 91)) + self.pushButton_1.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;") + self.pushButton_1.setText("") + self.pushButton_1.setIcon(icon) + self.pushButton_1.setIconSize(QtCore.QSize(32, 32)) + self.pushButton_1.setObjectName("pushButton_1") + self.pushButton_8 = QtWidgets.QPushButton(Dialog) + self.pushButton_8.setGeometry(QtCore.QRect(240, 140, 101, 91)) + self.pushButton_8.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;") + self.pushButton_8.setIcon(icon) + self.pushButton_8.setIconSize(QtCore.QSize(32, 32)) + self.pushButton_8.setObjectName("pushButton_8") + self.pushButton_5 = QtWidgets.QPushButton(Dialog) + self.pushButton_5.setGeometry(QtCore.QRect(240, 250, 101, 91)) + self.pushButton_5.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;") + self.pushButton_5.setText("") + self.pushButton_5.setIcon(icon) + self.pushButton_5.setIconSize(QtCore.QSize(32, 32)) + self.pushButton_5.setObjectName("pushButton_5") + self.pushButton_2 = QtWidgets.QPushButton(Dialog) + self.pushButton_2.setGeometry(QtCore.QRect(240, 360, 101, 91)) + self.pushButton_2.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;") + self.pushButton_2.setText("") + self.pushButton_2.setIcon(icon) + self.pushButton_2.setIconSize(QtCore.QSize(32, 32)) + self.pushButton_2.setObjectName("pushButton_2") + self.pushButton_9 = QtWidgets.QPushButton(Dialog) + self.pushButton_9.setGeometry(QtCore.QRect(370, 140, 101, 91)) + self.pushButton_9.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;") + self.pushButton_9.setIcon(icon) + self.pushButton_9.setIconSize(QtCore.QSize(32, 32)) + self.pushButton_9.setObjectName("pushButton_9") + self.pushButton_6 = QtWidgets.QPushButton(Dialog) + self.pushButton_6.setGeometry(QtCore.QRect(370, 250, 101, 91)) + self.pushButton_6.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;") + self.pushButton_6.setText("") + self.pushButton_6.setIcon(icon) + self.pushButton_6.setIconSize(QtCore.QSize(32, 32)) + self.pushButton_6.setObjectName("pushButton_6") + self.pushButton_3 = QtWidgets.QPushButton(Dialog) + self.pushButton_3.setGeometry(QtCore.QRect(370, 360, 101, 91)) + self.pushButton_3.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;") + self.pushButton_3.setText("") + self.pushButton_3.setIcon(icon) + self.pushButton_3.setIconSize(QtCore.QSize(32, 32)) + self.pushButton_3.setObjectName("pushButton_3") + self.label = QtWidgets.QLabel(Dialog) + self.label.setGeometry(QtCore.QRect(60, 10, 441, 121)) + self.label.setStyleSheet("color: rgb(255, 255, 255);") + self.label.setObjectName("label") + self.pbUnlock = QtWidgets.QPushButton(Dialog) + self.pbUnlock.setGeometry(QtCore.QRect(65, 550, 440, 60)) + font = QtGui.QFont() + font.setPointSize(24) + self.pbUnlock.setFont(font) + self.pbUnlock.setStyleSheet("background-color: rgb(184, 155, 104);\n" +"border-radius: 10px;\n" +"color: rgb(255, 255,255);") + self.pbUnlock.setObjectName("pbUnlock") + self.lineEdit = QtWidgets.QLineEdit(Dialog) + self.lineEdit.setGeometry(QtCore.QRect(65, 470, 440, 60)) + font = QtGui.QFont() + font.setPointSize(24) + self.lineEdit.setFont(font) + self.lineEdit.setStyleSheet("background-color: rgb(1, 0, 0);\n" +"border-radius: 10px;\n" +"color: rgb(255, 255,255);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: rgb(103, 103, 103);") + self.lineEdit.setMaxLength(10) + self.lineEdit.setEchoMode(QtWidgets.QLineEdit.Password) + self.lineEdit.setObjectName("lineEdit") + + self.retranslateUi(Dialog) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "PIN Entry")) + self.label.setText(_translate("Dialog", "

Enter Your PIN

Use PIN layout shown on your device to find the

location to press on the PIN pad.

")) + self.pbUnlock.setText(_translate("Dialog", "Unlock")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + Dialog = QtWidgets.QDialog() + ui = Ui_Dialog() + ui.setupUi(Dialog) + Dialog.show() + sys.exit(app.exec_()) diff --git a/PinUi.ui b/PinUi.ui new file mode 100644 index 00000000..c8ea7b15 --- /dev/null +++ b/PinUi.ui @@ -0,0 +1,354 @@ + + + Dialog + + + + 0 + 0 + 576 + 634 + + + + PIN Entry + + + background-color: rgb(1, 0, 0); + + + true + + + + + 110 + 140 + 101 + 91 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; + + + + + + + circle-32.pngcircle-32.png + + + + 32 + 32 + + + + + + + 110 + 250 + 101 + 91 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; + + + + circle-32.pngcircle-32.png + + + + 32 + 32 + + + + + + + 110 + 360 + 101 + 91 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; + + + + + + + circle-32.pngcircle-32.png + + + + 32 + 32 + + + + + + + 240 + 140 + 101 + 91 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; + + + + circle-32.pngcircle-32.png + + + + 32 + 32 + + + + + + + 240 + 250 + 101 + 91 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; + + + + + + + circle-32.pngcircle-32.png + + + + 32 + 32 + + + + + + + 240 + 360 + 101 + 91 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; + + + + + + + circle-32.pngcircle-32.png + + + + 32 + 32 + + + + + + + 370 + 140 + 101 + 91 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; + + + + circle-32.pngcircle-32.png + + + + 32 + 32 + + + + + + + 370 + 250 + 101 + 91 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; + + + + + + + circle-32.pngcircle-32.png + + + + 32 + 32 + + + + + + + 370 + 360 + 101 + 91 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; + + + + + + + circle-32.pngcircle-32.png + + + + 32 + 32 + + + + + + + 60 + 10 + 441 + 121 + + + + color: rgb(255, 255, 255); + + + <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">Enter Your PIN</span></p><p><span style=" font-size:18pt; font-weight:600;">Use PIN layout shown on your device to find the </span></p><p><span style=" font-size:18pt; font-weight:600;">location to press on the PIN pad.</span></p></body></html> + + + + + + 65 + 550 + 440 + 60 + + + + + 24 + + + + background-color: rgb(184, 155, 104); +border-radius: 10px; +color: rgb(255, 255,255); + + + Unlock + + + + + + 65 + 470 + 440 + 60 + + + + + 24 + + + + background-color: rgb(1, 0, 0); +border-radius: 10px; +color: rgb(255, 255,255); +border-width: 2px; +border-style: solid; +border-color: rgb(103, 103, 103); + + + 10 + + + QLineEdit::Password + + + + + + diff --git a/README.rst b/README.rst index 16e4edac..dc12d135 100644 --- a/README.rst +++ b/README.rst @@ -172,7 +172,7 @@ products such as the Yubikey. Build for MacOS --------------- KeepKeyAuthenticator.app is built using py2app. The setup file was created from the command line: - py2applet --make-setup KeepKeyAuthenticator.py + py2applet --make-setup KeepKeyAuthenticator.py authResources/*.png mv setup.py setupAuth.py and then saved as setupAuth.py. NOTE: Creating the setup.py file in this manner will overwrite the existing setup.py file for the python client. diff --git a/RemAccDialog.ui b/RemAccDialog.ui new file mode 100644 index 00000000..6436842c --- /dev/null +++ b/RemAccDialog.ui @@ -0,0 +1,234 @@ + + + RemAccDialog + + + + 0 + 0 + 465 + 388 + + + + Remove Account + + + background-color: rgb(1, 0, 0); + + + true + + + + + 50 + 80 + 350 + 40 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + RemoveAccButtonGroup + + + + + + 50 + 20 + 130 + 45 + + + + + 32 + + + + color: rgb(255, 255, 255); + + + + Accounts + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + 195 + 20 + 220 + 45 + + + + + 18 + + + + color: rgb(255, 255, 255); + + + + Click on account to PERMANENTLY remove + + + Qt::RichText + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + + 50 + 140 + 350 + 40 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + RemoveAccButtonGroup + + + + + + 50 + 200 + 350 + 40 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + RemoveAccButtonGroup + + + + + + 50 + 260 + 350 + 40 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + RemoveAccButtonGroup + + + + + + 50 + 320 + 350 + 40 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + RemoveAccButtonGroup + + + + + + + + + diff --git a/StatErr.py b/StatErr.py new file mode 100644 index 00000000..ec92d008 --- /dev/null +++ b/StatErr.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'StatErr.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(406, 137) + self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) + self.buttonBox.setGeometry(QtCore.QRect(290, 20, 81, 41)) + self.buttonBox.setOrientation(QtCore.Qt.Vertical) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + + def dialogMsg(self, Dialog, errstat): + Dialog.setText(errstat) + +# if __name__ == "__main__": +# import sys +# app = QtWidgets.QApplication(sys.argv) +# Dialog = QtWidgets.QDialog() +# ui = Ui_Dialog() +# ui.setupUi(Dialog) +# Dialog.show() +# sys.exit(app.exec_()) diff --git a/StatErr.ui b/StatErr.ui new file mode 100644 index 00000000..3e25f289 --- /dev/null +++ b/StatErr.ui @@ -0,0 +1,68 @@ + + + Dialog + + + + 0 + 0 + 406 + 137 + + + + Dialog + + + + + 290 + 20 + 81 + 41 + + + + Qt::Vertical + + + QDialogButtonBox::Ok + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/authResources/circle-32.png b/authResources/circle-32.png new file mode 100644 index 0000000000000000000000000000000000000000..0c6eea0ee49fdc2ade1028a29ca5eb4bbf03f55d GIT binary patch literal 512 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 VDpcgq%-?U90HUs^R;6N({10iA9?}2+ literal 0 HcmV?d00001 diff --git a/circle-32.png b/circle-32.png new file mode 100644 index 0000000000000000000000000000000000000000..0c6eea0ee49fdc2ade1028a29ca5eb4bbf03f55d GIT binary patch literal 512 zcmV+b0{{JqP)$EYTxqwR9~w%W)UZKb9Ax)}Crv-6UUHdei(#T@GHr2^fZY}Y>?Pn+ivf1>ulR`OKgUzxdWwj1 z;7!AFfVUAb$lLK%id9%{NInGaM8shNh=>8O)sP(ERYV;AbHt*O&9ez##(@`AvQdbx z^)%v4UtOEHHtMySLiC+h%g0vY!yM&bysVB-2$9rh>hBrEXX;Cn zPN!DAs1T$BJfs-U*D1#HF2#=e3pk+|&v&RWp8o~Epv31jpK8DW0000. +# +# The script has been modified for KeepKey Device. + +from . import messages_pb2 as proto +from KeepKeyAuthenticator import pingui_popup + +class GuiUIMixin(object): + # qt gui interface + + def __init__(self, *args, **kwargs): + super(GuiUIMixin, self).__init__(*args, **kwargs) + self.character_request_first_pass = True + + def callback_ButtonRequest(self, msg): + # log("Sending ButtonAck for %s " % get_buttonrequest_value(msg.code)) + return proto.ButtonAck() + + # def callback_RecoveryMatrix(self, msg): + # pass + + def callback_PinMatrixRequest(self, msg): + # get matrix position and ack + # pin = getPinDecoded() + pin = pingui_popup() + print(pin) + return proto.PinMatrixAck(pin=pin) + + # def callback_PassphraseRequest(self, msg): + # pass + + # def callback_CharacterRequest(self, msg): + # pass diff --git a/kk-icon-gold.png b/kk-icon-gold.png new file mode 100644 index 0000000000000000000000000000000000000000..0d5b7531c90f785c39be82eade69124d491d39c7 GIT binary patch literal 4644 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 VDpcgq%-?U90HUs^R;6N({10iA9?}2+ literal 0 HcmV?d00001 diff --git a/kkQRtest1.png b/kkQRtest1.png new file mode 100644 index 0000000000000000000000000000000000000000..cb8c6a59d78fc40cff3b3c8966752a3a6d80f70c GIT binary patch literal 51594 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 literal 0 HcmV?d00001 diff --git a/kkQRtest2.png b/kkQRtest2.png new file mode 100644 index 0000000000000000000000000000000000000000..226742b12965fcf0c328a3c49c7c84560cf25501 GIT binary patch literal 51775 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 Date: Mon, 12 Dec 2022 10:22:09 -0700 Subject: [PATCH 06/34] move authenticator to its own dir --- README.rst | 14 +------------ authenticator/AUTHENTICATOR.rst | 19 ++++++++++++++++++ AuthMain.py => authenticator/AuthMain.py | 0 AuthMain.ui => authenticator/AuthMain.ui | 0 .../KeepKeyAuthenticator.py | 8 ++++++-- PinUi.py => authenticator/PinUi.py | 0 PinUi.ui => authenticator/PinUi.ui | 0 .../RemAccDialog.ui | 0 StatErr.py => authenticator/StatErr.py | 0 StatErr.ui => authenticator/StatErr.ui | 0 authenticator/__init__.py | 0 .../authResources}/circle-32.png | Bin .../authResources}/kk-icon-gold.png | Bin circle-32.png => authenticator/circle-32.png | Bin .../kk-icon-gold.png | Bin kkQRtest.png => authenticator/kkQRtest.png | Bin kkQRtest1.png => authenticator/kkQRtest1.png | Bin kkQRtest2.png => authenticator/kkQRtest2.png | Bin .../remaccdialog.py | 0 setupauth.py => authenticator/setup.py | 0 setup.py | 2 +- 21 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 authenticator/AUTHENTICATOR.rst rename AuthMain.py => authenticator/AuthMain.py (100%) rename AuthMain.ui => authenticator/AuthMain.ui (100%) rename KeepKeyAuthenticator.py => authenticator/KeepKeyAuthenticator.py (98%) rename PinUi.py => authenticator/PinUi.py (100%) rename PinUi.ui => authenticator/PinUi.ui (100%) rename RemAccDialog.ui => authenticator/RemAccDialog.ui (100%) rename StatErr.py => authenticator/StatErr.py (100%) rename StatErr.ui => authenticator/StatErr.ui (100%) create mode 100644 authenticator/__init__.py rename {authResources => authenticator/authResources}/circle-32.png (100%) rename {authResources => authenticator/authResources}/kk-icon-gold.png (100%) rename circle-32.png => authenticator/circle-32.png (100%) rename kk-icon-gold.png => authenticator/kk-icon-gold.png (100%) rename kkQRtest.png => authenticator/kkQRtest.png (100%) rename kkQRtest1.png => authenticator/kkQRtest1.png (100%) rename kkQRtest2.png => authenticator/kkQRtest2.png (100%) rename remaccdialog.py => authenticator/remaccdialog.py (100%) rename setupauth.py => authenticator/setup.py (100%) diff --git a/README.rst b/README.rst index dc12d135..794416df 100644 --- a/README.rst +++ b/README.rst @@ -164,17 +164,5 @@ This will produce an executable install app KeepKey Authenticator ============== -The KeepKey Authenticator is a standalone app that enables the KeepKey device to perform as a -hardware off-line one-time passcode (OTP) generator for two factor authentication using the -TOPT algorithm. The KeepKey PIN and physical button gives it security features similar to other -products such as the Yubikey. +Documentation can be found [here](authenticator/AUTHENTICATOR.rst). -Build for MacOS ---------------- -KeepKeyAuthenticator.app is built using py2app. The setup file was created from the command line: - py2applet --make-setup KeepKeyAuthenticator.py authResources/*.png - mv setup.py setupAuth.py -and then saved as setupAuth.py. NOTE: Creating the setup.py file in this manner will overwrite -the existing setup.py file for the python client. -Create the app: - python setupAuth.py py2app diff --git a/authenticator/AUTHENTICATOR.rst b/authenticator/AUTHENTICATOR.rst new file mode 100644 index 00000000..49a059d4 --- /dev/null +++ b/authenticator/AUTHENTICATOR.rst @@ -0,0 +1,19 @@ + +KeepKey Authenticator +============== +The KeepKey Authenticator is a standalone app that enables the KeepKey device to perform as a +hardware off-line one-time passcode (OTP) generator for two factor authentication using the +TOPT algorithm. The KeepKey PIN and physical button gives it security features similar to other +products such as the Yubikey. + +Build for MacOS +--------------- +Make sure you have python-keepkey built and installed: + python python-keepkey/setup.py build install + +KeepKeyAuthenticator.app is built using py2app. The setup file was created from the command line: + py2applet --make-setup KeepKeyAuthenticator.py authResources/*.png +and then saved as setupAuth.py. NOTE: Creating the setup.py file in this manner will overwrite +the existing setup.py file if one exists. +Create the app: + python setupAuth.py py2app diff --git a/AuthMain.py b/authenticator/AuthMain.py similarity index 100% rename from AuthMain.py rename to authenticator/AuthMain.py diff --git a/AuthMain.ui b/authenticator/AuthMain.ui similarity index 100% rename from AuthMain.ui rename to authenticator/AuthMain.ui diff --git a/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py similarity index 98% rename from KeepKeyAuthenticator.py rename to authenticator/KeepKeyAuthenticator.py index b81441b3..1d26795a 100644 --- a/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -246,7 +246,7 @@ def KKConnect(self): "color: rgb(0, 255, 0)") self.ConnectKKButton.setText(_translate("MainWindow", "KeepKey\nConnected")) # get accounts if connected - self.getAccounts(client) + #self.getAccounts(client) def getAccounts(self, client): self.accounts = self.authOps.auth_accGet(client) @@ -386,13 +386,17 @@ def auth_accGet(self, client): retval = client.ping(msg = b'\x17' + bytes("getAccount:"+str(ctr), 'utf8')) accounts.append([ctr, retval]) except CallException as E: + print(E.args[1]) if E.args[1] == 'Account not found': accounts.append([ctr, '']) + if E.args[1] == 'Invalid PIN': + error_popup(E.args[1], '') + break if E.args[1] == 'Slot request out of range': break if E.args[1] == 'Device not initialized': error_popup('Device not initialized', 'Initialize KeepKey prior to using authentication feature') - break; + break ctr+=1 return accounts diff --git a/PinUi.py b/authenticator/PinUi.py similarity index 100% rename from PinUi.py rename to authenticator/PinUi.py diff --git a/PinUi.ui b/authenticator/PinUi.ui similarity index 100% rename from PinUi.ui rename to authenticator/PinUi.ui diff --git a/RemAccDialog.ui b/authenticator/RemAccDialog.ui similarity index 100% rename from RemAccDialog.ui rename to authenticator/RemAccDialog.ui diff --git a/StatErr.py b/authenticator/StatErr.py similarity index 100% rename from StatErr.py rename to authenticator/StatErr.py diff --git a/StatErr.ui b/authenticator/StatErr.ui similarity index 100% rename from StatErr.ui rename to authenticator/StatErr.ui diff --git a/authenticator/__init__.py b/authenticator/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/authResources/circle-32.png b/authenticator/authResources/circle-32.png similarity index 100% rename from authResources/circle-32.png rename to authenticator/authResources/circle-32.png diff --git a/authResources/kk-icon-gold.png b/authenticator/authResources/kk-icon-gold.png similarity index 100% rename from authResources/kk-icon-gold.png rename to authenticator/authResources/kk-icon-gold.png diff --git a/circle-32.png b/authenticator/circle-32.png similarity index 100% rename from circle-32.png rename to authenticator/circle-32.png diff --git a/kk-icon-gold.png b/authenticator/kk-icon-gold.png similarity index 100% rename from kk-icon-gold.png rename to authenticator/kk-icon-gold.png diff --git a/kkQRtest.png b/authenticator/kkQRtest.png similarity index 100% rename from kkQRtest.png rename to authenticator/kkQRtest.png diff --git a/kkQRtest1.png b/authenticator/kkQRtest1.png similarity index 100% rename from kkQRtest1.png rename to authenticator/kkQRtest1.png diff --git a/kkQRtest2.png b/authenticator/kkQRtest2.png similarity index 100% rename from kkQRtest2.png rename to authenticator/kkQRtest2.png diff --git a/remaccdialog.py b/authenticator/remaccdialog.py similarity index 100% rename from remaccdialog.py rename to authenticator/remaccdialog.py diff --git a/setupauth.py b/authenticator/setup.py similarity index 100% rename from setupauth.py rename to authenticator/setup.py diff --git a/setup.py b/setup.py index 813a2edf..2c457d02 100755 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name='keepkey', version='7.0.3', - author='TREZOR and KeepKey', + author='markrypto, KeepKey and TREZOR', author_email='support@keepkey.com', description='Python library for communicating with KeepKey Hardware Wallet', url='https://github.com/keepkey/python-keepkey', From 13752a24f49791c32a2a90e631158db4da26e35d Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Mon, 12 Dec 2022 10:23:51 -0700 Subject: [PATCH 07/34] change readme extension --- README.md | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..794416df --- /dev/null +++ b/README.md @@ -0,0 +1,168 @@ +.. image:: https://circleci.com/gh/keepkey/python-keepkey.svg?style=svg + :target: https://circleci.com/gh/keepkey/python-keepkey + +python-keepkey +============== + +Client side implementation for KeepKey-compatible Bitcoin hardware wallets. + +This is a modified version of python-trezor. The changes made were to +support KeepKey's protocol, as well as the additional feature set +of KeepKey. For example, by default, device_recovery command invokes +KeepKey's style of device recovery using the Recovery Cipher. + +See http://www.keepkey.com for more information. + +Example +------- + +also found in ``helloworld.py`` + +.. code:: python + + #!/usr/bin/env python + + from keepkeylib.client import KeepKeyClient + from keepkeylib.transport_hid import HidTransport + + def main(): + # List all connected KeepKeys on USB + devices = HidTransport.enumerate() + + # Check whether we found any + if len(devices) == 0: + print('No KeepKey found') + return + + # Use first connected device + transport = HidTransport(devices[0]) + + # Creates object for manipulating KeepKey + client = KeepKeyClient(transport) + + # Print out KeepKey's features and settings + print(client.features) + + # Get the first address of first BIP44 account + # (should be the same address as shown in KeepKey wallet Chrome extension) + bip32_path = client.expand_path("44'/0'/0'/0/0") + address = client.get_address('Bitcoin', bip32_path) + print('Bitcoin address:', address) + + client.close() + + if __name__ == '__main__': + main() + +PIN Entering +------------ + +When you are asked for PIN, you have to enter scrambled PIN. Follow the numbers shown on KeepKey display and enter the their positions using the numeric keyboard mapping: + +=== === === + 7 8 9 + 4 5 6 + 1 2 3 +=== === === + +Example: your PIN is **1234** and KeepKey is displaying the following: + +=== === === + 2 8 3 + 5 4 6 + 7 9 1 +=== === === + +You have to enter: **3795** + +How to install (virtualenv) +------------------------ +* Install virtualenv +* Clone repository +* Run "virtualenv env" in the project root +* Run "source env/bin/activate" +* Run "python setup.py install" + +How to install (Windows) +------------------------ +* Install Python 2.7 (http://python.org) +* Run C:\\python27\\scripts\\pip.exe install cython +* Install Microsoft Visual C++ Compiler for Python 2.7 +* Clone repository (using TortoiseGit) to local directory +* Run C:\\python27\\python.exe setup.py install (or develop) + +How to install (Debian-Ubuntu) +------------------------------ +* sudo apt-get install python-dev python-setuptools cython libusb-1.0-0-dev libudev-dev git +* git clone https://github.com/keepkey/python-keepkey.git +* cd python-keepkey +* python setup.py install (or develop) + +Running Tests +------------- + +To run unit tests that don't require a device: + +.. code:: shell + + $ python tests/unit/*.py + +Release Process +--------------- + +* Check that the testsuite runs cleanly +* Bump the version in setup.py +* Tag the release +* Build the release + * sudo python3 setup.py sdist bdist_wheel bdist_egg +* Upload the release + * sudo python3 -m twine upload dist/* -s --sign-with gpg2 + +KeepKey Bridge +============== +The KeepKey Bridge is a standalone TCP-to-webusb bridge the KeepKey. It runs a python-keepkey client +based process that allows a localhost-based process to communicate with the KeepKey wallet, thus +bypassing the need for a webusb connection from a browser based platform. + +The KeepKey Bridge is recommended only for advanced users who have problems connecting the KeepKey +on Windows. + +Running the KeepKey Bridge +-------------------------- +Download the KeepKey Bridge installer ``kkbsetup.exe`` for Windows in the release package here: + +https://github.com/keepkey/python-keepkey/releases + +When running the KeepKey Bridge, a blank cmd window with the title "KepKey Bridge" will be visible. +To stop the bridge, simply close the cmd window. + +Build for Windows +----------------- +Requirements: + +- Windows 10 +- python3 +- waitress (python package) +- py2exe +- Inno Setup Compiler (optional, for creating Windows install exe) + +From a command prompt terminal window, run + ``python wbsetup.py py2exe -d windows/dist`` + +This will create a ``windows\dist`` folder with the Windows stand-alone executable file ``wait-serv.exe`` + +Inno Setup Compiler +------------------- +This tool builds and packages the executable for install on Windows. Build with the provided installer +script (modify version, etc., as needed) + + ``windows/KeepKeyBridge.iss`` + +This will produce an executable install app + + ``windows/Output/kkbsetup.exe`` + +KeepKey Authenticator +============== +Documentation can be found [here](authenticator/AUTHENTICATOR.rst). + From 15b80f22ac9728b2668cef17cf2d88b6b7932493 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Mon, 12 Dec 2022 10:27:23 -0700 Subject: [PATCH 08/34] rst to md --- README.md | 2 +- README.rst | 168 ------------------ .../{AUTHENTICATOR.rst => AUTHENTICATOR.md} | 0 3 files changed, 1 insertion(+), 169 deletions(-) delete mode 100644 README.rst rename authenticator/{AUTHENTICATOR.rst => AUTHENTICATOR.md} (100%) diff --git a/README.md b/README.md index 794416df..ba188e23 100644 --- a/README.md +++ b/README.md @@ -164,5 +164,5 @@ This will produce an executable install app KeepKey Authenticator ============== -Documentation can be found [here](authenticator/AUTHENTICATOR.rst). +Documentation can be found [here](authenticator/AUTHENTICATOR.md). diff --git a/README.rst b/README.rst deleted file mode 100644 index 794416df..00000000 --- a/README.rst +++ /dev/null @@ -1,168 +0,0 @@ -.. image:: https://circleci.com/gh/keepkey/python-keepkey.svg?style=svg - :target: https://circleci.com/gh/keepkey/python-keepkey - -python-keepkey -============== - -Client side implementation for KeepKey-compatible Bitcoin hardware wallets. - -This is a modified version of python-trezor. The changes made were to -support KeepKey's protocol, as well as the additional feature set -of KeepKey. For example, by default, device_recovery command invokes -KeepKey's style of device recovery using the Recovery Cipher. - -See http://www.keepkey.com for more information. - -Example -------- - -also found in ``helloworld.py`` - -.. code:: python - - #!/usr/bin/env python - - from keepkeylib.client import KeepKeyClient - from keepkeylib.transport_hid import HidTransport - - def main(): - # List all connected KeepKeys on USB - devices = HidTransport.enumerate() - - # Check whether we found any - if len(devices) == 0: - print('No KeepKey found') - return - - # Use first connected device - transport = HidTransport(devices[0]) - - # Creates object for manipulating KeepKey - client = KeepKeyClient(transport) - - # Print out KeepKey's features and settings - print(client.features) - - # Get the first address of first BIP44 account - # (should be the same address as shown in KeepKey wallet Chrome extension) - bip32_path = client.expand_path("44'/0'/0'/0/0") - address = client.get_address('Bitcoin', bip32_path) - print('Bitcoin address:', address) - - client.close() - - if __name__ == '__main__': - main() - -PIN Entering ------------- - -When you are asked for PIN, you have to enter scrambled PIN. Follow the numbers shown on KeepKey display and enter the their positions using the numeric keyboard mapping: - -=== === === - 7 8 9 - 4 5 6 - 1 2 3 -=== === === - -Example: your PIN is **1234** and KeepKey is displaying the following: - -=== === === - 2 8 3 - 5 4 6 - 7 9 1 -=== === === - -You have to enter: **3795** - -How to install (virtualenv) ------------------------- -* Install virtualenv -* Clone repository -* Run "virtualenv env" in the project root -* Run "source env/bin/activate" -* Run "python setup.py install" - -How to install (Windows) ------------------------- -* Install Python 2.7 (http://python.org) -* Run C:\\python27\\scripts\\pip.exe install cython -* Install Microsoft Visual C++ Compiler for Python 2.7 -* Clone repository (using TortoiseGit) to local directory -* Run C:\\python27\\python.exe setup.py install (or develop) - -How to install (Debian-Ubuntu) ------------------------------- -* sudo apt-get install python-dev python-setuptools cython libusb-1.0-0-dev libudev-dev git -* git clone https://github.com/keepkey/python-keepkey.git -* cd python-keepkey -* python setup.py install (or develop) - -Running Tests -------------- - -To run unit tests that don't require a device: - -.. code:: shell - - $ python tests/unit/*.py - -Release Process ---------------- - -* Check that the testsuite runs cleanly -* Bump the version in setup.py -* Tag the release -* Build the release - * sudo python3 setup.py sdist bdist_wheel bdist_egg -* Upload the release - * sudo python3 -m twine upload dist/* -s --sign-with gpg2 - -KeepKey Bridge -============== -The KeepKey Bridge is a standalone TCP-to-webusb bridge the KeepKey. It runs a python-keepkey client -based process that allows a localhost-based process to communicate with the KeepKey wallet, thus -bypassing the need for a webusb connection from a browser based platform. - -The KeepKey Bridge is recommended only for advanced users who have problems connecting the KeepKey -on Windows. - -Running the KeepKey Bridge --------------------------- -Download the KeepKey Bridge installer ``kkbsetup.exe`` for Windows in the release package here: - -https://github.com/keepkey/python-keepkey/releases - -When running the KeepKey Bridge, a blank cmd window with the title "KepKey Bridge" will be visible. -To stop the bridge, simply close the cmd window. - -Build for Windows ------------------ -Requirements: - -- Windows 10 -- python3 -- waitress (python package) -- py2exe -- Inno Setup Compiler (optional, for creating Windows install exe) - -From a command prompt terminal window, run - ``python wbsetup.py py2exe -d windows/dist`` - -This will create a ``windows\dist`` folder with the Windows stand-alone executable file ``wait-serv.exe`` - -Inno Setup Compiler -------------------- -This tool builds and packages the executable for install on Windows. Build with the provided installer -script (modify version, etc., as needed) - - ``windows/KeepKeyBridge.iss`` - -This will produce an executable install app - - ``windows/Output/kkbsetup.exe`` - -KeepKey Authenticator -============== -Documentation can be found [here](authenticator/AUTHENTICATOR.rst). - diff --git a/authenticator/AUTHENTICATOR.rst b/authenticator/AUTHENTICATOR.md similarity index 100% rename from authenticator/AUTHENTICATOR.rst rename to authenticator/AUTHENTICATOR.md From f1de42a47df43a1129c6dbb5c62e174ed955311f Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Mon, 12 Dec 2022 10:30:42 -0700 Subject: [PATCH 09/34] doc formatting --- authenticator/AUTHENTICATOR.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/authenticator/AUTHENTICATOR.md b/authenticator/AUTHENTICATOR.md index 49a059d4..db6c15ba 100644 --- a/authenticator/AUTHENTICATOR.md +++ b/authenticator/AUTHENTICATOR.md @@ -9,11 +9,16 @@ products such as the Yubikey. Build for MacOS --------------- Make sure you have python-keepkey built and installed: - python python-keepkey/setup.py build install + + $ python python-keepkey/setup.py build install KeepKeyAuthenticator.app is built using py2app. The setup file was created from the command line: - py2applet --make-setup KeepKeyAuthenticator.py authResources/*.png + + $ py2applet --make-setup KeepKeyAuthenticator.py authResources/*.png + and then saved as setupAuth.py. NOTE: Creating the setup.py file in this manner will overwrite the existing setup.py file if one exists. + Create the app: - python setupAuth.py py2app + + $ python setupAuth.py py2app From 859a5684893e5f1c011eac79e79d13c8d03f58cc Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Wed, 14 Dec 2022 09:21:07 -0700 Subject: [PATCH 10/34] WIP - better UI flow --- authenticator/AUTHENTICATOR.md | 6 +- authenticator/AuthMain.py | 82 ++++++++++- authenticator/AuthMain.ui | 167 +++++++++++++++++++++- {keepkeylib => authenticator}/GUImixin.py | 9 +- authenticator/KeepKeyAuthenticator.py | 110 +++++++++++--- authenticator/RemAccDialog.ui | 160 ++++++++++++++++++++- authenticator/remaccdialog.py | 87 ++++++++++- 7 files changed, 585 insertions(+), 36 deletions(-) rename {keepkeylib => authenticator}/GUImixin.py (88%) diff --git a/authenticator/AUTHENTICATOR.md b/authenticator/AUTHENTICATOR.md index db6c15ba..8a410b95 100644 --- a/authenticator/AUTHENTICATOR.md +++ b/authenticator/AUTHENTICATOR.md @@ -1,10 +1,8 @@ KeepKey Authenticator ============== -The KeepKey Authenticator is a standalone app that enables the KeepKey device to perform as a -hardware off-line one-time passcode (OTP) generator for two factor authentication using the -TOPT algorithm. The KeepKey PIN and physical button gives it security features similar to other -products such as the Yubikey. +The KeepKey Authenticator is a standalone app intended to demo the authenticator feature. +It enables the KeepKey device to perform as a hardware off-line one-time passcode (OTP) generator for two factor authentication using the TOPT algorithm. The KeepKey PIN and physical button gives it security features similar to other products such as the Yubikey, but with better PIN security. Build for MacOS --------------- diff --git a/authenticator/AuthMain.py b/authenticator/AuthMain.py index 7f02eb5b..31477e03 100644 --- a/authenticator/AuthMain.py +++ b/authenticator/AuthMain.py @@ -14,7 +14,7 @@ class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(772, 628) + MainWindow.resize(772, 828) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap("kk-icon-gold.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) MainWindow.setWindowIcon(icon) @@ -207,6 +207,86 @@ def setupUi(self, MainWindow): self.OTPAcc_5.setAutoDefault(True) self.OTPAcc_5.setObjectName("OTPAcc_5") self.OTPButtonGroup.addButton(self.OTPAcc_5) + self.OTPAcc_6 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_6.setGeometry(QtCore.QRect(380, 520, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_6.setFont(font) + self.OTPAcc_6.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_6.setText("") + self.OTPAcc_6.setAutoDefault(True) + self.OTPAcc_6.setObjectName("OTPAcc_6") + self.OTPButtonGroup.addButton(self.OTPAcc_6) + self.OTPAcc_7 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_7.setGeometry(QtCore.QRect(380, 570, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_7.setFont(font) + self.OTPAcc_7.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_7.setText("") + self.OTPAcc_7.setAutoDefault(True) + self.OTPAcc_7.setObjectName("OTPAcc_7") + self.OTPButtonGroup.addButton(self.OTPAcc_7) + self.OTPAcc_8 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_8.setGeometry(QtCore.QRect(380, 620, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_8.setFont(font) + self.OTPAcc_8.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_8.setText("") + self.OTPAcc_8.setAutoDefault(True) + self.OTPAcc_8.setObjectName("OTPAcc_8") + self.OTPButtonGroup.addButton(self.OTPAcc_8) + self.OTPAcc_9 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_9.setGeometry(QtCore.QRect(380, 670, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_9.setFont(font) + self.OTPAcc_9.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_9.setText("") + self.OTPAcc_9.setAutoDefault(True) + self.OTPAcc_9.setObjectName("OTPAcc_9") + self.OTPButtonGroup.addButton(self.OTPAcc_9) + self.OTPAcc_10 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_10.setGeometry(QtCore.QRect(380, 720, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_10.setFont(font) + self.OTPAcc_10.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_10.setText("") + self.OTPAcc_10.setAutoDefault(True) + self.OTPAcc_10.setObjectName("OTPAcc_10") + self.OTPButtonGroup.addButton(self.OTPAcc_10) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 772, 24)) diff --git a/authenticator/AuthMain.ui b/authenticator/AuthMain.ui index 70f306ce..3e6794fc 100644 --- a/authenticator/AuthMain.ui +++ b/authenticator/AuthMain.ui @@ -7,7 +7,7 @@ 0 0 772 - 628 + 828 @@ -437,6 +437,171 @@ border-width: 2px; border-style: solid; border-color: black; color: rgb(0, 255, 0); +text-align:left; + + + + + + true + + + OTPButtonGroup + + + + + + 380 + 520 + 351 + 41 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + true + + + OTPButtonGroup + + + + + + 380 + 570 + 351 + 41 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + true + + + OTPButtonGroup + + + + + + 380 + 620 + 351 + 41 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + true + + + OTPButtonGroup + + + + + + 380 + 670 + 351 + 41 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + true + + + OTPButtonGroup + + + + + + 380 + 720 + 351 + 41 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); text-align:left; diff --git a/keepkeylib/GUImixin.py b/authenticator/GUImixin.py similarity index 88% rename from keepkeylib/GUImixin.py rename to authenticator/GUImixin.py index 543b3463..e7d18aeb 100644 --- a/keepkeylib/GUImixin.py +++ b/authenticator/GUImixin.py @@ -17,7 +17,7 @@ # # The script has been modified for KeepKey Device. -from . import messages_pb2 as proto +from keepkeylib import messages_pb2 as proto from KeepKeyAuthenticator import pingui_popup class GuiUIMixin(object): @@ -38,8 +38,11 @@ def callback_PinMatrixRequest(self, msg): # get matrix position and ack # pin = getPinDecoded() pin = pingui_popup() - print(pin) - return proto.PinMatrixAck(pin=pin) + print('pin is ', pin) + if pin == 'E': + return proto.Cancel() + else: + return proto.PinMatrixAck(pin=pin) # def callback_PassphraseRequest(self, msg): # pass diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index 1d26795a..86dffafb 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -37,9 +37,12 @@ from datetime import datetime import sys import semver +import usb1 as libusb from keepkeylib.transport_webusb import WebUsbTransport from keepkeylib.client import PinException, ProtocolMixin, BaseClient, CallException +from keepkeylib import types_pb2 as types + from AuthMain import Ui_MainWindow as Ui from PinUi import Ui_Dialog as PIN_Dialog @@ -50,7 +53,7 @@ def __init__(self): self.client = None def ConnectKK(self): - from keepkeylib.GUImixin import GuiUIMixin + from GUImixin import GuiUIMixin class KeepKeyClientAuth(ProtocolMixin, GuiUIMixin, BaseClient): pass @@ -92,6 +95,10 @@ def requires_firmware(self, ver_required): def getClient(self): return self.client + def closeClient(self): + self.client.close() + self.client=None + def error_popup(errmsg, infotext): # set up error/status message box popup msg = QMessageBox() @@ -117,6 +124,7 @@ def setupUi(self, Dialog): self.pushButton_9.clicked.connect(lambda: self.addPinClick('9')) self.pbUnlock.clicked.connect(lambda: self.returnPinAndClose(Dialog)) self.encodedPin = '' + self.unlockClicked = False def addPinClick(self, clickPosition): @@ -127,12 +135,17 @@ def addPinClick(self, clickPosition): def returnPinAndClose(self, Dialog): print("unlock pressed") + self.unlockClicked = True Dialog.close() def getEncodedPin(self): # return encoded PIN when unlock button pressed return self.encodedPin + def getUnlockClicked(self): + # return encoded PIN when unlock button pressed + return self.unlockClicked + def pingui_popup(): # set up PIN dialog PINDialog = QtWidgets.QDialog() @@ -140,15 +153,19 @@ def pingui_popup(): PIN_ui.setupUi(PINDialog) PINDialog.show() x = PINDialog.exec_() # show pin dialog - pin = PIN_ui.getEncodedPin() - print(pin) - return pin + if PIN_ui.getUnlockClicked() == True: + pin = PIN_ui.getEncodedPin() + print(pin) + return pin + else: + return 'E' # pin cancelled class RemAcc_Dialog(RemAcc_Dialog): def __init__(self, client, authOps): self.client = client self.authOps = authOps self.accounts = None + self.KKDisconnect = False return def setupUi(self, Dialog): @@ -166,7 +183,17 @@ def setupUi(self, Dialog): self.RemGetAccounts() def RemGetAccounts(self): - self.accounts = self.authOps.auth_accGet(self.client) + try: + self.accounts, fail = self.authOps.auth_accGet(self.client) + if fail in (-1, types.Failure_PinInvalid, + types.Failure_PinCancelled, types.Failure_PinExpected): + self.KKDisconnect = True + return + + except PinException as e: + error_popup("Invalid PIN", "") + return + # reset button list bList = self.RemoveAccButtonGroup.buttons() for b in bList: @@ -205,6 +232,9 @@ def RemoveAccount(self, id_): else: print("KeepKey not connected") + def getKKDisconnect(self): + return self.KKDisconnect + class Ui(Ui): def __init__(self): self.authOps = AuthClass() @@ -246,10 +276,29 @@ def KKConnect(self): "color: rgb(0, 255, 0)") self.ConnectKKButton.setText(_translate("MainWindow", "KeepKey\nConnected")) # get accounts if connected - #self.getAccounts(client) + self.getAccounts(client) + + def KKDisconnect(self): + self.accounts = None + _translate = QtCore.QCoreApplication.translate + self.clientOps.closeClient() + self.ConnectKKButton.setStyleSheet("border-radius: 10px;\n"\ + "background-color: rgb(255, 128, 4);\n"\ + "border-width: 2px;\n"\ + "border-style: solid;\n"\ + "border-color: black;\n"\ + "color: rgb(255, 255, 255)") + self.ConnectKKButton.setText(_translate("MainWindow", "Connect\nKeepKey")) + self.clearAccounts() def getAccounts(self, client): - self.accounts = self.authOps.auth_accGet(client) + # self.accounts = self.authOps.auth_accGet(client) + self.accounts, fail = self.authOps.auth_accGet(client) + if fail in (types.Failure_PinInvalid, + types.Failure_PinCancelled, types.Failure_PinExpected): + self.KKDisconnect() + return + print(self.accounts) # reset button list bList = self.OTPButtonGroup.buttons() @@ -267,6 +316,13 @@ def getAccounts(self, client): self.OTPButtonGroup.button(bNum).setText(acc[1]) self.otpButtonAcc.append((str(bNum), acc)) bNum += 1 + + def clearAccounts(self): + self.accounts = None + bList = self.OTPButtonGroup.buttons() + for b in bList: + b.setText('') + def QrScreencap(self): # grab fullscreen @@ -318,7 +374,15 @@ def OtpGen(self, id_): self.authOps.auth_otp(client, dom, acc) except PinException as e: error_popup("Invalid PIN", "") + except libusb.USBErrorNoDevice: + error_popup("No KeepKey found", "") + self.KKDisconnect() + except libusb.USBErrorTimeout: + error_popup("USB error", "Timeout") + except libusb.USBError as error: + error_popup("USB error", error) return + else: print("KeepKey not connected") @@ -328,8 +392,11 @@ def removeAcc(self): client = self.clientOps.getClient() RemAcc_ui = RemAcc_Dialog(client, self.authOps) RemAcc_ui.setupUi(RemAccDialog) + if RemAcc_ui.getKKDisconnect() == True: + self.KKDisconnect() + return RemAccDialog.show() - x = RemAccDialog.exec_() # show pin dialog + x = RemAccDialog.exec_() # show pin dialog self.getAccounts(client) def Test(self): @@ -373,11 +440,9 @@ def auth_otp(self, client, domain, account): T = Tslice.to_bytes(8, byteorder='big') retval = client.ping( msg = b'\x16' + bytes("generateOTPFrom:"+domain+":"+account+":", 'utf8') + - binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8') + binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8') ) - # print(retval) - # print("should be 007767") - + def auth_accGet(self, client): ctr=0 accounts = list() @@ -389,17 +454,30 @@ def auth_accGet(self, client): print(E.args[1]) if E.args[1] == 'Account not found': accounts.append([ctr, '']) - if E.args[1] == 'Invalid PIN': - error_popup(E.args[1], '') - break if E.args[1] == 'Slot request out of range': break if E.args[1] == 'Device not initialized': error_popup('Device not initialized', 'Initialize KeepKey prior to using authentication feature') break + if E.args[0] in (types.Failure_PinInvalid, types.Failure_PinCancelled, types.Failure_PinExpected): + error_popup(E.args[1], '') + return accounts, E.args[0] + + except libusb.USBErrorNoDevice: + error_popup("No KeepKey found", "") + return accounts, -1 + except libusb.USBErrorTimeout: + error_popup("USB error", "Timeout") + return accounts, -1 + except libusb.USBError as error: + error_popup("USB error", error) + return accounts, -1 + + + ctr+=1 - return accounts + return accounts, 0 def auth_accRem(self, client, domain, account): try: diff --git a/authenticator/RemAccDialog.ui b/authenticator/RemAccDialog.ui index 6436842c..11402a76 100644 --- a/authenticator/RemAccDialog.ui +++ b/authenticator/RemAccDialog.ui @@ -7,7 +7,7 @@ 0 0 465 - 388 + 540 @@ -109,7 +109,7 @@ text-align:left; 50 - 140 + 125 350 40 @@ -139,7 +139,7 @@ text-align:left; 50 - 200 + 170 350 40 @@ -169,7 +169,7 @@ text-align:left; 50 - 260 + 215 350 40 @@ -199,7 +199,157 @@ text-align:left; 50 - 320 + 260 + 350 + 40 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + RemoveAccButtonGroup + + + + + + 50 + 305 + 350 + 40 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + RemoveAccButtonGroup + + + + + + 50 + 350 + 350 + 40 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + RemoveAccButtonGroup + + + + + + 50 + 395 + 350 + 40 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + RemoveAccButtonGroup + + + + + + 50 + 440 + 350 + 40 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(0, 255, 0); +text-align:left; + + + + + + RemoveAccButtonGroup + + + + + + 50 + 485 350 40 diff --git a/authenticator/remaccdialog.py b/authenticator/remaccdialog.py index 964233dd..3f051bfc 100644 --- a/authenticator/remaccdialog.py +++ b/authenticator/remaccdialog.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'remaccdialog.ui' +# Form implementation generated from reading ui file 'RemAccDialog.ui' # # Created by: PyQt5 UI code generator 5.15.4 # @@ -14,7 +14,7 @@ class Ui_RemAccDialog(object): def setupUi(self, RemAccDialog): RemAccDialog.setObjectName("RemAccDialog") - RemAccDialog.resize(465, 388) + RemAccDialog.resize(465, 540) RemAccDialog.setStyleSheet("background-color: rgb(1, 0, 0);") RemAccDialog.setModal(True) self.RemAccButton_1 = QtWidgets.QPushButton(RemAccDialog) @@ -55,7 +55,7 @@ def setupUi(self, RemAccDialog): self.AccountList_2.setWordWrap(True) self.AccountList_2.setObjectName("AccountList_2") self.RemAccButton_2 = QtWidgets.QPushButton(RemAccDialog) - self.RemAccButton_2.setGeometry(QtCore.QRect(50, 140, 350, 40)) + self.RemAccButton_2.setGeometry(QtCore.QRect(50, 125, 350, 40)) font = QtGui.QFont() font.setPointSize(24) self.RemAccButton_2.setFont(font) @@ -70,7 +70,7 @@ def setupUi(self, RemAccDialog): self.RemAccButton_2.setObjectName("RemAccButton_2") self.RemoveAccButtonGroup.addButton(self.RemAccButton_2) self.RemAccButton_3 = QtWidgets.QPushButton(RemAccDialog) - self.RemAccButton_3.setGeometry(QtCore.QRect(50, 200, 350, 40)) + self.RemAccButton_3.setGeometry(QtCore.QRect(50, 170, 350, 40)) font = QtGui.QFont() font.setPointSize(24) self.RemAccButton_3.setFont(font) @@ -85,7 +85,7 @@ def setupUi(self, RemAccDialog): self.RemAccButton_3.setObjectName("RemAccButton_3") self.RemoveAccButtonGroup.addButton(self.RemAccButton_3) self.RemAccButton_4 = QtWidgets.QPushButton(RemAccDialog) - self.RemAccButton_4.setGeometry(QtCore.QRect(50, 260, 350, 40)) + self.RemAccButton_4.setGeometry(QtCore.QRect(50, 215, 350, 40)) font = QtGui.QFont() font.setPointSize(24) self.RemAccButton_4.setFont(font) @@ -100,7 +100,7 @@ def setupUi(self, RemAccDialog): self.RemAccButton_4.setObjectName("RemAccButton_4") self.RemoveAccButtonGroup.addButton(self.RemAccButton_4) self.RemAccButton_5 = QtWidgets.QPushButton(RemAccDialog) - self.RemAccButton_5.setGeometry(QtCore.QRect(50, 320, 350, 40)) + self.RemAccButton_5.setGeometry(QtCore.QRect(50, 260, 350, 40)) font = QtGui.QFont() font.setPointSize(24) self.RemAccButton_5.setFont(font) @@ -114,6 +114,81 @@ def setupUi(self, RemAccDialog): self.RemAccButton_5.setText("") self.RemAccButton_5.setObjectName("RemAccButton_5") self.RemoveAccButtonGroup.addButton(self.RemAccButton_5) + self.RemAccButton_6 = QtWidgets.QPushButton(RemAccDialog) + self.RemAccButton_6.setGeometry(QtCore.QRect(50, 305, 350, 40)) + font = QtGui.QFont() + font.setPointSize(24) + self.RemAccButton_6.setFont(font) + self.RemAccButton_6.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.RemAccButton_6.setText("") + self.RemAccButton_6.setObjectName("RemAccButton_6") + self.RemoveAccButtonGroup.addButton(self.RemAccButton_6) + self.RemAccButton_7 = QtWidgets.QPushButton(RemAccDialog) + self.RemAccButton_7.setGeometry(QtCore.QRect(50, 350, 350, 40)) + font = QtGui.QFont() + font.setPointSize(24) + self.RemAccButton_7.setFont(font) + self.RemAccButton_7.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.RemAccButton_7.setText("") + self.RemAccButton_7.setObjectName("RemAccButton_7") + self.RemoveAccButtonGroup.addButton(self.RemAccButton_7) + self.RemAccButton_8 = QtWidgets.QPushButton(RemAccDialog) + self.RemAccButton_8.setGeometry(QtCore.QRect(50, 395, 350, 40)) + font = QtGui.QFont() + font.setPointSize(24) + self.RemAccButton_8.setFont(font) + self.RemAccButton_8.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.RemAccButton_8.setText("") + self.RemAccButton_8.setObjectName("RemAccButton_8") + self.RemoveAccButtonGroup.addButton(self.RemAccButton_8) + self.RemAccButton_9 = QtWidgets.QPushButton(RemAccDialog) + self.RemAccButton_9.setGeometry(QtCore.QRect(50, 440, 350, 40)) + font = QtGui.QFont() + font.setPointSize(24) + self.RemAccButton_9.setFont(font) + self.RemAccButton_9.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.RemAccButton_9.setText("") + self.RemAccButton_9.setObjectName("RemAccButton_9") + self.RemoveAccButtonGroup.addButton(self.RemAccButton_9) + self.RemAccButton_10 = QtWidgets.QPushButton(RemAccDialog) + self.RemAccButton_10.setGeometry(QtCore.QRect(50, 485, 350, 40)) + font = QtGui.QFont() + font.setPointSize(24) + self.RemAccButton_10.setFont(font) + self.RemAccButton_10.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.RemAccButton_10.setText("") + self.RemAccButton_10.setObjectName("RemAccButton_10") + self.RemoveAccButtonGroup.addButton(self.RemAccButton_10) self.retranslateUi(RemAccDialog) QtCore.QMetaObject.connectSlotsByName(RemAccDialog) From 1641c9fcaaee5e39a16332c5de6b7f845163fe8c Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Fri, 16 Dec 2022 11:25:56 -0700 Subject: [PATCH 11/34] first release candidate --- authenticator/AddAccDialog.ui | 110 ++++++++ authenticator/KeepKeyAuthenticator.py | 384 +++++++++++++++++--------- authenticator/ManualAddAcc.ui | 230 +++++++++++++++ authenticator/addaccui.py | 76 +++++ authenticator/kkQRtest.png | Bin 52008 -> 51443 bytes authenticator/kkQRtest1.png | Bin 51594 -> 51730 bytes authenticator/kkQRtest2.png | Bin 51775 -> 51087 bytes authenticator/manualaddacc.py | 119 ++++++++ 8 files changed, 785 insertions(+), 134 deletions(-) create mode 100644 authenticator/AddAccDialog.ui create mode 100644 authenticator/ManualAddAcc.ui create mode 100644 authenticator/addaccui.py create mode 100644 authenticator/manualaddacc.py diff --git a/authenticator/AddAccDialog.ui b/authenticator/AddAccDialog.ui new file mode 100644 index 00000000..512a46a8 --- /dev/null +++ b/authenticator/AddAccDialog.ui @@ -0,0 +1,110 @@ + + + AddAccDialog + + + + 0 + 0 + 450 + 330 + + + + Add Account + + + background-color:rgb(40, 40, 40); + + + true + + + + + 125 + 30 + 200 + 70 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 1px; +border-style: solid; +border-color: rgb(0, 255, 0); +color: rgb(0, 255, 0); +text-align:center; + + + Scan QR + + + + + + 110 + 110 + 220 + 45 + + + + + 18 + + + + color: rgb(255, 255, 255); + + + + Make sure QR code is fully visible on main screen + + + Qt::RichText + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + + 125 + 210 + 200 + 70 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 1px; +border-style: solid; +border-color: rgb(0, 255, 0); +color: rgb(0, 255, 0); +text-align:center; + + + Enter Manually + + + + + + diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index 86dffafb..fa29e80e 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -47,7 +47,15 @@ from AuthMain import Ui_MainWindow as Ui from PinUi import Ui_Dialog as PIN_Dialog from remaccdialog import Ui_RemAccDialog as RemAcc_Dialog +from addaccui import Ui_AddAccDialog as AddAcc_Dialog +from manualaddacc import Ui_ManualAddAccDialog as ManAddAcc_Dialog +# for dev testing +_test = False + +authErrs = ('Invalid PIN', 'PIN Cancelled', 'PIN expected', 'Auth secret unknown error', + 'Account name missing or too long, or seed/message string missing', + 'Authenticator secret can\'t be decoded', 'Authenticator secret seed too large') class kkClient: def __init__(self): self.client = None @@ -129,12 +137,12 @@ def setupUi(self, Dialog): def addPinClick(self, clickPosition): self.encodedPin+=clickPosition - print("encoded pin ", self.encodedPin) + if _test: print("encoded pin ", self.encodedPin) self.lineEdit.setText(self.encodedPin) return def returnPinAndClose(self, Dialog): - print("unlock pressed") + if _test: print("unlock pressed") self.unlockClicked = True Dialog.close() @@ -155,7 +163,7 @@ def pingui_popup(): x = PINDialog.exec_() # show pin dialog if PIN_ui.getUnlockClicked() == True: pin = PIN_ui.getEncodedPin() - print(pin) + if _test: print(pin) return pin else: return 'E' # pin cancelled @@ -170,7 +178,7 @@ def __init__(self, client, authOps): def setupUi(self, Dialog): super(RemAcc_Dialog, self).setupUi(Dialog) - + self.Dialog = Dialog # Remove account button group # Add IDs to buttons to make for easy access bNum = 1 @@ -183,17 +191,12 @@ def setupUi(self, Dialog): self.RemGetAccounts() def RemGetAccounts(self): - try: - self.accounts, fail = self.authOps.auth_accGet(self.client) - if fail in (-1, types.Failure_PinInvalid, - types.Failure_PinCancelled, types.Failure_PinExpected): - self.KKDisconnect = True - return - except PinException as e: - error_popup("Invalid PIN", "") + self.accounts, fail = self.authOps.auth_accGet(self.client) + if fail in ('Invalid PIN', 'PIN Cancelled', 'PIN expected', 'usb err', 'Device not initialized'): + self.KKDisconnect = True return - + # reset button list bList = self.RemoveAccButtonGroup.buttons() for b in bList: @@ -220,18 +223,131 @@ def RemoveAccount(self, id_): for ba in self.RemAccButton: if ba[0] == str(id_): # button clicked is in button-account list dom, acc = ba[1][1].split(':') - print(dom+" "+acc) + if _test: print(dom+" "+acc) break - try: - self.authOps.auth_accRem(self.client, dom, acc) + err = self.authOps.auth_accRem(self.client, dom, acc) + if err == 'noerr': self.RemGetAccounts() + elif err == 'usb err': + self.KKDisconnect = True + self.Dialog.close() + return + else: + error_popup(err, '') + return + + else: + error_popup('Keepkey not connected', '') + + def getKKDisconnect(self): + return self.KKDisconnect + +class ManAddAcc_Dialog(ManAddAcc_Dialog): + def __init__(self, client, authOps): + self.client = client + self.authOps = authOps + self.KKDisconnect = False + self.secret = None + self.domain = None + self.account = None + return + + def setupUi(self, Dialog): + super(ManAddAcc_Dialog, self).setupUi(Dialog) + self.Dialog = Dialog + self.AddButton.clicked.connect(self.ManualAdd) + + def ManualAdd(self): + + self.secret = self.SecretlineEdit.text() + self.domain = self.IssuerlineEdit.text() + self.account = self.AccNamelineEdit.text() + self.Dialog.close() + + def getManAuth(self): + return self.domain, self.account, self.secret + + def getKKDisconnect(self): + return self.KKDisconnect + +class AddAcc_Dialog(AddAcc_Dialog): + def __init__(self, client, authOps): + self.client = client + self.authOps = authOps + self.accounts = None + self.KKDisconnect = False + return + + def setupUi(self, Dialog): + super(AddAcc_Dialog, self).setupUi(Dialog) + self.Dialog = Dialog + self.ScanQrButton.clicked.connect(self.QrScreencap) + self.EnterManuallyButton.clicked.connect(self.ManAddAcc) + + def QrScreencap(self): + # grab fullscreen + self.im = ImageGrab.grab(include_layered_windows=True) + #self.im.save("fullscreen.png") + + data = decode(self.im) + if (data == []): + error_popup("QR Code Error", "Could not read QR code") + return + data1 = str(data[0][0]).replace("b'",'').replace("'","") + type1 = str(data[0][1]) + if _test: print(data1) + if _test: print(type1) + secret = urlparse(data1).query.split('=')[1].split('&')[0] + domain = urlparse(data1).path.split('/')[1].split(':')[0] + account = urlparse(data1).path.split('/')[1].split(':')[1] + + self.KKAddAcc(self.client, secret, domain, account) + return + + def KKAddAcc(self, client, secret, domain, account): + if _test: print(secret) + if _test: print(domain) + if _test: print(account) + if (client != None): + err = self.authOps.auth_accAdd(client, secret, domain, account) + if err == 'noerr': + self.Dialog.close() + return + elif err in authErrs: + error_popup(err, '') + else: + error_popup(err, '') #usb error + self.KKDisconnect = True + self.Dialog.close() - except PinException as e: - error_popup("Invalid PIN", "") return else: - print("KeepKey not connected") - + error_popup('Keepkey not connected', '') + self.KKDisconnect = True + self.Dialog.close() + + def ManAddAcc(self): + if self.client == None: + return + + # set up manual add account dialog + ManAddAccDialog = QtWidgets.QDialog() + ManAddAcc_ui = ManAddAcc_Dialog(self.client, self.authOps) + ManAddAcc_ui.setupUi(ManAddAccDialog) + if ManAddAcc_ui.getKKDisconnect() == True: + self.KKDisconnect() + self.Dialog.close() + return + ManAddAccDialog.show() + x = ManAddAccDialog.exec_() # show dialog + domain, account, secret = ManAddAcc_ui.getManAuth() + if None in (domain, account, secret): + error_popup('Must enter values for domain, account and secret') + else: + self.KKAddAcc(self.client, secret, domain, account) + + self.Dialog.close() + def getKKDisconnect(self): return self.KKDisconnect @@ -245,9 +361,12 @@ def setupUi(self, MainWindow): self.clientOps = kkClient() self.ConnectKKButton.clicked.connect(self.KKConnect) - self.AddAccButton.clicked.connect(self.QrScreencap) + self.AddAccButton.clicked.connect(self.addAcc) self.RemoveAccButton.clicked.connect(self.removeAcc) - self.testButton.clicked.connect(self.Test) + if _test: + self.testButton.clicked.connect(self.Test) + else: + self.testButton.deleteLater() # OTP button group # Add IDs to buttons to make for easy access @@ -292,14 +411,12 @@ def KKDisconnect(self): self.clearAccounts() def getAccounts(self, client): - # self.accounts = self.authOps.auth_accGet(client) - self.accounts, fail = self.authOps.auth_accGet(client) - if fail in (types.Failure_PinInvalid, - types.Failure_PinCancelled, types.Failure_PinExpected): + self.accounts, err = self.authOps.auth_accGet(client) + if err in ('Invalid PIN', 'PIN Cancelled', 'PIN expected', 'usb err', 'Device not initialized'): self.KKDisconnect() return - print(self.accounts) + if _test: print(self.accounts) # reset button list bList = self.OTPButtonGroup.buttons() for b in bList: @@ -323,38 +440,21 @@ def clearAccounts(self): for b in bList: b.setText('') - - def QrScreencap(self): - # grab fullscreen - self.im = ImageGrab.grab(include_layered_windows=True) - #self.im.save("fullscreen.png") - - data = decode(self.im) - if (data == []): - error_popup("QR Code Error", "Could not read QR code") - return - data1 = str(data[0][0]).replace("b'",'').replace("'","") - type1 = str(data[0][1]) - print(data1) - print(type1) - secret = urlparse(data1).query.split('=')[1].split('&')[0] - domain = urlparse(data1).path.split('/')[1].split(':')[0] - account = urlparse(data1).path.split('/')[1].split(':')[1] - print(secret) - print(domain) - print(account) + def addAcc(self): client = self.clientOps.getClient() - if (client != None): - try: - self.authOps.auth_accAdd(client, secret, domain, account) - # re-establish otp list - self.getAccounts(client) + if client == None: + return - except PinException as e: - error_popup("Invalid PIN", "") + # set up add account dialog + AddAccDialog = QtWidgets.QDialog() + AddAcc_ui = AddAcc_Dialog(client, self.authOps) + AddAcc_ui.setupUi(AddAccDialog) + if AddAcc_ui.getKKDisconnect() == True: + self.KKDisconnect() return - else: - print("KeepKey not connected") + AddAccDialog.show() + x = AddAccDialog.exec_() # show pin dialog + self.getAccounts(client) def OtpGen(self, id_): # First check if this is an active button with an account @@ -368,28 +468,26 @@ def OtpGen(self, id_): for ba in self.otpButtonAcc: if ba[0] == str(id_): # button clicked is in button-account list dom, acc = ba[1][1].split(':') - print(dom+" "+acc) + if _test: print(dom+" "+acc) break - try: - self.authOps.auth_otp(client, dom, acc) - except PinException as e: - error_popup("Invalid PIN", "") - except libusb.USBErrorNoDevice: - error_popup("No KeepKey found", "") + err = self.authOps.auth_otp(client, dom, acc) + if err in authErrs: + error_popup(err, '') + elif err == 'usb err': + error_popup('usb error', '') self.KKDisconnect() - except libusb.USBErrorTimeout: - error_popup("USB error", "Timeout") - except libusb.USBError as error: - error_popup("USB error", error) - return + return else: - print("KeepKey not connected") + error_popup('Keepkey not connected', '') def removeAcc(self): - # set up remove account dialog - RemAccDialog = QtWidgets.QDialog() client = self.clientOps.getClient() + if client == None: + return + + # set up remove account dialog + RemAccDialog = QtWidgets.QDialog() RemAcc_ui = RemAcc_Dialog(client, self.authOps) RemAcc_ui.setupUi(RemAccDialog) if RemAcc_ui.getKKDisconnect() == True: @@ -401,7 +499,6 @@ def removeAcc(self): def Test(self): #test the keepkey function - print("test function") client = self.clientOps.getClient() if (client != None): try: @@ -411,23 +508,39 @@ def Test(self): error_popup("Invalid PIN", "") return else: - print("KeepKey not connected") + error_popup('Keepkey not connected', '') class AuthClass: - def auth_accAdd(self, client, secret, domain, account): - # try: - # ret = client.ping(msg = b'\x15' + bytes("initializeAuth:" + "python-test:" + "BASE32SECRET2345AB", 'utf8')) - # except PinException as e: - # print("Got exception in authenticator", e.args[0], " ", e.args[1]) - # exit() - + def sendMsg(self, client, msg): + err = '' + retval = None try: - retval = client.ping(msg = b'\x15' + bytes("initializeAuth:"+domain+":"+account+":"+secret, 'utf8')) + retval = client.ping(msg) except CallException as E: - if E.args[1] == 'Authenticator secret storage full': - error_popup(E.args[1], "Need to remove an account to add a new one to this KeepKey") - else: - error_popup(E.args[1], "") + err = E.args[1] + + except libusb.USBErrorNoDevice: + err = "No KeepKey found" + except libusb.USBErrorTimeout: + err = "USB error timeout" + except libusb.USBError as error: + err = "USB error %r" % error + + return retval, err + + + def auth_accAdd(self, client, secret, domain, account): + retval, err = self.sendMsg(client, msg = b'\x15' + bytes("initializeAuth:"+domain+":"+account+":"+secret, 'utf8')) + if err == 'Authenticator secret storage full': + error_popup(err, "Need to remove an account to add a new one to this KeepKey") + return err + elif err in authErrs: + error_popup(err, '') + return err + elif err != '': + error_popup(err, '') + return 'usb err' + return 'noerr' def auth_otp(self, client, domain, account): interval = 30 # 30 second interval @@ -436,72 +549,75 @@ def auth_otp(self, client, domain, account): T0 = datetime.now().timestamp() Tslice = int(T0/interval) Tremain = int((int(T0) - Tslice*30)) - print(Tremain) + if _test: print(Tremain) T = Tslice.to_bytes(8, byteorder='big') - retval = client.ping( + retval, err = self.sendMsg(client, msg = b'\x16' + bytes("generateOTPFrom:"+domain+":"+account+":", 'utf8') + binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8') ) - + if err in authErrs: + error_popup(err, '') + return err + elif err != '': + error_popup(err, '') + return 'usb err' + return 'noerr' + def auth_accGet(self, client): ctr=0 accounts = list() while True: - try: - retval = client.ping(msg = b'\x17' + bytes("getAccount:"+str(ctr), 'utf8')) + retval, err = self.sendMsg(client, msg = b'\x17' + bytes("getAccount:"+str(ctr), 'utf8')) + if err == '': accounts.append([ctr, retval]) - except CallException as E: - print(E.args[1]) - if E.args[1] == 'Account not found': - accounts.append([ctr, '']) - if E.args[1] == 'Slot request out of range': - break - if E.args[1] == 'Device not initialized': - error_popup('Device not initialized', 'Initialize KeepKey prior to using authentication feature') - break - if E.args[0] in (types.Failure_PinInvalid, types.Failure_PinCancelled, types.Failure_PinExpected): - error_popup(E.args[1], '') - return accounts, E.args[0] - - except libusb.USBErrorNoDevice: - error_popup("No KeepKey found", "") - return accounts, -1 - except libusb.USBErrorTimeout: - error_popup("USB error", "Timeout") - return accounts, -1 - except libusb.USBError as error: - error_popup("USB error", error) - return accounts, -1 - - + elif err == 'Account not found': + accounts.append([ctr, '']) + elif err == 'Slot request out of range': + break + elif err == 'Device not initialized': + error_popup(err, 'Initialize KeepKey prior to using authentication feature') + break + elif err in authErrs: + error_popup(err, '') + return accounts, err + else: + error_popup(err, '') + return accounts, 'usb err' ctr+=1 - return accounts, 0 + return accounts, 'noerr' def auth_accRem(self, client, domain, account): - try: - client.ping(msg = b'\x18' + bytes("removeAccount:"+domain+":"+account, 'utf8')) - except CallException as E: + retval, err = self.sendMsg(client, msg = b'\x18' + bytes("removeAccount:"+domain+":"+account, 'utf8')) + if err in authErrs: error_popup(E.args[1], domain+":"+account) - return + elif err != '': + error_popup(err, '') + return 'usb err' + + return 'noerr' def auth_test(self, client): - try: - retval = client.ping(msg = b'\x15' + bytes("initializeAuth:"+"GitHub"+":"+"mooneytestgithub"+":"+"ZKLHM3W3XAHG4CBN", 'utf8')) - retval = client.ping(msg = b'\x15' + bytes("initializeAuth:"+"Shapeshift"+":"+"markrypto"+":"+"BASE32SECRET2345AC", 'utf8')) - retval = client.ping(msg = b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto2"+":"+"BASE32SECRET2345AD", 'utf8')) - except CallException as E: - if E.args[1] == 'Authenticator secret storage full': - error_popup(E.args[1], "Need to remove an account to add a new one to this KeepKey") - else: - error_popup(E.args[1], "") - - - # ret = client.ping(msg = b'\x15' + bytes("initializeAuth:" + "python-test:" + "BASE32SECRET2345AB", 'utf8')) + # otpauth://totp/KeepKey:markrypto?secret=ZKLHM3W3XAHG4CBN&issuer=kk + for msg in ( + b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto"+":"+"ZKLHM3W3XAHG4CBN", 'utf8'), + b'\x15' + bytes("initializeAuth:"+"Shapeshift"+":"+"markrypto"+":"+"BASE32SECRET2345AB", 'utf8'), + b'\x15' + bytes("initializeAuth:"+"Shapeshift"+":"+"markrypto"+":"+"BASE32SECRET2345AB", 'utf8') + ): + retval, err = self.sendMsg(client, msg) + if err == 'Authenticator secret storage full': + error_popup(err, "Need to remove an account to add a new one to this KeepKey") + return err + elif err in authErrs: + error_popup(err, '') + return err + elif err != '': + error_popup(err, '') + return 'usb err' + return 'noerr' - # interval = 30 # 30 second interval # #T0 = 1535317397 # T0 = 1536262427 @@ -510,8 +626,8 @@ def auth_test(self, client): # retval = client.ping( # msg = b'\x16' + bytes("generateOTPFrom:" + "python-test:", 'utf8') + binascii.hexlify(bytearray(T)) # ) - # print(retval) - # print("should be 007767") + # if _test: print(retval) + # if _test: print("should be 007767") def main(): app = QtWidgets.QApplication(sys.argv) diff --git a/authenticator/ManualAddAcc.ui b/authenticator/ManualAddAcc.ui new file mode 100644 index 00000000..d690b47e --- /dev/null +++ b/authenticator/ManualAddAcc.ui @@ -0,0 +1,230 @@ + + + ManualAddAccDialog + + + + 0 + 0 + 498 + 464 + + + + Add Account + + + background-color:rgb(40, 40, 40); + + + true + + + + + 50 + 10 + 401 + 61 + + + + + 18 + + + + color: rgb(255, 255, 255); + + + + <html><head/><body><p>Add Account</p><p>Use manual entry if there is no QR code available.</p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + 50 + 130 + 411 + 25 + + + + + 20 + + + + color: rgb(0, 255, 0); + + + + + + 50 + 100 + 71 + 21 + + + + + 18 + + + + color: rgb(255, 255, 255); + + + + <html><head/><body><p>Issuer</p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + 50 + 175 + 121 + 21 + + + + + 18 + + + + color: rgb(255, 255, 255); + + + + <html><head/><body><p>Account name</p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + 50 + 245 + 91 + 21 + + + + + 18 + + + + color: rgb(255, 255, 255); + + + + <html><head/><body><p>Secret key</p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + 50 + 330 + 111 + 70 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 1px; +border-style: solid; +border-color: rgb(0, 255, 0); +color: rgb(0, 255, 0); +text-align:center; + + + ADD + + + + + + 50 + 205 + 411 + 25 + + + + + 20 + + + + color: rgb(0, 255, 0); + + + + + + 50 + 275 + 411 + 25 + + + + + 20 + + + + color: rgb(0, 255, 0); + + + + + + diff --git a/authenticator/addaccui.py b/authenticator/addaccui.py new file mode 100644 index 00000000..3a80b872 --- /dev/null +++ b/authenticator/addaccui.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'AddAccDialog.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_AddAccDialog(object): + def setupUi(self, AddAccDialog): + AddAccDialog.setObjectName("AddAccDialog") + AddAccDialog.resize(450, 330) + AddAccDialog.setStyleSheet("background-color:rgb(40, 40, 40);") + AddAccDialog.setModal(True) + self.ScanQrButton = QtWidgets.QPushButton(AddAccDialog) + self.ScanQrButton.setGeometry(QtCore.QRect(125, 30, 200, 70)) + font = QtGui.QFont() + font.setPointSize(24) + self.ScanQrButton.setFont(font) + self.ScanQrButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 1px;\n" +"border-style: solid;\n" +"border-color: rgb(0, 255, 0);\n" +"color: rgb(0, 255, 0);\n" +"text-align:center;") + self.ScanQrButton.setObjectName("ScanQrButton") + self.QR_instructions = QtWidgets.QLabel(AddAccDialog) + self.QR_instructions.setGeometry(QtCore.QRect(110, 110, 220, 45)) + font = QtGui.QFont() + font.setPointSize(18) + self.QR_instructions.setFont(font) + self.QR_instructions.setStyleSheet("color: rgb(255, 255, 255);\n" +"") + self.QR_instructions.setTextFormat(QtCore.Qt.RichText) + self.QR_instructions.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop) + self.QR_instructions.setWordWrap(True) + self.QR_instructions.setObjectName("QR_instructions") + self.EnterManuallyButton = QtWidgets.QPushButton(AddAccDialog) + self.EnterManuallyButton.setGeometry(QtCore.QRect(125, 210, 200, 70)) + font = QtGui.QFont() + font.setPointSize(24) + self.EnterManuallyButton.setFont(font) + self.EnterManuallyButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 1px;\n" +"border-style: solid;\n" +"border-color: rgb(0, 255, 0);\n" +"color: rgb(0, 255, 0);\n" +"text-align:center;") + self.EnterManuallyButton.setObjectName("EnterManuallyButton") + + self.retranslateUi(AddAccDialog) + QtCore.QMetaObject.connectSlotsByName(AddAccDialog) + + def retranslateUi(self, AddAccDialog): + _translate = QtCore.QCoreApplication.translate + AddAccDialog.setWindowTitle(_translate("AddAccDialog", "Add Account")) + self.ScanQrButton.setText(_translate("AddAccDialog", "Scan QR")) + self.QR_instructions.setText(_translate("AddAccDialog", "Make sure QR code is fully visible on main screen")) + self.EnterManuallyButton.setText(_translate("AddAccDialog", "Enter Manually")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + AddAccDialog = QtWidgets.QDialog() + ui = Ui_AddAccDialog() + ui.setupUi(AddAccDialog) + AddAccDialog.show() + sys.exit(app.exec_()) diff --git a/authenticator/kkQRtest.png b/authenticator/kkQRtest.png index 4930a29315f0fe9dc899271a5794a8f9e6f42e49..80c90f2b7c81ee6439cf863559b008c51742bcdf 100644 GIT binary patch literal 51443 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%0AWwS~&{$b2K(#mCnKV($9W?V}l*izJAQ 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 YjyRm5^R`HY1OD5iW3W4Gm)-gQ50JZFDF6Tf literal 52008 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 diff --git a/authenticator/kkQRtest2.png b/authenticator/kkQRtest2.png index 226742b12965fcf0c328a3c49c7c84560cf25501..ca4d0cba3f4b48d25dde7c53ec0521e70cfdfa13 100644 GIT binary patch literal 51087 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

Add Account

Use manual entry if there is no QR code available.

")) + self.IssuerLabel.setText(_translate("ManualAddAccDialog", "

Issuer

")) + self.AccNameLabel.setText(_translate("ManualAddAccDialog", "

Account name

")) + self.SecretKeyLabel.setText(_translate("ManualAddAccDialog", "

Secret key

")) + self.AddButton.setText(_translate("ManualAddAccDialog", "ADD")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + ManualAddAccDialog = QtWidgets.QDialog() + ui = Ui_ManualAddAccDialog() + ui.setupUi(ManualAddAccDialog) + ManualAddAccDialog.show() + sys.exit(app.exec_()) From 5a74331cf6e02c87cfe65651b08adc4146afb51b Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Mon, 19 Dec 2022 16:28:09 -0700 Subject: [PATCH 12/34] now uses pyqt6, added auth tests --- authenticator/AUTHENTICATOR.md | 2 +- authenticator/AuthMain.py | 24 +++-- authenticator/KeepKeyAuthenticator.py | 36 ++++---- authenticator/StatErr.py | 43 --------- authenticator/StatErr.ui | 68 -------------- .../{addaccui.py => addaccdialog.py} | 14 ++- authenticator/manualaddacc.py | 26 +++--- authenticator/{PinUi.py => pindialog.py} | 14 ++- authenticator/remaccdialog.py | 16 ++-- tests/test_msg_authfeature.py | 88 +++++++++++++++++++ 10 files changed, 148 insertions(+), 183 deletions(-) delete mode 100644 authenticator/StatErr.py delete mode 100644 authenticator/StatErr.ui rename authenticator/{addaccui.py => addaccdialog.py} (89%) rename authenticator/{PinUi.py => pindialog.py} (96%) create mode 100644 tests/test_msg_authfeature.py diff --git a/authenticator/AUTHENTICATOR.md b/authenticator/AUTHENTICATOR.md index 8a410b95..4944e977 100644 --- a/authenticator/AUTHENTICATOR.md +++ b/authenticator/AUTHENTICATOR.md @@ -19,4 +19,4 @@ the existing setup.py file if one exists. Create the app: - $ python setupAuth.py py2app + $ python setup.py py2app diff --git a/authenticator/AuthMain.py b/authenticator/AuthMain.py index 31477e03..0b289a93 100644 --- a/authenticator/AuthMain.py +++ b/authenticator/AuthMain.py @@ -1,14 +1,12 @@ -# -*- coding: utf-8 -*- - # Form implementation generated from reading ui file 'AuthMain.ui' # -# Created by: PyQt5 UI code generator 5.15.4 +# Created by: PyQt6 UI code generator 6.1.0 # -# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): @@ -16,7 +14,7 @@ def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(772, 828) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap("kk-icon-gold.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon.addPixmap(QtGui.QPixmap("kk-icon-gold.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) MainWindow.setWindowIcon(icon) MainWindow.setStyleSheet("background-color: rgb(1, 0, 0);") self.centralwidget = QtWidgets.QWidget(MainWindow) @@ -32,7 +30,7 @@ def setupUi(self, MainWindow): font.setPointSize(32) self.TitleDesc.setFont(font) self.TitleDesc.setStyleSheet("color: rgb(255, 255, 255);") - self.TitleDesc.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.TitleDesc.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) self.TitleDesc.setWordWrap(True) self.TitleDesc.setObjectName("TitleDesc") self.Author = QtWidgets.QLabel(self.centralwidget) @@ -45,9 +43,9 @@ def setupUi(self, MainWindow): self.line = QtWidgets.QFrame(self.centralwidget) self.line.setGeometry(QtCore.QRect(30, 200, 720, 16)) self.line.setStyleSheet("color: rgb(255, 255, 255)") - self.line.setFrameShadow(QtWidgets.QFrame.Plain) + self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) self.line.setLineWidth(1) - self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine) self.line.setObjectName("line") self.ConnectKKButton = QtWidgets.QPushButton(self.centralwidget) self.ConnectKKButton.setGeometry(QtCore.QRect(610, 120, 140, 70)) @@ -99,7 +97,7 @@ def setupUi(self, MainWindow): self.AccountList.setFont(font) self.AccountList.setStyleSheet("color: rgb(255, 255, 255);\n" "") - self.AccountList.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.AccountList.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) self.AccountList.setObjectName("AccountList") self.RemoveAccButton = QtWidgets.QPushButton(self.centralwidget) self.RemoveAccButton.setGeometry(QtCore.QRect(30, 300, 200, 50)) @@ -139,8 +137,8 @@ def setupUi(self, MainWindow): self.AccountList_2.setFont(font) self.AccountList_2.setStyleSheet("color: rgb(255, 255, 255);\n" "") - self.AccountList_2.setTextFormat(QtCore.Qt.RichText) - self.AccountList_2.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop) + self.AccountList_2.setTextFormat(QtCore.Qt.TextFormat.RichText) + self.AccountList_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) self.AccountList_2.setWordWrap(True) self.AccountList_2.setObjectName("AccountList_2") self.OTPAcc_2 = QtWidgets.QPushButton(self.centralwidget) @@ -320,4 +318,4 @@ def retranslateUi(self, MainWindow): ui = Ui_MainWindow() ui.setupUi(MainWindow) MainWindow.show() - sys.exit(app.exec_()) + sys.exit(app.exec()) diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index fa29e80e..60fdd587 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -17,15 +17,14 @@ # # The script has been modified for KeepKey Device. -from PyQt5 import QtWidgets, uic -from PyQt5.QtWidgets import QFileDialog, QMenuBar,QAction,QMessageBox,QPushButton -from PyQt5.QtGui import * -from PyQt5 import QtCore +from PyQt6 import QtWidgets, uic +from PyQt6.QtWidgets import QFileDialog, QMenuBar,QMessageBox,QPushButton +from PyQt6.QtGui import * +from PyQt6 import QtCore import sys import os from PIL import Image from pyzbar.pyzbar import decode -import qdarkgraystyle import qrcode import time from urllib.parse import urlparse @@ -44,14 +43,14 @@ from keepkeylib import types_pb2 as types -from AuthMain import Ui_MainWindow as Ui -from PinUi import Ui_Dialog as PIN_Dialog +from authmain import Ui_MainWindow as Ui +from pindialog import Ui_Dialog as PIN_Dialog from remaccdialog import Ui_RemAccDialog as RemAcc_Dialog -from addaccui import Ui_AddAccDialog as AddAcc_Dialog +from addaccdialog import Ui_AddAccDialog as AddAcc_Dialog from manualaddacc import Ui_ManualAddAccDialog as ManAddAcc_Dialog # for dev testing -_test = False +_test = True authErrs = ('Invalid PIN', 'PIN Cancelled', 'PIN expected', 'Auth secret unknown error', 'Account name missing or too long, or seed/message string missing', @@ -111,11 +110,9 @@ def error_popup(errmsg, infotext): # set up error/status message box popup msg = QMessageBox() msg.setWindowTitle("ERROR") - msg.setIcon(QMessageBox.Critical) - msg.setDefaultButton(QMessageBox.Ok) msg.setText(errmsg) msg.setInformativeText(infotext) - x = msg.exec_() # show message box + x = msg.exec() # show message box class PIN_Dialog(PIN_Dialog): def setupUi(self, Dialog): @@ -160,7 +157,7 @@ def pingui_popup(): PIN_ui = PIN_Dialog() PIN_ui.setupUi(PINDialog) PINDialog.show() - x = PINDialog.exec_() # show pin dialog + x = PINDialog.exec() # show pin dialog if PIN_ui.getUnlockClicked() == True: pin = PIN_ui.getEncodedPin() if _test: print(pin) @@ -339,10 +336,10 @@ def ManAddAcc(self): self.Dialog.close() return ManAddAccDialog.show() - x = ManAddAccDialog.exec_() # show dialog + x = ManAddAccDialog.exec() # show dialog domain, account, secret = ManAddAcc_ui.getManAuth() if None in (domain, account, secret): - error_popup('Must enter values for domain, account and secret') + error_popup('Must enter values for domain, account and secret', '') else: self.KKAddAcc(self.client, secret, domain, account) @@ -453,7 +450,7 @@ def addAcc(self): self.KKDisconnect() return AddAccDialog.show() - x = AddAccDialog.exec_() # show pin dialog + x = AddAccDialog.exec() # show pin dialog self.getAccounts(client) def OtpGen(self, id_): @@ -494,7 +491,7 @@ def removeAcc(self): self.KKDisconnect() return RemAccDialog.show() - x = RemAccDialog.exec_() # show pin dialog + x = RemAccDialog.exec() # show pin dialog self.getAccounts(client) def Test(self): @@ -530,6 +527,7 @@ def sendMsg(self, client, msg): def auth_accAdd(self, client, secret, domain, account): + print(b'\x15' + bytes("initializeAuth:"+domain+":"+account+":"+secret, 'utf8')) retval, err = self.sendMsg(client, msg = b'\x15' + bytes("initializeAuth:"+domain+":"+account+":"+secret, 'utf8')) if err == 'Authenticator secret storage full': error_popup(err, "Need to remove an account to add a new one to this KeepKey") @@ -603,7 +601,7 @@ def auth_test(self, client): for msg in ( b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto"+":"+"ZKLHM3W3XAHG4CBN", 'utf8'), b'\x15' + bytes("initializeAuth:"+"Shapeshift"+":"+"markrypto"+":"+"BASE32SECRET2345AB", 'utf8'), - b'\x15' + bytes("initializeAuth:"+"Shapeshift"+":"+"markrypto"+":"+"BASE32SECRET2345AB", 'utf8') + b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto2"+":"+"BASE32SECRET2345AD", 'utf8') ): retval, err = self.sendMsg(client, msg) if err == 'Authenticator secret storage full': @@ -634,7 +632,7 @@ def main(): MainWindow = QtWidgets.QMainWindow() ui = Ui() ui.setupUi(MainWindow) - sys.exit(app.exec_()) + sys.exit(app.exec()) if __name__ == '__main__': main() diff --git a/authenticator/StatErr.py b/authenticator/StatErr.py deleted file mode 100644 index ec92d008..00000000 --- a/authenticator/StatErr.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'StatErr.ui' -# -# Created by: PyQt5 UI code generator 5.15.4 -# -# WARNING: Any manual changes made to this file will be lost when pyuic5 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt5 import QtCore, QtGui, QtWidgets - - -class Ui_Dialog(object): - def setupUi(self, Dialog): - Dialog.setObjectName("Dialog") - Dialog.resize(406, 137) - self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) - self.buttonBox.setGeometry(QtCore.QRect(290, 20, 81, 41)) - self.buttonBox.setOrientation(QtCore.Qt.Vertical) - self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok) - self.buttonBox.setObjectName("buttonBox") - - self.retranslateUi(Dialog) - self.buttonBox.accepted.connect(Dialog.accept) - self.buttonBox.rejected.connect(Dialog.reject) - QtCore.QMetaObject.connectSlotsByName(Dialog) - - def retranslateUi(self, Dialog): - _translate = QtCore.QCoreApplication.translate - Dialog.setWindowTitle(_translate("Dialog", "Dialog")) - - def dialogMsg(self, Dialog, errstat): - Dialog.setText(errstat) - -# if __name__ == "__main__": -# import sys -# app = QtWidgets.QApplication(sys.argv) -# Dialog = QtWidgets.QDialog() -# ui = Ui_Dialog() -# ui.setupUi(Dialog) -# Dialog.show() -# sys.exit(app.exec_()) diff --git a/authenticator/StatErr.ui b/authenticator/StatErr.ui deleted file mode 100644 index 3e25f289..00000000 --- a/authenticator/StatErr.ui +++ /dev/null @@ -1,68 +0,0 @@ - - - Dialog - - - - 0 - 0 - 406 - 137 - - - - Dialog - - - - - 290 - 20 - 81 - 41 - - - - Qt::Vertical - - - QDialogButtonBox::Ok - - - - - - - buttonBox - accepted() - Dialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - Dialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/authenticator/addaccui.py b/authenticator/addaccdialog.py similarity index 89% rename from authenticator/addaccui.py rename to authenticator/addaccdialog.py index 3a80b872..0d79d588 100644 --- a/authenticator/addaccui.py +++ b/authenticator/addaccdialog.py @@ -1,14 +1,12 @@ -# -*- coding: utf-8 -*- - # Form implementation generated from reading ui file 'AddAccDialog.ui' # -# Created by: PyQt5 UI code generator 5.15.4 +# Created by: PyQt6 UI code generator 6.1.0 # -# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class Ui_AddAccDialog(object): @@ -37,8 +35,8 @@ def setupUi(self, AddAccDialog): self.QR_instructions.setFont(font) self.QR_instructions.setStyleSheet("color: rgb(255, 255, 255);\n" "") - self.QR_instructions.setTextFormat(QtCore.Qt.RichText) - self.QR_instructions.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop) + self.QR_instructions.setTextFormat(QtCore.Qt.TextFormat.RichText) + self.QR_instructions.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) self.QR_instructions.setWordWrap(True) self.QR_instructions.setObjectName("QR_instructions") self.EnterManuallyButton = QtWidgets.QPushButton(AddAccDialog) @@ -73,4 +71,4 @@ def retranslateUi(self, AddAccDialog): ui = Ui_AddAccDialog() ui.setupUi(AddAccDialog) AddAccDialog.show() - sys.exit(app.exec_()) + sys.exit(app.exec()) diff --git a/authenticator/manualaddacc.py b/authenticator/manualaddacc.py index 60ce1a70..c6fc964f 100644 --- a/authenticator/manualaddacc.py +++ b/authenticator/manualaddacc.py @@ -1,14 +1,12 @@ -# -*- coding: utf-8 -*- - # Form implementation generated from reading ui file 'ManualAddAcc.ui' # -# Created by: PyQt5 UI code generator 5.15.4 +# Created by: PyQt6 UI code generator 6.1.0 # -# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class Ui_ManualAddAccDialog(object): @@ -24,8 +22,8 @@ def setupUi(self, ManualAddAccDialog): self.QR_instructions.setFont(font) self.QR_instructions.setStyleSheet("color: rgb(255, 255, 255);\n" "") - self.QR_instructions.setTextFormat(QtCore.Qt.RichText) - self.QR_instructions.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.QR_instructions.setTextFormat(QtCore.Qt.TextFormat.RichText) + self.QR_instructions.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) self.QR_instructions.setWordWrap(True) self.QR_instructions.setObjectName("QR_instructions") self.IssuerlineEdit = QtWidgets.QLineEdit(ManualAddAccDialog) @@ -42,8 +40,8 @@ def setupUi(self, ManualAddAccDialog): self.IssuerLabel.setFont(font) self.IssuerLabel.setStyleSheet("color: rgb(255, 255, 255);\n" "") - self.IssuerLabel.setTextFormat(QtCore.Qt.RichText) - self.IssuerLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.IssuerLabel.setTextFormat(QtCore.Qt.TextFormat.RichText) + self.IssuerLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) self.IssuerLabel.setWordWrap(True) self.IssuerLabel.setObjectName("IssuerLabel") self.AccNameLabel = QtWidgets.QLabel(ManualAddAccDialog) @@ -53,8 +51,8 @@ def setupUi(self, ManualAddAccDialog): self.AccNameLabel.setFont(font) self.AccNameLabel.setStyleSheet("color: rgb(255, 255, 255);\n" "") - self.AccNameLabel.setTextFormat(QtCore.Qt.RichText) - self.AccNameLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.AccNameLabel.setTextFormat(QtCore.Qt.TextFormat.RichText) + self.AccNameLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) self.AccNameLabel.setWordWrap(True) self.AccNameLabel.setObjectName("AccNameLabel") self.SecretKeyLabel = QtWidgets.QLabel(ManualAddAccDialog) @@ -64,8 +62,8 @@ def setupUi(self, ManualAddAccDialog): self.SecretKeyLabel.setFont(font) self.SecretKeyLabel.setStyleSheet("color: rgb(255, 255, 255);\n" "") - self.SecretKeyLabel.setTextFormat(QtCore.Qt.RichText) - self.SecretKeyLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.SecretKeyLabel.setTextFormat(QtCore.Qt.TextFormat.RichText) + self.SecretKeyLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) self.SecretKeyLabel.setWordWrap(True) self.SecretKeyLabel.setObjectName("SecretKeyLabel") self.AddButton = QtWidgets.QPushButton(ManualAddAccDialog) @@ -116,4 +114,4 @@ def retranslateUi(self, ManualAddAccDialog): ui = Ui_ManualAddAccDialog() ui.setupUi(ManualAddAccDialog) ManualAddAccDialog.show() - sys.exit(app.exec_()) + sys.exit(app.exec()) diff --git a/authenticator/PinUi.py b/authenticator/pindialog.py similarity index 96% rename from authenticator/PinUi.py rename to authenticator/pindialog.py index 89818477..41de6486 100644 --- a/authenticator/PinUi.py +++ b/authenticator/pindialog.py @@ -1,14 +1,12 @@ -# -*- coding: utf-8 -*- - # Form implementation generated from reading ui file 'PinUi.ui' # -# Created by: PyQt5 UI code generator 5.15.4 +# Created by: PyQt6 UI code generator 6.1.0 # -# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): @@ -26,7 +24,7 @@ def setupUi(self, Dialog): "border-color: black;") self.pushButton_7.setText("") icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap("circle-32.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon.addPixmap(QtGui.QPixmap("circle-32.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) self.pushButton_7.setIcon(icon) self.pushButton_7.setIconSize(QtCore.QSize(32, 32)) self.pushButton_7.setObjectName("pushButton_7") @@ -140,7 +138,7 @@ def setupUi(self, Dialog): "border-style: solid;\n" "border-color: rgb(103, 103, 103);") self.lineEdit.setMaxLength(10) - self.lineEdit.setEchoMode(QtWidgets.QLineEdit.Password) + self.lineEdit.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password) self.lineEdit.setObjectName("lineEdit") self.retranslateUi(Dialog) @@ -160,4 +158,4 @@ def retranslateUi(self, Dialog): ui = Ui_Dialog() ui.setupUi(Dialog) Dialog.show() - sys.exit(app.exec_()) + sys.exit(app.exec()) diff --git a/authenticator/remaccdialog.py b/authenticator/remaccdialog.py index 3f051bfc..4c5ee9a1 100644 --- a/authenticator/remaccdialog.py +++ b/authenticator/remaccdialog.py @@ -1,14 +1,12 @@ -# -*- coding: utf-8 -*- - # Form implementation generated from reading ui file 'RemAccDialog.ui' # -# Created by: PyQt5 UI code generator 5.15.4 +# Created by: PyQt6 UI code generator 6.1.0 # -# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class Ui_RemAccDialog(object): @@ -41,7 +39,7 @@ def setupUi(self, RemAccDialog): self.AccountList.setFont(font) self.AccountList.setStyleSheet("color: rgb(255, 255, 255);\n" "") - self.AccountList.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.AccountList.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) self.AccountList.setObjectName("AccountList") self.AccountList_2 = QtWidgets.QLabel(RemAccDialog) self.AccountList_2.setGeometry(QtCore.QRect(195, 20, 220, 45)) @@ -50,8 +48,8 @@ def setupUi(self, RemAccDialog): self.AccountList_2.setFont(font) self.AccountList_2.setStyleSheet("color: rgb(255, 255, 255);\n" "") - self.AccountList_2.setTextFormat(QtCore.Qt.RichText) - self.AccountList_2.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop) + self.AccountList_2.setTextFormat(QtCore.Qt.TextFormat.RichText) + self.AccountList_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) self.AccountList_2.setWordWrap(True) self.AccountList_2.setObjectName("AccountList_2") self.RemAccButton_2 = QtWidgets.QPushButton(RemAccDialog) @@ -207,4 +205,4 @@ def retranslateUi(self, RemAccDialog): ui = Ui_RemAccDialog() ui.setupUi(RemAccDialog) RemAccDialog.show() - sys.exit(app.exec_()) + sys.exit(app.exec()) diff --git a/tests/test_msg_authfeature.py b/tests/test_msg_authfeature.py new file mode 100644 index 00000000..062cb311 --- /dev/null +++ b/tests/test_msg_authfeature.py @@ -0,0 +1,88 @@ +# Copyright (C) 2022 markrypto +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +import unittest +import common +import binascii +import usb1 as libusb + +from keepkeylib import messages_pb2 as proto +from keepkeylib import types_pb2 as proto_types +from keepkeylib.client import PinException, CallException + + +class TestAuthFeature(common.KeepKeyTest): + def sendMsg(self, client, msg): + err = '' + retval = None + try: + retval = client.ping(msg) + except CallException as E: + err = E.args[1] + + except libusb.USBErrorNoDevice: + err = "No KeepKey found" + except libusb.USBErrorTimeout: + err = "USB error timeout" + except libusb.USBError as error: + err = "USB error %r" % error + + return retval, err + + def clearAuthData(self, client): + ctr=0 + while True: + retval, err = self.sendMsg(client, msg = b'\x17' + bytes("getAccount:"+str(ctr), 'utf8')) + if err == '': + retval, err = self.sendMsg(client, msg = b'\x18' + bytes("removeAccount:"+retval, 'utf8')) + else: + break + ctr+=1 + if err == 'Account not found': + return '' + return err + + def test_InitGetOTPClear(self): + self.requires_firmware("7.7.0") + self.setup_mnemonic_pin_passphrase() + self.client.clear_session() + self.clearAuthData(self.client) + for msg in ( + b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto"+":"+"ZKLHM3W3XAHG4CBN", 'utf8'), + b'\x15' + bytes("initializeAuth:"+"Shapeshift"+":"+"markrypto"+":"+"BASE32SECRET2345AB", 'utf8'), + b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto2"+":"+"BACE32SECRET2345AB", 'utf8') + ): + retval, err = self.sendMsg(self.client, msg) + self.assertEqual(err, '') + + T0 = 1535317397 + interval = 30 + Tslice = int(T0/interval) + Tremain = 3 + T = Tslice.to_bytes(8, byteorder='big') + for vector in ( + (b'\x16' + bytes("generateOTPFrom:KeepKey:markrypto:", 'utf8') + binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8'), '910862'), + (b'\x16' + bytes("generateOTPFrom:Shapeshift:markrypto:", 'utf8') + binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8'), '280672'), + (b'\x16' + bytes("generateOTPFrom:KeepKey:markrypto2:", 'utf8') + binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8'), '020352') + ): + retval, err = self.sendMsg(self.client, vector[0]) + self.assertEqual(err, '') + self.assertEqual(retval, vector[1]) + + err = self.clearAuthData(self.client) + self.assertEqual(err, '') + + +if __name__ == '__main__': + unittest.main() From 09c522c417d5db3bdde9d156fc6384066d83f91f Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Mon, 19 Dec 2022 16:33:34 -0700 Subject: [PATCH 13/34] turn off test logging --- authenticator/KeepKeyAuthenticator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index 60fdd587..d54fe18b 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -50,7 +50,7 @@ from manualaddacc import Ui_ManualAddAccDialog as ManAddAcc_Dialog # for dev testing -_test = True +_test = False authErrs = ('Invalid PIN', 'PIN Cancelled', 'PIN expected', 'Auth secret unknown error', 'Account name missing or too long, or seed/message string missing', From 2dcc013eea406a03e93b6aaf1bbd74880f2235f8 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Mon, 19 Dec 2022 16:48:48 -0700 Subject: [PATCH 14/34] document auth feature m1 build hints --- authenticator/AUTHENTICATOR.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/authenticator/AUTHENTICATOR.md b/authenticator/AUTHENTICATOR.md index 4944e977..c85a7b28 100644 --- a/authenticator/AUTHENTICATOR.md +++ b/authenticator/AUTHENTICATOR.md @@ -4,8 +4,8 @@ KeepKey Authenticator The KeepKey Authenticator is a standalone app intended to demo the authenticator feature. It enables the KeepKey device to perform as a hardware off-line one-time passcode (OTP) generator for two factor authentication using the TOPT algorithm. The KeepKey PIN and physical button gives it security features similar to other products such as the Yubikey, but with better PIN security. -Build for MacOS ---------------- +Build for MacOS - Intel +----------------------- Make sure you have python-keepkey built and installed: $ python python-keepkey/setup.py build install @@ -20,3 +20,14 @@ the existing setup.py file if one exists. Create the app: $ python setup.py py2app + +Build for MacOS - M1 +-------------------- +from https://py2app.readthedocs.io/_/downloads/en/stable/pdf/ + +M1 Macs and libraries not available for arm64 A lot of libraries are not yet available as arm64 or universal2 libraries. For applications using those libraries you can create an x86_64 (Intel) application instead: +1. Create a new virtual environment and activate this +2. Use arch -x86_64 python -mpip install ... to install libraries. + The arch command is necessary here to ensure that pip selects variants that are compatible with the x86_64 architecture instead of arm64. +3. Use arch -x86_64 python setup.py py2app --arch x86_64 to build +This results in an application bundle where the launcher is an x86_64 only binary, and where included C extensions and libraries are compatible with that architecture as well. \ No newline at end of file From 4bcb592e2ccc64a2ad0773486de23a9f6b0f02d5 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Wed, 21 Dec 2022 11:50:52 -0700 Subject: [PATCH 15/34] refactor keepkey authenticator timeslice string --- authenticator/KeepKeyAuthenticator.py | 4 +--- keepkeylib/client.py | 4 ++++ tests/test_msg_authfeature.py | 12 +++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index d54fe18b..96093e52 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -542,8 +542,6 @@ def auth_accAdd(self, client, secret, domain, account): def auth_otp(self, client, domain, account): interval = 30 # 30 second interval - #T0 = 1535317397 - #T0 = 1536262427 T0 = datetime.now().timestamp() Tslice = int(T0/interval) Tremain = int((int(T0) - Tslice*30)) @@ -551,7 +549,7 @@ def auth_otp(self, client, domain, account): T = Tslice.to_bytes(8, byteorder='big') retval, err = self.sendMsg(client, msg = b'\x16' + bytes("generateOTPFrom:"+domain+":"+account+":", 'utf8') + - binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8') + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8') ) if err in authErrs: error_popup(err, '') diff --git a/keepkeylib/client.py b/keepkeylib/client.py index 7ded0f89..9d8b9ad2 100644 --- a/keepkeylib/client.py +++ b/keepkeylib/client.py @@ -1,5 +1,6 @@ # This file is part of the TREZOR project. # +# Copyright (C) 2022 markrypto # Copyright (C) 2012-2016 Marek Palatinus # Copyright (C) 2012-2016 Pavol Rusnak # Copyright (C) 2016 Jochen Hoenicke @@ -369,6 +370,9 @@ def __init__(self, *args, **kwargs): # Use blank passphrase self.set_passphrase('') + def setAutoButton(self, tf): + self.auto_button = tf + def close(self): super(DebugLinkMixin, self).close() if self.debug: diff --git a/tests/test_msg_authfeature.py b/tests/test_msg_authfeature.py index 062cb311..da4f5a59 100644 --- a/tests/test_msg_authfeature.py +++ b/tests/test_msg_authfeature.py @@ -27,7 +27,7 @@ def sendMsg(self, client, msg): err = '' retval = None try: - retval = client.ping(msg) + retval = client.ping(msg=msg) except CallException as E: err = E.args[1] @@ -69,12 +69,11 @@ def test_InitGetOTPClear(self): T0 = 1535317397 interval = 30 Tslice = int(T0/interval) - Tremain = 3 - T = Tslice.to_bytes(8, byteorder='big') + Tremain = 7 for vector in ( - (b'\x16' + bytes("generateOTPFrom:KeepKey:markrypto:", 'utf8') + binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8'), '910862'), - (b'\x16' + bytes("generateOTPFrom:Shapeshift:markrypto:", 'utf8') + binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8'), '280672'), - (b'\x16' + bytes("generateOTPFrom:KeepKey:markrypto2:", 'utf8') + binascii.hexlify(bytearray(T)) + bytes(":" + str(Tremain), 'utf8'), '020352') + (b'\x16' + bytes("generateOTPFrom:KeepKey:markrypto:", 'utf8') + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8'), '910862'), + (b'\x16' + bytes("generateOTPFrom:Shapeshift:markrypto:", 'utf8') + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8'), '280672'), + (b'\x16' + bytes("generateOTPFrom:KeepKey:markrypto2:", 'utf8') + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8'), '020352') ): retval, err = self.sendMsg(self.client, vector[0]) self.assertEqual(err, '') @@ -83,6 +82,5 @@ def test_InitGetOTPClear(self): err = self.clearAuthData(self.client) self.assertEqual(err, '') - if __name__ == '__main__': unittest.main() From 9daa62180624e933b1fed853e7b3572a6a86af22 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Wed, 21 Dec 2022 12:02:39 -0700 Subject: [PATCH 16/34] fix time remaining interval for OTP generation --- authenticator/KeepKeyAuthenticator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index 96093e52..3dcff966 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -544,9 +544,8 @@ def auth_otp(self, client, domain, account): interval = 30 # 30 second interval T0 = datetime.now().timestamp() Tslice = int(T0/interval) - Tremain = int((int(T0) - Tslice*30)) + Tremain = interval - int((int(T0) - Tslice*30)) if _test: print(Tremain) - T = Tslice.to_bytes(8, byteorder='big') retval, err = self.sendMsg(client, msg = b'\x16' + bytes("generateOTPFrom:"+domain+":"+account+":", 'utf8') + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8') From 76e3012ce3d6b96cd46b696779adc499de61995d Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Wed, 21 Dec 2022 16:56:35 -0700 Subject: [PATCH 17/34] changed fw ver req to 7.6.0 --- authenticator/KeepKeyAuthenticator.py | 2 +- tests/test_msg_authfeature.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index 3dcff966..4f17d56a 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -87,7 +87,7 @@ class KeepKeyClientAuth(ProtocolMixin, GuiUIMixin, BaseClient): # Creates object for manipulating KeepKey self.client = KeepKeyClientAuth(transport) - self.requires_firmware("7.7.0") + self.requires_firmware("7.6.0") def requires_firmware(self, ver_required): diff --git a/tests/test_msg_authfeature.py b/tests/test_msg_authfeature.py index da4f5a59..aa726f4a 100644 --- a/tests/test_msg_authfeature.py +++ b/tests/test_msg_authfeature.py @@ -54,7 +54,7 @@ def clearAuthData(self, client): return err def test_InitGetOTPClear(self): - self.requires_firmware("7.7.0") + self.requires_firmware("7.6.0") self.setup_mnemonic_pin_passphrase() self.client.clear_session() self.clearAuthData(self.client) From 27912166049fb8a28793f1a1dc9daf9d571946ec Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Thu, 22 Dec 2022 09:23:35 -0700 Subject: [PATCH 18/34] add versioning --- authenticator/AuthMain.py | 12 ++++++++++-- authenticator/AuthMain.ui | 27 ++++++++++++++++++++++++--- authenticator/KeepKeyAuthenticator.py | 9 +++++++-- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/authenticator/AuthMain.py b/authenticator/AuthMain.py index 0b289a93..70327d5e 100644 --- a/authenticator/AuthMain.py +++ b/authenticator/AuthMain.py @@ -34,7 +34,7 @@ def setupUi(self, MainWindow): self.TitleDesc.setWordWrap(True) self.TitleDesc.setObjectName("TitleDesc") self.Author = QtWidgets.QLabel(self.centralwidget) - self.Author.setGeometry(QtCore.QRect(290, 120, 150, 50)) + self.Author.setGeometry(QtCore.QRect(290, 150, 150, 40)) font = QtGui.QFont() font.setPointSize(24) self.Author.setFont(font) @@ -285,9 +285,17 @@ def setupUi(self, MainWindow): self.OTPAcc_10.setAutoDefault(True) self.OTPAcc_10.setObjectName("OTPAcc_10") self.OTPButtonGroup.addButton(self.OTPAcc_10) + self.Version = QtWidgets.QLabel(self.centralwidget) + self.Version.setGeometry(QtCore.QRect(290, 110, 220, 40)) + font = QtGui.QFont() + font.setPointSize(24) + self.Version.setFont(font) + self.Version.setStyleSheet("color: rgb(255, 255, 255);") + self.Version.setText("") + self.Version.setObjectName("Version") MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 772, 24)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 772, 21)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) diff --git a/authenticator/AuthMain.ui b/authenticator/AuthMain.ui index 3e6794fc..b08f28a9 100644 --- a/authenticator/AuthMain.ui +++ b/authenticator/AuthMain.ui @@ -68,9 +68,9 @@ 290 - 120 + 150 150 - 50 + 40 @@ -614,6 +614,27 @@ text-align:left; OTPButtonGroup
+ + + + 290 + 110 + 220 + 40 + + + + + 24 + + + + color: rgb(255, 255, 255); + + + + + @@ -621,7 +642,7 @@ text-align:left; 0 0 772 - 24 + 21 diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index 4f17d56a..fadfd9e6 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -17,6 +17,8 @@ # # The script has been modified for KeepKey Device. +__version__ = 0.5.0 + from PyQt6 import QtWidgets, uic from PyQt6.QtWidgets import QFileDialog, QMenuBar,QMessageBox,QPushButton from PyQt6.QtGui import * @@ -354,9 +356,12 @@ def __init__(self): def setupUi(self, MainWindow): super(Ui, self).setupUi(MainWindow) - + _translate = QtCore.QCoreApplication.translate + self.clientOps = kkClient() - + versionText = "Version %s" % str(__version__) + self.Version.setText(_translate("MainWindow", versionText)) + self.ConnectKKButton.clicked.connect(self.KKConnect) self.AddAccButton.clicked.connect(self.addAcc) self.RemoveAccButton.clicked.connect(self.removeAcc) From 12411a58f4dcb6b2ca46ef727a7865afcdbf9daa Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Thu, 22 Dec 2022 09:29:18 -0700 Subject: [PATCH 19/34] bug fix --- authenticator/KeepKeyAuthenticator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index fadfd9e6..695623ef 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -17,7 +17,7 @@ # # The script has been modified for KeepKey Device. -__version__ = 0.5.0 +__version__ = "0.5.0" from PyQt6 import QtWidgets, uic from PyQt6.QtWidgets import QFileDialog, QMenuBar,QMessageBox,QPushButton @@ -359,7 +359,7 @@ def setupUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate self.clientOps = kkClient() - versionText = "Version %s" % str(__version__) + versionText = "Version %s" % __version__ self.Version.setText(_translate("MainWindow", versionText)) self.ConnectKKButton.clicked.connect(self.KKConnect) From f4285826284509cdeca23abf1ddab28f9a846f4b Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Thu, 22 Dec 2022 11:31:40 -0700 Subject: [PATCH 20/34] temporarily get feature branch for circleci --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 14597889..9ac13a01 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: git config --global url."https://github.com/".insteadOf git@github.com: git config --global url."https://".insteadOf git:// mv .pykk ../ - git clone $FIRMWARE_REPO --depth 1 -b master . + git clone $FIRMWARE_REPO --depth 1 -b feature-auth . git submodule update --init --recursive rm -rf deps/python-keepkey mv ../.pykk deps/python-keepkey From dda76bd9eaa0f9de5b2bf67656812a4062435e46 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Thu, 22 Dec 2022 11:39:24 -0700 Subject: [PATCH 21/34] revert config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9ac13a01..14597889 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: git config --global url."https://github.com/".insteadOf git@github.com: git config --global url."https://".insteadOf git:// mv .pykk ../ - git clone $FIRMWARE_REPO --depth 1 -b feature-auth . + git clone $FIRMWARE_REPO --depth 1 -b master . git submodule update --init --recursive rm -rf deps/python-keepkey mv ../.pykk deps/python-keepkey From d55468073b6271940a6f9617d96f1714fe28c820 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Thu, 22 Dec 2022 11:59:49 -0700 Subject: [PATCH 22/34] remove usb1 mod dep in tests --- tests/test_msg_authfeature.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/test_msg_authfeature.py b/tests/test_msg_authfeature.py index aa726f4a..0062d629 100644 --- a/tests/test_msg_authfeature.py +++ b/tests/test_msg_authfeature.py @@ -15,7 +15,6 @@ import unittest import common import binascii -import usb1 as libusb from keepkeylib import messages_pb2 as proto from keepkeylib import types_pb2 as proto_types @@ -30,14 +29,6 @@ def sendMsg(self, client, msg): retval = client.ping(msg=msg) except CallException as E: err = E.args[1] - - except libusb.USBErrorNoDevice: - err = "No KeepKey found" - except libusb.USBErrorTimeout: - err = "USB error timeout" - except libusb.USBError as error: - err = "USB error %r" % error - return retval, err def clearAuthData(self, client): From c9526d6e865aea57651d7bee595aca9ba52edf8b Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Thu, 22 Dec 2022 15:21:57 -0700 Subject: [PATCH 23/34] auth feature test skipped on emulator build --- tests/test_msg_authfeature.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_msg_authfeature.py b/tests/test_msg_authfeature.py index 0062d629..99cf5dec 100644 --- a/tests/test_msg_authfeature.py +++ b/tests/test_msg_authfeature.py @@ -45,6 +45,10 @@ def clearAuthData(self, client): return err def test_InitGetOTPClear(self): + if self.client.features.firmware_variant == "Emulator": + self.skipTest("Skip test in emulator, test on physical KeepKey") + return + self.requires_firmware("7.6.0") self.setup_mnemonic_pin_passphrase() self.client.clear_session() From 3ab35e5207ee8232f365bdab425fa5ff443dd84f Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Thu, 22 Dec 2022 15:44:43 -0700 Subject: [PATCH 24/34] update eip712 tests --- tests/eip712tests.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/eip712tests.json b/tests/eip712tests.json index edabb8df..76a7f25d 100644 --- a/tests/eip712tests.json +++ b/tests/eip712tests.json @@ -511,16 +511,6 @@ "domain_separator_hash": "0x6192106f129ce05c9075d319c1fa6ea9b3ae37cbd0c1ef92e2be7137bb07baa1", "sig": "0x7bd7c6a1df52c56800285a3e680f48eee559db9a5b171e3faea2191e995141401aabb48d5f5595ee75bb2bf29593d07a2afcb85686271c7c37b76afbdb9c86cc1c" } - }, - {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Permit":[{"name":"owner","type":"address"},{"name":"spender","type":"address"},{"name":"value","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"}]},"domain":{"name":"USD Coin","version":"2","verifyingContract":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","chainId":1},"primaryType":"Permit","message":{"owner":"0x33b35c665496bA8E71B22373843376740401F106","spender":"0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45","value":"4023865","nonce":0,"deadline":1655431026}, - "path": "m/44'/60'/0'/0/0", - "results": { - "test_data": "actual", - "address": "0x73d0385F4d8E00C5e6504C6030F47BF6212736A8", - "message_hash": "0x12b75f932b4f17e1f62bc7a630a033f46649a18f4e759bb1ff559c57cb2bc39b", - "domain_separator_hash": "0x06c37168a7db5138defc7866392bb87a741f9b3d104deb5094588ce041cae335", - "sig": "0x7ce6f01f14d8a1d1923073ccd77f97b78972b3cf14b9c2874d6a46f6a196cc0b7fef13c100b2a5a3517c0baa18981e0fe19cb7d9f869279041b537d91e839d281c" - } } ] } \ No newline at end of file From f03fbca94e8484b604ec406c656e8a3cd4af10d0 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Thu, 22 Dec 2022 16:13:40 -0700 Subject: [PATCH 25/34] fix mac/unix filename divergence --- authenticator/AuthMain.py | 329 -------------------------------------- 1 file changed, 329 deletions(-) delete mode 100644 authenticator/AuthMain.py diff --git a/authenticator/AuthMain.py b/authenticator/AuthMain.py deleted file mode 100644 index 70327d5e..00000000 --- a/authenticator/AuthMain.py +++ /dev/null @@ -1,329 +0,0 @@ -# Form implementation generated from reading ui file 'AuthMain.ui' -# -# Created by: PyQt6 UI code generator 6.1.0 -# -# WARNING: Any manual changes made to this file will be lost when pyuic6 is -# run again. Do not edit this file unless you know what you are doing. - - -from PyQt6 import QtCore, QtGui, QtWidgets - - -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - MainWindow.setObjectName("MainWindow") - MainWindow.resize(772, 828) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap("kk-icon-gold.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) - MainWindow.setWindowIcon(icon) - MainWindow.setStyleSheet("background-color: rgb(1, 0, 0);") - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setObjectName("centralwidget") - self.kklogo = QtWidgets.QLabel(self.centralwidget) - self.kklogo.setGeometry(QtCore.QRect(30, 30, 200, 160)) - self.kklogo.setText("") - self.kklogo.setPixmap(QtGui.QPixmap("kk-icon-gold.png")) - self.kklogo.setObjectName("kklogo") - self.TitleDesc = QtWidgets.QLabel(self.centralwidget) - self.TitleDesc.setGeometry(QtCore.QRect(290, 30, 460, 80)) - font = QtGui.QFont() - font.setPointSize(32) - self.TitleDesc.setFont(font) - self.TitleDesc.setStyleSheet("color: rgb(255, 255, 255);") - self.TitleDesc.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) - self.TitleDesc.setWordWrap(True) - self.TitleDesc.setObjectName("TitleDesc") - self.Author = QtWidgets.QLabel(self.centralwidget) - self.Author.setGeometry(QtCore.QRect(290, 150, 150, 40)) - font = QtGui.QFont() - font.setPointSize(24) - self.Author.setFont(font) - self.Author.setStyleSheet("color: rgb(55, 62, 255);") - self.Author.setObjectName("Author") - self.line = QtWidgets.QFrame(self.centralwidget) - self.line.setGeometry(QtCore.QRect(30, 200, 720, 16)) - self.line.setStyleSheet("color: rgb(255, 255, 255)") - self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) - self.line.setLineWidth(1) - self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine) - self.line.setObjectName("line") - self.ConnectKKButton = QtWidgets.QPushButton(self.centralwidget) - self.ConnectKKButton.setGeometry(QtCore.QRect(610, 120, 140, 70)) - font = QtGui.QFont() - font.setPointSize(20) - self.ConnectKKButton.setFont(font) - self.ConnectKKButton.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(255, 128, 4);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(255, 255, 255)") - self.ConnectKKButton.setAutoDefault(True) - self.ConnectKKButton.setObjectName("ConnectKKButton") - self.AddAccButton = QtWidgets.QPushButton(self.centralwidget) - self.AddAccButton.setGeometry(QtCore.QRect(30, 220, 200, 50)) - font = QtGui.QFont() - font.setPointSize(24) - self.AddAccButton.setFont(font) - self.AddAccButton.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(103, 103, 103);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(255, 255, 255)") - self.AddAccButton.setAutoDefault(True) - self.AddAccButton.setObjectName("AddAccButton") - self.testButton = QtWidgets.QPushButton(self.centralwidget) - self.testButton.setGeometry(QtCore.QRect(30, 480, 200, 50)) - font = QtGui.QFont() - font.setPointSize(24) - self.testButton.setFont(font) - self.testButton.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(103, 103, 103);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(255, 255, 255);\n" -"QPushButton::pressed { \n" -" background-color: rgb(35, 40, 49);\n" -" border: 2px solid rgb(43, 50, 61);\n" -" }") - self.testButton.setAutoDefault(True) - self.testButton.setObjectName("testButton") - self.AccountList = QtWidgets.QLabel(self.centralwidget) - self.AccountList.setGeometry(QtCore.QRect(380, 220, 130, 45)) - font = QtGui.QFont() - font.setPointSize(32) - self.AccountList.setFont(font) - self.AccountList.setStyleSheet("color: rgb(255, 255, 255);\n" -"") - self.AccountList.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) - self.AccountList.setObjectName("AccountList") - self.RemoveAccButton = QtWidgets.QPushButton(self.centralwidget) - self.RemoveAccButton.setGeometry(QtCore.QRect(30, 300, 200, 50)) - font = QtGui.QFont() - font.setPointSize(24) - self.RemoveAccButton.setFont(font) - self.RemoveAccButton.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(103, 103, 103);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(255, 255, 255)") - self.RemoveAccButton.setAutoDefault(True) - self.RemoveAccButton.setObjectName("RemoveAccButton") - self.OTPAcc_1 = QtWidgets.QPushButton(self.centralwidget) - self.OTPAcc_1.setGeometry(QtCore.QRect(380, 270, 351, 41)) - font = QtGui.QFont() - font.setPointSize(24) - self.OTPAcc_1.setFont(font) - self.OTPAcc_1.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(1, 0, 0);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(0, 255, 0);\n" -"text-align:left;") - self.OTPAcc_1.setText("") - self.OTPAcc_1.setAutoDefault(True) - self.OTPAcc_1.setObjectName("OTPAcc_1") - self.OTPButtonGroup = QtWidgets.QButtonGroup(MainWindow) - self.OTPButtonGroup.setObjectName("OTPButtonGroup") - self.OTPButtonGroup.addButton(self.OTPAcc_1) - self.AccountList_2 = QtWidgets.QLabel(self.centralwidget) - self.AccountList_2.setGeometry(QtCore.QRect(525, 220, 220, 45)) - font = QtGui.QFont() - font.setPointSize(18) - self.AccountList_2.setFont(font) - self.AccountList_2.setStyleSheet("color: rgb(255, 255, 255);\n" -"") - self.AccountList_2.setTextFormat(QtCore.Qt.TextFormat.RichText) - self.AccountList_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) - self.AccountList_2.setWordWrap(True) - self.AccountList_2.setObjectName("AccountList_2") - self.OTPAcc_2 = QtWidgets.QPushButton(self.centralwidget) - self.OTPAcc_2.setGeometry(QtCore.QRect(380, 320, 351, 41)) - font = QtGui.QFont() - font.setPointSize(24) - self.OTPAcc_2.setFont(font) - self.OTPAcc_2.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(1, 0, 0);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(0, 255, 0);\n" -"text-align:left;") - self.OTPAcc_2.setText("") - self.OTPAcc_2.setAutoDefault(True) - self.OTPAcc_2.setObjectName("OTPAcc_2") - self.OTPButtonGroup.addButton(self.OTPAcc_2) - self.OTPAcc_3 = QtWidgets.QPushButton(self.centralwidget) - self.OTPAcc_3.setGeometry(QtCore.QRect(380, 370, 351, 41)) - font = QtGui.QFont() - font.setPointSize(24) - self.OTPAcc_3.setFont(font) - self.OTPAcc_3.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(1, 0, 0);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(0, 255, 0);\n" -"text-align:left;") - self.OTPAcc_3.setText("") - self.OTPAcc_3.setAutoDefault(True) - self.OTPAcc_3.setObjectName("OTPAcc_3") - self.OTPButtonGroup.addButton(self.OTPAcc_3) - self.OTPAcc_4 = QtWidgets.QPushButton(self.centralwidget) - self.OTPAcc_4.setGeometry(QtCore.QRect(380, 420, 351, 41)) - font = QtGui.QFont() - font.setPointSize(24) - self.OTPAcc_4.setFont(font) - self.OTPAcc_4.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(1, 0, 0);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(0, 255, 0);\n" -"text-align:left;") - self.OTPAcc_4.setText("") - self.OTPAcc_4.setAutoDefault(True) - self.OTPAcc_4.setObjectName("OTPAcc_4") - self.OTPButtonGroup.addButton(self.OTPAcc_4) - self.OTPAcc_5 = QtWidgets.QPushButton(self.centralwidget) - self.OTPAcc_5.setGeometry(QtCore.QRect(380, 470, 351, 41)) - font = QtGui.QFont() - font.setPointSize(24) - self.OTPAcc_5.setFont(font) - self.OTPAcc_5.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(1, 0, 0);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(0, 255, 0);\n" -"text-align:left;") - self.OTPAcc_5.setText("") - self.OTPAcc_5.setAutoDefault(True) - self.OTPAcc_5.setObjectName("OTPAcc_5") - self.OTPButtonGroup.addButton(self.OTPAcc_5) - self.OTPAcc_6 = QtWidgets.QPushButton(self.centralwidget) - self.OTPAcc_6.setGeometry(QtCore.QRect(380, 520, 351, 41)) - font = QtGui.QFont() - font.setPointSize(24) - self.OTPAcc_6.setFont(font) - self.OTPAcc_6.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(1, 0, 0);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(0, 255, 0);\n" -"text-align:left;") - self.OTPAcc_6.setText("") - self.OTPAcc_6.setAutoDefault(True) - self.OTPAcc_6.setObjectName("OTPAcc_6") - self.OTPButtonGroup.addButton(self.OTPAcc_6) - self.OTPAcc_7 = QtWidgets.QPushButton(self.centralwidget) - self.OTPAcc_7.setGeometry(QtCore.QRect(380, 570, 351, 41)) - font = QtGui.QFont() - font.setPointSize(24) - self.OTPAcc_7.setFont(font) - self.OTPAcc_7.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(1, 0, 0);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(0, 255, 0);\n" -"text-align:left;") - self.OTPAcc_7.setText("") - self.OTPAcc_7.setAutoDefault(True) - self.OTPAcc_7.setObjectName("OTPAcc_7") - self.OTPButtonGroup.addButton(self.OTPAcc_7) - self.OTPAcc_8 = QtWidgets.QPushButton(self.centralwidget) - self.OTPAcc_8.setGeometry(QtCore.QRect(380, 620, 351, 41)) - font = QtGui.QFont() - font.setPointSize(24) - self.OTPAcc_8.setFont(font) - self.OTPAcc_8.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(1, 0, 0);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(0, 255, 0);\n" -"text-align:left;") - self.OTPAcc_8.setText("") - self.OTPAcc_8.setAutoDefault(True) - self.OTPAcc_8.setObjectName("OTPAcc_8") - self.OTPButtonGroup.addButton(self.OTPAcc_8) - self.OTPAcc_9 = QtWidgets.QPushButton(self.centralwidget) - self.OTPAcc_9.setGeometry(QtCore.QRect(380, 670, 351, 41)) - font = QtGui.QFont() - font.setPointSize(24) - self.OTPAcc_9.setFont(font) - self.OTPAcc_9.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(1, 0, 0);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(0, 255, 0);\n" -"text-align:left;") - self.OTPAcc_9.setText("") - self.OTPAcc_9.setAutoDefault(True) - self.OTPAcc_9.setObjectName("OTPAcc_9") - self.OTPButtonGroup.addButton(self.OTPAcc_9) - self.OTPAcc_10 = QtWidgets.QPushButton(self.centralwidget) - self.OTPAcc_10.setGeometry(QtCore.QRect(380, 720, 351, 41)) - font = QtGui.QFont() - font.setPointSize(24) - self.OTPAcc_10.setFont(font) - self.OTPAcc_10.setStyleSheet("border-radius: 10px;\n" -"background-color: rgb(1, 0, 0);\n" -"border-width: 2px;\n" -"border-style: solid;\n" -"border-color: black;\n" -"color: rgb(0, 255, 0);\n" -"text-align:left;") - self.OTPAcc_10.setText("") - self.OTPAcc_10.setAutoDefault(True) - self.OTPAcc_10.setObjectName("OTPAcc_10") - self.OTPButtonGroup.addButton(self.OTPAcc_10) - self.Version = QtWidgets.QLabel(self.centralwidget) - self.Version.setGeometry(QtCore.QRect(290, 110, 220, 40)) - font = QtGui.QFont() - font.setPointSize(24) - self.Version.setFont(font) - self.Version.setStyleSheet("color: rgb(255, 255, 255);") - self.Version.setText("") - self.Version.setObjectName("Version") - MainWindow.setCentralWidget(self.centralwidget) - self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 772, 21)) - self.menubar.setObjectName("menubar") - MainWindow.setMenuBar(self.menubar) - self.statusbar = QtWidgets.QStatusBar(MainWindow) - self.statusbar.setObjectName("statusbar") - MainWindow.setStatusBar(self.statusbar) - - self.retranslateUi(MainWindow) - QtCore.QMetaObject.connectSlotsByName(MainWindow) - - def retranslateUi(self, MainWindow): - _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindow", "KeepKey Authenticator")) - self.TitleDesc.setText(_translate("MainWindow", "Authenticator for the KeepKey Python client")) - self.Author.setText(_translate("MainWindow", "by markrypto")) - self.ConnectKKButton.setText(_translate("MainWindow", "Connect\n" -"KeepKey")) - self.AddAccButton.setText(_translate("MainWindow", "Add Account")) - self.testButton.setText(_translate("MainWindow", "test")) - self.AccountList.setText(_translate("MainWindow", "Accounts")) - self.RemoveAccButton.setText(_translate("MainWindow", "Remove Account")) - self.AccountList_2.setText(_translate("MainWindow", "Click on account to generate OTP on KeepKey")) - - -if __name__ == "__main__": - import sys - app = QtWidgets.QApplication(sys.argv) - MainWindow = QtWidgets.QMainWindow() - ui = Ui_MainWindow() - ui.setupUi(MainWindow) - MainWindow.show() - sys.exit(app.exec()) From 1c356c129f0d3d5f0d5036247a4000abb37c5e30 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Thu, 22 Dec 2022 16:21:51 -0700 Subject: [PATCH 26/34] add back file as all lower --- authenticator/authmain.py | 329 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 authenticator/authmain.py diff --git a/authenticator/authmain.py b/authenticator/authmain.py new file mode 100644 index 00000000..70327d5e --- /dev/null +++ b/authenticator/authmain.py @@ -0,0 +1,329 @@ +# Form implementation generated from reading ui file 'AuthMain.ui' +# +# Created by: PyQt6 UI code generator 6.1.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(772, 828) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap("kk-icon-gold.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) + MainWindow.setWindowIcon(icon) + MainWindow.setStyleSheet("background-color: rgb(1, 0, 0);") + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.kklogo = QtWidgets.QLabel(self.centralwidget) + self.kklogo.setGeometry(QtCore.QRect(30, 30, 200, 160)) + self.kklogo.setText("") + self.kklogo.setPixmap(QtGui.QPixmap("kk-icon-gold.png")) + self.kklogo.setObjectName("kklogo") + self.TitleDesc = QtWidgets.QLabel(self.centralwidget) + self.TitleDesc.setGeometry(QtCore.QRect(290, 30, 460, 80)) + font = QtGui.QFont() + font.setPointSize(32) + self.TitleDesc.setFont(font) + self.TitleDesc.setStyleSheet("color: rgb(255, 255, 255);") + self.TitleDesc.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) + self.TitleDesc.setWordWrap(True) + self.TitleDesc.setObjectName("TitleDesc") + self.Author = QtWidgets.QLabel(self.centralwidget) + self.Author.setGeometry(QtCore.QRect(290, 150, 150, 40)) + font = QtGui.QFont() + font.setPointSize(24) + self.Author.setFont(font) + self.Author.setStyleSheet("color: rgb(55, 62, 255);") + self.Author.setObjectName("Author") + self.line = QtWidgets.QFrame(self.centralwidget) + self.line.setGeometry(QtCore.QRect(30, 200, 720, 16)) + self.line.setStyleSheet("color: rgb(255, 255, 255)") + self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) + self.line.setLineWidth(1) + self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine) + self.line.setObjectName("line") + self.ConnectKKButton = QtWidgets.QPushButton(self.centralwidget) + self.ConnectKKButton.setGeometry(QtCore.QRect(610, 120, 140, 70)) + font = QtGui.QFont() + font.setPointSize(20) + self.ConnectKKButton.setFont(font) + self.ConnectKKButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(255, 128, 4);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(255, 255, 255)") + self.ConnectKKButton.setAutoDefault(True) + self.ConnectKKButton.setObjectName("ConnectKKButton") + self.AddAccButton = QtWidgets.QPushButton(self.centralwidget) + self.AddAccButton.setGeometry(QtCore.QRect(30, 220, 200, 50)) + font = QtGui.QFont() + font.setPointSize(24) + self.AddAccButton.setFont(font) + self.AddAccButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(255, 255, 255)") + self.AddAccButton.setAutoDefault(True) + self.AddAccButton.setObjectName("AddAccButton") + self.testButton = QtWidgets.QPushButton(self.centralwidget) + self.testButton.setGeometry(QtCore.QRect(30, 480, 200, 50)) + font = QtGui.QFont() + font.setPointSize(24) + self.testButton.setFont(font) + self.testButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(255, 255, 255);\n" +"QPushButton::pressed { \n" +" background-color: rgb(35, 40, 49);\n" +" border: 2px solid rgb(43, 50, 61);\n" +" }") + self.testButton.setAutoDefault(True) + self.testButton.setObjectName("testButton") + self.AccountList = QtWidgets.QLabel(self.centralwidget) + self.AccountList.setGeometry(QtCore.QRect(380, 220, 130, 45)) + font = QtGui.QFont() + font.setPointSize(32) + self.AccountList.setFont(font) + self.AccountList.setStyleSheet("color: rgb(255, 255, 255);\n" +"") + self.AccountList.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) + self.AccountList.setObjectName("AccountList") + self.RemoveAccButton = QtWidgets.QPushButton(self.centralwidget) + self.RemoveAccButton.setGeometry(QtCore.QRect(30, 300, 200, 50)) + font = QtGui.QFont() + font.setPointSize(24) + self.RemoveAccButton.setFont(font) + self.RemoveAccButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(255, 255, 255)") + self.RemoveAccButton.setAutoDefault(True) + self.RemoveAccButton.setObjectName("RemoveAccButton") + self.OTPAcc_1 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_1.setGeometry(QtCore.QRect(380, 270, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_1.setFont(font) + self.OTPAcc_1.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_1.setText("") + self.OTPAcc_1.setAutoDefault(True) + self.OTPAcc_1.setObjectName("OTPAcc_1") + self.OTPButtonGroup = QtWidgets.QButtonGroup(MainWindow) + self.OTPButtonGroup.setObjectName("OTPButtonGroup") + self.OTPButtonGroup.addButton(self.OTPAcc_1) + self.AccountList_2 = QtWidgets.QLabel(self.centralwidget) + self.AccountList_2.setGeometry(QtCore.QRect(525, 220, 220, 45)) + font = QtGui.QFont() + font.setPointSize(18) + self.AccountList_2.setFont(font) + self.AccountList_2.setStyleSheet("color: rgb(255, 255, 255);\n" +"") + self.AccountList_2.setTextFormat(QtCore.Qt.TextFormat.RichText) + self.AccountList_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter|QtCore.Qt.AlignmentFlag.AlignTop) + self.AccountList_2.setWordWrap(True) + self.AccountList_2.setObjectName("AccountList_2") + self.OTPAcc_2 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_2.setGeometry(QtCore.QRect(380, 320, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_2.setFont(font) + self.OTPAcc_2.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_2.setText("") + self.OTPAcc_2.setAutoDefault(True) + self.OTPAcc_2.setObjectName("OTPAcc_2") + self.OTPButtonGroup.addButton(self.OTPAcc_2) + self.OTPAcc_3 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_3.setGeometry(QtCore.QRect(380, 370, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_3.setFont(font) + self.OTPAcc_3.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_3.setText("") + self.OTPAcc_3.setAutoDefault(True) + self.OTPAcc_3.setObjectName("OTPAcc_3") + self.OTPButtonGroup.addButton(self.OTPAcc_3) + self.OTPAcc_4 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_4.setGeometry(QtCore.QRect(380, 420, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_4.setFont(font) + self.OTPAcc_4.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_4.setText("") + self.OTPAcc_4.setAutoDefault(True) + self.OTPAcc_4.setObjectName("OTPAcc_4") + self.OTPButtonGroup.addButton(self.OTPAcc_4) + self.OTPAcc_5 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_5.setGeometry(QtCore.QRect(380, 470, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_5.setFont(font) + self.OTPAcc_5.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_5.setText("") + self.OTPAcc_5.setAutoDefault(True) + self.OTPAcc_5.setObjectName("OTPAcc_5") + self.OTPButtonGroup.addButton(self.OTPAcc_5) + self.OTPAcc_6 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_6.setGeometry(QtCore.QRect(380, 520, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_6.setFont(font) + self.OTPAcc_6.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_6.setText("") + self.OTPAcc_6.setAutoDefault(True) + self.OTPAcc_6.setObjectName("OTPAcc_6") + self.OTPButtonGroup.addButton(self.OTPAcc_6) + self.OTPAcc_7 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_7.setGeometry(QtCore.QRect(380, 570, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_7.setFont(font) + self.OTPAcc_7.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_7.setText("") + self.OTPAcc_7.setAutoDefault(True) + self.OTPAcc_7.setObjectName("OTPAcc_7") + self.OTPButtonGroup.addButton(self.OTPAcc_7) + self.OTPAcc_8 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_8.setGeometry(QtCore.QRect(380, 620, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_8.setFont(font) + self.OTPAcc_8.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_8.setText("") + self.OTPAcc_8.setAutoDefault(True) + self.OTPAcc_8.setObjectName("OTPAcc_8") + self.OTPButtonGroup.addButton(self.OTPAcc_8) + self.OTPAcc_9 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_9.setGeometry(QtCore.QRect(380, 670, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_9.setFont(font) + self.OTPAcc_9.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_9.setText("") + self.OTPAcc_9.setAutoDefault(True) + self.OTPAcc_9.setObjectName("OTPAcc_9") + self.OTPButtonGroup.addButton(self.OTPAcc_9) + self.OTPAcc_10 = QtWidgets.QPushButton(self.centralwidget) + self.OTPAcc_10.setGeometry(QtCore.QRect(380, 720, 351, 41)) + font = QtGui.QFont() + font.setPointSize(24) + self.OTPAcc_10.setFont(font) + self.OTPAcc_10.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(0, 255, 0);\n" +"text-align:left;") + self.OTPAcc_10.setText("") + self.OTPAcc_10.setAutoDefault(True) + self.OTPAcc_10.setObjectName("OTPAcc_10") + self.OTPButtonGroup.addButton(self.OTPAcc_10) + self.Version = QtWidgets.QLabel(self.centralwidget) + self.Version.setGeometry(QtCore.QRect(290, 110, 220, 40)) + font = QtGui.QFont() + font.setPointSize(24) + self.Version.setFont(font) + self.Version.setStyleSheet("color: rgb(255, 255, 255);") + self.Version.setText("") + self.Version.setObjectName("Version") + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 772, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "KeepKey Authenticator")) + self.TitleDesc.setText(_translate("MainWindow", "Authenticator for the KeepKey Python client")) + self.Author.setText(_translate("MainWindow", "by markrypto")) + self.ConnectKKButton.setText(_translate("MainWindow", "Connect\n" +"KeepKey")) + self.AddAccButton.setText(_translate("MainWindow", "Add Account")) + self.testButton.setText(_translate("MainWindow", "test")) + self.AccountList.setText(_translate("MainWindow", "Accounts")) + self.RemoveAccButton.setText(_translate("MainWindow", "Remove Account")) + self.AccountList_2.setText(_translate("MainWindow", "Click on account to generate OTP on KeepKey")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + MainWindow = QtWidgets.QMainWindow() + ui = Ui_MainWindow() + ui.setupUi(MainWindow) + MainWindow.show() + sys.exit(app.exec()) From e07e462604ae24d26db75f4768c7ed84fd604b17 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Tue, 3 Jan 2023 10:40:33 -0700 Subject: [PATCH 27/34] WIP adding passphrase protection to authdata --- authenticator/GUImixin.py | 11 ++- authenticator/KeepKeyAuthenticator.py | 44 ++++++++++- authenticator/passphraseDialog.ui | 101 ++++++++++++++++++++++++++ authenticator/passphrasedialog.py | 67 +++++++++++++++++ 4 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 authenticator/passphraseDialog.ui create mode 100644 authenticator/passphrasedialog.py diff --git a/authenticator/GUImixin.py b/authenticator/GUImixin.py index e7d18aeb..6c0ad10c 100644 --- a/authenticator/GUImixin.py +++ b/authenticator/GUImixin.py @@ -19,6 +19,7 @@ from keepkeylib import messages_pb2 as proto from KeepKeyAuthenticator import pingui_popup +from KeepKeyAuthenticator import passphrase_popup class GuiUIMixin(object): # qt gui interface @@ -44,8 +45,14 @@ def callback_PinMatrixRequest(self, msg): else: return proto.PinMatrixAck(pin=pin) - # def callback_PassphraseRequest(self, msg): - # pass + def callback_PassphraseRequest(self, msg): + passphrase = passphrase_popup() + # passphrase = "YEJ@.h*B!KLAJLpQ!6AMfaQLdijAmZRXMe3@a_FqmNfpbAN4p" + return proto.PassphraseAck(passphrase=passphrase) + # def callback_CharacterRequest(self, msg): # pass + + + diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index 695623ef..ab0aec5c 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -50,9 +50,10 @@ from remaccdialog import Ui_RemAccDialog as RemAcc_Dialog from addaccdialog import Ui_AddAccDialog as AddAcc_Dialog from manualaddacc import Ui_ManualAddAccDialog as ManAddAcc_Dialog +from passphrasedialog import Ui_PassphraseDialog as Passphrase_Dialog # for dev testing -_test = False +_test = True authErrs = ('Invalid PIN', 'PIN Cancelled', 'PIN expected', 'Auth secret unknown error', 'Account name missing or too long, or seed/message string missing', @@ -167,6 +168,47 @@ def pingui_popup(): else: return 'E' # pin cancelled +def passphrase_popup(): + # set up passphrase dialog + passphraseDialog = QtWidgets.QDialog() + passphrase_ui = Passphrase_Dialog() + passphrase_ui.setupUi(passphraseDialog) + passphraseDialog.show() + x = passphraseDialog.exec() # show pin dialog + if passphrase_ui.getEnterClicked() == True: + passphrase = passphrase_ui.getPassphrase() + if _test: print(passphrase) + return passphrase + else: + return 'E' # passphrase canceled + +class Passphrase_Dialog(Passphrase_Dialog): + def __init__(self): + self.KKDisconnect = False + self.passphrase = None + self.enterClicked = False + return + + def setupUi(self, Dialog): + super(Passphrase_Dialog, self).setupUi(Dialog) + self.Dialog = Dialog + self.EnterButton.clicked.connect(self.Enter) + + def Enter(self): + self.passphrase = self.passphraseLineEdit.text() + self.enterClicked = True + self.Dialog.close() + + def getEnterClicked(self): + return self.enterClicked + + def getPassphrase(self): + return self.passphrase + + def getKKDisconnect(self): + return self.KKDisconnect + + class RemAcc_Dialog(RemAcc_Dialog): def __init__(self, client, authOps): self.client = client diff --git a/authenticator/passphraseDialog.ui b/authenticator/passphraseDialog.ui new file mode 100644 index 00000000..2f09cd5e --- /dev/null +++ b/authenticator/passphraseDialog.ui @@ -0,0 +1,101 @@ + + + PassphraseDialog + + + + 0 + 0 + 902 + 163 + + + + Enter Passphrase + + + background-color:rgb(40, 40, 40); + + + true + + + + + 50 + 10 + 101 + 21 + + + + + 18 + + + + color: rgb(255, 255, 255); + + + + <html><head/><body><p>Passphrase:</p></body></html> + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + 50 + 40 + 731 + 25 + + + + + 20 + + + + color: rgb(0, 255, 0); + + + + + + 50 + 80 + 111 + 70 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(1, 0, 0); +border-width: 1px; +border-style: solid; +border-color: rgb(0, 255, 0); +color: rgb(0, 255, 0); +text-align:center; + + + Enter + + + + + + diff --git a/authenticator/passphrasedialog.py b/authenticator/passphrasedialog.py new file mode 100644 index 00000000..abc78e0d --- /dev/null +++ b/authenticator/passphrasedialog.py @@ -0,0 +1,67 @@ +# Form implementation generated from reading ui file 'passphraseDialog.ui' +# +# Created by: PyQt6 UI code generator 6.1.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_PassphraseDialog(object): + def setupUi(self, PassphraseDialog): + PassphraseDialog.setObjectName("PassphraseDialog") + PassphraseDialog.resize(902, 163) + PassphraseDialog.setStyleSheet("background-color:rgb(40, 40, 40);") + PassphraseDialog.setModal(True) + self.passphrase = QtWidgets.QLabel(PassphraseDialog) + self.passphrase.setGeometry(QtCore.QRect(50, 10, 101, 21)) + font = QtGui.QFont() + font.setPointSize(18) + self.passphrase.setFont(font) + self.passphrase.setStyleSheet("color: rgb(255, 255, 255);\n" +"") + self.passphrase.setTextFormat(QtCore.Qt.TextFormat.RichText) + self.passphrase.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) + self.passphrase.setWordWrap(True) + self.passphrase.setObjectName("passphrase") + self.passphraseLineEdit = QtWidgets.QLineEdit(PassphraseDialog) + self.passphraseLineEdit.setGeometry(QtCore.QRect(50, 40, 731, 25)) + font = QtGui.QFont() + font.setPointSize(20) + self.passphraseLineEdit.setFont(font) + self.passphraseLineEdit.setStyleSheet("color: rgb(0, 255, 0);") + self.passphraseLineEdit.setObjectName("passphraseLineEdit") + self.EnterButton = QtWidgets.QPushButton(PassphraseDialog) + self.EnterButton.setGeometry(QtCore.QRect(50, 80, 111, 70)) + font = QtGui.QFont() + font.setPointSize(24) + self.EnterButton.setFont(font) + self.EnterButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(1, 0, 0);\n" +"border-width: 1px;\n" +"border-style: solid;\n" +"border-color: rgb(0, 255, 0);\n" +"color: rgb(0, 255, 0);\n" +"text-align:center;") + self.EnterButton.setObjectName("EnterButton") + + self.retranslateUi(PassphraseDialog) + QtCore.QMetaObject.connectSlotsByName(PassphraseDialog) + + def retranslateUi(self, PassphraseDialog): + _translate = QtCore.QCoreApplication.translate + PassphraseDialog.setWindowTitle(_translate("PassphraseDialog", "Enter Passphrase")) + self.passphrase.setText(_translate("PassphraseDialog", "

Passphrase:

")) + self.EnterButton.setText(_translate("PassphraseDialog", "Enter")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + PassphraseDialog = QtWidgets.QDialog() + ui = Ui_PassphraseDialog() + ui.setupUi(PassphraseDialog) + PassphraseDialog.show() + sys.exit(app.exec()) From 1e32069bd8e6f38d1d5a95c2fb17e23f1712a6df Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Wed, 11 Jan 2023 14:48:49 -0700 Subject: [PATCH 28/34] add authdata passphrase testing --- authenticator/AuthMain.ui | 29 +++++++++++++++ authenticator/KeepKeyAuthenticator.py | 51 ++++++++++++++++++++++---- authenticator/authmain.py | 14 +++++++ authenticator/kkQRtest2.png | Bin 51087 -> 1062 bytes keepkeyctl | 10 ++++- tests/test_msg_authfeature.py | 20 +++------- 6 files changed, 101 insertions(+), 23 deletions(-) diff --git a/authenticator/AuthMain.ui b/authenticator/AuthMain.ui index b08f28a9..58fdd371 100644 --- a/authenticator/AuthMain.ui +++ b/authenticator/AuthMain.ui @@ -635,6 +635,35 @@ text-align:left; + + + + 30 + 380 + 200 + 50 + + + + + 24 + + + + border-radius: 10px; +background-color: rgb(103, 103, 103); +border-width: 2px; +border-style: solid; +border-color: black; +color: rgb(255, 255, 255) + + + Wipe All Accounts + + + true + + diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index ab0aec5c..f95db9b1 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -1,6 +1,4 @@ -# This file is part of the TREZOR project. -# -# Copyright (C) 2022 markrypto +# Copyright (C) 2023 markrypto # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by @@ -57,7 +55,8 @@ authErrs = ('Invalid PIN', 'PIN Cancelled', 'PIN expected', 'Auth secret unknown error', 'Account name missing or too long, or seed/message string missing', - 'Authenticator secret can\'t be decoded', 'Authenticator secret seed too large') + 'Authenticator secret can\'t be decoded', 'Authenticator secret seed too large', + 'passphrase incorrect for authdata') class kkClient: def __init__(self): self.client = None @@ -106,8 +105,9 @@ def getClient(self): return self.client def closeClient(self): - self.client.close() - self.client=None + if self.client != None: + self.client.close() + self.client=None def error_popup(errmsg, infotext): # set up error/status message box popup @@ -407,6 +407,7 @@ def setupUi(self, MainWindow): self.ConnectKKButton.clicked.connect(self.KKConnect) self.AddAccButton.clicked.connect(self.addAcc) self.RemoveAccButton.clicked.connect(self.removeAcc) + self.WipeAccButton.clicked.connect(self.wipeData) if _test: self.testButton.clicked.connect(self.Test) else: @@ -440,6 +441,8 @@ def KKConnect(self): self.ConnectKKButton.setText(_translate("MainWindow", "KeepKey\nConnected")) # get accounts if connected self.getAccounts(client) + else: + self.KKDisconnect() def KKDisconnect(self): self.accounts = None @@ -456,9 +459,13 @@ def KKDisconnect(self): def getAccounts(self, client): self.accounts, err = self.authOps.auth_accGet(client) + if _test:print("get accounts err: "+err) if err in ('Invalid PIN', 'PIN Cancelled', 'PIN expected', 'usb err', 'Device not initialized'): self.KKDisconnect() return + if (err == 'passphrase incorrect for authdata'): + self.clearAccounts() + return if _test: print(self.accounts) # reset button list @@ -541,6 +548,20 @@ def removeAcc(self): x = RemAccDialog.exec() # show pin dialog self.getAccounts(client) + def wipeData(self): + client = self.clientOps.getClient() + + if (client != None): + try: + self.authOps.auth_wipe(client) + self.KKDisconnect() # disconnect and reestablish + except PinException as e: + error_popup("Invalid PIN", "") + return + else: + error_popup('Keepkey not connected', '') + return + def Test(self): #test the keepkey function client = self.clientOps.getClient() @@ -592,7 +613,8 @@ def auth_otp(self, client, domain, account): T0 = datetime.now().timestamp() Tslice = int(T0/interval) Tremain = interval - int((int(T0) - Tslice*30)) - if _test: print(Tremain) + if _test: print(b'\x16' + bytes("generateOTPFrom:"+domain+":"+account+":", 'utf8') + + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8')) retval, err = self.sendMsg(client, msg = b'\x16' + bytes("generateOTPFrom:"+domain+":"+account+":", 'utf8') + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8') @@ -603,6 +625,9 @@ def auth_otp(self, client, domain, account): elif err != '': error_popup(err, '') return 'usb err' + + if _test: print("OTP return: "+retval) + return 'noerr' def auth_accGet(self, client): @@ -640,12 +665,22 @@ def auth_accRem(self, client, domain, account): return 'noerr' + def auth_wipe(self, client): + retval, err = self.sendMsg(client, msg = b'\x19' + bytes("wipeAuthdata:", 'utf8')) + if err in authErrs: + error_popup(E.args[1]) + elif err != '': + error_popup(err, '') + return 'usb err' + + return 'noerr' + def auth_test(self, client): # otpauth://totp/KeepKey:markrypto?secret=ZKLHM3W3XAHG4CBN&issuer=kk for msg in ( b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto"+":"+"ZKLHM3W3XAHG4CBN", 'utf8'), b'\x15' + bytes("initializeAuth:"+"Shapeshift"+":"+"markrypto"+":"+"BASE32SECRET2345AB", 'utf8'), - b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto2"+":"+"BASE32SECRET2345AD", 'utf8') + b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto2"+":"+"JBSWY3DPEHPK3PXP", 'utf8') ): retval, err = self.sendMsg(client, msg) if err == 'Authenticator secret storage full': diff --git a/authenticator/authmain.py b/authenticator/authmain.py index 70327d5e..22e92aa4 100644 --- a/authenticator/authmain.py +++ b/authenticator/authmain.py @@ -293,6 +293,19 @@ def setupUi(self, MainWindow): self.Version.setStyleSheet("color: rgb(255, 255, 255);") self.Version.setText("") self.Version.setObjectName("Version") + self.WipeAccButton = QtWidgets.QPushButton(self.centralwidget) + self.WipeAccButton.setGeometry(QtCore.QRect(30, 380, 200, 50)) + font = QtGui.QFont() + font.setPointSize(24) + self.WipeAccButton.setFont(font) + self.WipeAccButton.setStyleSheet("border-radius: 10px;\n" +"background-color: rgb(103, 103, 103);\n" +"border-width: 2px;\n" +"border-style: solid;\n" +"border-color: black;\n" +"color: rgb(255, 255, 255)") + self.WipeAccButton.setAutoDefault(True) + self.WipeAccButton.setObjectName("WipeAccButton") MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 772, 21)) @@ -317,6 +330,7 @@ def retranslateUi(self, MainWindow): self.AccountList.setText(_translate("MainWindow", "Accounts")) self.RemoveAccButton.setText(_translate("MainWindow", "Remove Account")) self.AccountList_2.setText(_translate("MainWindow", "Click on account to generate OTP on KeepKey")) + self.WipeAccButton.setText(_translate("MainWindow", "Wipe All Accounts")) if __name__ == "__main__": diff --git a/authenticator/kkQRtest2.png b/authenticator/kkQRtest2.png index ca4d0cba3f4b48d25dde7c53ec0521e70cfdfa13..6b2f40aba63d5655b9addc7864b8ca245f3cec6a 100644 GIT binary patch literal 1062 zcmeAS@N?(olHy`uVBq!ia0y~yV7dgtjLblhQ%jb-1X7H}LGDfr>(0r5I4tRozK#qG z8~eHcB(eheYymzYu0Z<#|Nl#G&c6#}aTa()7BevL9R^{>O!jxr%9%yl`DM)I8tZ@}7HpeA1^$mb`k>cQ5`+oAqPyB0i`q?p`ws zziRg{ir2uj_0qTB2ce#uayQ#FVcV;+u6*9)-Mfq9o+&b?FqwhDx9o3uUS0mnk7V7A zi(Be{|J%{(Qm9pHbMDXQ|25;?@6Vb(oc5Y?V@_G_nXx^>w!Ix1M0wU3ck6n+I3LK= z{`j|NqiTBxRHyrf?W?`~5A3zy@4Bg0dH)&f4I9CLKe64Yz2n!N_s70|I=NQIw$aaB zzpNdqDfjJzO)Qr`m}~F$_27Hh&2@Di1H_4C&egB~F4upW7=MZ_cI)@}G|q#2yyCyG zKn<0?mAykIDSiHDk; zcVAC`CNs~IchN1w>u+BxMn~t|m4j;ID69OTcl6q-{-Z|A91N_iXFwB~UB_D~#$$4y zPGv6%e>t7!7jv~^F-Hy)%%VX5O%#_q5d4 SrJ}%`%;4$j=d#Wzp$P!*@EZ64 literal 51087 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 +# Copyright (C) 2023 markrypto # Copyright (C) 2012-2016 Marek Palatinus # Copyright (C) 2012-2016 Pavol Rusnak # Copyright (C) 2016 Jochen Hoenicke @@ -441,6 +441,9 @@ class Commands(object): def set_label(self, args): return self.client.apply_settings(label=args.label) + def set_pass(self, args): + return self.client.apply_settings(use_passphrase=args.passphrase_protection) + def clear_session(self, args): return self.client.clear_session() @@ -562,6 +565,7 @@ class Commands(object): get_features.help = 'Retrieve device features and settings' get_public_node.help = 'Get public node of given path' set_label.help = 'Set new wallet label' + set_pass.help = 'Set passphrase protection' clear_session.help = 'Clear session (remove cached PIN, passphrase, etc.)' change_pin.help = 'Change new PIN or remove existing' apply_policy.help = 'Apply a policy' @@ -696,6 +700,10 @@ class Commands(object): # (('-c', '--clear'), {'action': 'store_true', 'default': False}) ) + set_pass.arguments = ( + (('-r', '--passphrase-protection'), {'action': 'store_true', 'default': False}), + ) + change_pin.arguments = ( (('-r', '--remove'), {'action': 'store_true', 'default': False}), ) diff --git a/tests/test_msg_authfeature.py b/tests/test_msg_authfeature.py index 99cf5dec..e394f4da 100644 --- a/tests/test_msg_authfeature.py +++ b/tests/test_msg_authfeature.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 markrypto +# Copyright (C) 2023 markrypto # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 @@ -32,16 +32,7 @@ def sendMsg(self, client, msg): return retval, err def clearAuthData(self, client): - ctr=0 - while True: - retval, err = self.sendMsg(client, msg = b'\x17' + bytes("getAccount:"+str(ctr), 'utf8')) - if err == '': - retval, err = self.sendMsg(client, msg = b'\x18' + bytes("removeAccount:"+retval, 'utf8')) - else: - break - ctr+=1 - if err == 'Account not found': - return '' + retval, err = self.sendMsg(client, b'\x19' + bytes("wipeAuthdata:", 'utf8')) return err def test_InitGetOTPClear(self): @@ -53,10 +44,11 @@ def test_InitGetOTPClear(self): self.setup_mnemonic_pin_passphrase() self.client.clear_session() self.clearAuthData(self.client) + for msg in ( b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto"+":"+"ZKLHM3W3XAHG4CBN", 'utf8'), b'\x15' + bytes("initializeAuth:"+"Shapeshift"+":"+"markrypto"+":"+"BASE32SECRET2345AB", 'utf8'), - b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto2"+":"+"BACE32SECRET2345AB", 'utf8') + b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto2"+":"+"JBSWY3DPEHPK3PXP", 'utf8') ): retval, err = self.sendMsg(self.client, msg) self.assertEqual(err, '') @@ -68,11 +60,11 @@ def test_InitGetOTPClear(self): for vector in ( (b'\x16' + bytes("generateOTPFrom:KeepKey:markrypto:", 'utf8') + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8'), '910862'), (b'\x16' + bytes("generateOTPFrom:Shapeshift:markrypto:", 'utf8') + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8'), '280672'), - (b'\x16' + bytes("generateOTPFrom:KeepKey:markrypto2:", 'utf8') + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8'), '020352') + (b'\x16' + bytes("generateOTPFrom:KeepKey:markrypto2:", 'utf8') + bytes(str(Tslice), 'utf8') + bytes(":" + str(Tremain), 'utf8'), '660041') ): retval, err = self.sendMsg(self.client, vector[0]) self.assertEqual(err, '') - self.assertEqual(retval, vector[1]) + self.assertEqual(retval[:6], vector[1]) err = self.clearAuthData(self.client) self.assertEqual(err, '') From f0740051a44e8fb5b7823cde246874f3a9f111a6 Mon Sep 17 00:00:00 2001 From: pastaghost <62026038+pastaghost@users.noreply.github.com> Date: Thu, 12 Jan 2023 01:27:32 -0700 Subject: [PATCH 29/34] Update AUTHENTICATOR.md --- authenticator/AUTHENTICATOR.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/authenticator/AUTHENTICATOR.md b/authenticator/AUTHENTICATOR.md index c85a7b28..4e6640fc 100644 --- a/authenticator/AUTHENTICATOR.md +++ b/authenticator/AUTHENTICATOR.md @@ -10,7 +10,12 @@ Make sure you have python-keepkey built and installed: $ python python-keepkey/setup.py build install -KeepKeyAuthenticator.app is built using py2app. The setup file was created from the command line: +KeepKeyAuthenticator.app is built using py2app. +If not already installed, install py2app with: + + $ pip install py2app + +The setup file was created from the command line: $ py2applet --make-setup KeepKeyAuthenticator.py authResources/*.png @@ -30,4 +35,4 @@ M1 Macs and libraries not available for arm64 A lot of libraries are not yet ava 2. Use arch -x86_64 python -mpip install ... to install libraries. The arch command is necessary here to ensure that pip selects variants that are compatible with the x86_64 architecture instead of arm64. 3. Use arch -x86_64 python setup.py py2app --arch x86_64 to build -This results in an application bundle where the launcher is an x86_64 only binary, and where included C extensions and libraries are compatible with that architecture as well. \ No newline at end of file +This results in an application bundle where the launcher is an x86_64 only binary, and where included C extensions and libraries are compatible with that architecture as well. From 0241ecb6e46245c435590381c63ef7f6451587f7 Mon Sep 17 00:00:00 2001 From: pastaghost <62026038+pastaghost@users.noreply.github.com> Date: Thu, 12 Jan 2023 01:30:31 -0700 Subject: [PATCH 30/34] Update AUTHENTICATOR.md --- authenticator/AUTHENTICATOR.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/authenticator/AUTHENTICATOR.md b/authenticator/AUTHENTICATOR.md index 4e6640fc..a3bf1450 100644 --- a/authenticator/AUTHENTICATOR.md +++ b/authenticator/AUTHENTICATOR.md @@ -14,6 +14,10 @@ KeepKeyAuthenticator.app is built using py2app. If not already installed, install py2app with: $ pip install py2app + +Change to the pytthon-keepkey/authenticator directory: + + $ cd authenticator The setup file was created from the command line: From d72e566ba4fe3cdd88ceda2f61fb3766b89d8ce0 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Sun, 15 Jan 2023 10:34:52 -0700 Subject: [PATCH 31/34] updated authenticator doc --- authenticator/AUTHENTICATOR.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authenticator/AUTHENTICATOR.md b/authenticator/AUTHENTICATOR.md index a3bf1450..7b24919b 100644 --- a/authenticator/AUTHENTICATOR.md +++ b/authenticator/AUTHENTICATOR.md @@ -2,7 +2,7 @@ KeepKey Authenticator ============== The KeepKey Authenticator is a standalone app intended to demo the authenticator feature. -It enables the KeepKey device to perform as a hardware off-line one-time passcode (OTP) generator for two factor authentication using the TOPT algorithm. The KeepKey PIN and physical button gives it security features similar to other products such as the Yubikey, but with better PIN security. +It enables the KeepKey device to perform as a hardware off-line one-time passcode (OTP) generator for two factor authentication using the TOPT algorithm. The KeepKey (optional) PIN, (optional) passphrase, and physical button gives it security features similar to other hardware authenticator products, but with better OTP display security. Build for MacOS - Intel ----------------------- From 5c5bd42de740057b3372c9960a3da74bbf400d08 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Fri, 20 Jan 2023 10:12:11 -0700 Subject: [PATCH 32/34] turn off test mode --- authenticator/KeepKeyAuthenticator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index f95db9b1..12f90a93 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -51,7 +51,7 @@ from passphrasedialog import Ui_PassphraseDialog as Passphrase_Dialog # for dev testing -_test = True +_test = False authErrs = ('Invalid PIN', 'PIN Cancelled', 'PIN expected', 'Auth secret unknown error', 'Account name missing or too long, or seed/message string missing', From 7b10dda391da2ce0b984e8c580bdf5aaa00c1bff Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Sun, 22 Jan 2023 11:07:02 -0700 Subject: [PATCH 33/34] better QR parsing --- authenticator/KKQRkey160.png | Bin 0 -> 133336 bytes authenticator/KeepKeyAuthenticator.py | 29 +++++++++++++++++++------- authenticator/kkQRtest2.png | Bin 1062 -> 107389 bytes 3 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 authenticator/KKQRkey160.png diff --git a/authenticator/KKQRkey160.png b/authenticator/KKQRkey160.png new file mode 100644 index 0000000000000000000000000000000000000000..820ee0c12cf87e81f75d1d8dfc890ef522c1e8cd GIT binary patch literal 133336 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 PESzm}^%9h96dnC9l8=KK literal 0 HcmV?d00001 diff --git a/authenticator/KeepKeyAuthenticator.py b/authenticator/KeepKeyAuthenticator.py index 12f90a93..e36377c2 100644 --- a/authenticator/KeepKeyAuthenticator.py +++ b/authenticator/KeepKeyAuthenticator.py @@ -50,7 +50,7 @@ from manualaddacc import Ui_ManualAddAccDialog as ManAddAcc_Dialog from passphrasedialog import Ui_PassphraseDialog as Passphrase_Dialog -# for dev testing +# logs for dev testing _test = False authErrs = ('Invalid PIN', 'PIN Cancelled', 'PIN expected', 'Auth secret unknown error', @@ -334,14 +334,27 @@ def QrScreencap(self): if (data == []): error_popup("QR Code Error", "Could not read QR code") return - data1 = str(data[0][0]).replace("b'",'').replace("'","") + rawdata = str(data[0][0]).replace("b'",'').replace("'","") type1 = str(data[0][1]) - if _test: print(data1) + if _test: print(rawdata) if _test: print(type1) - secret = urlparse(data1).query.split('=')[1].split('&')[0] - domain = urlparse(data1).path.split('/')[1].split(':')[0] - account = urlparse(data1).path.split('/')[1].split(':')[1] + + unqData = urllib.parse.unquote(rawdata) # replace any %xx escapes with single char equivalent + # parse check + pUrl = urlparse(unqData) + if pUrl.scheme == 'otpauth' and pUrl.netloc == 'totp': + try: + secret = pUrl.query.split('=')[1].split('&')[0] + domain = pUrl.path.split('/')[1].split(':')[0] + account = pUrl.path.split('/')[1].split(':')[1] + except (IndexError, ValueError): + error_popup("QR Code Parse Error", ("Could not parse %s" % unqData)) + return + else: + error_popup("QR Code Error", ("invalid otpauth url\n%s" % unqData)) + return + self.KKAddAcc(self.client, secret, domain, account) return @@ -680,7 +693,9 @@ def auth_test(self, client): for msg in ( b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto"+":"+"ZKLHM3W3XAHG4CBN", 'utf8'), b'\x15' + bytes("initializeAuth:"+"Shapeshift"+":"+"markrypto"+":"+"BASE32SECRET2345AB", 'utf8'), - b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto2"+":"+"JBSWY3DPEHPK3PXP", 'utf8') + b'\x15' + bytes("initializeAuth:"+"KeepKey"+":"+"markrypto2"+":"+"JBSWY3DPEHPK3PXP", 'utf8'), + # 160-bit key + b'\x15' + bytes("initializeAuth:"+"Google"+":"+"novicecoingroup"+":"+"liabmylfzm3qta2txzqgcunbp3y76pkb", 'utf8') ): retval, err = self.sendMsg(client, msg) if err == 'Authenticator secret storage full': diff --git a/authenticator/kkQRtest2.png b/authenticator/kkQRtest2.png index 6b2f40aba63d5655b9addc7864b8ca245f3cec6a..dbc71d2e6547309ed29c7185f5bedc087b4d610e 100644 GIT binary patch literal 107389 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 literal 1062 zcmeAS@N?(olHy`uVBq!ia0y~yV7dgtjLblhQ%jb-1X7H}LGDfr>(0r5I4tRozK#qG z8~eHcB(eheYymzYu0Z<#|Nl#G&c6#}aTa()7BevL9R^{>O!jxr%9%yl`DM)I8tZ@}7HpeA1^$mb`k>cQ5`+oAqPyB0i`q?p`ws zziRg{ir2uj_0qTB2ce#uayQ#FVcV;+u6*9)-Mfq9o+&b?FqwhDx9o3uUS0mnk7V7A zi(Be{|J%{(Qm9pHbMDXQ|25;?@6Vb(oc5Y?V@_G_nXx^>w!Ix1M0wU3ck6n+I3LK= z{`j|NqiTBxRHyrf?W?`~5A3zy@4Bg0dH)&f4I9CLKe64Yz2n!N_s70|I=NQIw$aaB zzpNdqDfjJzO)Qr`m}~F$_27Hh&2@Di1H_4C&egB~F4upW7=MZ_cI)@}G|q#2yyCyG zKn<0?mAykIDSiHDk; zcVAC`CNs~IchN1w>u+BxMn~t|m4j;ID69OTcl6q-{-Z|A91N_iXFwB~UB_D~#$$4y zPGv6%e>t7!7jv~^F-Hy)%%VX5O%#_q5d4 SrJ}%`%;4$j=d#Wzp$P!*@EZ64 From b7af582e9a380ad5b440a533734857728966ac85 Mon Sep 17 00:00:00 2001 From: markrypt0 Date: Thu, 26 Jan 2023 10:07:48 -0700 Subject: [PATCH 34/34] remove obsolete test --- tests/test_msg_ethereum_cfunc.py | 118 ------------------------------- 1 file changed, 118 deletions(-) delete mode 100644 tests/test_msg_ethereum_cfunc.py diff --git a/tests/test_msg_ethereum_cfunc.py b/tests/test_msg_ethereum_cfunc.py deleted file mode 100644 index 7efbad50..00000000 --- a/tests/test_msg_ethereum_cfunc.py +++ /dev/null @@ -1,118 +0,0 @@ -# This file is part of the KEEPKEY project. -# -# Copyright (C) 2022 markrypto -# -# This library is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -from base64 import b64encode -import unittest -import common -import binascii -import struct - -import keepkeylib.messages_pb2 as proto -import keepkeylib.types_pb2 as proto_types -from keepkeylib.client import CallException -from keepkeylib.tools import int_to_big_endian - - -# test contract function confirm when contract isn't recognized, e.g., gnosis safe proxy contracts - -class TestMsgEthereumCfunc(common.KeepKeyTest): - - def test_sign_execTx(self): - self.requires_firmware("7.5.2") - self.setup_mnemonic_nopin_nopassphrase() - - sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( - n=[2147483692,2147483708,2147483648,0,0], - nonce=0xab, - gas_price=0x24c988ac00, - gas_limit=0x26249, - value=0x0, - to=binascii.unhexlify('c8fff0d944406a40475a0a8264328aac8d64927b'), # gnosis proxy - address_type=0, - chain_id=1, - # The data below is generally broken into 32-byte chunks except for the function selector (4 bytes_ and - # keccak signatures (4 bytes) - - # Function: execTransaction(address to, uint256 value, bytes data, uint8 operation, uint256 safeTxGas, - # uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, bytes signatures) - data=binascii.unhexlify('6a761202' + # execTransaction - '000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + # to address - '0000000000000000000000000000000000000000000000000000000000000000' + # value in eth - '0000000000000000000000000000000000000000000000000000000000000140' + # offset to data - '0000000000000000000000000000000000000000000000000000000000000000' + # operation {Call, DelegateCall} - '0000000000000000000000000000000000000000000000000000000000000000' + # safeTxGas - '0000000000000000000000000000000000000000000000000000000000000000' + # baseGas - '0000000000000000000000000000000000000000000000000000000000000000' + # gasPrice - '0000000000000000000000000000000000000000000000000000000000000000' + # gasToken (0 if eth) - '0000000000000000000000000000000000000000000000000000000000000000' + # refundReceiver of gas payment (0 if tx.origin) - '00000000000000000000000000000000000000000000000000000000000001c0' + # offset to signatures data - '0000000000000000000000000000000000000000000000000000000000000044' + # data: len of data - 'a9059cbb000000000000000000000000b5bd898fadf4dc19313bc70c932bcee7' + # data payload bytes - 'a90d9bb300000000000000000000000000000000000000000000000000000000' + - '0ee6b28000000000000000000000000000000000000000000000000000000000' + - '0000000000000000000000000000000000000000000000000000000000000041' + # signatures: len of signatures - '00000000000000000000000021c9a94af76b59b171b32fd125a4edf0e9a2ad3e' + # signatures bytes - '0000000000000000000000000000000000000000000000000000000000000000' + - '0100000000000000000000000000000000000000000000000000000000000000') - ) - self.assertEqual(sig_v, 37) - self.assertEqual(binascii.hexlify(sig_r), '4ec68dfe39d7993e55366b305c65d235d4ecb3e0749b3b78830076f878c5b2c2') - self.assertEqual(binascii.hexlify(sig_s), '2c270f8f4b39ba22c06786126df0f544e010ccd7fae621054caa05f6ae947bb8') - - - sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( - n=[2147483692,2147483708,2147483648,0,0], - nonce=0xab, - gas_price=0x24c988ac00, - gas_limit=0x26249, - value=0x0, - to=binascii.unhexlify('c8fff0d944406a40475a0a8264328aac8d64927b'), # gnosis proxy - address_type=0, - chain_id=1, - # The data below is generally broken into 32-byte chunks except for the function selector (4 bytes_ and - # keccak signatures (4 bytes) - - # Function: execTransaction(address to, uint256 value, bytes data, uint8 operation, uint256 safeTxGas, - # uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, bytes signatures) - data=binascii.unhexlify('6a761202' + # execTransaction - '000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' + # to address - '0000000000000000000000000000000000000000000000000000000000000000' + # value in eth - '0000000000000000000000000000000000000000000000000000000000000140' + # offset to data - '0000000000000000000000000000000000000000000000000000000000000000' + # operation {Call, DelegateCall} - '0000000000000000000000000000000000000000000000000000000000126249' + # safeTxGas - '0000000000000000000000000000000000000000000000000000000000026249' + # baseGas - '0000000000000000000000000000000000000000000000000000000024c988ac' + # gasPrice - '0000000000000000000000000000000000000000000000000000000000000000' + # gasToken (0 if eth) - '0000000000000000000000000000000000000000000000000000000000000000' + # refundReceiver of gas payment (0 if tx.origin) - '00000000000000000000000000000000000000000000000000000000000001c0' + # offset to signatures data - '0000000000000000000000000000000000000000000000000000000000000044' + # data: len of data - 'a9059cbb000000000000000000000000b5bd898fadf4dc19313bc70c932bcee7' + # data payload bytes - 'a90d9bb300000000000000000000000000000000000000000000000000000000' + - '0ee6b28000000000000000000000000000000000000000000000000000000000' + - '0000000000000000000000000000000000000000000000000000000000000082' + # signatures: len of signatures - '00000000000000000000000021c9a94af76b59b171b32fd125a4edf0e9a2ad3e' + # signatures bytes - '0000000000000000000000000000000000000000000000000000000000000000' + - '01abcdef10000000000000000021c9a94af76b59b171b32fd125a4edf0e9a2ad' + - '3e00000000000000000000000000000000000000000000000000000000000000' + - '0001000000000000000000000000000000000000000000000000000000000000') - ) - self.assertEqual(sig_v, 38) - self.assertEqual(binascii.hexlify(sig_r), '828dbc0c6002c89e9c4c0a9a0a8e8170fe552780f3c6811d1f7f110bf3056150') - self.assertEqual(binascii.hexlify(sig_s), '0ba6f2d0e6e849cafab77e9316cfa2634abe79345dfd414ac773d1dd1caa7c66') - -if __name__ == '__main__': - unittest.main()