diff --git a/.gitignore b/.gitignore index eff6475..9a4ea21 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ bin debug dep obj -src/u2f_crypto_data.h +src/glyph.c + diff --git a/Makefile.genericwallet b/Makefile.genericwallet index 5e28421..225d775 100755 --- a/Makefile.genericwallet +++ b/Makefile.genericwallet @@ -45,8 +45,14 @@ PROG := token-genericwallet CONFIG_PRODUCTIONS := bin/$(PROG) +GLYPH_FILES := $(addprefix glyphs/,$(sort $(notdir $(shell find glyphs/)))) +GLYPH_DEST := src_common/glyphs.c +$(GLYPH_DEST): $(GLYPH_FILES) $(BOLOS_SDK)/icon.py + -rm $@ + if [ ! -z "$(GLYPH_FILES)" ] ; then for gif in $(GLYPH_FILES) ; do python $(BOLOS_SDK)/icon.py $$gif | grep -v "bitmap}," ; done > $(GLYPH_DEST) ; fi + SOURCE_PATH := src_genericwallet $(BOLOS_SDK)/src $(dir $(shell find $(BOLOS_SDK)/lib_stusb* | grep "\.c$$")) src_common -SOURCE_FILES := $(foreach path, $(SOURCE_PATH),$(shell find $(path) | grep "\.c$$") ) +SOURCE_FILES := $(foreach path, $(SOURCE_PATH),$(shell find $(path) | grep "\.c$$") ) $(GLYPH_DEST) INCLUDES_PATH := $(dir $(shell find $(BOLOS_SDK)/lib_stusb* | grep "\.h$$")) include src_genericwallet $(BOLOS_SDK)/include $(BOLOS_SDK)/include/arm src_common ### platform definitions @@ -55,6 +61,16 @@ DEFINES := ST31 gcc __IO=volatile DEFINES += OS_IO_SEPROXYHAL IO_SEPROXYHAL_BUFFER_SIZE_B=128 DEFINES += HAVE_BAGL HAVE_PRINTF DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=7 IO_HID_EP_LENGTH=64 HAVE_USB_APDU +DEFINES += LEDGER_MAJOR_VERSION=1 LEDGER_MINOR_VERSION=0 LEDGER_PATCH_VERSION=2 + +# U2F +DEFINES += HAVE_U2F +DEFINES += USB_SEGMENT_SIZE=64 +DEFINES += BLE_SEGMENT_SIZE=32 #max MTU, min 20 +DEFINES += U2F_MAX_MESSAGE_SIZE=264 #257+5+2 +DEFINES += UNUSED\(x\)=\(void\)x +DEFINES += PRINTF\(...\)= +DEFINES += TARGET_ID=$(TARGET_ID) ############## # Compiler # @@ -113,9 +129,9 @@ ifeq ($(filter clean,$(MAKECMDGOALS)),) endif clean: - rm -fr obj bin debug dep + rm -fr obj bin debug dep $(GLYPH_DEST) -prepare: +prepare: $(GLYPH_DEST) @mkdir -p bin obj debug dep .SECONDEXPANSION: @@ -126,10 +142,10 @@ log = $(if $(strip $(VERBOSE)),$1,@$1) default: prepare bin/$(PROG) load: - python -m ledgerblue.loadApp --targetId $(TARGET_ID) --fileName bin/$(PROG).hex --appName $(APPNAME) --icon `python $(BOLOS_SDK)/icon.py 16 16 icon.gif hexbitmaponly` $(APP_LOAD_PARAMS) + python -m ledgerblue.loadApp --targetId $(TARGET_ID) --fileName bin/$(PROG).hex --appName $(APPNAME) --icon `python $(BOLOS_SDK)/icon.py 16 16 icon.gif hexbitmaponly` --apdu $(APP_LOAD_PARAMS) load_release: - python -m ledgerblue.loadApp --targetId $(TARGET_ID) --fileName bin/$(PROG).hex --appName $(APPNAME) --icon `python $(BOLOS_SDK)/icon.py 16 16 icon.gif hexbitmaponly` $(APP_LOAD_PARAMS) --signature 3044022065e5bcf6519ea12ff991e429cc85bf7ecc789bd1a7a141d5e2dc358e5544c6170220726801803361b3296b83a3d14be8afc3374bda215b4dadd3d67bc3518e1f78bc + python -m ledgerblue.loadApp --targetId $(TARGET_ID) --fileName bin/$(PROG).hex --appName $(APPNAME) --icon `python $(BOLOS_SDK)/icon.py 16 16 icon.gif hexbitmaponly` $(APP_LOAD_PARAMS) --signature 304402205385091595d285e0b06b8f968457d70415140497a0edea385a63b9f526ebd2630220524c832f5f0d5e7ecce56db5839a70e52dc1b79bdda37c3aa08282f28b410082 delete: python -m ledgerblue.deleteApp --targetId $(TARGET_ID) --appName $(APPNAME) diff --git a/glyphs/icon_back.gif b/glyphs/icon_back.gif new file mode 100644 index 0000000..a2a7e6d Binary files /dev/null and b/glyphs/icon_back.gif differ diff --git a/src_genericwallet/main.c b/src_genericwallet/main.c index e61cd50..71d404e 100644 --- a/src_genericwallet/main.c +++ b/src_genericwallet/main.c @@ -24,6 +24,21 @@ #include "os_io_seproxyhal.h" #include "string.h" + +#ifdef HAVE_U2F + +#include "u2f_service.h" +#include "u2f_transport.h" + +volatile unsigned char u2fInputBuffer[64]; +volatile unsigned char u2fOutputBuffer[64]; +volatile unsigned char u2fMessageBuffer[U2F_MAX_MESSAGE_SIZE]; + +extern void USB_power_U2F(unsigned char enabled, unsigned char fido); +extern bool fidoActivated; + +#endif + unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; unsigned int io_seproxyhal_touch_exit(const bagl_element_t *e); @@ -39,8 +54,11 @@ uint32_t set_result_get_publicKey(void); #define CLA 0xE0 #define INS_GET_PUBLIC_KEY 0x02 #define INS_SIGN 0x04 +#define INS_GET_APP_CONFIGURATION 0x06 #define P1_CONFIRM 0x01 #define P1_NON_CONFIRM 0x00 +#define P2_NO_CHAINCODE 0x00 +#define P2_CHAINCODE 0x01 #define P1_FIRST 0x00 #define P1_MORE 0x80 @@ -56,6 +74,8 @@ uint32_t set_result_get_publicKey(void); typedef struct publicKeyContext_t { cx_ecfp_public_key_t publicKey; uint8_t address[41]; + uint8_t chainCode[32]; + bool getChaincode; } publicKeyContext_t; typedef struct transactionContext_t { @@ -71,18 +91,51 @@ union { txContext_t txContext; txContent_t txContent; cx_sha3_t sha3; +volatile uint8_t dataAllowed; +volatile uint8_t fidoTransport; volatile char addressSummary[21]; volatile char address1[21]; volatile char address2[21]; volatile char fullAmount[50]; -volatile char gasPrice[50]; -volatile char startgas[50]; +volatile char maxFee[50]; +volatile bool dataPresent; +volatile bool skipWarning; + +#ifdef HAVE_U2F + +volatile u2f_service_t u2fService; + +#endif ux_state_t ux; // display stepped screens unsigned int ux_step; unsigned int ux_step_count; +typedef struct internalStorage_t { + uint8_t dataAllowed; + uint8_t fidoTransport; + uint8_t initialized; +} internalStorage_t; + +WIDE internalStorage_t N_storage_real; +#define N_storage (*(WIDE internalStorage_t *)PIC(&N_storage_real)) + +static const char const CONTRACT_ADDRESS[] = "New contract"; + +#ifdef HAVE_U2F + +void u2f_proxy_response(u2f_service_t *service, unsigned int tx) { + os_memset(service->messageBuffer, 0, 5); + os_memmove(service->messageBuffer + 5, G_io_apdu_buffer, tx); + service->messageBuffer[tx + 5] = 0x90; + service->messageBuffer[tx + 6] = 0x00; + u2f_send_fragmented_response(service, U2F_CMD_MSG, service->messageBuffer, + tx + 7, true); +} + +#endif + // UI displayed when no signature proposal has been received static const bagl_element_t const ui_idle_blue[] = { {{BAGL_RECTANGLE, 0x00, 0, 0, 320, 480, 0, 0, BAGL_FILL, 0xf9f9f9, 0xf9f9f9, @@ -146,7 +199,7 @@ const bagl_element_t ui_idle_nanos[] = { NULL, NULL}, - {{BAGL_ICON, 0x01, 17, 9, 14, 14, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + {{BAGL_ICON, 0x01, 12, 9, 14, 14, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_ETHEREUM_BADGE}, NULL, 0, @@ -155,8 +208,8 @@ const bagl_element_t ui_idle_nanos[] = { NULL, NULL, NULL}, - {{BAGL_LABELINE, 0x01, 38, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_REGULAR_11px, 0}, + {{BAGL_LABELINE, 0x01, 33, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px, 0}, "Use wallet to", 0, 0, @@ -164,8 +217,8 @@ const bagl_element_t ui_idle_nanos[] = { NULL, NULL, NULL}, - {{BAGL_LABELINE, 0x01, 39, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_REGULAR_11px, 0}, + {{BAGL_LABELINE, 0x01, 34, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px, 0}, "view accounts", 0, 0, @@ -183,7 +236,38 @@ const bagl_element_t ui_idle_nanos[] = { NULL, NULL}, - {{BAGL_ICON, 0x02, 29, 9, 14, 14, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + // NO! //{{BAGL_LABELINE , 0x02, 34, 3, 128, 32, + // 0, 0, 0 , 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px, 0 + // }, "view accounts", 0, 0, 0, NULL, NULL, NULL }, + {{BAGL_LABELINE, 0x02, 0, 19, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Settings", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_ICON, 0x02, 3, 14, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_UP}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_ICON, 0x02, 118, 14, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_DOWN}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_ICON, 0x03, 29, 9, 14, 14, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_DASHBOARD_BADGE}, NULL, 0, @@ -192,11 +276,7 @@ const bagl_element_t ui_idle_nanos[] = { NULL, NULL, NULL}, - //{{BAGL_LABELINE , 0x02, 0, 3, 128, 32, 0, 0, 0 - //, 0xFFFFFF, 0x000000, - //BAGL_FONT_OPEN_SANS_REGULAR_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, "view - //accounts", 0, 0, 0, NULL, NULL, NULL }, - {{BAGL_LABELINE, 0x02, 50, 19, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + {{BAGL_LABELINE, 0x03, 50, 19, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px, 0}, "Quit app", 0, @@ -205,7 +285,7 @@ const bagl_element_t ui_idle_nanos[] = { NULL, NULL, NULL}, - {{BAGL_ICON, 0x02, 3, 14, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + {{BAGL_ICON, 0x03, 3, 14, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_UP}, NULL, 0, @@ -226,6 +306,365 @@ unsigned int ui_idle_nanos_prepro(const bagl_element_t *element) { return 1; } +#ifdef HAVE_U2F + +const bagl_element_t ui_settings_nanos[] = { + // type userid x y w h str rad + // fill fg bg fid iid txt touchparams... ] + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, + 0, 0}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_LABELINE, 0x01, 0, 19, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Contract data", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + //{{BAGL_ICON , 0x01, 3, 14, 7, 4, 0, 0, 0 + //, 0xFFFFFF, 0x000000, 0, + //BAGL_GLYPH_ICON_UP }, NULL, 0, 0, 0, NULL, NULL, NULL }, + {{BAGL_ICON, 0x01, 118, 14, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_DOWN}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_LABELINE, 0x01, 0, 35, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Browser support", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_ICON, 0x02, 3, 14, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_UP}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_LABELINE, 0x02, 0, 3, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Contract data", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_LABELINE, 0x02, 0, 19, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Browser support", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_ICON, 0x02, 118, 14, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_DOWN}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_ICON, 0x13, 29, 9, 14, 14, 0, 0, 0, 0xFFFFFF, 0x000000, 0, 0}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_LABELINE, 0x03, 61, 19, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px, 0}, + "Back", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_ICON, 0x03, 3, 14, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_UP}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, +}; + +#else + +const bagl_element_t ui_settings_nanos[] = { + // type userid x y w h str rad + // fill fg bg fid iid txt touchparams... ] + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, + 0, 0}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_LABELINE, 0x01, 0, 19, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Contract data", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_ICON, 0x01, 118, 14, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_DOWN}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_ICON, 0x12, 29, 9, 14, 14, 0, 0, 0, 0xFFFFFF, 0x000000, 0, 0}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_LABELINE, 0x02, 61, 19, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px, 0}, + "Back", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_ICON, 0x02, 3, 14, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_UP}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + +}; + +#endif + +unsigned int ui_settings_nanos_button(unsigned int button_mask, + unsigned int button_mask_counter); + +unsigned int ui_settings_nanos_state; +unsigned int ui_settings_nanos_prepro(const bagl_element_t *element) { + if (element->component.userid > 0) { + unsigned char displayed = (ui_settings_nanos_state == + ((element->component.userid - 1) & 0x0F)); + // display custom icon for + if (displayed && (element->component.userid == 0x13 || + element->component.userid == 0x12)) { + extern unsigned int const C_icon_back_colors[]; + extern unsigned char const C_icon_back_bitmap[]; + io_seproxyhal_display_bitmap(40, 9, 14, 14, C_icon_back_colors, 1, + C_icon_back_bitmap); + // superseded + return 0; + } + return displayed; + } + return 1; +} + +const bagl_element_t ui_settings_data_nanos[] = { + // erase + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, + 0, 0}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_LABELINE, 0x01, 37, 19, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_REGULAR_11px, 0}, + "Allow", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_ICON, 0x02, 74, 11, 16, 10, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_TOGGLE_ON}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_ICON, 0x03, 74, 11, 16, 10, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_TOGGLE_OFF}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + // icons + {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_CROSS}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_CHECK}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, +}; +unsigned int ui_settings_data_nanos_button(unsigned int button_mask, + unsigned int button_mask_counter); + +unsigned int ui_settings_data_nanos_prepro(const bagl_element_t *element) { + unsigned int display = 1; + switch (element->component.userid) { + case 0x02: + display = dataAllowed; + break; + case 0x03: + display = !dataAllowed; + break; + } + return display; +} + +#ifdef HAVE_U2F + +const bagl_element_t ui_settings_fido_nanos[] = { + // erase + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, + 0, 0}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_LABELINE, 0x01, 35, 19, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_REGULAR_11px, 0}, + "Enable", + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_ICON, 0x02, 76, 11, 16, 10, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_TOGGLE_ON}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + {{BAGL_ICON, 0x03, 76, 11, 16, 10, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_TOGGLE_OFF}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + // icons + {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_CROSS}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, + + {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, + BAGL_GLYPH_ICON_CHECK}, + NULL, + 0, + 0, + 0, + NULL, + NULL, + NULL}, +}; +unsigned int ui_settings_fido_nanos_button(unsigned int button_mask, + unsigned int button_mask_counter); + +unsigned int ui_settings_fido_nanos_prepro(const bagl_element_t *element) { + unsigned int display = 1; + switch (element->component.userid) { + case 0x02: + display = fidoTransport; + break; + case 0x03: + display = !fidoTransport; + break; + } + return display; +} + +#endif + bagl_element_t const ui_address_blue[] = { {{BAGL_RECTANGLE, 0x00, 0, 0, 320, 480, 0, 0, BAGL_FILL, 0xf9f9f9, 0xf9f9f9, 0, 0}, @@ -377,7 +816,7 @@ const bagl_element_t ui_address_nanos[] = { NULL}, {{BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - addressSummary, + (char *)addressSummary, 0, 0, 0, @@ -531,7 +970,7 @@ static const bagl_element_t const ui_approval_blue[] = { NULL}, {{BAGL_LABEL, 0x00, 0, 265, 320, 32, 0, 0, 0, 0x000000, 0xF9F9F9, BAGL_FONT_OPEN_SANS_LIGHT_14px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - "Gas price / start", + "Maximum fee", 0, 0, 0, @@ -540,16 +979,7 @@ static const bagl_element_t const ui_approval_blue[] = { NULL}, {{BAGL_LABEL, 0x00, 0, 305, 320, 33, 0, 0, 0, 0x000000, 0xF9F9F9, BAGL_FONT_OPEN_SANS_LIGHT_16px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - (const char *)gasPrice, - 0, - 0, - 0, - NULL, - NULL, - NULL}, - {{BAGL_LABEL, 0x00, 0, 325, 320, 33, 0, 0, 0, 0x000000, 0xF9F9F9, - BAGL_FONT_OPEN_SANS_LIGHT_16px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - (const char *)startgas, + (const char *)maxFee, 0, 0, 0, @@ -625,16 +1055,16 @@ const bagl_element_t ui_approval_nanos[] = { {{BAGL_LABELINE, 0x02, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - "Amount", + "WARNING", 0, 0, 0, NULL, NULL, NULL}, - {{BAGL_LABELINE, 0x02, 23, 26, 82, 11, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 26}, - fullAmount, + {{BAGL_LABELINE, 0x02, 23, 26, 82, 11, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + "Data present", 0, 0, 0, @@ -644,16 +1074,16 @@ const bagl_element_t ui_approval_nanos[] = { {{BAGL_LABELINE, 0x03, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - "Recipient account", + "Amount", 0, 0, 0, NULL, NULL, NULL}, - {{BAGL_LABELINE, 0x03, 16, 26, 96, 11, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - addressSummary, + {{BAGL_LABELINE, 0x03, 23, 26, 82, 11, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 26}, + (char *)fullAmount, 0, 0, 0, @@ -663,16 +1093,16 @@ const bagl_element_t ui_approval_nanos[] = { {{BAGL_LABELINE, 0x04, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - "Gas price", + "Recipient account", 0, 0, 0, NULL, NULL, NULL}, - {{BAGL_LABELINE, 0x04, 23, 26, 82, 11, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 26}, - gasPrice, + {{BAGL_LABELINE, 0x04, 16, 26, 96, 11, 0, 0, 0, 0xFFFFFF, 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + (char *)addressSummary, 0, 0, 0, @@ -682,7 +1112,7 @@ const bagl_element_t ui_approval_nanos[] = { {{BAGL_LABELINE, 0x05, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - "Gas limit", + "Maximum fees", 0, 0, 0, @@ -691,33 +1121,39 @@ const bagl_element_t ui_approval_nanos[] = { NULL}, {{BAGL_LABELINE, 0x05, 23, 26, 82, 11, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 26}, - startgas, + (char *)maxFee, 0, 0, 0, NULL, NULL, NULL}, + }; unsigned int ui_approval_prepro(const bagl_element_t *element) { + unsigned int display = 1; if (element->component.userid > 0) { - unsigned int display = (ux_step == element->component.userid - 1); + display = (ux_step == element->component.userid - 1); if (display) { switch (element->component.userid) { case 1: io_seproxyhal_setup_ticker(2000); break; - case 3: - io_seproxyhal_setup_ticker(3000); - break; case 2: + if (dataPresent) { + io_seproxyhal_setup_ticker(3000); + } else { + display = 0; + ux_step++; // display the next step + } + break; + case 3: io_seproxyhal_setup_ticker(MAX( 3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); break; case 4: - io_seproxyhal_setup_ticker(MAX( - 3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); + io_seproxyhal_setup_ticker(3000); break; case 5: io_seproxyhal_setup_ticker(MAX( @@ -725,15 +1161,15 @@ unsigned int ui_approval_prepro(const bagl_element_t *element) { break; } } - return display; } - return 1; + return display; } unsigned int ui_approval_nanos_button(unsigned int button_mask, unsigned int button_mask_counter); void ui_idle(void) { + skipWarning = false; if (os_seph_features() & SEPROXYHAL_TAG_SESSION_START_EVENT_FEATURE_SCREEN_BIG) { UX_DISPLAY(ui_idle_blue, NULL); @@ -753,21 +1189,24 @@ unsigned int ui_idle_nanos_button(unsigned int button_mask, unsigned int button_mask_counter) { switch (button_mask) { case BUTTON_EVT_RELEASED | BUTTON_LEFT: // UP - if (ui_idle_nanos_state == 1) { - ui_idle_nanos_state = 0; + if (ui_idle_nanos_state != 0) { + ui_idle_nanos_state--; UX_DISPLAY(ui_idle_nanos, ui_idle_nanos_prepro); } break; case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // DOWN - if (ui_idle_nanos_state == 0) { - ui_idle_nanos_state = 1; + if (ui_idle_nanos_state != 2) { + ui_idle_nanos_state++; UX_DISPLAY(ui_idle_nanos, ui_idle_nanos_prepro); } break; - case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: // EXIT + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: // Settings, EXIT if (ui_idle_nanos_state == 1) { + ui_settings_nanos_state = 0; + UX_DISPLAY(ui_settings_nanos, ui_settings_nanos_prepro); + } else if (ui_idle_nanos_state == 2) { io_seproxyhal_touch_exit(NULL); } break; @@ -775,12 +1214,112 @@ unsigned int ui_idle_nanos_button(unsigned int button_mask, return 0; } +unsigned int ui_settings_nanos_button(unsigned int button_mask, + unsigned int button_mask_counter) { + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: // UP + if (ui_settings_nanos_state != 0) { + ui_settings_nanos_state--; + UX_DISPLAY(ui_settings_nanos, ui_settings_nanos_prepro); + } + break; + + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // DOWN +#ifdef HAVE_U2F + if (ui_settings_nanos_state != 2) { +#else + if (ui_settings_nanos_state != 1) { +#endif + ui_settings_nanos_state++; + UX_DISPLAY(ui_settings_nanos, ui_settings_nanos_prepro); + } + break; + + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: // Settings, EXIT + if (ui_settings_nanos_state == 0) { + dataAllowed = N_storage.dataAllowed; + UX_DISPLAY(ui_settings_data_nanos, ui_settings_data_nanos_prepro); + } +#ifdef HAVE_U2F + else if (ui_settings_nanos_state == 1) { + fidoTransport = N_storage.fidoTransport; + UX_DISPLAY(ui_settings_fido_nanos, ui_settings_fido_nanos_prepro); + } else if (ui_settings_nanos_state == 2) { + UX_DISPLAY(ui_idle_nanos, ui_idle_nanos_prepro); + } +#else + else if (ui_settings_nanos_state == 1) { + UX_DISPLAY(ui_idle_nanos, ui_idle_nanos_prepro); + } +#endif + break; + } + return 0; +} + +unsigned int ui_settings_data_nanos_button(unsigned int button_mask, + unsigned int button_mask_counter) { + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: + dataAllowed = 0x00; + goto set; + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: + dataAllowed = 0x01; + set: + if (N_storage.dataAllowed != dataAllowed) { + nvm_write(&N_storage.dataAllowed, (void *)&dataAllowed, + sizeof(uint8_t)); + } + // no break is intentional + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: + UX_DISPLAY(ui_settings_nanos, ui_settings_nanos_prepro); + break; + } + return 0; +} + +#ifdef HAVE_U2F + +unsigned int ui_settings_fido_nanos_button(unsigned int button_mask, + unsigned int button_mask_counter) { + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: + fidoTransport = 0x00; + goto set; + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: + fidoTransport = 0x01; + set: + if (N_storage.fidoTransport != fidoTransport) { + nvm_write(&N_storage.fidoTransport, (void *)&fidoTransport, + sizeof(uint8_t)); + USB_power_U2F(0, 0); + USB_power_U2F(1, N_storage.fidoTransport); + } + // no break is intentional + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: + UX_DISPLAY(ui_settings_nanos, ui_settings_nanos_prepro); + break; + } + return 0; +} + +#endif + unsigned int io_seproxyhal_touch_address_ok(const bagl_element_t *e) { uint32_t tx = set_result_get_publicKey(); G_io_apdu_buffer[tx++] = 0x90; G_io_apdu_buffer[tx++] = 0x00; +#ifdef HAVE_U2F + if (fidoActivated) { + u2f_proxy_response((u2f_service_t *)&u2fService, tx); + } else { + // Send back the response, do not restart the event loop + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, tx); + } +#else // Send back the response, do not restart the event loop io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, tx); +#endif // Display back the original UX ui_idle(); return 0; // do not redraw the widget @@ -789,8 +1328,17 @@ unsigned int io_seproxyhal_touch_address_ok(const bagl_element_t *e) { unsigned int io_seproxyhal_touch_address_cancel(const bagl_element_t *e) { G_io_apdu_buffer[0] = 0x69; G_io_apdu_buffer[1] = 0x85; +#ifdef HAVE_U2F + if (fidoActivated) { + u2f_proxy_response((u2f_service_t *)&u2fService, 2); + } else { + // Send back the response, do not restart the event loop + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); + } +#else // Send back the response, do not restart the event loop io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); +#endif // Display back the original UX ui_idle(); return 0; // do not redraw the widget @@ -840,8 +1388,17 @@ unsigned int io_seproxyhal_touch_tx_ok(const bagl_element_t *e) { tx = 65; G_io_apdu_buffer[tx++] = 0x90; G_io_apdu_buffer[tx++] = 0x00; +#ifdef HAVE_U2F + if (fidoActivated) { + u2f_proxy_response((u2f_service_t *)&u2fService, tx); + } else { + // Send back the response, do not restart the event loop + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, tx); + } +#else // Send back the response, do not restart the event loop io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, tx); +#endif // Display back the original UX ui_idle(); return 0; // do not redraw the widget @@ -850,8 +1407,17 @@ unsigned int io_seproxyhal_touch_tx_ok(const bagl_element_t *e) { unsigned int io_seproxyhal_touch_tx_cancel(const bagl_element_t *e) { G_io_apdu_buffer[0] = 0x69; G_io_apdu_buffer[1] = 0x85; +#ifdef HAVE_U2F + if (fidoActivated) { + u2f_proxy_response((u2f_service_t *)&u2fService, 2); + } else { + // Send back the response, do not restart the event loop + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); + } +#else // Send back the response, do not restart the event loop io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); +#endif // Display back the original UX ui_idle(); return 0; // do not redraw the widget @@ -906,6 +1472,11 @@ uint32_t set_result_get_publicKey() { G_io_apdu_buffer[tx++] = 40; os_memmove(G_io_apdu_buffer + tx, tmpCtx.publicKeyContext.address, 40); tx += 40; + if (tmpCtx.publicKeyContext.getChaincode) { + os_memmove(G_io_apdu_buffer + tx, tmpCtx.publicKeyContext.chainCode, + 32); + tx += 32; + } return tx; } @@ -919,12 +1490,297 @@ void convertUint256BE(uint8_t *data, uint32_t length, uint256_t *target) { bool customProcessor(txContext_t *context) { if ((context->currentField == TX_RLP_DATA) && (context->currentFieldLength != 0)) { - screen_printf("Data field forbidden\n"); - THROW(EXCEPTION); + if (!N_storage.dataAllowed) { + screen_printf("Data field forbidden\n"); + THROW(EXCEPTION); + } else { + dataPresent = true; + } } return false; } +void handleGetPublicKey(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, + uint16_t dataLength, volatile unsigned int *flags, + volatile unsigned int *tx) { + UNUSED(dataLength); + uint8_t privateKeyData[32]; + uint32_t bip32Path[MAX_BIP32_PATH]; + uint32_t i; + uint8_t bip32PathLength = *(dataBuffer++); + cx_ecfp_private_key_t privateKey; + + if ((bip32PathLength < 0x01) || (bip32PathLength > MAX_BIP32_PATH)) { + screen_printf("Invalid path\n"); + THROW(0x6a80); + } + if ((p1 != P1_CONFIRM) && (p1 != P1_NON_CONFIRM)) { + THROW(0x6B00); + } + if ((p2 != P2_CHAINCODE) && (p2 != P2_NO_CHAINCODE)) { + THROW(0x6B00); + } + for (i = 0; i < bip32PathLength; i++) { + bip32Path[i] = (dataBuffer[0] << 24) | (dataBuffer[1] << 16) | + (dataBuffer[2] << 8) | (dataBuffer[3]); + dataBuffer += 4; + } + tmpCtx.publicKeyContext.getChaincode = (p2 == P2_CHAINCODE); + os_perso_derive_node_bip32(CX_CURVE_256K1, bip32Path, bip32PathLength, + privateKeyData, + (tmpCtx.publicKeyContext.getChaincode + ? tmpCtx.publicKeyContext.chainCode + : NULL)); + cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &privateKey); + cx_ecfp_generate_pair(CX_CURVE_256K1, &tmpCtx.publicKeyContext.publicKey, + &privateKey, 1); + os_memset(&privateKey, 0, sizeof(privateKey)); + os_memset(privateKeyData, 0, sizeof(privateKeyData)); + getEthAddressStringFromKey(&tmpCtx.publicKeyContext.publicKey, + tmpCtx.publicKeyContext.address, &sha3); + if (p1 == P1_NON_CONFIRM) { + *tx = set_result_get_publicKey(); + THROW(0x9000); + } else { + addressSummary[0] = '0'; + addressSummary[1] = 'x'; + os_memmove((unsigned char *)(addressSummary + 2), + tmpCtx.publicKeyContext.address, 4); + os_memmove((unsigned char *)(addressSummary + 6), "...", 3); + os_memmove((unsigned char *)(addressSummary + 9), + tmpCtx.publicKeyContext.address + 40 - 4, 4); + addressSummary[13] = '\0'; + os_memmove((unsigned char *)address1, tmpCtx.publicKeyContext.address, + 20); + address1[20] = '\0'; + os_memmove((unsigned char *)address2, + tmpCtx.publicKeyContext.address + 20, 20); + address2[20] = '\0'; + + // prepare for a UI based reply + skipWarning = false; + if (os_seph_features() & + SEPROXYHAL_TAG_SESSION_START_EVENT_FEATURE_SCREEN_BIG) { + UX_DISPLAY(ui_address_blue, NULL); + } else { + ux_step = 0; + ux_step_count = 2; + UX_DISPLAY(ui_address_nanos, ui_address_prepro); + } + + *flags |= IO_ASYNCH_REPLY; + } +} + +void handleSign(uint8_t p1, uint8_t p2, uint8_t *workBuffer, + uint16_t dataLength, volatile unsigned int *flags, + volatile unsigned int *tx) { + UNUSED(tx); + parserStatus_e txResult; + uint256_t gasPrice, startGas, uint256; + uint32_t i; + uint8_t address[41]; + if (p1 == P1_FIRST) { + tmpCtx.transactionContext.pathLength = workBuffer[0]; + if ((tmpCtx.transactionContext.pathLength < 0x01) || + (tmpCtx.transactionContext.pathLength > MAX_BIP32_PATH)) { + screen_printf("Invalid path\n"); + THROW(0x6a80); + } + workBuffer++; + dataLength--; + for (i = 0; i < tmpCtx.transactionContext.pathLength; i++) { + tmpCtx.transactionContext.bip32Path[i] = + (workBuffer[0] << 24) | (workBuffer[1] << 16) | + (workBuffer[2] << 8) | (workBuffer[3]); + workBuffer += 4; + dataLength -= 4; + } + dataPresent = false; + initTx(&txContext, &sha3, &txContent, customProcessor, NULL); + } else if (p1 != P1_MORE) { + THROW(0x6B00); + } + if (p2 != 0) { + THROW(0x6B00); + } + if (txContext.currentField == TX_RLP_NONE) { + screen_printf("Parser not initialized\n"); + THROW(0x6985); + } + txResult = processTx(&txContext, workBuffer, dataLength); + switch (txResult) { + case USTREAM_FINISHED: + break; + case USTREAM_PROCESSING: + THROW(0x9000); + case USTREAM_FAULT: + THROW(0x6A80); + default: + screen_printf("Unexpected parser status\n"); + THROW(0x6A80); + } + + // Store the hash + cx_hash((cx_hash_t *)&sha3, CX_LAST, tmpCtx.transactionContext.hash, 0, + tmpCtx.transactionContext.hash); + // Add address + if (txContent.destinationLength != 0) { + getEthAddressStringFromBinary(txContent.destination, address, &sha3); + addressSummary[0] = '0'; + addressSummary[1] = 'x'; + os_memmove((unsigned char *)(addressSummary + 2), address, 4); + os_memmove((unsigned char *)(addressSummary + 6), "...", 3); + os_memmove((unsigned char *)(addressSummary + 9), address + 40 - 4, 4); + addressSummary[13] = '\0'; + os_memmove((unsigned char *)address1, address, 20); + address1[20] = '\0'; + os_memmove((unsigned char *)address2, address + 20, 20); + address2[20] = '\0'; + } else { + os_memmove((void *)addressSummary, CONTRACT_ADDRESS, + sizeof(CONTRACT_ADDRESS)); + address1[0] = '\0'; + address2[0] = '\0'; + } + // Add amount in ethers + convertUint256BE(txContent.value.value, txContent.value.length, &uint256); + tostring256(&uint256, 10, (char *)(G_io_apdu_buffer + 100), 100); + i = 0; + while (G_io_apdu_buffer[100 + i]) { + i++; + } + adjustDecimals((char *)(G_io_apdu_buffer + 100), i, + (char *)G_io_apdu_buffer, 100, WEI_TO_ETHER); + i = 0; + fullAmount[0] = 'E'; + fullAmount[1] = 'T'; + fullAmount[2] = 'H'; + fullAmount[3] = ' '; + while (G_io_apdu_buffer[i]) { + fullAmount[4 + i] = G_io_apdu_buffer[i]; + i++; + } + fullAmount[4 + i] = '\0'; + // Compute maximum fee + convertUint256BE(txContent.gasprice.value, txContent.gasprice.length, + &gasPrice); + convertUint256BE(txContent.startgas.value, txContent.startgas.length, + &startGas); + mul256(&gasPrice, &startGas, &uint256); + tostring256(&uint256, 10, (char *)(G_io_apdu_buffer + 100), 100); + i = 0; + while (G_io_apdu_buffer[100 + i]) { + i++; + } + adjustDecimals((char *)(G_io_apdu_buffer + 100), i, + (char *)G_io_apdu_buffer, 100, WEI_TO_ETHER); + i = 0; + maxFee[0] = 'E'; + maxFee[1] = 'T'; + maxFee[2] = 'H'; + maxFee[3] = ' '; + while (G_io_apdu_buffer[i]) { + maxFee[4 + i] = G_io_apdu_buffer[i]; + i++; + } + maxFee[4 + i] = '\0'; + + if (os_seph_features() & + SEPROXYHAL_TAG_SESSION_START_EVENT_FEATURE_SCREEN_BIG) { + skipWarning = false; + UX_DISPLAY(ui_approval_blue, NULL); + } else { + skipWarning = !dataPresent; + ux_step = 0; + ux_step_count = 5; + UX_DISPLAY(ui_approval_nanos, ui_approval_prepro); + } + + *flags |= IO_ASYNCH_REPLY; +} + +void handleGetAppConfiguration(uint8_t p1, uint8_t p2, uint8_t *workBuffer, + uint16_t dataLength, + volatile unsigned int *flags, + volatile unsigned int *tx) { + UNUSED(p1); + UNUSED(p2); + UNUSED(workBuffer); + UNUSED(dataLength); + UNUSED(flags); + G_io_apdu_buffer[0] = (N_storage.dataAllowed ? 0x01 : 0x00); + G_io_apdu_buffer[1] = LEDGER_MAJOR_VERSION; + G_io_apdu_buffer[2] = LEDGER_MINOR_VERSION; + G_io_apdu_buffer[3] = LEDGER_PATCH_VERSION; + *tx = 4; + THROW(0x9000); +} + +void handleApdu(volatile unsigned int *flags, volatile unsigned int *tx) { + unsigned short sw = 0; + + BEGIN_TRY { + TRY { + if (G_io_apdu_buffer[OFFSET_CLA] != CLA) { + THROW(0x6E00); + } + + switch (G_io_apdu_buffer[OFFSET_INS]) { + case INS_GET_PUBLIC_KEY: + handleGetPublicKey(G_io_apdu_buffer[OFFSET_P1], + G_io_apdu_buffer[OFFSET_P2], + G_io_apdu_buffer + OFFSET_CDATA, + G_io_apdu_buffer[OFFSET_LC], flags, tx); + break; + + case INS_SIGN: + handleSign(G_io_apdu_buffer[OFFSET_P1], + G_io_apdu_buffer[OFFSET_P2], + G_io_apdu_buffer + OFFSET_CDATA, + G_io_apdu_buffer[OFFSET_LC], flags, tx); + break; + + case INS_GET_APP_CONFIGURATION: + handleGetAppConfiguration( + G_io_apdu_buffer[OFFSET_P1], G_io_apdu_buffer[OFFSET_P2], + G_io_apdu_buffer + OFFSET_CDATA, + G_io_apdu_buffer[OFFSET_LC], flags, tx); + break; + + + default: + THROW(0x6D00); + break; + } + } + CATCH_OTHER(e) { + switch (e & 0xF000) { + case 0x6000: + // Wipe the transaction context and report the exception + sw = e; + os_memset(&txContext, 0, sizeof(txContext)); + break; + case 0x9000: + // All is well + sw = e; + break; + default: + // Internal error + sw = 0x6800 | (e & 0x7FF); + break; + } + // Unexpected exception => report + G_io_apdu_buffer[*tx] = sw >> 8; + G_io_apdu_buffer[*tx + 1] = sw; + *tx += 2; + } + FINALLY { + } + } + END_TRY; +} + void sample_main(void) { volatile unsigned int rx = 0; volatile unsigned int tx = 0; @@ -953,229 +1809,7 @@ void sample_main(void) { THROW(0x6982); } - if (G_io_apdu_buffer[OFFSET_CLA] != CLA) { - THROW(0x6E00); - } - - switch (G_io_apdu_buffer[OFFSET_INS]) { - case INS_GET_PUBLIC_KEY: { - uint8_t privateKeyData[32]; - uint32_t bip32Path[MAX_BIP32_PATH]; - uint32_t i; - uint8_t bip32PathLength = G_io_apdu_buffer[OFFSET_CDATA]; - uint8_t p1 = G_io_apdu_buffer[OFFSET_P1]; - uint8_t *dataBuffer = G_io_apdu_buffer + OFFSET_CDATA + 1; - cx_ecfp_private_key_t privateKey; - - if ((bip32PathLength < 0x01) || - (bip32PathLength > MAX_BIP32_PATH)) { - screen_printf("Invalid path\n"); - THROW(0x6a80); - } - if ((p1 != P1_CONFIRM) && (p1 != P1_NON_CONFIRM)) { - THROW(0x6B00); - } - if (G_io_apdu_buffer[OFFSET_P2] != 0) { - THROW(0x6B00); - } - for (i = 0; i < bip32PathLength; i++) { - bip32Path[i] = (dataBuffer[0] << 24) | - (dataBuffer[1] << 16) | - (dataBuffer[2] << 8) | (dataBuffer[3]); - dataBuffer += 4; - } - os_perso_derive_node_bip32(CX_CURVE_256K1, bip32Path, - bip32PathLength, privateKeyData, - NULL); - cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, - &privateKey); - cx_ecfp_generate_pair(CX_CURVE_256K1, - &tmpCtx.publicKeyContext.publicKey, - &privateKey, 1); - os_memset(&privateKey, 0, sizeof(privateKey)); - os_memset(privateKeyData, 0, sizeof(privateKeyData)); - getEthAddressStringFromKey( - &tmpCtx.publicKeyContext.publicKey, - tmpCtx.publicKeyContext.address, &sha3); - if (p1 == P1_NON_CONFIRM) { - tx = set_result_get_publicKey(); - THROW(0x9000); - } else { - addressSummary[0] = '0'; - addressSummary[1] = 'x'; - os_memmove((unsigned char *)(addressSummary + 2), - tmpCtx.publicKeyContext.address, 4); - os_memmove((unsigned char *)(addressSummary + 6), "...", - 3); - os_memmove((unsigned char *)(addressSummary + 9), - tmpCtx.publicKeyContext.address + 40 - 4, 4); - addressSummary[13] = '\0'; - os_memmove((unsigned char *)address1, - tmpCtx.publicKeyContext.address, 20); - address1[20] = '\0'; - os_memmove((unsigned char *)address2, - tmpCtx.publicKeyContext.address + 20, 20); - address2[20] = '\0'; - - // prepare for a UI based reply - if (os_seph_features() & - SEPROXYHAL_TAG_SESSION_START_EVENT_FEATURE_SCREEN_BIG) { - UX_DISPLAY(ui_address_blue, NULL); - } else { - ux_step = 0; - ux_step_count = 2; - // io_seproxyhal_setup_ticker(2000); - UX_DISPLAY(ui_address_nanos, ui_address_prepro); - } - - flags |= IO_ASYNCH_REPLY; - } - } - - break; - - case INS_SIGN: { - uint8_t commandLength = G_io_apdu_buffer[OFFSET_LC]; - uint8_t *workBuffer = G_io_apdu_buffer + OFFSET_CDATA; - parserStatus_e txResult; - uint256_t uint256; - uint32_t i; - uint8_t address[41]; - if (G_io_apdu_buffer[OFFSET_P1] == P1_FIRST) { - tmpCtx.transactionContext.pathLength = workBuffer[0]; - if ((tmpCtx.transactionContext.pathLength < 0x01) || - (tmpCtx.transactionContext.pathLength > - MAX_BIP32_PATH)) { - screen_printf("Invalid path\n"); - THROW(0x6a80); - } - workBuffer++; - commandLength--; - for (i = 0; i < tmpCtx.transactionContext.pathLength; - i++) { - tmpCtx.transactionContext.bip32Path[i] = - (workBuffer[0] << 24) | (workBuffer[1] << 16) | - (workBuffer[2] << 8) | (workBuffer[3]); - workBuffer += 4; - commandLength -= 4; - } - initTx(&txContext, &sha3, &txContent, customProcessor, - NULL); - } else if (G_io_apdu_buffer[OFFSET_P1] != P1_MORE) { - THROW(0x6B00); - } - if (G_io_apdu_buffer[OFFSET_P2] != 0) { - THROW(0x6B00); - } - if (txContext.currentField == TX_RLP_NONE) { - screen_printf("Parser not initialized\n"); - THROW(0x6985); - } - txResult = processTx(&txContext, workBuffer, commandLength); - switch (txResult) { - case USTREAM_FINISHED: - break; - case USTREAM_PROCESSING: - THROW(0x9000); - case USTREAM_FAULT: - THROW(0x6A80); - default: - screen_printf("Unexpected parser status\n"); - THROW(0x6A80); - } - - // Store the hash - cx_hash((cx_hash_t *)&sha3, CX_LAST, - tmpCtx.transactionContext.hash, 0, - tmpCtx.transactionContext.hash); - // Add address - getEthAddressStringFromBinary(txContent.destination, - address, &sha3); - addressSummary[0] = '0'; - addressSummary[1] = 'x'; - os_memmove((unsigned char *)(addressSummary + 2), address, - 4); - os_memmove((unsigned char *)(addressSummary + 6), "...", 3); - os_memmove((unsigned char *)(addressSummary + 9), - address + 40 - 4, 4); - addressSummary[13] = '\0'; - os_memmove((unsigned char *)address1, address, 20); - address1[20] = '\0'; - os_memmove((unsigned char *)address2, address + 20, 20); - address2[20] = '\0'; - // Add amount in ethers - convertUint256BE(txContent.value.value, - txContent.value.length, &uint256); - tostring256(&uint256, 10, (char *)(G_io_apdu_buffer + 100), - 100); - i = 0; - while (G_io_apdu_buffer[100 + i]) { - i++; - } - adjustDecimals((char *)(G_io_apdu_buffer + 100), i, - (char *)G_io_apdu_buffer, 100, WEI_TO_ETHER); - i = 0; - fullAmount[0] = 'E'; - fullAmount[1] = 'T'; - fullAmount[2] = 'H'; - fullAmount[3] = ' '; - while (G_io_apdu_buffer[i]) { - fullAmount[4 + i] = G_io_apdu_buffer[i]; - i++; - } - fullAmount[4 + i] = '\0'; - // Add gas price in ethers - convertUint256BE(txContent.gasprice.value, - txContent.gasprice.length, &uint256); - tostring256(&uint256, 10, (char *)(G_io_apdu_buffer + 100), - 100); - i = 0; - while (G_io_apdu_buffer[100 + i]) { - i++; - } - adjustDecimals((char *)(G_io_apdu_buffer + 100), i, - (char *)G_io_apdu_buffer, 100, WEI_TO_ETHER); - i = 0; - gasPrice[0] = 'E'; - gasPrice[1] = 'T'; - gasPrice[2] = 'H'; - gasPrice[3] = ' '; - while (G_io_apdu_buffer[i]) { - gasPrice[4 + i] = G_io_apdu_buffer[i]; - i++; - } - gasPrice[4 + i] = '\0'; - // Add gas limit in native format - convertUint256BE(txContent.startgas.value, - txContent.startgas.length, &uint256); - tostring256(&uint256, 10, (char *)G_io_apdu_buffer, 100); - i = 0; - while (G_io_apdu_buffer[i]) { - startgas[i] = G_io_apdu_buffer[i]; - i++; - } - startgas[i] = '\0'; - - if (os_seph_features() & - SEPROXYHAL_TAG_SESSION_START_EVENT_FEATURE_SCREEN_BIG) { - UX_DISPLAY(ui_approval_blue, NULL); - } else { - ux_step = 0; - ux_step_count = 5; - // io_seproxyhal_setup_ticker(2000); - UX_DISPLAY(ui_approval_nanos, ui_approval_prepro); - } - - flags |= IO_ASYNCH_REPLY; - } break; - - case 0xFF: // return to dashboard - goto return_to_dashboard; - - default: - THROW(0x6D00); - break; - } + handleApdu(&flags, &tx); } CATCH_OTHER(e) { switch (e & 0xF000) { @@ -1204,7 +1838,7 @@ void sample_main(void) { END_TRY; } -return_to_dashboard: + // return_to_dashboard: return; } @@ -1231,12 +1865,27 @@ unsigned char io_event(unsigned char channel) { // TODO perform actions after all screen elements have been // displayed } else { - UX_DISPLAY_PROCESSED_EVENT(); + // UX_DISPLAY_PROCESSED_EVENT(); + // process elements until the prepro accepts or send a new one in + // the background + while (ux.elements && ux.elements_current < ux.elements_count && + !io_seproxyhal_spi_is_status_sent()) { + if (!ux.elements_preprocessor || + ux.elements_preprocessor( + &ux.elements[ux.elements_current])) { + io_seproxyhal_display(&ux.elements[ux.elements_current++]); + break; + } + ux.elements_current++; + } } break; case SEPROXYHAL_TAG_TICKER_EVENT: // prepare next screen + if (skipWarning && (ux_step == 0)) { + ux_step++; + } ux_step = (ux_step + 1) % ux_step_count; // redisplay screen UX_REDISPLAY(); @@ -1282,7 +1931,27 @@ __attribute__((section(".boot"))) int main(void) { TRY { io_seproxyhal_init(); - USB_power(1); + if (N_storage.initialized != 0x01) { + internalStorage_t storage; + storage.dataAllowed = 0x00; + storage.fidoTransport = 0x00; + storage.initialized = 0x01; + nvm_write(&N_storage, (void *)&storage, + sizeof(internalStorage_t)); + } + +#ifdef HAVE_U2F + os_memset((unsigned char *)&u2fService, 0, sizeof(u2fService)); + u2fService.inputBuffer = (uint8_t *)u2fInputBuffer; + u2fService.outputBuffer = (uint8_t *)u2fOutputBuffer; + u2fService.messageBuffer = (uint8_t *)u2fMessageBuffer; + u2fService.messageBufferSize = U2F_MAX_MESSAGE_SIZE; + u2f_initialize_service((u2f_service_t *)&u2fService); + + USB_power_U2F(1, N_storage.fidoTransport); +#else + USB_power_U2F(1, 0); +#endif ui_idle(); diff --git a/src_genericwallet/u2f_io.c b/src_genericwallet/u2f_io.c new file mode 100644 index 0000000..32b51ab --- /dev/null +++ b/src_genericwallet/u2f_io.c @@ -0,0 +1,79 @@ +#ifdef HAVE_U2F + +/* +******************************************************************************* +* Portable FIDO U2F implementation +* Ledger Blue specific initialization +* (c) 2016 Ledger +* +* 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 +#include "os.h" + +#include "os_io_seproxyhal.h" + +#include "u2f_io.h" +#include "u2f_transport.h" + +extern void u2f_reset_display(void); + +volatile unsigned char u2fCommandSent = 0; +volatile unsigned char u2fFirstCommand = 0; +volatile unsigned char u2fClosed = 0; + +void u2f_io_open_session(void) { + // PRINTF("u2f_io_open_session\n"); + u2fCommandSent = 0; + u2fFirstCommand = 1; + u2fClosed = 0; +} + +unsigned char u2fSegment[MAX_SEGMENT_SIZE]; + +void u2f_io_send(uint8_t *buffer, uint16_t length, + u2f_transport_media_t media) { + if (media == U2F_MEDIA_USB) { + os_memset(u2fSegment, 0, sizeof(u2fSegment)); + } + os_memmove(u2fSegment, buffer, length); + // PRINTF("u2f_io_send\n"); + if (u2fFirstCommand) { + u2fFirstCommand = 0; + } + switch (media) { + case U2F_MEDIA_USB: + io_usb_send_apdu_data(u2fSegment, USB_SEGMENT_SIZE); + break; +#ifdef HAVE_BLE + case U2F_MEDIA_BLE: + BLE_protocol_send(buffer, length); + break; +#endif + default: + PRINTF("Request to send on unsupported media %d\n", media); + break; + } +} + +void u2f_io_close_session(void) { + // PRINTF("u2f_close_session\n"); + if (!u2fClosed) { + // u2f_reset_display(); + u2fClosed = 1; + } +} + +#endif diff --git a/src_genericwallet/u2f_io.h b/src_genericwallet/u2f_io.h new file mode 100644 index 0000000..495b222 --- /dev/null +++ b/src_genericwallet/u2f_io.h @@ -0,0 +1,35 @@ +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* 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 +#include + +#ifndef __U2F_IO_H__ + +#define __U2F_IO_H__ + +#include "u2f_service.h" + +#define EXCEPTION_DISCONNECT 0x80 + +void u2f_io_open_session(void); +void u2f_io_send(uint8_t *buffer, uint16_t length, u2f_transport_media_t media); +void u2f_io_close_session(void); + +#endif diff --git a/src_genericwallet/u2f_processing.c b/src_genericwallet/u2f_processing.c new file mode 100644 index 0000000..407ca1f --- /dev/null +++ b/src_genericwallet/u2f_processing.c @@ -0,0 +1,281 @@ +#ifdef HAVE_U2F + +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* 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 +#include "os.h" +#include "cx.h" +#include "u2f_service.h" +#include "u2f_transport.h" +#include "u2f_processing.h" + +void handleApdu(volatile unsigned int *flags, volatile unsigned int *tx); +void u2f_proxy_response(u2f_service_t *service, unsigned int tx); + +static const uint8_t SW_SUCCESS[] = {0x90, 0x00}; +static const uint8_t SW_PROOF_OF_PRESENCE_REQUIRED[] = {0x69, 0x85}; +static const uint8_t SW_BAD_KEY_HANDLE[] = {0x6A, 0x80}; + +static const uint8_t VERSION[] = {'U', '2', 'F', '_', 'V', '2', 0x90, 0x00}; +static const uint8_t DUMMY_ZERO[] = {0x00}; + +static const uint8_t SW_UNKNOWN_INSTRUCTION[] = {0x6d, 0x00}; +static const uint8_t SW_UNKNOWN_CLASS[] = {0x6e, 0x00}; +static const uint8_t SW_WRONG_LENGTH[] = {0x67, 0x00}; +static const uint8_t SW_INTERNAL[] = {0x6F, 0x00}; + +static const uint8_t NOTIFY_USER_PRESENCE_NEEDED[] = { + KEEPALIVE_REASON_TUP_NEEDED}; + +static const uint8_t PROXY_MAGIC[] = {'w', '0', 'w'}; + +#define INIT_U2F_VERSION 0x02 +#define INIT_DEVICE_VERSION_MAJOR 0 +#define INIT_DEVICE_VERSION_MINOR 1 +#define INIT_BUILD_VERSION 0 +#define INIT_CAPABILITIES 0x00 + +#define FIDO_CLA 0x00 +#define FIDO_INS_ENROLL 0x01 +#define FIDO_INS_SIGN 0x02 +#define FIDO_INS_GET_VERSION 0x03 + +#define FIDO_INS_PROP_GET_COUNTER 0xC0 // U2F_VENDOR_FIRST + +#define P1_SIGN_CHECK_ONLY 0x07 +#define P1_SIGN_SIGN 0x03 + +#define U2F_ENROLL_RESERVED 0x05 +#define SIGN_USER_PRESENCE_MASK 0x01 + +#define MAX_SEQ_TIMEOUT_MS 500 +#define MAX_KEEPALIVE_TIMEOUT_MS 500 + +static const uint8_t DUMMY_USER_PRESENCE[] = {SIGN_USER_PRESENCE_MASK}; + + +void u2f_handle_enroll(u2f_service_t *service, uint8_t p1, uint8_t p2, + uint8_t *buffer, uint16_t length) { + (void)p1; + (void)p2; + (void)buffer; + if (length != 32 + 32) { + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_WRONG_LENGTH, + sizeof(SW_WRONG_LENGTH), true); + return; + } + u2f_send_fragmented_response(service, U2F_CMD_MSG, (uint8_t *)SW_INTERNAL, + sizeof(SW_INTERNAL), true); +} + +void u2f_handle_sign(u2f_service_t *service, uint8_t p1, uint8_t p2, + uint8_t *buffer, uint16_t length) { + (void)p1; + (void)p2; + (void)length; + uint8_t keyHandleLength; + uint8_t i; + volatile unsigned int flags = 0; + volatile unsigned int tx = 0; + + if (length < 32 + 32 + 1) { + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_WRONG_LENGTH, + sizeof(SW_WRONG_LENGTH), true); + return; + } + if ((p1 != P1_SIGN_CHECK_ONLY) && (p1 != P1_SIGN_SIGN)) { + u2f_response_error(service, ERROR_PROP_INVALID_PARAMETERS_APDU, true, + service->channel); + return; + } + + + keyHandleLength = buffer[64]; + for (i = 0; i < keyHandleLength; i++) { + buffer[65 + i] ^= PROXY_MAGIC[i % sizeof(PROXY_MAGIC)]; + } + // Check magic + if (length != (32 + 32 + 1 + 5 + buffer[65 + 4])) { + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_BAD_KEY_HANDLE, + sizeof(SW_BAD_KEY_HANDLE), true); + } + // Check that it looks like an APDU + os_memmove(G_io_apdu_buffer, buffer + 65, keyHandleLength); + handleApdu(&flags, &tx); + if ((flags & IO_ASYNCH_REPLY) == 0) { + u2f_proxy_response(service, tx); + } +} + +void u2f_handle_get_version(u2f_service_t *service, uint8_t p1, uint8_t p2, + uint8_t *buffer, uint16_t length) { + // screen_printf("U2F version\n"); + (void)p1; + (void)p2; + (void)buffer; + if (length != 0) { + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_WRONG_LENGTH, + sizeof(SW_WRONG_LENGTH), true); + return; + } + u2f_send_fragmented_response(service, U2F_CMD_MSG, (uint8_t *)VERSION, + sizeof(VERSION), true); +} + +void u2f_handle_cmd_init(u2f_service_t *service, uint8_t *buffer, + uint16_t length, uint8_t *channelInit) { + // screen_printf("U2F init\n"); + uint8_t channel[4]; + (void)length; + uint16_t offset = 0; + if (u2f_is_channel_forbidden(channelInit)) { + u2f_response_error(service, ERROR_INVALID_CID, true, channelInit); + return; + } + if (u2f_is_channel_broadcast(channelInit)) { + cx_rng(channel, 4); + } else { + os_memmove(channel, channelInit, 4); + } + os_memmove(service->messageBuffer + offset, buffer, 8); + offset += 8; + os_memmove(service->messageBuffer + offset, channel, 4); + offset += 4; + service->messageBuffer[offset++] = INIT_U2F_VERSION; + service->messageBuffer[offset++] = INIT_DEVICE_VERSION_MAJOR; + service->messageBuffer[offset++] = INIT_DEVICE_VERSION_MINOR; + service->messageBuffer[offset++] = INIT_BUILD_VERSION; + service->messageBuffer[offset++] = INIT_CAPABILITIES; + if (u2f_is_channel_broadcast(channelInit)) { + os_memset(service->channel, 0xff, 4); + } else { + os_memmove(service->channel, channel, 4); + } + service->keepUserPresence = true; + u2f_send_fragmented_response(service, U2F_CMD_INIT, service->messageBuffer, + offset, true); + // os_memmove(service->channel, channel, 4); +} + +void u2f_handle_cmd_ping(u2f_service_t *service, uint8_t *buffer, + uint16_t length) { + // screen_printf("U2F ping\n"); + u2f_send_fragmented_response(service, U2F_CMD_PING, buffer, length, true); +} + +void u2f_handle_cmd_msg(u2f_service_t *service, uint8_t *buffer, + uint16_t length) { + // screen_printf("U2F msg\n"); + uint8_t cla = buffer[0]; + uint8_t ins = buffer[1]; + uint8_t p1 = buffer[2]; + uint8_t p2 = buffer[3]; + uint32_t dataLength = (buffer[4] << 16) | (buffer[5] << 8) | (buffer[6]); + if ((dataLength != (uint16_t)(length - 9)) && + (dataLength != (uint16_t)(length - 7))) { // Le is optional + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_WRONG_LENGTH, + sizeof(SW_WRONG_LENGTH), true); + return; + } + if (cla != FIDO_CLA) { + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_UNKNOWN_CLASS, + sizeof(SW_UNKNOWN_CLASS), true); + return; + } + switch (ins) { + case FIDO_INS_ENROLL: + // screen_printf("enroll\n"); + u2f_handle_enroll(service, p1, p2, buffer + 7, dataLength); + break; + case FIDO_INS_SIGN: + // screen_printf("sign\n"); + u2f_handle_sign(service, p1, p2, buffer + 7, dataLength); + break; + case FIDO_INS_GET_VERSION: + // screen_printf("version\n"); + u2f_handle_get_version(service, p1, p2, buffer + 7, dataLength); + break; + default: + // screen_printf("unsupported\n"); + u2f_send_fragmented_response(service, U2F_CMD_MSG, + (uint8_t *)SW_UNKNOWN_INSTRUCTION, + sizeof(SW_UNKNOWN_INSTRUCTION), true); + return; + } +} + +void u2f_process_message(u2f_service_t *service, uint8_t *buffer, + uint8_t *channel) { + uint8_t cmd = buffer[0]; + uint16_t length = (buffer[1] << 8) | (buffer[2]); + switch (cmd) { + case U2F_CMD_INIT: + u2f_handle_cmd_init(service, buffer + 3, length, channel); + break; + case U2F_CMD_PING: + service->pendingContinuation = false; + u2f_handle_cmd_ping(service, buffer + 3, length); + break; + case U2F_CMD_MSG: + service->pendingContinuation = false; + if (!service->noReentry && service->runningCommand) { + u2f_response_error(service, ERROR_CHANNEL_BUSY, false, + service->channel); + break; + } + service->runningCommand = true; + u2f_handle_cmd_msg(service, buffer + 3, length); + break; + } +} + +void u2f_timeout(u2f_service_t *service) { + service->timerNeedGeneralStatus = true; + if ((service->transportMedia == U2F_MEDIA_USB) && + (service->pendingContinuation)) { + service->seqTimeout += service->timerInterval; + if (service->seqTimeout > MAX_SEQ_TIMEOUT_MS) { + service->pendingContinuation = false; + u2f_response_error(service, ERROR_MSG_TIMEOUT, true, + service->lastContinuationChannel); + } + } +#ifdef HAVE_BLE + if ((service->transportMedia == U2F_MEDIA_BLE) && + (service->requireKeepalive)) { + service->keepaliveTimeout += service->timerInterval; + if (service->keepaliveTimeout > MAX_KEEPALIVE_TIMEOUT_MS) { + service->keepaliveTimeout = 0; + u2f_send_fragmented_response(service, U2F_CMD_KEEPALIVE, + (uint8_t *)NOTIFY_USER_PRESENCE_NEEDED, + sizeof(NOTIFY_USER_PRESENCE_NEEDED), + false); + } + } +#endif +} + +#endif diff --git a/src_genericwallet/u2f_processing.h b/src_genericwallet/u2f_processing.h new file mode 100644 index 0000000..429c9ba --- /dev/null +++ b/src_genericwallet/u2f_processing.h @@ -0,0 +1,29 @@ +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* 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. +********************************************************************************/ + +#ifndef __U2F_PROCESSING_H__ + +#define __U2F_PROCESSING_H__ + +void u2f_process_message(u2f_service_t *service, uint8_t *buffer, + uint8_t *channel); +void u2f_timeout(u2f_service_t *service); + +void u2f_handle_ux_callback(u2f_service_t *service); + +#endif diff --git a/src_genericwallet/u2f_service.c b/src_genericwallet/u2f_service.c new file mode 100644 index 0000000..b7a7b9c --- /dev/null +++ b/src_genericwallet/u2f_service.c @@ -0,0 +1,146 @@ +#ifdef HAVE_U2F + +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* 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 +#include "u2f_service.h" +#include "u2f_transport.h" +#include "u2f_processing.h" +#include "u2f_timer.h" + +// not too fast blinking +#define DEFAULT_TIMER_INTERVAL_MS 500 + +void u2f_reset(u2f_service_t *service, bool keepUserPresence) { + service->transportState = U2F_IDLE; + service->runningCommand = false; + // service->promptUserPresence = false; + if (service->keepUserPresence) { + keepUserPresence = true; + service->keepUserPresence = false; + } +#ifdef HAVE_NO_USER_PRESENCE_CHECK + service->keepUserPresence = true; + service->userPresence = true; +#endif // HAVE_NO_USER_PRESENCE_CHECK +} + +void u2f_initialize_service(u2f_service_t *service) { + service->handleFunction = (u2fHandle_t)u2f_process_message; + service->timeoutFunction = (u2fTimer_t)u2f_timeout; + service->timerInterval = DEFAULT_TIMER_INTERVAL_MS; + u2f_reset(service, false); + service->promptUserPresence = false; + service->userPresence = false; +#ifdef HAVE_NO_USER_PRESENCE_CHECK + service->keepUserPresence = true; + service->userPresence = true; +#endif // HAVE_NO_USER_PRESENCE_CHECK +} + +void u2f_send_direct_response_short(u2f_service_t *service, uint8_t *buffer, + uint16_t len) { + (void)service; + uint16_t maxSize = 0; + switch (service->packetMedia) { + case U2F_MEDIA_USB: + maxSize = USB_SEGMENT_SIZE; + break; +#ifdef HAVE_BLE + case U2F_MEDIA_BLE: + maxSize = service->bleMtu; + break; +#endif + default: + PRINTF("Request to send on unsupported media %d\n", + service->packetMedia); + break; + } + if (len > maxSize) { + return; + } + u2f_io_send(buffer, len, service->packetMedia); + u2f_io_close_session(); +} + +void u2f_send_fragmented_response(u2f_service_t *service, uint8_t cmd, + uint8_t *buffer, uint16_t len, + bool resetAfterSend) { + if (resetAfterSend) { + service->transportState = U2F_SENDING_RESPONSE; + } + service->sending = true; + service->sendPacketIndex = 0; + service->sendBuffer = buffer; + service->sendOffset = 0; + service->sendLength = len; + service->sendCmd = cmd; + service->resetAfterSend = resetAfterSend; + u2f_continue_sending_fragmented_response(service); +} + +void u2f_continue_sending_fragmented_response(u2f_service_t *service) { + do { + uint16_t channelHeader = + (service->transportMedia == U2F_MEDIA_USB ? 4 : 0); + uint8_t headerSize = + (service->sendPacketIndex == 0 ? (channelHeader + 3) + : (channelHeader + 1)); + uint16_t maxBlockSize = + (service->transportMedia == U2F_MEDIA_USB ? USB_SEGMENT_SIZE + : service->bleMtu); + uint16_t blockSize = ((service->sendLength - service->sendOffset) > + (maxBlockSize - headerSize) + ? (maxBlockSize - headerSize) + : service->sendLength - service->sendOffset); + uint16_t dataSize = blockSize + headerSize; + uint16_t offset = 0; + // Fragment + if (service->transportMedia == U2F_MEDIA_USB) { + os_memset(service->outputBuffer, 0, USB_SEGMENT_SIZE); + os_memmove(service->outputBuffer + offset, service->channel, 4); + offset += 4; + } + if (service->sendPacketIndex == 0) { + service->outputBuffer[offset++] = service->sendCmd; + service->outputBuffer[offset++] = (service->sendLength >> 8); + service->outputBuffer[offset++] = (service->sendLength & 0xff); + } else { + service->outputBuffer[offset++] = (service->sendPacketIndex - 1); + } + if (service->sendBuffer != NULL) { + os_memmove(service->outputBuffer + headerSize, + service->sendBuffer + service->sendOffset, blockSize); + } + u2f_io_send(service->outputBuffer, dataSize, service->packetMedia); + service->sendOffset += blockSize; + service->sendPacketIndex++; + } while (service->sendOffset != service->sendLength); + if (service->sendOffset == service->sendLength) { + u2f_io_close_session(); + service->sending = false; + if (service->resetAfterSend) { + u2f_reset(service, false); + } + } +} + + +#endif diff --git a/src_genericwallet/u2f_service.h b/src_genericwallet/u2f_service.h new file mode 100644 index 0000000..a78319b --- /dev/null +++ b/src_genericwallet/u2f_service.h @@ -0,0 +1,112 @@ +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* 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 +#include + +#ifndef __U2F_SERVICE_H__ + +#define __U2F_SERVICE_H__ + +struct u2f_service_t; + +typedef void (*u2fHandle_t)(struct u2f_service_t *service, uint8_t *inputBuffer, + uint8_t *channel); +typedef void (*u2fPromptUserPresence_t)(struct u2f_service_t *service, + bool enroll, + uint8_t *applicationParameter); +typedef void (*u2fTimer_t)(struct u2f_service_t *service); + +typedef enum { + U2F_IDLE, + U2F_HANDLE_SEGMENTED, + U2F_PROCESSING_COMMAND, + U2F_SENDING_RESPONSE +} u2f_transport_state_t; + +typedef enum { + U2F_MEDIA_NONE, + U2F_MEDIA_USB, + U2F_MEDIA_NFC, + U2F_MEDIA_BLE +} u2f_transport_media_t; + +typedef struct u2f_service_t { + // Internal + + uint8_t channel[4]; + uint8_t lastContinuationChannel[4]; + uint16_t transportOffset; + u2f_transport_state_t transportState; + u2f_transport_media_t transportMedia; + u2f_transport_media_t packetMedia; + uint8_t expectedContinuationPacket; + uint16_t lastCommandLength; + bool runningCommand; + + u2fHandle_t handleFunction; + + bool userPresence; + bool promptUserPresence; + bool reportUserPresence; + bool keepUserPresence; + u2fPromptUserPresence_t promptUserPresenceFunction; + + u2fTimer_t timeoutFunction; + uint32_t timerInterval; + bool timerNeedGeneralStatus; + uint32_t seqTimeout; + bool pendingContinuation; + bool requireKeepalive; + uint32_t keepaliveTimeout; + + bool sending; + uint8_t sendPacketIndex; + uint8_t *sendBuffer; + uint16_t sendOffset; + uint16_t sendLength; + uint8_t sendCmd; + bool resetAfterSend; + + // External, to be filled + + uint8_t *inputBuffer; + uint8_t *outputBuffer; + uint8_t *messageBuffer; + uint16_t messageBufferSize; + uint8_t *confirmedApplicationParameter; + bool noReentry; + uint16_t bleMtu; +} u2f_service_t; + +void u2f_initialize_service(u2f_service_t *service); +void u2f_send_direct_response_short(u2f_service_t *service, uint8_t *buffer, + uint16_t len); +void u2f_send_fragmented_response(u2f_service_t *service, uint8_t cmd, + uint8_t *buffer, uint16_t len, + bool resetAfterSend); +void u2f_confirm_user_presence(u2f_service_t *service, bool userPresence, + bool resume); +void u2f_continue_sending_fragmented_response(u2f_service_t *service); +void u2f_reset(u2f_service_t *service, bool keepUserPresence); + +// export global +extern volatile u2f_service_t u2fService; + +#endif diff --git a/src_genericwallet/u2f_timer.h b/src_genericwallet/u2f_timer.h new file mode 100644 index 0000000..9a845ab --- /dev/null +++ b/src_genericwallet/u2f_timer.h @@ -0,0 +1,33 @@ +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* 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 +#include + +#ifndef __U2F_TIMER_H__ + +#define __U2F_TIMER_H__ + +typedef void (*u2fTimer_t)(struct u2f_service_t *service); + +void u2f_timer_init(void); +void u2f_timer_register(uint32_t timerMs, u2fTimer_t timerCallback); +void u2f_timer_cancel(void); + +#endif diff --git a/src_genericwallet/u2f_transport.c b/src_genericwallet/u2f_transport.c new file mode 100644 index 0000000..38fe6b4 --- /dev/null +++ b/src_genericwallet/u2f_transport.c @@ -0,0 +1,227 @@ +#ifdef HAVE_U2F + +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* 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 +#include "u2f_service.h" +#include "u2f_transport.h" + +#define U2F_MASK_COMMAND 0x80 +#define U2F_COMMAND_HEADER_SIZE 3 + +static const uint8_t const BROADCAST_CHANNEL[] = {0xff, 0xff, 0xff, 0xff}; +static const uint8_t const FORBIDDEN_CHANNEL[] = {0x00, 0x00, 0x00, 0x00}; + +void u2f_transport_handle(u2f_service_t *service, uint8_t *buffer, + uint16_t size, u2f_transport_media_t media) { + uint16_t channelHeader = (media == U2F_MEDIA_USB ? 4 : 0); + uint8_t channel[4] = {0}; + if (media == U2F_MEDIA_USB) { + os_memmove(channel, buffer, 4); + } + // screen_printf("U2F transport\n"); + service->packetMedia = media; + u2f_io_open_session(); + // If busy, answer immediately + if (service->noReentry) { + if ((service->transportState == U2F_PROCESSING_COMMAND) || + (service->transportState == U2F_SENDING_RESPONSE)) { + u2f_response_error(service, ERROR_CHANNEL_BUSY, false, channel); + goto error; + } + } + if (size < (1 + channelHeader)) { + // Message to short, abort + u2f_response_error(service, ERROR_PROP_MESSAGE_TOO_SHORT, true, + channel); + goto error; + } + if ((buffer[channelHeader] & U2F_MASK_COMMAND) != 0) { + if (size < (channelHeader + 3)) { + // Message to short, abort + u2f_response_error(service, ERROR_PROP_MESSAGE_TOO_SHORT, true, + channel); + goto error; + } + // If waiting for a continuation on a different channel, reply BUSY + // immediately + if (media == U2F_MEDIA_USB) { + if ((service->pendingContinuation) && + (os_memcmp(channel, service->lastContinuationChannel, 4) != + 0) && + (buffer[channelHeader] != U2F_CMD_INIT)) { + u2f_response_error(service, ERROR_CHANNEL_BUSY, false, channel); + goto error; + } + } + // If a command was already sent, and we are not processing a INIT + // command, abort + if ((service->transportState == U2F_HANDLE_SEGMENTED) && + !((media == U2F_MEDIA_USB) && + (buffer[channelHeader] == U2F_CMD_INIT))) { + // Unexpected continuation at this stage, abort + u2f_response_error(service, ERROR_INVALID_SEQ, true, channel); + goto error; + } + // Check the length + uint16_t commandLength = + (buffer[channelHeader + 1] << 8) | (buffer[channelHeader + 2]); + if (commandLength > (service->messageBufferSize - 3)) { + // Overflow in message size, abort + u2f_response_error(service, ERROR_INVALID_LEN, true, channel); + goto error; + } + // Check if the command is supported + switch (buffer[channelHeader]) { + case U2F_CMD_PING: + case U2F_CMD_MSG: + if (media == U2F_MEDIA_USB) { + if (u2f_is_channel_broadcast(channel) || + u2f_is_channel_forbidden(channel)) { + u2f_response_error(service, ERROR_INVALID_CID, true, + channel); + goto error; + } + } + break; + case U2F_CMD_INIT: + if (media != U2F_MEDIA_USB) { + // Unknown command, abort + u2f_response_error(service, ERROR_INVALID_CMD, true, channel); + goto error; + } + break; + default: + // Unknown command, abort + u2f_response_error(service, ERROR_INVALID_CMD, true, channel); + goto error; + } + // Ok, initialize the buffer + os_memmove(service->channel, channel, 4); + service->lastCommandLength = commandLength; + service->expectedContinuationPacket = 0; + os_memmove(service->messageBuffer, buffer + channelHeader, + size - channelHeader); + service->transportOffset = size - channelHeader; + service->transportMedia = media; + } else { + // Continuation + if (size < (channelHeader + 2)) { + // Message to short, abort + u2f_response_error(service, ERROR_PROP_MESSAGE_TOO_SHORT, true, + channel); + goto error; + } + if (media != service->transportMedia) { + // Mixed medias + u2f_response_error(service, ERROR_PROP_MEDIA_MIXED, true, channel); + goto error; + } + if (service->transportState != U2F_HANDLE_SEGMENTED) { + // Unexpected continuation at this stage, abort + // TODO : review the behavior is HID only + if (media == U2F_MEDIA_USB) { + u2f_reset(service, true); + goto error; + } else { + u2f_response_error(service, ERROR_INVALID_SEQ, true, channel); + goto error; + } + } + if (media == U2F_MEDIA_USB) { + // Check the channel + if (os_memcmp(buffer, service->channel, 4) != 0) { + u2f_response_error(service, ERROR_CHANNEL_BUSY, true, channel); + goto error; + } + } + + if (buffer[channelHeader] != service->expectedContinuationPacket) { + // Bad continuation packet, abort + u2f_response_error(service, ERROR_INVALID_SEQ, true, channel); + goto error; + } + if ((service->transportOffset + (size - (channelHeader + 1))) > + (service->messageBufferSize - 3)) { + // Overflow, abort + u2f_response_error(service, ERROR_INVALID_LEN, true, channel); + goto error; + } + os_memmove(service->messageBuffer + service->transportOffset, + buffer + channelHeader + 1, size - (channelHeader + 1)); + service->transportOffset += size - (channelHeader + 1); + service->expectedContinuationPacket++; + } + // See if we can process the command + if ((media != U2F_MEDIA_USB) && + (service->transportOffset > + (service->lastCommandLength + U2F_COMMAND_HEADER_SIZE))) { + // Overflow, abort + u2f_response_error(service, ERROR_INVALID_LEN, true, channel); + goto error; + } else if (service->transportOffset >= + (service->lastCommandLength + U2F_COMMAND_HEADER_SIZE)) { + // screen_printf("Process command\n"); + service->transportState = U2F_PROCESSING_COMMAND; + service->handleFunction(service, service->messageBuffer, channel); + } else { + // screen_printf("segmented\n"); + service->seqTimeout = 0; + service->transportState = U2F_HANDLE_SEGMENTED; + service->pendingContinuation = true; + os_memmove(service->lastContinuationChannel, channel, 4); + u2f_io_close_session(); + } + return; +error: + if ((media == U2F_MEDIA_USB) && (service->pendingContinuation) && + (os_memcmp(channel, service->lastContinuationChannel, 4) == 0)) { + service->pendingContinuation = false; + } + return; +} + +void u2f_response_error(u2f_service_t *service, char errorCode, bool reset, + uint8_t *channel) { + uint8_t offset = 0; + os_memset(service->outputBuffer, 0, MAX_SEGMENT_SIZE); + if (service->transportMedia == U2F_MEDIA_USB) { + os_memmove(service->outputBuffer + offset, channel, 4); + offset += 4; + } + service->outputBuffer[offset++] = U2F_STATUS_ERROR; + service->outputBuffer[offset++] = 0x00; + service->outputBuffer[offset++] = 0x01; + service->outputBuffer[offset++] = errorCode; + u2f_send_direct_response_short(service, service->outputBuffer, offset); + if (reset) { + u2f_reset(service, true); + } +} + +bool u2f_is_channel_broadcast(uint8_t *channel) { + return (os_memcmp(channel, BROADCAST_CHANNEL, 4) == 0); +} + +bool u2f_is_channel_forbidden(uint8_t *channel) { + return (os_memcmp(channel, FORBIDDEN_CHANNEL, 4) == 0); +} + +#endif diff --git a/src_genericwallet/u2f_transport.h b/src_genericwallet/u2f_transport.h new file mode 100644 index 0000000..2d9dc5e --- /dev/null +++ b/src_genericwallet/u2f_transport.h @@ -0,0 +1,78 @@ +/* +******************************************************************************* +* Portable FIDO U2F implementation +* (c) 2016 Ledger +* +* 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. +********************************************************************************/ + +#ifndef __U2F_TRANSPORT_H__ + +#define __U2F_TRANSPORT_H__ + +#include "u2f_service.h" + +#define MAX_SEGMENT_SIZE \ + (USB_SEGMENT_SIZE > BLE_SEGMENT_SIZE ? USB_SEGMENT_SIZE : BLE_SEGMENT_SIZE) + +// Shared commands +#define U2F_CMD_PING 0x81 +#define U2F_CMD_MSG 0x83 + +// USB only commands +#define U2F_CMD_INIT 0x86 +#define U2F_CMD_LOCK 0x84 +#define U2F_CMD_WINK 0x88 + +// BLE only commands +#define U2F_CMD_KEEPALIVE 0x82 +#define KEEPALIVE_REASON_PROCESSING 0x01 +#define KEEPALIVE_REASON_TUP_NEEDED 0x02 + +#define U2F_STATUS_ERROR 0xBF + +// Shared errors +#define ERROR_NONE 0x00 +#define ERROR_INVALID_CMD 0x01 +#define ERROR_INVALID_PAR 0x02 +#define ERROR_INVALID_LEN 0x03 +#define ERROR_INVALID_SEQ 0x04 +#define ERROR_MSG_TIMEOUT 0x05 +#define ERROR_OTHER 0x7f +// USB only errors +#define ERROR_CHANNEL_BUSY 0x06 +#define ERROR_LOCK_REQUIRED 0x0a +#define ERROR_INVALID_CID 0x0b +#define ERROR_PROP_UNKNOWN_COMMAND 0x80 +#define ERROR_PROP_COMMAND_TOO_LONG 0x81 +#define ERROR_PROP_INVALID_CONTINUATION 0x82 +#define ERROR_PROP_UNEXPECTED_CONTINUATION 0x83 +#define ERROR_PROP_CONTINUATION_OVERFLOW 0x84 +#define ERROR_PROP_MESSAGE_TOO_SHORT 0x85 +#define ERROR_PROP_UNCONSISTENT_MSG_LENGTH 0x86 +#define ERROR_PROP_UNSUPPORTED_MSG_APDU 0x87 +#define ERROR_PROP_INVALID_DATA_LENGTH_APDU 0x88 +#define ERROR_PROP_INTERNAL_ERROR_APDU 0x89 +#define ERROR_PROP_INVALID_PARAMETERS_APDU 0x8A +#define ERROR_PROP_INVALID_DATA_APDU 0x8B +#define ERROR_PROP_DEVICE_NOT_SETUP 0x8C +#define ERROR_PROP_MEDIA_MIXED 0x8D + +void u2f_transport_handle(u2f_service_t *service, uint8_t *buffer, + uint16_t size, u2f_transport_media_t media); +void u2f_response_error(u2f_service_t *service, char errorCode, bool reset, + uint8_t *channel); +bool u2f_is_channel_broadcast(uint8_t *channel); +bool u2f_is_channel_forbidden(uint8_t *channel); + +#endif diff --git a/src_genericwallet/usbd_hid_impl.c b/src_genericwallet/usbd_hid_impl.c new file mode 100644 index 0000000..2868812 --- /dev/null +++ b/src_genericwallet/usbd_hid_impl.c @@ -0,0 +1,531 @@ +/** + ****************************************************************************** + * @file usbd_hid.c + * @author MCD Application Team + * @version V2.2.0 + * @date 13-June-2014 + * @brief This file provides the HID core functions. + * + * @verbatim + * + * =================================================================== + * HID Class Description + * =================================================================== + * This module manages the HID class V1.11 following the "Device + *Class Definition + * for Human Interface Devices (HID) Version 1.11 Jun 27, 2001". + * This driver implements the following aspects of the specification: + * - The Boot Interface Subclass + * - Usage Page : Generic Desktop + * - Usage : Vendor + * - Collection : Application + * + * @note In HS mode and when the DMA is used, all variables and data + *structures + * dealing with the DMA during the transaction process should be + *32-bit aligned. + * + * + * @endverbatim + * + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2014 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (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.st.com/software_license_agreement_liberty_v2 + * + * 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 "os.h" + +/* Includes ------------------------------------------------------------------*/ +#include "usbd_hid.h" +#include "usbd_ctlreq.h" + +#include "usbd_core.h" +#include "usbd_conf.h" + +#include "usbd_def.h" +#include "os_io_seproxyhal.h" + +#include "u2f_service.h" +#include "u2f_transport.h" + +/** @togroup STM32_USB_DEVICE_LIBRARY + * @{ + */ + +/** @defgroup USBD_HID + * @brief usbd core module + * @{ + */ + +/** @defgroup USBD_HID_Private_TypesDefinitions + * @{ + */ +/** + * @} + */ + +/** @defgroup USBD_HID_Private_Defines + * @{ + */ + +/** + * @} + */ + +/** @defgroup USBD_HID_Private_Macros + * @{ + */ +/** + * @} + */ +/** @defgroup USBD_HID_Private_FunctionPrototypes + * @{ + */ + + +/** + * @} + */ + +/** @defgroup USBD_HID_Private_Variables + * @{ + */ + +#define HID_EPIN_ADDR 0x82 +#define HID_EPIN_SIZE 0x40 + +#define HID_EPOUT_ADDR 0x02 +#define HID_EPOUT_SIZE 0x40 + +#ifdef HAVE_VID_PID_PROBER +#define USBD_VID 0x2581 +#define USBD_PID 0xf1d1 +#define USBD_LANGID_STRING 0x409 +#else +#define USBD_VID 0x2C97 +#if TARGET_ID == 0x31000002 // blue +#define USBD_PID 0x0000 +const uint8_t const USBD_PRODUCT_FS_STRING[] = { + 4 * 2 + 2, USB_DESC_TYPE_STRING, 'B', 0, 'l', 0, 'u', 0, 'e', 0, +}; + +#elif TARGET_ID == 0x31100002 // nano s +#define USBD_PID 0x0001 +const uint8_t const USBD_PRODUCT_FS_STRING[] = { + 6 * 2 + 2, USB_DESC_TYPE_STRING, + 'N', 0, + 'a', 0, + 'n', 0, + 'o', 0, + ' ', 0, + 'S', 0, +}; +#elif TARGET_ID == 0x31200002 // aramis +#define USBD_PID 0x0002 +const uint8_t const USBD_PRODUCT_FS_STRING[] = { + 6 * 2 + 2, USB_DESC_TYPE_STRING, + 'A', 0, + 'r', 0, + 'a', 0, + 'm', 0, + 'i', 0, + 's', 0, +}; +#else +#error unknown TARGET_ID +#endif +#define USBD_LANGID_STRING 0x409 +#endif + +/* USB Standard Device Descriptor */ +const uint8_t const USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC] = { + USB_LEN_LANGID_STR_DESC, USB_DESC_TYPE_STRING, LOBYTE(USBD_LANGID_STRING), + HIBYTE(USBD_LANGID_STRING), +}; + +const uint8_t const USB_SERIAL_STRING[] = { + 0x2, USB_DESC_TYPE_STRING, +}; + +const uint8_t const USBD_MANUFACTURER_STRING[] = { + 6 * 2 + 2, USB_DESC_TYPE_STRING, + 'L', 0, + 'e', 0, + 'd', 0, + 'g', 0, + 'e', 0, + 'r', 0, +}; + +#define USBD_INTERFACE_FS_STRING USBD_PRODUCT_FS_STRING +#define USBD_CONFIGURATION_FS_STRING USBD_PRODUCT_FS_STRING + +const uint8_t const HID_ReportDesc[] = { + 0x06, 0xD0, 0xF1, // Usage page (vendor defined) + 0x09, 0x01, // Usage ID (vendor defined) + 0xA1, 0x01, // Collection (application) + + // The Input report + 0x09, 0x03, // Usage ID - vendor defined + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8 bits) + 0x95, HID_EPIN_SIZE, // Report Count (64 fields) + 0x81, 0x08, // Input (Data, Variable, Absolute) + + // The Output report + 0x09, 0x04, // Usage ID - vendor defined + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8 bits) + 0x95, HID_EPOUT_SIZE, // Report Count (64 fields) + 0x91, 0x08, // Output (Data, Variable, Absolute) + 0xC0}; + +#define PAGE_FIDO 0xF1D0 +#define PAGE_GENERIC 0xFFA0 + +uint8_t HID_DynReportDesc[sizeof(HID_ReportDesc)]; +bool fidoActivated; + +/* USB HID device Configuration Descriptor */ +__ALIGN_BEGIN const uint8_t const USBD_HID_CfgDesc[] __ALIGN_END = { + 0x09, /* bLength: Configuration Descriptor size */ + USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ + 0x29, + /* wTotalLength: Bytes returned */ + 0x00, 0x01, /*bNumInterfaces: 1 interface*/ + 0x01, /*bConfigurationValue: Configuration value*/ + USBD_IDX_PRODUCT_STR, /*iConfiguration: Index of string descriptor +describing +the configuration*/ + 0xC0, /*bmAttributes: bus powered */ + 0x32, /*MaxPower 100 mA: this current is used for detecting Vbus*/ + + /************** Descriptor of CUSTOM HID interface ****************/ + /* 09 */ + 0x09, /*bLength: Interface Descriptor size*/ + USB_DESC_TYPE_INTERFACE, /*bDescriptorType: Interface descriptor type*/ + 0x00, /*bInterfaceNumber: Number of Interface*/ + 0x00, /*bAlternateSetting: Alternate setting*/ + 0x02, /*bNumEndpoints*/ + 0x03, /*bInterfaceClass: HID*/ + 0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/ + 0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/ + USBD_IDX_PRODUCT_STR, /*iInterface: Index of string descriptor*/ + /******************** Descriptor of HID *************************/ + /* 18 */ + 0x09, /*bLength: HID Descriptor size*/ + HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ + 0x11, /*bHIDUSTOM_HID: HID Class Spec release number*/ + 0x01, + 0x00, /*bCountryCode: Hardware target country*/ + 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ + 0x22, /*bDescriptorType*/ + sizeof( + HID_DynReportDesc), /*wItemLength: Total length of Report descriptor*/ + 0x00, + /******************** Descriptor of Custom HID endpoints + ********************/ + /* 27 */ + 0x07, /*bLength: Endpoint Descriptor size*/ + USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ + HID_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ + 0x03, /*bmAttributes: Interrupt endpoint*/ + HID_EPIN_SIZE, /*wMaxPacketSize: 2 Byte max */ + 0x00, + 0x01, /*bInterval: Polling Interval (20 ms)*/ + /* 34 */ + + 0x07, /* bLength: Endpoint Descriptor size */ + USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: */ + HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/ + 0x03, /* bmAttributes: Interrupt endpoint */ + HID_EPOUT_SIZE, /* wMaxPacketSize: 2 Bytes max */ + 0x00, 0x01, /* bInterval: Polling Interval (20 ms) */ + /* 41 */ +}; + +/* USB HID device Configuration Descriptor */ +__ALIGN_BEGIN const uint8_t const USBD_HID_Desc[] __ALIGN_END = { + /* 18 */ + 0x09, /*bLength: HID Descriptor size*/ + HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ + 0x11, /*bHIDUSTOM_HID: HID Class Spec release number*/ + 0x01, 0x00, /*bCountryCode: Hardware target country*/ + 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ + 0x22, /*bDescriptorType*/ + sizeof( + HID_DynReportDesc), /*wItemLength: Total length of Report descriptor*/ + 0x00, +}; + +/* USB Standard Device Descriptor */ +__ALIGN_BEGIN const uint8_t const USBD_HID_DeviceQualifierDesc[] __ALIGN_END = { + USB_LEN_DEV_QUALIFIER_DESC, + USB_DESC_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x40, + 0x01, + 0x00, +}; + +/* USB Standard Device Descriptor */ +const uint8_t const USBD_DeviceDesc[USB_LEN_DEV_DESC] = { + 0x12, /* bLength */ + USB_DESC_TYPE_DEVICE, /* bDescriptorType */ + 0x00, /* bcdUSB */ + 0x02, 0x00, /* bDeviceClass */ + 0x00, /* bDeviceSubClass */ + 0x00, /* bDeviceProtocol */ + USB_MAX_EP0_SIZE, /* bMaxPacketSize */ + LOBYTE(USBD_VID), /* idVendor */ + HIBYTE(USBD_VID), /* idVendor */ + LOBYTE(USBD_PID), /* idVendor */ + HIBYTE(USBD_PID), /* idVendor */ + 0x00, /* bcdDevice rel. 2.00 */ + 0x02, USBD_IDX_MFC_STR, /* Index of manufacturer string */ + USBD_IDX_PRODUCT_STR, /* Index of product string */ + USBD_IDX_SERIAL_STR, /* Index of serial number string */ + USBD_MAX_NUM_CONFIGURATION /* bNumConfigurations */ +}; /* USB_DeviceDescriptor */ + +/** + * @brief Returns the device descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_DeviceDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_DeviceDesc); + return (uint8_t *)USBD_DeviceDesc; +} + +/** + * @brief Returns the LangID string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_LangIDStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_LangIDDesc); + return (uint8_t *)USBD_LangIDDesc; +} + +/** + * @brief Returns the product string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_ProductStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_PRODUCT_FS_STRING); + return (uint8_t *)USBD_PRODUCT_FS_STRING; +} + +/** + * @brief Returns the manufacturer string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_ManufacturerStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_MANUFACTURER_STRING); + return (uint8_t *)USBD_MANUFACTURER_STRING; +} + +/** + * @brief Returns the serial number string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_SerialStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USB_SERIAL_STRING); + return (uint8_t *)USB_SERIAL_STRING; +} + +/** + * @brief Returns the configuration string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_ConfigStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_CONFIGURATION_FS_STRING); + return (uint8_t *)USBD_CONFIGURATION_FS_STRING; +} + +/** + * @brief Returns the interface string descriptor. + * @param speed: Current device speed + * @param length: Pointer to data length variable + * @retval Pointer to descriptor buffer + */ +uint8_t *USBD_HID_InterfaceStrDescriptor(USBD_SpeedTypeDef speed, + uint16_t *length) { + UNUSED(speed); + *length = sizeof(USBD_INTERFACE_FS_STRING); + return (uint8_t *)USBD_INTERFACE_FS_STRING; +} + +/** +* @brief DeviceQualifierDescriptor +* return Device Qualifier descriptor +* @param length : pointer data length +* @retval pointer to descriptor buffer +*/ +uint8_t *USBD_HID_GetDeviceQualifierDesc_impl(uint16_t *length) { + *length = sizeof(USBD_HID_DeviceQualifierDesc); + return (uint8_t *)USBD_HID_DeviceQualifierDesc; +} + +/** + * @brief USBD_CUSTOM_HID_GetCfgDesc + * return configuration descriptor + * @param speed : current device speed + * @param length : pointer data length + * @retval pointer to descriptor buffer + */ +uint8_t *USBD_HID_GetCfgDesc_impl(uint16_t *length) { + *length = sizeof(USBD_HID_CfgDesc); + return (uint8_t *)USBD_HID_CfgDesc; +} + +uint8_t *USBD_HID_GetHidDescriptor_impl(uint16_t *len) { + *len = sizeof(USBD_HID_Desc); + return (uint8_t *)USBD_HID_Desc; +} + +uint8_t *USBD_HID_GetReportDescriptor_impl(uint16_t *len) { + *len = sizeof(HID_DynReportDesc); + return (uint8_t *)HID_DynReportDesc; +} + +/** + * @} + */ + +/** + * @brief USBD_HID_DataOut + * handle data OUT Stage + * @param pdev: device instance + * @param epnum: endpoint index + * @retval status + * + * This function is the default behavior for our implementation when data are + * sent over the out hid endpoint + */ +extern volatile unsigned short G_io_apdu_length; + +uint8_t USBD_HID_DataOut_impl(USBD_HandleTypeDef *pdev, uint8_t epnum, + uint8_t *buffer) { + UNUSED(epnum); + + // prepare receiving the next chunk (masked time) + USBD_LL_PrepareReceive(pdev, HID_EPOUT_ADDR, HID_EPOUT_SIZE); + + if (fidoActivated) { +#ifdef HAVE_U2F + u2f_transport_handle(&u2fService, buffer, + io_seproxyhal_get_ep_rx_size(HID_EPOUT_ADDR), + U2F_MEDIA_USB); +#endif + } else { + // add to the hid transport + switch ( + io_usb_hid_receive(io_usb_send_apdu_data, buffer, + io_seproxyhal_get_ep_rx_size(HID_EPOUT_ADDR))) { + default: + break; + + case IO_USB_APDU_RECEIVED: + G_io_apdu_length = G_io_usb_hid_total_length; + break; + } + } + + return USBD_OK; +} + +/** @defgroup USBD_HID_Private_Functions + * @{ + */ + +// note: how core lib usb calls the hid class +const USBD_DescriptorsTypeDef const HID_Desc = { + USBD_HID_DeviceDescriptor, USBD_HID_LangIDStrDescriptor, + USBD_HID_ManufacturerStrDescriptor, USBD_HID_ProductStrDescriptor, + USBD_HID_SerialStrDescriptor, USBD_HID_ConfigStrDescriptor, + USBD_HID_InterfaceStrDescriptor, NULL, +}; + +// the USB device +USBD_HandleTypeDef USBD_Device; + +void USB_power_U2F(unsigned char enabled, unsigned char fido) { + uint16_t page = (fido ? PAGE_FIDO : PAGE_GENERIC); + os_memmove(HID_DynReportDesc, HID_ReportDesc, sizeof(HID_ReportDesc)); + HID_DynReportDesc[1] = (page & 0xff); + HID_DynReportDesc[2] = ((page >> 8) & 0xff); + fidoActivated = (fido ? true : false); + + os_memset(&USBD_Device, 0, sizeof(USBD_Device)); + + if (enabled) { + os_memset(&USBD_Device, 0, sizeof(USBD_Device)); + /* Init Device Library */ + USBD_Init(&USBD_Device, (USBD_DescriptorsTypeDef *)&HID_Desc, 0); + + /* Register the HID class */ + USBD_RegisterClass(&USBD_Device, (USBD_ClassTypeDef *)&USBD_HID); + + /* Start Device Process */ + USBD_Start(&USBD_Device); + } else { + USBD_DeInit(&USBD_Device); + } +} + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/