def do_send_static(d): ''' Decouples button slot from running instance in case user stops/restarts the plugin while TxDialogs are up. ''' plugin = Plugin.Instance_ref() if plugin: plugin.do_send(d) else: print_error("[cosigner_pool] No plugin.")
def getDevice2FAMode(self): apdu = [0xe0, 0x24, 0x01, 0x00, 0x00, 0x01] # get 2fa mode try: mode = self.dongle.exchange(bytearray(apdu)) return mode except BTChipException as e: print_error('Device getMode Failed') return 0x11
def give_error(self, message, clear_client=False): print_error(message) if not self.signing: self.handler.show_error(message) else: self.signing = False if clear_client: self.client = None raise Exception(message)
def __init__(self, config): super().__init__(None) # Top-level Object if Exception_Hook._instance: return # This is ok, we will be GC'd later. Exception_Hook._instance = self # strong reference to self should keep us alive until uninstall() is called self.config = config sys.excepthook = self.handler # yet another strong reference. We really won't die unless uninstall() is called self._report_exception.connect(_show_window) print_error("[{}] Installed.".format(__class__.__qualname__)) finalization_print_error( self, "[{}] Finalized.".format(__class__.__qualname__)) destroyed_print_error(self)
def qr_input(self, callback=None): if self.qr_dialog: # Re-entrancy prevention -- there is some lag between when the user # taps the QR button and the modal dialog appears. We want to # prevent multiple instances of the dialog from appearing, so we # must do this. util.print_error( "[ScanQRTextEdit] Warning: QR dialog is already presented, ignoring." ) return from . import ElectrumGui if ElectrumGui.instance.warn_if_cant_import_qrreader(self): return from oregano import get_config from .qrreader import QrReaderCameraDialog try: self.qr_dialog = QrReaderCameraDialog( parent=self.top_level_window()) def _on_qr_reader_finished(success: bool, error: str, result): if self.qr_dialog: self.qr_dialog.deleteLater() self.qr_dialog = None if not success: if error: self.show_error(error) return if not result: result = '' if self.allow_multi: new_text = self.text() + result + '\n' else: new_text = result self.setText(new_text) if callback and success: callback(result) self.qr_dialog.qr_finished.connect(_on_qr_reader_finished) self.qr_dialog.start_scan(get_config().get_video_device()) except Exception as e: if util.is_verbose: import traceback traceback.print_exc() self.qr_dialog = None self.show_error(str(e))
def verify_ssl_socket(host, port, timeout=5.0): path = (Network.get_instance() and Network.get_instance().config and Network.get_instance().config.path) or None if not path: print_error("verify_ssl_socket: no config path!") return False server = "{}:{}:s".format(host, port) q = queue.Queue() c = Connection(server, q, path) socket = None try: server, socket = q.get(timeout=timeout) except queue.Empty: pass ret = bool(socket and socket.fileno() > -1) if socket: socket.close() del (q, c, socket, server) return ret
def patch(dark: bool = False, darkstyle_ver: tuple = None): if not dark: return app = QtWidgets.QApplication.instance() if darkstyle_ver is None or darkstyle_ver < (2, 6, 8): # only apply this patch to qdarkstyle < 2.6.8. # 2.6.8 and above seem to not need it. style_sheet = app.styleSheet() style_sheet = style_sheet + ''' QWidget:disabled { color: hsl(0, 0, 50%); } QPushButton:disabled { border-color: hsl(0, 0, 50%); color: hsl(0, 0, 50%); } ''' app.setStyleSheet(style_sheet) print_error( "[style_patcher] qdarkstyle < 2.6.8 detected; stylesheet patch #1 applied" ) else: # This patch is for qdarkstyle >= 2.6.8. style_sheet = app.styleSheet() style_sheet = style_sheet + ''' /* PayToEdit was being cut off on QDatkStyle >= 2.6.8. This fixes that. */ ButtonsTextEdit { padding: 0px; } /* History List labels, when editing, were looking terrible in Windows and were cut off. This fixes that.*/ QTreeWidget QLineEdit { show-decoration-selected: 1; padding: 0px; } ''' app.setStyleSheet(style_sheet) print_error( "[style_patcher] qdarkstyle >= 2.6.8 detected; stylesheet patch #2 applied" )
def hid_send_plain(self, msg): reply = "" try: serial_number = self.dbb_hid.get_serial_number_string() if "v2.0." in serial_number or "v1." in serial_number: hidBufSize = 4096 self.dbb_hid.write('\0' + msg + '\0' * (hidBufSize - len(msg))) r = bytearray() while len(r) < hidBufSize: r += bytearray(self.dbb_hid.read(hidBufSize)) else: self.hid_send_frame(msg) r = self.hid_read_frame() r = r.rstrip(b' \t\r\n\0') r = r.replace(b"\0", b'') r = to_string(r, 'utf8') reply = json.loads(r) except Exception as e: print_error('Exception caught ' + str(e)) return reply
def reset_seed_dialog(self, msg): print_error("In reset_seed_dialog") parent = self.top_level_window() d = WindowModalDialog(parent, _("Enter PIN")) pw = QLineEdit() pw.setEchoMode(2) pw.setMinimumWidth(200) cb_reset_2FA = QCheckBox(_('Also reset 2FA')) vbox = QVBoxLayout() vbox.addWidget(WWLabel(msg)) vbox.addWidget(pw) vbox.addWidget(cb_reset_2FA) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) d.setLayout(vbox) passphrase = pw.text() if d.exec_() else None reset_2FA= cb_reset_2FA.isChecked() return (passphrase, reset_2FA)
def change_pin(self, client): print_error("In change_pin") msg_oldpin = _("Enter the current PIN for your Satochip:") msg_newpin = _("Enter a new PIN for your Satochip:") msg_confirm = _("Please confirm the new PIN for your Satochip:") msg_error= _("The PIN values do not match! Please type PIN again!") msg_cancel= _("PIN Change cancelled!") (is_pin, oldpin, newpin) = client.PIN_change_dialog(msg_oldpin, msg_newpin, msg_confirm, msg_error, msg_cancel) if (not is_pin): return oldpin= list(oldpin) newpin= list(newpin) (response, sw1, sw2)= client.cc.card_change_PIN(0, oldpin, newpin) if (sw1==0x90 and sw2==0x00): msg= _("PIN changed successfully!") client.handler.show_message(msg) else: msg= _("Failed to change PIN!") client.handler.show_error(msg)
def show_values(self, client): print_error("Show value!") sw_rel= 'v' + str(SATOCHIP_PROTOCOL_MAJOR_VERSION) + '.' + str(SATOCHIP_PROTOCOL_MINOR_VERSION) self.sw_version.setText('<tt>%s' % sw_rel) (response, sw1, sw2, d)=client.cc.card_get_status() if (sw1==0x90 and sw2==0x00): fw_rel= 'v' + str(d["protocol_major_version"]) + '.' + str(d["protocol_minor_version"]) self.fw_version.setText('<tt>%s' % fw_rel) #is_seeded? if len(response) >=10: self.is_seeded.setText('<tt>%s' % "yes") if d["is_seeded"] else self.is_seeded.setText('<tt>%s' % "no") else: #for earlier versions try: client.cc.card_bip32_get_authentikey() self.is_seeded.setText('<tt>%s' % "yes") except Exception: self.is_seeded.setText('<tt>%s' % "no") # needs2FA? if d["needs2FA"]: self.needs_2FA.setText('<tt>%s' % "yes") else: self.needs_2FA.setText('<tt>%s' % "no") # needs secure channel if d["needs_secure_channel"]: self.needs_SC.setText('<tt>%s' % "yes") else: self.needs_SC.setText('<tt>%s' % "no") else: fw_rel= "(unitialized)" self.fw_version.setText('<tt>%s' % fw_rel) self.needs_2FA.setText('<tt>%s' % "(unitialized)") self.is_seeded.setText('<tt>%s' % "no") self.needs_SC.setText('<tt>%s' % "(unknown)")
def hid_send_encrypt(self, msg): sha256_byte_len = 32 reply = "" try: encryption_key, authentication_key = derive_keys(self.password) msg = EncodeAES_bytes(encryption_key, msg) hmac_digest = hmac_oneshot(authentication_key, msg, hashlib.sha256) authenticated_msg = base64.b64encode(msg + hmac_digest) reply = self.hid_send_plain(authenticated_msg) if 'ciphertext' in reply: b64_unencoded = bytes(base64.b64decode(''.join(reply["ciphertext"]))) reply_hmac = b64_unencoded[-sha256_byte_len:] hmac_calculated = hmac_oneshot(authentication_key, b64_unencoded[:-sha256_byte_len], hashlib.sha256) if not hmac.compare_digest(reply_hmac, hmac_calculated): raise Exception("Failed to validate HMAC") reply = DecodeAES_bytes(encryption_key, b64_unencoded[:-sha256_byte_len]) reply = to_string(reply, 'utf8') reply = json.loads(reply) if 'error' in reply: self.password = None except Exception as e: print_error('Exception caught ' + str(e)) return reply
def present(self, frame: QVideoFrame) -> bool: if not frame.isValid(): return False image_format = QVideoFrame.imageFormatFromPixelFormat( frame.pixelFormat()) if image_format == QVideoFrame.Format_Invalid: print_error( _('QR code scanner for video frame with invalid pixel format')) return False if not frame.map(QAbstractVideoBuffer.ReadOnly): print_error(_('QR code scanner failed to map video frame')) return False try: img = QImage(frame.bits(), frame.width(), frame.height(), image_format) # Check whether we need to flip the image on any axis surface_format = self.surfaceFormat() flip_x = surface_format.isMirrored() flip_y = surface_format.scanLineDirection( ) == QVideoSurfaceFormat.BottomToTop # Mirror the image if needed if flip_x or flip_y: img = img.mirrored(flip_x, flip_y) # Create a copy of the image so the original frame data can be freed img = img.copy() finally: frame.unmap() self.frame_available.emit(img) return True
def reset_seed(self, client): print_error("In reset_seed") # pin msg = ''.join([ _("WARNING!\n"), _("You are about to reset the seed of your Satochip. This process is irreversible!\n"), _("Please be sure that your wallet is empty and that you have a backup of the seed as a precaution.\n\n"), _("To proceed, enter the PIN for your Satochip:") ]) (password, reset_2FA)= self.reset_seed_dialog(msg) if (password is None): return pin = password.encode('utf8') pin= list(pin) # if 2FA is enabled, get challenge-response hmac=[] if (client.cc.needs_2FA==None): (response, sw1, sw2, d)=client.cc.card_get_status() if client.cc.needs_2FA: # challenge based on authentikey authentikeyx= bytearray(client.cc.parser.authentikey_coordx).hex() # format & encrypt msg import json msg= {'action':"reset_seed", 'authentikeyx':authentikeyx} msg= json.dumps(msg) (id_2FA, msg_out)= client.cc.card_crypt_transaction_2FA(msg, True) d={} d['msg_encrypt']= msg_out d['id_2FA']= id_2FA # print_error("encrypted message: "+msg_out) #print_error("id_2FA: "+id_2FA) #do challenge-response with 2FA device... client.handler.show_message('2FA request sent! Approve or reject request on your second device.') Satochip2FA.do_challenge_response(d) # decrypt and parse reply to extract challenge response try: reply_encrypt= d['reply_encrypt'] except Exception as e: self.give_error("No response received from 2FA.\nPlease ensure that the Satochip-2FA plugin is enabled in Tools>Optional Features", True) reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False) print_error("challenge:response= "+ reply_decrypt) reply_decrypt= reply_decrypt.split(":") chalresponse=reply_decrypt[1] hmac= list(bytes.fromhex(chalresponse)) # send request (response, sw1, sw2) = client.cc.card_reset_seed(pin, hmac) if (sw1==0x90 and sw2==0x00): msg= _("Seed reset successfully!\nYou should close this wallet and launch the wizard to generate a new wallet.") client.handler.show_message(msg) #to do: close client? else: msg= _(f"Failed to reset seed with error code: {hex(sw1)}{hex(sw2)}") client.handler.show_error(msg) if reset_2FA and client.cc.needs_2FA: # challenge based on ID_2FA # format & encrypt msg import json msg= {'action':"reset_2FA"} msg= json.dumps(msg) (id_2FA, msg_out)= client.cc.card_crypt_transaction_2FA(msg, True) d={} d['msg_encrypt']= msg_out d['id_2FA']= id_2FA # print_error("encrypted message: "+msg_out) print_error("id_2FA: "+id_2FA) #do challenge-response with 2FA device... client.handler.show_message('2FA request sent! Approve or reject request on your second device.') Satochip2FA.do_challenge_response(d) # decrypt and parse reply to extract challenge response try: reply_encrypt= d['reply_encrypt'] except Exception as e: self.give_error("No response received from 2FA.\nPlease ensure that the Satochip-2FA plugin is enabled in Tools>Optional Features", True) reply_decrypt= client.cc.card_crypt_transaction_2FA(reply_encrypt, False) print_error("challenge:response= "+ reply_decrypt) reply_decrypt= reply_decrypt.split(":") chalresponse=reply_decrypt[1] hmac= list(bytes.fromhex(chalresponse)) # send request (response, sw1, sw2) = client.cc.card_reset_2FA_key(hmac) if (sw1==0x90 and sw2==0x00): msg= _("2FA reset successfully!") client.cc.needs_2FA= False client.handler.show_message(msg) else: msg= _(f"Failed to reset 2FA with error code: {hex(sw1)}{hex(sw2)}") client.handler.show_error(msg)
from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton) PLATFORM_LIBS = { 'Linux': 'libportaudio.so', } err_reason = 'Audio MODEM is available.' if platform.system() in PLATFORM_LIBS: try: import amodem.audio import amodem.main import amodem.config print_error(err_reason) amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr)) amodem.log.setLevel(amodem.logging.INFO) except ImportError: amodem = None err_reason = 'Audio MODEM is not found.' print_error(err_reason) else: amodem = None err_reason = "Audio modem plugin is not available for this platform" print_error(err_reason) class Plugin(BasePlugin): def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name)
def update_status(self, b): print_error('trezor status', b)
def uninstall(): sys.excepthook = sys.__excepthook__ if Exception_Hook._instance: print_error("[{}] Uninstalled.".format(__class__.__qualname__)) Exception_Hook._instance = None
def sign_transaction(self, tx, password, *, use_cache=False): if tx.is_complete(): return try: p2pkhTransaction = True derivations = self.get_tx_derivations(tx) inputhasharray = [] hasharray = [] pubkeyarray = [] # Build hasharray from inputs for i, txin in enumerate(tx.inputs()): if txin['type'] == 'coinbase': self.give_error("Coinbase not supported") # should never happen if txin['type'] != 'p2pkh': p2pkhTransaction = False for x_pubkey in txin['x_pubkeys']: if x_pubkey in derivations: index = derivations.get(x_pubkey) inputPath = "%s/%d/%d" % (self.get_derivation(), index[0], index[1]) inputHash = Hash(binascii.unhexlify(tx.serialize_preimage(i))) hasharray_i = {'hash': to_hexstr(inputHash), 'keypath': inputPath} hasharray.append(hasharray_i) inputhasharray.append(inputHash) break else: self.give_error("No matching x_key for sign_transaction") # should never happen # Build pubkeyarray from outputs for _type, address, amount in tx.outputs(): info = tx.output_info.get(address) if info is not None: index, xpubs, m, script_type = info changePath = self.get_derivation() + "/%d/%d" % index changePubkey = self.derive_pubkey(index[0], index[1]) pubkeyarray_i = {'pubkey': changePubkey, 'keypath': changePath} pubkeyarray.append(pubkeyarray_i) # Special serialization of the unsigned transaction for # the mobile verification app. # At the moment, verification only works for p2pkh transactions. if p2pkhTransaction: class CustomTXSerialization(Transaction): @classmethod def input_script(self, txin, estimate_size=False, sign_schnorr=False): if txin['type'] == 'p2pkh': return Transaction.get_preimage_script(txin) if txin['type'] == 'p2sh': # Multisig verification has partial support, but is disabled. This is the # expected serialization though, so we leave it here until we activate it. return '00' + push_script(Transaction.get_preimage_script(txin)) raise Exception("unsupported type %s" % txin['type']) tx_dbb_serialized = CustomTXSerialization(tx.serialize()).serialize() else: # We only need this for the signing echo / verification. tx_dbb_serialized = None # Build sign command dbb_signatures = [] steps = math.ceil(1.0 * len(hasharray) / self.maxInputs) for step in range(int(steps)): hashes = hasharray[step * self.maxInputs : (step + 1) * self.maxInputs] msg = { "sign": { "data": hashes, "checkpub": pubkeyarray, }, } if tx_dbb_serialized is not None: msg["sign"]["meta"] = to_hexstr(Hash(tx_dbb_serialized)) msg = json.dumps(msg).encode('ascii') dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): raise Exception("Could not sign transaction.") reply = dbb_client.hid_send_encrypt(msg) if 'error' in reply: raise Exception(reply['error']['message']) if 'echo' not in reply: raise Exception("Could not sign transaction.") if self.plugin.is_mobile_paired() and tx_dbb_serialized is not None: reply['tx'] = tx_dbb_serialized self.plugin.comserver_post_notification(reply) if steps > 1: self.handler.show_message(_("Signing large transaction. Please be patient ...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + " " + _("(Touch {} of {})").format((step + 1), steps) + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout.") + "\n\n") else: self.handler.show_message(_("Signing transaction...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for 3 seconds.") + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout.")) # Send twice, first returns an echo for smart verification reply = dbb_client.hid_send_encrypt(msg) self.handler.finished() if 'error' in reply: if reply["error"].get('code') in (600, 601): # aborted via LED short touch or timeout raise UserCancelled() raise Exception(reply['error']['message']) if 'sign' not in reply: raise Exception("Could not sign transaction.") dbb_signatures.extend(reply['sign']) # Fill signatures if len(dbb_signatures) != len(tx.inputs()): raise Exception("Incorrect number of transactions signed.") # Should never occur for i, txin in enumerate(tx.inputs()): num = txin['num_sig'] for pubkey in txin['pubkeys']: signatures = list(filter(None, txin['signatures'])) if len(signatures) == num: break # txin is complete ii = txin['pubkeys'].index(pubkey) signed = dbb_signatures[i] if 'recid' in signed: # firmware > v2.1.1 recid = int(signed['recid'], 16) s = binascii.unhexlify(signed['sig']) h = inputhasharray[i] pk = MyVerifyingKey.from_signature(s, recid, h, curve = SECP256k1) pk = to_hexstr(point_to_ser(pk.pubkey.point, True)) elif 'pubkey' in signed: # firmware <= v2.1.1 pk = signed['pubkey'] if pk != pubkey: continue sig_r = int(signed['sig'][:64], 16) sig_s = int(signed['sig'][64:], 16) sig = sigencode_der(sig_r, sig_s, generator_secp256k1.order()) txin['signatures'][ii] = to_hexstr(sig) + '41' tx._inputs[i] = txin except UserCancelled: raise except BaseException as e: self.give_error(e, True) else: print_error("Transaction is_complete", tx.is_complete()) tx.raw = tx.serialize()
from pysatochip.CardConnector import CardConnector, UninitializedSeedError, logger from pysatochip.JCconstants import JCconstants from pysatochip.TxParser import TxParser from pysatochip.Satochip2FA import Satochip2FA from pysatochip.ecc import CURVE_ORDER, der_sig_from_r_and_s, get_r_and_s_from_der_sig, ECPubkey from pysatochip.version import SATOCHIP_PROTOCOL_MAJOR_VERSION, SATOCHIP_PROTOCOL_MINOR_VERSION, SATOCHIP_PROTOCOL_VERSION #pyscard from smartcard.sw.SWExceptions import SWException from smartcard.Exceptions import CardConnectionException, CardRequestTimeoutException from smartcard.CardType import AnyCardType from smartcard.CardRequest import CardRequest LIBS_AVAILABLE = True except: LIBS_AVAILABLE = False print_error("[satochip] failed to import requisite libraries, please install the 'pyscard' and 'pysatochip' libraries") print_error("[satochip] satochip will not not be available") raise logging.basicConfig(level=logging.DEBUG, format='%(levelname)s [%(module)s] %(funcName)s | %(message)s') #debugSatochip # debug: smartcard reader ids SATOCHIP_VID= 0x096E SATOCHIP_PID= 0x0503 MSG_USE_2FA= _("Do you want to use 2-Factor-Authentication (2FA)?\n\nWith 2FA, " "any transaction must be confirmed on a second device such as your " "smartphone. First you have to install the Satochip-2FA android app on " "google play. Then you have to pair your 2FA device with your Satochip " "by scanning the qr-code on the next screen. Warning: be sure to backup " "a copy of the qr-code in a safe place, in case you have to reinstall the app!")