Pairing and fingerprint checking works nicely now

This commit is contained in:
jaseg 2018-11-14 22:00:06 +09:00
parent b84de745fa
commit 66f9e82c5c
6 changed files with 240 additions and 20 deletions

88
USB_icon.svg Normal file
View file

@ -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>

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -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:

View file

@ -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)
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)
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())
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

BIN
secureusb_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -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);

20
src/tracing.h Normal file
View file

@ -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