diff --git a/src/common/wallet.c b/src/common/wallet.c index 96ac0f4b..8590cb29 100644 --- a/src/common/wallet.c +++ b/src/common/wallet.c @@ -2,6 +2,7 @@ #include #include +#include "../common/base58.h" #include "../common/bip32.h" #include "../common/buffer.h" #include "../common/script.h" @@ -358,20 +359,41 @@ int parse_policy_map_key_info(buffer_t *buffer, policy_map_key_info_t *out, int // consume the rest of the buffer into the pubkey, except possibly the final "/**" unsigned int ext_pubkey_len = 0; + char ext_pubkey_str[MAX_SERIALIZED_PUBKEY_LENGTH]; uint8_t c; while (ext_pubkey_len < MAX_SERIALIZED_PUBKEY_LENGTH && buffer_peek(buffer, &c) && is_alphanumeric(c)) { - out->ext_pubkey[ext_pubkey_len] = c; + ext_pubkey_str[ext_pubkey_len] = c; ++ext_pubkey_len; buffer_seek_cur(buffer, 1); } - out->ext_pubkey[ext_pubkey_len] = '\0'; + ext_pubkey_str[ext_pubkey_len] = '\0'; if (ext_pubkey_len < 111 || ext_pubkey_len > 112) { // loose sanity check; pubkeys in bitcoin can be 111 or 112 characters long return WITH_ERROR(-1, "Invalid extended pubkey length"); } + serialized_extended_pubkey_check_t ext_pubkey_check; + if (base58_decode(ext_pubkey_str, + ext_pubkey_len, + (uint8_t *) &ext_pubkey_check, + sizeof(ext_pubkey_check)) < 0) { + return WITH_ERROR(-1, "Error decoding serialized extended pubkey"); + } + + // verify checksum + uint8_t checksum[4]; + crypto_get_checksum((uint8_t *) &ext_pubkey_check.serialized_extended_pubkey, + sizeof(ext_pubkey_check.serialized_extended_pubkey), + checksum); + + if (memcmp(&ext_pubkey_check.checksum, checksum, sizeof(checksum)) != 0) { + return WITH_ERROR(-1, "Wrong extended pubkey checksum"); + } + + out->ext_pubkey = ext_pubkey_check.serialized_extended_pubkey; + // either the string terminates now, or it has a final "/**" suffix for the wildcard. if (!buffer_can_read(buffer, 1)) { // no wildcard; this is an error in V1 diff --git a/src/common/wallet.h b/src/common/wallet.h index 06713184..b96e1781 100644 --- a/src/common/wallet.h +++ b/src/common/wallet.h @@ -6,6 +6,7 @@ #include "common/bip32.h" #include "common/buffer.h" #include "../constants.h" +#include "../crypto.h" #ifndef SKIP_FOR_CMOCKA #include "os.h" @@ -80,7 +81,7 @@ typedef struct { uint8_t master_key_derivation_len; uint8_t has_key_origin; uint8_t has_wildcard; // true iff the keys ends with the wildcard (/ followed by **) - char ext_pubkey[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; + serialized_extended_pubkey_t ext_pubkey; } policy_map_key_info_t; typedef struct { diff --git a/src/crypto.c b/src/crypto.c index 37c0a194..2dab98f6 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -296,14 +296,10 @@ bool crypto_derive_symmetric_key(const char *label, size_t label_len, uint8_t ke return true; } -// TODO: Split serialization from key derivation? -// It might be difficult to have a clean API without wasting memory, as the checksum -// needs to be concatenated to the data before base58 serialization. -int get_serialized_extended_pubkey_at_path(const uint32_t bip32_path[], - uint8_t bip32_path_len, - uint32_t bip32_pubkey_version, - char out_xpub[static MAX_SERIALIZED_PUBKEY_LENGTH + 1], - serialized_extended_pubkey_t *out_pubkey) { +int get_extended_pubkey_at_path(const uint32_t bip32_path[], + uint8_t bip32_path_len, + uint32_t bip32_pubkey_version, + serialized_extended_pubkey_t *out_pubkey) { // find parent key's fingerprint and child number uint32_t parent_fingerprint = 0; uint32_t child_number = 0; @@ -312,43 +308,30 @@ int get_serialized_extended_pubkey_at_path(const uint32_t bip32_path[], // for the response, in order to save memory uint8_t parent_pubkey[33]; - crypto_get_compressed_pubkey_at_path(bip32_path, bip32_path_len - 1, parent_pubkey, NULL); + if (!crypto_get_compressed_pubkey_at_path(bip32_path, + bip32_path_len - 1, + parent_pubkey, + NULL)) { + return -1; + } parent_fingerprint = crypto_get_key_fingerprint(parent_pubkey); child_number = bip32_path[bip32_path_len - 1]; } - struct { - serialized_extended_pubkey_t ext_pubkey; - uint8_t checksum[4]; - } ext_pubkey_check; // extended pubkey and checksum - - serialized_extended_pubkey_t *ext_pubkey = &ext_pubkey_check.ext_pubkey; - - write_u32_be(ext_pubkey->version, 0, bip32_pubkey_version); - ext_pubkey->depth = bip32_path_len; - write_u32_be(ext_pubkey->parent_fingerprint, 0, parent_fingerprint); - write_u32_be(ext_pubkey->child_number, 0, child_number); - - crypto_get_compressed_pubkey_at_path(bip32_path, - bip32_path_len, - ext_pubkey->compressed_pubkey, - ext_pubkey->chain_code); - crypto_get_checksum((uint8_t *) ext_pubkey, 78, ext_pubkey_check.checksum); + write_u32_be(out_pubkey->version, 0, bip32_pubkey_version); + out_pubkey->depth = bip32_path_len; + write_u32_be(out_pubkey->parent_fingerprint, 0, parent_fingerprint); + write_u32_be(out_pubkey->child_number, 0, child_number); - if (out_pubkey != NULL) { - memcpy(out_pubkey, &ext_pubkey_check.ext_pubkey, sizeof(ext_pubkey_check.ext_pubkey)); + if (!crypto_get_compressed_pubkey_at_path(bip32_path, + bip32_path_len, + out_pubkey->compressed_pubkey, + out_pubkey->chain_code)) { + return -1; } - int serialized_pubkey_len = base58_encode((uint8_t *) &ext_pubkey_check, - 78 + 4, - out_xpub, - MAX_SERIALIZED_PUBKEY_LENGTH); - - if (serialized_pubkey_len > 0) { - out_xpub[serialized_pubkey_len] = '\0'; - } - return serialized_pubkey_len; + return 0; } int base58_encode_address(const uint8_t in[20], uint32_t version, char *out, size_t out_len) { diff --git a/src/crypto.h b/src/crypto.h index 89ac3adf..4765ca18 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -260,19 +260,15 @@ uint32_t crypto_get_master_key_fingerprint(); * Number of steps in the BIP32 derivation. * @param[in] bip32_pubkey_version * Version prefix to use for the pubkey. - * @param[out] out_xpub - * Pointer to the output buffer, which must be long enough to contain the result - * (including the terminating null character). * @param[out] out_pubkey - * If not NULL, pointer to a serialized_extended_pubkey_t. + * A pointer to a serialized_extended_pubkey_t. * - * @return the length of the output pubkey (not including the null character), or -1 on error. + * @return 0 on success, or -1 on error. */ -int get_serialized_extended_pubkey_at_path(const uint32_t bip32_path[], - uint8_t bip32_path_len, - uint32_t bip32_pubkey_version, - char out_xpub[static MAX_SERIALIZED_PUBKEY_LENGTH + 1], - serialized_extended_pubkey_t *out_pubkey); +int get_extended_pubkey_at_path(const uint32_t bip32_path[], + uint8_t bip32_path_len, + uint32_t bip32_pubkey_version, + serialized_extended_pubkey_t *out_pubkey); /** * Derives the level-1 symmetric key at the given label using SLIP-0021. diff --git a/src/handler/get_extended_pubkey.c b/src/handler/get_extended_pubkey.c index 5ab2475a..4a21c467 100644 --- a/src/handler/get_extended_pubkey.c +++ b/src/handler/get_extended_pubkey.c @@ -20,6 +20,7 @@ #include "boilerplate/io.h" #include "boilerplate/dispatcher.h" #include "boilerplate/sw.h" +#include "../common/base58.h" #include "../common/bip32.h" #include "../commands.h" #include "../constants.h" @@ -141,27 +142,41 @@ void handler_get_extended_pubkey(dispatcher_context_t *dc, uint8_t protocol_vers return; } - char serialized_pubkey_str[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; + serialized_extended_pubkey_check_t pubkey_check; + if (0 > get_extended_pubkey_at_path(bip32_path, + bip32_path_len, + BIP32_PUBKEY_VERSION, + &pubkey_check.serialized_extended_pubkey)) { + PRINTF("Failed getting bip32 pubkey\n"); + SEND_SW(dc, SW_BAD_STATE); + return; + } + + crypto_get_checksum((uint8_t *) &pubkey_check.serialized_extended_pubkey, + sizeof(pubkey_check.serialized_extended_pubkey), + pubkey_check.checksum); - int serialized_pubkey_len = get_serialized_extended_pubkey_at_path(bip32_path, - bip32_path_len, - BIP32_PUBKEY_VERSION, - serialized_pubkey_str, - NULL); - if (serialized_pubkey_len == -1) { + char pubkey_str[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; + int pubkey_str_len = base58_encode((uint8_t *) &pubkey_check, + sizeof(pubkey_check), + pubkey_str, + sizeof(pubkey_str)); + if (pubkey_str_len != 111 && pubkey_str_len != 112) { + PRINTF("Failed encoding base58 pubkey\n"); SEND_SW(dc, SW_BAD_STATE); return; } + pubkey_str[pubkey_str_len] = 0; char path_str[MAX_SERIALIZED_BIP32_PATH_LENGTH + 1] = "(Master key)"; if (bip32_path_len > 0) { bip32_path_format(bip32_path, bip32_path_len, path_str, sizeof(path_str)); } - if (display && !ui_display_pubkey(dc, path_str, !is_safe, serialized_pubkey_str)) { + if (display && !ui_display_pubkey(dc, path_str, !is_safe, pubkey_str)) { SEND_SW(dc, SW_DENY); return; } - SEND_RESPONSE(dc, serialized_pubkey_str, strlen(serialized_pubkey_str), SW_OK); + SEND_RESPONSE(dc, pubkey_str, pubkey_str_len, SW_OK); } diff --git a/src/handler/lib/policy.c b/src/handler/lib/policy.c index 5a77e919..40ce58a3 100644 --- a/src/handler/lib/policy.c +++ b/src/handler/lib/policy.c @@ -411,20 +411,7 @@ __attribute__((noinline, warn_unused_result)) static int get_extended_pubkey( return -1; } } - - // decode pubkey - serialized_extended_pubkey_check_t decoded_pubkey_check; - if (base58_decode(key_info.ext_pubkey, - strlen(key_info.ext_pubkey), - (uint8_t *) &decoded_pubkey_check, - sizeof(decoded_pubkey_check)) == -1) { - return -1; - } - // TODO: validate checksum - - memcpy(out, - &decoded_pubkey_check.serialized_extended_pubkey, - sizeof(decoded_pubkey_check.serialized_extended_pubkey)); + *out = key_info.ext_pubkey; return key_info.has_wildcard ? 1 : 0; } @@ -1376,19 +1363,16 @@ bool is_wallet_policy_standard(dispatcher_context_t *dispatcher_context, } // generate pubkey and check if it matches - char pubkey_derived[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; - int serialized_pubkey_len = - get_serialized_extended_pubkey_at_path(key_info.master_key_derivation, - key_info.master_key_derivation_len, - BIP32_PUBKEY_VERSION, - pubkey_derived, - NULL); - if (serialized_pubkey_len == -1) { + serialized_extended_pubkey_t derived_pubkey; + if (0 > get_extended_pubkey_at_path(key_info.master_key_derivation, + key_info.master_key_derivation_len, + BIP32_PUBKEY_VERSION, + &derived_pubkey)) { PRINTF("Failed to derive pubkey\n"); return false; } - if (strncmp(key_info.ext_pubkey, pubkey_derived, MAX_SERIALIZED_PUBKEY_LENGTH) != 0) { + if (memcmp(&key_info.ext_pubkey, &derived_pubkey, sizeof(derived_pubkey)) != 0) { return false; } @@ -1661,13 +1645,13 @@ int get_key_placeholder_by_index(const policy_node_t *policy, return -1; } -// Utility function to extract the i-th xpub from the keys information vector -static int get_xpub_from_merkle_tree(dispatcher_context_t *dispatcher_context, - int wallet_version, - const uint8_t keys_merkle_root[static 32], - uint32_t n_keys, - uint32_t index, - char out[static MAX_SERIALIZED_PUBKEY_LENGTH + 1]) { +// Utility function to extract and decode the i-th xpub from the keys information vector +static int get_pubkey_from_merkle_tree(dispatcher_context_t *dispatcher_context, + int wallet_version, + const uint8_t keys_merkle_root[static 32], + uint32_t n_keys, + uint32_t index, + serialized_extended_pubkey_t *out) { char key_info_str[MAX_POLICY_KEY_INFO_LEN]; int key_info_len = call_get_merkle_leaf_element(dispatcher_context, keys_merkle_root, @@ -1686,7 +1670,7 @@ static int get_xpub_from_merkle_tree(dispatcher_context_t *dispatcher_context, if (parse_policy_map_key_info(&key_info_buffer, &key_info, wallet_version) == -1) { return WITH_ERROR(-1, "Failed to parse key information"); } - strncpy(out, key_info.ext_pubkey, MAX_SERIALIZED_PUBKEY_LENGTH + 1); + *out = key_info.ext_pubkey; return 0; } @@ -1750,28 +1734,33 @@ int is_policy_sane(dispatcher_context_t *dispatcher_context, // check that all the xpubs are different for (unsigned int i = 0; i < n_keys - 1; i++) { // no point in running this for the last key - char xpub_i[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; - if (0 > get_xpub_from_merkle_tree(dispatcher_context, - wallet_version, - keys_merkle_root, - n_keys, - i, - xpub_i)) { + serialized_extended_pubkey_t pubkey_i; + if (0 > get_pubkey_from_merkle_tree(dispatcher_context, + wallet_version, + keys_merkle_root, + n_keys, + i, + &pubkey_i)) { return -1; } for (unsigned int j = i + 1; j < n_keys; j++) { - char xpub_j[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; - if (0 > get_xpub_from_merkle_tree(dispatcher_context, - wallet_version, - keys_merkle_root, - n_keys, - j, - xpub_j)) { + serialized_extended_pubkey_t pubkey_j; + if (0 > get_pubkey_from_merkle_tree(dispatcher_context, + wallet_version, + keys_merkle_root, + n_keys, + j, + &pubkey_j)) { return -1; } - if (strncmp(xpub_i, xpub_j, sizeof(xpub_i)) == 0) { + // We reject if any two xpubs have the same pubkey + // Conservatively, we only compare the compressed pubkey, rather than the whole xpub: + // there is no good reason for allowing two different xpubs with the same pubkey. + if (memcmp(pubkey_i.compressed_pubkey, + pubkey_j.compressed_pubkey, + sizeof(pubkey_i.compressed_pubkey)) == 0) { // duplicated pubkey return WITH_ERROR(-1, "Repeated pubkey in wallet policy"); } diff --git a/src/handler/register_wallet.c b/src/handler/register_wallet.c index 2efba65d..e033a0a8 100644 --- a/src/handler/register_wallet.c +++ b/src/handler/register_wallet.c @@ -170,20 +170,19 @@ void handler_register_wallet(dispatcher_context_t *dc, uint8_t protocol_version) if (key_info.has_key_origin && read_u32_be(key_info.master_key_fingerprint, 0) == master_key_fingerprint) { // we verify that we can actually generate the same pubkey - char pubkey_derived[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; + serialized_extended_pubkey_t pubkey_derived; int serialized_pubkey_len = - get_serialized_extended_pubkey_at_path(key_info.master_key_derivation, - key_info.master_key_derivation_len, - BIP32_PUBKEY_VERSION, - pubkey_derived, - NULL); + get_extended_pubkey_at_path(key_info.master_key_derivation, + key_info.master_key_derivation_len, + BIP32_PUBKEY_VERSION, + &pubkey_derived); if (serialized_pubkey_len == -1) { SEND_SW(dc, SW_BAD_STATE); ui_post_processing_confirm_wallet_registration(dc, false); return; } - if (strncmp(key_info.ext_pubkey, pubkey_derived, MAX_SERIALIZED_PUBKEY_LENGTH) == 0) { + if (memcmp(&key_info.ext_pubkey, &pubkey_derived, sizeof(pubkey_derived)) == 0) { is_key_internal = true; ++n_internal_keys; } diff --git a/src/handler/sign_psbt.c b/src/handler/sign_psbt.c index a89fccf6..ce793bc7 100644 --- a/src/handler/sign_psbt.c +++ b/src/handler/sign_psbt.c @@ -705,19 +705,17 @@ fill_placeholder_info_if_internal(dispatcher_context_t *dc, { // it could be a collision on the fingerprint; we verify that we can actually generate // the same pubkey - char pubkey_derived[MAX_SERIALIZED_PUBKEY_LENGTH + 1]; - int serialized_pubkey_len = - get_serialized_extended_pubkey_at_path(key_info.master_key_derivation, - key_info.master_key_derivation_len, - BIP32_PUBKEY_VERSION, - pubkey_derived, - &placeholder_info->pubkey); - if (serialized_pubkey_len == -1) { + if (0 > get_extended_pubkey_at_path(key_info.master_key_derivation, + key_info.master_key_derivation_len, + BIP32_PUBKEY_VERSION, + &placeholder_info->pubkey)) { SEND_SW(dc, SW_BAD_STATE); return false; } - if (strncmp(key_info.ext_pubkey, pubkey_derived, MAX_SERIALIZED_PUBKEY_LENGTH) != 0) { + if (memcmp(&key_info.ext_pubkey, + &placeholder_info->pubkey, + sizeof(placeholder_info->pubkey)) != 0) { return false; } diff --git a/unit-tests/CMakeLists.txt b/unit-tests/CMakeLists.txt index 059a7f8f..b67f4d35 100644 --- a/unit-tests/CMakeLists.txt +++ b/unit-tests/CMakeLists.txt @@ -1,18 +1,17 @@ cmake_minimum_required(VERSION 3.10) if(${CMAKE_VERSION} VERSION_LESS 3.10) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) endif() # project information project(unit_tests - VERSION 0.1 - DESCRIPTION "Unit tests for Ledger Nano application" - LANGUAGES C) - + VERSION 0.1 + DESCRIPTION "Unit tests for Ledger Nano application" + LANGUAGES C) # guard against bad build-type strings -if (NOT CMAKE_BUILD_TYPE) +if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug") endif() @@ -37,6 +36,7 @@ add_compile_definitions(TEST DEBUG=0 SKIP_FOR_CMOCKA PRINTF=printf) include_directories(../src) include_directories(mock_includes) +include_directories(libs) add_executable(test_apdu_parser test_apdu_parser.c) add_executable(test_base58 test_base58.c) @@ -49,8 +49,14 @@ add_executable(test_parser test_parser.c) add_executable(test_script test_script.c) add_executable(test_wallet test_wallet.c) add_executable(test_write test_write.c) -#add_executable(test_crypto test_crypto.c) +# add_executable(test_crypto test_crypto.c) + +# Mock libraries +add_library(crypto_mocks SHARED libs/crypto_mocks.c) +add_library(sha256 SHARED libs/sha-256.c) + +# App's libraries add_library(apdu_parser SHARED ../src/boilerplate/apdu_parser.c) add_library(base58 SHARED ../src/common/base58.c) add_library(bip32 SHARED ../src/common/bip32.c) @@ -63,8 +69,13 @@ add_library(script SHARED ../src/common/script.c) add_library(varint SHARED ../src/common/varint.c) add_library(wallet SHARED ../src/common/wallet.c) add_library(write SHARED ../src/common/write.c) -#add_library(crypto SHARED ../src/crypto.c) +# add_library(crypto SHARED ../src/crypto.c) + +# Mock libraries +target_link_libraries(crypto_mocks PUBLIC sha256) + +# App's libraries target_link_libraries(test_apdu_parser PUBLIC cmocka gcov apdu_parser) target_link_libraries(test_base58 PUBLIC cmocka gcov base58) target_link_libraries(test_bip32 PUBLIC cmocka gcov bip32 read) @@ -74,10 +85,10 @@ target_link_libraries(test_display_utils PUBLIC cmocka gcov display_utils) target_link_libraries(test_format PUBLIC cmocka gcov format) target_link_libraries(test_parser PUBLIC cmocka gcov parser buffer varint read write bip32) target_link_libraries(test_script PUBLIC cmocka gcov script buffer varint read write bip32) -target_link_libraries(test_wallet PUBLIC cmocka gcov wallet script buffer varint read write bip32) +target_link_libraries(test_wallet PUBLIC cmocka gcov wallet script buffer varint read write bip32 base58 crypto_mocks) target_link_libraries(test_write PUBLIC cmocka gcov write) -#target_link_libraries(test_crypto PUBLIC cmocka gcov crypto) +# target_link_libraries(test_crypto PUBLIC cmocka gcov crypto) add_test(test_apdu_parser test_apdu_parser) add_test(test_base58 test_base58) add_test(test_bip32 test_bip32) @@ -89,4 +100,5 @@ add_test(test_parser test_parser) add_test(test_script test_script) add_test(test_wallet test_wallet) add_test(test_write test_write) -#add_test(test_crypto test_crypto) + +# add_test(test_crypto test_crypto) diff --git a/unit-tests/libs/crypto_mocks.c b/unit-tests/libs/crypto_mocks.c new file mode 100644 index 00000000..79d09d06 --- /dev/null +++ b/unit-tests/libs/crypto_mocks.c @@ -0,0 +1,10 @@ +#include +#include "crypto_mocks.h" +#include "sha-256.h" + +void crypto_get_checksum(const uint8_t *in, uint16_t in_len, uint8_t out[static 4]) { + uint8_t buffer[32]; + calc_sha_256(buffer, in, in_len); + calc_sha_256(buffer, buffer, 32); + memmove(out, buffer, 4); +} diff --git a/unit-tests/libs/crypto_mocks.h b/unit-tests/libs/crypto_mocks.h new file mode 100644 index 00000000..436076ea --- /dev/null +++ b/unit-tests/libs/crypto_mocks.h @@ -0,0 +1,7 @@ +// We're currently unable to compile the app's crypto.c in unit tests. +// This library mocks the functions currently used in other modules that are part of +// the unit tests. + +#include + +void crypto_get_checksum(const uint8_t *in, uint16_t in_len, uint8_t out[static 4]); \ No newline at end of file diff --git a/unit-tests/libs/sha-256.c b/unit-tests/libs/sha-256.c new file mode 100644 index 00000000..3ae891b7 --- /dev/null +++ b/unit-tests/libs/sha-256.c @@ -0,0 +1,224 @@ +// from https://github.com/amosnier/sha-2/blob/0be5e1601b487b2aa6869e2fe12bd30ac2ca543c/sha-256.c + +#include "sha-256.h" + +#define TOTAL_LEN_LEN 8 + +/* + * Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here. + * When useful for clarification, portions of the pseudo-code are reproduced here too. + */ + +/* + * @brief Rotate a 32-bit value by a number of bits to the right. + * @param value The value to be rotated. + * @param count The number of bits to rotate by. + * @return The rotated value. + */ +static inline uint32_t right_rot(uint32_t value, unsigned int count) +{ + /* + * Defined behaviour in standard C for all count where 0 < count < 32, which is what we need here. + */ + return value >> count | value << (32 - count); +} + +/* + * @brief Update a hash value under calculation with a new chunk of data. + * @param h Pointer to the first hash item, of a total of eight. + * @param p Pointer to the chunk data, which has a standard length. + * + * @note This is the SHA-256 work horse. + */ +static inline void consume_chunk(uint32_t *h, const uint8_t *p) +{ + unsigned i, j; + uint32_t ah[8]; + + /* Initialize working variables to current hash value: */ + for (i = 0; i < 8; i++) + ah[i] = h[i]; + + /* + * The w-array is really w[64], but since we only need 16 of them at a time, we save stack by + * calculating 16 at a time. + * + * This optimization was not there initially and the rest of the comments about w[64] are kept in their + * initial state. + */ + + /* + * create a 64-entry message schedule array w[0..63] of 32-bit words (The initial values in w[0..63] + * don't matter, so many implementations zero them here) copy chunk into first 16 words w[0..15] of the + * message schedule array + */ + uint32_t w[16]; + + /* Compression function main loop: */ + for (i = 0; i < 4; i++) { + for (j = 0; j < 16; j++) { + if (i == 0) { + w[j] = + (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3]; + p += 4; + } else { + /* Extend the first 16 words into the remaining 48 words w[16..63] of the + * message schedule array: */ + const uint32_t s0 = right_rot(w[(j + 1) & 0xf], 7) ^ right_rot(w[(j + 1) & 0xf], 18) ^ + (w[(j + 1) & 0xf] >> 3); + const uint32_t s1 = right_rot(w[(j + 14) & 0xf], 17) ^ + right_rot(w[(j + 14) & 0xf], 19) ^ (w[(j + 14) & 0xf] >> 10); + w[j] = w[j] + s0 + w[(j + 9) & 0xf] + s1; + } + const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25); + const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]); + + /* + * Initialize array of round constants: + * (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311): + */ + static const uint32_t k[] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, + 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, + 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, + 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, + 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, + 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, + 0xc67178f2}; + + const uint32_t temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j]; + const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22); + const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]); + const uint32_t temp2 = s0 + maj; + + ah[7] = ah[6]; + ah[6] = ah[5]; + ah[5] = ah[4]; + ah[4] = ah[3] + temp1; + ah[3] = ah[2]; + ah[2] = ah[1]; + ah[1] = ah[0]; + ah[0] = temp1 + temp2; + } + } + + /* Add the compressed chunk to the current hash value: */ + for (i = 0; i < 8; i++) + h[i] += ah[i]; +} + +/* + * Public functions. See header file for documentation. + */ + +void sha_256_init(struct Sha_256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH]) +{ + sha_256->hash = hash; + sha_256->chunk_pos = sha_256->chunk; + sha_256->space_left = SIZE_OF_SHA_256_CHUNK; + sha_256->total_len = 0; + /* + * Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes + * 2..19): + */ + sha_256->h[0] = 0x6a09e667; + sha_256->h[1] = 0xbb67ae85; + sha_256->h[2] = 0x3c6ef372; + sha_256->h[3] = 0xa54ff53a; + sha_256->h[4] = 0x510e527f; + sha_256->h[5] = 0x9b05688c; + sha_256->h[6] = 0x1f83d9ab; + sha_256->h[7] = 0x5be0cd19; +} + +void sha_256_write(struct Sha_256 *sha_256, const void *data, size_t len) +{ + sha_256->total_len += len; + + const uint8_t *p = data; + + while (len > 0) { + /* + * If the input chunks have sizes that are multiples of the calculation chunk size, no copies are + * necessary. We operate directly on the input data instead. + */ + if (sha_256->space_left == SIZE_OF_SHA_256_CHUNK && len >= SIZE_OF_SHA_256_CHUNK) { + consume_chunk(sha_256->h, p); + len -= SIZE_OF_SHA_256_CHUNK; + p += SIZE_OF_SHA_256_CHUNK; + continue; + } + /* General case, no particular optimization. */ + const size_t consumed_len = len < sha_256->space_left ? len : sha_256->space_left; + memcpy(sha_256->chunk_pos, p, consumed_len); + sha_256->space_left -= consumed_len; + len -= consumed_len; + p += consumed_len; + if (sha_256->space_left == 0) { + consume_chunk(sha_256->h, sha_256->chunk); + sha_256->chunk_pos = sha_256->chunk; + sha_256->space_left = SIZE_OF_SHA_256_CHUNK; + } else { + sha_256->chunk_pos += consumed_len; + } + } +} + +uint8_t *sha_256_close(struct Sha_256 *sha_256) +{ + uint8_t *pos = sha_256->chunk_pos; + size_t space_left = sha_256->space_left; + uint32_t *const h = sha_256->h; + + /* + * The current chunk cannot be full. Otherwise, it would already have been consumed. I.e. there is space left for + * at least one byte. The next step in the calculation is to add a single one-bit to the data. + */ + *pos++ = 0x80; + --space_left; + + /* + * Now, the last step is to add the total data length at the end of the last chunk, and zero padding before + * that. But we do not necessarily have enough space left. If not, we pad the current chunk with zeroes, and add + * an extra chunk at the end. + */ + if (space_left < TOTAL_LEN_LEN) { + memset(pos, 0x00, space_left); + consume_chunk(h, sha_256->chunk); + pos = sha_256->chunk; + space_left = SIZE_OF_SHA_256_CHUNK; + } + const size_t left = space_left - TOTAL_LEN_LEN; + memset(pos, 0x00, left); + pos += left; + size_t len = sha_256->total_len; + pos[7] = (uint8_t)(len << 3); + len >>= 5; + int i; + for (i = 6; i >= 0; --i) { + pos[i] = (uint8_t)len; + len >>= 8; + } + consume_chunk(h, sha_256->chunk); + /* Produce the final hash value (big-endian): */ + int j; + uint8_t *const hash = sha_256->hash; + for (i = 0, j = 0; i < 8; i++) { + hash[j++] = (uint8_t)(h[i] >> 24); + hash[j++] = (uint8_t)(h[i] >> 16); + hash[j++] = (uint8_t)(h[i] >> 8); + hash[j++] = (uint8_t)h[i]; + } + return sha_256->hash; +} + +void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const void *input, size_t len) +{ + struct Sha_256 sha_256; + sha_256_init(&sha_256, hash); + sha_256_write(&sha_256, input, len); + (void)sha_256_close(&sha_256); +} \ No newline at end of file diff --git a/unit-tests/libs/sha-256.h b/unit-tests/libs/sha-256.h new file mode 100644 index 00000000..2c1c8047 --- /dev/null +++ b/unit-tests/libs/sha-256.h @@ -0,0 +1,105 @@ +// from https://github.com/amosnier/sha-2/blob/0be5e1601b487b2aa6869e2fe12bd30ac2ca543c/sha-256.h + +#ifndef SHA_256_H +#define SHA_256_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * @brief Size of the SHA-256 sum. This times eight is 256 bits. + */ +#define SIZE_OF_SHA_256_HASH 32 + +/* + * @brief Size of the chunks used for the calculations. + * + * @note This should mostly be ignored by the user, although when using the streaming API, it has an impact for + * performance. Add chunks whose size is a multiple of this, and you will avoid a lot of superfluous copying in RAM! + */ +#define SIZE_OF_SHA_256_CHUNK 64 + +/* + * @brief The opaque SHA-256 type, that should be instantiated when using the streaming API. + * + * @note Although the details are exposed here, in order to make instantiation easy, you should refrain from directly + * accessing the fields, as they may change in the future. + */ +struct Sha_256 { + uint8_t *hash; + uint8_t chunk[SIZE_OF_SHA_256_CHUNK]; + uint8_t *chunk_pos; + size_t space_left; + size_t total_len; + uint32_t h[8]; +}; + +/* + * @brief The simple SHA-256 calculation function. + * @param hash Hash array, where the result is delivered. + * @param input Pointer to the data the hash shall be calculated on. + * @param len Length of the input data, in byte. + * + * @note If all of the data you are calculating the hash value on is available in a contiguous buffer in memory, this is + * the function you should use. + * + * @note If either of the passed pointers is NULL, the results are unpredictable. + */ +void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const void *input, size_t len); + +/* + * @brief Initialize a SHA-256 streaming calculation. + * @param sha_256 A pointer to a SHA-256 structure. + * @param hash Hash array, where the result will be delivered. + * + * @note If all of the data you are calculating the hash value on is not available in a contiguous buffer in memory, this is + * where you should start. Instantiate a SHA-256 structure, for instance by simply declaring it locally, make your hash + * buffer available, and invoke this function. Once a SHA-256 hash has been calculated (see further below) a SHA-256 + * structure can be initialized again for the next calculation. + * + * @note If either of the passed pointers is NULL, the results are unpredictable. + */ +void sha_256_init(struct Sha_256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH]); + +/* + * @brief Stream more input data for an on-going SHA-256 calculation. + * @param sha_256 A pointer to a previously initialized SHA-256 structure. + * @param data Pointer to the data to be added to the calculation. + * @param len Length of the data to add, in byte. + * + * @note This function may be invoked an arbitrary number of times between initialization and closing, but the maximum + * data length is limited by the SHA-256 algorithm: the total number of bits (i.e. the total number of bytes times + * eight) must be representable by a 64-bit unsigned integer. While that is not a practical limitation, the results are + * unpredictable if that limit is exceeded. + * + * @note This function may be invoked on empty data (zero length), although that obviously will not add any data. + * + * @note If either of the passed pointers is NULL, the results are unpredictable. + */ +void sha_256_write(struct Sha_256 *sha_256, const void *data, size_t len); + +/* + * @brief Conclude a SHA-256 streaming calculation, making the hash value available. + * @param sha_256 A pointer to a previously initialized SHA-256 structure. + * @return Pointer to the hash array, where the result is delivered. + * + * @note After this function has been invoked, the result is available in the hash buffer that initially was provided. A + * pointer to the hash value is returned for convenience, but you should feel free to ignore it: it is simply a pointer + * to the first byte of your initially provided hash array. + * + * @note If the passed pointer is NULL, the results are unpredictable. + * + * @note Invoking this function for a calculation with no data (the writing function has never been invoked, or it only + * has been invoked with empty data) is legal. It will calculate the SHA-256 value of the empty string. + */ +uint8_t *sha_256_close(struct Sha_256 *sha_256); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file