diff --git a/firmware/src/hal/include/hal/communication.h b/firmware/src/hal/include/hal/communication.h index 71da6fe1..f807a3da 100644 --- a/firmware/src/hal/include/hal/communication.h +++ b/firmware/src/hal/include/hal/communication.h @@ -53,71 +53,4 @@ unsigned char *communication_get_msg_buffer(); */ size_t communication_get_msg_buffer_size(); -/** - * @brief Exchanges bytes with the host. This function blocks until the host - * sends a message. - * - * The message exchanges data with the host using the msg_buffer. If there are - * any bytes to transmit, they are transmitted first. After that the function - * blocks until a new message is received from the host. - * - * @param tx The number of bytes sent to the host - * - * @returns the number of bytes received from the host - */ -unsigned short communication_io_exchange(unsigned short tx); - -// BEGINNING of platform-dependent code -#if defined(HSM_PLATFORM_X86) - -#include - -/** - * @brief For an external module processing - * APDU messages - * - * @param cb the external module processing callback - */ -typedef unsigned short (*communication_external_module_process_t)( - unsigned short tx); -void communication_set_external_module_process( - communication_external_module_process_t cb); - -/** - * @brief Starts a TCP server at the given host and port and sets it as the - * channel on which communication_io_exchange will perform the IO operations - * Either this or communication_set_input_file must be called before - * using communication_io_exchange. - * - * @param port the port on which to listen for connections - * @param host the interface to bind to - */ -void communication_set_and_start_server(int port, const char *host); - -/** - * @brief Sets the input file from which communication_io_exchange - * will read the input. - * Either this or communication_set_server must be called before - * using communication_io_exchange. - * - * @param _input_file the input file that is used for I/O - */ -void communication_set_input_file(FILE *_input_file); - -/** - * @brief Sets the replica file to which communication_io_exchange will - * write the input. Setting this is optional. - * - * @param _replica_file the file to which to replicate the inputs - */ -void communication_set_replica_file(FILE *_replica_file); - -/** - * @brief Perform an empty message write on the IO channel - */ -void communication_reply(); - -#endif -// END of platform-dependent code - #endif // __HAL_COMMUNICATION_H diff --git a/firmware/src/hal/src/common/communication.c b/firmware/src/hal/src/common/communication.c new file mode 100644 index 00000000..ebad7b21 --- /dev/null +++ b/firmware/src/hal/src/common/communication.c @@ -0,0 +1,44 @@ +/** + * 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 "hal/communication.h" + +static unsigned char* msg_buffer; +static size_t msg_buffer_size; + +// HAL implementation +bool communication_init(unsigned char* _msg_buffer, size_t _msg_buffer_size) { + // Setup the exchange buffer + msg_buffer = _msg_buffer; + msg_buffer_size = _msg_buffer_size; + return true; +} + +unsigned char* communication_get_msg_buffer() { + return msg_buffer; +} + +size_t communication_get_msg_buffer_size() { + return msg_buffer_size; +} diff --git a/firmware/src/hal/src/ledger/communication.c b/firmware/src/hal/src/ledger/communication.c deleted file mode 100644 index 2f12023a..00000000 --- a/firmware/src/hal/src/ledger/communication.c +++ /dev/null @@ -1,49 +0,0 @@ -/** - * 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 "os.h" -#include "hal/communication.h" - -static unsigned char* msg_buffer; -static size_t msg_buffer_size; - -// HAL implementation -bool communication_init(unsigned char* _msg_buffer, size_t _msg_buffer_size) { - // Setup the exchange buffer - msg_buffer = _msg_buffer; - msg_buffer_size = _msg_buffer_size; - return true; -} - -unsigned char* communication_get_msg_buffer() { - return msg_buffer; -} - -size_t communication_get_msg_buffer_size() { - return msg_buffer_size; -} - -unsigned short communication_io_exchange(unsigned short tx) { - return io_exchange(CHANNEL_APDU, tx); -} diff --git a/firmware/src/hal/src/ledger/communication.c b/firmware/src/hal/src/ledger/communication.c new file mode 120000 index 00000000..78676300 --- /dev/null +++ b/firmware/src/hal/src/ledger/communication.c @@ -0,0 +1 @@ +../common/communication.c \ No newline at end of file diff --git a/firmware/src/hal/src/x86/communication.c b/firmware/src/hal/src/x86/communication.c deleted file mode 100644 index a477b4f9..00000000 --- a/firmware/src/hal/src/x86/communication.c +++ /dev/null @@ -1,425 +0,0 @@ -/** - * 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 -#include -#include -#include -#include -#include - -#include "hal/communication.h" -#include "hal/log.h" -#include "apdu.h" - -/** - * APDU buffer - */ -#define IO_APDU_BUFFER_SIZE 85 -static unsigned char apdu_buffer[IO_APDU_BUFFER_SIZE]; - -#define MAX_FUZZ_TRANSFER IO_APDU_BUFFER_SIZE - -enum io_mode_e { IO_MODE_SERVER, IO_MODE_INPUT_FILE }; -enum io_mode_e io_mode; - -/** - * For the TCP server - */ -int server; -int socketfd; -struct sockaddr_in servaddr, cli; - -/** - * For the file input mode - */ -static FILE *input_file; - -/** - * Copy all input to this file, if set. - */ -static FILE *replica_file; - -/** - * For flushing in between mains - */ -static bool io_exchange_write_only; - -/** - * For an external module processing - * APDU messages - */ -typedef unsigned short (*communication_external_module_process_t)( - unsigned short tx); -communication_external_module_process_t external_module_process_cb = NULL; - -void communication_set_external_module_process( - communication_external_module_process_t cb) { - external_module_process_cb = cb; -} - -/** - * @brief Start server, return a socket on connection - * - * @arg[in] PORT tcp port - * @arg[in] HOST HOST string - * - * @returns socket file descriptor - */ -static int start_server(int port, const char *host) { - int sockfd; - struct hostent *hostinfo; - hostinfo = gethostbyname(host); - - if (hostinfo == NULL) { - LOG("Host not found.\n"); - exit(1); - } - - // socket create and verification - sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd == -1) { - LOG("Socket creation failed...\n"); - exit(1); - } - - bzero(&servaddr, sizeof(servaddr)); - - if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < - 0) { - LOG("Socket option setting failed failed\n"); - exit(1); - } - - if (setsockopt(sockfd, SOL_TCP, TCP_NODELAY, &(int){1}, sizeof(int)) < 0) { - LOG("Socket option setting failed failed\n"); - exit(1); - } - - // Set address and port - servaddr.sin_family = AF_INET; - memcpy(&servaddr.sin_addr, hostinfo->h_addr_list[0], hostinfo->h_length); - servaddr.sin_port = htons(port); - - // Binding newly created socket to given IP and verification - if ((bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) != 0) { - LOG("Socket bind failed...\n"); - exit(1); - } - - // Now server is ready to listen and verification - if ((listen(sockfd, 5)) != 0) { - LOG("Listen failed...\n"); - exit(1); - } - - LOG("Server listening...\n"); - return sockfd; -} - -/** - * @brief Accept the data packet from client and verification - * - * @arg[in] sockfd server socket - * - * @returns connection file descriptor - */ -static int accept_connection(int sockfd) { - int len = sizeof(cli); - int connfd = accept(sockfd, (struct sockaddr *)&cli, &len); - if (connfd < 0) { - LOG("Client connection failed...\n"); - exit(1); - } - - LOG("Client connected...\n"); - return connfd; -} - -void communication_set_and_start_server(int port, const char *host) { - server = start_server(port, host); - socketfd = 0; - io_mode = IO_MODE_SERVER; - io_exchange_write_only = false; -} - -void communication_set_input_file(FILE *_input_file) { - input_file = _input_file; - io_mode = IO_MODE_INPUT_FILE; -} - -void communication_set_replica_file(FILE *_replica_file) { - replica_file = _replica_file; -} - -/** - * Perform an empty message - * write on the IO channel - */ -void communication_reply() { - io_exchange_write_only = true; - apdu_buffer[0] = (APDU_OK & 0xff00) >> 8; - apdu_buffer[1] = APDU_OK & 0xff; - communication_io_exchange(2); -} - -/* - * This function emulates USB device, writing bytes to tcpsocket instead - * @arg[in] tx amount of bytes to transmit to the client - * @ret amount of bytes received from the client - */ -static unsigned short io_exchange_server(unsigned short tx) { - uint32_t tx_net, rx_net; - unsigned int rx; - int readlen; - - while (true) { - if (!socketfd) { - socketfd = accept_connection(server); - tx = 0; - } - - // Write len (Compatibility with LegerBlue commTCP.py) - if (tx > 0) { - // Write APDU length minus two bytes of the sw - // (encoded in 4 bytes network byte-order) - // (compatibility with LegerBlue commTCP.py) - tx_net = tx - 2; - tx_net = htonl(tx_net); - size_t written; - if (send(socketfd, &tx_net, sizeof(tx_net), MSG_NOSIGNAL) == -1) { - LOG("Connection closed by the client\n"); - socketfd = 0; - continue; - } - // Write APDU - if (send(socketfd, apdu_buffer, tx, MSG_NOSIGNAL) == -1) { - LOG("Connection closed by the client\n"); - socketfd = 0; - continue; - } - LOG_HEX("Dongle =>", apdu_buffer, tx); - } - - // Only write? We're done - if (io_exchange_write_only) { - io_exchange_write_only = false; - return 0; - } - - // Read APDU length - // (encoded in 4 bytes network byte-order) - // (compatibility with LegerBlue commTCP.py) - readlen = read(socketfd, &rx_net, sizeof(rx_net)); - if (readlen == sizeof(rx_net)) { - rx = ntohl(rx_net); - if (rx > 0) { - // Read APDU from socket - readlen = read(socketfd, apdu_buffer, sizeof(apdu_buffer)); - if (readlen != rx) { - LOG("Error reading APDU (got %d bytes != %d bytes). " - "Disconnected\n", - readlen, - rx); - socketfd = 0; - continue; - } - LOG_HEX("Dongle <=", apdu_buffer, rx); - } else { - // Empty packet - LOG("Dongle <= \n"); - } - - return rx; - } else if (readlen == 0) { - // EOF => socket closed - LOG("Connection closed by the client\n"); - } else if (readlen == -1) { - // Error reading - LOG("Error reading from socket. Disconnected\n"); - } else { - // Invalid packet header - LOG("Error reading APDU length (got %d bytes != %ld bytes). " - "Disconnected\n", - readlen, - sizeof(rx_net)); - } - socketfd = 0; - } -} - -/* This function emulates the HOST device, reading bytes from a file instead - * @arg[in] tx_len amount of bytes to transmit to the client - * @arg[in] inputfd the input file - * @ret amount of bytes received from the client - */ -static unsigned short io_exchange_file(unsigned char tx, FILE *input_file) { - // File input format: |1 byte length| |len bytes data| - static unsigned long file_index = 0; - LOG_HEX("Dongle => ", apdu_buffer, tx); - - // Only write? We're done - if (io_exchange_write_only) { - io_exchange_write_only = false; - return 0; - } - - unsigned char announced_rx; - if (fread(&announced_rx, sizeof(char), 1, input_file) != 1) { - if (feof(input_file)) { - LOG("Server: EOF\n"); - exit(0); - } - LOG("Server: could not read rx size\n"); - exit(1); - } - - // Read a capped amount of bytes to keep it reasonably realistic - unsigned short capped_rx; - if (announced_rx <= MAX_FUZZ_TRANSFER) { - capped_rx = announced_rx; - } else { - capped_rx = MAX_FUZZ_TRANSFER; - } - - LOG("Server: reading %d (announced: %d) bytes at index: %d\n", - capped_rx, - announced_rx, - file_index); - unsigned short rx = fread(apdu_buffer, sizeof(char), capped_rx, input_file); - - if (rx != capped_rx) { - // if we reach EOF while reading the data portion it means - // the announced size did not match the file - if (feof(input_file)) { - LOG("Server: malformed input, tried reading %d bytes but reached " - "EOF after %d\n", - capped_rx, - rx); - exit(1); - } - LOG("Server: Could not read %d bytes (only: %d) from input file\n", - capped_rx, - rx); - exit(1); - } - - // Move the offset to wherever the input said it should be, - // even if we actually did not read the whole data. - // If not, this would lead the file_index - // interpreting data as the length. - unsigned long index_offset = announced_rx + 1; - if (file_index > (ULONG_MAX - index_offset)) { - LOG("Server: input file too big, can't store offset."); - exit(1); - } - - file_index += index_offset; - LOG_HEX("Dongle <= ", apdu_buffer, rx); - return capped_rx; -} - -/* Append a received command to file - * @arg[in] filename Binary file to append commands - * @arg[in] rx Lenght of the command - * @ret number of bytes written - */ -static unsigned int replicate_to_file(FILE *replica_file, unsigned short rx) { - unsigned char rx_byte = rx; - LOG_HEX("Replica =>", apdu_buffer, rx_byte); - unsigned int written = fwrite(&rx_byte, sizeof(char), 1, replica_file); - written += fwrite(apdu_buffer, sizeof(char), rx_byte, replica_file); - - // XXX: This should not be necessary. We are correctly closing the file - // at the end of the process. But for whatever reason, this does not seem - // to work for small inputs. Force flushing after every input to avoid - // corrupted replica files. - fflush(replica_file); - return written; -} - -/***** BEGIN HAL Communication module implementation *****/ - -/** - * @brief Initializes the communication module - * - * @param msg_buffer The buffer to use for communication - * @param msg_buffer_size The size of the message buffer in bytes - * - * @returns whether the initialisation succeeded - */ -bool communication_init(unsigned char *msg_buffer, size_t msg_buffer_size) { - // Nothing to do here -} - -unsigned char *communication_get_msg_buffer() { - return apdu_buffer; -} - -size_t communication_get_msg_buffer_size() { - return sizeof(apdu_buffer); -} - -unsigned short communication_io_exchange(unsigned short tx) { - unsigned short rx, tx_ex; - - switch (io_mode) { - case IO_MODE_SERVER: - rx = io_exchange_server(tx); - break; - case IO_MODE_INPUT_FILE: - rx = io_exchange_file(tx, input_file); - break; - default: - LOG("[TCPSigner] No IO Mode set! This is a bug."); - exit(1); - break; - } - - if (replica_file != NULL) { - int written = replicate_to_file(replica_file, rx); - if (written != rx + 1) { - LOG("Error writting to output file %s\n", replica_file); - exit(-1); - } - } - - // Process one APDU message using an external module callback - // (there should generally speaking be at most one of these in a row, - // so the stack shouldn't overflow) - if (external_module_process_cb) { - tx_ex = external_module_process_cb(rx); - if (tx_ex) { - return communication_io_exchange(tx_ex); - } - } - - return rx; -} - -/***** END HAL Communication module implementation *****/ diff --git a/firmware/src/hal/src/x86/communication.c b/firmware/src/hal/src/x86/communication.c new file mode 120000 index 00000000..78676300 --- /dev/null +++ b/firmware/src/hal/src/x86/communication.c @@ -0,0 +1 @@ +../common/communication.c \ No newline at end of file diff --git a/firmware/src/ledger/signer/src/main.c b/firmware/src/ledger/signer/src/main.c index 7bdaebe8..b287ca13 100644 --- a/firmware/src/ledger/signer/src/main.c +++ b/firmware/src/ledger/signer/src/main.c @@ -210,6 +210,34 @@ unsigned char io_event(unsigned char channel) { return 1; } +static bool do_io_exchange(volatile unsigned int *rtx) { + BEGIN_TRY { + TRY { + *rtx = io_exchange(CHANNEL_APDU, *rtx); + return true; + } + CATCH_OTHER(e) { + *rtx = 0; + G_io_apdu_buffer[(*rtx)++] = 0x68; + G_io_apdu_buffer[(*rtx)++] = e & 0xFF; + return false; + } + FINALLY { + } + } + END_TRY; +} + +static void main_loop() { + volatile unsigned int rtx = 0; + + while (!hsm_exit_requested()) { + if (!do_io_exchange(&rtx)) + continue; + rtx = hsm_process_apdu(rtx); + } +} + __attribute__((section(".boot"))) int main(int argc, char **argv) { __asm volatile("cpsie i"); @@ -248,8 +276,8 @@ __attribute__((section(".boot"))) int main(int argc, char **argv) { // HSM context initialization hsm_init(); - // HSM main loop - hsm_main_loop(); + // Main loop + main_loop(); // HAL modules finalisation // Nothing for now diff --git a/firmware/src/powhsm/src/hsm.c b/firmware/src/powhsm/src/hsm.c index e1a0f12f..1318705e 100644 --- a/firmware/src/powhsm/src/hsm.c +++ b/firmware/src/powhsm/src/hsm.c @@ -91,7 +91,7 @@ static void app_exit(void) { _hsm_exit_requested = true; } -static unsigned int hsm_process_apdu(volatile unsigned int rx) { +static unsigned int hsm_process_command(volatile unsigned int rx) { unsigned int tx = 0; uint8_t pubkey_length; @@ -284,10 +284,6 @@ static unsigned int hsm_process_exception(unsigned short code, return tx; } -static bool hsm_exit_requested() { - return _hsm_exit_requested; -} - void hsm_init() { // Initialize current operation // (0 = no operation being executed) @@ -300,34 +296,26 @@ void hsm_init() { bc_init_state(); } -void hsm_main_loop() { - volatile unsigned int rx = 0; - volatile unsigned int tx = 0; - - // DESIGN NOTE: the bootloader ignores the way APDU are fetched. The only - // goal is to retrieve APDU. - // When APDU are to be fetched from multiple IOs, like NFC+USB+BLE, make - // sure the io_event is called with a - // switch event, before the apdu is replied to the bootloader. This avoid - // APDU injection faults. - while (!hsm_exit_requested()) { - BEGIN_TRY { - TRY { - // ensure no race in catch_other if io_exchange throws - // an error - rx = tx; - tx = 0; - rx = communication_io_exchange(rx); - - tx = hsm_process_apdu(rx); - THROW(0x9000); - } - CATCH_OTHER(e) { - tx = hsm_process_exception(e, tx); - } - FINALLY { - } +unsigned int hsm_process_apdu(unsigned int rx) { + unsigned int tx = 0; + + BEGIN_TRY { + TRY { + tx = hsm_process_command(rx); + THROW(0x9000); + } + CATCH_OTHER(e) { + tx = hsm_process_exception(e, tx); + } + FINALLY { } - END_TRY; } + END_TRY; + + return tx; +} + +bool hsm_exit_requested() { + return _hsm_exit_requested; } + diff --git a/firmware/src/powhsm/src/hsm.h b/firmware/src/powhsm/src/hsm.h index be9a5048..df8529c5 100644 --- a/firmware/src/powhsm/src/hsm.h +++ b/firmware/src/powhsm/src/hsm.h @@ -29,12 +29,8 @@ void hsm_init(); -void hsm_main_loop(); +unsigned int hsm_process_apdu(unsigned int rx); -// unsigned int hsm_process_apdu(volatile unsigned int rx); - -// unsigned int hsm_process_exception(unsigned short code, unsigned int tx); - -// bool hsm_exit_requested(); +bool hsm_exit_requested(); #endif // __HSM_H diff --git a/firmware/src/tcpsigner/src/hsmsim_io.c b/firmware/src/tcpsigner/src/hsmsim_io.c new file mode 100644 index 00000000..9f80c92d --- /dev/null +++ b/firmware/src/tcpsigner/src/hsmsim_io.c @@ -0,0 +1,405 @@ +/** + * 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 +#include +#include +#include +#include +#include + +#include "hal/log.h" +#include "hsmsim_io.h" +#include "apdu.h" + +/** + * APDU buffer + */ +#define IO_APDU_BUFFER_SIZE 85 +static unsigned char apdu_buffer[IO_APDU_BUFFER_SIZE]; + +#define MAX_FUZZ_TRANSFER IO_APDU_BUFFER_SIZE + +enum io_mode_e { IO_MODE_SERVER, IO_MODE_INPUT_FILE }; +enum io_mode_e io_mode; + +/** + * For the TCP server + */ +int server; +int socketfd; +struct sockaddr_in servaddr, cli; + +/** + * For the file input mode + */ +static FILE *input_file; + +/** + * Copy all input to this file, if set. + */ +static FILE *replica_file; + +/** + * For flushing in between mains + */ +static bool io_exchange_write_only; + +/** + * For an external module processing + * APDU messages + */ +typedef unsigned short (*hsmsim_io_external_module_process_t)( + unsigned short tx); +hsmsim_io_external_module_process_t external_module_process_cb = NULL; + +void hsmsim_io_set_external_module_process( + hsmsim_io_external_module_process_t cb) { + external_module_process_cb = cb; +} + +/** + * @brief Start server, return a socket on connection + * + * @arg[in] PORT tcp port + * @arg[in] HOST HOST string + * + * @returns socket file descriptor + */ +static int start_server(int port, const char *host) { + int sockfd; + struct hostent *hostinfo; + hostinfo = gethostbyname(host); + + if (hostinfo == NULL) { + LOG("Host not found.\n"); + exit(1); + } + + // socket create and verification + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd == -1) { + LOG("Socket creation failed...\n"); + exit(1); + } + + bzero(&servaddr, sizeof(servaddr)); + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < + 0) { + LOG("Socket option setting failed failed\n"); + exit(1); + } + + if (setsockopt(sockfd, SOL_TCP, TCP_NODELAY, &(int){1}, sizeof(int)) < 0) { + LOG("Socket option setting failed failed\n"); + exit(1); + } + + // Set address and port + servaddr.sin_family = AF_INET; + memcpy(&servaddr.sin_addr, hostinfo->h_addr_list[0], hostinfo->h_length); + servaddr.sin_port = htons(port); + + // Binding newly created socket to given IP and verification + if ((bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) != 0) { + LOG("Socket bind failed...\n"); + exit(1); + } + + // Now server is ready to listen and verification + if ((listen(sockfd, 5)) != 0) { + LOG("Listen failed...\n"); + exit(1); + } + + LOG("Server listening...\n"); + return sockfd; +} + +/** + * @brief Accept the data packet from client and verification + * + * @arg[in] sockfd server socket + * + * @returns connection file descriptor + */ +static int accept_connection(int sockfd) { + int len = sizeof(cli); + int connfd = accept(sockfd, (struct sockaddr *)&cli, &len); + if (connfd < 0) { + LOG("Client connection failed...\n"); + exit(1); + } + + LOG("Client connected...\n"); + return connfd; +} + +void hsmsim_io_init() { + communication_init(apdu_buffer, sizeof(apdu_buffer)); +} + +void hsmsim_io_set_and_start_server(int port, const char *host) { + server = start_server(port, host); + socketfd = 0; + io_mode = IO_MODE_SERVER; + io_exchange_write_only = false; +} + +void hsmsim_io_set_input_file(FILE *_input_file) { + input_file = _input_file; + io_mode = IO_MODE_INPUT_FILE; +} + +void hsmsim_io_set_replica_file(FILE *_replica_file) { + replica_file = _replica_file; +} + +/** + * Perform an empty message + * write on the IO channel + */ +void hsmsim_io_reply() { + io_exchange_write_only = true; + apdu_buffer[0] = (APDU_OK & 0xff00) >> 8; + apdu_buffer[1] = APDU_OK & 0xff; + hsmsim_io_exchange(2); +} + +/* + * This function emulates USB device, writing bytes to tcpsocket instead + * @arg[in] tx amount of bytes to transmit to the client + * @ret amount of bytes received from the client + */ +static unsigned short io_exchange_server(unsigned short tx) { + uint32_t tx_net, rx_net; + unsigned int rx; + int readlen; + + while (true) { + if (!socketfd) { + socketfd = accept_connection(server); + tx = 0; + } + + // Write len (Compatibility with LegerBlue commTCP.py) + if (tx > 0) { + // Write APDU length minus two bytes of the sw + // (encoded in 4 bytes network byte-order) + // (compatibility with LegerBlue commTCP.py) + tx_net = tx - 2; + tx_net = htonl(tx_net); + size_t written; + if (send(socketfd, &tx_net, sizeof(tx_net), MSG_NOSIGNAL) == -1) { + LOG("Connection closed by the client\n"); + socketfd = 0; + continue; + } + // Write APDU + if (send(socketfd, apdu_buffer, tx, MSG_NOSIGNAL) == -1) { + LOG("Connection closed by the client\n"); + socketfd = 0; + continue; + } + LOG_HEX("Dongle =>", apdu_buffer, tx); + } + + // Only write? We're done + if (io_exchange_write_only) { + io_exchange_write_only = false; + return 0; + } + + // Read APDU length + // (encoded in 4 bytes network byte-order) + // (compatibility with LegerBlue commTCP.py) + readlen = read(socketfd, &rx_net, sizeof(rx_net)); + if (readlen == sizeof(rx_net)) { + rx = ntohl(rx_net); + if (rx > 0) { + // Read APDU from socket + readlen = read(socketfd, apdu_buffer, sizeof(apdu_buffer)); + if (readlen != rx) { + LOG("Error reading APDU (got %d bytes != %d bytes). " + "Disconnected\n", + readlen, + rx); + socketfd = 0; + continue; + } + LOG_HEX("Dongle <=", apdu_buffer, rx); + } else { + // Empty packet + LOG("Dongle <= \n"); + } + + return rx; + } else if (readlen == 0) { + // EOF => socket closed + LOG("Connection closed by the client\n"); + } else if (readlen == -1) { + // Error reading + LOG("Error reading from socket. Disconnected\n"); + } else { + // Invalid packet header + LOG("Error reading APDU length (got %d bytes != %ld bytes). " + "Disconnected\n", + readlen, + sizeof(rx_net)); + } + socketfd = 0; + } +} + +/* This function emulates the HOST device, reading bytes from a file instead + * @arg[in] tx_len amount of bytes to transmit to the client + * @arg[in] inputfd the input file + * @ret amount of bytes received from the client + */ +static unsigned short io_exchange_file(unsigned char tx, FILE *input_file) { + // File input format: |1 byte length| |len bytes data| + static unsigned long file_index = 0; + LOG_HEX("Dongle => ", apdu_buffer, tx); + + // Only write? We're done + if (io_exchange_write_only) { + io_exchange_write_only = false; + return 0; + } + + unsigned char announced_rx; + if (fread(&announced_rx, sizeof(char), 1, input_file) != 1) { + if (feof(input_file)) { + LOG("Server: EOF\n"); + exit(0); + } + LOG("Server: could not read rx size\n"); + exit(1); + } + + // Read a capped amount of bytes to keep it reasonably realistic + unsigned short capped_rx; + if (announced_rx <= MAX_FUZZ_TRANSFER) { + capped_rx = announced_rx; + } else { + capped_rx = MAX_FUZZ_TRANSFER; + } + + LOG("Server: reading %d (announced: %d) bytes at index: %d\n", + capped_rx, + announced_rx, + file_index); + unsigned short rx = fread(apdu_buffer, sizeof(char), capped_rx, input_file); + + if (rx != capped_rx) { + // if we reach EOF while reading the data portion it means + // the announced size did not match the file + if (feof(input_file)) { + LOG("Server: malformed input, tried reading %d bytes but reached " + "EOF after %d\n", + capped_rx, + rx); + exit(1); + } + LOG("Server: Could not read %d bytes (only: %d) from input file\n", + capped_rx, + rx); + exit(1); + } + + // Move the offset to wherever the input said it should be, + // even if we actually did not read the whole data. + // If not, this would lead the file_index + // interpreting data as the length. + unsigned long index_offset = announced_rx + 1; + if (file_index > (ULONG_MAX - index_offset)) { + LOG("Server: input file too big, can't store offset."); + exit(1); + } + + file_index += index_offset; + LOG_HEX("Dongle <= ", apdu_buffer, rx); + return capped_rx; +} + +/* Append a received command to file + * @arg[in] filename Binary file to append commands + * @arg[in] rx Lenght of the command + * @ret number of bytes written + */ +static unsigned int replicate_to_file(FILE *replica_file, unsigned short rx) { + unsigned char rx_byte = rx; + LOG_HEX("Replica =>", apdu_buffer, rx_byte); + unsigned int written = fwrite(&rx_byte, sizeof(char), 1, replica_file); + written += fwrite(apdu_buffer, sizeof(char), rx_byte, replica_file); + + // XXX: This should not be necessary. We are correctly closing the file + // at the end of the process. But for whatever reason, this does not seem + // to work for small inputs. Force flushing after every input to avoid + // corrupted replica files. + fflush(replica_file); + return written; +} + +unsigned short hsmsim_io_exchange(unsigned short tx) { + unsigned short rx, tx_ex; + + switch (io_mode) { + case IO_MODE_SERVER: + rx = io_exchange_server(tx); + break; + case IO_MODE_INPUT_FILE: + rx = io_exchange_file(tx, input_file); + break; + default: + LOG("[TCPSigner] No IO Mode set! This is a bug."); + exit(1); + break; + } + + if (replica_file != NULL) { + int written = replicate_to_file(replica_file, rx); + if (written != rx + 1) { + LOG("Error writting to output file %s\n", replica_file); + exit(-1); + } + } + + // Process one APDU message using an external module callback + // (there should generally speaking be at most one of these in a row, + // so the stack shouldn't overflow) + if (external_module_process_cb) { + tx_ex = external_module_process_cb(rx); + if (tx_ex) { + return hsmsim_io_exchange(tx_ex); + } + } + + return rx; +} diff --git a/firmware/src/tcpsigner/src/hsmsim_io.h b/firmware/src/tcpsigner/src/hsmsim_io.h new file mode 100644 index 00000000..0b0bdaf6 --- /dev/null +++ b/firmware/src/tcpsigner/src/hsmsim_io.h @@ -0,0 +1,94 @@ +/** + * 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 __HSMSIM_IO_H +#define __HSMSIM_IO_H + +#include + +/** + * @brief Initializes the I/O module + */ +void hsmsim_io_init(); + +/** + * @brief Exchanges bytes with the host. This function blocks until the host + * sends a message. + * + * The message exchanges data with the host using the msg_buffer. If there are + * any bytes to transmit, they are transmitted first. After that the function + * blocks until a new message is received from the host. + * + * @param tx The number of bytes sent to the host + * + * @returns the number of bytes received from the host + */ +unsigned short hsmsim_io_exchange(unsigned short tx); + +/** + * @brief For an external module processing + * APDU messages + * + * @param cb the external module processing callback + */ +typedef unsigned short (*communication_external_module_process_t)( + unsigned short tx); +void hsmsim_io_set_external_module_process( + communication_external_module_process_t cb); + +/** + * @brief Starts a TCP server at the given host and port and sets it as the + * channel on which communication_io_exchange will perform the IO operations + * Either this or communication_set_input_file must be called before + * using communication_io_exchange. + * + * @param port the port on which to listen for connections + * @param host the interface to bind to + */ +void hsmsim_io_set_and_start_server(int port, const char *host); + +/** + * @brief Sets the input file from which communication_io_exchange + * will read the input. + * Either this or communication_set_server must be called before + * using communication_io_exchange. + * + * @param _input_file the input file that is used for I/O + */ +void hsmsim_io_set_input_file(FILE *_input_file); + +/** + * @brief Sets the replica file to which communication_io_exchange will + * write the input. Setting this is optional. + * + * @param _replica_file the file to which to replicate the inputs + */ +void hsmsim_io_set_replica_file(FILE *_replica_file); + +/** + * @brief Perform an empty message write on the IO channel + */ +void hsmsim_io_reply(); + +#endif // __HSMSIM_IO_H \ No newline at end of file diff --git a/firmware/src/tcpsigner/src/tcpsigner.c b/firmware/src/tcpsigner/src/tcpsigner.c index a049f844..4eb7c7ac 100644 --- a/firmware/src/tcpsigner/src/tcpsigner.c +++ b/firmware/src/tcpsigner/src/tcpsigner.c @@ -42,6 +42,7 @@ #include "hal/endorsement.h" #include "hal/log.h" +#include "hsmsim_io.h" #include "hsmsim_nu.h" #include "hsmsim_admin.h" @@ -278,6 +279,15 @@ static void set_signal_handlers() { signal(SIGABRT, finalise); } +static void signer_main_loop() { + unsigned int rtx = 0; + + while (!hsm_exit_requested()) { + rtx = hsmsim_io_exchange(rtx); + rtx = hsm_process_apdu(rtx); + } +} + // Main function void main(int argc, char **argv) { set_signal_handlers(); @@ -400,6 +410,9 @@ void main(int argc, char **argv) { // Initialize admin hsmsim_admin_init(); + // Initialize I/O + hsmsim_io_init(); + // Initialize hsm hsm_init(); @@ -414,12 +427,12 @@ void main(int argc, char **argv) { exit(1); } - communication_set_input_file(inputfd); + hsmsim_io_set_input_file(inputfd); } else { LOG("Starting TCP server on %s:%i\n", arguments.bind, arguments.port); - communication_set_and_start_server(arguments.port, arguments.bind); + hsmsim_io_set_and_start_server(arguments.port, arguments.bind); } FILE *replicafd; @@ -430,11 +443,11 @@ void main(int argc, char **argv) { arguments.replicafile); exit(1); }; - communication_set_replica_file(replicafd); + hsmsim_io_set_replica_file(replicafd); }; // Set the admin module callback for the communication module - communication_set_external_module_process(&admin_process); + hsmsim_io_set_external_module_process(&admin_process); // Run the Signer main loop and the // UI heartbeat main loop in an alternate @@ -442,16 +455,16 @@ void main(int argc, char **argv) { while (true) { LOG("Running signer main loop...\n"); hsm_init(); - hsm_main_loop(); + signer_main_loop(); // Send an empty reply so that the client // doesn't hang waiting - communication_reply(); + hsmsim_io_reply(); LOG("Running UI heartbeat main loop...\n"); ui_heartbeat_init(&ui_heartbeat_ctx); ui_heartbeat_main(&ui_heartbeat_ctx); // Ditto - communication_reply(); + hsmsim_io_reply(); } if (replicafd != NULL) { diff --git a/firmware/src/tcpsigner/src/ui_deps.c b/firmware/src/tcpsigner/src/ui_deps.c index a35ed7cb..aae164c5 100644 --- a/firmware/src/tcpsigner/src/ui_deps.c +++ b/firmware/src/tcpsigner/src/ui_deps.c @@ -31,6 +31,7 @@ #include "hal/exceptions.h" #include "hal/log.h" +#include "hsmsim_io.h" #include "ui_deps.h" #include "ui_err.h" #include "sha256.h" @@ -68,5 +69,5 @@ unsigned int os_global_pin_retries() { } unsigned short io_exchange(unsigned char channel_and_flags, unsigned short tx) { - return communication_io_exchange(tx); + return hsmsim_io_exchange(tx); } \ No newline at end of file