From 964df1f38316a6233a6f06a862c4b2448626bddc Mon Sep 17 00:00:00 2001 From: italo sampaio Date: Mon, 14 Nov 2022 07:53:43 -0300 Subject: [PATCH 1/9] Factored out UI communication and onboard modules - Factored out onboard operations - Factored out communication operations - Removed unused parameters from unlock and pin modules - Reworked pin module --- ledger/src/ui/src/bolos_ux.c | 136 ++------------- ledger/src/ui/src/bolos_ux.h | 30 +--- ledger/src/ui/src/communication.c | 65 +++++++ ledger/src/ui/src/communication.h | 57 +++++++ ledger/src/ui/src/onboard.c | 160 ++++++++++++++++++ ledger/src/ui/src/onboard.h | 81 +++++++++ ledger/src/ui/src/pin.c | 131 +++++++------- ledger/src/ui/src/pin.h | 65 +++---- ledger/src/ui/src/unlock.c | 11 +- ledger/src/ui/src/unlock.h | 4 +- .../bolos_ux_onboarding_seed_bip39.c | 1 + .../bolos_ux_onboarding_seed_bip39.h | 47 +++++ 12 files changed, 532 insertions(+), 256 deletions(-) create mode 100644 ledger/src/ui/src/communication.c create mode 100644 ledger/src/ui/src/communication.h create mode 100644 ledger/src/ui/src/onboard.c create mode 100644 ledger/src/ui/src/onboard.h create mode 100644 ledger/src/ui/src_common/bolos_ux_onboarding_seed_bip39.h diff --git a/ledger/src/ui/src/bolos_ux.c b/ledger/src/ui/src/bolos_ux.c index cce8554f..0c5ffab4 100644 --- a/ledger/src/ui/src/bolos_ux.c +++ b/ledger/src/ui/src/bolos_ux.c @@ -50,16 +50,11 @@ #include "defs.h" #include "err.h" #include "attestation.h" +#include "communication.h" #include "signer_authorization.h" #include "memutil.h" #include "unlock.h" -// Onboarded with the UI flag -const unsigned char N_onboarded_ui[1] = {0}; - -// PIN buffer used for authenticated operations -unsigned char G_pin_buffer[PIN_BUFFER_LENGTH]; - #ifdef OS_IO_SEPROXYHAL #define ARRAYLEN(array) (sizeof(array) / sizeof(array[0])) @@ -240,8 +235,8 @@ void io_seproxyhal_display(const bagl_element_t *element) { // Signer authorization context shorthand #define sigaut_ctx (G_bolos_ux_context.sigaut) -// Pin context shorthand -#define pin_ctx (G_bolos_ux_context.pin) +// Onboard context shorthand +#define onboard_ctx (G_bolos_ux_context.onboard) // Operation being currently executed static unsigned char curr_cmd; @@ -256,14 +251,9 @@ static void reset_if_starting(unsigned char cmd) { // Otherwise we already reset when curr_cmd started. if (cmd != curr_cmd) { curr_cmd = cmd; - explicit_bzero(G_bolos_ux_context.words_buffer, - sizeof(G_bolos_ux_context.words_buffer)); - explicit_bzero(G_bolos_ux_context.string_buffer, - sizeof(G_bolos_ux_context.string_buffer)); - G_bolos_ux_context.words_buffer_length = 0; reset_attestation(&attestation_ctx); reset_signer_authorization(&sigaut_ctx); - reset_pin_ctx(&pin_ctx); + reset_onboard_ctx(&onboard_ctx); } } @@ -271,9 +261,6 @@ static void sample_main(void) { volatile unsigned int rx = 0; volatile unsigned int tx = 0; volatile unsigned int flags = 0; - volatile unsigned char pin = 0; - volatile int i = 0; - volatile unsigned char aux; // Initialize current operation curr_cmd = 0; // 0 = no operation being executed @@ -312,124 +299,39 @@ static void sample_main(void) { switch (APDU_CMD()) { case RSK_SEED_CMD: // Send wordlist reset_if_starting(RSK_META_CMD_UIOP); - pin = APDU_AT(2); - if ((pin >= 0) && ((unsigned int)(pin) <= - sizeof(G_bolos_ux_context.words_buffer))) - G_bolos_ux_context.words_buffer[pin] = APDU_AT(3); + tx = set_host_seed(rx, &onboard_ctx); THROW(APDU_OK); break; case RSK_PIN_CMD: // Send pin_buffer reset_if_starting(RSK_META_CMD_UIOP); - init_pin_ctx(&pin_ctx, G_pin_buffer); - tx = update_pin_buffer(rx, &pin_ctx); + tx = update_pin_buffer(rx); THROW(APDU_OK); break; case RSK_IS_ONBOARD: // Wheter it's onboarded or not reset_if_starting(RSK_IS_ONBOARD); - uint8_t output_index = CMDPOS; - SET_APDU_AT(output_index++, os_perso_isonboarded()); - SET_APDU_AT(output_index++, VERSION_MAJOR); - SET_APDU_AT(output_index++, VERSION_MINOR); - SET_APDU_AT(output_index++, VERSION_PATCH); - tx = 5; + tx = is_onboarded(); THROW(APDU_OK); break; case RSK_WIPE: //--- wipe and onboard device --- reset_if_starting(RSK_META_CMD_UIOP); - - // Reset the onboarding flag to mark onboarding - // hasn't been done just in case something fails - aux = 0; - nvm_write( - (void *)PIC(N_onboarded_ui), (void *)&aux, sizeof(aux)); - - init_pin_ctx(&pin_ctx, G_pin_buffer); -#ifndef DEBUG_BUILD - if (!is_pin_valid(&pin_ctx)) { - THROW(ERR_INVALID_PIN); - } -#endif - // Wipe device - os_global_pin_invalidate(); - os_perso_wipe(); - G_bolos_ux_context.onboarding_kind = - BOLOS_UX_ONBOARDING_NEW_24; - // Generate 32 bytes of random with onboard rng - cx_rng((unsigned char *)G_bolos_ux_context.string_buffer, - HASHSIZE); - // XOR with host-generated 32 bytes random - for (i = 0; i < HASHSIZE; i++) { - G_bolos_ux_context.string_buffer[i] ^= - G_bolos_ux_context.words_buffer[i]; - } - // The seed is now in string_buffer, generate the mnemonic - os_memset(G_bolos_ux_context.words_buffer, - 0, - sizeof(G_bolos_ux_context.words_buffer)); - G_bolos_ux_context.words_buffer_length = - bolos_ux_mnemonic_from_data( - (unsigned char *)G_bolos_ux_context.string_buffer, - SEEDSIZE, - (unsigned char *)G_bolos_ux_context.words_buffer, - sizeof(G_bolos_ux_context.words_buffer)); - // Clear the seed - explicit_bzero(G_bolos_ux_context.string_buffer, - sizeof(G_bolos_ux_context.string_buffer)); - // Set seed from mnemonic - os_perso_derive_and_set_seed( - 0, - NULL, - 0, - NULL, - 0, - G_bolos_ux_context.words_buffer, - strlen(G_bolos_ux_context.words_buffer)); - // Clear the mnemonic - explicit_bzero(G_bolos_ux_context.words_buffer, - sizeof(G_bolos_ux_context.words_buffer)); - // Set PIN - os_perso_set_pin( - 0, GET_PIN(&pin_ctx), GET_PIN_LENGTH(&pin_ctx)); - // Finalize onboarding - os_perso_finalize(); - os_global_pin_invalidate(); - SET_APDU_AT(1, 2); - SET_APDU_AT(2, - os_global_pin_check(GET_PIN(&pin_ctx), - GET_PIN_LENGTH(&pin_ctx))); - // Clear pin buffer - explicit_bzero(G_pin_buffer, sizeof(G_pin_buffer)); - // Turn the onboarding flag on to mark onboarding - // has been done using the UI - aux = 1; - nvm_write( - (void *)PIC(N_onboarded_ui), (void *)&aux, sizeof(aux)); - // Output - tx = 3; + tx = onboard_device(&onboard_ctx); + clear_pin(); THROW(APDU_OK); break; case RSK_NEWPIN: reset_if_starting(RSK_META_CMD_UIOP); - init_pin_ctx(&pin_ctx, G_pin_buffer); -#ifndef DEBUG_BUILD - if (!is_pin_valid(&pin_ctx)) { - THROW(ERR_INVALID_PIN); - } -#endif - tx = set_device_pin(rx, &pin_ctx); - // Clear pin buffer - explicit_bzero(G_pin_buffer, sizeof(G_pin_buffer)); + tx = set_pin(); + clear_pin(); THROW(APDU_OK); break; case RSK_ECHO_CMD: // echo reset_if_starting(RSK_ECHO_CMD); - tx = rx; + tx = echo(rx); THROW(APDU_OK); break; case RSK_MODE_CMD: // print mode reset_if_starting(RSK_MODE_CMD); - SET_APDU_AT(1, RSK_MODE_BOOTLOADER); - tx = 2; + tx = get_mode(); THROW(APDU_OK); break; case INS_ATTESTATION: @@ -444,14 +346,12 @@ static void sample_main(void) { break; case RSK_RETRIES: reset_if_starting(RSK_RETRIES); - SET_APDU_AT(2, (unsigned char)os_global_pin_retries()); - tx = 3; + tx = get_retries(); THROW(APDU_OK); break; case RSK_UNLOCK_CMD: // Unlock reset_if_starting(RSK_META_CMD_UIOP); - init_pin_ctx(&pin_ctx, G_pin_buffer); - tx = unlock(rx, &pin_ctx); + tx = unlock(); // The pin value will also be used in // BOLOS_UX_CONSENT_APP_ADD command, so we can't wipe the // pin buffer here @@ -634,11 +534,9 @@ void bolos_ux_main(void) { // PIN is invalidated so we must check it again. The pin value // used here is the same as in RSK_UNLOCK_CMD, so we also // don't have a prepended length byte - init_pin_ctx(&pin_ctx, G_pin_buffer); - os_global_pin_check(pin_ctx.pin_buffer, - strlen((const char *)pin_ctx.pin_buffer)); + validate_pin(false); G_bolos_ux_context.exit_code = BOLOS_UX_OK; - explicit_bzero(G_pin_buffer, sizeof(G_pin_buffer)); + clear_pin(); break; } else { G_bolos_ux_context.exit_code = BOLOS_UX_CANCEL; diff --git a/ledger/src/ui/src/bolos_ux.h b/ledger/src/ui/src/bolos_ux.h index b81f7159..74cdb464 100644 --- a/ledger/src/ui/src/bolos_ux.h +++ b/ledger/src/ui/src/bolos_ux.h @@ -47,6 +47,7 @@ #include "attestation.h" #include "signer_authorization.h" #include "pin.h" +#include "onboard.h" #ifdef HAVE_BOLOS_UX @@ -127,16 +128,6 @@ typedef struct bolos_ux_context { unsigned int last_ux_id; -#define BOLOS_UX_ONBOARDING_NEW 1 -#define BOLOS_UX_ONBOARDING_NEW_12 12 -#define BOLOS_UX_ONBOARDING_NEW_18 18 -#define BOLOS_UX_ONBOARDING_NEW_24 24 -#define BOLOS_UX_ONBOARDING_RESTORE 2 -#define BOLOS_UX_ONBOARDING_RESTORE_12 12 -#define BOLOS_UX_ONBOARDING_RESTORE_18 18 -#define BOLOS_UX_ONBOARDING_RESTORE_24 24 - unsigned int onboarding_kind; - union { struct { unsigned int onboarding_step; @@ -145,22 +136,12 @@ typedef struct bolos_ux_context { unsigned int onboarding_words_are_valid; unsigned int onboarding_step_checked_inc; unsigned int onboarding_step_checked; - - unsigned int words_buffer_length; - // after an int to make sure it's aligned - char string_buffer[MAX(32, - sizeof(bagl_icon_details_t) + - BOLOS_APP_ICON_SIZE_B - - 1)]; // to store the seed wholy - - char words_buffer[257]; // 128 of words (215 => hashed to 64, or - // 128) + HMAC_LENGTH*2 = 256 }; union { att_t attestation; sigaut_t sigaut; - pin_t pin; + onboard_t onboard; }; }; @@ -221,13 +202,6 @@ unsigned int bolos_ux_get_word_ptr(unsigned char **word, unsigned int max_length, unsigned int word_index); -// passphrase will be prefixed with "MNEMONIC" from BIP39, the passphrase -// content shall start @ 8 -unsigned int bolos_ux_mnemonic_from_data(unsigned char *in, - unsigned int inLength, - unsigned char *out, - unsigned int outLength); - /** * Bolos system app internal UX entry point (could be overriden by a further * loaded BOLOS_UX application) diff --git a/ledger/src/ui/src/communication.c b/ledger/src/ui/src/communication.c new file mode 100644 index 00000000..40a1729b --- /dev/null +++ b/ledger/src/ui/src/communication.c @@ -0,0 +1,65 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "apdu.h" +#include "defs.h" +#include "os.h" +#include "communication.h" + +/* + * Implement the RSK ECHO command. + * + * @arg[in] rx number of received bytes from the Host + * @ret number of transmited bytes to the host + */ +unsigned int echo(unsigned int rx) { + return rx; +} + +/* + * Implement the RSK MODE command. + * + * Since this is only ever called from the bootloader, this always returns + * RSK_MODE_BOOTLOADER + * + * @ret number of transmited bytes to the host + */ +unsigned int get_mode() { + unsigned char output_index = CMDPOS; + SET_APDU_AT(output_index++, RSK_MODE_BOOTLOADER); + return output_index; +} + +/* + * Implement the RSK RETRIES command. + * + * Returns the current number of pin retries for the device + * + * @ret number of transmited bytes to the host + */ +unsigned int get_retries() { + unsigned char output_index = OP; + SET_APDU_AT(output_index++, (unsigned char)os_global_pin_retries()); + return output_index; +} diff --git a/ledger/src/ui/src/communication.h b/ledger/src/ui/src/communication.h new file mode 100644 index 00000000..4279782a --- /dev/null +++ b/ledger/src/ui/src/communication.h @@ -0,0 +1,57 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __COMMUNICATION +#define __COMMUNICATION + +#include + +/* + * Implement the RSK ECHO command. + * + * @arg[in] rx number of received bytes from the Host + * @ret number of transmited bytes to the host + */ +unsigned int echo(unsigned int rx); + +/* + * Implement the RSK MODE command. + * + * Since the UI only runs on BOOTLOADER mode, this always returns + * RSK_MODE_BOOTLOADER + * + * @ret number of transmited bytes to the host + */ +unsigned int get_mode(); + +/* + * Implement the RSK RETRIES command. + * + * Returns the current number of pin retries for the device + * + * @ret number of transmited bytes to the host + */ +unsigned int get_retries(); + +#endif diff --git a/ledger/src/ui/src/onboard.c b/ledger/src/ui/src/onboard.c new file mode 100644 index 00000000..6c7314a6 --- /dev/null +++ b/ledger/src/ui/src/onboard.c @@ -0,0 +1,160 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +#include "apdu.h" +#include "bolos_ux_onboarding_seed_bip39.h" +#include "defs.h" +#include "err.h" +#include "os.h" +#include "onboard.h" + +// Global onboarding flag +const unsigned char *N_onboarded_ui[1]; + +/* + * Reset the given onboard context + * + * @arg[out] onboard_ctx onboard context + */ +void reset_onboard_ctx(onboard_t *onboard_ctx) { + explicit_bzero(onboard_ctx, sizeof(onboard_t)); +} + +/* + * Implement the RSK WIPE command. + * + * Wipes and onboards the device. + * + * @arg[out] onboard_ctx onboard context + * @ret number of transmited bytes to the host + */ +unsigned int onboard_device(onboard_t *onboard_ctx) { + volatile unsigned char onboarded_flag = 0; + + // Reset the onboarding flag to mark onboarding + // hasn't been done just in case something fails + nvm_write((void *)PIC(N_onboarded_ui), + (void *)&onboarded_flag, + sizeof(onboarded_flag)); + +#ifndef DEBUG_BUILD + if (!is_pin_valid()) { + THROW(ERR_INVALID_PIN); + } +#endif + + // Wipe device + os_global_pin_invalidate(); + os_perso_wipe(); + // Generate 32 bytes of random with onboard rng + cx_rng((unsigned char *)onboard_ctx->seed, sizeof(onboard_ctx->seed)); + // XOR with host-generated 32 bytes random + for (unsigned int i = 0; i < sizeof(onboard_ctx->seed); i++) { + onboard_ctx->seed[i] ^= onboard_ctx->host_seed[i]; + } + // The seed is now in onboard_ctx->seed, generate the mnemonic + os_memset(onboard_ctx->words_buffer, 0, sizeof(onboard_ctx->words_buffer)); + onboard_ctx->words_buffer_length = + bolos_ux_mnemonic_from_data((unsigned char *)onboard_ctx->seed, + sizeof(onboard_ctx->seed), + (unsigned char *)onboard_ctx->words_buffer, + sizeof(onboard_ctx->words_buffer)); + // Clear the seed + explicit_bzero(onboard_ctx->seed, sizeof(onboard_ctx->seed)); + + // Set seed from mnemonic + os_perso_derive_and_set_seed(0, + NULL, + 0, + NULL, + 0, + (const char *)onboard_ctx->words_buffer, + onboard_ctx->words_buffer_length); + + // Clear the mnemonic + explicit_bzero(onboard_ctx->words_buffer, + sizeof(onboard_ctx->words_buffer)); + onboard_ctx->words_buffer_length = 0; + + // Set PIN + set_device_pin(); + // Finalize onboarding + os_perso_finalize(); + os_global_pin_invalidate(); + unsigned char output_index = CMDPOS; + SET_APDU_AT(output_index++, 2); + SET_APDU_AT(output_index++, validate_pin(true)); + + // Turn the onboarding flag on to mark onboarding + // has been done using the UI + onboarded_flag = 1; + nvm_write((void *)PIC(N_onboarded_ui), + (void *)&onboarded_flag, + sizeof(onboarded_flag)); + + return output_index; +} + +/* + * Implement the RSK SEED command. + * + * Receives one byte at a time and fills host_seed with the host-generated + * seed. + * + * @arg[in] rx number of received bytes from the Host + * @arg[out] onboard_ctx onboard context + * @ret number of transmited bytes to the host + */ +unsigned int set_host_seed(volatile unsigned int rx, onboard_t *onboard_ctx) { + // Should receive 1 byte per call + if (APDU_DATA_SIZE(rx) != 1) { + THROW(PROT_INVALID); + } + + unsigned char index = APDU_OP(); + if ((index >= 0) && ((size_t)index <= sizeof(onboard_ctx->host_seed))) { + onboard_ctx->host_seed[index] = APDU_AT(3); + } + + // No bytes transmited to host + return 0; +} + +/* + * Implement the RSK IS_ONBOARD command. + * + * Returns onboard status to host + * + * @ret number of transmited bytes to the host + */ +unsigned int is_onboarded() { + uint8_t output_index = CMDPOS; + SET_APDU_AT(output_index++, os_perso_isonboarded()); + SET_APDU_AT(output_index++, VERSION_MAJOR); + SET_APDU_AT(output_index++, VERSION_MINOR); + SET_APDU_AT(output_index++, VERSION_PATCH); + return output_index; +} diff --git a/ledger/src/ui/src/onboard.h b/ledger/src/ui/src/onboard.h new file mode 100644 index 00000000..049e1f24 --- /dev/null +++ b/ledger/src/ui/src/onboard.h @@ -0,0 +1,81 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __ONBOARD +#define __ONBOARD + +#include "os.h" +#include "os_io_seproxyhal.h" +#include "pin.h" + +// 128 of words (215 => hashed to 64, or 128) + HMAC_LENGTH*2 = 256 +#define WORDS_BUFFER_SIZE 257 +// Onboard context +typedef struct { + union { + unsigned char words_buffer[WORDS_BUFFER_SIZE]; + unsigned char host_seed[SEEDSIZE]; + }; + unsigned char seed[SEEDSIZE]; + unsigned int words_buffer_length; +} onboard_t; + +/* + * Reset the given onboard context + * + * @arg[in] onboard_ctx onboard context + */ +void reset_onboard_ctx(onboard_t *onboard_ctx); + +/* + * Implement the RSK WIPE command. + * + * Wipes and onboards the device. + * + * @arg[out] onboard_ctx onboard context + * @ret number of transmited bytes to the host + */ +unsigned int onboard_device(onboard_t *onboard_ctx); + +/* + * Implement the RSK SEED command. + * + * Receives one byte at a time and fills host_seed with the host-generated + * seed. + * + * @arg[in] rx number of received bytes from the Host + * @arg[out] onboard_ctx onboard context + * @ret number of transmited bytes to the host + */ +unsigned int set_host_seed(volatile unsigned int rx, onboard_t *onboard_ctx); + +/* + * Implement the RSK IS_ONBOARD command. + * + * Returns onboard status to host + * + * @ret number of transmited bytes to the host + */ +unsigned int is_onboarded(); +#endif diff --git a/ledger/src/ui/src/pin.c b/ledger/src/ui/src/pin.c index cf8f6d8e..72eecf0c 100644 --- a/ledger/src/ui/src/pin.c +++ b/ledger/src/ui/src/pin.c @@ -35,50 +35,13 @@ #define IS_NUM(c) IS_IN_RANGE(c, '0', '9') #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) -/* - * Validates that the pin has exactly PIN_LENGTH alphanumeric characters - * with at least one alphabetic character. - * - * @arg[in] pin_ctx pin context (with prepended length) - * @ret true if pin is valid, false otherwise - */ -bool is_pin_valid(pin_t* pin_ctx) { - // PIN_LENGTH is the only length accepted - if (GET_PIN_LENGTH(pin_ctx) != PIN_LENGTH) { - return false; - } - // Check if PIN is alphanumeric - bool hasAlpha = false; - for (int i = 0; i < PIN_LENGTH; i++) { - if (!IS_ALPHANUM(GET_PIN(pin_ctx)[i])) { - return false; - } - if (hasAlpha || IS_ALPHA(GET_PIN(pin_ctx)[i])) { - hasAlpha = true; - } - } - - return hasAlpha; -} - -/* - * Reset the given pin context to point to a target buffer - * - * @arg[out] pin_ctx pin context - * @arg[in] pin_buffer pin buffer to which the pin context should point - */ -void init_pin_ctx(pin_t* pin_ctx, unsigned char* pin_buffer) { - pin_ctx->pin_buffer = pin_buffer; -} - -/* - * Reset the given pin context - * - * @arg[in] pin_ctx pin context - */ -void reset_pin_ctx(pin_t* pin_ctx) { - explicit_bzero(pin_ctx, sizeof(pin_t)); -} +#define PIN_LENGTH 8 +#define PIN_BUFFER_LENGTH (PIN_LENGTH + 2) +// Internal PIN buffer used for authenticated operations +unsigned char G_pin_buffer[PIN_BUFFER_LENGTH]; +// Helper macros for pin manipulation when prepended length is used +#define GET_PIN() ((unsigned char *)(G_pin_buffer + 1)) +#define GET_PIN_LENGTH() strlen((const char *)GET_PIN()) /* * Implements RSK PIN command. @@ -87,10 +50,9 @@ void reset_pin_ctx(pin_t* pin_ctx) { * at the end. * * @arg[in] rx number of received bytes from the Host - * @arg[in] pin_ctx pin context * @ret number of transmited bytes to the host */ -unsigned int update_pin_buffer(volatile unsigned int rx, pin_t* pin_ctx) { +unsigned int update_pin_buffer(volatile unsigned int rx) { // Should receive 1 byte per call if (APDU_DATA_SIZE(rx) != 1) { THROW(PROT_INVALID); @@ -98,8 +60,8 @@ unsigned int update_pin_buffer(volatile unsigned int rx, pin_t* pin_ctx) { unsigned char index = APDU_OP(); if ((index >= 0) && (index <= PIN_LENGTH)) { - pin_ctx->pin_buffer[index] = APDU_AT(DATA); - pin_ctx->pin_buffer[index + 1] = 0; + G_pin_buffer[index] = APDU_AT(DATA); + G_pin_buffer[index + 1] = 0; } return 3; @@ -108,28 +70,79 @@ unsigned int update_pin_buffer(volatile unsigned int rx, pin_t* pin_ctx) { /* * Implements RSK NEW PIN command. * - * Sets the device pin. + * Sets and checks the device pin. * - * @arg[in] rx number of received bytes from the Host - * @arg[in] pin_ctx pin context - * @ret number of transmited bytes to the host + * @ret number of transmited bytes to the host */ -unsigned int set_device_pin(volatile unsigned int rx, pin_t* pin_ctx) { - // NEW_PIN command does not use any input from apdu buffer - UNUSED(rx); - +unsigned int set_pin() { #ifndef DEBUG_BUILD - if (!is_pin_valid(pin_ctx)) { + if (!is_pin_valid()) { THROW(ERR_INVALID_PIN); } #endif // Set PIN - os_perso_set_pin(0, GET_PIN(pin_ctx), GET_PIN_LENGTH(pin_ctx)); + os_perso_set_pin(0, GET_PIN(), GET_PIN_LENGTH()); // check PIN os_global_pin_invalidate(); unsigned char output_index = CMDPOS; SET_APDU_AT(output_index++, 2); SET_APDU_AT(output_index++, - os_global_pin_check(GET_PIN(pin_ctx), GET_PIN_LENGTH(pin_ctx))); + os_global_pin_check(GET_PIN(), GET_PIN_LENGTH())); return output_index; +} + +/* + * Validates that the pin curently saved to the internal buffer has exactly + * PIN_LENGTH alphanumeric characters with at least one alphabetic character. + * + * @ret true if pin is valid, false otherwise + */ +bool is_pin_valid() { + // PIN_LENGTH is the only length accepted + if (GET_PIN_LENGTH() != PIN_LENGTH) { + return false; + } + // Check if PIN is alphanumeric + bool hasAlpha = false; + for (int i = 0; i < PIN_LENGTH; i++) { + if (!IS_ALPHANUM(GET_PIN()[i])) { + return false; + } + if (hasAlpha || IS_ALPHA(GET_PIN()[i])) { + hasAlpha = true; + } + } + + return hasAlpha; +} + +/* + * Fills the internal pin buffer with zeroes + */ +void clear_pin() { + explicit_bzero(G_pin_buffer, sizeof(G_pin_buffer)); +} + +/* + * Validates the pin currently saved to the internal pin buffer + * + * @arg[in] prepended_length true if the internal buffer includes a prepended + * length byte, false otherwise + * @ret 1 if pin validated successfully, 0 otherwise + */ +unsigned int validate_pin(bool prepended_length) { + if (prepended_length) { + return os_global_pin_check(GET_PIN(), GET_PIN_LENGTH()); + } else { + return os_global_pin_check(G_pin_buffer, + strlen((const char *)G_pin_buffer)); + } +} + +/* + * Sets the pin currently saved to the internal pin buffer as the device's pin. + * This function assumes the pin is saved with a prepended length byte. + */ +void set_device_pin() { + os_perso_set_pin(0, GET_PIN(), GET_PIN_LENGTH()); } \ No newline at end of file diff --git a/ledger/src/ui/src/pin.h b/ledger/src/ui/src/pin.h index 0eb270bd..2ba801fb 100644 --- a/ledger/src/ui/src/pin.h +++ b/ledger/src/ui/src/pin.h @@ -27,33 +27,6 @@ #include -#define PIN_LENGTH 8 -#define PIN_BUFFER_LENGTH (PIN_LENGTH + 2) - -// Pin context -typedef struct { - unsigned char* pin_buffer; -} pin_t; - -// Helper macros for pin context manipulation -#define GET_PIN(pin_ctx) ((unsigned char*)((pin_ctx)->pin_buffer + 1)) -#define GET_PIN_LENGTH(pin_ctx) strlen((const char*)GET_PIN(pin_ctx)) - -/* - * Reset the given pin context - * - * @arg[in] pin_ctx pin context - */ -void reset_pin_ctx(pin_t* pin_ctx); - -/* - * Reset the given pin context to point to a target buffer - * - * @arg[out] pin_ctx pin context - * @arg[in] pin_buffer pin buffer to which the pin context should point - */ -void init_pin_ctx(pin_t* pin_ctx, unsigned char* pin_buffer); - // ----------------------------------------------------------------------- // RSK protocol implementation // ----------------------------------------------------------------------- @@ -65,33 +38,49 @@ void init_pin_ctx(pin_t* pin_ctx, unsigned char* pin_buffer); * at the end. * * @arg[in] rx number of received bytes from the Host - * @arg[in] pin_ctx pin context * @ret number of transmited bytes to the host */ -unsigned int update_pin_buffer(volatile unsigned int rx, pin_t* pin_ctx); +unsigned int update_pin_buffer(volatile unsigned int rx); /* * Implements RSK NEW PIN command. * - * Sets the device pin. + * Sets and checks the device pin. * - * @arg[in] rx number of received bytes from the Host - * @arg[in] pin_ctx pin context - * @ret number of transmited bytes to the host + * @ret number of transmited bytes to the host */ -unsigned int set_device_pin(volatile unsigned int rx, pin_t* pin_ctx); +unsigned int set_pin(); // ----------------------------------------------------------------------- // Pin manipulation utilities // ----------------------------------------------------------------------- /* - * Validates that the pin has exactly PIN_LENGTH alphanumeric characters - * with at least one alphabetic character. + * Validates that the pin curently saved to the internal buffer has exactly + * PIN_LENGTH alphanumeric characters with at least one alphabetic character. * - * @arg[in] pin_ctx pin context (with prepended length) * @ret true if pin is valid, false otherwise */ -bool is_pin_valid(pin_t* pin_ctx); +bool is_pin_valid(); + +/* + * Fills the internal pin buffer with zeroes + */ +void clear_pin(); + +/* + * Validates the pin currently saved to the internal pin buffer + * + * @arg[in] prepended_length true if the internal buffer includes a prepended + * length byte, false otherwise + * @ret 1 if pin validated successfully, 0 otherwise + */ +unsigned int validate_pin(bool prepended_length); + +/* + * Sets the pin currently saved to the internal pin buffer as the device's pin. + * This function assumes the pin is saved with a prepended length byte. + */ +void set_device_pin(); #endif \ No newline at end of file diff --git a/ledger/src/ui/src/unlock.c b/ledger/src/ui/src/unlock.c index e8e3592f..8195d9d1 100644 --- a/ledger/src/ui/src/unlock.c +++ b/ledger/src/ui/src/unlock.c @@ -32,17 +32,10 @@ * * Unlocks the device. * - * @arg[in] rx number of received bytes from the Host - * @arg[in] pin_ctx pin context * @ret number of transmited bytes to the host */ -unsigned int unlock(volatile unsigned int rx, pin_t *pin_ctx) { - // Unlock command does not use any input from apdu buffer - UNUSED(rx); - +unsigned int unlock() { unsigned char output_index = OP; - SET_APDU_AT(output_index++, - os_global_pin_check(pin_ctx->pin_buffer, - strlen((const char *)pin_ctx->pin_buffer))); + SET_APDU_AT(output_index++, validate_pin(false)); return output_index; } diff --git a/ledger/src/ui/src/unlock.h b/ledger/src/ui/src/unlock.h index e631d1f1..4b0e0f2e 100644 --- a/ledger/src/ui/src/unlock.h +++ b/ledger/src/ui/src/unlock.h @@ -32,10 +32,8 @@ * * Unlocks the device. * - * @arg[in] rx number of received bytes from the Host - * @arg[in] pin_ctx pin context * @ret number of transmited bytes to the host */ -unsigned int unlock(volatile unsigned int rx, pin_t *pin_ctx); +unsigned int unlock(); #endif diff --git a/ledger/src/ui/src_common/bolos_ux_onboarding_seed_bip39.c b/ledger/src/ui/src_common/bolos_ux_onboarding_seed_bip39.c index 2ead8584..3925952b 100644 --- a/ledger/src/ui/src_common/bolos_ux_onboarding_seed_bip39.c +++ b/ledger/src/ui/src_common/bolos_ux_onboarding_seed_bip39.c @@ -43,6 +43,7 @@ #include "cx.h" #include "bolos_ux_common.h" +#include "bolos_ux_onboarding_seed_bip39.h" unsigned int bolos_ux_mnemonic_from_data(unsigned char *in, unsigned int inLength, diff --git a/ledger/src/ui/src_common/bolos_ux_onboarding_seed_bip39.h b/ledger/src/ui/src_common/bolos_ux_onboarding_seed_bip39.h new file mode 100644 index 00000000..7c36ae19 --- /dev/null +++ b/ledger/src/ui/src_common/bolos_ux_onboarding_seed_bip39.h @@ -0,0 +1,47 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/******************************************************************************* + * Ledger Blue - Secure firmware + * (c) 2016, 2017 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. + ********************************************************************************/ + +// passphrase will be prefixed with "MNEMONIC" from BIP39, the passphrase +// content shall start @ 8 +unsigned int bolos_ux_mnemonic_from_data(unsigned char *in, + unsigned int inLength, + unsigned char *out, + unsigned int outLength); \ No newline at end of file From 5d0fa7e2aa252f24a93d28c6c1b24f78515f3d20 Mon Sep 17 00:00:00 2001 From: italo sampaio Date: Mon, 14 Nov 2022 12:03:43 -0300 Subject: [PATCH 2/9] Some additional fixes addressed on code review --- ledger/src/ui/src/bolos_ux.c | 2 +- ledger/src/ui/src/onboard.c | 9 +++++---- ledger/src/ui/src/onboard.h | 2 -- ledger/src/ui/src/pin.c | 4 ++-- ledger/src/ui/src/pin.h | 4 ++-- ledger/src/ui/src/unlock.c | 2 +- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ledger/src/ui/src/bolos_ux.c b/ledger/src/ui/src/bolos_ux.c index 0c5ffab4..f0f9be71 100644 --- a/ledger/src/ui/src/bolos_ux.c +++ b/ledger/src/ui/src/bolos_ux.c @@ -534,7 +534,7 @@ void bolos_ux_main(void) { // PIN is invalidated so we must check it again. The pin value // used here is the same as in RSK_UNLOCK_CMD, so we also // don't have a prepended length byte - validate_pin(false); + unlock_with_pin(false); G_bolos_ux_context.exit_code = BOLOS_UX_OK; clear_pin(); break; diff --git a/ledger/src/ui/src/onboard.c b/ledger/src/ui/src/onboard.c index 6c7314a6..ca0369a5 100644 --- a/ledger/src/ui/src/onboard.c +++ b/ledger/src/ui/src/onboard.c @@ -32,7 +32,7 @@ #include "onboard.h" // Global onboarding flag -const unsigned char *N_onboarded_ui[1]; +const unsigned char N_onboarded_ui[1]; /* * Reset the given onboard context @@ -76,7 +76,8 @@ unsigned int onboard_device(onboard_t *onboard_ctx) { onboard_ctx->seed[i] ^= onboard_ctx->host_seed[i]; } // The seed is now in onboard_ctx->seed, generate the mnemonic - os_memset(onboard_ctx->words_buffer, 0, sizeof(onboard_ctx->words_buffer)); + explicit_bzero(onboard_ctx->words_buffer, + sizeof(onboard_ctx->words_buffer)); onboard_ctx->words_buffer_length = bolos_ux_mnemonic_from_data((unsigned char *)onboard_ctx->seed, sizeof(onboard_ctx->seed), @@ -106,7 +107,7 @@ unsigned int onboard_device(onboard_t *onboard_ctx) { os_global_pin_invalidate(); unsigned char output_index = CMDPOS; SET_APDU_AT(output_index++, 2); - SET_APDU_AT(output_index++, validate_pin(true)); + SET_APDU_AT(output_index++, unlock_with_pin(true)); // Turn the onboarding flag on to mark onboarding // has been done using the UI @@ -135,7 +136,7 @@ unsigned int set_host_seed(volatile unsigned int rx, onboard_t *onboard_ctx) { } unsigned char index = APDU_OP(); - if ((index >= 0) && ((size_t)index <= sizeof(onboard_ctx->host_seed))) { + if ((index >= 0) && ((size_t)index < sizeof(onboard_ctx->host_seed))) { onboard_ctx->host_seed[index] = APDU_AT(3); } diff --git a/ledger/src/ui/src/onboard.h b/ledger/src/ui/src/onboard.h index 049e1f24..e9e8255f 100644 --- a/ledger/src/ui/src/onboard.h +++ b/ledger/src/ui/src/onboard.h @@ -25,8 +25,6 @@ #ifndef __ONBOARD #define __ONBOARD -#include "os.h" -#include "os_io_seproxyhal.h" #include "pin.h" // 128 of words (215 => hashed to 64, or 128) + HMAC_LENGTH*2 = 256 diff --git a/ledger/src/ui/src/pin.c b/ledger/src/ui/src/pin.c index 72eecf0c..a19898b8 100644 --- a/ledger/src/ui/src/pin.c +++ b/ledger/src/ui/src/pin.c @@ -124,13 +124,13 @@ void clear_pin() { } /* - * Validates the pin currently saved to the internal pin buffer + * Uses the pin currently saved to the internal pin buffer to unlock the device * * @arg[in] prepended_length true if the internal buffer includes a prepended * length byte, false otherwise * @ret 1 if pin validated successfully, 0 otherwise */ -unsigned int validate_pin(bool prepended_length) { +unsigned int unlock_with_pin(bool prepended_length) { if (prepended_length) { return os_global_pin_check(GET_PIN(), GET_PIN_LENGTH()); } else { diff --git a/ledger/src/ui/src/pin.h b/ledger/src/ui/src/pin.h index 2ba801fb..5589954f 100644 --- a/ledger/src/ui/src/pin.h +++ b/ledger/src/ui/src/pin.h @@ -69,13 +69,13 @@ bool is_pin_valid(); void clear_pin(); /* - * Validates the pin currently saved to the internal pin buffer + * Uses the pin currently saved to the internal pin buffer to unlock the device * * @arg[in] prepended_length true if the internal buffer includes a prepended * length byte, false otherwise * @ret 1 if pin validated successfully, 0 otherwise */ -unsigned int validate_pin(bool prepended_length); +unsigned int unlock_with_pin(bool prepended_length); /* * Sets the pin currently saved to the internal pin buffer as the device's pin. diff --git a/ledger/src/ui/src/unlock.c b/ledger/src/ui/src/unlock.c index 8195d9d1..b041b786 100644 --- a/ledger/src/ui/src/unlock.c +++ b/ledger/src/ui/src/unlock.c @@ -36,6 +36,6 @@ */ unsigned int unlock() { unsigned char output_index = OP; - SET_APDU_AT(output_index++, validate_pin(false)); + SET_APDU_AT(output_index++, unlock_with_pin(false)); return output_index; } From 7c00d07f0231ee3742c2b09c6f9ea231a81834a8 Mon Sep 17 00:00:00 2001 From: italo sampaio Date: Thu, 17 Nov 2022 10:25:34 -0300 Subject: [PATCH 3/9] Added unit tests for communication and onboard modules --- ledger/src/ui/test/communication/Makefile | 50 +++++ .../test/communication/test_communication.c | 59 ++++++ .../mock/bolos_ux_onboarding_seed_bip39.h | 1 + ledger/src/ui/test/mock/cx.c | 31 +++ ledger/src/ui/test/mock/cx.h | 28 +++ ledger/src/ui/test/mock/os.c | 94 ++++++++- ledger/src/ui/test/mock/os.h | 38 ++++ ledger/src/ui/test/onboard/Makefile | 57 ++++++ ledger/src/ui/test/onboard/test_onboard.c | 180 ++++++++++++++++++ 9 files changed, 530 insertions(+), 8 deletions(-) create mode 100644 ledger/src/ui/test/communication/Makefile create mode 100644 ledger/src/ui/test/communication/test_communication.c create mode 120000 ledger/src/ui/test/mock/bolos_ux_onboarding_seed_bip39.h create mode 100644 ledger/src/ui/test/mock/cx.c create mode 100644 ledger/src/ui/test/mock/cx.h create mode 100644 ledger/src/ui/test/onboard/Makefile create mode 100644 ledger/src/ui/test/onboard/test_onboard.c diff --git a/ledger/src/ui/test/communication/Makefile b/ledger/src/ui/test/communication/Makefile new file mode 100644 index 00000000..d9b3227b --- /dev/null +++ b/ledger/src/ui/test/communication/Makefile @@ -0,0 +1,50 @@ +# The MIT License (MIT) +# +# Copyright (c) 2021 RSK Labs Ltd +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is furnished to do +# so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +SRCDIR = ../../src +MOCKDIR = ../mock +CFLAGS = -I $(SRCDIR) -I $(MOCKDIR) -I ./ + +PROG = test.out +OBJS = os.o communication.o test_communication.o + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) -o $@ $^ + +test_communication.o: test_communication.c + $(CC) $(CFLAGS) -c -o $@ $^ + +communication.o: $(SRCDIR)/communication.c + $(CC) $(CFLAGS) -c -o $@ $^ + +os.o: $(MOCKDIR)/os.c + $(CC) $(CFLAGS) -c -o $@ $^ + +.PHONY: clean test + +clean: + rm -f $(PROG) ./*.o + +test: all + ./$(PROG) diff --git a/ledger/src/ui/test/communication/test_communication.c b/ledger/src/ui/test/communication/test_communication.c new file mode 100644 index 00000000..5aea87c0 --- /dev/null +++ b/ledger/src/ui/test/communication/test_communication.c @@ -0,0 +1,59 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include + +#include "defs.h" +#include "communication.h" +#include "os.h" + +void test_echo() { + printf("Test echo...\n"); + unsigned int rx = 4; + assert(4 == echo(rx)); +} + +void test_get_mode() { + printf("Test get mode...\n"); + assert(2 == get_mode()); + assert(RSK_MODE_BOOTLOADER == APDU_AT(1)); +} + +void test_get_retries() { + printf("Test get retries...\n"); + reset_mock_func_call_list(); + assert(3 == get_retries()); + assert(0 == APDU_AT(2)); + assert(get_mock_func_call(0) == MOCK_FUNC_OS_GLOBAL_PIN_RETRIES); + assert(get_mock_func_call_count() == 1); +} + +int main() { + test_echo(); + test_get_mode(); + test_get_retries(); + + return 0; +} diff --git a/ledger/src/ui/test/mock/bolos_ux_onboarding_seed_bip39.h b/ledger/src/ui/test/mock/bolos_ux_onboarding_seed_bip39.h new file mode 120000 index 00000000..3cba64e4 --- /dev/null +++ b/ledger/src/ui/test/mock/bolos_ux_onboarding_seed_bip39.h @@ -0,0 +1 @@ +../../src_common/bolos_ux_onboarding_seed_bip39.h \ No newline at end of file diff --git a/ledger/src/ui/test/mock/cx.c b/ledger/src/ui/test/mock/cx.c new file mode 100644 index 00000000..885a985d --- /dev/null +++ b/ledger/src/ui/test/mock/cx.c @@ -0,0 +1,31 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "cx.h" + +unsigned char mock_random_buffer[] = "random-buffer\0"; + +unsigned char *cx_rng(unsigned char *buffer, unsigned int len) { + return mock_random_buffer; +} \ No newline at end of file diff --git a/ledger/src/ui/test/mock/cx.h b/ledger/src/ui/test/mock/cx.h new file mode 100644 index 00000000..8a6b6a38 --- /dev/null +++ b/ledger/src/ui/test/mock/cx.h @@ -0,0 +1,28 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/** + * generate a random buffer + */ +unsigned char *cx_rng(unsigned char *buffer, unsigned int len); \ No newline at end of file diff --git a/ledger/src/ui/test/mock/os.c b/ledger/src/ui/test/mock/os.c index 1590a831..9c6c5c04 100644 --- a/ledger/src/ui/test/mock/os.c +++ b/ledger/src/ui/test/mock/os.c @@ -25,38 +25,116 @@ #include "os.h" #include "string.h" +static mock_func_call_t mock_func_call_list[128]; +static size_t mock_func_call_count = 0; + /** * Mocks pin currently loaded to device */ unsigned char current_pin[10]; /** - * APDU buffer + * Helper functions to handle call list */ -unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; +void reset_mock_func_call_list() { + explicit_bzero(mock_func_call_list, sizeof(mock_func_call_list)); + mock_func_call_count = 0; +} -unsigned int os_global_pin_check(unsigned char *pin_buffer, - unsigned char pin_length) { - return !strncmp( - (const char *)pin_buffer, (const char *)current_pin, pin_length); +void add_mock_func_call(mock_func_call_t func) { + mock_func_call_list[mock_func_call_count++] = func; +} + +mock_func_call_t get_mock_func_call(int order) { + return mock_func_call_list[order]; } +int get_mock_func_call_count() { + return mock_func_call_count; +} + +/** + * APDU buffer + */ +unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; + void explicit_bzero(void *s, size_t len) { memset(s, '\0', len); /* Compiler barrier. */ asm volatile("" ::: "memory"); } +unsigned int os_global_pin_check(unsigned char *pin_buffer, + unsigned char pin_length) { + add_mock_func_call(MOCK_FUNC_OS_GLOBAL_PIN_CHECK); + return !strncmp( + (const char *)pin_buffer, (const char *)current_pin, pin_length); +} + void os_perso_set_pin(unsigned int identity, unsigned char *pin, unsigned int length) { - // Do nothing + add_mock_func_call(MOCK_FUNC_OS_PERSO_SET_PIN); + strncpy((char *)current_pin, (char *)pin, length); } void os_global_pin_invalidate(void) { - // Do nothing + add_mock_func_call(MOCK_FUNC_OS_GLOBAL_PIN_INVALIDATE); +} + +void os_memset(void *dst, unsigned char c, unsigned int length) { + memset(dst, c, length); } void mock_set_pin(unsigned char *pin, size_t n) { memcpy(current_pin, pin, n); +} + +void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len) { + add_mock_func_call(MOCK_FUNC_NVM_WRITE); + if (src_adr == NULL) { + // Treat as memory reset + memset(dst_adr, 0, src_len); + } else { + // Treat as normal copy + memmove(dst_adr, src_adr, src_len); + } +} + +void os_perso_wipe() { + add_mock_func_call(MOCK_FUNC_OS_PERSO_WIPE); +} + +void os_perso_derive_and_set_seed(unsigned char identity, + const char *prefix, + unsigned int prefix_length, + const char *passphrase, + unsigned int passphrase_length, + const char *words, + unsigned int words_length) { + add_mock_func_call(MOCK_FUNC_OS_PERSO_DERIVE_AND_SET_SEED); +} + +void os_perso_finalize(void) { + add_mock_func_call(MOCK_FUNC_OS_PERSO_FINALIZE); +} + +unsigned int os_perso_isonboarded(void) { + add_mock_func_call(MOCK_FUNC_OS_PERSO_ISONBOARDED); + return 1; +} + +unsigned int os_global_pin_retries(void) { + add_mock_func_call(MOCK_FUNC_OS_GLOBAL_PIN_RETRIES); + return 0; +} + +unsigned int bolos_ux_mnemonic_from_data(unsigned char *in, + unsigned int inLength, + unsigned char *out, + unsigned int outLength) { + add_mock_func_call(MOCK_FUNC_BOLOS_UX_MNEMONIC_FROM_DATA); + const char mnemonic[] = "the-mnemonics"; + strcpy((char *)out, mnemonic); + return strlen(mnemonic); } \ No newline at end of file diff --git a/ledger/src/ui/test/mock/os.h b/ledger/src/ui/test/mock/os.h index deb74c04..a319844e 100644 --- a/ledger/src/ui/test/mock/os.h +++ b/ledger/src/ui/test/mock/os.h @@ -23,12 +23,29 @@ */ #include +#include +#include "cx.h" /** * Utility macros */ #define UNUSED(x) (void)x #define THROW(e) return e +#define PIC(x) (x) + +typedef enum { + MOCK_FUNC_OS_GLOBAL_PIN_CHECK, + MOCK_FUNC_OS_PERSO_SET_PIN, + MOCK_FUNC_OS_GLOBAL_PIN_INVALIDATE, + MOCK_FUNC_OS_MEMSET, + MOCK_FUNC_NVM_WRITE, + MOCK_FUNC_OS_PERSO_WIPE, + MOCK_FUNC_OS_PERSO_DERIVE_AND_SET_SEED, + MOCK_FUNC_OS_PERSO_FINALIZE, + MOCK_FUNC_OS_PERSO_ISONBOARDED, + MOCK_FUNC_BOLOS_UX_MNEMONIC_FROM_DATA, + MOCK_FUNC_OS_GLOBAL_PIN_RETRIES, +} mock_func_call_t; /** * Mock APDU buffer @@ -36,6 +53,14 @@ #define IO_APDU_BUFFER_SIZE 85 extern unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; +/** + * Helper functions to handle call list + */ +void reset_mock_func_call_list(); +void add_mock_func_call(mock_func_call_t func); +mock_func_call_t get_mock_func_call(int order); +int get_mock_func_call_count(); + /** * Mock calls for os API */ @@ -45,6 +70,19 @@ void os_perso_set_pin(unsigned int identity, unsigned char *pin, unsigned int length); void os_global_pin_invalidate(void); +void os_memset(void *dst, unsigned char c, unsigned int length); +void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len); +void os_perso_wipe(); +void os_perso_derive_and_set_seed(unsigned char identity, + const char *prefix, + unsigned int prefix_length, + const char *passphrase, + unsigned int passphrase_length, + const char *words, + unsigned int words_length); +void os_perso_finalize(void); +unsigned int os_perso_isonboarded(void); +unsigned int os_global_pin_retries(void); /** * Other mocks diff --git a/ledger/src/ui/test/onboard/Makefile b/ledger/src/ui/test/onboard/Makefile new file mode 100644 index 00000000..69cc6fa9 --- /dev/null +++ b/ledger/src/ui/test/onboard/Makefile @@ -0,0 +1,57 @@ +# The MIT License (MIT) +# +# Copyright (c) 2021 RSK Labs Ltd +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is furnished to do +# so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +SRCDIR = ../../src +COMMONDIR = ../../src_common +MOCKDIR = ../mock +CFLAGS = -I $(SRCDIR) -I $(MOCKDIR) -I ./ + +PROG = test.out +OBJS = os.o cx.o pin.o onboard.o test_onboard.o + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) -o $@ $^ + +test_onboard.o: test_onboard.c + $(CC) $(CFLAGS) -c -o $@ $^ + +onboard.o: $(SRCDIR)/onboard.c + $(CC) $(CFLAGS) -c -o $@ $^ + +pin.o: $(SRCDIR)/pin.c + $(CC) $(CFLAGS) -c -o $@ $^ + +os.o: $(MOCKDIR)/os.c + $(CC) $(CFLAGS) -c -o $@ $^ + +cx.o: $(MOCKDIR)/cx.c + $(CC) $(CFLAGS) -c -o $@ $^ + +.PHONY: clean test + +clean: + rm -f $(PROG) ./*.o + +test: all + ./$(PROG) diff --git a/ledger/src/ui/test/onboard/test_onboard.c b/ledger/src/ui/test/onboard/test_onboard.c new file mode 100644 index 00000000..df65e01c --- /dev/null +++ b/ledger/src/ui/test/onboard/test_onboard.c @@ -0,0 +1,180 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2021 RSK Labs Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "cx.h" +#include "defs.h" +#include "onboard.h" +#include "os.h" +#include "pin.h" + +char words_buffer[] = "words_buffer"; +char seed[] = "seed_buffer"; + +void test_reset_onboard_ctx() { + printf("Test reset onboard context...\n"); + onboard_t onboard_ctx; + memcpy(onboard_ctx.words_buffer, words_buffer, sizeof(words_buffer)); + memcpy(onboard_ctx.seed, seed, sizeof(seed)); + + reset_onboard_ctx(&onboard_ctx); + + char expected_words_buffer[sizeof(words_buffer)]; + char expected_seed[sizeof(seed)]; + memset(expected_words_buffer, 0, sizeof(expected_words_buffer)); + memset(expected_seed, 0, sizeof(expected_seed)); + assert(memcmp(expected_words_buffer, + onboard_ctx.words_buffer, + sizeof(expected_words_buffer)) == 0); + assert(memcmp(expected_seed, onboard_ctx.seed, sizeof(expected_seed)) == 0); + assert(onboard_ctx.words_buffer_length == 0); +} + +void test_set_host_seed() { + printf("Test set host seed...\n"); + onboard_t onboard_ctx; + reset_onboard_ctx(&onboard_ctx); + // mock 32 bytes random host seed + const char host_seed[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + unsigned int rx = 4; + for (int i = 0; i < strlen(host_seed); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, host_seed[i]); + assert(0 == set_host_seed(rx, &onboard_ctx)); + } + assert(0 == strncmp((char*)onboard_ctx.host_seed, host_seed, SEEDSIZE)); +} + +void test_onboard_device() { + printf("Test onboard device...\n"); + onboard_t onboard_ctx; + reset_onboard_ctx(&onboard_ctx); + // mock 32 bytes random host seed + const char host_seed[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + // device pin (with prepended length) + const unsigned char valid_pin[] = "X1234567a"; + unsigned int rx; + + // Mock RSK_PIN_CMD + rx = 4; + for (int i = 0; i < sizeof(valid_pin); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, valid_pin[i]); + assert(3 == update_pin_buffer(rx)); + } + + // Mock RSK_SEED_CMD + rx = 4; + for (int i = 0; i < strlen(host_seed); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, host_seed[i]); + assert(0 == set_host_seed(rx, &onboard_ctx)); + } + + reset_mock_func_call_list(); + assert(3 == onboard_device(&onboard_ctx)); + assert(2 == APDU_AT(1)); + assert(1 == APDU_AT(2)); + assert(get_mock_func_call(0) == MOCK_FUNC_NVM_WRITE); + assert(get_mock_func_call(1) == MOCK_FUNC_OS_GLOBAL_PIN_INVALIDATE); + assert(get_mock_func_call(2) == MOCK_FUNC_OS_PERSO_WIPE); + assert(get_mock_func_call(3) == MOCK_FUNC_BOLOS_UX_MNEMONIC_FROM_DATA); + assert(get_mock_func_call(4) == MOCK_FUNC_OS_PERSO_DERIVE_AND_SET_SEED); + assert(get_mock_func_call(5) == MOCK_FUNC_OS_PERSO_SET_PIN); + assert(get_mock_func_call(6) == MOCK_FUNC_OS_PERSO_FINALIZE); + assert(get_mock_func_call(7) == MOCK_FUNC_OS_GLOBAL_PIN_INVALIDATE); + assert(get_mock_func_call(8) == MOCK_FUNC_OS_GLOBAL_PIN_CHECK); + assert(get_mock_func_call(9) == MOCK_FUNC_NVM_WRITE); + assert(get_mock_func_call_count() == 10); + + // Make sure all mnemonic and seed information is wiped after onboard_device + char expected_words_buffer[sizeof(words_buffer)]; + char expected_seed[sizeof(seed)]; + memset(expected_words_buffer, 0, sizeof(expected_words_buffer)); + memset(expected_seed, 0, sizeof(expected_seed)); + assert(memcmp(expected_words_buffer, + onboard_ctx.words_buffer, + sizeof(expected_words_buffer)) == 0); + assert(memcmp(expected_seed, onboard_ctx.seed, sizeof(expected_seed)) == 0); + assert(onboard_ctx.words_buffer_length == 0); +} + +void test_onboard_device_invalid_pin() { + printf("Test onboard device (invalid pin)...\n"); + onboard_t onboard_ctx; + reset_onboard_ctx(&onboard_ctx); + // mock 32 bytes random host seed + const char host_seed[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + // wrong device pin (without prepended length) + const unsigned char invalid_pin[] = "1234567a"; + unsigned int rx; + + // Mock RSK_PIN_CMD + rx = 4; + for (int i = 0; i < sizeof(invalid_pin); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, invalid_pin[i]); + assert(3 == update_pin_buffer(rx)); + } + + // Mock RSK_SEED_CMD + rx = 4; + for (int i = 0; i < strlen(host_seed); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, host_seed[i]); + assert(0 == set_host_seed(rx, &onboard_ctx)); + } + + reset_mock_func_call_list(); + // ERR_INVALID_PIN + assert(0x69A0 == onboard_device(&onboard_ctx)); + assert(get_mock_func_call(0) == MOCK_FUNC_NVM_WRITE); + assert(get_mock_func_call_count() == 1); +} + +void test_is_onboarded() { + printf("Test is onboarded...\n"); + reset_mock_func_call_list(); + assert(5 == is_onboarded()); + assert(1 == APDU_AT(1)); + assert(VERSION_MAJOR == APDU_AT(2)); + assert(VERSION_MINOR == APDU_AT(3)); + assert(VERSION_PATCH == APDU_AT(4)); + assert(get_mock_func_call(0) == MOCK_FUNC_OS_PERSO_ISONBOARDED); + assert(get_mock_func_call_count() == 1); +} + +int main() { + test_reset_onboard_ctx(); + test_set_host_seed(); + test_onboard_device(); + test_onboard_device_invalid_pin(); + test_is_onboarded(); + + return 0; +} From bedd820f33d77232495d1f972b48d8f5ded8a9cb Mon Sep 17 00:00:00 2001 From: italo sampaio Date: Thu, 17 Nov 2022 10:55:13 -0300 Subject: [PATCH 4/9] Added UI communication and onboard tests to the CI --- .github/workflows/run-tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 788b44f9..0a89245f 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -27,6 +27,13 @@ jobs: (cd "$d" && make clean test) done + - name: Ledger UI's tests + working-directory: ledger/src/ui/test/ + run: | + for d in communication onboard; do + (cd "$d" && make clean test) + done + - name: Ledger common lib tests working-directory: ledger/src/common/test/ run: | From ea3abd33a1ea818506a68835cc19e696a44baeb1 Mon Sep 17 00:00:00 2001 From: italo sampaio Date: Fri, 18 Nov 2022 09:22:49 -0300 Subject: [PATCH 5/9] Fixed pin and unlock unit tests for UI --- .github/workflows/run-tests.yml | 2 +- ledger/src/ui/test/pin/test_pin.c | 166 +++++++++++++++++++----- ledger/src/ui/test/unlock/test_unlock.c | 41 +++--- 3 files changed, 154 insertions(+), 55 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 0a89245f..a6b6d2e1 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -30,7 +30,7 @@ jobs: - name: Ledger UI's tests working-directory: ledger/src/ui/test/ run: | - for d in communication onboard; do + for d in communication onboard pin unlock; do (cd "$d" && make clean test) done diff --git a/ledger/src/ui/test/pin/test_pin.c b/ledger/src/ui/test/pin/test_pin.c index d4933b0d..ed0621cb 100644 --- a/ledger/src/ui/test/pin/test_pin.c +++ b/ledger/src/ui/test/pin/test_pin.c @@ -27,29 +27,80 @@ #include #include +#include "apdu.h" +#include "os.h" #include "pin.h" #define IS_VALID true #define IS_NOT_VALID false -void set_payload(pin_t *pin_ctx, unsigned char *payload, size_t n) { - memcpy(GET_PIN(pin_ctx), payload, n); -} - void assert_pin(char *pin, bool expected) { unsigned char pin_length = (char)strlen(pin); - unsigned char *pin_buffer = malloc(pin_length + 2); + unsigned char pin_buffer[16]; + memset(pin_buffer, 0, sizeof(pin_buffer)); pin_buffer[0] = pin_length; strcpy((char *)pin_buffer + 1, pin); - pin_t pin_ctx; - init_pin_ctx(&pin_ctx, pin_buffer); - assert(is_pin_valid(&pin_ctx) == expected); - free(pin_buffer); + // Mock RSK_PIN_CMD + unsigned int rx = 4; + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + + assert(is_pin_valid() == expected); +} + +void test_update_pin_buffer() { + printf("Test update pin buffer...\n"); + + unsigned char pin_buffer[] = "X1234567a"; + unsigned int rx = 4; + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } +} + +void test_set_pin() { + printf("Test set pin ok...\n"); + + unsigned char pin_buffer[] = "X1234567a"; + unsigned int rx = 4; + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + + reset_mock_func_call_list(); + assert(3 == set_pin()); + assert(get_mock_func_call(0) == MOCK_FUNC_OS_PERSO_SET_PIN); + assert(get_mock_func_call(1) == MOCK_FUNC_OS_GLOBAL_PIN_INVALIDATE); + assert(get_mock_func_call(2) == MOCK_FUNC_OS_GLOBAL_PIN_CHECK); + assert(get_mock_func_call_count() == 3); } -void test_ok() { - printf("Test OK...\n"); +void test_set_pin_invalid() { + printf("Test set pin invalid...\n"); + + unsigned char pin_buffer[] = "X12345678"; + unsigned int rx = 4; + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + + reset_mock_func_call_list(); + assert(0x69A0 == set_pin()); // ERR_INVALID_PIN + assert(get_mock_func_call_count() == 0); +} + +void test_validate_ok() { + printf("Test validate pin OK...\n"); assert_pin("abcdefgh", IS_VALID); assert_pin("8b23ef1s", IS_VALID); @@ -57,8 +108,8 @@ void test_ok() { assert_pin("MN22p3s9", IS_VALID); } -void test_numeric_pin() { - printf("Test pin with only numbers...\n"); +void test_validate_numeric_pin() { + printf("Test validate pin with only numbers...\n"); assert_pin("1234", IS_NOT_VALID); assert_pin("123456", IS_NOT_VALID); @@ -66,17 +117,18 @@ void test_numeric_pin() { assert_pin("1234567890", IS_NOT_VALID); } -void test_pin_too_long() { - printf("Test pin buffer too long...\n"); +void test_validate_pin_too_long() { + printf("Test validate pin buffer too long...\n"); - assert_pin("abcdefghi", IS_NOT_VALID); - assert_pin("8b23ef1s85", IS_NOT_VALID); - assert_pin("MN22P3S9P20", IS_NOT_VALID); - assert_pin("MNOPQRSTQDAS", IS_NOT_VALID); + // Long pins are accepted, but internally capped to PIN_LENGTH + assert_pin("abcdefghi", IS_VALID); + assert_pin("8b23ef1s85", IS_VALID); + assert_pin("MN22P3S9P20", IS_VALID); + assert_pin("MNOPQRSTQDAS", IS_VALID); } -void test_pin_too_short() { - printf("Test pin buffer too short...\n"); +void test_validate_pin_too_short() { + printf("Test validate pin buffer too short...\n"); assert_pin("abcdefg", IS_NOT_VALID); assert_pin("8b23ef", IS_NOT_VALID); @@ -84,8 +136,8 @@ void test_pin_too_short() { assert_pin("MNOP", IS_NOT_VALID); } -void test_pin_non_alpha() { - printf("Test pin non alpha chars...\n"); +void test_validate_pin_non_alpha() { + printf("Test validate pin non alpha chars...\n"); assert_pin("a1-@.;", IS_NOT_VALID); assert_pin("!@#$^&*", IS_NOT_VALID); @@ -93,13 +145,69 @@ void test_pin_non_alpha() { assert_pin("abcdefg", IS_NOT_VALID); } -int main() { - test_ok(); +void test_unlock_with_pin() { + printf("Test unlock with pin...\n"); + + unsigned char pin_buffer[] = "1234567a"; + unsigned int rx = 4; + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + + reset_mock_func_call_list(); + assert(1 == unlock_with_pin(false)); + assert(get_mock_func_call(0) == MOCK_FUNC_OS_GLOBAL_PIN_CHECK); + assert(get_mock_func_call_count() == 1); +} - test_numeric_pin(); - test_pin_too_long(); - test_pin_too_short(); - test_pin_non_alpha(); +void test_unlock_with_pin_prepended_length() { + printf("Test unlock with pin (prepended length)...\n"); + + unsigned char pin_buffer[] = "X1234567a"; + unsigned int rx = 4; + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + + reset_mock_func_call_list(); + assert(1 == unlock_with_pin(true)); + assert(get_mock_func_call(0) == MOCK_FUNC_OS_GLOBAL_PIN_CHECK); + assert(get_mock_func_call_count() == 1); +} + +void test_set_device_pin() { + printf("Test set device pin...\n"); + + unsigned char pin_buffer[] = "X1234567a"; + unsigned int rx = 4; + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + + reset_mock_func_call_list(); + set_device_pin(); + assert(get_mock_func_call(0) == MOCK_FUNC_OS_PERSO_SET_PIN); + assert(get_mock_func_call_count() == 1); +} + +int main() { + test_update_pin_buffer(); + test_set_pin(); + test_set_pin_invalid(); + test_validate_ok(); + test_validate_numeric_pin(); + test_validate_pin_too_long(); + test_validate_pin_too_short(); + test_validate_pin_non_alpha(); + test_unlock_with_pin(); + test_unlock_with_pin_prepended_length(); + test_set_device_pin(); return 0; } \ No newline at end of file diff --git a/ledger/src/ui/test/unlock/test_unlock.c b/ledger/src/ui/test/unlock/test_unlock.c index 22cb14eb..cef6782e 100644 --- a/ledger/src/ui/test/unlock/test_unlock.c +++ b/ledger/src/ui/test/unlock/test_unlock.c @@ -31,34 +31,25 @@ #include "os.h" #include "unlock.h" -void test_ok() { - printf("Test OK...\n"); - - pin_t pin_ctx; - init_pin_ctx(&pin_ctx, (unsigned char *)"1234567a\0\0"); - - unsigned int tx = unlock(&pin_ctx); - assert(tx == 3); - assert(APDU_OP() == 1); -} - -void test_wrong_pin() { - printf("Test wrong pin...\n"); - - pin_t pin_ctx; - init_pin_ctx(&pin_ctx, (unsigned char *)"wrong-pin\0"); - - unsigned int tx = unlock(&pin_ctx); - assert(tx == 3); - assert(APDU_OP() == 0); +void test_unlock() { + printf("Test unlock...\n"); + + unsigned char pin_buffer[] = "1234567a"; + unsigned int rx = 4; + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + + reset_mock_func_call_list(); + assert(3 == unlock()); + assert(get_mock_func_call(0) == MOCK_FUNC_OS_GLOBAL_PIN_CHECK); + assert(get_mock_func_call_count() == 1); } int main() { - // Set device pin - mock_set_pin((unsigned char *)"1234567a", strlen("1234567a")); - - test_ok(); - test_wrong_pin(); + test_unlock(); return 0; } From c59175124834a41e8b44790528bcb20ae9021360 Mon Sep 17 00:00:00 2001 From: italo sampaio Date: Tue, 22 Nov 2022 11:17:57 -0300 Subject: [PATCH 6/9] Rework on UI unit test to change from function call list to a more behaviour based approach - Updated communication tests - Updated onboard tests - Updated pin tests - Updated unlock tests --- ledger/src/ui/test/communication/Makefile | 5 +- .../test/communication/test_communication.c | 43 ++++- ledger/src/ui/test/mock/cx.c | 14 +- ledger/src/ui/test/mock/cx.h | 4 +- ledger/src/ui/test/mock/os.c | 95 +++++------ ledger/src/ui/test/mock/os.h | 32 ++-- ledger/src/ui/test/onboard/test_onboard.c | 102 +++++++++--- ledger/src/ui/test/pin/test_pin.c | 155 ++++++++++-------- ledger/src/ui/test/unlock/test_unlock.c | 36 +++- 9 files changed, 314 insertions(+), 172 deletions(-) diff --git a/ledger/src/ui/test/communication/Makefile b/ledger/src/ui/test/communication/Makefile index d9b3227b..099696ff 100644 --- a/ledger/src/ui/test/communication/Makefile +++ b/ledger/src/ui/test/communication/Makefile @@ -25,7 +25,7 @@ MOCKDIR = ../mock CFLAGS = -I $(SRCDIR) -I $(MOCKDIR) -I ./ PROG = test.out -OBJS = os.o communication.o test_communication.o +OBJS = os.o pin.o communication.o test_communication.o all: $(PROG) @@ -41,6 +41,9 @@ communication.o: $(SRCDIR)/communication.c os.o: $(MOCKDIR)/os.c $(CC) $(CFLAGS) -c -o $@ $^ +pin.o: $(SRCDIR)/pin.c + $(CC) $(CFLAGS) -c -o $@ $^ + .PHONY: clean test clean: diff --git a/ledger/src/ui/test/communication/test_communication.c b/ledger/src/ui/test/communication/test_communication.c index 5aea87c0..1124233b 100644 --- a/ledger/src/ui/test/communication/test_communication.c +++ b/ledger/src/ui/test/communication/test_communication.c @@ -23,11 +23,13 @@ */ #include +#include #include #include "defs.h" #include "communication.h" #include "os.h" +#include "pin.h" void test_echo() { printf("Test echo...\n"); @@ -43,11 +45,46 @@ void test_get_mode() { void test_get_retries() { printf("Test get retries...\n"); - reset_mock_func_call_list(); + + unsigned char pin_buffer[] = "X1234567a"; + unsigned char wrong_pin[] = "Xa7654321"; + unsigned int rx = 4; + init_mock_ctx(); + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + + assert(3 == set_pin()); + assert(3 == get_retries()); + assert(0 == APDU_AT(2)); + + // Send wrong pin + for (int i = 0; i < strlen((const char *)wrong_pin); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, wrong_pin[i]); + assert(3 == update_pin_buffer(rx)); + } + assert(!unlock_with_pin(true)); + assert(3 == get_retries()); + assert(1 == APDU_AT(2)); + assert(!unlock_with_pin(true)); + assert(3 == get_retries()); + assert(2 == APDU_AT(2)); + assert(!unlock_with_pin(true)); + assert(3 == get_retries()); + assert(3 == APDU_AT(2)); + + // Send right pin again + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + assert(unlock_with_pin(true)); assert(3 == get_retries()); assert(0 == APDU_AT(2)); - assert(get_mock_func_call(0) == MOCK_FUNC_OS_GLOBAL_PIN_RETRIES); - assert(get_mock_func_call_count() == 1); } int main() { diff --git a/ledger/src/ui/test/mock/cx.c b/ledger/src/ui/test/mock/cx.c index 885a985d..bb6b1a9a 100644 --- a/ledger/src/ui/test/mock/cx.c +++ b/ledger/src/ui/test/mock/cx.c @@ -24,8 +24,18 @@ #include "cx.h" -unsigned char mock_random_buffer[] = "random-buffer\0"; +static unsigned char mock_seed[32]; + +void set_mock_seed(const unsigned char *data, unsigned int len) { + for (unsigned int i = 0; i < len; i++) { + mock_seed[i] = data[i]; + } +} unsigned char *cx_rng(unsigned char *buffer, unsigned int len) { - return mock_random_buffer; + // Mock 32 random bytes + for (int i = 0; i < len; i++) { + buffer[i] = mock_seed[i]; + } + return 0; } \ No newline at end of file diff --git a/ledger/src/ui/test/mock/cx.h b/ledger/src/ui/test/mock/cx.h index 8a6b6a38..1e3ccf60 100644 --- a/ledger/src/ui/test/mock/cx.h +++ b/ledger/src/ui/test/mock/cx.h @@ -25,4 +25,6 @@ /** * generate a random buffer */ -unsigned char *cx_rng(unsigned char *buffer, unsigned int len); \ No newline at end of file +unsigned char *cx_rng(unsigned char *buffer, unsigned int len); + +void set_mock_seed(const unsigned char *data, unsigned int len); \ No newline at end of file diff --git a/ledger/src/ui/test/mock/os.c b/ledger/src/ui/test/mock/os.c index 9c6c5c04..a2c2c88a 100644 --- a/ledger/src/ui/test/mock/os.c +++ b/ledger/src/ui/test/mock/os.c @@ -21,36 +21,24 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ - +#include +#include +#include "defs.h" #include "os.h" #include "string.h" - -static mock_func_call_t mock_func_call_list[128]; -static size_t mock_func_call_count = 0; - -/** - * Mocks pin currently loaded to device - */ -unsigned char current_pin[10]; +#include "onboard.h" /** - * Helper functions to handle call list + * Mock context used to assert current state */ -void reset_mock_func_call_list() { - explicit_bzero(mock_func_call_list, sizeof(mock_func_call_list)); - mock_func_call_count = 0; -} - -void add_mock_func_call(mock_func_call_t func) { - mock_func_call_list[mock_func_call_count++] = func; -} +static mock_ctx_t mock_ctx; -mock_func_call_t get_mock_func_call(int order) { - return mock_func_call_list[order]; +void init_mock_ctx() { + memset(&mock_ctx, 0, sizeof(mock_ctx)); } -int get_mock_func_call_count() { - return mock_func_call_count; +void get_mock_ctx(mock_ctx_t *ctx) { + memcpy(ctx, &mock_ctx, sizeof(mock_ctx)); } /** @@ -66,32 +54,33 @@ void explicit_bzero(void *s, size_t len) { unsigned int os_global_pin_check(unsigned char *pin_buffer, unsigned char pin_length) { - add_mock_func_call(MOCK_FUNC_OS_GLOBAL_PIN_CHECK); - return !strncmp( - (const char *)pin_buffer, (const char *)current_pin, pin_length); + mock_ctx.device_unlocked = !strncmp((const char *)pin_buffer, + (const char *)mock_ctx.global_pin, + pin_length); + if (mock_ctx.device_unlocked) { + mock_ctx.retries = 0; + } else { + mock_ctx.retries++; + } + + return mock_ctx.device_unlocked; } void os_perso_set_pin(unsigned int identity, unsigned char *pin, unsigned int length) { - add_mock_func_call(MOCK_FUNC_OS_PERSO_SET_PIN); - strncpy((char *)current_pin, (char *)pin, length); + strncpy((char *)mock_ctx.global_pin, (char *)pin, length); } void os_global_pin_invalidate(void) { - add_mock_func_call(MOCK_FUNC_OS_GLOBAL_PIN_INVALIDATE); + mock_ctx.device_unlocked = false; } void os_memset(void *dst, unsigned char c, unsigned int length) { memset(dst, c, length); } -void mock_set_pin(unsigned char *pin, size_t n) { - memcpy(current_pin, pin, n); -} - void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len) { - add_mock_func_call(MOCK_FUNC_NVM_WRITE); if (src_adr == NULL) { // Treat as memory reset memset(dst_adr, 0, src_len); @@ -102,39 +91,37 @@ void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len) { } void os_perso_wipe() { - add_mock_func_call(MOCK_FUNC_OS_PERSO_WIPE); -} - -void os_perso_derive_and_set_seed(unsigned char identity, - const char *prefix, - unsigned int prefix_length, - const char *passphrase, - unsigned int passphrase_length, - const char *words, - unsigned int words_length) { - add_mock_func_call(MOCK_FUNC_OS_PERSO_DERIVE_AND_SET_SEED); + // wipe global pin, seed and state + init_mock_ctx(); } void os_perso_finalize(void) { - add_mock_func_call(MOCK_FUNC_OS_PERSO_FINALIZE); + mock_ctx.device_onboarded = true; } unsigned int os_perso_isonboarded(void) { - add_mock_func_call(MOCK_FUNC_OS_PERSO_ISONBOARDED); - return 1; + return mock_ctx.device_onboarded; } unsigned int os_global_pin_retries(void) { - add_mock_func_call(MOCK_FUNC_OS_GLOBAL_PIN_RETRIES); - return 0; + return mock_ctx.retries; } +// Generated mnemonics buffer will be "mnemonics-generated-from:" unsigned int bolos_ux_mnemonic_from_data(unsigned char *in, unsigned int inLength, unsigned char *out, unsigned int outLength) { - add_mock_func_call(MOCK_FUNC_BOLOS_UX_MNEMONIC_FROM_DATA); - const char mnemonic[] = "the-mnemonics"; - strcpy((char *)out, mnemonic); - return strlen(mnemonic); -} \ No newline at end of file + sprintf((char *)out, "mnemonics-generated-from-%s", in); + return strlen((const char *)out); +} + +void os_perso_derive_and_set_seed(unsigned char identity, + const char *prefix, + unsigned int prefix_length, + const char *passphrase, + unsigned int passphrase_length, + const char *words, + unsigned int words_length) { + sprintf((char *)mock_ctx.global_seed, "seed-generated-from-%s", words); +} diff --git a/ledger/src/ui/test/mock/os.h b/ledger/src/ui/test/mock/os.h index a319844e..2e7ad43d 100644 --- a/ledger/src/ui/test/mock/os.h +++ b/ledger/src/ui/test/mock/os.h @@ -22,6 +22,7 @@ * IN THE SOFTWARE. */ +#include #include #include #include "cx.h" @@ -33,20 +34,6 @@ #define THROW(e) return e #define PIC(x) (x) -typedef enum { - MOCK_FUNC_OS_GLOBAL_PIN_CHECK, - MOCK_FUNC_OS_PERSO_SET_PIN, - MOCK_FUNC_OS_GLOBAL_PIN_INVALIDATE, - MOCK_FUNC_OS_MEMSET, - MOCK_FUNC_NVM_WRITE, - MOCK_FUNC_OS_PERSO_WIPE, - MOCK_FUNC_OS_PERSO_DERIVE_AND_SET_SEED, - MOCK_FUNC_OS_PERSO_FINALIZE, - MOCK_FUNC_OS_PERSO_ISONBOARDED, - MOCK_FUNC_BOLOS_UX_MNEMONIC_FROM_DATA, - MOCK_FUNC_OS_GLOBAL_PIN_RETRIES, -} mock_func_call_t; - /** * Mock APDU buffer */ @@ -54,12 +41,18 @@ typedef enum { extern unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; /** - * Helper functions to handle call list + * Mock context used to assert current state */ -void reset_mock_func_call_list(); -void add_mock_func_call(mock_func_call_t func); -mock_func_call_t get_mock_func_call(int order); -int get_mock_func_call_count(); +typedef struct { + unsigned char global_pin[10]; + unsigned char global_seed[257]; + bool device_unlocked; + bool device_onboarded; + unsigned int retries; +} mock_ctx_t; + +void init_mock_ctx(); +void get_mock_ctx(mock_ctx_t *ctx); /** * Mock calls for os API @@ -88,4 +81,3 @@ unsigned int os_global_pin_retries(void); * Other mocks */ void explicit_bzero(void *s, size_t len); -void mock_set_pin(unsigned char *pin, size_t n); diff --git a/ledger/src/ui/test/onboard/test_onboard.c b/ledger/src/ui/test/onboard/test_onboard.c index df65e01c..82db192b 100644 --- a/ledger/src/ui/test/onboard/test_onboard.c +++ b/ledger/src/ui/test/onboard/test_onboard.c @@ -67,7 +67,7 @@ void test_set_host_seed() { SET_APDU_AT(3, host_seed[i]); assert(0 == set_host_seed(rx, &onboard_ctx)); } - assert(0 == strncmp((char*)onboard_ctx.host_seed, host_seed, SEEDSIZE)); + assert(0 == strncmp((char *)onboard_ctx.host_seed, host_seed, SEEDSIZE)); } void test_onboard_device() { @@ -75,7 +75,9 @@ void test_onboard_device() { onboard_t onboard_ctx; reset_onboard_ctx(&onboard_ctx); // mock 32 bytes random host seed - const char host_seed[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const unsigned char host_seed[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + // mock 32 bytes handom seed + const unsigned char seed[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; // device pin (with prepended length) const unsigned char valid_pin[] = "X1234567a"; unsigned int rx; @@ -90,27 +92,39 @@ void test_onboard_device() { // Mock RSK_SEED_CMD rx = 4; - for (int i = 0; i < strlen(host_seed); i++) { + for (int i = 0; i < strlen((const char *)host_seed); i++) { SET_APDU_AT(2, i); SET_APDU_AT(3, host_seed[i]); assert(0 == set_host_seed(rx, &onboard_ctx)); } - reset_mock_func_call_list(); + char generated_seed[SEEDSIZE]; + for (int i = 0; i < SEEDSIZE; i++) { + generated_seed[i] = host_seed[i] ^ seed[i]; + } + char expected_global_seed[257]; + memset(expected_global_seed, 0, sizeof(expected_global_seed)); + char *seed_str = stpcpy(expected_global_seed, + "seed-generated-from-mnemonics-generated-from-"); + for (int i = 0; i < SEEDSIZE; i++) { + seed_str[i] = generated_seed[i]; + } + + init_mock_ctx(); + set_mock_seed(seed, SEEDSIZE); assert(3 == onboard_device(&onboard_ctx)); assert(2 == APDU_AT(1)); assert(1 == APDU_AT(2)); - assert(get_mock_func_call(0) == MOCK_FUNC_NVM_WRITE); - assert(get_mock_func_call(1) == MOCK_FUNC_OS_GLOBAL_PIN_INVALIDATE); - assert(get_mock_func_call(2) == MOCK_FUNC_OS_PERSO_WIPE); - assert(get_mock_func_call(3) == MOCK_FUNC_BOLOS_UX_MNEMONIC_FROM_DATA); - assert(get_mock_func_call(4) == MOCK_FUNC_OS_PERSO_DERIVE_AND_SET_SEED); - assert(get_mock_func_call(5) == MOCK_FUNC_OS_PERSO_SET_PIN); - assert(get_mock_func_call(6) == MOCK_FUNC_OS_PERSO_FINALIZE); - assert(get_mock_func_call(7) == MOCK_FUNC_OS_GLOBAL_PIN_INVALIDATE); - assert(get_mock_func_call(8) == MOCK_FUNC_OS_GLOBAL_PIN_CHECK); - assert(get_mock_func_call(9) == MOCK_FUNC_NVM_WRITE); - assert(get_mock_func_call_count() == 10); + + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + assert(mock_ctx.device_unlocked == true); + assert(mock_ctx.device_onboarded == true); + assert(mock_ctx.retries == 0); + assert(!strcmp((const char *)(valid_pin + 1), + (const char *)mock_ctx.global_pin)); + assert(!strcmp((const char *)expected_global_seed, + (const char *)mock_ctx.global_seed)); // Make sure all mnemonic and seed information is wiped after onboard_device char expected_words_buffer[sizeof(words_buffer)]; @@ -150,23 +164,69 @@ void test_onboard_device_invalid_pin() { assert(0 == set_host_seed(rx, &onboard_ctx)); } - reset_mock_func_call_list(); + init_mock_ctx(); // ERR_INVALID_PIN assert(0x69A0 == onboard_device(&onboard_ctx)); - assert(get_mock_func_call(0) == MOCK_FUNC_NVM_WRITE); - assert(get_mock_func_call_count() == 1); + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + + // assert internal state was not affected + unsigned char expected_global_pin[sizeof(mock_ctx.global_pin)]; + memset(expected_global_pin, 0, sizeof(expected_global_pin)); + unsigned char expected_global_seed[sizeof(mock_ctx.global_seed)]; + memset(expected_global_seed, 0, sizeof(expected_global_seed)); + + assert(false == mock_ctx.device_onboarded); + assert(false == mock_ctx.device_unlocked); + assert(0 == mock_ctx.retries); + assert(0 == memcmp(expected_global_pin, + mock_ctx.global_pin, + sizeof(expected_global_pin))); + assert(0 == memcmp(expected_global_seed, + mock_ctx.global_seed, + sizeof(expected_global_seed))); } void test_is_onboarded() { printf("Test is onboarded...\n"); - reset_mock_func_call_list(); + + onboard_t onboard_ctx; + reset_onboard_ctx(&onboard_ctx); + // mock 32 bytes random host seed + const unsigned char host_seed[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + // device pin (with prepended length) + const unsigned char valid_pin[] = "X1234567a"; + unsigned int rx; + + // Mock RSK_PIN_CMD + rx = 4; + for (int i = 0; i < sizeof(valid_pin); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, valid_pin[i]); + assert(3 == update_pin_buffer(rx)); + } + + // Mock RSK_SEED_CMD + rx = 4; + for (int i = 0; i < strlen((const char *)host_seed); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, host_seed[i]); + assert(0 == set_host_seed(rx, &onboard_ctx)); + } + + assert(5 == is_onboarded()); + assert(0 == APDU_AT(1)); + assert(VERSION_MAJOR == APDU_AT(2)); + assert(VERSION_MINOR == APDU_AT(3)); + assert(VERSION_PATCH == APDU_AT(4)); + + onboard_device(&onboard_ctx); + assert(5 == is_onboarded()); assert(1 == APDU_AT(1)); assert(VERSION_MAJOR == APDU_AT(2)); assert(VERSION_MINOR == APDU_AT(3)); assert(VERSION_PATCH == APDU_AT(4)); - assert(get_mock_func_call(0) == MOCK_FUNC_OS_PERSO_ISONBOARDED); - assert(get_mock_func_call_count() == 1); } int main() { diff --git a/ledger/src/ui/test/pin/test_pin.c b/ledger/src/ui/test/pin/test_pin.c index ed0621cb..edbf677b 100644 --- a/ledger/src/ui/test/pin/test_pin.c +++ b/ledger/src/ui/test/pin/test_pin.c @@ -52,53 +52,6 @@ void assert_pin(char *pin, bool expected) { assert(is_pin_valid() == expected); } -void test_update_pin_buffer() { - printf("Test update pin buffer...\n"); - - unsigned char pin_buffer[] = "X1234567a"; - unsigned int rx = 4; - for (int i = 0; i < strlen((const char *)pin_buffer); i++) { - SET_APDU_AT(2, i); - SET_APDU_AT(3, pin_buffer[i]); - assert(3 == update_pin_buffer(rx)); - } -} - -void test_set_pin() { - printf("Test set pin ok...\n"); - - unsigned char pin_buffer[] = "X1234567a"; - unsigned int rx = 4; - for (int i = 0; i < strlen((const char *)pin_buffer); i++) { - SET_APDU_AT(2, i); - SET_APDU_AT(3, pin_buffer[i]); - assert(3 == update_pin_buffer(rx)); - } - - reset_mock_func_call_list(); - assert(3 == set_pin()); - assert(get_mock_func_call(0) == MOCK_FUNC_OS_PERSO_SET_PIN); - assert(get_mock_func_call(1) == MOCK_FUNC_OS_GLOBAL_PIN_INVALIDATE); - assert(get_mock_func_call(2) == MOCK_FUNC_OS_GLOBAL_PIN_CHECK); - assert(get_mock_func_call_count() == 3); -} - -void test_set_pin_invalid() { - printf("Test set pin invalid...\n"); - - unsigned char pin_buffer[] = "X12345678"; - unsigned int rx = 4; - for (int i = 0; i < strlen((const char *)pin_buffer); i++) { - SET_APDU_AT(2, i); - SET_APDU_AT(3, pin_buffer[i]); - assert(3 == update_pin_buffer(rx)); - } - - reset_mock_func_call_list(); - assert(0x69A0 == set_pin()); // ERR_INVALID_PIN - assert(get_mock_func_call_count() == 0); -} - void test_validate_ok() { printf("Test validate pin OK...\n"); @@ -145,38 +98,102 @@ void test_validate_pin_non_alpha() { assert_pin("abcdefg", IS_NOT_VALID); } +void test_update_pin_buffer() { + printf("Test update pin buffer...\n"); + + unsigned char pin_buffer[] = "X1234567a"; + unsigned int rx = 4; + init_mock_ctx(); + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + const char *expected_global_pin[sizeof(mock_ctx.global_pin)]; + memset(expected_global_pin, 0, sizeof(expected_global_pin)); + assert(!strcmp((const char *)expected_global_pin, + (const char *)mock_ctx.global_pin)); +} + +void test_set_pin() { + printf("Test set pin ok...\n"); + + unsigned char pin_buffer[] = "X1234567a"; + unsigned int rx = 4; + init_mock_ctx(); + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + + assert(3 == set_pin()); + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + assert(true == mock_ctx.device_unlocked); +} + +void test_set_pin_invalid() { + printf("Test set pin invalid...\n"); + + unsigned char pin_buffer[] = "X12345678"; + unsigned int rx = 4; + init_mock_ctx(); + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + + assert(0x69A0 == set_pin()); // ERR_INVALID_PIN + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + assert(false == mock_ctx.device_unlocked); +} + void test_unlock_with_pin() { printf("Test unlock with pin...\n"); - unsigned char pin_buffer[] = "1234567a"; + unsigned char pin_buffer[] = "X1234567a"; unsigned int rx = 4; + init_mock_ctx(); for (int i = 0; i < strlen((const char *)pin_buffer); i++) { SET_APDU_AT(2, i); SET_APDU_AT(3, pin_buffer[i]); assert(3 == update_pin_buffer(rx)); } - - reset_mock_func_call_list(); - assert(1 == unlock_with_pin(false)); - assert(get_mock_func_call(0) == MOCK_FUNC_OS_GLOBAL_PIN_CHECK); - assert(get_mock_func_call_count() == 1); + assert(3 == set_pin()); + assert(1 == unlock_with_pin(true)); + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + // Skip prepended length + assert(!strcmp((const char *)(pin_buffer + 1), + (const char *)mock_ctx.global_pin)); + assert(true == mock_ctx.device_unlocked); } -void test_unlock_with_pin_prepended_length() { - printf("Test unlock with pin (prepended length)...\n"); +void test_unlock_with_pin_not_set() { + printf("Test unlock with pin (pin not set)...\n"); unsigned char pin_buffer[] = "X1234567a"; unsigned int rx = 4; + init_mock_ctx(); for (int i = 0; i < strlen((const char *)pin_buffer); i++) { SET_APDU_AT(2, i); SET_APDU_AT(3, pin_buffer[i]); assert(3 == update_pin_buffer(rx)); } - reset_mock_func_call_list(); - assert(1 == unlock_with_pin(true)); - assert(get_mock_func_call(0) == MOCK_FUNC_OS_GLOBAL_PIN_CHECK); - assert(get_mock_func_call_count() == 1); + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + assert(0 == unlock_with_pin(true)); + assert(false == mock_ctx.device_unlocked); + const char *expected_global_pin[sizeof(mock_ctx.global_pin)]; + memset(expected_global_pin, 0, sizeof(expected_global_pin)); + assert(!strcmp((const char *)expected_global_pin, + (const char *)mock_ctx.global_pin)); } void test_set_device_pin() { @@ -184,29 +201,33 @@ void test_set_device_pin() { unsigned char pin_buffer[] = "X1234567a"; unsigned int rx = 4; + init_mock_ctx(); for (int i = 0; i < strlen((const char *)pin_buffer); i++) { SET_APDU_AT(2, i); SET_APDU_AT(3, pin_buffer[i]); assert(3 == update_pin_buffer(rx)); } - reset_mock_func_call_list(); set_device_pin(); - assert(get_mock_func_call(0) == MOCK_FUNC_OS_PERSO_SET_PIN); - assert(get_mock_func_call_count() == 1); + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + // Skip prepended length + assert(!strcmp((const char *)(pin_buffer + 1), + (const char *)mock_ctx.global_pin)); + assert(false == mock_ctx.device_unlocked); } int main() { - test_update_pin_buffer(); - test_set_pin(); - test_set_pin_invalid(); - test_validate_ok(); test_validate_numeric_pin(); test_validate_pin_too_long(); test_validate_pin_too_short(); test_validate_pin_non_alpha(); + test_update_pin_buffer(); + test_set_pin(); + test_set_pin_invalid(); + test_validate_ok(); test_unlock_with_pin(); - test_unlock_with_pin_prepended_length(); + test_unlock_with_pin_not_set(); test_set_device_pin(); return 0; diff --git a/ledger/src/ui/test/unlock/test_unlock.c b/ledger/src/ui/test/unlock/test_unlock.c index cef6782e..6e34b558 100644 --- a/ledger/src/ui/test/unlock/test_unlock.c +++ b/ledger/src/ui/test/unlock/test_unlock.c @@ -36,20 +36,50 @@ void test_unlock() { unsigned char pin_buffer[] = "1234567a"; unsigned int rx = 4; + init_mock_ctx(); for (int i = 0; i < strlen((const char *)pin_buffer); i++) { SET_APDU_AT(2, i); SET_APDU_AT(3, pin_buffer[i]); assert(3 == update_pin_buffer(rx)); } - reset_mock_func_call_list(); + // assert(3 == set_pin()); + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + assert(false == mock_ctx.device_unlocked); + os_perso_set_pin(0, pin_buffer, strlen((const char *)pin_buffer)); assert(3 == unlock()); - assert(get_mock_func_call(0) == MOCK_FUNC_OS_GLOBAL_PIN_CHECK); - assert(get_mock_func_call_count() == 1); + get_mock_ctx(&mock_ctx); + assert(true == mock_ctx.device_unlocked); + assert(1 == APDU_AT(2)); +} + +void test_unlock_wrong_pin() { + printf("Test unlock (wrong pin)...\n"); + + unsigned char pin_buffer[] = "1234567a"; + unsigned char wrong_pin[] = "a7654321"; + unsigned int rx = 4; + init_mock_ctx(); + for (int i = 0; i < strlen((const char *)wrong_pin); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, wrong_pin[i]); + assert(3 == update_pin_buffer(rx)); + } + + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + assert(false == mock_ctx.device_unlocked); + os_perso_set_pin(0, pin_buffer, strlen((const char *)pin_buffer)); + assert(3 == unlock()); + get_mock_ctx(&mock_ctx); + assert(false == mock_ctx.device_unlocked); + assert(0 == APDU_AT(2)); } int main() { test_unlock(); + test_unlock_wrong_pin(); return 0; } From 58afed0b516dd3ecedbdb0a00d64636c099ce0ba Mon Sep 17 00:00:00 2001 From: italo sampaio Date: Fri, 25 Nov 2022 08:10:13 -0300 Subject: [PATCH 7/9] More adjustments after code review - Reworked retries test to just assert that the returned value is valid - Changed some function names for clarity - Removed retries counter logic from mock - Added mock variables to assert that wipe and unlock operations are done while the device is locked - Added a test case to assert that pin capping is applied --- .../test/communication/test_communication.c | 39 +------------------ ledger/src/ui/test/mock/cx.c | 2 +- ledger/src/ui/test/mock/cx.h | 2 +- ledger/src/ui/test/mock/os.c | 28 ++++++++----- ledger/src/ui/test/mock/os.h | 7 +++- ledger/src/ui/test/onboard/test_onboard.c | 11 +++--- ledger/src/ui/test/pin/test_pin.c | 26 +++++++++++++ 7 files changed, 58 insertions(+), 57 deletions(-) diff --git a/ledger/src/ui/test/communication/test_communication.c b/ledger/src/ui/test/communication/test_communication.c index 1124233b..2e69b916 100644 --- a/ledger/src/ui/test/communication/test_communication.c +++ b/ledger/src/ui/test/communication/test_communication.c @@ -46,45 +46,8 @@ void test_get_mode() { void test_get_retries() { printf("Test get retries...\n"); - unsigned char pin_buffer[] = "X1234567a"; - unsigned char wrong_pin[] = "Xa7654321"; - unsigned int rx = 4; - init_mock_ctx(); - for (int i = 0; i < strlen((const char *)pin_buffer); i++) { - SET_APDU_AT(2, i); - SET_APDU_AT(3, pin_buffer[i]); - assert(3 == update_pin_buffer(rx)); - } - - assert(3 == set_pin()); - assert(3 == get_retries()); - assert(0 == APDU_AT(2)); - - // Send wrong pin - for (int i = 0; i < strlen((const char *)wrong_pin); i++) { - SET_APDU_AT(2, i); - SET_APDU_AT(3, wrong_pin[i]); - assert(3 == update_pin_buffer(rx)); - } - assert(!unlock_with_pin(true)); - assert(3 == get_retries()); - assert(1 == APDU_AT(2)); - assert(!unlock_with_pin(true)); - assert(3 == get_retries()); - assert(2 == APDU_AT(2)); - assert(!unlock_with_pin(true)); - assert(3 == get_retries()); - assert(3 == APDU_AT(2)); - - // Send right pin again - for (int i = 0; i < strlen((const char *)pin_buffer); i++) { - SET_APDU_AT(2, i); - SET_APDU_AT(3, pin_buffer[i]); - assert(3 == update_pin_buffer(rx)); - } - assert(unlock_with_pin(true)); assert(3 == get_retries()); - assert(0 == APDU_AT(2)); + assert(MOCK_INTERNAL_RETRIES_COUNTER == APDU_AT(2)); } int main() { diff --git a/ledger/src/ui/test/mock/cx.c b/ledger/src/ui/test/mock/cx.c index bb6b1a9a..c8863040 100644 --- a/ledger/src/ui/test/mock/cx.c +++ b/ledger/src/ui/test/mock/cx.c @@ -26,7 +26,7 @@ static unsigned char mock_seed[32]; -void set_mock_seed(const unsigned char *data, unsigned int len) { +void mock_cx_rng(const unsigned char *data, unsigned int len) { for (unsigned int i = 0; i < len; i++) { mock_seed[i] = data[i]; } diff --git a/ledger/src/ui/test/mock/cx.h b/ledger/src/ui/test/mock/cx.h index 1e3ccf60..01894e39 100644 --- a/ledger/src/ui/test/mock/cx.h +++ b/ledger/src/ui/test/mock/cx.h @@ -27,4 +27,4 @@ */ unsigned char *cx_rng(unsigned char *buffer, unsigned int len); -void set_mock_seed(const unsigned char *data, unsigned int len); \ No newline at end of file +void mock_cx_rng(const unsigned char *data, unsigned int len); diff --git a/ledger/src/ui/test/mock/os.c b/ledger/src/ui/test/mock/os.c index a2c2c88a..cb4d688a 100644 --- a/ledger/src/ui/test/mock/os.c +++ b/ledger/src/ui/test/mock/os.c @@ -54,16 +54,18 @@ void explicit_bzero(void *s, size_t len) { unsigned int os_global_pin_check(unsigned char *pin_buffer, unsigned char pin_length) { - mock_ctx.device_unlocked = !strncmp((const char *)pin_buffer, - (const char *)mock_ctx.global_pin, - pin_length); - if (mock_ctx.device_unlocked) { - mock_ctx.retries = 0; - } else { - mock_ctx.retries++; + bool pin_matches = !strncmp((const char *)pin_buffer, + (const char *)mock_ctx.global_pin, + pin_length); + + // Assert that unlock was performed while the device was locked + if (pin_matches && !mock_ctx.device_unlocked) { + mock_ctx.successful_unlock_while_locked_count++; } + // Update mock state + mock_ctx.device_unlocked = pin_matches; - return mock_ctx.device_unlocked; + return (int)pin_matches; } void os_perso_set_pin(unsigned int identity, @@ -91,8 +93,14 @@ void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len) { } void os_perso_wipe() { + if (!mock_ctx.device_unlocked) { + mock_ctx.wipe_while_locked_count++; + } // wipe global pin, seed and state - init_mock_ctx(); + memset(mock_ctx.global_pin, 0x0, sizeof(mock_ctx.global_pin)); + memset(mock_ctx.global_seed, 0x0, sizeof(mock_ctx.global_seed)); + mock_ctx.device_unlocked = false; + mock_ctx.device_onboarded = false; } void os_perso_finalize(void) { @@ -104,7 +112,7 @@ unsigned int os_perso_isonboarded(void) { } unsigned int os_global_pin_retries(void) { - return mock_ctx.retries; + return (unsigned int)MOCK_INTERNAL_RETRIES_COUNTER; } // Generated mnemonics buffer will be "mnemonics-generated-from:" diff --git a/ledger/src/ui/test/mock/os.h b/ledger/src/ui/test/mock/os.h index 2e7ad43d..380124c7 100644 --- a/ledger/src/ui/test/mock/os.h +++ b/ledger/src/ui/test/mock/os.h @@ -40,6 +40,10 @@ #define IO_APDU_BUFFER_SIZE 85 extern unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; +/** + * Mock internal retries counter + */ +#define MOCK_INTERNAL_RETRIES_COUNTER 123 /** * Mock context used to assert current state */ @@ -48,7 +52,8 @@ typedef struct { unsigned char global_seed[257]; bool device_unlocked; bool device_onboarded; - unsigned int retries; + unsigned int wipe_while_locked_count; + unsigned int successful_unlock_while_locked_count; } mock_ctx_t; void init_mock_ctx(); diff --git a/ledger/src/ui/test/onboard/test_onboard.c b/ledger/src/ui/test/onboard/test_onboard.c index 82db192b..e3ca087e 100644 --- a/ledger/src/ui/test/onboard/test_onboard.c +++ b/ledger/src/ui/test/onboard/test_onboard.c @@ -111,16 +111,16 @@ void test_onboard_device() { } init_mock_ctx(); - set_mock_seed(seed, SEEDSIZE); + mock_cx_rng(seed, SEEDSIZE); assert(3 == onboard_device(&onboard_ctx)); assert(2 == APDU_AT(1)); assert(1 == APDU_AT(2)); mock_ctx_t mock_ctx; get_mock_ctx(&mock_ctx); - assert(mock_ctx.device_unlocked == true); - assert(mock_ctx.device_onboarded == true); - assert(mock_ctx.retries == 0); + assert(true == mock_ctx.device_unlocked); + assert(true == mock_ctx.device_onboarded); + assert(1 == mock_ctx.wipe_while_locked_count); assert(!strcmp((const char *)(valid_pin + 1), (const char *)mock_ctx.global_pin)); assert(!strcmp((const char *)expected_global_seed, @@ -135,7 +135,7 @@ void test_onboard_device() { onboard_ctx.words_buffer, sizeof(expected_words_buffer)) == 0); assert(memcmp(expected_seed, onboard_ctx.seed, sizeof(expected_seed)) == 0); - assert(onboard_ctx.words_buffer_length == 0); + assert(0 == onboard_ctx.words_buffer_length); } void test_onboard_device_invalid_pin() { @@ -178,7 +178,6 @@ void test_onboard_device_invalid_pin() { assert(false == mock_ctx.device_onboarded); assert(false == mock_ctx.device_unlocked); - assert(0 == mock_ctx.retries); assert(0 == memcmp(expected_global_pin, mock_ctx.global_pin, sizeof(expected_global_pin))); diff --git a/ledger/src/ui/test/pin/test_pin.c b/ledger/src/ui/test/pin/test_pin.c index edbf677b..e470005d 100644 --- a/ledger/src/ui/test/pin/test_pin.c +++ b/ledger/src/ui/test/pin/test_pin.c @@ -133,6 +133,7 @@ void test_set_pin() { mock_ctx_t mock_ctx; get_mock_ctx(&mock_ctx); assert(true == mock_ctx.device_unlocked); + assert(1 == mock_ctx.successful_unlock_while_locked_count); } void test_set_pin_invalid() { @@ -172,6 +173,29 @@ void test_unlock_with_pin() { assert(!strcmp((const char *)(pin_buffer + 1), (const char *)mock_ctx.global_pin)); assert(true == mock_ctx.device_unlocked); + assert(1 == mock_ctx.successful_unlock_while_locked_count); +} + +void test_unlock_with_pin_capping() { + printf("Test unlock with pin capping...\n"); + + unsigned char pin_buffer[] = "X1234567abcdef"; + unsigned int rx = 4; + init_mock_ctx(); + for (int i = 0; i < strlen((const char *)pin_buffer); i++) { + SET_APDU_AT(2, i); + SET_APDU_AT(3, pin_buffer[i]); + assert(3 == update_pin_buffer(rx)); + } + assert(3 == set_pin()); + assert(1 == unlock_with_pin(true)); + mock_ctx_t mock_ctx; + get_mock_ctx(&mock_ctx); + // Make sure pin capping is applied + // (i.e. only the first 8 bytes are copied to global buffer) + assert(!strcmp("1234567a", (const char *)mock_ctx.global_pin)); + assert(true == mock_ctx.device_unlocked); + assert(1 == mock_ctx.successful_unlock_while_locked_count); } void test_unlock_with_pin_not_set() { @@ -190,6 +214,7 @@ void test_unlock_with_pin_not_set() { get_mock_ctx(&mock_ctx); assert(0 == unlock_with_pin(true)); assert(false == mock_ctx.device_unlocked); + assert(0 == mock_ctx.successful_unlock_while_locked_count); const char *expected_global_pin[sizeof(mock_ctx.global_pin)]; memset(expected_global_pin, 0, sizeof(expected_global_pin)); assert(!strcmp((const char *)expected_global_pin, @@ -227,6 +252,7 @@ int main() { test_set_pin_invalid(); test_validate_ok(); test_unlock_with_pin(); + test_unlock_with_pin_capping(); test_unlock_with_pin_not_set(); test_set_device_pin(); From 1e3f46478755fed352ecfd7d89ba2526a3c23c05 Mon Sep 17 00:00:00 2001 From: italo sampaio Date: Fri, 25 Nov 2022 09:31:11 -0300 Subject: [PATCH 8/9] Added mock function to set the internal retries counter instead of using a constant --- ledger/src/ui/test/communication/test_communication.c | 3 ++- ledger/src/ui/test/mock/os.c | 11 ++++++++++- ledger/src/ui/test/mock/os.h | 5 +---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ledger/src/ui/test/communication/test_communication.c b/ledger/src/ui/test/communication/test_communication.c index 2e69b916..db972156 100644 --- a/ledger/src/ui/test/communication/test_communication.c +++ b/ledger/src/ui/test/communication/test_communication.c @@ -45,9 +45,10 @@ void test_get_mode() { void test_get_retries() { printf("Test get retries...\n"); + set_mock_retries(123); assert(3 == get_retries()); - assert(MOCK_INTERNAL_RETRIES_COUNTER == APDU_AT(2)); + assert(123 == APDU_AT(2)); } int main() { diff --git a/ledger/src/ui/test/mock/os.c b/ledger/src/ui/test/mock/os.c index cb4d688a..18eb7ada 100644 --- a/ledger/src/ui/test/mock/os.c +++ b/ledger/src/ui/test/mock/os.c @@ -33,6 +33,11 @@ */ static mock_ctx_t mock_ctx; +/** + * Mock internal retries counter + */ +static unsigned int mock_retries = 0; + void init_mock_ctx() { memset(&mock_ctx, 0, sizeof(mock_ctx)); } @@ -41,6 +46,10 @@ void get_mock_ctx(mock_ctx_t *ctx) { memcpy(ctx, &mock_ctx, sizeof(mock_ctx)); } +void set_mock_retries(unsigned int retries) { + mock_retries = retries; +} + /** * APDU buffer */ @@ -112,7 +121,7 @@ unsigned int os_perso_isonboarded(void) { } unsigned int os_global_pin_retries(void) { - return (unsigned int)MOCK_INTERNAL_RETRIES_COUNTER; + return mock_retries; } // Generated mnemonics buffer will be "mnemonics-generated-from:" diff --git a/ledger/src/ui/test/mock/os.h b/ledger/src/ui/test/mock/os.h index 380124c7..b88640bc 100644 --- a/ledger/src/ui/test/mock/os.h +++ b/ledger/src/ui/test/mock/os.h @@ -40,10 +40,6 @@ #define IO_APDU_BUFFER_SIZE 85 extern unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; -/** - * Mock internal retries counter - */ -#define MOCK_INTERNAL_RETRIES_COUNTER 123 /** * Mock context used to assert current state */ @@ -58,6 +54,7 @@ typedef struct { void init_mock_ctx(); void get_mock_ctx(mock_ctx_t *ctx); +void set_mock_retries(unsigned int retries); /** * Mock calls for os API From bf2713e314c1c4a1e166af6d4a04167a0ec15559 Mon Sep 17 00:00:00 2001 From: italo sampaio Date: Fri, 25 Nov 2022 14:45:14 -0300 Subject: [PATCH 9/9] Changed mocked values of host_seed and seed to more realistic values --- ledger/src/ui/test/onboard/test_onboard.c | 27 ++++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ledger/src/ui/test/onboard/test_onboard.c b/ledger/src/ui/test/onboard/test_onboard.c index e3ca087e..c870ec08 100644 --- a/ledger/src/ui/test/onboard/test_onboard.c +++ b/ledger/src/ui/test/onboard/test_onboard.c @@ -60,7 +60,10 @@ void test_set_host_seed() { onboard_t onboard_ctx; reset_onboard_ctx(&onboard_ctx); // mock 32 bytes random host seed - const char host_seed[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const char host_seed[] = {0x9c, 0xc9, 0x8b, 0xde, 0x40, 0xb3, 0x33, 0xbc, + 0x34, 0xf2, 0xae, 0x6f, 0xe6, 0x09, 0x1f, 0x57, + 0xfd, 0x4b, 0x6d, 0xcd, 0xc0, 0x62, 0x61, 0x53, + 0x03, 0xf0, 0xef, 0x03, 0xce, 0x84, 0x15, 0xd3}; unsigned int rx = 4; for (int i = 0; i < strlen(host_seed); i++) { SET_APDU_AT(2, i); @@ -75,9 +78,21 @@ void test_onboard_device() { onboard_t onboard_ctx; reset_onboard_ctx(&onboard_ctx); // mock 32 bytes random host seed - const unsigned char host_seed[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const unsigned char host_seed[] = { + 0x9c, 0xc9, 0x8b, 0xde, 0x40, 0xb3, 0x33, 0xbc, 0x34, 0xf2, 0xae, + 0x6f, 0xe6, 0x09, 0x1f, 0x57, 0xfd, 0x4b, 0x6d, 0xcd, 0xc0, 0x62, + 0x61, 0x53, 0x03, 0xf0, 0xef, 0x03, 0xce, 0x84, 0x15, 0xd3}; // mock 32 bytes handom seed - const unsigned char seed[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + const unsigned char seed[] = { + 0x21, 0x9f, 0x24, 0x3a, 0xf4, 0x23, 0x2e, 0x26, 0x0b, 0x37, 0xfe, + 0xf4, 0xd8, 0xc8, 0xf4, 0x88, 0xa5, 0x3a, 0x36, 0xd7, 0xa8, 0xa2, + 0xd5, 0x42, 0x8f, 0x57, 0xb8, 0x92, 0x79, 0x7f, 0xb0, 0xd3}; + // host_seed XOR seed + const unsigned char generated_seed[] = { + 0xbd, 0x56, 0xaf, 0xe4, 0xb4, 0x90, 0x1d, 0x9a, 0x3f, 0xc5, 0x50, + 0x9b, 0x3e, 0xc1, 0xeb, 0xdf, 0x58, 0x71, 0x5b, 0x1a, 0x68, 0xc0, + 0xb4, 0x11, 0x8c, 0xa7, 0x57, 0x91, 0xb7, 0xfb, 0xa5, 0x00}; + // device pin (with prepended length) const unsigned char valid_pin[] = "X1234567a"; unsigned int rx; @@ -92,16 +107,12 @@ void test_onboard_device() { // Mock RSK_SEED_CMD rx = 4; - for (int i = 0; i < strlen((const char *)host_seed); i++) { + for (int i = 0; i < sizeof(host_seed); i++) { SET_APDU_AT(2, i); SET_APDU_AT(3, host_seed[i]); assert(0 == set_host_seed(rx, &onboard_ctx)); } - char generated_seed[SEEDSIZE]; - for (int i = 0; i < SEEDSIZE; i++) { - generated_seed[i] = host_seed[i] ^ seed[i]; - } char expected_global_seed[257]; memset(expected_global_seed, 0, sizeof(expected_global_seed)); char *seed_str = stpcpy(expected_global_seed,