diff options
author | jaseg <git@jaseg.net> | 2018-11-14 22:00:06 +0900 |
---|---|---|
committer | jaseg <git@jaseg.net> | 2018-11-14 22:00:06 +0900 |
commit | 66f9e82c5ca313fb90edff6a9d1956c02c973934 (patch) | |
tree | 69ae4d6ed1a10a37e511710f68021fda5214d032 | |
parent | b84de745fac0708ca67b9aead7f9f2b36891cd35 (diff) | |
download | secure-hid-66f9e82c5ca313fb90edff6a9d1956c02c973934.tar.gz secure-hid-66f9e82c5ca313fb90edff6a9d1956c02c973934.tar.bz2 secure-hid-66f9e82c5ca313fb90edff6a9d1956c02c973934.zip |
Pairing and fingerprint checking works nicely now
-rw-r--r-- | USB_icon.svg | 88 | ||||
-rwxr-xr-x | hexnoise.py | 26 | ||||
-rwxr-xr-x | pairing.py | 80 | ||||
-rw-r--r-- | secureusb_icon.png | bin | 0 -> 13260 bytes | |||
-rw-r--r-- | src/demo.c | 46 | ||||
-rw-r--r-- | src/tracing.h | 20 |
6 files changed, 240 insertions, 20 deletions
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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + width="24" + height="24" + viewBox="0 0 24.000001 24" + id="Layer_1" + xml:space="preserve" + sodipodi:docname="USB_icon.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + inkscape:export-filename="/home/user/ref/libusbhost/secureusb_icon.png" + inkscape:export-xdpi="1280" + inkscape:export-ydpi="1280"><metadata + id="metadata7"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="1" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1030" + id="namedview5" + showgrid="false" + units="px" + inkscape:zoom="22.627417" + inkscape:cx="6.8983878" + inkscape:cy="13.626763" + inkscape:window-x="0" + inkscape:window-y="50" + inkscape:window-maximized="0" + inkscape:current-layer="Layer_1" /><defs + id="defs1337" /> +<path + d="M 9.1909916,1.0365848 6.7543191,5.2559831 H 8.4925226 V 15.009993 L 4.0559868,10.810749 C 3.7695331,10.453379 3.568585,9.9857932 3.5574545,9.5048004 c 0,-1.9458109 -5.065e-4,-1.7888256 -0.00136,-2.2141627 0.8214146,-0.2883098 1.414308,-1.0627543 1.414308,-1.983421 0,-1.1647037 -0.9451206,-2.1099103 -2.1102466,-2.1099103 -1.1656325,0 -2.11041486,0.9451205 -2.11041486,2.1099103 0,0.9206667 0.59255516,1.6951112 1.41329516,1.983421 l -5.905e-4,2.1728424 c 0,0.9446159 0.5182659,1.9344279 1.1258318,2.5644249 -0.018039,-0.01719 -0.037271,-0.03508 3.365e-4,10e-4 0.014994,0.01332 4.7065449,4.455256 4.7065449,4.455256 0.2860334,0.356613 0.4857161,0.823945 0.4971849,1.304601 v 8.752206 c -1.6117146,0.323388 -2.8260028,1.746635 -2.8260028,3.45389 0,1.946487 1.5778153,3.524302 3.5237111,3.524302 1.9464859,0 3.5243859,-1.577815 3.5243859,-3.524302 0,-1.707592 -1.2153,-3.130838 -2.8283611,-3.454226 v -8.709455 c 0,-0.0061 3.365e-4,-0.01224 0,-0.01856 v -5.301631 c 0.012235,-0.479729 0.2121621,-0.94647 0.4985321,-1.302745 0,0 4.691197,-4.4409224 4.706376,-4.4546676 0.03771,-0.035583 0.01805,-0.017872 3.38e-4,-3.365e-4 0.60748,-0.6299968 1.125408,-1.6202312 1.125408,-2.565013 L 16.215975,3.454345 h 1.414729 V -0.76611257 H 13.410639 V 3.4543809 h 1.412874 c 0,0 -0.0015,-1.7403408 -0.0015,0.7750052 -0.01102,0.4810766 -0.211742,0.9491696 -0.498196,1.306372 L 9.8863699,9.7359362 V 5.2559831 h 1.7409841 z" + id="path1334" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none" + inkscape:transform-center-x="3.8484292" + inkscape:transform-center-y="16.5482" + sodipodi:nodetypes="cccccccssscccccccssscscccccccccccccccccc" /> +<g + id="g4661" + transform="matrix(0.95637878,0,0,0.95637878,49.375726,8.0349764)" + inkscape:export-xdpi="1280" + inkscape:export-ydpi="1280"><rect + ry="1.6849748" + y="6.75" + x="-39.125" + height="9.8125" + width="12.375" + id="rect4608" + style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.8904658;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><rect + ry="3.9323201" + y="0.68743151" + x="-36.86982" + height="9.4375687" + width="7.8646402" + id="rect4610" + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.69644809;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><g + transform="matrix(0.93546514,0,0,0.93546514,-1.0854549,1.4271834)" + id="g4654"><circle + style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.72307682;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path4627" + cx="-34.051041" + cy="8.8928328" + r="1.84375" /><path + style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="m -34.754167,8.8303324 h 1.40625 l 1.140625,4.8258756 c 0.125,0.616281 -0.508719,1.140625 -1.140625,1.140625 h -1.40625 c -0.631906,0 -1.25,-0.508719 -1.140625,-1.140625 z" + id="rect4629" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccsscc" /></g></g><path + style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + d="M -21.203125 -21.580078 L -21.203125 45.580078 L 45.203125 45.580078 L 45.203125 -21.580078 L -21.203125 -21.580078 z M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 z " + id="rect816" /></svg>
\ 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: @@ -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'<b>Error: {e}!</b>') + + GLib.idle_add(self.finish_pairing) + except hexnoise.ProtocolError as e: + GLib.idle_add(self.label.set_markup, f'<b>Error: {e}</b>') + + def finish_pairing(self): + self.label.set_markup(f'<b>Step 3</b>\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 Binary files differnew file mode 100644 index 0000000..97a68cf --- /dev/null +++ b/secureusb_icon.png @@ -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 <libopencm3/stm32/gpio.h> + +#define TRACING_SET(i) gpio_set(GPIOD, (1<<i)) +#define TRACING_CLEAR(i) gpio_clear(GPIOD, (1<<i)) + +enum tracing_channels { + TR_HID_MESSAGE_HANDLER = 0, + TR_DEBUG_OUT_DMA_IRQ = 1, + TR_HOST_IF_DMA_IRQ = 2, + TR_HOST_IF_USART_IRQ = 3, + TR_USBH_POLL = 4, + TR_HOST_PKT_HANDLER = 5, + TR_NOISE_HANDSHAKE = 6, + TR_RNG = 7, +}; + +#endif |