Compare commits

..

No commits in common. "a6a4c1a6ccba362924778d60bb5204e51034a33b" and "880916166bea15d3ef68de4e81dc72847d1e0ca8" have entirely different histories.

4 changed files with 76 additions and 223 deletions

View File

@ -4,28 +4,28 @@ purpose
=======
passkey is a USB dongle to paste credentials.
It appears as a serial port to enter the credentials, and HID keyboard to paste them.
It appears a serial port to enter the credentials, and HID keyboard to paste them.
Usage:
- insert the passkey dongle in the USB port, with "passkey" text facing up
- insert the passkey dongle in the USB port, buttons facing up
- it will appear as serial port to enter the credentials, and HID keyboard to paste them
- the red light will blink, indicating no credentials have been entered yet
- connect to it using your favourite serial terminal (e.g. [putty](https://putty.org/) or [picocom](https://github.com/npat-efault/picocom)) using any baud rate
- the red light will blink, indicating it no credentials have been entered yet
- connect to it using your favorite serial terminal (e.g. [putty](https://putty.org/) or [picocom](https://github.com/npat-efault/picocom)) using any baud rate
- press 'c' to enter your credentials to paste (username and password)
- the light will remain on
- press one button of the dongle for it to just paste the password
- press the other button to paste the username and password (tab separated)
You can configure passkey over the serial terminal:
You can configure it over the serial terminal:
- press 'h' to list all available options
- press 'b' to swap the buttons, changing which is for the password or username and password
- press 'l' to set which keyboard layout should be used: use the same are configured in your OS, else some letter in the pasted credentials might be different
- press 'b' to swap the button, changing which is for the password or username and password
- press 'l' to set using keyboard layout should be used: use the same are configured in your OS, else the pasted credentials might not be exactly the ones you entered
- by default the credentials will be cleared 12 hours after they have been entered. Press 'g' to change this time, up to 12 hours
- by default the credentials will be cleared 3 hours after the last time they have been pasted. Press 'r' to change this time, up to 3 hours
- press 'a' to authenticate the device. Enter a random word, and it will provide an URL and a corresponding result. Click on the link, and if the response is the same, the device is running the original firmware.
- press 'k' to enter your own key, which you need to remember. You can then authenticate the device locally (without using the link), using any SHA256 calculator.
- press 'a' to authenticate the device. Enter a random word, and it will provide a corresponding result. Copy the provided text to the [homepage](https://passkey.cuvoodoo.info/). If the response is the same, the device is running the original firmware.
- press 'k' to enter your own key, which you need to remember. You can then authenticate the device locally (without using the homepage), using any SHA256 calculator.
security
========
@ -43,61 +43,45 @@ You can clear the credentials by:
- unplugging the dongle
- pressing both buttons at the same time
- pressing 4 times CapsLock or NumLock within 2 seconds on your keyboard
- pressing 4 times CapsLock within 2 seconds on your keyboard
- pressing 4 times NumLock within 2 seconds on your keyboard
The credentials will be cleared up to 12 hours after they have been entered.
The credentials will be cleared up to 3 hours after the last time they have been pasted.
This decreases the risk of having them leaked if the dongle is left unattended for too long.
This decreases the risk of having them leaked if the device is left unattended for too long.
These timeouts can be configured over the serial port.
The dongle can be locked after been programmed.
The dongle is locked after been programmed.
This prevents from:
- using the SWD interface to attach a debugger on the test points and dump the credentials from the running system
- using the SWD interface to attach a debugger on the test point and dump the credentials from the running system
- using the bootloader to flash a new malicious firmware (e.g. that could store the credentials on non-volatile memory and reveal them later)
- mass erase the device, to re-enable debugging and flashing
The flash memory can also write protected.
Only the last 4 kB of flash are re-writable, as they are used to store the configuration set over serial.
This makes it very hard from exploiting the runtime firmware and overwriting it.
The flash memory is also write protected.
Only the last 4 kB of flash are re-writable, as the are used to store the configuration set over serial.
The makes it very hard from exploiting the runtime firmware and overwrite.
To verify if the firmware is original, use the authentication menu.
If you get the passkey from CuVoodoo, you will also has received it's ID per email.
This ID should the prefix of the authentication token.
Each device has been programmed with an individual key before being locked.
Matching the result with the website ensures the key is the same.
To avoid using the website, you can set a user key, and perform the authentication locally using any SHA256 calculator.
risks
-----
The [STM32F042F6P](https://www.st.com/en/microcontrollers-microprocessors/stm32f042f6.html) micro-controller does not have security certifications.
If the device locking mechanism can be circumvented (e.g. using fault injection), a malicious firmware could be installed.
Because of that, it is not recommended to leave the dongle unattended.
It has a hole to pass a string through and attach it to a key-chain you keep with you.
This string also allows to easily unplug the device from the computer.
It is also recommended to draw on the back of the device, so it becomes unique and hard to tamper with unnoticed.
Obermaier and Tatschner [showed in 20179(https://www.usenix.org/system/files/conference/woot17/woot17-paper-obermaier.pdf) how to degrade the readout protection.
This involved decapsulating the chip, which would make it easy to see that the device has been tampered with once you added a drawing onto it.
Other side channel or fault injection attacks could exist to retrieve the key in a less destructive way.
Since the passkey is inexpensive, just toss it away and get a new one if you suspect it has been tampered with.
The other possibility would be for and attacker to intercept the device on the shipping way, extract the ID and corresponding manufacturer key, and programming them on a new chip, along with a malicious firmware.
This malicious device could still pass authentication.
To prevent this attack, you can manufacturer the passkey yourself.
The board design files and firmware source code are open and available.
The bill of material in around $2, and the parts can be hand soldered with little experience.
It is also recommend to draw on the back of the device, so it becomes unique and hard to tamper with unnoticed.
firmware
========
The devices is based on a [STM32F042F6P](https://www.st.com/en/microcontrollers-microprocessors/stm32f042f6.html) micro-controller.
The firmware uses the [TinyUSB](https://github.com/hathach/tinyusb) USB stack.
If you got the passkey from CuVoodoo, it comes configured with a manufacturer key and locked.
You can also request for a blank devices, and manufacturer it and flash the firmware yourself.
To compile the firmware, you will need and ARM GCC toolchain and run these commands:
The firmware uses [TinyUSB](https://github.com/hathach/tinyusb).
The device comes locked, preventing it to be re-flashed.
I connected a DAPlink programmer to flash it, using the SWD test points on the back.
To compile and flash it:
~~~
git clone https://git.cuvoodoo.info/kingkevin/passkey_fw
@ -106,34 +90,5 @@ git checkout passkey
cd examples/device/hid_cdc_passkey
make BOARD=stm32f042passkey get-deps
make BOARD=stm32f042passkey
~~~
When no firmware has been programmed on the passkey, the STM32 bootloader will allow to flash the firmware over USB using dfu-util:
~~~
make dfu
~~~
Re-plug the dongle in the USB port and the passkey will boot the flashed firmware.
Using the serial terminal you can then enter the manufacturer key by pressing 'K'.
Finally, you can lock the firmware by pressing 'L'.
This it not revocable.
Once locked, you can re-flash the device, change de manufacturer key, or debug the device.
Before it is locked, you can erase the firmware using 'E'.
Re-plugging the dongle will start the bootloader again, allowing to re-flash a firmware.
You can also connect an SWD programmer, such as a DAPlink, on the test points.
The pinout is a follow:
- G: ground
- C: SWCLK
- D: SWDIO
- T: the UART TX used to print debug messages
You can then flash and debug the firmware.
~~~
make flash
make debug
~~~

View File

@ -1,65 +0,0 @@
#pragma once
/** build year as number */
#define COMPUTE_BUILD_YEAR \
( \
(__DATE__[ 7] - '0') * 1000 + \
(__DATE__[ 8] - '0') * 100 + \
(__DATE__[ 9] - '0') * 10 + \
(__DATE__[10] - '0') \
)
/** build day as number */
#define COMPUTE_BUILD_DAY \
( \
((__DATE__[4] >= '0') ? (__DATE__[4] - '0') * 10 : 0) + \
(__DATE__[5] - '0') \
)
/** check if build month is January */
#define BUILD_MONTH_IS_JAN (__DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n')
/** check if build month is February */
#define BUILD_MONTH_IS_FEB (__DATE__[0] == 'F')
/** check if build month is March */
#define BUILD_MONTH_IS_MAR (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r')
/** check if build month is April */
#define BUILD_MONTH_IS_APR (__DATE__[0] == 'A' && __DATE__[1] == 'p')
/** check if build month is May */
#define BUILD_MONTH_IS_MAY (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y')
/** check if build month is June */
#define BUILD_MONTH_IS_JUN (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n')
/** check if build month is July */
#define BUILD_MONTH_IS_JUL (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l')
/** check if build month is August */
#define BUILD_MONTH_IS_AUG (__DATE__[0] == 'A' && __DATE__[1] == 'u')
/** check if build month is September */
#define BUILD_MONTH_IS_SEP (__DATE__[0] == 'S')
/** check if build month is October */
#define BUILD_MONTH_IS_OCT (__DATE__[0] == 'O')
/** check if build month is November */
#define BUILD_MONTH_IS_NOV (__DATE__[0] == 'N')
/** check if build month is December */
#define BUILD_MONTH_IS_DEC (__DATE__[0] == 'D')
/** build month as number */
#define COMPUTE_BUILD_MONTH \
( \
(BUILD_MONTH_IS_JAN) ? 1 : \
(BUILD_MONTH_IS_FEB) ? 2 : \
(BUILD_MONTH_IS_MAR) ? 3 : \
(BUILD_MONTH_IS_APR) ? 4 : \
(BUILD_MONTH_IS_MAY) ? 5 : \
(BUILD_MONTH_IS_JUN) ? 6 : \
(BUILD_MONTH_IS_JUL) ? 7 : \
(BUILD_MONTH_IS_AUG) ? 8 : \
(BUILD_MONTH_IS_SEP) ? 9 : \
(BUILD_MONTH_IS_OCT) ? 10 : \
(BUILD_MONTH_IS_NOV) ? 11 : \
(BUILD_MONTH_IS_DEC) ? 12 : \
/* error default */ 99 \
)
/** check if build date is unknown */
#define BUILD_DATE_IS_BAD (__DATE__[0] == '?')
/** build year as number if known, or 0 if unknown */
#define BUILD_YEAR ((BUILD_DATE_IS_BAD) ? 0 : COMPUTE_BUILD_YEAR)
/** build month as number if known, or 0 if unknown */
#define BUILD_MONTH ((BUILD_DATE_IS_BAD) ? 99 : COMPUTE_BUILD_MONTH)
/** build day as number if known, or 0 if unknown */
#define BUILD_DAY ((BUILD_DATE_IS_BAD) ? 99 : COMPUTE_BUILD_DAY)

View File

@ -13,7 +13,6 @@
#include "usb_descriptors.h"
#include "keylayouts.h"
#include "WjCryptLib_Sha256.h"
#include "date.h"
// get the length of an array
#define LENGTH(x) (sizeof(x) / sizeof((x)[0]))
@ -77,12 +76,6 @@ static const char* help_str[] = {
"l set keyboard layout\r\n",
};
static const char* prog_str[] = {
"K set manufacturer key\r\n",
"L lock device\r\n",
"E erase firmware\r\n",
};
enum {
MENU_HOME,
MENU_USER,
@ -122,13 +115,6 @@ uint32_t board_button2_read(void)
return BUTTON2_STATE_ACTIVE == HAL_GPIO_ReadPin(BUTTON2_PORT, BUTTON2_PIN);
}
static bool locked(void)
{
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
return (OB_RDP_LEVEL_2 == ob.RDPLevel);
}
static void clear_credentials(void)
{
// clear credentials
@ -153,7 +139,7 @@ static void load_config(void)
if (!manuf_ok) {
memset(manuf_key, 0, sizeof(manuf_key));
}
memcpy(&config, (uint8_t*)FLASH_CONFIG_ADDR, sizeof(config));
uint8_t crc = 0x42; // start with non-zero value to have a checksum work with empty config
for (uint16_t i = 0; i < sizeof(config); i++) {
@ -280,9 +266,7 @@ int main(void)
// init device stack on configured roothub port
tud_init(BOARD_TUD_RHPORT);
printf("\r\npasskey\r\n");
printf("hardware version: 2\r\n"); // for now we just have version 2
printf("firmware date: %04u-%02u-%02u\r\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); // show firmware build date
printf("\r\npasskey v2\r\n");
load_config();
FLASH_OBProgramInitTypeDef ob;
@ -384,7 +368,7 @@ void cdc_task(void)
case 'c': // input credentials
i = 0; // reset index
menu = MENU_USER; // go to corresponding menu
str = "\r\nenter credentials to paste\r\nusername: ";
str = "\r\nusername: ";
break;
case 'b': // swap buttons
config.button_swap = !config.button_swap;
@ -418,55 +402,50 @@ void cdc_task(void)
}
break;
case 'l': // set keyboard layout
echo = false;
tud_cdc_write(buf, 1); // since echo will be off
tud_cdc_write_str("\n\r");
tud_cdc_write_flush();
echo = false;
for (uint8_t j = 0; j < LENGTH(name_asciimap); j++) {
snprintf(tmp, sizeof(tmp), "%02u %s\r\n", j, name_asciimap[j]);
tud_cdc_write_str_flush(tmp);
}
snprintf(tmp, sizeof(tmp), "set keyboard layout [%s]: ", name_asciimap[config.layout]);
snprintf(tmp, sizeof(tmp), "\r\nset keyboard layout [%s]: ", name_asciimap[config.layout]);
tud_cdc_write_str_flush(tmp);
i = 0; // reset index
menu = MENU_LAYOUT;
break;
// hidden menu
case 'K': // set manufacturer key
if (!locked()) {
i = 0; // reset index
menu = MENU_MANUF;
str = "\r\nenter manufacturer key (up to 32 char): ";
{
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
if (OB_RDP_LEVEL_2 == ob.RDPLevel) {
str = "\r\ndevice locked\r\n";
} else {
i = 0; // reset index
menu = MENU_MANUF;
str = "\r\nenter manufacturer key (up to 32 char): ";
}
}
break;
case 'L': // lock device
if (!locked()) {
tud_cdc_write_str_flush("\r\nlocking device\r\n");
{
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
ob.RDPLevel = OB_RDP_LEVEL_2; // level 2, disabling debug, and write protecting option byte
ob.WRPState = OB_WRPSTATE_ENABLE; // write protect pages
ob.WRPPage = 0x7; // all but last sector holding the config
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
//HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
HAL_FLASH_OB_Launch();
}
break;
case 'E': // mass erase to restart STM32 bootloader
if (!locked()) {
tud_cdc_write_str_flush("\r\nerasing firmware\r\n");
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef EraseInitStruct;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_MASSERASE;
uint32_t error;
if (HAL_FLASHEx_Erase(&EraseInitStruct, &error) != HAL_OK) {
printf("mass erase failed\r\n");
if (OB_RDP_LEVEL_2 == ob.RDPLevel) {
str = "\r\ndevice locked\r\n";
} else {
str = "\r\nlocking device\r\n";
ob.RDPLevel = OB_RDP_LEVEL_2; // level 2, disabling debug, and write protecting option byte
ob.WRPState = OB_WRPSTATE_ENABLE; // write protect pages
ob.WRPPage = 0x7; // all but last sector holding the config
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
//HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
HAL_FLASH_OB_Launch();
}
HAL_FLASH_Lock();
HAL_FLASH_OB_Launch(); // just to restart
}
break;
case '\r':
@ -480,11 +459,6 @@ void cdc_task(void)
for (uint8_t j = 0; j < LENGTH(help_str); j++) {
tud_cdc_write_str_flush(help_str[j]);
}
if (!locked()) {
for (uint8_t j = 0; j < LENGTH(prog_str); j++) {
tud_cdc_write_str_flush(prog_str[j]);
}
}
}
break;
case MENU_USER:
@ -740,41 +714,33 @@ void tud_cdc_rx_cb(uint8_t itf)
//--------------------------------------------------------------------+
// USB HID
//--------------------------------------------------------------------+
static void send_hid_report(uint8_t modifier, uint8_t keycode)
static void send_hid_report(char c)
{
// skip if hid is not ready yet
if ( !tud_hid_ready() ) {
return;
}
uint8_t keycodes[6] = {keycode, 0, 0, 0, 0, 0};
if (keycode) {
key_when = board_millis(); // remember when we sent the key
key_pressed = true; // remember we pressed a key
} else {
key_pressed = false; // key released
// ensure it's a printable ASCII
if (0 != c && c < ' ') {
return;
}
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, modifier, keycodes);
}
static void send_hid_char(char c)
{
// set default to US
if (config.layout >= LENGTH(map_asciimap)) {
config.layout = 0;
}
if (0 == c) {
send_hid_report(0, 0);
} else if ('\t' == c) {
send_hid_report(0, KEY_TAB);
} else if ('\r' == c || '\n' == c) {
send_hid_report(0, KEY_ENTER);
} else if (c >= ' ' && c <= '~') {
uint8_t keycode[6] = {0, 0, 0, 0, 0, 0};
if (c) {
const uint16_t code = map_asciimap[config.layout][c - ' '];
const uint8_t modifier = code >> 8;
const uint8_t keycode = code & 0xff;
send_hid_report(modifier, keycode);
keycode[0] = code & 0xff;
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, modifier, keycode);
key_when = board_millis(); // remember when we sent the key
key_pressed = true; // remember we pressed a key
} else {
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode); // release press
key_pressed = false;
}
}
@ -791,10 +757,10 @@ void hid_task(void)
return;
}
if (pass_paste) { // send password
if (pass_paste < strlen(password)) { // send password
send_hid_char(password[pass_paste++]);
if (pass_paste < strlen(username)) { // send password
send_hid_report(password[pass_paste++]);
} else if (pass_paste == strlen(password)) { // press enter
send_hid_char('\n');
send_hid_report('\n');
pass_paste++;
} else if (pass_paste == (strlen(password) + 1)) { // finished pasting
user_paste = 0;
@ -804,13 +770,13 @@ void hid_task(void)
}
if (user_paste) { // send username
if (user_paste < strlen(username)) { // send username
send_hid_char(username[user_paste++]);
send_hid_report(username[user_paste++]);
} else if (user_paste == strlen(username)) { // switch to password
send_hid_char('\t');
send_hid_report('\t');
user_paste++;
} else if (user_paste == (strlen(username) + 1)) { // start sending password
pass_paste = 0;
send_hid_char(password[pass_paste++]);
send_hid_report(password[pass_paste++]);
user_paste++;
}
}
@ -829,17 +795,17 @@ static void paste_credentials(bool user)
pasted_when = board_millis();
if (user) {
send_hid_char(username[user_paste++]);
send_hid_report(username[user_paste++]);
printf("pasting username and password\r\n");
} else {
send_hid_char(password[pass_paste++]);
send_hid_report(password[pass_paste++]);
printf("pasting password\r\n");
}
}
void button_task(void)
{
static uint32_t button1_ms = 0; // when the button has been pressed (in ms)
static uint32_t button1_ms = 0; // when the button has been pressed (in ms)
static bool button1_ok = false; // if the button is pressed (after debounce)
static uint32_t button2_ms = 0; // when the button has been pressed (in ms)
static bool button2_ok = false; // if the button is pressed (after debounce)
@ -921,7 +887,7 @@ void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_
if ((report[0] == REPORT_ID_KEYBOARD) && key_pressed) {
//tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL); // release press
send_hid_report(0, 0); // release key
send_hid_report(0); // release key
//return;
}
}
@ -949,7 +915,7 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_
static uint8_t kbd_leds_last = 0; // last state
static uint32_t kbd_leds_when = 0; // when did the state change
static uint8_t kbd_leds_count = 0; // how many times it changed (within a certain period)
if (report_type == HID_REPORT_TYPE_OUTPUT) {
// Set keyboard LED e.g Capslock, Numlock etc...
if (report_id == REPORT_ID_KEYBOARD) {

View File

@ -23,6 +23,3 @@ flash: $(BUILD)/$(PROJECT).elf
debug: $(BUILD)/$(PROJECT).elf
$(GDB) --eval-command='target remote | $(OOCD) --file interface/$(OOCD_INTERFACE).cfg --command "transport select swd" --file target/$(OOCD_TARGET).cfg --command "gdb_port pipe; log_output /dev/null; init"' $<
dfu: $(BUILD)/$(PROJECT).bin
dfu-util --device 0483:df11 --cfg 1 --intf 0 --alt 0 --detach --dfuse-address 0x08000000 --download $<