diff --git a/examples/tutorials/hotp/hotp_complete/Makefile b/examples/tutorials/hotp/hotp_complete/Makefile new file mode 100644 index 000000000..2960019ad --- /dev/null +++ b/examples/tutorials/hotp/hotp_complete/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../../../ + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk diff --git a/examples/tutorials/hotp/hotp_complete/README.md b/examples/tutorials/hotp/hotp_complete/README.md new file mode 100644 index 000000000..10c5858c0 --- /dev/null +++ b/examples/tutorials/hotp/hotp_complete/README.md @@ -0,0 +1,5 @@ + +Additional abilities: + * Can program a new key on button hold + * Saves key in Flash across reboots + * Supports multiple keys (one per button) diff --git a/examples/tutorials/hotp/hotp_complete/base32.c b/examples/tutorials/hotp/hotp_complete/base32.c new file mode 100644 index 000000000..1a5171833 --- /dev/null +++ b/examples/tutorials/hotp/hotp_complete/base32.c @@ -0,0 +1,95 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "base32.h" + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) { + int buffer = 0; + int bitsLeft = 0; + int count = 0; + for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) { + uint8_t ch = *ptr; + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { + continue; + } + buffer <<= 5; + + // Deal with commonly mistyped characters + if (ch == '0') { + ch = 'O'; + } else if (ch == '1') { + ch = 'L'; + } else if (ch == '8') { + ch = 'B'; + } + + // Look up one base32 digit + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + ch = (ch & 0x1F) - 1; + } else if (ch >= '2' && ch <= '7') { + ch -= '2' - 26; + } else { + return -1; + } + + buffer |= ch; + bitsLeft += 5; + if (bitsLeft >= 8) { + result[count++] = buffer >> (bitsLeft - 8); + bitsLeft -= 8; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} + +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) { + if (length < 0 || length > (1 << 28)) { + return -1; + } + int count = 0; + if (length > 0) { + int buffer = data[0]; + int next = 1; + int bitsLeft = 8; + while (count < bufSize && (bitsLeft > 0 || next < length)) { + if (bitsLeft < 5) { + if (next < length) { + buffer <<= 8; + buffer |= data[next++] & 0xFF; + bitsLeft += 8; + } else { + int pad = 5 - bitsLeft; + buffer <<= pad; + bitsLeft += pad; + } + } + int index = 0x1F & (buffer >> (bitsLeft - 5)); + bitsLeft -= 5; + result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index]; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} diff --git a/examples/tutorials/hotp/hotp_complete/base32.h b/examples/tutorials/hotp/hotp_complete/base32.h new file mode 100644 index 000000000..82f4f26e0 --- /dev/null +++ b/examples/tutorials/hotp/hotp_complete/base32.h @@ -0,0 +1,38 @@ +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Encode and decode from base32 encoding using the following alphabet: +// ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 +// This alphabet is documented in RFC 4648/3548 +// +// We allow white-space and hyphens, but all other characters are considered +// invalid. +// +// All functions return the number of output bytes or -1 on error. If the +// output buffer is too small, the result will silently be truncated. + +#ifndef _BASE32_H_ +#define _BASE32_H_ + +#include + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) + __attribute__((visibility("hidden"))); +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) + __attribute__((visibility("hidden"))); + +#endif /* _BASE32_H_ */ diff --git a/examples/tutorials/hotp/hotp_complete/main.c b/examples/tutorials/hotp/hotp_complete/main.c new file mode 100644 index 000000000..ee0d50d80 --- /dev/null +++ b/examples/tutorials/hotp/hotp_complete/main.c @@ -0,0 +1,407 @@ +// TODO: description here +// probably should be a license too, right? + + +// Test with: https://www.verifyr.com/en/otp/check#hotp +// Use the "Generate HOTP Code" window with whatever secret you want +// Counter should be the current counter value +// MUST use algorithm "sha256" +// Digits should be "6" first the first two slots, "7" for the third, and "8" for the last + +// --- Python3 example code --- +// +// Base32-encoded key: +// O775VWOS5TBT6VZ4VNDMB4SOMGKNC2BXOVKCRNDFYJ4WSDLPTELUG24QJCNIT53CDACYE6CDKDQKOXSINABRA5UFOPOU5WIDZJLFBNQ= +// +// To generate HOTP values in Python, use: +// >>> import pyotp, hashlib +// >>> otp = pyotp.HOTP("$THE_ABOVE_BASE32_KEY", digest=hashlib.sha256) +// >>> otp.at(1) +// '571577' + +// C standard library includes +#include +#include +#include +#include +#include +#include +#include + +// Libtock includes +#include +#include +#include +#include +#include +#include +#include + +// Local includes +#include "base32.h" + + +// --- Definitions for HOTP App --- + +#define NUM_KEYS 4 + +// Select how many digits each key is +// Slot 0: 6 digits +// Slot 1: 6 digits +// Slot 2: 7 digits +// Slot 3: 8 digits +int key_digits[NUM_KEYS] = {6, 6, 7, 8}; + +typedef uint64_t counter_t; + +typedef struct { + uint8_t len; + uint8_t key[64]; + counter_t counter; +} hotp_key_t; + +//hotp_key_t hotp_key = {0}; + + +// --- Button Handling --- + +// Global to keep track of most recently pressed button +int pressed_btn_num; + +// Callback for button presses. +// num: The index of the button associated with the callback +// val: 1 if pressed, 0 if depressed +static void button_upcall(int num, + int val, + __attribute__ ((unused)) int arg2, + void * ud) { + if (val == 1) { + pressed_btn_num = num; + *((bool*)ud) = true; + } +} + +// Initializes interrupts for all buttons on the board +static int initialize_buttons(bool* flag_pointer) { + // Enable button interrupts + int err = button_subscribe(button_upcall, (void*)flag_pointer); + if (err != RETURNCODE_SUCCESS) { + return err; + } + + // Enable interrupts on each button. + int count = 0; + err = button_count(&count); + if (err != RETURNCODE_SUCCESS) { + return err; + } + + for (int i = 0; i < count; i++) { + button_enable_interrupt(i); + } + + return RETURNCODE_SUCCESS; +} + + +// --- App State Handling --- + +typedef struct { + uint32_t magic; + hotp_key_t keys[NUM_KEYS]; +} key_storage_t; + +APP_STATE_DECLARE(key_storage_t, keystore); + +static int initialize_app_state(void) { + // Recover state from flash if it exists + int ret = app_state_load_sync(); + if (ret != 0) { + printf("ERROR(%i): Could not read the flash region.\r\n", ret); + return ret; + } else { + printf("Flash read\r\n"); + } + + // Initialize default values if nothing previously existed + if (keystore.magic != 0xdeadbeef) { + keystore.magic = 0xdeadbeef; + for (int i = 0; i < NUM_KEYS; i++) { + keystore.keys[i].len = 0; + } + ret = app_state_save_sync(); + if (ret != 0) { + printf("ERROR(%i): Could not write back to flash.\r\n", ret); + return ret; + } else { + printf("Initialized state\r\n"); + } + } + + return RETURNCODE_SUCCESS; +} + + +// --- HMAC Handling --- + +static void hmac_upcall(__attribute__ ((unused)) int arg0, + __attribute__ ((unused)) int arg1, + __attribute__ ((unused)) int arg2, + void* done_flag) { + *((bool *) done_flag) = true; +} + +static int hmac(const uint8_t* key, int key_len, const uint8_t* data, int data_len, uint8_t* output_buffer, + int output_buffer_len) { + int ret; + bool hmac_done = false; + + ret = hmac_set_callback(hmac_upcall, &hmac_done); + if (ret < 0) { + goto done; + } + + ret = hmac_set_key_buffer(key, key_len); + if (ret < 0) { + goto deregister_upcall; + } + + ret = hmac_set_dest_buffer(output_buffer, output_buffer_len); + if (ret < 0) { + goto unallow_key_buffer; + } + + ret = hmac_set_data_buffer(data, data_len); + if (ret < 0) { + goto unallow_dest_buffer; + } + + ret = hmac_set_algorithm(TOCK_HMAC_ALG_SHA256); + if (ret < 0) { + goto unallow_data_buffer; + } + + ret = hmac_run(); + if (ret < 0) { + printf("HMAC failure: %d\r\n", ret); + goto unallow_data_buffer; + } + + yield_for(&hmac_done); + +unallow_data_buffer: + hmac_set_data_buffer(NULL, 0); + +unallow_dest_buffer: + hmac_set_dest_buffer(NULL, 0); + +unallow_key_buffer: + hmac_set_key_buffer(NULL, 0); + +deregister_upcall: + hmac_set_callback(NULL, NULL); + +done: + return ret; +} + +static int decrypt(const uint8_t* cipher, int cipherlen, uint8_t* plaintext, int plaintext_capacity) { + int copylen = cipherlen; + if (plaintext_capacity < cipherlen) { + copylen = plaintext_capacity; + } + memcpy(plaintext, cipher, copylen); + return copylen; +} + + +// --- HOTP Actions --- + +static void program_default_secret(void) { + led_on(0); + const char* default_secret = "test"; + + // Decode base32 to get HOTP key value + int ret = base32_decode((const uint8_t*)default_secret, keystore.keys[0].key, 64); + if (ret < 0 ) { + printf("ERROR cannot base32 decode secret\r\n"); + keystore.keys[0].len = 0; + return; + } + + // Initialize remainder of HOTP key + keystore.keys[0].len = ret; + keystore.keys[0].counter = 0; + + printf("Programmed \"%s\" as key \r\n", default_secret); + led_off(0); +} + +static void program_new_secret(int slot_num) { + // Request user input + led_on(slot_num); + printf("Program a new key in slot %d\r\n", slot_num); + printf("(hit enter without typing to cancel)\r\n"); + + // Read key values from user + // TODO: sure would be nice to clear all previous input before starting this + uint8_t newkey[128]; + int i = 0; + while (i < 127) { + // read next character + char c = getch(); + + // break on enter + if (c == '\n') { + break; + } + + // only record alphanumeric characters + if (isalnum(c)) { + newkey[i] = c; + i++; + + // echo input to user + putnstr(&c, 1); + } + } + + // Finished. Append null terminator and echo newline + newkey[i] = '\0'; + putnstr("\r\n", 2); + + // Handle early exits + if (newkey[0] == '\0') { + printf("Aborted\r\n"); + led_off(slot_num); + return; + } + + // Decode and save secret to flash + keystore.keys[slot_num].len = base32_decode(newkey, keystore.keys[slot_num].key, 64); + keystore.keys[slot_num].counter = 0; + int ret = app_state_save_sync(); + if (ret != 0) { + printf("ERROR(%i): Could not write back to flash.\r\n", ret); + } + + // Completed! + printf("Programmed \"%s\" to slot %d\r\n", newkey, slot_num); + led_off(slot_num); +} + +static void get_next_code(int slot_num) { + led_on(slot_num); + + // Decrypt the key + // TODO: should this be here in PART1 of the tutorial? + uint8_t key[64]; + int keylen = decrypt(keystore.keys[slot_num].key, keystore.keys[slot_num].len, key, 64); + + // Generate the HMAC'ed data from the "moving factor" (timestamp in TOTP, + // counter in HOTP), shuffled in a specific way: + uint8_t moving_factor[sizeof(counter_t)]; + for (size_t i = 0; i < sizeof(counter_t); i++) { + moving_factor[i] = (keystore.keys[slot_num].counter >> ((sizeof(counter_t) - i - 1) * 8)) & 0xFF; + } + + // Perform the HMAC operation + const uint8_t HMAC_OUTPUT_BUF_LEN = 32; + uint8_t hmac_output_buf[HMAC_OUTPUT_BUF_LEN]; + hmac(key, keylen, moving_factor, sizeof(counter_t), hmac_output_buf, HMAC_OUTPUT_BUF_LEN); + + // Increment the counter and save to flash + keystore.keys[slot_num].counter++; + int ret = app_state_save_sync(); + if (ret != 0) { + printf("ERROR(%i): Could not write back to flash.\r\n", ret); + } + + // Get output value + uint8_t offset = hmac_output_buf[HMAC_OUTPUT_BUF_LEN - 1] & 0x0f; + uint32_t S = (((hmac_output_buf[offset] & 0x7f) << 24) + | ((hmac_output_buf[offset + 1] & 0xff) << 16) + | ((hmac_output_buf[offset + 2] & 0xff) << 8) + | ((hmac_output_buf[offset + 3] & 0xff))); + + // Limit output to correct number of digits. Modulus by 10^digits + double digit_count = pow(10, key_digits[slot_num]); + S %= (uint32_t)digit_count; + + // Record value as a string + char hotp_format_buffer[16]; + int len = snprintf(hotp_format_buffer, 16, "%.*ld", key_digits[slot_num], S); + if (len < 0) { + len = 0; + } else if (len > 16) { + len = 16; + } + + // Write the value to the USB keyboard. + uint8_t keyboard_buffer[64]; // TODO: grab PR #333 once merged to remove this unnecessary buffer + ret = usb_keyboard_hid_send_string_sync(keyboard_buffer, hotp_format_buffer, len); + if (ret < 0) { + printf("ERROR sending string with USB keyboard HID: %i\r\n", ret); + } else { + printf("Counter: %u. Typed \"%s\" on the USB HID the keyboard\r\n", (size_t)keystore.keys[slot_num].counter - 1, + hotp_format_buffer); + } + + // Complete + led_off(slot_num); +} + + +// --- Main Loop --- + +// Performs initialization and interactivity. +int main(void) { + delay_ms(1000); + printf("Tock HOTP App Started. Usage:\r\n" + "* Press a button to get the next HOTP code for that slot.\r\n" + "* Hold a button to enter a new HOTP secret for that slot.\r\n"); + + // Initialize app state + if (initialize_app_state() != RETURNCODE_SUCCESS) { + printf("ERROR initializing app store\r\n"); + return 1; + } + + // Initialize buttons + bool button_pressed = false; + if (initialize_buttons(&button_pressed) != RETURNCODE_SUCCESS) { + printf("ERROR initializing buttons\r\n"); + return 1; + } + + // Configure a default HOTP secret + program_default_secret(); + + // Main loop. Waits for button presses + while (true) { + // Yield until a button is pressed + button_pressed = false; + yield_for(&button_pressed); + int btn_num = pressed_btn_num; + + // Delay and check if button is still pressed, signalling a "hold" + delay_ms(500); + int new_val = 0; + button_read(btn_num, &new_val); + + // Handle long presses (program new secret) + if (new_val) { + program_new_secret(btn_num); + + // Handle short presses on already configured keys (output next code) + } else if (btn_num < NUM_KEYS && keystore.keys[btn_num].len > 0) { + get_next_code(btn_num); + + // Error for short press on a non-configured key + } else if (keystore.keys[btn_num].len == 0) { + printf("HOTP / TOTP slot %d not yet configured.\r\n", btn_num); + } + } + + return 0; +} diff --git a/examples/tutorials/hotp/hotp_milestone_one/Makefile b/examples/tutorials/hotp/hotp_milestone_one/Makefile new file mode 100644 index 000000000..2960019ad --- /dev/null +++ b/examples/tutorials/hotp/hotp_milestone_one/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../../../ + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk diff --git a/examples/tutorials/hotp/hotp_milestone_one/README.md b/examples/tutorials/hotp/hotp_milestone_one/README.md new file mode 100644 index 000000000..544ccc657 --- /dev/null +++ b/examples/tutorials/hotp/hotp_milestone_one/README.md @@ -0,0 +1,5 @@ + +Additional abilities: + * Can program a new key on button hold + * Does NOT save key in Flash across reboots + * Does NOT support multiple keys diff --git a/examples/tutorials/hotp/hotp_milestone_one/base32.c b/examples/tutorials/hotp/hotp_milestone_one/base32.c new file mode 100644 index 000000000..1a5171833 --- /dev/null +++ b/examples/tutorials/hotp/hotp_milestone_one/base32.c @@ -0,0 +1,95 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "base32.h" + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) { + int buffer = 0; + int bitsLeft = 0; + int count = 0; + for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) { + uint8_t ch = *ptr; + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { + continue; + } + buffer <<= 5; + + // Deal with commonly mistyped characters + if (ch == '0') { + ch = 'O'; + } else if (ch == '1') { + ch = 'L'; + } else if (ch == '8') { + ch = 'B'; + } + + // Look up one base32 digit + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + ch = (ch & 0x1F) - 1; + } else if (ch >= '2' && ch <= '7') { + ch -= '2' - 26; + } else { + return -1; + } + + buffer |= ch; + bitsLeft += 5; + if (bitsLeft >= 8) { + result[count++] = buffer >> (bitsLeft - 8); + bitsLeft -= 8; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} + +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) { + if (length < 0 || length > (1 << 28)) { + return -1; + } + int count = 0; + if (length > 0) { + int buffer = data[0]; + int next = 1; + int bitsLeft = 8; + while (count < bufSize && (bitsLeft > 0 || next < length)) { + if (bitsLeft < 5) { + if (next < length) { + buffer <<= 8; + buffer |= data[next++] & 0xFF; + bitsLeft += 8; + } else { + int pad = 5 - bitsLeft; + buffer <<= pad; + bitsLeft += pad; + } + } + int index = 0x1F & (buffer >> (bitsLeft - 5)); + bitsLeft -= 5; + result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index]; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} diff --git a/examples/tutorials/hotp/hotp_milestone_one/base32.h b/examples/tutorials/hotp/hotp_milestone_one/base32.h new file mode 100644 index 000000000..82f4f26e0 --- /dev/null +++ b/examples/tutorials/hotp/hotp_milestone_one/base32.h @@ -0,0 +1,38 @@ +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Encode and decode from base32 encoding using the following alphabet: +// ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 +// This alphabet is documented in RFC 4648/3548 +// +// We allow white-space and hyphens, but all other characters are considered +// invalid. +// +// All functions return the number of output bytes or -1 on error. If the +// output buffer is too small, the result will silently be truncated. + +#ifndef _BASE32_H_ +#define _BASE32_H_ + +#include + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) + __attribute__((visibility("hidden"))); +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) + __attribute__((visibility("hidden"))); + +#endif /* _BASE32_H_ */ diff --git a/examples/tutorials/hotp/hotp_milestone_one/main.c b/examples/tutorials/hotp/hotp_milestone_one/main.c new file mode 100644 index 000000000..ce2c7b4be --- /dev/null +++ b/examples/tutorials/hotp/hotp_milestone_one/main.c @@ -0,0 +1,350 @@ +// TODO: description here +// probably should be a license too, right? + + +// Test with: https://www.verifyr.com/en/otp/check#hotp +// Use the "Generate HOTP Code" window with whatever secret you want +// Counter should be the current counter value +// MUST use algorithm "sha256" +// Digits should be "6" first the first two slots, "7" for the third, and "8" for the last + +// --- Python3 example code --- +// +// Base32-encoded key: +// O775VWOS5TBT6VZ4VNDMB4SOMGKNC2BXOVKCRNDFYJ4WSDLPTELUG24QJCNIT53CDACYE6CDKDQKOXSINABRA5UFOPOU5WIDZJLFBNQ= +// +// To generate HOTP values in Python, use: +// >>> import pyotp, hashlib +// >>> otp = pyotp.HOTP("$THE_ABOVE_BASE32_KEY", digest=hashlib.sha256) +// >>> otp.at(1) +// '571577' + +// C standard library includes +#include +#include +#include +#include +#include +#include +#include + +// Libtock includes +#include +#include +#include +#include +#include +#include +#include + +// Local includes +#include "base32.h" + + +// --- Definitions for HOTP App --- + +// Select how many digits for a key +#define KEY_DIGITS 6 + +typedef uint64_t counter_t; + +typedef struct { + uint8_t len; + uint8_t key[64]; + counter_t counter; +} hotp_key_t; + +hotp_key_t hotp_key = {0}; + + +// --- Button Handling --- + +// Global to keep track of most recently pressed button +int pressed_btn_num; + +// Callback for button presses. +// num: The index of the button associated with the callback +// val: 1 if pressed, 0 if depressed +static void button_upcall(int num, + int val, + __attribute__ ((unused)) int arg2, + void * ud) { + if (val == 1) { + pressed_btn_num = num; + *((bool*)ud) = true; + } +} + +// Initializes interrupts on a button +static int initialize_buttons(bool* flag_pointer) { + // Enable button interrupts + int err = button_subscribe(button_upcall, (void*)flag_pointer); + if (err != RETURNCODE_SUCCESS) { + return err; + } + + // Determine the number of supported buttons + int count = 0; + err = button_count(&count); + if (err != RETURNCODE_SUCCESS) { + return err; + } + + // Enable interrupts if a button exists + if (count > 0) { + button_enable_interrupt(0); + } + + return RETURNCODE_SUCCESS; +} + + +// --- HMAC Handling --- + +static void hmac_upcall(__attribute__ ((unused)) int arg0, + __attribute__ ((unused)) int arg1, + __attribute__ ((unused)) int arg2, + void* done_flag) { + *((bool *) done_flag) = true; +} + +static int hmac(const uint8_t* key, int key_len, const uint8_t* data, int data_len, uint8_t* output_buffer, + int output_buffer_len) { + int ret; + bool hmac_done = false; + + ret = hmac_set_callback(hmac_upcall, &hmac_done); + if (ret < 0) { + goto done; + } + + ret = hmac_set_key_buffer(key, key_len); + if (ret < 0) { + goto deregister_upcall; + } + + ret = hmac_set_dest_buffer(output_buffer, output_buffer_len); + if (ret < 0) { + goto unallow_key_buffer; + } + + ret = hmac_set_data_buffer(data, data_len); + if (ret < 0) { + goto unallow_dest_buffer; + } + + ret = hmac_set_algorithm(TOCK_HMAC_ALG_SHA256); + if (ret < 0) { + goto unallow_data_buffer; + } + + ret = hmac_run(); + if (ret < 0) { + printf("HMAC failure: %d\r\n", ret); + goto unallow_data_buffer; + } + + yield_for(&hmac_done); + +unallow_data_buffer: + hmac_set_data_buffer(NULL, 0); + +unallow_dest_buffer: + hmac_set_dest_buffer(NULL, 0); + +unallow_key_buffer: + hmac_set_key_buffer(NULL, 0); + +deregister_upcall: + hmac_set_callback(NULL, NULL); + +done: + return ret; +} + +static int decrypt(const uint8_t* cipher, int cipherlen, uint8_t* plaintext, int plaintext_capacity) { + int copylen = cipherlen; + if (plaintext_capacity < cipherlen) { + copylen = plaintext_capacity; + } + memcpy(plaintext, cipher, copylen); + return copylen; +} + + +// --- HOTP Actions --- + +static void program_default_secret(void) { + led_on(0); + const char* default_secret = "test"; + + // Decode base32 to get HOTP key value + int ret = base32_decode((const uint8_t*)default_secret, hotp_key.key, 64); + if (ret < 0 ) { + printf("ERROR cannot base32 decode secret\r\n"); + hotp_key.len = 0; + return; + } + + // Initialize remainder of HOTP key + hotp_key.len = ret; + hotp_key.counter = 0; + + printf("Programmed \"%s\" as key \r\n", default_secret); + led_off(0); +} + +static void program_new_secret(void) { + // Request user input + led_on(0); + printf("Program a new key\r\n"); + printf("(hit enter without typing to cancel)\r\n"); + + // Read key values from user + // TODO: sure would be nice to clear all previous input before starting this + uint8_t newkey[128]; + int i = 0; + while (i < 127) { + // read next character + char c = getch(); + + // break on enter + if (c == '\n') { + break; + } + + // only record alphanumeric characters + if (isalnum(c)) { + newkey[i] = c; + i++; + + // echo input to user + putnstr(&c, 1); + } + } + + // Finished. Append null terminator and echo newline + newkey[i] = '\0'; + putnstr("\r\n", 2); + + // Handle early exits + if (newkey[0] == '\0') { + printf("Aborted\r\n"); + led_off(0); + return; + } + + // Decode base32 secret to get HOTP key value + hotp_key.len = base32_decode(newkey, hotp_key.key, 64); + hotp_key.counter = 0; + + // Completed! + printf("Programmed \"%s\" as key\r\n", newkey); + led_off(0); +} + +static void get_next_code(void) { + led_on(0); + + // Decrypt the key + // TODO: should this be here in PART1 of the tutorial? + uint8_t key[64]; + int keylen = decrypt(hotp_key.key, hotp_key.len, key, 64); + + // Generate the HMAC'ed data from the "moving factor" (timestamp in TOTP, + // counter in HOTP), shuffled in a specific way: + uint8_t moving_factor[sizeof(counter_t)]; + for (size_t i = 0; i < sizeof(counter_t); i++) { + moving_factor[i] = (hotp_key.counter >> ((sizeof(counter_t) - i - 1) * 8)) & 0xFF; + } + + // Perform the HMAC operation + const uint8_t HMAC_OUTPUT_BUF_LEN = 32; + uint8_t hmac_output_buf[HMAC_OUTPUT_BUF_LEN]; + hmac(key, keylen, moving_factor, sizeof(counter_t), hmac_output_buf, HMAC_OUTPUT_BUF_LEN); + + // Increment the counter + hotp_key.counter++; + + // Get output value + uint8_t offset = hmac_output_buf[HMAC_OUTPUT_BUF_LEN - 1] & 0x0f; + uint32_t S = (((hmac_output_buf[offset] & 0x7f) << 24) + | ((hmac_output_buf[offset + 1] & 0xff) << 16) + | ((hmac_output_buf[offset + 2] & 0xff) << 8) + | ((hmac_output_buf[offset + 3] & 0xff))); + + // Limit output to correct number of digits. Modulus by 10^digits + double digit_count = pow(10, KEY_DIGITS); + S %= (uint32_t)digit_count; + + // Record value as a string + char hotp_format_buffer[16]; + int len = snprintf(hotp_format_buffer, 16, "%.*ld", KEY_DIGITS, S); + if (len < 0) { + len = 0; + } else if (len > 16) { + len = 16; + } + + // Write the value to the USB keyboard. + uint8_t keyboard_buffer[64]; // TODO: grab PR #333 once merged to remove this unnecessary buffer + int ret = usb_keyboard_hid_send_string_sync(keyboard_buffer, hotp_format_buffer, len); + if (ret < 0) { + printf("ERROR sending string with USB keyboard HID: %i\r\n", ret); + } else { + printf("Counter: %u. Typed \"%s\" on the USB HID the keyboard\r\n", (size_t)hotp_key.counter - 1, + hotp_format_buffer); + } + + // Complete + led_off(0); +} + + +// --- Main Loop --- + +// Performs initialization and interactivity. +int main(void) { + delay_ms(1000); + printf("Tock HOTP App Started. Usage:\r\n" + "* Press button 0 to get the next HOTP code for that slot.\r\n" + "* Hold button 0 to enter a new HOTP secret for that slot.\r\n"); + + // Initialize buttons + bool button_pressed = false; + if (initialize_buttons(&button_pressed) != RETURNCODE_SUCCESS) { + printf("ERROR initializing buttons\r\n"); + return 1; + } + + // Configure a default HOTP secret + program_default_secret(); + + // Main loop. Waits for button presses + while (true) { + // Yield until a button is pressed + button_pressed = false; + yield_for(&button_pressed); + int btn_num = pressed_btn_num; + + // Delay and check if button is still pressed, signalling a "hold" + delay_ms(500); + int new_val = 0; + button_read(btn_num, &new_val); + + // Handle long presses (program new secret) + if (new_val) { + program_new_secret(); + + // Handle short presses on already configured keys (output next code) + } else if (hotp_key.len > 0) { + get_next_code(); + + // Error for short press on a non-configured key + } else if (hotp_key.len == 0) { + printf("HOTP / TOTP key not yet configured.\r\n"); + } + } + + return 0; +} diff --git a/examples/tutorials/hotp/hotp_milestone_two/Makefile b/examples/tutorials/hotp/hotp_milestone_two/Makefile new file mode 100644 index 000000000..2960019ad --- /dev/null +++ b/examples/tutorials/hotp/hotp_milestone_two/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../../../ + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk diff --git a/examples/tutorials/hotp/hotp_milestone_two/README.md b/examples/tutorials/hotp/hotp_milestone_two/README.md new file mode 100644 index 000000000..ebc6216d1 --- /dev/null +++ b/examples/tutorials/hotp/hotp_milestone_two/README.md @@ -0,0 +1,5 @@ + +Additional abilities: + * Can program a new key on button hold + * Saves key in Flash across reboots + * Does NOT support multiple keys diff --git a/examples/tutorials/hotp/hotp_milestone_two/base32.c b/examples/tutorials/hotp/hotp_milestone_two/base32.c new file mode 100644 index 000000000..1a5171833 --- /dev/null +++ b/examples/tutorials/hotp/hotp_milestone_two/base32.c @@ -0,0 +1,95 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "base32.h" + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) { + int buffer = 0; + int bitsLeft = 0; + int count = 0; + for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) { + uint8_t ch = *ptr; + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { + continue; + } + buffer <<= 5; + + // Deal with commonly mistyped characters + if (ch == '0') { + ch = 'O'; + } else if (ch == '1') { + ch = 'L'; + } else if (ch == '8') { + ch = 'B'; + } + + // Look up one base32 digit + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + ch = (ch & 0x1F) - 1; + } else if (ch >= '2' && ch <= '7') { + ch -= '2' - 26; + } else { + return -1; + } + + buffer |= ch; + bitsLeft += 5; + if (bitsLeft >= 8) { + result[count++] = buffer >> (bitsLeft - 8); + bitsLeft -= 8; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} + +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) { + if (length < 0 || length > (1 << 28)) { + return -1; + } + int count = 0; + if (length > 0) { + int buffer = data[0]; + int next = 1; + int bitsLeft = 8; + while (count < bufSize && (bitsLeft > 0 || next < length)) { + if (bitsLeft < 5) { + if (next < length) { + buffer <<= 8; + buffer |= data[next++] & 0xFF; + bitsLeft += 8; + } else { + int pad = 5 - bitsLeft; + buffer <<= pad; + bitsLeft += pad; + } + } + int index = 0x1F & (buffer >> (bitsLeft - 5)); + bitsLeft -= 5; + result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index]; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} diff --git a/examples/tutorials/hotp/hotp_milestone_two/base32.h b/examples/tutorials/hotp/hotp_milestone_two/base32.h new file mode 100644 index 000000000..82f4f26e0 --- /dev/null +++ b/examples/tutorials/hotp/hotp_milestone_two/base32.h @@ -0,0 +1,38 @@ +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Encode and decode from base32 encoding using the following alphabet: +// ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 +// This alphabet is documented in RFC 4648/3548 +// +// We allow white-space and hyphens, but all other characters are considered +// invalid. +// +// All functions return the number of output bytes or -1 on error. If the +// output buffer is too small, the result will silently be truncated. + +#ifndef _BASE32_H_ +#define _BASE32_H_ + +#include + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) + __attribute__((visibility("hidden"))); +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) + __attribute__((visibility("hidden"))); + +#endif /* _BASE32_H_ */ diff --git a/examples/tutorials/hotp/hotp_milestone_two/main.c b/examples/tutorials/hotp/hotp_milestone_two/main.c new file mode 100644 index 000000000..22bdda311 --- /dev/null +++ b/examples/tutorials/hotp/hotp_milestone_two/main.c @@ -0,0 +1,400 @@ +// TODO: description here +// probably should be a license too, right? + + +// Test with: https://www.verifyr.com/en/otp/check#hotp +// Use the "Generate HOTP Code" window with whatever secret you want +// Counter should be the current counter value +// MUST use algorithm "sha256" +// Digits should be "6" first the first two slots, "7" for the third, and "8" for the last + +// --- Python3 example code --- +// +// Base32-encoded key: +// O775VWOS5TBT6VZ4VNDMB4SOMGKNC2BXOVKCRNDFYJ4WSDLPTELUG24QJCNIT53CDACYE6CDKDQKOXSINABRA5UFOPOU5WIDZJLFBNQ= +// +// To generate HOTP values in Python, use: +// >>> import pyotp, hashlib +// >>> otp = pyotp.HOTP("$THE_ABOVE_BASE32_KEY", digest=hashlib.sha256) +// >>> otp.at(1) +// '571577' + +// C standard library includes +#include +#include +#include +#include +#include +#include +#include + +// Libtock includes +#include +#include +#include +#include +#include +#include +#include + +// Local includes +#include "base32.h" + + +// --- Definitions for HOTP App --- + +// Select how many digits for a key +#define KEY_DIGITS 6 + +typedef uint64_t counter_t; + +typedef struct { + uint8_t len; + uint8_t key[64]; + counter_t counter; +} hotp_key_t; + +//hotp_key_t hotp_key = {0}; + + +// --- Button Handling --- + +// Global to keep track of most recently pressed button +int pressed_btn_num; + +// Callback for button presses. +// num: The index of the button associated with the callback +// val: 1 if pressed, 0 if depressed +static void button_upcall(int num, + int val, + __attribute__ ((unused)) int arg2, + void * ud) { + if (val == 1) { + pressed_btn_num = num; + *((bool*)ud) = true; + } +} + +// Initializes interrupts on a button +static int initialize_buttons(bool* flag_pointer) { + // Enable button interrupts + int err = button_subscribe(button_upcall, (void*)flag_pointer); + if (err != RETURNCODE_SUCCESS) { + return err; + } + + // Determine the number of supported buttons + int count = 0; + err = button_count(&count); + if (err != RETURNCODE_SUCCESS) { + return err; + } + + // Enable interrupts if a button exists + if (count > 0) { + button_enable_interrupt(0); + } + + return RETURNCODE_SUCCESS; +} + + +// --- App State Handling --- + +typedef struct { + uint32_t magic; + hotp_key_t key; +} key_storage_t; + +APP_STATE_DECLARE(key_storage_t, keystore); + +static int initialize_app_state(void) { + // Recover state from flash if it exists + int ret = app_state_load_sync(); + if (ret != 0) { + printf("ERROR(%i): Could not read the flash region.\r\n", ret); + return ret; + } else { + printf("Flash read\r\n"); + } + + // Initialize default values if nothing previously existed + if (keystore.magic != 0xdeadbeef) { + keystore.magic = 0xdeadbeef; + keystore.key.len = 0; + ret = app_state_save_sync(); + if (ret != 0) { + printf("ERROR(%i): Could not write back to flash.\r\n", ret); + return ret; + } else { + printf("Initialized state\r\n"); + } + } + + return RETURNCODE_SUCCESS; +} + + +// --- HMAC Handling --- + +static void hmac_upcall(__attribute__ ((unused)) int arg0, + __attribute__ ((unused)) int arg1, + __attribute__ ((unused)) int arg2, + void* done_flag) { + *((bool *) done_flag) = true; +} + +static int hmac(const uint8_t* key, int key_len, const uint8_t* data, int data_len, uint8_t* output_buffer, + int output_buffer_len) { + int ret; + bool hmac_done = false; + + ret = hmac_set_callback(hmac_upcall, &hmac_done); + if (ret < 0) { + goto done; + } + + ret = hmac_set_key_buffer(key, key_len); + if (ret < 0) { + goto deregister_upcall; + } + + ret = hmac_set_dest_buffer(output_buffer, output_buffer_len); + if (ret < 0) { + goto unallow_key_buffer; + } + + ret = hmac_set_data_buffer(data, data_len); + if (ret < 0) { + goto unallow_dest_buffer; + } + + ret = hmac_set_algorithm(TOCK_HMAC_ALG_SHA256); + if (ret < 0) { + goto unallow_data_buffer; + } + + ret = hmac_run(); + if (ret < 0) { + printf("HMAC failure: %d\r\n", ret); + goto unallow_data_buffer; + } + + yield_for(&hmac_done); + +unallow_data_buffer: + hmac_set_data_buffer(NULL, 0); + +unallow_dest_buffer: + hmac_set_dest_buffer(NULL, 0); + +unallow_key_buffer: + hmac_set_key_buffer(NULL, 0); + +deregister_upcall: + hmac_set_callback(NULL, NULL); + +done: + return ret; +} + +static int decrypt(const uint8_t* cipher, int cipherlen, uint8_t* plaintext, int plaintext_capacity) { + int copylen = cipherlen; + if (plaintext_capacity < cipherlen) { + copylen = plaintext_capacity; + } + memcpy(plaintext, cipher, copylen); + return copylen; +} + + +// --- HOTP Actions --- + +static void program_default_secret(void) { + led_on(0); + const char* default_secret = "test"; + + // Decode base32 to get HOTP key value + int ret = base32_decode((const uint8_t*)default_secret, keystore.key.key, 64); + if (ret < 0 ) { + printf("ERROR cannot base32 decode secret\r\n"); + keystore.key.len = 0; + return; + } + + // Initialize remainder of HOTP key + keystore.key.len = ret; + keystore.key.counter = 0; + + printf("Programmed \"%s\" as key \r\n", default_secret); + led_off(0); +} + +static void program_new_secret(void) { + // Request user input + led_on(0); + printf("Program a new key\r\n"); + printf("(hit enter without typing to cancel)\r\n"); + + // Read key values from user + // TODO: sure would be nice to clear all previous input before starting this + uint8_t newkey[128]; + int i = 0; + while (i < 127) { + // read next character + char c = getch(); + + // break on enter + if (c == '\n') { + break; + } + + // only record alphanumeric characters + if (isalnum(c)) { + newkey[i] = c; + i++; + + // echo input to user + putnstr(&c, 1); + } + } + + // Finished. Append null terminator and echo newline + newkey[i] = '\0'; + putnstr("\r\n", 2); + + // Handle early exits + if (newkey[0] == '\0') { + printf("Aborted\r\n"); + led_off(0); + return; + } + + // Decode and save secret to flash + keystore.key.len = base32_decode(newkey, keystore.key.key, 64); + keystore.key.counter = 0; + int ret = app_state_save_sync(); + if (ret != 0) { + printf("ERROR(%i): Could not write back to flash.\r\n", ret); + } + + // Completed! + printf("Programmed \"%s\" as key\r\n", newkey); + led_off(0); +} + +static void get_next_code(void) { + led_on(0); + + // Decrypt the key + // TODO: should this be here in PART1 of the tutorial? + uint8_t key[64]; + int keylen = decrypt(keystore.key.key, keystore.key.len, key, 64); + + // Generate the HMAC'ed data from the "moving factor" (timestamp in TOTP, + // counter in HOTP), shuffled in a specific way: + uint8_t moving_factor[sizeof(counter_t)]; + for (size_t i = 0; i < sizeof(counter_t); i++) { + moving_factor[i] = (keystore.key.counter >> ((sizeof(counter_t) - i - 1) * 8)) & 0xFF; + } + + // Perform the HMAC operation + const uint8_t HMAC_OUTPUT_BUF_LEN = 32; + uint8_t hmac_output_buf[HMAC_OUTPUT_BUF_LEN]; + hmac(key, keylen, moving_factor, sizeof(counter_t), hmac_output_buf, HMAC_OUTPUT_BUF_LEN); + + // Increment the counter and save to flash + keystore.key.counter++; + int ret = app_state_save_sync(); + if (ret != 0) { + printf("ERROR(%i): Could not write back to flash.\r\n", ret); + } + + // Get output value + uint8_t offset = hmac_output_buf[HMAC_OUTPUT_BUF_LEN - 1] & 0x0f; + uint32_t S = (((hmac_output_buf[offset] & 0x7f) << 24) + | ((hmac_output_buf[offset + 1] & 0xff) << 16) + | ((hmac_output_buf[offset + 2] & 0xff) << 8) + | ((hmac_output_buf[offset + 3] & 0xff))); + + // Limit output to correct number of digits. Modulus by 10^digits + double digit_count = pow(10, KEY_DIGITS); + S %= (uint32_t)digit_count; + + // Record value as a string + char hotp_format_buffer[16]; + int len = snprintf(hotp_format_buffer, 16, "%.*ld", KEY_DIGITS, S); + if (len < 0) { + len = 0; + } else if (len > 16) { + len = 16; + } + + // Write the value to the USB keyboard. + uint8_t keyboard_buffer[64]; // TODO: grab PR #333 once merged to remove this unnecessary buffer + ret = usb_keyboard_hid_send_string_sync(keyboard_buffer, hotp_format_buffer, len); + if (ret < 0) { + printf("ERROR sending string with USB keyboard HID: %i\r\n", ret); + } else { + printf("Counter: %u. Typed \"%s\" on the USB HID the keyboard\r\n", (size_t)keystore.key.counter - 1, + hotp_format_buffer); + } + + // Complete + led_off(0); +} + + +// --- Main Loop --- + +// Performs initialization and interactivity. +int main(void) { + delay_ms(1000); + printf("Tock HOTP App Started. Usage:\r\n" + "* Press button 0 to get the next HOTP code for that slot.\r\n" + "* Hold button 0 to enter a new HOTP secret for that slot.\r\n"); + + // Initialize app state + if (initialize_app_state() != RETURNCODE_SUCCESS) { + printf("ERROR initializing app store\r\n"); + return 1; + } + + // Initialize buttons + bool button_pressed = false; + if (initialize_buttons(&button_pressed) != RETURNCODE_SUCCESS) { + printf("ERROR initializing buttons\r\n"); + return 1; + } + + // Configure a default HOTP secret + program_default_secret(); + + // Main loop. Waits for button presses + while (true) { + // Yield until a button is pressed + button_pressed = false; + yield_for(&button_pressed); + int btn_num = pressed_btn_num; + + // Delay and check if button is still pressed, signalling a "hold" + delay_ms(500); + int new_val = 0; + button_read(btn_num, &new_val); + + // Handle long presses (program new secret) + if (new_val) { + program_new_secret(); + + // Handle short presses on already configured keys (output next code) + } else if (keystore.key.len > 0) { + get_next_code(); + + // Error for short press on a non-configured key + } else if (keystore.key.len == 0) { + printf("HOTP / TOTP key not yet configured.\r\n"); + } + } + + return 0; +} diff --git a/examples/tutorials/hotp/hotp_starter/Makefile b/examples/tutorials/hotp/hotp_starter/Makefile new file mode 100644 index 000000000..2960019ad --- /dev/null +++ b/examples/tutorials/hotp/hotp_starter/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../../../ + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk diff --git a/examples/tutorials/hotp/hotp_starter/README.md b/examples/tutorials/hotp/hotp_starter/README.md new file mode 100644 index 000000000..75c238b29 --- /dev/null +++ b/examples/tutorials/hotp/hotp_starter/README.md @@ -0,0 +1,5 @@ + +Additional abilities: + * Does NOT program a new key on button hold + * Does NOT save key in Flash across reboots + * Does NOT support multiple keys diff --git a/examples/tutorials/hotp/hotp_starter/base32.c b/examples/tutorials/hotp/hotp_starter/base32.c new file mode 100644 index 000000000..1a5171833 --- /dev/null +++ b/examples/tutorials/hotp/hotp_starter/base32.c @@ -0,0 +1,95 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "base32.h" + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) { + int buffer = 0; + int bitsLeft = 0; + int count = 0; + for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) { + uint8_t ch = *ptr; + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { + continue; + } + buffer <<= 5; + + // Deal with commonly mistyped characters + if (ch == '0') { + ch = 'O'; + } else if (ch == '1') { + ch = 'L'; + } else if (ch == '8') { + ch = 'B'; + } + + // Look up one base32 digit + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + ch = (ch & 0x1F) - 1; + } else if (ch >= '2' && ch <= '7') { + ch -= '2' - 26; + } else { + return -1; + } + + buffer |= ch; + bitsLeft += 5; + if (bitsLeft >= 8) { + result[count++] = buffer >> (bitsLeft - 8); + bitsLeft -= 8; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} + +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) { + if (length < 0 || length > (1 << 28)) { + return -1; + } + int count = 0; + if (length > 0) { + int buffer = data[0]; + int next = 1; + int bitsLeft = 8; + while (count < bufSize && (bitsLeft > 0 || next < length)) { + if (bitsLeft < 5) { + if (next < length) { + buffer <<= 8; + buffer |= data[next++] & 0xFF; + bitsLeft += 8; + } else { + int pad = 5 - bitsLeft; + buffer <<= pad; + bitsLeft += pad; + } + } + int index = 0x1F & (buffer >> (bitsLeft - 5)); + bitsLeft -= 5; + result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index]; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} diff --git a/examples/tutorials/hotp/hotp_starter/base32.h b/examples/tutorials/hotp/hotp_starter/base32.h new file mode 100644 index 000000000..82f4f26e0 --- /dev/null +++ b/examples/tutorials/hotp/hotp_starter/base32.h @@ -0,0 +1,38 @@ +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Encode and decode from base32 encoding using the following alphabet: +// ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 +// This alphabet is documented in RFC 4648/3548 +// +// We allow white-space and hyphens, but all other characters are considered +// invalid. +// +// All functions return the number of output bytes or -1 on error. If the +// output buffer is too small, the result will silently be truncated. + +#ifndef _BASE32_H_ +#define _BASE32_H_ + +#include + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) + __attribute__((visibility("hidden"))); +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) + __attribute__((visibility("hidden"))); + +#endif /* _BASE32_H_ */ diff --git a/examples/tutorials/hotp/hotp_starter/main.c b/examples/tutorials/hotp/hotp_starter/main.c new file mode 100644 index 000000000..d124345f1 --- /dev/null +++ b/examples/tutorials/hotp/hotp_starter/main.c @@ -0,0 +1,291 @@ +// TODO: description here +// probably should be a license too, right? + + +// Test with: https://www.verifyr.com/en/otp/check#hotp +// Use the "Generate HOTP Code" window with whatever secret you want +// Counter should be the current counter value +// MUST use algorithm "sha256" +// Digits should be "6" first the first two slots, "7" for the third, and "8" for the last + +// --- Python3 example code --- +// +// Base32-encoded key: +// O775VWOS5TBT6VZ4VNDMB4SOMGKNC2BXOVKCRNDFYJ4WSDLPTELUG24QJCNIT53CDACYE6CDKDQKOXSINABRA5UFOPOU5WIDZJLFBNQ= +// +// To generate HOTP values in Python, use: +// >>> import pyotp, hashlib +// >>> otp = pyotp.HOTP("$THE_ABOVE_BASE32_KEY", digest=hashlib.sha256) +// >>> otp.at(1) +// '571577' + +// C standard library includes +#include +#include +#include +#include +#include +#include +#include + +// Libtock includes +#include +#include +#include +#include +#include +#include +#include + +// Local includes +#include "base32.h" + + +// --- Definitions for HOTP App --- + +// Select how many digits for a key +#define KEY_DIGITS 6 + +typedef uint64_t counter_t; + +typedef struct { + uint8_t len; + uint8_t key[64]; + counter_t counter; +} hotp_key_t; + +hotp_key_t hotp_key = {0}; + + +// --- Button Handling --- + +// Global to keep track of most recently pressed button +int pressed_btn_num; + +// Callback for button presses. +// num: The index of the button associated with the callback +// val: 1 if pressed, 0 if depressed +static void button_upcall(int num, + int val, + __attribute__ ((unused)) int arg2, + void * ud) { + if (val == 1) { + pressed_btn_num = num; + *((bool*)ud) = true; + } +} + +// Initializes interrupts on a button +static int initialize_buttons(bool* flag_pointer) { + // Enable button interrupts + int err = button_subscribe(button_upcall, (void*)flag_pointer); + if (err != RETURNCODE_SUCCESS) { + return err; + } + + // Determine the number of supported buttons + int count = 0; + err = button_count(&count); + if (err != RETURNCODE_SUCCESS) { + return err; + } + + // Enable interrupts if a button exists + if (count > 0) { + button_enable_interrupt(0); + } + + return RETURNCODE_SUCCESS; +} + + +// --- HMAC Handling --- + +static void hmac_upcall(__attribute__ ((unused)) int arg0, + __attribute__ ((unused)) int arg1, + __attribute__ ((unused)) int arg2, + void* done_flag) { + *((bool *) done_flag) = true; +} + +static int hmac(const uint8_t* key, int key_len, const uint8_t* data, int data_len, uint8_t* output_buffer, + int output_buffer_len) { + int ret; + bool hmac_done = false; + + ret = hmac_set_callback(hmac_upcall, &hmac_done); + if (ret < 0) { + goto done; + } + + ret = hmac_set_key_buffer(key, key_len); + if (ret < 0) { + goto deregister_upcall; + } + + ret = hmac_set_dest_buffer(output_buffer, output_buffer_len); + if (ret < 0) { + goto unallow_key_buffer; + } + + ret = hmac_set_data_buffer(data, data_len); + if (ret < 0) { + goto unallow_dest_buffer; + } + + ret = hmac_set_algorithm(TOCK_HMAC_ALG_SHA256); + if (ret < 0) { + goto unallow_data_buffer; + } + + ret = hmac_run(); + if (ret < 0) { + printf("HMAC failure: %d\r\n", ret); + goto unallow_data_buffer; + } + + yield_for(&hmac_done); + +unallow_data_buffer: + hmac_set_data_buffer(NULL, 0); + +unallow_dest_buffer: + hmac_set_dest_buffer(NULL, 0); + +unallow_key_buffer: + hmac_set_key_buffer(NULL, 0); + +deregister_upcall: + hmac_set_callback(NULL, NULL); + +done: + return ret; +} + +static int decrypt(const uint8_t* cipher, int cipherlen, uint8_t* plaintext, int plaintext_capacity) { + int copylen = cipherlen; + if (plaintext_capacity < cipherlen) { + copylen = plaintext_capacity; + } + memcpy(plaintext, cipher, copylen); + return copylen; +} + + +// --- HOTP Actions --- + +static void program_default_secret(void) { + led_on(0); + const char* default_secret = "test"; + + // Decode base32 to get HOTP key value + int ret = base32_decode((const uint8_t*)default_secret, hotp_key.key, 64); + if (ret < 0 ) { + printf("ERROR cannot base32 decode secret\r\n"); + hotp_key.len = 0; + return; + } + + // Initialize remainder of HOTP key + hotp_key.len = ret; + hotp_key.counter = 0; + + printf("Programmed \"%s\" as key \r\n", default_secret); + led_off(0); +} + +static void get_next_code(void) { + led_on(0); + + // Decrypt the key + // TODO: should this be here in PART1 of the tutorial? + uint8_t key[64]; + int keylen = decrypt(hotp_key.key, hotp_key.len, key, 64); + + // Generate the HMAC'ed data from the "moving factor" (timestamp in TOTP, + // counter in HOTP), shuffled in a specific way: + uint8_t moving_factor[sizeof(counter_t)]; + for (size_t i = 0; i < sizeof(counter_t); i++) { + moving_factor[i] = (hotp_key.counter >> ((sizeof(counter_t) - i - 1) * 8)) & 0xFF; + } + + // Perform the HMAC operation + const uint8_t HMAC_OUTPUT_BUF_LEN = 32; + uint8_t hmac_output_buf[HMAC_OUTPUT_BUF_LEN]; + hmac(key, keylen, moving_factor, sizeof(counter_t), hmac_output_buf, HMAC_OUTPUT_BUF_LEN); + + // Increment the counter + hotp_key.counter++; + + // Get output value + uint8_t offset = hmac_output_buf[HMAC_OUTPUT_BUF_LEN - 1] & 0x0f; + uint32_t S = (((hmac_output_buf[offset] & 0x7f) << 24) + | ((hmac_output_buf[offset + 1] & 0xff) << 16) + | ((hmac_output_buf[offset + 2] & 0xff) << 8) + | ((hmac_output_buf[offset + 3] & 0xff))); + + // Limit output to correct number of digits. Modulus by 10^digits + double digit_count = pow(10, KEY_DIGITS); + S %= (uint32_t)digit_count; + + // Record value as a string + char hotp_format_buffer[16]; + int len = snprintf(hotp_format_buffer, 16, "%.*ld", KEY_DIGITS, S); + if (len < 0) { + len = 0; + } else if (len > 16) { + len = 16; + } + + // Write the value to the USB keyboard. + uint8_t keyboard_buffer[64]; // TODO: grab PR #333 once merged to remove this unnecessary buffer + int ret = usb_keyboard_hid_send_string_sync(keyboard_buffer, hotp_format_buffer, len); + if (ret < 0) { + printf("ERROR sending string with USB keyboard HID: %i\r\n", ret); + } else { + printf("Counter: %u. Typed \"%s\" on the USB HID the keyboard\r\n", (size_t)hotp_key.counter - 1, + hotp_format_buffer); + } + + // Complete + led_off(0); +} + + +// --- Main Loop --- + +// Performs initialization and interactivity. +int main(void) { + delay_ms(1000); + printf("Tock HOTP App Started. Usage:\r\n" + "* Press button 0 to get the next HOTP code for that slot.\r\n" + "* Hold button 0 to enter a new HOTP secret for that slot.\r\n"); + + // Initialize buttons + bool button_pressed = false; + if (initialize_buttons(&button_pressed) != RETURNCODE_SUCCESS) { + printf("ERROR initializing buttons\r\n"); + return 1; + } + + // Configure a default HOTP secret + program_default_secret(); + + // Main loop. Waits for button presses + while (true) { + // Yield until a button is pressed + button_pressed = false; + yield_for(&button_pressed); + + // Handle short presses on already configured keys (output next code) + if (hotp_key.len > 0) { + get_next_code(); + + // Error for short press on a non-configured key + } else if (hotp_key.len == 0) { + printf("HOTP / TOTP key not yet configured.\r\n"); + } + } + + return 0; +}