summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git@jaseg.net>2018-11-14 22:00:06 +0900
committerjaseg <git@jaseg.net>2018-11-14 22:00:06 +0900
commit66f9e82c5ca313fb90edff6a9d1956c02c973934 (patch)
tree69ae4d6ed1a10a37e511710f68021fda5214d032
parentb84de745fac0708ca67b9aead7f9f2b36891cd35 (diff)
downloadsecure-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.svg88
-rwxr-xr-xhexnoise.py26
-rwxr-xr-xpairing.py80
-rw-r--r--secureusb_icon.pngbin0 -> 13260 bytes
-rw-r--r--src/demo.c46
-rw-r--r--src/tracing.h20
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:
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'<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
new file mode 100644
index 0000000..97a68cf
--- /dev/null
+++ b/secureusb_icon.png
Binary files 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 <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