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 @@
+
+
+
+
\ 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)
-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
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<