From 66f9e82c5ca313fb90edff6a9d1956c02c973934 Mon Sep 17 00:00:00 2001 From: jaseg Date: Wed, 14 Nov 2018 22:00:06 +0900 Subject: Pairing and fingerprint checking works nicely now --- USB_icon.svg | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++ hexnoise.py | 26 +++++++++++++--- pairing.py | 80 ++++++++++++++++++++++++++++++++++++++++-------- secureusb_icon.png | Bin 0 -> 13260 bytes src/demo.c | 46 ++++++++++++++++++++++++++-- src/tracing.h | 20 ++++++++++++ 6 files changed, 240 insertions(+), 20 deletions(-) create mode 100644 USB_icon.svg create mode 100644 secureusb_icon.png create mode 100644 src/tracing.h diff --git a/USB_icon.svg b/USB_icon.svg new file mode 100644 index 0000000..aeaefff --- /dev/null +++ b/USB_icon.svg @@ -0,0 +1,88 @@ + + + +image/svg+xml + + \ No newline at end of file diff --git a/hexnoise.py b/hexnoise.py index f2c1790..8ae6c4b 100755 --- a/hexnoise.py +++ b/hexnoise.py @@ -4,6 +4,8 @@ import time import enum import sys from contextlib import contextmanager, suppress, wraps +import hashlib +import secrets import serial from cobs import cobs @@ -195,20 +197,33 @@ class Magic: '''.split() class NoiseEngine: - def __init__(self, packetizer, debug=False): + def __init__(self, host_key, packetizer, debug=False): self.debug = debug self.packetizer = packetizer - self.static_local = bytes([ # FIXME - 0xbb, 0xdb, 0x4c, 0xdb, 0xd3, 0x09, 0xf1, 0xa1, 0xf2, 0xe1, 0x45, 0x69, 0x67, 0xfe, 0x28, 0x8c, - 0xad, 0xd6, 0xf7, 0x12, 0xd6, 0x5d, 0xc7, 0xb7, 0x79, 0x3d, 0x5e, 0x63, 0xda, 0x6b, 0x37, 0x5b - ]) + self.static_local = host_key self.proto = NoiseConnection.from_name(b'Noise_XX_25519_ChaChaPoly_BLAKE2s') self.proto.set_as_initiator() self.proto.set_keypair_from_private_bytes(Keypair.STATIC, self.static_local) self.proto.start_handshake() + self.handshake = self.proto.noise_protocol.handshake_state # save for later because someone didn't think self.paired = False self.connected = False + @property + def remote_fingerprint(self): + ''' Return the SHA-256 hash of the remote static key (rs). This can be used to fingerprint the remote party. ''' + return hashlib.sha256(self.handshake.rs.public_bytes).hexdigest() + + @classmethod + def generate_private_key_x25519(kls): + # This is taken from noise-c's reference implementation. This would not be needed had not cryptography/hazmat + # decided noone would ever need serialized x25519 private keys and noiseprotocol stopped just short of implementing + # key generation (who'd need that anyway, amiright?) -.- + key = list(secrets.token_bytes(32)) + key[0] &= 0xF8 + key[31] = (key[31] & 0x7F) | 0x40 + return bytes(key) + @wraps(print) def debug_print(self, *args, **kwargs): if self.debug: @@ -306,6 +321,7 @@ class NoiseEngine: yield user_input elif msg_type is ReportType.PAIRING_SUCCESS: + self.paired = True break elif msg_type is ReportType.PAIRING_ERROR: diff --git a/pairing.py b/pairing.py index 3924755..840e0bc 100755 --- a/pairing.py +++ b/pairing.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import threading +import binascii import re +import os import serial import gi @@ -14,6 +16,7 @@ class PairingWindow(Gtk.Window): Gtk.Window.__init__(self, title='SecureHID pairing') self.noise = noise self.debug = debug + self.trusted = False self.set_border_width(10) self.set_default_size(600, 200) @@ -30,6 +33,16 @@ class PairingWindow(Gtk.Window): self.entry.set_editable(False) self.vbox.pack_start(self.entry, True, True, 0) + self.confirm_button = Gtk.Button(label='Trust this device') + self.confirm_button.connect('clicked', self.confirm_trust) + self.confirm_button.set_sensitive(False) + self.abort_button = Gtk.Button(label='Abort') + self.abort_button.connect('clicked', lambda _foo: self.destroy()) + self.bbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + self.bbox.pack_start(self.confirm_button, True, True, 0) + self.bbox.pack_start(self.abort_button, True, True, 0) + self.vbox.pack_start(self.bbox, True, True, 0) + self.add(self.vbox) self.handshaker = threading.Thread(target=self.pair, daemon=True) @@ -64,9 +77,19 @@ class PairingWindow(Gtk.Window): try: for user_input in self.noise.pairing_messages(): GLib.idle_add(update_text, user_input) - self.destroy() - except noise.ProtocolError as e: - GLib.idle_add(self.label.set_markup, f'Error: {e}!') + + GLib.idle_add(self.finish_pairing) + except hexnoise.ProtocolError as e: + GLib.idle_add(self.label.set_markup, f'Error: {e}') + + def finish_pairing(self): + self.label.set_markup(f'Step 3\n\nConfirm pairing.\n' + f'In case the device did not sound an alarm just now, confirm pairing now using the button below.') + self.confirm_button.set_sensitive(True) + + def confirm_trust(self, _foo): + self.trusted = True + self.destroy() class StatusIcon(Gtk.StatusIcon): @@ -75,12 +98,30 @@ class StatusIcon(Gtk.StatusIcon): self.set_tooltip_text('SecureHID connected') self.set_from_file('secureusb_icon.png') +def run_pairing_gui(port, baudrate, debug=False): + XDG_CONFIG_HOME = os.environ.get('XDG_CONFIG_HOME') or os.path.join(os.path.expandvars('$HOME'), '.config', 'secure_hid') + if not os.path.isdir(XDG_CONFIG_HOME): + os.mkdir(XDG_CONFIG_HOME) + + private_key_file = os.path.join(XDG_CONFIG_HOME, 'host_key.pem') + if not os.path.isfile(private_key_file): + with open(private_key_file, 'w') as f: + f.write(binascii.hexlify(hexnoise.NoiseEngine.generate_private_key_x25519()).decode()) + + known_devices_file = os.path.join(XDG_CONFIG_HOME, 'known_devices') + if not os.path.isfile(known_devices_file): + with open(known_devices_file, 'w') as f: + f.write('# This file contains the hex-encoded SHA-256 fingerprints of the X25519 keys of all trusted SecureHID devices\n') + + with open(private_key_file) as f: + host_key_private = binascii.unhexlify(f.read()) -def run_pairing_gui(serial, baudrate, debug=False): - ser = serial.Serial(serial, baudrate) - packetizer = hexnoise.Packetizer(serial, debug=debug) - noise = hexnoise.NoiseEngine(packetizer, debug=debug) + ser = serial.Serial(port, baudrate) + packetizer = hexnoise.Packetizer(ser, debug=debug) + noise = hexnoise.NoiseEngine(host_key_private, packetizer, debug=debug) noise.perform_handshake() + print('Connected.') + print('Device fingerprint:', noise.remote_fingerprint) if not noise.paired: window = PairingWindow(noise, debug=debug) @@ -88,12 +129,27 @@ def run_pairing_gui(serial, baudrate, debug=False): window.show_all() Gtk.main() - if self.noise.paired: - input_runner = threading.Thread(target=noise.uinput_passthrough, daemon=True) - input_runner.start() + if not window.trusted: + raise SystemError('User abort') - status_icon = StatusIcon() - Gtk.main() + if not noise.paired: + raise SystemError('Unknown noise error') + + with open(known_devices_file, 'a') as f: + f.write(noise.remote_fingerprint) + + else: + with open(known_devices_file) as f: + known_devices = [ l.strip() for l in f.readlines() if not l[0] == '#' ] + + if noise.remote_fingerprint not in known_devices: + raise ValueError('Remote host is untrusted but seems to trust us.') + + input_runner = threading.Thread(target=noise.uinput_passthrough, daemon=True) + input_runner.start() + + status_icon = StatusIcon() + Gtk.main() if __name__ == '__main__': import argparse diff --git a/secureusb_icon.png b/secureusb_icon.png new file mode 100644 index 0000000..97a68cf Binary files /dev/null and b/secureusb_icon.png differ diff --git a/src/demo.c b/src/demo.c index abe8f19..ad73cff 100644 --- a/src/demo.c +++ b/src/demo.c @@ -175,7 +175,6 @@ int pairing_check(struct NoiseState *st, const char *buf) { if (strncasecmp(p, "and", plen)) { /* ignore "and" */ int num = -1; - /* FIXME ignore "and", ignore commata and dots */ for (int i=0; i<256; i++) { if ((!strncasecmp(p, adjectives[i], plen) && plen == strlen(adjectives[i])) || (!strncasecmp(p, nouns[i], plen) && plen == strlen(nouns[i] ))) { @@ -237,7 +236,7 @@ void pairing_input(uint8_t modbyte, uint8_t keycode) { case KEY_BACKSPACE: if (pairing_buf_pos > 0) pairing_buf_pos--; - pairing_buf[pairing_buf_pos] = '\0'; /* FIXME debug */ + pairing_buf[pairing_buf_pos] = '\0'; ch = '\b'; break; @@ -250,7 +249,7 @@ void pairing_input(uint8_t modbyte, uint8_t keycode) { if (pairing_buf_pos < sizeof(pairing_buf)-1) /* allow for terminating null byte */ { pairing_buf[pairing_buf_pos++] = ch; - pairing_buf[pairing_buf_pos] = '\0'; /* FIXME debug */ + pairing_buf[pairing_buf_pos] = '\0'; } else { LOG_PRINTF("Pairing confirmation user input buffer full\n"); @@ -356,6 +355,42 @@ struct dma_usart_file debug_out_s = { }; struct dma_usart_file *debug_out = &debug_out_s; +/* FIXME start unsafe debug code */ +void usart1_isr(void) { + if (USART1_SR & USART_SR_ORE) { /* Overrun handling */ + LOG_PRINTF("USART1 data register overrun\n"); + /* Clear interrupt flag */ + return (void)USART1_DR; + } + + uint8_t data = USART1_DR; /* This automatically acknowledges the IRQ */ + for (size_t i=0; keycode_mapping[i].kc != KEY_NONE; i++) { + struct hid_report report = {0}; + if (keycode_mapping[i].ch[0] == data) + report.modifiers = 0; + else if (keycode_mapping[i].ch[1] == data) + report.modifiers = MOD_LSHIFT; + else continue; + + report.keycodes[0] = keycode_mapping[i].kc; + pairing_parse_report(&report, 8); + break; + } + LOG_PRINTF(" %02x ", data); + if (data == 0x7f) { + struct hid_report report = {.modifiers=0, .keycodes={KEY_BACKSPACE, 0}}; + pairing_parse_report(&report, 8); + } else if (data == '\r') { + struct hid_report report = {.modifiers=0, .keycodes={KEY_ENTER, 0}}; + pairing_parse_report(&report, 8); + LOG_PRINTF("\n"); + } + + struct hid_report report = {0}; + pairing_parse_report(&report, 8); +} +/* end unsafe debug code */ + void DMA_ISR(DEBUG_USART_DMA_NUM, DEBUG_USART_DMA_STREAM_NUM)(void) { TRACING_SET(TR_DEBUG_OUT_DMA_IRQ); if (dma_get_interrupt_flag(debug_out->dma, debug_out->stream, DMA_FEIF)) { @@ -384,6 +419,11 @@ int main(void) #ifdef USART_DEBUG usart_dma_init(debug_out); + /* FIXME start unsafe debug code */ + usart_enable_rx_interrupt(debug_out->usart); + nvic_enable_irq(NVIC_USART1_IRQ); + nvic_set_priority(NVIC_USART1_IRQ, 3<<4); + /* end unsafe debug code */ #endif usart_dma_init(usart2_out); diff --git a/src/tracing.h b/src/tracing.h new file mode 100644 index 0000000..9aa9216 --- /dev/null +++ b/src/tracing.h @@ -0,0 +1,20 @@ +#ifndef __TRACING_H__ +#define __TRACING_H__ + +#include + +#define TRACING_SET(i) gpio_set(GPIOD, (1<