From c38fa410a8207d830a921c409570baa25e952d12 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Wed, 27 Nov 2024 11:29:30 +0100 Subject: [PATCH 1/5] securechip: add initial Optiga implementation This commit implements the securechip.h interface for the Optiga Trust M V3, with an interface and configuration roughly corresponding to how we use the ATECC608 secure chip: - An KDF key that is internally generated and cannot be read and written from the host MCU - A KDF key that is generated on the host - A monotonic counter attached to the first KDF key which limits the maxmium number of uses of the key over the lifetime of the device - Attestation key that is internally generated and used to sign attestation challenges The factory setup configures the metadata of each object we use, setting the state to Operational. After this, metadata cannot be changed, and the access conditions apply as specified. Shielded communication encrypts the communication with the chip and is active and enforced through the medata access configs. It roughly corresponds to IO protection in the ATECC608. In the ATECC608, we additionally authorize each command with the authorization_key, another pre-shared secret. The Optiga offers the same functionality, but we don't use it to authorize all commands, as it is reundnant to using the shielded communication in terms of enabling the host MCU to execute commands. Co-Authored-By: Niklas --- src/CMakeLists.txt | 11 + src/atecc/atecc.c | 20 +- src/atecc/atecc.h | 88 +- src/memory/memory_shared.c | 11 +- src/memory/memory_shared.h | 2 +- src/optiga/optiga.c | 1307 +++++++++++++++++ src/optiga/optiga.h | 44 + src/optiga/pal/README.bitbox02.md | 1 + src/optiga/pal/README.md | 275 ++++ src/optiga/pal/pal.c | 52 + src/optiga/pal/pal_gpio.c | 66 + src/optiga/pal/pal_i2c.c | 313 ++++ src/optiga/pal/pal_ifx_i2c_config.c | 90 ++ src/optiga/pal/pal_logger.c | 95 ++ src/optiga/pal/pal_os_datastore.c | 82 ++ src/optiga/pal/pal_os_event.c | 110 ++ src/optiga/pal/pal_os_lock.c | 88 ++ src/optiga/pal/pal_os_memory.c | 23 + src/optiga/pal/pal_os_timer.c | 88 ++ src/rust/Cargo.lock | 1 + src/rust/bitbox02-rust-c/Cargo.toml | 3 +- src/rust/bitbox02-rust-c/src/der.rs | 79 + src/rust/bitbox02-rust-c/src/lib.rs | 3 + .../bitbox02-rust/src/hww/api/device_info.rs | 1 + src/securechip/securechip.c | 24 +- src/securechip/securechip.h | 38 +- test/simulator/framework/mock_securechip.c | 27 +- test/unit-test/framework/mock_securechip.c | 27 +- 28 files changed, 2808 insertions(+), 161 deletions(-) create mode 100644 src/optiga/optiga.c create mode 100644 src/optiga/optiga.h create mode 100644 src/optiga/pal/README.bitbox02.md create mode 100644 src/optiga/pal/README.md create mode 100644 src/optiga/pal/pal.c create mode 100644 src/optiga/pal/pal_gpio.c create mode 100644 src/optiga/pal/pal_i2c.c create mode 100644 src/optiga/pal/pal_ifx_i2c_config.c create mode 100644 src/optiga/pal/pal_logger.c create mode 100644 src/optiga/pal/pal_os_datastore.c create mode 100644 src/optiga/pal/pal_os_event.c create mode 100644 src/optiga/pal/pal_os_lock.c create mode 100644 src/optiga/pal/pal_os_memory.c create mode 100644 src/optiga/pal/pal_os_timer.c create mode 100644 src/rust/bitbox02-rust-c/src/der.rs diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 825de7993..63733cae1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -148,6 +148,17 @@ set(PLATFORM-BITBOX02-SOURCES ${PLATFORM-BITBOX02-SOURCES} PARENT_SCOPE) set(SECURECHIP-SOURCES ${CMAKE_SOURCE_DIR}/src/atecc/atecc.c ${CMAKE_SOURCE_DIR}/src/securechip/securechip.c + ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal.c + ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_gpio.c + ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_i2c.c + ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_ifx_i2c_config.c + ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_logger.c + ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_os_datastore.c + ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_os_event.c + ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_os_lock.c + ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_os_timer.c + ${CMAKE_SOURCE_DIR}/src/optiga/pal/pal_os_memory.c + ${CMAKE_SOURCE_DIR}/src/optiga/optiga.c ) set(SECURECHIP-SOURCES ${SECURECHIP-SOURCES} PARENT_SCOPE) diff --git a/src/atecc/atecc.c b/src/atecc/atecc.c index 7ffef9591..db026f039 100644 --- a/src/atecc/atecc.c +++ b/src/atecc/atecc.c @@ -234,7 +234,7 @@ static ATCA_STATUS _lock_slot(atecc_slot_t slot) static ATCA_STATUS _factory_setup(void) { if (_interface_functions == NULL) { - return (ATCA_STATUS)ATECC_ERR_IFS; + return (ATCA_STATUS)SC_ERR_IFS; } bool is_config_locked = false; ATCA_STATUS result = atcab_is_locked(LOCK_ZONE_CONFIG, &is_config_locked); @@ -336,14 +336,14 @@ static int _verify_config(void) return result; } if (!is_locked) { - return ATECC_ERR_ZONE_UNLOCKED_CONFIG; + return SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG; } result = atcab_is_locked(LOCK_ZONE_DATA, &is_locked); if (result != ATCA_SUCCESS) { return result; } if (!is_locked) { - return ATECC_ERR_ZONE_UNLOCKED_DATA; + return SC_ATECC_ERR_ZONE_UNLOCKED_DATA; } bool same_config = false; @@ -352,7 +352,7 @@ static int _verify_config(void) return result; } if (!same_config) { - return ATECC_ERR_CONFIG_MISMATCH; + return SC_ERR_CONFIG_MISMATCH; } // Check that the slots are individually locked. @@ -361,21 +361,21 @@ static int _verify_config(void) return result; } if (!is_locked) { - return ATECC_ERR_SLOT_UNLOCKED_IO; + return SC_ATECC_ERR_SLOT_UNLOCKED_IO; } result = atcab_is_slot_locked(ATECC_SLOT_AUTHKEY, &is_locked); if (result != ATCA_SUCCESS) { return result; } if (!is_locked) { - return ATECC_ERR_SLOT_UNLOCKED_AUTH; + return SC_ATECC_ERR_SLOT_UNLOCKED_AUTH; } result = atcab_is_slot_locked(ATECC_SLOT_ENCRYPTION_KEY, &is_locked); if (result != ATCA_SUCCESS) { return result; } if (!is_locked) { - return ATECC_ERR_SLOT_UNLOCKED_ENC; + return SC_ATECC_ERR_SLOT_UNLOCKED_ENC; } return ATCA_SUCCESS; } @@ -383,7 +383,7 @@ static int _verify_config(void) int atecc_setup(const securechip_interface_functions_t* ifs) { if (ifs == NULL) { - return ATECC_ERR_IFS; + return SC_ERR_IFS; } _interface_functions = ifs; ATCA_STATUS result = atcab_init(&cfg); @@ -527,10 +527,10 @@ bool atecc_update_keys(void) static int _atecc_kdf(atecc_slot_t slot, const uint8_t* msg, size_t len, uint8_t* kdf_out) { if (len > 127 || (slot != ATECC_SLOT_ROLLKEY && slot != ATECC_SLOT_KDF)) { - return ATECC_ERR_INVALID_ARGS; + return SC_ERR_INVALID_ARGS; } if (msg == kdf_out) { - return ATECC_ERR_INVALID_ARGS; + return SC_ERR_INVALID_ARGS; } ATCA_STATUS result = _authorize_key(); diff --git a/src/atecc/atecc.h b/src/atecc/atecc.h index 45e52fe1c..7566ff048 100644 --- a/src/atecc/atecc.h +++ b/src/atecc/atecc.h @@ -15,6 +15,9 @@ #ifndef _ATECC_H_ #define _ATECC_H_ +/* ATECC implementation of the secure chip functions. */ +/* See securechip.h for the docstrings of the individual functions. */ + #include "compiler_util.h" #include "securechip/securechip.h" #include @@ -22,105 +25,20 @@ #include #include -typedef enum { - ATECC_ERR_ZONE_UNLOCKED_CONFIG = -1, - ATECC_ERR_ZONE_UNLOCKED_DATA = -2, - ATECC_ERR_CONFIG_MISMATCH = -3, - ATECC_ERR_SLOT_UNLOCKED_IO = -4, - ATECC_ERR_SLOT_UNLOCKED_AUTH = -5, - ATECC_ERR_SLOT_UNLOCKED_ENC = -6, - ATECC_ERR_IFS = -7, - ATECC_ERR_INVALID_ARGS = -8, -} atecc_error_t; - -/** - * Initializes the cryptoauthlib communication, by providing a custom i2c chip - * communication interface/bridge to cryptoauthlib. On first call, the chip - * is configured and locked. - * @param[in] ifs Interface functions. - * @return values of `atecc_error_t` if negative, values of `ATCA_STATUS` if positive, 0 on - * success. - */ USE_RESULT int atecc_setup(const securechip_interface_functions_t* ifs); - -/** - * Updates the two KDF keys (rollkey and kdf key). The previous keys are lost - * and cannot be restored. Calling this function does not increment the - * monotonic counter Counter0. - * @return true on success. - */ USE_RESULT bool atecc_update_keys(void); - -/** - * Perform HMAC using the key in KDF slot with the input msg. - * @param[in] msg Use this msg as input - * @param[in] len Must be <= 127. - * @param[out] kdf_out Must have size 32. Result of the kdf will be stored here. - * Cannot be the same as `msg`. - * @return values of `atecc_error_t` if negative, values of `ATCA_STATUS` if positive, 0 on - */ USE_RESULT int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out); - -/** - * Perform KDF using the key in rollkey slot with the input msg. - * Calling this function increments the - * monotonic counter Counter0. - * @param[in] msg Use this msg as input - * @param[in] len Must be <= 127. - * @param[out] kdf_out Must have size 32. Result of the kdf will be stored here. - * Cannot be the same as `msg`. - * @return values of `securechip_error_t` if negative, values of `ATCA_STATUS` if positive, 0 on - */ USE_RESULT int atecc_kdf_rollkey(const uint8_t* msg, size_t len, uint8_t* kdf_out); - -/** - * Generates a new attestation device key and outputs the public key. - * @param[out] pubkey_out - */ USE_RESULT bool atecc_gen_attestation_key(uint8_t* pubkey_out); - -/** - * @param[in] msg 32 byte message to sign. - * @param[out] signature_out must be 64 bytes. R/S P256 signature. - */ USE_RESULT bool atecc_attestation_sign(const uint8_t* challenge, uint8_t* signature_out); - -/** - * Retrieves the number of remaining possible counter increments (max value - Counter0). - * The counter is increment when using `atecc_kdf()` (see its docstring). - * @param[out] remaining_out current value of the monotonic counter. - * @return false if there was a communication error with the SC. - */ USE_RESULT bool atecc_monotonic_increments_remaining(uint32_t* remaining_out); - -/** - * @param[out] rand_out must be 32 bytes. - */ USE_RESULT bool atecc_random(uint8_t* rand_out); - #if APP_U2F == 1 || FACTORYSETUP == 1 -/** - * Set the u2f counter to `counter`. Should only be used for initialization. - * @param[in] counter Value to set counter to - * @return True if success - */ USE_RESULT bool atecc_u2f_counter_set(uint32_t counter); #endif - #if APP_U2F == 1 -/** - * Monotonically increase the U2F counter and return the current value - * @param[out] counter Next counter value - * @return True if success - */ USE_RESULT bool atecc_u2f_counter_inc(uint32_t* counter); #endif - -/** - * Output the atecc model. - * @param[out] model_out atecc model - * @return True if success - */ USE_RESULT bool atecc_model(securechip_model_t* model_out); #endif diff --git a/src/memory/memory_shared.c b/src/memory/memory_shared.c index 9488183e5..71e3874c1 100644 --- a/src/memory/memory_shared.c +++ b/src/memory/memory_shared.c @@ -54,5 +54,14 @@ uint8_t memory_get_screen_type(void) uint8_t memory_get_securechip_type(void) { - return MEMORY_SECURECHIP_TYPE_ATECC; + chunk_shared_t chunk = {0}; + memory_read_shared_bootdata(&chunk); + uint8_t securechip_type = chunk.fields.securechip_type; + util_zero(&chunk, sizeof(chunk)); + switch (securechip_type) { + case MEMORY_SECURECHIP_TYPE_OPTIGA: + return securechip_type; + default: + return MEMORY_SECURECHIP_TYPE_ATECC; + } } diff --git a/src/memory/memory_shared.h b/src/memory/memory_shared.h index 5e8f8f985..c1a91e6bb 100644 --- a/src/memory/memory_shared.h +++ b/src/memory/memory_shared.h @@ -75,8 +75,8 @@ typedef union { uint8_t auto_enter; uint8_t upside_down; uint8_t screen_type; + uint8_t securechip_type; // Following are used by firmware only - uint8_t reserved[1]; uint8_t io_protection_key_split[32]; uint8_t authorization_key_split[32]; uint8_t encryption_key_split[32]; diff --git a/src/optiga/optiga.c b/src/optiga/optiga.c new file mode 100644 index 000000000..c12440129 --- /dev/null +++ b/src/optiga/optiga.c @@ -0,0 +1,1307 @@ +// Copyright 2024 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "optiga.h" + +#include "pal/pal_i2c.h" +#include "pal/pal_os_datastore.h" +#include "pal/pal_os_timer.h" + +#include +#include +#include +#include +#include +#include +#include + +// Set this to 1 for a more convenience during development. +// Factory setup will be performed in the normal firmware, which makes it easier to tinker with the +// chip setup and config. +// Must be 0 for the production firmware releases. +#define FACTORY_DURING_PROD 0 + +// Number of times the first kdf slot can be used. +// The maxmimum does not seem to be specified, so we use something a little below the endurance +// indication of 600000 updates. See Solution Reference Manual Figure 32. +#define MONOTONIC_COUNTER_MAX_USE (590000) + +// The Data Object IDs we use. + +// Stores a shared secret used for a shielded connection. Is is used to encrypt +// communications. Read/write disabled. +#define OID_PLATFORM_BINDING 0xE140 +// CMAC key slot, read/write prohibited. Used to perform KDF using CMAC. Key is regenerated at +// factory setup and with each device reset. Monotonic counter `OID_COUNTER` attached. +#define OID_AES_SYMKEY 0xE200 +// HMAC key slot, read prohibited, write allowed. 32 random bytes are written to it at factory setup +// and with each device reset. +#define OID_HMAC 0xF1D0 +// Arbitrary data object, stores up to 140 bytes. Used to store the U2F counter. +#define OID_ARBITRARY_DATA 0xF1D1 +// ECC slot used for creating the device attestation key and signing with it. Read/write +// disabled. The Key is internally generated at factory setup and used to sign the device +// attestation host challenge. +#define OID_ATTESTATION 0xE0F1 +// Monotonic counter, initialized at 0 and attached to `OID_AES_SYMKEY` - every CMAC generation +// increments the counter. When the threshold `MONOTONIC_COUNTER_MAX_USE` is reached, further CMAC +// computations return an error. +#define OID_COUNTER 0xE120 + +// See Solution Reference Manual Table 79 "Data structure arbitrary data object". +#define ARBITRARY_DATA_OBJECT_TYPE_3_MAX_SIZE 140 + +// Struct stored in the arbitrary data object. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpacked" +#pragma GCC diagnostic ignored "-Wattributes" +typedef union { + struct __attribute__((__packed__)) { + uint32_t u2f_counter; + } fields; + uint8_t bytes[ARBITRARY_DATA_OBJECT_TYPE_3_MAX_SIZE]; +} arbitrary_data_t; +#pragma GCC diagnostic pop + +#define ABORT_IF_NULL(ptr) \ + do { \ + if ((ptr) == 0) { \ + AbortAutoenter("Not initalized"); \ + } \ + } while (0) + +static optiga_util_t* _util; +static optiga_crypt_t* _crypt; + +static const securechip_interface_functions_t* _ifs = NULL; + +// Values of life cycle states. +// See Table "Life Cycle Status": +// https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#link05d4c12a_5c94_4a05_a05d_102c53684d3d +#define LCSO_STATE_CREATION (0x01) +#define LCSO_STATE_OPERATIONAL (0x07) + +#define TAG_LCSO 0xC0 + +// Set the object LcsO flag to Operational. After this, the metadata cannot be changed anymore. +// During development, set this to `LCSO_STATE_CREATION`. +#define FINAL_LCSO_STATE LCSO_STATE_OPERATIONAL + +static const uint8_t _platform_binding_metadata[] = { + // Metadata tag in the data object + 0x20, + // Number of bytes that follow + 17, + // Set LcsO. Refer to macro to see the value or some more notes. + 0xC0, + 0x01, + FINAL_LCSO_STATE, + // Change/Write access. This allows updating the binding secret when LcsO < op. + 0xD0, + 0x03, + 0xE1, + 0xFC, + LCSO_STATE_OPERATIONAL, + // Disallow reads + 0xD1, + 0x01, + 0xFF, + // Allow execute + 0xD3, + 0x01, + 0x00, + // Data object type set to PTFBIND (Platform binding secret) + 0xE8, + 0x01, + 0x22, +}; + +static const uint8_t _aes_symkey_metadata[] = { + // Metadata tag in the data object + 0x20, + // Number of bytes that follow + 21, + // Set LcsO. Refer to macro to see the value or some more notes. + 0xC0, + 0x01, + FINAL_LCSO_STATE, + // Set key usage to "Enc". + // See Table "Metadata associated with data and key objects" -> 0xE1 + // https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#link8051b344_ff66_4d6b_bcfd_d21bb87d05d4 + 0xE1, + 0x01, + 0x02, + // Allow writes - GenSymkey requires this to be able to write. + // However, writes from the host are forbidden. + // Table "Common key objects with TAG’s and AC‘s" - 0xE200: + // https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#linkf056a0f7_a31f_41c3_b1d9_f270a4fe0378 + // "The GetDataObject, and SetDataObject commands are not allowed for the data part of the key + // object even if the metadata state the access rights differently" + 0xD0, + 0x01, + 0x00, + // Disallow reads + 0xD1, + 0x01, + 0xFF, + // Attach execution to counter at 0xE120 and enforce shielded connection. + // See Table 'Access Condition Identifier and Operators" -> "Conf": + // https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#linkc15dfea4_2cc2_46ae_a53b_1e6ea9487f34 + 0xD3, + 0x07, + 0x40, + 0xE1, + 0x20, + // && + 0xFD, + // Enforce shielded connection. According to 4.4.1.7 "EncryptSym" shielded protection is + // enforced anyway, but better to be explicit. + 0x20, + 0xE1, + 0x40, +}; + +static const uint8_t _hmac_metadata[] = { + // Metadata tag in the data object + 0x20, + // Number of bytes that follow + 19, + // Set LcsO. Refer to macro to see the value or some more notes. + 0xC0, + 0x01, + FINAL_LCSO_STATE, + // Data object type: PRESSEC + // See table "Data Object Types": + // https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#linkaf9aa284_1397_4161_8761_8c44fbbfa69d + 0xE8, + 0x01, + 0x21, + // Allow writes, enforce shielded connection. + 0xD0, + 0x03, + 0x20, + 0xE1, + 0x40, + // Disallow reads + 0xD1, + 0x01, + 0xFF, + // Execute: enforce shielded connection. + // See Table 'Access Condition Identifier and Operators" -> "Conf": + // https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#linkc15dfea4_2cc2_46ae_a53b_1e6ea9487f34 + 0xD3, + 0x03, + 0x20, + 0xE1, + 0x40, +}; + +static const uint8_t _arbitrary_data_metadata[] = { + // Metadata tag in the data object + 0x20, + // Number of bytes that follow + 19, + // Set LcsO. Refer to macro to see the value or some more notes. + 0xC0, + 0x01, + FINAL_LCSO_STATE, + // Data object type: BSTR. + // See table "Data Object Types": + // https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#linkaf9aa284_1397_4161_8761_8c44fbbfa69d + 0xE8, + 0x01, + 0x00, + // Allow writes, enforce shielded connection. + 0xD0, + 0x03, + 0x20, + 0xE1, + 0x40, + // Allow reads, enforce shielded connection. + 0xD1, + 0x03, + 0x20, + 0xE1, + 0x40, + // Disallow exe + 0xD3, + 0x01, + 0xFF, +}; + +static const uint8_t _attestation_metadata[] = { + // Metadata tag in the data object + 0x20, + // Number of bytes that follow + 17, + // Set LcsO. Refer to macro to see the value or some more notes. + 0xC0, + 0x01, + FINAL_LCSO_STATE, + // Key usage associated with key container: Sign + // See table "Metadata associated with data and key objects": + // https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#link8051b344_ff66_4d6b_bcfd_d21bb87d05d4 + 0xE1, + 0x01, + 0x10, + // Allow writes - GenKeyPair requires this to be able to write. + // However, writes from the host are forbidden. + // Table "Common key objects with TAG’s and AC‘s" - 0xE0F1: + // https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#linkf056a0f7_a31f_41c3_b1d9_f270a4fe0378 + // "The GetDataObject, and SetDataObject commands are not allowed for the data part of the key + // object even if the metadata state the access rights differently" + 0xD0, + 0x01, + 0x00, + // Disallow reads + 0xD1, + 0x01, + 0xFF, + // Execute: enforce shielded connection. + // See Table 'Access Condition Identifier and Operators" -> "Conf": + // https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#linkc15dfea4_2cc2_46ae_a53b_1e6ea9487f34 + 0xD3, + 0x03, + 0x20, + 0xE1, + 0x40, +}; + +static const uint8_t _counter_metadata[] = { + // Metadata tag in the data object + 0x20, + // Number of bytes that follow + 16, + // Set LcsO. Refer to macro to see the value or some more notes. + 0xC0, + 0x01, + FINAL_LCSO_STATE, + // Change/Write access. This allows updating the counter when LcsO < op. + 0xD0, + 0x03, + 0xE1, + 0xFC, + LCSO_STATE_OPERATIONAL, + // Allow reads, enforce shielded connection. + 0xD1, + 0x03, + 0x20, + 0xE1, + 0x40, + // Allow exe + 0xD3, + 0x01, + 0x00, +}; +// +// Sync wrappers around optiga util/crypt functions +// + +// The OPTIGA library is asynchronous and will schedule a callback when the command is done. The +// callback will set this shared variable to the result of the command. +static volatile optiga_lib_status_t _optiga_lib_status; + +static void _optiga_lib_callback(void* callback_ctx, optiga_lib_status_t event) +{ + (void)callback_ctx; + _optiga_lib_status = event; +} + +// Helper that is used in the main thread to busy wait for the callback to update the shared +// variable. +// It first checks the return status of the command, then busy waits, and then checks the +// asynchronous return status. +// Will return from caller if command failed. +// `return_status` will be updated with the actual return status +// Return statuses are documented in optiga_lib_return_codes.h +#define _WAIT(return_status, optiga_lib_status) \ + do { \ + if ((return_status) != OPTIGA_UTIL_SUCCESS) { \ + return (return_status); \ + } \ + while (OPTIGA_LIB_BUSY == (optiga_lib_status)) { \ + } \ + if (OPTIGA_LIB_SUCCESS != (optiga_lib_status)) { \ + return (optiga_lib_status); \ + } \ + (return_status) = (optiga_lib_status); \ + } while (0) + +static optiga_lib_status_t _optiga_util_read_data_sync( + optiga_util_t* me, + uint16_t optiga_oid, + uint16_t offset, + uint8_t* buffer, + uint16_t* length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_util_read_data(me, optiga_oid, offset, buffer, length); + _WAIT(res, _optiga_lib_status); + return res; +} + +static optiga_lib_status_t _optiga_util_write_data_sync( + optiga_util_t* me, + uint16_t optiga_oid, + uint8_t write_type, + uint16_t offset, + const uint8_t* buffer, + uint16_t length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = + optiga_util_write_data(me, optiga_oid, write_type, offset, buffer, length); + _WAIT(res, _optiga_lib_status); + return res; +} + +static optiga_lib_status_t _optiga_util_read_metadata_sync( + optiga_util_t* me, + uint16_t optiga_oid, + uint8_t* buffer, + uint16_t* length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_util_read_metadata(me, optiga_oid, buffer, length); + _WAIT(res, _optiga_lib_status); + return res; +} + +#if FACTORYSETUP == 1 || FACTORY_DURING_PROD == 1 +static optiga_lib_status_t _optiga_util_write_metadata_sync( + optiga_util_t* me, + uint16_t optiga_oid, + const uint8_t* buffer, + uint8_t length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_util_write_metadata(me, optiga_oid, buffer, length); + _WAIT(res, _optiga_lib_status); + return res; +} +#endif + +static optiga_lib_status_t _optiga_util_open_application_sync( + optiga_util_t* me, + bool_t perform_restore) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_util_open_application(me, perform_restore); + _WAIT(res, _optiga_lib_status); + return res; +} + +#if FACTORYSETUP == 1 || FACTORY_DURING_PROD == 1 +static optiga_lib_status_t _optiga_util_close_application_sync( + optiga_util_t* me, + bool_t perform_hibernate) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_util_close_application(me, perform_hibernate); + _WAIT(res, _optiga_lib_status); + return res; +} +#endif + +static optiga_lib_status_t _optiga_crypt_hmac_sync( + optiga_crypt_t* me, + optiga_hmac_type_t type, + uint16_t secret, + const uint8_t* input_data, + uint32_t input_data_length, + uint8_t* mac, + uint32_t* mac_length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = + optiga_crypt_hmac(me, type, secret, input_data, input_data_length, mac, mac_length); + _WAIT(res, _optiga_lib_status); + return res; +} + +static optiga_lib_status_t _optiga_crypt_ecc_generate_keypair_sync( + optiga_crypt_t* me, + optiga_ecc_curve_t curve_id, + uint8_t key_usage, + bool_t export_private_key, + void* private_key, + uint8_t* public_key, + uint16_t* public_key_length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_crypt_ecc_generate_keypair( + me, curve_id, key_usage, export_private_key, private_key, public_key, public_key_length); + _WAIT(res, _optiga_lib_status); + return res; +} + +static optiga_lib_status_t _optiga_crypt_ecdsa_sign_sync( + optiga_crypt_t* me, + const uint8_t* digest, + uint8_t digest_length, + optiga_key_id_t private_key, + uint8_t* signature, + uint16_t* signature_length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_crypt_ecdsa_sign( + me, digest, digest_length, private_key, signature, signature_length); + _WAIT(res, _optiga_lib_status); + return res; +} + +static optiga_lib_status_t _optiga_crypt_symmetric_encrypt_sync( + optiga_crypt_t* me, + optiga_symmetric_encryption_mode_t encryption_mode, + optiga_key_id_t symmetric_key_oid, + const uint8_t* plain_data, + uint32_t plain_data_length, + const uint8_t* iv, + uint16_t iv_length, + const uint8_t* associated_data, + uint16_t associated_data_length, + uint8_t* encrypted_data, + uint32_t* encrypted_data_length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_crypt_symmetric_encrypt( + me, + encryption_mode, + symmetric_key_oid, + plain_data, + plain_data_length, + iv, + iv_length, + associated_data, + associated_data_length, + encrypted_data, + encrypted_data_length); + _WAIT(res, _optiga_lib_status); + return res; +} + +static optiga_lib_status_t _optiga_crypt_random_sync( + optiga_crypt_t* me, + optiga_rng_type_t rng_type, + uint8_t* random_data, + uint16_t random_data_length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_crypt_random(me, rng_type, random_data, random_data_length); + _WAIT(res, _optiga_lib_status); + return res; +} + +static optiga_lib_status_t _optiga_crypt_symmetric_generate_key_sync( + optiga_crypt_t* me, + optiga_symmetric_key_type_t key_type, + uint8_t key_usage, + bool_t export_symmetric_key, + void* symmetric_key) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_crypt_symmetric_generate_key( + me, key_type, key_usage, export_symmetric_key, symmetric_key); + _WAIT(res, _optiga_lib_status); + return res; +} + +#if APP_U2F == 1 || FACTORYSETUP == 1 +static bool _read_arbitrary_data(arbitrary_data_t* data_out) +{ + memset(data_out->bytes, 0x00, sizeof(data_out->bytes)); + uint16_t len = sizeof(data_out->bytes); + optiga_lib_status_t res = + _optiga_util_read_data_sync(_util, OID_ARBITRARY_DATA, 0, data_out->bytes, &len); + if (res != OPTIGA_UTIL_SUCCESS) { + util_log("could not read arbitrary data: %x", res); + return false; + } + if (len != sizeof(data_out->bytes)) { + util_log( + "arbitrary data: expected to read size %d, but read %d. Data read: %s", + (int)sizeof(data_out->bytes), + (int)len, + util_dbg_hex(data_out->bytes, len)); + return false; + } + return true; +} +#endif + +#if APP_U2F == 1 || FACTORYSETUP == 1 || FACTORY_DURING_PROD == 1 +static int _write_arbitrary_data(const arbitrary_data_t* data) +{ + optiga_lib_status_t res = _optiga_util_write_data_sync( + _util, + OID_ARBITRARY_DATA, + OPTIGA_UTIL_ERASE_AND_WRITE, + 0, + &data->bytes[0], + sizeof(data->bytes)); + if (res != OPTIGA_LIB_SUCCESS) { + util_log("could not write arbitrary %x", res); + return res; + } + return 0; +} +#endif + +// In a metadata object (0x20 ...), +// extract tag data for a specific tag. +// Returns false if the metadata is invalid or the tag is not present. +static bool _read_metadata_tag( + const uint8_t* metadata, + size_t metadata_len, + uint8_t tag, + uint8_t* data_out, + size_t* data_len_out) +{ + if (metadata_len < 2 || metadata[0] != 0x20) { + // Metadata does not start with the expected tag or is too short + return false; + } + + uint8_t metadata_size = metadata[1]; + if ((size_t)(metadata_size + 2) > metadata_len) { + // Malformed metadata: declared size exceeds buffer length + return false; + } + + const uint8_t* tlv = &metadata[2]; + size_t tlv_len = metadata_size; + + size_t offset = 0; + + while (offset + 2 <= tlv_len) { // Ensure at least are available + uint8_t current_tag = tlv[offset]; + uint8_t size = tlv[offset + 1]; + + if (offset + 2 + size > tlv_len) { + // Malformed TLV: size exceeds remaining length + return false; + } + + if (current_tag == tag) { + // Found the tag, copy data to output + if (data_out) { + memcpy(data_out, &tlv[offset + 2], size); + } + if (data_len_out) { + *data_len_out = size; + } + return true; + } + + // Move to the next TLV + offset += 2 + size; + } + + // Tag not found + return false; +} + +#if FACTORYSETUP == 1 || FACTORY_DURING_PROD == 1 +// Read the LcsO status from a metadata object. Returns false if the metadata is invalid or LcsO is +// not present. +static bool _read_lcso(const uint8_t* metadata, size_t metadata_len, uint8_t* lcso_out) +{ + uint8_t tag_data[100] = {0}; + size_t tag_data_len = 0; + if (!_read_metadata_tag(metadata, metadata_len, TAG_LCSO, tag_data, &tag_data_len)) { + return false; + } + if (tag_data_len != 1) { + return false; + } + *lcso_out = tag_data[0]; + return true; +} + +static int _read_lcso_of_object(uint16_t optiga_oid, uint8_t* lcso_out, bool unprotected) +{ + uint8_t metadata[1000] = {0}; + uint16_t metadata_size = sizeof(metadata); + + if (unprotected) { + // Is reset to full protection after the metadata read command. + OPTIGA_UTIL_SET_COMMS_PROTECTION_LEVEL(_util, OPTIGA_COMMS_NO_PROTECTION); + } + optiga_lib_status_t res = + _optiga_util_read_metadata_sync(_util, optiga_oid, metadata, &metadata_size); + if (res != OPTIGA_LIB_SUCCESS) { + util_log("fail: read binding secret metadata: %x", res); + return res; + } + if (!_read_lcso(metadata, metadata_size, lcso_out)) { + return SC_OPTIGA_ERR_UNEXPECTED_METADATA; + } + return 0; +} + +// Setup shielded communication. +// Writes the shared secret to the chip 0xE140 data object and sets the metadata. +// See solution reference manual 2.3.4 "Use case: Pair OPTIGA™ Trust M with host (pre-shared secret +// based)". +static int _setup_shielded_communication(void) +{ + const uint16_t oid = OID_PLATFORM_BINDING; + uint8_t lcso = 0; + optiga_lib_status_t res = _read_lcso_of_object(oid, &lcso, true); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + if (lcso >= LCSO_STATE_OPERATIONAL) { + util_log("shared secret already setup"); + return 0; + } + + util_log("setting up shielded communication"); + + uint8_t platform_binding_secret[32]; + uint16_t platform_binding_secret_size = sizeof(platform_binding_secret); + + pal_status_t pal_res = pal_os_datastore_read( + OPTIGA_PLATFORM_BINDING_SHARED_SECRET_ID, + platform_binding_secret, + &platform_binding_secret_size); + if (PAL_STATUS_SUCCESS != pal_res || + platform_binding_secret_size != sizeof(platform_binding_secret)) { + util_log("failed datastore read: %x", pal_res); + return SC_OPTIGA_ERR_PAL; + } + + // We write the binding secret before updating the metadata, as the metadata update locks the + // slot. Shielded communication is disabled as it is not set up yet and not required for + // updating the platform binding object. + OPTIGA_UTIL_SET_COMMS_PROTECTION_LEVEL(_util, OPTIGA_COMMS_NO_PROTECTION); + res = _optiga_util_write_data_sync( + _util, + oid, + OPTIGA_UTIL_ERASE_AND_WRITE, + 0, + platform_binding_secret, + sizeof(platform_binding_secret)); + if (res != OPTIGA_LIB_SUCCESS) { + util_log("fail: write binding secret to chip: %x", res); + return res; + } + + // Shielded communication is disabled as it is not set up yet and not required for updating the + // platform binding object. + OPTIGA_UTIL_SET_COMMS_PROTECTION_LEVEL(_util, OPTIGA_COMMS_NO_PROTECTION); + res = _optiga_util_write_metadata_sync( + _util, oid, _platform_binding_metadata, sizeof(_platform_binding_metadata)); + if (res != OPTIGA_LIB_SUCCESS) { + util_log("fail: write metadata of platform binding: %x", res); + return res; + } + + return 0; +} + +static int _configure_object_aes_symkey(void) +{ + const uint16_t oid = OID_AES_SYMKEY; + + uint8_t lcso = 0; + optiga_lib_status_t res = _read_lcso_of_object(oid, &lcso, false); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + if (lcso >= LCSO_STATE_OPERATIONAL) { + util_log("_configure_object_aes_symkey: already setup"); + return 0; + } + util_log("_configure_object_aes_symkey: setting up"); + return _optiga_util_write_metadata_sync( + _util, oid, _aes_symkey_metadata, sizeof(_aes_symkey_metadata)); +} + +static int _configure_object_hmac(void) +{ + const uint16_t oid = OID_HMAC; + + uint8_t lcso = 0; + optiga_lib_status_t res = _read_lcso_of_object(oid, &lcso, false); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + if (lcso >= LCSO_STATE_OPERATIONAL) { + util_log("_configure_object_hmac: already setup"); + return 0; + } + util_log("_configure_object_hmac: setting up"); + return _optiga_util_write_metadata_sync(_util, oid, _hmac_metadata, sizeof(_hmac_metadata)); +} + +static int _configure_object_arbitrary_data(void) +{ + const uint16_t oid = OID_ARBITRARY_DATA; + + uint8_t lcso = 0; + optiga_lib_status_t res = _read_lcso_of_object(oid, &lcso, false); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + if (lcso >= LCSO_STATE_OPERATIONAL) { + util_log("_configure_object_arbitrary_data: already setup"); + return 0; + } + util_log("_configure_object_arbitrary_data: setting up"); + + res = _optiga_util_write_metadata_sync( + _util, oid, _arbitrary_data_metadata, sizeof(_arbitrary_data_metadata)); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + + // Initialize arbitrary data, all zeroes. + const arbitrary_data_t arbitrary_data = {0}; + int write_res = _write_arbitrary_data(&arbitrary_data); + if (write_res) { + util_log("could not initialize arbitrary data"); + return write_res; + } + return 0; +} + +static int _configure_object_counter(void) +{ + const uint16_t oid = OID_COUNTER; + + uint8_t lcso = 0; + optiga_lib_status_t res = _read_lcso_of_object(oid, &lcso, false); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + if (lcso >= LCSO_STATE_OPERATIONAL) { + util_log("_configure_object_counter: already setup"); + return 0; + } + util_log("_configure_object_counter: setting up"); + + // Configure the monotonic counter. + // Table "Common data structures" -> "Counter": + // https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#link24b48059_db81_40f5_8b65_7afca4918ab1 + // Bytes 0-3 are the initial counter value, set to 0. + // Bytes 4-7 are the threshold. + // Ints are encoded as uint32 big endian. + uint8_t counter_buf[8] = {0}; + optiga_common_set_uint32(&counter_buf[4], MONOTONIC_COUNTER_MAX_USE); + res = _optiga_util_write_data_sync( + _util, oid, OPTIGA_UTIL_ERASE_AND_WRITE, 0, counter_buf, sizeof(counter_buf)); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + + return _optiga_util_write_metadata_sync( + _util, oid, _counter_metadata, sizeof(_counter_metadata)); +} + +static int _configure_object_attestation(void) +{ + const uint16_t oid = OID_ATTESTATION; + + uint8_t lcso = 0; + optiga_lib_status_t res = _read_lcso_of_object(oid, &lcso, false); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + if (lcso >= LCSO_STATE_OPERATIONAL) { + util_log("_configure_attestation: already setup"); + return 0; + } + util_log("_configure_attestation: setting up"); + + return _optiga_util_write_metadata_sync( + _util, oid, _attestation_metadata, sizeof(_attestation_metadata)); +} + +static int _factory_write_config(void) +{ + int res_shielded = _setup_shielded_communication(); + if (res_shielded) { + return res_shielded; + } + + optiga_lib_status_t res; + + res = _configure_object_aes_symkey(); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + + res = _configure_object_hmac(); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + + res = _configure_object_arbitrary_data(); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + + res = _configure_object_counter(); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + + res = _configure_object_attestation(); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + + util_log("write config OK"); + + return 0; +} + +static int _factory_setup(void) +{ + optiga_lib_status_t res; + + _util = optiga_util_create(OPTIGA_INSTANCE_ID_0, _optiga_lib_callback, NULL); + if (NULL == _util) { + util_log("couldn't create optiga util"); + return SC_OPTIGA_ERR_CREATE; + } + + _crypt = optiga_crypt_create(OPTIGA_INSTANCE_ID_0, _optiga_lib_callback, NULL); + if (NULL == _crypt) { + util_log("couldn't create optiga crypt"); + return SC_OPTIGA_ERR_CREATE; + } + + OPTIGA_UTIL_SET_COMMS_PROTOCOL_VERSION(_util, OPTIGA_COMMS_PROTOCOL_VERSION_PRE_SHARED_SECRET); + OPTIGA_CRYPT_SET_COMMS_PROTOCOL_VERSION( + _crypt, OPTIGA_COMMS_PROTOCOL_VERSION_PRE_SHARED_SECRET); + + OPTIGA_UTIL_SET_COMMS_PROTECTION_LEVEL(_util, OPTIGA_COMMS_NO_PROTECTION); + res = _optiga_util_open_application_sync(_util, 0); + if (res != OPTIGA_LIB_SUCCESS) { + util_log("failed to open util application: %x", res); + return res; + } + + res = _factory_write_config(); + if (res) { + return res; + } + + res = _optiga_util_close_application_sync(_util, 0); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + + if (NULL != _crypt) { + optiga_crypt_destroy(_crypt); + _crypt = NULL; + } + + if (NULL != _util) { + optiga_util_destroy(_util); + _util = NULL; + } + + return 0; +} +#endif // FACTORYSETUP == 1 || FACTORY_DURING_PROD == 1 + +static int _verify_metadata( + uint16_t oid, + const uint8_t* expected_metadata, + size_t expected_metadata_len, + const uint8_t* check_tags, + size_t check_tags_len) +{ + uint8_t actual_metadata[1000] = {0}; + uint16_t actual_metadata_len = sizeof(actual_metadata); + + optiga_lib_status_t res = + _optiga_util_read_metadata_sync(_util, oid, actual_metadata, &actual_metadata_len); + if (res != OPTIGA_LIB_SUCCESS) { + util_log("fail: read binding secret metadata: %x", res); + return res; + } + + uint8_t expected_tag_data[100] = {0}; + size_t expected_tag_data_len = 0; + uint8_t actual_tag_data[100] = {0}; + size_t actual_tag_data_len = 0; + + for (size_t i = 0; i < check_tags_len; i++) { + uint8_t tag = check_tags[i]; + if (!_read_metadata_tag( + expected_metadata, + expected_metadata_len, + tag, + expected_tag_data, + &expected_tag_data_len)) { + return SC_OPTIGA_ERR_UNEXPECTED_METADATA; + } + if (!_read_metadata_tag( + actual_metadata, actual_metadata_len, tag, actual_tag_data, &actual_tag_data_len)) { + return SC_OPTIGA_ERR_UNEXPECTED_METADATA; + } + if (actual_tag_data_len != expected_tag_data_len || + !MEMEQ(actual_tag_data, expected_tag_data, actual_tag_data_len)) { + return SC_ERR_CONFIG_MISMATCH; + } + } + return 0; +} + +static int _verify_config(void) +{ + int res; + + OPTIGA_UTIL_SET_COMMS_PROTOCOL_VERSION(_util, OPTIGA_COMMS_PROTOCOL_VERSION_PRE_SHARED_SECRET); + OPTIGA_CRYPT_SET_COMMS_PROTOCOL_VERSION( + _crypt, OPTIGA_COMMS_PROTOCOL_VERSION_PRE_SHARED_SECRET); + + // Verify shielded connection is active. + if (_crypt->protection_level != OPTIGA_COMMS_FULL_PROTECTION) { + util_log("crypt protection level expected to be FULL"); + return SC_ERR_CONFIG_MISMATCH; + } + if (_util->protection_level != OPTIGA_COMMS_FULL_PROTECTION) { + util_log("util protection level expected to be FULL"); + return SC_ERR_CONFIG_MISMATCH; + } + + res = _optiga_util_open_application_sync(_util, 0); + if (res) { + return res; + } + + // Verify metadata tags are setup as expected. + + { + const uint8_t check_tags[] = {0xC0, 0xD0, 0xD1, 0xD3, 0xE8}; + res = _verify_metadata( + OID_PLATFORM_BINDING, + _platform_binding_metadata, + sizeof(_platform_binding_metadata), + check_tags, + sizeof(check_tags)); + if (res) { + return res; + } + } + { + const uint8_t check_tags[] = {0xC0, 0xE1, 0xD0, 0xD1, 0xD3}; + res = _verify_metadata( + OID_AES_SYMKEY, + _aes_symkey_metadata, + sizeof(_aes_symkey_metadata), + check_tags, + sizeof(check_tags)); + if (res) { + return res; + } + } + { + const uint8_t check_tags[] = {0xC0, 0xE8, 0xD0, 0xD1, 0xD3}; + res = _verify_metadata( + OID_HMAC, _hmac_metadata, sizeof(_hmac_metadata), check_tags, sizeof(check_tags)); + if (res) { + return res; + } + } + { + const uint8_t check_tags[] = {0xC0, 0xE8, 0xD0, 0xD1, 0xD3}; + res = _verify_metadata( + OID_ARBITRARY_DATA, + _arbitrary_data_metadata, + sizeof(_arbitrary_data_metadata), + check_tags, + sizeof(check_tags)); + if (res) { + return res; + } + } + { + const uint8_t check_tags[] = {0xC0, 0xE1, 0xD0, 0xD1, 0xD3}; + res = _verify_metadata( + OID_ATTESTATION, + _attestation_metadata, + sizeof(_attestation_metadata), + check_tags, + sizeof(check_tags)); + if (res) { + return res; + } + } + { + const uint8_t check_tags[] = {0xC0, 0xD0, 0xD1, 0xD3}; + res = _verify_metadata( + OID_COUNTER, + _counter_metadata, + sizeof(_counter_metadata), + check_tags, + sizeof(check_tags)); + if (res) { + return res; + } + } + return 0; +} + +int optiga_setup(const securechip_interface_functions_t* ifs) +{ + if (ifs == NULL) { + return SC_ERR_IFS; + } + _ifs = ifs; + + util_log("optiga_setup"); + + // A timer is used to provide the OPTIGA library with the ability to schedule work on the main + // event loop + pal_timer_init(); + +#if FACTORYSETUP == 1 || FACTORY_DURING_PROD == 1 + int res = _factory_setup(); + if (res) { + util_log("factory setup failed"); + return res; + } +#endif + + _util = optiga_util_create(OPTIGA_INSTANCE_ID_0, _optiga_lib_callback, NULL); + if (NULL == _util) { + return SC_OPTIGA_ERR_CREATE; + } + + _crypt = optiga_crypt_create(OPTIGA_INSTANCE_ID_0, _optiga_lib_callback, NULL); + if (NULL == _crypt) { + return SC_OPTIGA_ERR_CREATE; + } + + return _verify_config(); +} + +bool optiga_update_keys(void) +{ + ABORT_IF_NULL(_ifs); + ABORT_IF_NULL(_ifs->random_32_bytes); + + uint8_t new_key[32] = {0}; + _ifs->random_32_bytes(new_key); + + optiga_lib_status_t res = _optiga_util_write_data_sync( + _util, OID_HMAC, OPTIGA_UTIL_ERASE_AND_WRITE, 0x00, new_key, sizeof(new_key)); + if (res != OPTIGA_UTIL_SUCCESS) { + util_log("failed updating the hmac key: %x", res); + return false; + } + + optiga_key_id_t keyid = OPTIGA_KEY_ID_SECRET_BASED; + res = _optiga_crypt_symmetric_generate_key_sync( + _crypt, OPTIGA_SYMMETRIC_AES_256, OPTIGA_KEY_USAGE_ENCRYPTION, false, &keyid); + if (res != OPTIGA_UTIL_SUCCESS) { + util_log("failed updating the sym key: %x", res); + return false; + } + + return true; +} + +int optiga_kdf_external(const uint8_t* msg, size_t len, uint8_t* mac_out) +{ + if (len != 32) { + return SC_ERR_INVALID_ARGS; + } + + ABORT_IF_NULL(_crypt); + optiga_lib_status_t res; + // The equivalient of python `mac_out = hmac.new(key, msg[:len], hashlib.sha256).digest()` + + uint32_t mac_out_len = 32; + + res = _optiga_crypt_hmac_sync( + _crypt, OPTIGA_HMAC_SHA_256, OID_HMAC, msg, len, mac_out, &mac_out_len); + if (res != OPTIGA_LIB_SUCCESS) { + util_log("kdf fail err=%x", res); + return res; + } + if (mac_out_len != 32) { + return SC_OPTIGA_ERR_UNEXPECTED_LEN; + } + + return 0; +} + +int optiga_kdf_internal(const uint8_t* msg, size_t len, uint8_t* kdf_out) +{ + if (len != 32) { + return SC_ERR_INVALID_ARGS; + } + ABORT_IF_NULL(_crypt); + optiga_lib_status_t res; + + uint8_t mac_out[16] = {0}; + uint32_t mac_out_len = sizeof(mac_out); + + res = _optiga_crypt_symmetric_encrypt_sync( + _crypt, + OPTIGA_SYMMETRIC_CMAC, + OID_AES_SYMKEY, + msg, + len, + NULL, + 0, + NULL, + 0, + mac_out, + &mac_out_len); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + if (mac_out_len != sizeof(mac_out)) { + return SC_OPTIGA_ERR_UNEXPECTED_LEN; + } + rust_sha256(mac_out, mac_out_len, kdf_out); + return 0; +} + +bool optiga_gen_attestation_key(uint8_t* pubkey_out) +{ + ABORT_IF_NULL(_crypt); + optiga_key_id_t slot = OPTIGA_KEY_ID_E0F1; + uint8_t pubkey_der[68] = {0}; + uint16_t pubkey_der_size = sizeof(pubkey_der); + optiga_lib_status_t res = _optiga_crypt_ecc_generate_keypair_sync( + _crypt, + OPTIGA_ECC_CURVE_NIST_P_256, + OPTIGA_KEY_USAGE_SIGN, + false, + (void*)&slot, + pubkey_der, + &pubkey_der_size); + if (res != OPTIGA_CRYPT_SUCCESS) { + util_log("gen keypair failed: %x", res); + return false; + } + // Parse DER "BIT STRING", see Solution Reference Manual 6.2.2, + // example for ECC NIST-P256. + // The 64 byte X/Y values are the last 64 bytes. + if (pubkey_der_size != 68 || !MEMEQ(pubkey_der, "\x03\x42\x00\x04", 4)) { + return false; + } + memcpy(pubkey_out, pubkey_der + 4, 64); + return true; +} + +bool optiga_attestation_sign(const uint8_t* challenge, uint8_t* signature_out) +{ + ABORT_IF_NULL(_crypt); + uint8_t sig_der[70] = {0}; + uint16_t sig_der_size = sizeof(sig_der); + optiga_lib_status_t res = _optiga_crypt_ecdsa_sign_sync( + _crypt, challenge, 32, OPTIGA_KEY_ID_E0F1, sig_der, &sig_der_size); + if (res != OPTIGA_CRYPT_SUCCESS) { + util_log("sign failed: %x", res); + return false; + } + // Parse signature, see Solution Reference Manual 6.2.2, + // example for ECC NIST-P256 signature. + // The R/S components are + return rust_der_parse_optiga_signature( + rust_util_bytes(sig_der, sig_der_size), rust_util_bytes_mut(signature_out, 64)); +} + +bool optiga_monotonic_increments_remaining(uint32_t* remaining_out) +{ + uint8_t buf[4] = {0}; + uint16_t size = sizeof(buf); + optiga_lib_status_t res = _optiga_util_read_data_sync(_util, OID_COUNTER, 0, buf, &size); + if (res != OPTIGA_LIB_SUCCESS) { + return false; + } + + uint32_t counter = optiga_common_get_uint32(buf); + if (counter > MONOTONIC_COUNTER_MAX_USE) { + Abort("optiga monotonic counter larget than max"); + } + *remaining_out = MONOTONIC_COUNTER_MAX_USE - counter; + return true; +} + +// rand_out must be 32 bytes +bool optiga_random(uint8_t* rand_out) +{ + optiga_lib_status_t res = _optiga_crypt_random_sync(_crypt, OPTIGA_RNG_TYPE_TRNG, rand_out, 32); + if (res != OPTIGA_CRYPT_SUCCESS) { + util_log("optiga_random failed: %x", res); + return false; + } + return true; +} + +#if APP_U2F == 1 || FACTORYSETUP == 1 +bool optiga_u2f_counter_set(uint32_t counter) +{ + arbitrary_data_t data = {0}; + if (!_read_arbitrary_data(&data)) { + return false; + } + data.fields.u2f_counter = counter; + return _write_arbitrary_data(&data) == 0; +} +#endif + +#if APP_U2F == 1 +bool optiga_u2f_counter_inc(uint32_t* counter) +{ + arbitrary_data_t data = {0}; + if (!_read_arbitrary_data(&data)) { + return false; + } + data.fields.u2f_counter += 1; + *counter = data.fields.u2f_counter; + return _write_arbitrary_data(&data); +} +#endif + +bool optiga_model(securechip_model_t* model_out) +{ + *model_out = OPTIGA_TRUST_M_V3; + return true; +} diff --git a/src/optiga/optiga.h b/src/optiga/optiga.h new file mode 100644 index 000000000..19545ccb2 --- /dev/null +++ b/src/optiga/optiga.h @@ -0,0 +1,44 @@ +// Copyright 2024 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _OPTIGA_H_ +#define _OPTIGA_H_ + +/* Optiga Trust M implementation of the secure chip functions. */ +/* See securechip.h for the docstrings of the individual functions. */ + +#include "compiler_util.h" +#include "securechip/securechip.h" +#include +#include +#include +#include + +USE_RESULT int optiga_setup(const securechip_interface_functions_t* ifs); +USE_RESULT bool optiga_update_keys(void); +USE_RESULT int optiga_kdf_external(const uint8_t* msg, size_t len, uint8_t* mac_out); +USE_RESULT int optiga_kdf_internal(const uint8_t* msg, size_t len, uint8_t* mac_out); +USE_RESULT bool optiga_gen_attestation_key(uint8_t* pubkey_out); +USE_RESULT bool optiga_attestation_sign(const uint8_t* challenge, uint8_t* signature_out); +USE_RESULT bool optiga_monotonic_increments_remaining(uint32_t* remaining_out); +USE_RESULT bool optiga_random(uint8_t* rand_out); +#if APP_U2F == 1 || FACTORYSETUP == 1 +USE_RESULT bool optiga_u2f_counter_set(uint32_t counter); +#endif +#if APP_U2F == 1 +USE_RESULT bool optiga_u2f_counter_inc(uint32_t* counter); +#endif +USE_RESULT bool optiga_model(securechip_model_t* model_out); + +#endif // _OPTIGA_H_ diff --git a/src/optiga/pal/README.bitbox02.md b/src/optiga/pal/README.bitbox02.md new file mode 100644 index 000000000..3797bb8e6 --- /dev/null +++ b/src/optiga/pal/README.bitbox02.md @@ -0,0 +1 @@ +copied from extras/pal/NEW_PAL_TEMPLATE/ diff --git a/src/optiga/pal/README.md b/src/optiga/pal/README.md new file mode 100644 index 000000000..f86613d2a --- /dev/null +++ b/src/optiga/pal/README.md @@ -0,0 +1,275 @@ +# Porting Guide + +The implementation of Platform Abstraction Layer (PAL) needs to be updated in order to migrate to a new +target platform. + +The PAL reference code for the XMC4800 IoT connectivity kit is provided as part of package which can be used. +The implementation can be found in “/pal/xmc4800” and the header files are available in +“/optiga/include” with the required APIs used by upper layers. The header files are platform +agnostic and would not require any changes. The low level drivers used by PAL for XMC4800 are configured and +generated using DAVE™. + +## Communication with OPTIGA™ Trust M + +The hardware/platform resource configuration with respect to I2C master and GPIOs (Vdd and Reset) are to be +updated in pal_ifx_i2c_config.c. These configurations are used by the IFX I2C implementation to communicate +with OPTIGA™ Trust M. + +### Update I2C master platform specific context[e.g. (void)&i2c_master_0] + +```c +/* +* \brief PAL I2C configuration for OPTIGA +*/ +pal_i2c_t optiga_pal_i2c_context_0 = +{ + /// Pointer to I2C master platform specific context + (void)&i2c_master_0, + /// Slave address + 0x30, + /// Upper layer context + NULL, + /// Callback event handler + NULL +}; +``` + +### Update platform specific context for GPIOs (Vdd and Reset) [e.g. (void)&pin_3_4] +```c +/* +* \brief Vdd pin configuration for OPTIGA +*/ +pal_gpio_t optiga_vdd_0 = +{ + // Platform specific GPIO context for the pin used to toggle Vdd + (void*)&vdd_pin +}; + +/** +* \brief Reset pin configuration for OPTIGA +*/ + pal_gpio_t optiga_reset_0 = +{ + // Platform specific GPIO context for the pin used to toggle Reset + (void*)&reset_pin +}; +``` + +### Update PAL I2C APIs [pal_i2c.c] to communicate with OPTIGA™ Trust M + +The `pal_i2c` is expected to provide the APIs for I2C driver initialization, de-initialization, read, write and set +bitrate kind of operations + +1. `pal_i2c_init` +2. `pal_i2c_deinit` +3. `pal_i2c_read` +4. `pal_i2c_write` +5. `pal_i2c_set_bitrate` + +A few target platforms, the I2C master driver initialization (`pal_i2c_init`) is done during the platform start up. In +such an environment, there is no need to implement pal_i2c_init and `pal_i2c_deinit` functions. Otherwise, +these (`pal_i2c_init` & `pal_i2c_deinit`) functions must be implemented as per the upper layer expectations based +on the need. The details of these expectations are available in the [Host library API documentation (chm)](https://github.com/Infineon/optiga-trust-m/blob/master/documents/OPTIGA_Trust_M_V1_Host_Library_Documentation.chm). + +The reference implementation of PAL I2C based on XMC4800 IoT connectivity kit does not need to have the +platform I2C driver initialization explicitly done as part of pal_i2c_init as it is taken care by the DAVE™ library +initialization. Hence pal_i2c_init & pal_i2c_deinit are not implemented. + +In addition to the above specified APIs, the PAL I2C must handle the events from the low level I2C driver and +invoke the upper layer handlers registered with PAL I2C context for the respective transaction as shown in the +below example. + +```c +//I2C driver callback function when the transmit is completed successfully +void i2c_master_end_of_transmit_callback(void) +{ + invoke_upper_layer_callback(gp_pal_i2c_current_ctx, (uint8_t)PAL_I2C_EVENT_TX_SUCCESS); +} +``` + +In above example the I2C driver callback, when transmission is successful invokes the handler to inform the +result. + +### Update PAL GPIO [pal_gpio.c] to power on and reset the OPTIGA™ Trust M + +1. `pal_gpio_set_high` +2. `pal_gpio_set_low` + +### Update PAL Timer [pal_os_timer.c] to enable timer + +1. `pal_os_timer_get_time_in_milliseconds` +2. `pal_os_timer_delay_in_milliseconds` + +### Update Event management for the asynchronous interactions for IFX I2C [pal_os_event.c] + +1. `pal_os_event_register_callback_oneshot` +2. `pal_os_event_trigger_registered_callback` + +The `pal_os_event_register_callback_oneshot` function is expected to register the handler and context +provided as part of input parameters and triggers the timer for the requested time. The `p_pal_os_event` is an +event instance created using `pal_os_event_create`. + +```c +void pal_os_event_register_callback_oneshot(pal_os_event_t * p_pal_os_event, + register_callback callback, + void* callback_args, + uint32_t time_us) +{ + p_pal_os_event->callback_registered = callback; + p_pal_os_event->callback_ctx = callback_args; + //lint --e{534} suppress "Return value is not required to be checked" + TIMER_SetTimeInterval(&scheduler_timer, (time_us*100)); + TIMER_Start(&scheduler_timer); +} +``` + +The handler registered must be invoked once the timer has elapsed as shown in +`pal_os_event_trigger_registered_callback`. The `pal_os_event_trigger_registered_callback` is to be +registered with event timer interrupt to get trigerred when the timer expires. The `pal_os_event_0` is the +instance in the `pal_os_event` used store the registered callback and context. + +```c +void pal_os_event_trigger_registered_callback(void) +{ + register_callback callback; + + TIMER_ClearEvent(&scheduler_timer); + //lint --e{534} suppress "Return value is not required to be checked" + TIMER_Stop(&scheduler_timer); + TIMER_Clear(&scheduler_timer); + + if (pal_os_event_0.callback_registered) + { + Callback = pal_os_event_0.callback_registered; + callback ((void*)callback_ctx); + } +} +``` + +## Initalisation hints + +This is a code sample demonstrating initialization routine for the security chip, as well as a hibernation example +```c +#include "optiga/optiga_util.h" +#include "optiga/pal/pal_os_event.h" +#include "optiga/pal/pal.h" +#include "optiga/pal/pal_os_timer.h" +/** + * Callback when optiga_util_xxxx operation is completed asynchronously + */ +static volatile optiga_lib_status_t optiga_lib_status; +static void optiga_util_callback(void * context, optiga_lib_status_t return_status) +{ + optiga_lib_status = return_status; +} + +int32_t main(void) +{ + uint8_t return_value = 0; + + optiga_lib_status_t return_status; + + optiga_util_t * me_util; + + do + { + //Create an instance of optiga_util to open the application on OPTIGA. + me_util = optiga_util_create(0, optiga_util_callback, NULL); + + /** + * Open the application on OPTIGA which is a precondition to perform any other operations + * using optiga_util_open_application + */ + optiga_lib_status = OPTIGA_LIB_BUSY; + return_status = optiga_util_open_application(me_util, 0); + + if (OPTIGA_LIB_SUCCESS != return_status) + { + break; + } + while (optiga_lib_status == OPTIGA_LIB_BUSY) + { + //Wait until the optiga_util_open_application is completed + } + if (OPTIGA_LIB_SUCCESS != optiga_lib_status) + { + //optiga util open application failed + break; + } + /** + * Hibernate the application on OPTIGA + * using optiga_util_close_application + */ + optiga_lib_status = OPTIGA_LIB_BUSY; + return_status = optiga_util_close_application(me_util, 1); + + if (OPTIGA_LIB_SUCCESS != return_status) + { + break; + } + + while (optiga_lib_status == OPTIGA_LIB_BUSY) + { + //Wait until the optiga_util_close_application is completed + } + + if (OPTIGA_LIB_SUCCESS != optiga_lib_status) + { + //optiga util close application failed + break; + } + /** + * Restore the application on OPTIGA + * using optiga_util_open_application + */ + optiga_lib_status = OPTIGA_LIB_BUSY; + return_status = optiga_util_open_application(me_util, 1); + + if (OPTIGA_LIB_SUCCESS != return_status) + { + break; + } + while (OPTIGA_LIB_BUSY == optiga_lib_status) + { + //Wait until the optiga_util_open_application is completed + } + if (OPTIGA_LIB_SUCCESS != optiga_lib_status) + { + //optiga util open application failed + break; + } + + /* + Paste your code is here + */ + + /** + * Close the application on OPTIGA after all the operations are executed + * using optiga_util_close_application + */ + optiga_lib_status = OPTIGA_LIB_BUSY; + return_status = optiga_util_close_application(me_util, 0); + + if (OPTIGA_LIB_SUCCESS != return_status) + { + break; + } + + while (optiga_lib_status == OPTIGA_LIB_BUSY) + { + //Wait until the optiga_util_close_application is completed + } + + if (OPTIGA_LIB_SUCCESS != optiga_lib_status) + { + //optiga util close application failed + break; + } + + // destroy util and crypt instances + optiga_util_destroy(me_util); + }while (FALSE); + + return return_value; +} +``` diff --git a/src/optiga/pal/pal.c b/src/optiga/pal/pal.c new file mode 100644 index 000000000..851557386 --- /dev/null +++ b/src/optiga/pal/pal.c @@ -0,0 +1,52 @@ +/** + * \copyright + * MIT License + * + * Copyright (c) 2019 Infineon Technologies AG + * + * 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 + * + * \endcopyright + * + * \author Infineon Technologies AG + * + * \file pal.c + * + * \brief This file implements the platform abstraction layer APIs. + * + * \ingroup grPAL + * + * @{ + */ + +#include "pal.h" + +pal_status_t pal_init(void) +{ + return PAL_STATUS_SUCCESS; +} + +pal_status_t pal_deinit(void) +{ + return PAL_STATUS_SUCCESS; +} + +/** + * @} + */ diff --git a/src/optiga/pal/pal_gpio.c b/src/optiga/pal/pal_gpio.c new file mode 100644 index 000000000..4fa6bfb2f --- /dev/null +++ b/src/optiga/pal/pal_gpio.c @@ -0,0 +1,66 @@ +/** + * \copyright + * MIT License + * + * Copyright (c) 2019 Infineon Technologies AG + * + * 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 + * + * \endcopyright + * + * \author Infineon Technologies AG + * + * \file pal_gpio.c + * + * \brief This file implements the platform abstraction layer APIs for GPIO. + * + * \ingroup grPAL + * + * @{ + */ + +#include "pal_gpio.h" + +pal_status_t pal_gpio_init(const pal_gpio_t* p_gpio_context) +{ + (void)p_gpio_context; + return PAL_STATUS_SUCCESS; +} + +pal_status_t pal_gpio_deinit(const pal_gpio_t* p_gpio_context) +{ + (void)p_gpio_context; + return PAL_STATUS_SUCCESS; +} + +void pal_gpio_set_high(const pal_gpio_t* p_gpio_context) +{ + (void)p_gpio_context; + // We don't support setting vdd or rst pins +} + +void pal_gpio_set_low(const pal_gpio_t* p_gpio_context) +{ + (void)p_gpio_context; + // We don't support setting vdd or rst pins +} + +/** + * @} + */ diff --git a/src/optiga/pal/pal_i2c.c b/src/optiga/pal/pal_i2c.c new file mode 100644 index 000000000..f78cfd0cd --- /dev/null +++ b/src/optiga/pal/pal_i2c.c @@ -0,0 +1,313 @@ +/** + * \copyright + * MIT License + * + * Copyright (c) 2019 Infineon Technologies AG + * + * 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 + * + * \endcopyright + * + * \author Infineon Technologies AG + * + * \file pal_i2c.c + * + * \brief This file implements the platform abstraction layer(pal) APIs for I2C. + * + * \ingroup grPAL + * + * @{ + */ + +#include "pal_i2c.h" +#include "hal_delay.h" +#include "hal_i2c_m_sync.h" +#include "pal_os_timer.h" +#include "util.h" +extern struct i2c_m_sync_desc I2C_0; + +#define PAL_I2C_MASTER_MAX_BITRATE (400U) + +static volatile uint32_t g_entry_count = 0; +static const pal_i2c_t* gp_pal_i2c_current_ctx; + +static pal_status_t pal_i2c_acquire(const void* p_i2c_context) +{ + // To avoid compiler errors/warnings. This context might be used by a target + // system to implement a proper mutex handling + (void)p_i2c_context; + + if (0 == g_entry_count) { + g_entry_count++; + if (1 == g_entry_count) { + return PAL_STATUS_SUCCESS; + } + } + return PAL_STATUS_FAILURE; +} + +static void pal_i2c_release(const void* p_i2c_context) +{ + // To avoid compiler errors/warnings. This context might be used by a target + // system to implement a proper mutex handling + (void)p_i2c_context; + + g_entry_count = 0; +} + +static void invoke_upper_layer_callback(const pal_i2c_t* p_pal_i2c_ctx, optiga_lib_status_t event) +{ + upper_layer_callback_t upper_layer_handler; + + // Casting a data pointer to a function pointer is not OK according to ISO C. However, everyone + // does it... +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + upper_layer_handler = (upper_layer_callback_t)p_pal_i2c_ctx->upper_layer_event_handler; +#pragma GCC diagnostic pop + + upper_layer_handler(p_pal_i2c_ctx->p_upper_layer_ctx, event); + + // Release I2C Bus + pal_i2c_release(p_pal_i2c_ctx->p_upper_layer_ctx); +} + +// !!!OPTIGA_LIB_PORTING_REQUIRED +// The next 5 functions are required only in case you have interrupt based i2c implementation +// void i2c_master_end_of_transmit_callback(void) +//{ +// invoke_upper_layer_callback(gp_pal_i2c_current_ctx, PAL_I2C_EVENT_SUCCESS); +//} +// +// void i2c_master_end_of_receive_callback(void) +//{ +// invoke_upper_layer_callback(gp_pal_i2c_current_ctx, PAL_I2C_EVENT_SUCCESS); +//} +// +// void i2c_master_error_detected_callback(void) +//{ +// invoke_upper_layer_callback(gp_pal_i2c_current_ctx, PAL_I2C_EVENT_ERROR); +//} +// +// void i2c_master_nack_received_callback(void) +//{ +// i2c_master_error_detected_callback(); +//} +// +// void i2c_master_arbitration_lost_callback(void) +//{ +// i2c_master_error_detected_callback(); +//} + +pal_status_t pal_i2c_init(const pal_i2c_t* p_i2c_context) +{ + (void)p_i2c_context; + return PAL_STATUS_SUCCESS; +} + +pal_status_t pal_i2c_deinit(const pal_i2c_t* p_i2c_context) +{ + (void)p_i2c_context; + return PAL_STATUS_SUCCESS; +} + +pal_status_t pal_i2c_write(const pal_i2c_t* p_i2c_context, uint8_t* p_data, uint16_t length) +{ + pal_status_t status = PAL_STATUS_FAILURE; + struct _i2c_m_msg packet; + uint8_t retries = 25U; + int32_t r; + + packet.addr = p_i2c_context->slave_address; + packet.len = (int32_t)length; + packet.buffer = p_data; + packet.flags = I2C_M_SEVEN | I2C_M_STOP; + + // Acquire the I2C bus before read/write + if (PAL_STATUS_SUCCESS == pal_i2c_acquire(p_i2c_context)) { + gp_pal_i2c_current_ctx = p_i2c_context; + + // Invoke the low level i2c master driver API to write to the bus + // !!!OPTIGA_LIB_PORTING_REQUIRED + do { + r = i2c_m_sync_transfer(p_i2c_context->p_i2c_hw_config, &packet); + delay_ms(2U); + } while (retries-- && r != I2C_OK); + + if (r != I2C_OK) { + // If I2C Master fails to invoke the write operation, invoke upper layer event handler + // with error. + + // Casting a data pointer to a function pointer is not OK according to ISO C. However, + // everyone does it... +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + ((upper_layer_callback_t)(p_i2c_context->upper_layer_event_handler))( + p_i2c_context->p_upper_layer_ctx, PAL_I2C_EVENT_ERROR); +#pragma GCC diagnostic pop + + // Release I2C Bus + pal_i2c_release(p_i2c_context); + } else { + // !!!OPTIGA_LIB_PORTING_REQUIRED + /** + * Infineon I2C Protocol is a polling based protocol, if foo_i2c_write will fail it will + * be reported to the upper layers by calling + * (p_i2c_context->upper_layer_event_handler))(p_i2c_context->p_upper_layer_ctx , + * PAL_I2C_EVENT_ERROR); If the function foo_i2c_write() will succedd then two options + * are possible + * 1. if foo_i2c_write() is interrupt based, then you need to configure interrupts in + * the function pal_i2c_init() so that on a succesfull transmit interrupt the callback + * i2c_master_end_of_transmit_callback(), in case of successfull receive + * i2c_master_end_of_receive_callback() callback in case of not acknowedged, arbitration + * lost, generic error i2c_master_nack_received_callback() or + * i2c_master_arbitration_lost_callback() + * 2. If foo_i2c_write() is a blocking function which will return either ok or failure + * after transmitting data you can handle this case directly here and call + * invoke_upper_layer_callback(gp_pal_i2c_current_ctx, PAL_I2C_EVENT_SUCCESS); + * + */ + invoke_upper_layer_callback(gp_pal_i2c_current_ctx, PAL_I2C_EVENT_SUCCESS); + status = PAL_STATUS_SUCCESS; + } + } else { + status = PAL_STATUS_I2C_BUSY; + // Casting a data pointer to a function pointer is not OK according to ISO C. However, + // everyone does it... +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + ((upper_layer_callback_t)(p_i2c_context->upper_layer_event_handler))( + p_i2c_context->p_upper_layer_ctx, PAL_I2C_EVENT_BUSY); +#pragma GCC diagnostic pop + } + return status; +} + +pal_status_t pal_i2c_read(const pal_i2c_t* p_i2c_context, uint8_t* p_data, uint16_t length) +{ + // int32_t start = pal_os_timer_get_time_in_milliseconds(); + pal_status_t status = PAL_STATUS_FAILURE; + struct _i2c_m_msg packet; + uint8_t retries = 25U; + int32_t r; + + packet.addr = p_i2c_context->slave_address; + packet.len = (int32_t)length; + packet.buffer = p_data; + packet.flags = I2C_M_SEVEN | I2C_M_RD | I2C_M_STOP; + + // Acquire the I2C bus before read/write + if (PAL_STATUS_SUCCESS == pal_i2c_acquire(p_i2c_context)) { + gp_pal_i2c_current_ctx = p_i2c_context; + + // Invoke the low level i2c master driver API to read from the bus + do { + r = i2c_m_sync_transfer(p_i2c_context->p_i2c_hw_config, &packet); + delay_ms(2U); + } while (retries-- && r != I2C_OK); + + if (r != I2C_OK) { + // If I2C Master fails to invoke the read operation, invoke upper layer event handler + // with error. + // Casting a data pointer to a function pointer is not OK according to ISO C. However, + // everyone does it... +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + ((upper_layer_callback_t)(p_i2c_context->upper_layer_event_handler))( + p_i2c_context->p_upper_layer_ctx, PAL_I2C_EVENT_ERROR); +#pragma GCC diagnostic pop + + // Release I2C Bus + pal_i2c_release(p_i2c_context); + } else { + // !!!OPTIGA_LIB_PORTING_REQUIRED + /** + * Similar to the foo_i2c_write() case you can directly call + * invoke_upper_layer_callback(gp_pal_i2c_current_ctx, PAL_I2C_EVENT_SUCCESS); + * if you have blocking (non-interrupt) i2c calls + */ + invoke_upper_layer_callback(gp_pal_i2c_current_ctx, PAL_I2C_EVENT_SUCCESS); + status = PAL_STATUS_SUCCESS; + } + } else { + status = PAL_STATUS_I2C_BUSY; + // Casting a data pointer to a function pointer is not OK according to ISO C. However, + // everyone does it... +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + ((upper_layer_callback_t)(p_i2c_context->upper_layer_event_handler))( + p_i2c_context->p_upper_layer_ctx, PAL_I2C_EVENT_BUSY); +#pragma GCC diagnostic pop + } + // uint32_t final_time = pal_os_timer_get_time_in_milliseconds() - start; + return status; +} + +pal_status_t pal_i2c_set_bitrate(const pal_i2c_t* p_i2c_context, uint16_t bitrate) +{ + pal_status_t return_status = PAL_STATUS_FAILURE; + optiga_lib_status_t event = PAL_I2C_EVENT_ERROR; + + // Acquire the I2C bus before setting the bitrate + if (PAL_STATUS_SUCCESS == pal_i2c_acquire(p_i2c_context)) { + // If the user provided bitrate is greater than the I2C master hardware maximum supported + // value, set the I2C master to its maximum supported value. + if (bitrate > PAL_I2C_MASTER_MAX_BITRATE) { + bitrate = PAL_I2C_MASTER_MAX_BITRATE; + } + + do { + if (0 != i2c_m_sync_disable(&I2C_0)) { + return_status = PAL_STATUS_FAILURE; + break; + } + + if (0 != i2c_m_sync_set_baudrate(p_i2c_context->p_i2c_hw_config, 0, bitrate)) { + return_status = PAL_STATUS_FAILURE; + break; + } + + if (0 != i2c_m_sync_enable(&I2C_0)) { + return_status = PAL_STATUS_FAILURE; + break; + } + event = PAL_I2C_EVENT_SUCCESS; + return_status = PAL_STATUS_SUCCESS; + } while (0); + } else { + return_status = PAL_STATUS_I2C_BUSY; + event = PAL_I2C_EVENT_BUSY; + } + if (0 != p_i2c_context->upper_layer_event_handler) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + ((upper_layer_callback_t)(p_i2c_context->upper_layer_event_handler))( + p_i2c_context->p_upper_layer_ctx, event); +#pragma GCC diagnostic pop + } + // Release I2C Bus if its acquired + if (PAL_STATUS_I2C_BUSY != return_status) { + pal_i2c_release(p_i2c_context); + } + return return_status; +} + +/** + * @} + */ diff --git a/src/optiga/pal/pal_ifx_i2c_config.c b/src/optiga/pal/pal_ifx_i2c_config.c new file mode 100644 index 000000000..e75043ef0 --- /dev/null +++ b/src/optiga/pal/pal_ifx_i2c_config.c @@ -0,0 +1,90 @@ +/** + * \copyright + * MIT License + * + * Copyright (c) 2019 Infineon Technologies AG + * + * 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 + * + * \endcopyright + * + * \author Infineon Technologies AG + * + * \file pal_ifx_i2c_config.c + * + * \brief This file implements platform abstraction layer configurations for ifx i2c protocol. + * + * \ingroup grPAL + * + * @{ + */ + +#include "driver_init.h" +#include "ifx_i2c_config.h" +#include "pal_gpio.h" +#include "pal_i2c.h" + +// !!!OPTIGA_LIB_PORTING_REQUIRED +// typedef struct locl_i2c_struct_to_descroibe_master +//{ +// // you parameters to control the master instance +// // See other implementation to get intuition on how to implement this part +//}local_i2c_struct_to_descroibe_master_t; +// +// local_i2c_struct_to_descroibe_master_t i2c_master_0; + +/** + * \brief PAL I2C configuration for OPTIGA. + */ +pal_i2c_t optiga_pal_i2c_context_0 = { + /// Pointer to I2C master platform specific context + (void*)&I2C_0, + /// Upper layer context + NULL, + /// Callback event handler + NULL, + /// Slave address + 0x30, +}; + +/** + * \brief PAL vdd pin configuration for OPTIGA. + * NC on bitbox02 + */ +pal_gpio_t optiga_vdd_0 = { + // !!!OPTIGA_LIB_PORTING_REQUIRED + // Platform specific GPIO context for the pin used to toggle Vdd. + // You should have vdd_pin define in your system, + // alternativly you can put here raw GPIO number, but without the & sign + 0}; + +/** + * \brief PAL reset pin configuration for OPTIGA. + * NC on bitbox02 + */ +pal_gpio_t optiga_reset_0 = { + // !!!OPTIGA_LIB_PORTING_REQUIRED + // Platform specific GPIO context for the pin used to toggle Reset. + // You should have reset_pin define in your system, + // alternativly you can put here raw GPIO number, but without the & sign + 0}; + +/** + * @} + */ diff --git a/src/optiga/pal/pal_logger.c b/src/optiga/pal/pal_logger.c new file mode 100644 index 000000000..01854098e --- /dev/null +++ b/src/optiga/pal/pal_logger.c @@ -0,0 +1,95 @@ +/** + * \copyright + * MIT License + * + * Copyright (c) 2019 Infineon Technologies AG + * + * 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 + * + * \endcopyright + * + * \author Infineon Technologies AG + * + * \file pal_logger.c + * + * \brief This file provides the prototypes declarations for pal logger. + * + * \ingroup grPAL + * + * @{ + */ +#include "pal_logger.h" +#include "util.h" + +pal_logger_t logger_console = { + .logger_config_ptr = NULL, + .logger_rx_flag = 0, + .logger_tx_flag = 1, +}; + +pal_status_t pal_logger_init(void* p_logger_context) +{ + pal_status_t return_status = PAL_STATUS_FAILURE; + pal_logger_t* p_log_context = p_logger_context; + (void)p_log_context; + + do { + // !!!OPTIGA_LIB_PORTING_REQUIRED + } while (0); + return return_status; +} + +pal_status_t pal_logger_deinit(void* p_logger_context) +{ + pal_status_t return_status = PAL_STATUS_FAILURE; + pal_logger_t* p_log_context = p_logger_context; + (void)p_log_context; + + do { + // !!!OPTIGA_LIB_PORTING_REQUIRED + } while (0); + return return_status; +} + +pal_status_t pal_logger_write( + void* p_logger_context, + const uint8_t* p_log_data, + uint32_t log_data_length) +{ + (void)p_logger_context; + char s[10000]; + snprintf(s, sizeof(s), "%s", p_log_data); + s[log_data_length] = 0; + util_log("%s", s); + return PAL_STATUS_SUCCESS; +} + +// NOLINTBEGIN(readability-non-const-parameter) +pal_status_t pal_logger_read(void* p_logger_context, uint8_t* p_log_data, uint32_t log_data_length) +{ + (void)p_logger_context; + (void)p_log_data; + (void)log_data_length; + return PAL_STATUS_FAILURE; +} +// NOLINTEND(readability-non-const-parameter) + +/** + * @} + */ diff --git a/src/optiga/pal/pal_os_datastore.c b/src/optiga/pal/pal_os_datastore.c new file mode 100644 index 000000000..26207e4ce --- /dev/null +++ b/src/optiga/pal/pal_os_datastore.c @@ -0,0 +1,82 @@ +/** + * \copyright + * MIT License + * + * Copyright (c) 2020 Infineon Technologies AG + * + * 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 + * + * \endcopyright + * + * \author Infineon Technologies AG + * + * \file pal_os_datastore.c + * + * \brief This file implements the platform abstraction layer APIs for data store. + * + * \ingroup grPAL + * + * @{ + */ + +#include "pal_os_datastore.h" +#include "memory/memory.h" +#include + +/// @cond hidden + +// While the binding shared secret is read using pal_os_datastore_read() by the optiga library, +// we are suppoesd to store it manually using pal_os_datastore_write() during factory setup. +// However, we use the memory io protection key for that, which is initialized during +// memory_setup(). +pal_status_t pal_os_datastore_write(uint16_t datastore_id, const uint8_t* p_buffer, uint16_t length) +{ + (void)p_buffer; + (void)length; + util_log("pal_datastore_write, id=%d", datastore_id); + return PAL_STATUS_FAILURE; +} + +pal_status_t pal_os_datastore_read( + uint16_t datastore_id, + uint8_t* p_buffer, + uint16_t* p_buffer_length) +{ + pal_status_t return_status = PAL_STATUS_FAILURE; + util_log("pal_datastore_read, id=%d", datastore_id); + + switch (datastore_id) { + case OPTIGA_PLATFORM_BINDING_SHARED_SECRET_ID: { + memory_get_io_protection_key(p_buffer); + *p_buffer_length = 32; + return_status = PAL_STATUS_SUCCESS; + break; + } + default: { + *p_buffer_length = 0; + break; + } + } + + return return_status; +} +/// @endcond +/** + * @} + */ diff --git a/src/optiga/pal/pal_os_event.c b/src/optiga/pal/pal_os_event.c new file mode 100644 index 000000000..e16bf484e --- /dev/null +++ b/src/optiga/pal/pal_os_event.c @@ -0,0 +1,110 @@ +/** + * \copyright + * MIT License + * + * Copyright (c) 2019 Infineon Technologies AG + * + * 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 + * + * \endcopyright + * + * \author Infineon Technologies AG + * + * \file pal_os_event.c + * + * \brief This file implements the platform abstraction layer APIs for os event/scheduler. + * + * \ingroup grPAL + * + * @{ + */ + +#include "pal_os_event.h" +#include "hal_timer.h" +#include "util.h" +extern struct timer_descriptor TIMER_0; + +static pal_os_event_t pal_os_event_0 = {0}; + +void pal_os_event_start( + pal_os_event_t* p_pal_os_event, + register_callback callback, + void* callback_args) +{ + if (0 == p_pal_os_event->is_event_triggered) { + p_pal_os_event->is_event_triggered = TRUE; + pal_os_event_register_callback_oneshot(p_pal_os_event, callback, callback_args, 1000); + } +} + +void pal_os_event_stop(pal_os_event_t* p_pal_os_event) +{ + p_pal_os_event->is_event_triggered = 0; +} + +pal_os_event_t* pal_os_event_create(register_callback callback, void* callback_args) +{ + if ((NULL != callback) && (NULL != callback_args)) { + pal_os_event_start(&pal_os_event_0, callback, callback_args); + } + return (&pal_os_event_0); +} + +static struct timer_task scheduler; + +void pal_os_event_trigger_registered_callback(void) +{ + // traceln("%s: called", __func__); + register_callback callback; + + if (pal_os_event_0.callback_registered) { + callback = pal_os_event_0.callback_registered; + callback(pal_os_event_0.callback_ctx); + } +} + +static void _timer_cb(const struct timer_task* const timer_task) +{ + (void)timer_task; + pal_os_event_trigger_registered_callback(); +} + +void pal_os_event_register_callback_oneshot( + pal_os_event_t* p_pal_os_event, + register_callback callback, + void* callback_args, + uint32_t time_us) +{ + p_pal_os_event->callback_registered = callback; + p_pal_os_event->callback_ctx = callback_args; + + scheduler.interval = (time_us + 99) / 100; + scheduler.cb = _timer_cb; + scheduler.mode = TIMER_TASK_ONE_SHOT; + timer_add_task(&TIMER_0, &scheduler); +} + +void pal_os_event_destroy(pal_os_event_t* pal_os_event) +{ + (void)pal_os_event; +} + +/** + * @} + */ diff --git a/src/optiga/pal/pal_os_lock.c b/src/optiga/pal/pal_os_lock.c new file mode 100644 index 000000000..7715ab830 --- /dev/null +++ b/src/optiga/pal/pal_os_lock.c @@ -0,0 +1,88 @@ +/** + * \copyright + * MIT License + * + * Copyright (c) 2019 Infineon Technologies AG + * + * 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 + * + * \endcopyright + * + * \author Infineon Technologies AG + * + * \file pal_os_lock.c + * + * \brief This file implements the platform abstraction layer APIs for os locks (e.g. semaphore). + * + * \ingroup grPAL + * + * @{ + */ + +#include "pal_os_lock.h" + +void pal_os_lock_create(pal_os_lock_t* p_lock, uint8_t lock_type) +{ + p_lock->type = lock_type; + p_lock->lock = 0; +} + +void pal_os_lock_destroy(pal_os_lock_t* p_lock) +{ + (void)p_lock; +} + +pal_status_t pal_os_lock_acquire(pal_os_lock_t* p_lock) +{ + pal_status_t return_status = PAL_STATUS_FAILURE; + + // Below is a sample shared resource acquire mechanism + // it doesn't provide a guarantee against a deadlock + if (!(p_lock->lock)) { + p_lock->lock++; + if (1 != p_lock->lock) { + p_lock->lock--; + } + return_status = PAL_STATUS_SUCCESS; + } + return return_status; +} + +void pal_os_lock_release(pal_os_lock_t* p_lock) +{ + // Below is a sample shared resource acquire mechanism + // it doesn't provide a guarantee against a deadlock + if (0 != p_lock->lock) { + p_lock->lock--; + } +} + +void pal_os_lock_enter_critical_section(void) +{ + // For safety critical systems it is recommended to implement a critical section entry +} + +void pal_os_lock_exit_critical_section(void) +{ + // For safety critical systems it is recommended to implement a critical section exit +} + +/** + * @} + */ diff --git a/src/optiga/pal/pal_os_memory.c b/src/optiga/pal/pal_os_memory.c new file mode 100644 index 000000000..267d31ee1 --- /dev/null +++ b/src/optiga/pal/pal_os_memory.c @@ -0,0 +1,23 @@ +#include "pal_os_memory.h" +#include "util.h" +#include + +void* pal_os_calloc(uint32_t number_of_blocks, uint32_t block_size) +{ + return calloc(number_of_blocks, block_size); +} + +void pal_os_free(void* block) +{ + free(block); +} + +void pal_os_memcpy(void* p_destination, const void* p_source, uint32_t size) +{ + memcpy(p_destination, p_source, size); +} + +void pal_os_memset(void* p_buffer, uint32_t value, uint32_t size) +{ + memset(p_buffer, value, size); +} diff --git a/src/optiga/pal/pal_os_timer.c b/src/optiga/pal/pal_os_timer.c new file mode 100644 index 000000000..97424d30f --- /dev/null +++ b/src/optiga/pal/pal_os_timer.c @@ -0,0 +1,88 @@ +/** + * \copyright + * MIT License + * + * Copyright (c) 2019 Infineon Technologies AG + * + * 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 + * + * \endcopyright + * + * \author Infineon Technologies AG + * + * \file pal_os_timer.c + * + * \brief This file implements the platform abstraction layer APIs for timer. + * + * \ingroup grPAL + * + * @{ + */ + +#include "pal_os_timer.h" +#include "hal_delay.h" +#include "hal_timer.h" +#include "hpl_time_measure.h" +#include "util.h" +extern struct timer_descriptor TIMER_0; + +static volatile uint32_t g_ms_count = 0; +static struct timer_task scheduler; + +uint32_t pal_os_timer_get_time_in_microseconds(void) +{ + static uint32_t count = 0; + // The implementation must ensure that every invocation of this API returns a unique + // value. + return g_ms_count * 1000 + (count++); +} + +uint32_t pal_os_timer_get_time_in_milliseconds(void) +{ + return g_ms_count; +} + +void pal_os_timer_delay_in_milliseconds(uint16_t milliseconds) +{ + delay_ms(milliseconds); +} + +static void _timer_cb(const struct timer_task* const timer_task) +{ + (void)timer_task; + g_ms_count++; +} + +pal_status_t pal_timer_init(void) +{ + scheduler.interval = 1; + scheduler.cb = _timer_cb; + scheduler.mode = TIMER_TASK_REPEAT; + timer_add_task(&TIMER_0, &scheduler); + return PAL_STATUS_SUCCESS; +} + +pal_status_t pal_timer_deinit(void) +{ + timer_remove_task(&TIMER_0, &scheduler); + return PAL_STATUS_SUCCESS; +} +/** + * @} + */ diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 43f841733..974a1909c 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -136,6 +136,7 @@ dependencies = [ "bitbox02-noise", "bitbox02-rust", "bitcoin", + "der", "hex", "p256", "sha2", diff --git a/src/rust/bitbox02-rust-c/Cargo.toml b/src/rust/bitbox02-rust-c/Cargo.toml index d996e979d..b96068ff5 100644 --- a/src/rust/bitbox02-rust-c/Cargo.toml +++ b/src/rust/bitbox02-rust-c/Cargo.toml @@ -29,6 +29,7 @@ bitbox02 = { path = "../bitbox02", optional = true } bitbox02-noise = { path = "../bitbox02-noise", optional = true } util = { path = "../util" } p256 = { version = "0.13.2", default-features = false, features = ["arithmetic", "ecdsa"], optional = true } +der = { version = "0.7.9", default-features = false, optional = true } hex = { workspace = true } sha2 = { workspace = true, optional = true } sha3 = { workspace = true, optional = true } @@ -63,7 +64,7 @@ target-c-unit-tests = [ platform-bitbox02 = [] bootloader = [] -firmware = ["bitbox02-rust", "bitbox02", "bitbox02-noise", "sha2", "p256"] +firmware = ["bitbox02-rust", "bitbox02", "bitbox02-noise", "sha2", "p256", "der"] # Only to be enabled in Rust unit tests. testing = ["bitbox02-rust/testing", "bitbox02/testing"] diff --git a/src/rust/bitbox02-rust-c/src/der.rs b/src/rust/bitbox02-rust-c/src/der.rs new file mode 100644 index 000000000..ba8d745d4 --- /dev/null +++ b/src/rust/bitbox02-rust-c/src/der.rs @@ -0,0 +1,79 @@ +// Copyright 2024 Shift Crypto AG +// +// 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. + +use der::asn1::UintRef; +use der::{Decode, SliceReader}; + +fn parse_int256(decoder: &mut SliceReader) -> Result<[u8; 32], ()> { + let int = UintRef::decode(decoder).map_err(|_| ())?; + let int_bytes = int.as_bytes(); + if int_bytes.len() > 32 { + return Err(()); + } + let mut array = [0u8; 32]; + let start_index = 32 - int_bytes.len(); + array[start_index..].copy_from_slice(int_bytes); + Ok(array) +} + +fn parse_two_int256s(data: &[u8]) -> Result<([u8; 32], [u8; 32]), ()> { + let mut decoder = SliceReader::new(data).map_err(|_| ())?; + + let first = parse_int256(&mut decoder)?; + let second = parse_int256(&mut decoder)?; + + Ok((first, second)) +} + +/// Parse a ECC signature as returned by the Optiga Trust M. +/// See Solution Reference Manual 6.2.2, example for ECC NIST-P256 signature. +/// https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/pdf/OPTIGA_Trust_M_Datasheet_v3.70.pdf +/// The input is the DER encoding of the signature R/S values encoded as two DER "INGEGER". +/// It's the same encoding as a regular DER-signature, but without the 0x30 sequence header. +/// sig_compact_out must be 64 bytes and will contain the R/S values (each 32 bytes). +#[no_mangle] +pub extern "C" fn rust_der_parse_optiga_signature( + sig_der: crate::util::Bytes, + mut sig_compact_out: crate::util::BytesMut, +) -> bool { + match parse_two_int256s(sig_der.as_ref()) { + Ok((first, second)) => { + sig_compact_out.as_mut()[..32].copy_from_slice(&first); + sig_compact_out.as_mut()[32..].copy_from_slice(&second); + true + } + Err(_) => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rust_der_parse_optiga_signature() { + let sig_der = b"\x02\x02\x12\x34\x02\x03\x00\xab\xcd"; + let mut sig_compact = [0u8; 64]; + assert!(rust_der_parse_optiga_signature( + unsafe { crate::util::rust_util_bytes(sig_der.as_ptr(), sig_der.len()) }, + unsafe { + crate::util::rust_util_bytes_mut(sig_compact.as_mut_ptr(), sig_compact.len()) + }, + )); + assert_eq!( + hex::encode(sig_compact), + "0000000000000000000000000000000000000000000000000000000000001234000000000000000000000000000000000000000000000000000000000000abcd", + ); + } +} diff --git a/src/rust/bitbox02-rust-c/src/lib.rs b/src/rust/bitbox02-rust-c/src/lib.rs index 5c15e6ce2..8cef5d1db 100644 --- a/src/rust/bitbox02-rust-c/src/lib.rs +++ b/src/rust/bitbox02-rust-c/src/lib.rs @@ -37,6 +37,9 @@ mod sha2; #[cfg(feature = "firmware")] mod workflow; +#[cfg(feature = "firmware")] +mod der; + // Whenever execution reaches somewhere it isn't supposed to rust code will "panic". Our panic // handler will print the available information on the screen and over RTT. If we compile with // `panic=abort` this code will never get executed. diff --git a/src/rust/bitbox02-rust/src/hww/api/device_info.rs b/src/rust/bitbox02-rust/src/hww/api/device_info.rs index 41f00227f..b2fa68b7f 100644 --- a/src/rust/bitbox02-rust/src/hww/api/device_info.rs +++ b/src/rust/bitbox02-rust/src/hww/api/device_info.rs @@ -29,6 +29,7 @@ pub fn process() -> Result { securechip_model: match securechip::model()? { securechip::Model::ATECC_ATECC608A => "ATECC608A".into(), securechip::Model::ATECC_ATECC608B => "ATECC608B".into(), + securechip::Model::OPTIGA_TRUST_M_V3 => "OPTIGA_TRUST_M_V3".into(), }, })) } diff --git a/src/securechip/securechip.c b/src/securechip/securechip.c index 110cafba3..91463a857 100644 --- a/src/securechip/securechip.c +++ b/src/securechip/securechip.c @@ -13,9 +13,11 @@ // limitations under the License. #include "securechip.h" -#include "atecc/atecc.h" -#include "hardfault.h" -#include "memory/memory_shared.h" + +#include +#include +#include +#include typedef struct { int (*setup)(const securechip_interface_functions_t* fns); @@ -42,7 +44,21 @@ bool securechip_init(void) { switch (memory_get_securechip_type()) { case MEMORY_SECURECHIP_TYPE_OPTIGA: - Abort("Not implemented"); + _fns.setup = optiga_setup; + _fns.update_keys = optiga_update_keys; + _fns.kdf = optiga_kdf_external; + _fns.kdf_rollkey = optiga_kdf_internal; + _fns.gen_attestation_key = optiga_gen_attestation_key; + _fns.attestation_sign = optiga_attestation_sign; + _fns.monotonic_increments_remaining = optiga_monotonic_increments_remaining; + _fns.random = optiga_random; +#if APP_U2F == 1 || FACTORYSETUP == 1 + _fns.u2f_counter_set = optiga_u2f_counter_set; +#endif +#if APP_U2F == 1 + _fns.u2f_counter_inc = optiga_u2f_counter_inc; +#endif + _fns.model = optiga_model; break; case MEMORY_SECURECHIP_TYPE_ATECC: default: diff --git a/src/securechip/securechip.h b/src/securechip/securechip.h index ca26ae05c..4925a82ee 100644 --- a/src/securechip/securechip.h +++ b/src/securechip/securechip.h @@ -23,14 +23,23 @@ #include typedef enum { - SC_ERR_ZONE_UNLOCKED_CONFIG = -1, - SC_ERR_ZONE_UNLOCKED_DATA = -2, + // Errors common to any securechip implementation + SC_ERR_IFS = -1, + SC_ERR_INVALID_ARGS = -2, SC_ERR_CONFIG_MISMATCH = -3, - SC_ERR_SLOT_UNLOCKED_IO = -4, - SC_ERR_SLOT_UNLOCKED_AUTH = -5, - SC_ERR_SLOT_UNLOCKED_ENC = -6, - SC_ERR_IFS = -7, - SC_ERR_INVALID_ARGS = -8, + + // Errors specific to the ATECC + SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG = -100, + SC_ATECC_ERR_ZONE_UNLOCKED_DATA = -101, + SC_ATECC_ERR_SLOT_UNLOCKED_IO = -103, + SC_ATECC_ERR_SLOT_UNLOCKED_AUTH = -104, + SC_ATECC_ERR_SLOT_UNLOCKED_ENC = -105, + + // Errors specific to the Optiga + SC_OPTIGA_ERR_CREATE = -201, + SC_OPTIGA_ERR_UNEXPECTED_METADATA = -204, + SC_OPTIGA_ERR_PAL = -205, + SC_OPTIGA_ERR_UNEXPECTED_LEN = -206, } securechip_error_t; typedef struct { @@ -61,15 +70,15 @@ USE_RESULT bool securechip_init(void); * communication interface/bridge to cryptoauthlib. On first call, the chip * is configured and locked. * @param[in] ifs Interface functions. - * @return values of `securechip_error_t` if negative, values of `ATCA_STATUS` if positive, 0 on - * success. + * @return 0 on success. Values of `securechip_error_t` if negative. If positive, values of + * `ATCA_STATUS` for ATECC, values of optiga_lib_return_codes.h for Optiga. */ USE_RESULT int securechip_setup(const securechip_interface_functions_t* ifs); /** * Updates the two KDF keys (rollkey and kdf key). The previous keys are lost * and cannot be restored. Calling this function does not increment the - * monotonic counter Counter0. + * monotonic counter. * @return true on success. */ USE_RESULT bool securechip_update_keys(void); @@ -80,7 +89,8 @@ USE_RESULT bool securechip_update_keys(void); * @param[in] len Must be <= 127. * @param[out] kdf_out Must have size 32. Result of the kdf will be stored here. * Cannot be the same as `msg`. - * @return values of `securechip_error_t` if negative, values of `ATCA_STATUS` if positive, 0 on + * @return 0 on success. Values of `securechip_error_t` if negative. If positive, values of + * `ATCA_STATUS` for ATECC, values of optiga_lib_return_codes.h for Optiga. */ USE_RESULT int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out); @@ -91,7 +101,8 @@ USE_RESULT int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out); * @param[in] len Must be <= 127. * @param[out] kdf_out Must have size 32. Result of the kdf will be stored here. * Cannot be the same as `msg`. - * @return values of `securechip_error_t` if negative, values of `ATCA_STATUS` if positive, 0 on + * @return 0 on success. Values of `securechip_error_t` if negative. If positive, values of + * `ATCA_STATUS` for ATECC, values of optiga_lib_return_codes.h for Optiga. */ USE_RESULT int securechip_kdf_rollkey(const uint8_t* msg, size_t len, uint8_t* kdf_out); @@ -108,7 +119,7 @@ USE_RESULT bool securechip_gen_attestation_key(uint8_t* pubkey_out); USE_RESULT bool securechip_attestation_sign(const uint8_t* challenge, uint8_t* signature_out); /** - * Retrieves the number of remaining possible counter increments (max value - Counter0). + * Retrieves the number of remaining possible counter increments (max value - Counter). * The counter is increment when using `securechip_kdf()` (see its docstring). * @param[out] remaining_out current value of the monotonic counter. * @return false if there was a communication error with the SC. @@ -141,6 +152,7 @@ USE_RESULT bool securechip_u2f_counter_inc(uint32_t* counter); typedef enum { ATECC_ATECC608A, ATECC_ATECC608B, + OPTIGA_TRUST_M_V3, } securechip_model_t; /** diff --git a/test/simulator/framework/mock_securechip.c b/test/simulator/framework/mock_securechip.c index 42bebb063..208459ebc 100644 --- a/test/simulator/framework/mock_securechip.c +++ b/test/simulator/framework/mock_securechip.c @@ -22,11 +22,6 @@ #include #include -typedef enum { - SECURECHIP_SLOT_ROLLKEY = 3, - SECURECHIP_SLOT_KDF = 4, -} securechip_slot_t; - static uint32_t _u2f_counter; bool securechip_update_keys(void) @@ -44,29 +39,15 @@ static const uint8_t _kdfkey[32] = "\xd2\xe1\xe6\xb1\x8b\x6c\x6b\x08\x43\x3e\xdb\xc1\xd1\x68\xc1\xa0\x04\x37\x74\xa4\x22\x18\x77" "\xe7\x9e\xd5\x66\x84\xbe\x5a\xc0\x1b"; -static int _securechip_kdf(securechip_slot_t slot, const uint8_t* msg, size_t len, uint8_t* kdf_out) -{ - const uint8_t* key; - switch (slot) { - case SECURECHIP_SLOT_ROLLKEY: - key = _rollkey; - break; - case SECURECHIP_SLOT_KDF: - key = _kdfkey; - break; - default: - return SC_ERR_INVALID_ARGS; - } - wally_hmac_sha256(key, 32, msg, len, kdf_out, 32); - return 0; -} int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out) { - return _securechip_kdf(SECURECHIP_SLOT_KDF, msg, len, kdf_out); + wally_hmac_sha256(_kdfkey, 32, msg, len, kdf_out, 32); + return 0; } int securechip_kdf_rollkey(const uint8_t* msg, size_t len, uint8_t* kdf_out) { - return _securechip_kdf(SECURECHIP_SLOT_ROLLKEY, msg, len, kdf_out); + wally_hmac_sha256(_rollkey, 32, msg, len, kdf_out, 32); + return 0; } bool securechip_u2f_counter_set(uint32_t counter) diff --git a/test/unit-test/framework/mock_securechip.c b/test/unit-test/framework/mock_securechip.c index f0fbf3e47..ca1c98a42 100644 --- a/test/unit-test/framework/mock_securechip.c +++ b/test/unit-test/framework/mock_securechip.c @@ -23,11 +23,6 @@ #include #include -typedef enum { - SECURECHIP_SLOT_ROLLKEY = 3, - SECURECHIP_SLOT_KDF = 4, -} securechip_slot_t; - static uint32_t _u2f_counter; bool securechip_update_keys(void) @@ -45,29 +40,15 @@ static const uint8_t _kdfkey[32] = "\xd2\xe1\xe6\xb1\x8b\x6c\x6b\x08\x43\x3e\xdb\xc1\xd1\x68\xc1\xa0\x04\x37\x74\xa4\x22\x18\x77" "\xe7\x9e\xd5\x66\x84\xbe\x5a\xc0\x1b"; -static int _securechip_kdf(securechip_slot_t slot, const uint8_t* msg, size_t len, uint8_t* kdf_out) -{ - const uint8_t* key; - switch (slot) { - case SECURECHIP_SLOT_ROLLKEY: - key = _rollkey; - break; - case SECURECHIP_SLOT_KDF: - key = _kdfkey; - break; - default: - return SC_ERR_INVALID_ARGS; - } - wally_hmac_sha256(key, 32, msg, len, kdf_out, 32); - return 0; -} int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out) { - return _securechip_kdf(SECURECHIP_SLOT_KDF, msg, len, kdf_out); + wally_hmac_sha256(_kdfkey, 32, msg, len, kdf_out, 32); + return 0; } int securechip_kdf_rollkey(const uint8_t* msg, size_t len, uint8_t* kdf_out) { - return _securechip_kdf(SECURECHIP_SLOT_ROLLKEY, msg, len, kdf_out); + wally_hmac_sha256(_rollkey, 32, msg, len, kdf_out, 32); + return 0; } bool securechip_u2f_counter_set(uint32_t counter) From 83c26155503e17964c25ead48ba99f7f3d523220 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Tue, 17 Dec 2024 09:23:19 +0100 Subject: [PATCH 2/5] optiga: implement critical section To disable interrupts when processing Optiga commands. --- src/optiga/pal/pal_os_lock.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/optiga/pal/pal_os_lock.c b/src/optiga/pal/pal_os_lock.c index 7715ab830..73d98a1f3 100644 --- a/src/optiga/pal/pal_os_lock.c +++ b/src/optiga/pal/pal_os_lock.c @@ -37,6 +37,8 @@ #include "pal_os_lock.h" +#include + void pal_os_lock_create(pal_os_lock_t* p_lock, uint8_t lock_type) { p_lock->type = lock_type; @@ -73,14 +75,16 @@ void pal_os_lock_release(pal_os_lock_t* p_lock) } } +static volatile hal_atomic_t _atomic; + void pal_os_lock_enter_critical_section(void) { - // For safety critical systems it is recommended to implement a critical section entry + atomic_enter_critical(&_atomic); } void pal_os_lock_exit_critical_section(void) { - // For safety critical systems it is recommended to implement a critical section exit + atomic_leave_critical(&_atomic); } /** From 1a7259f245a0e670e6c0e061dd176fc0944d8398 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Mon, 16 Dec 2024 10:05:32 +0100 Subject: [PATCH 3/5] keystore: make password stretching a function of the securechip So the two different securechip implementations (ATECC608 and Optiga Trust M V3) can implement stretching differently. This is needed because in the Optiga, we will add an additional stretching step. --- src/atecc/atecc.c | 54 ++++++++++++++- src/atecc/atecc.h | 2 +- src/keystore.c | 77 +++------------------- src/optiga/optiga.c | 57 +++++++++++++++- src/optiga/optiga.h | 2 +- src/securechip/securechip.c | 12 ++-- src/securechip/securechip.h | 7 +- test/simulator/framework/mock_securechip.c | 9 +-- test/unit-test/framework/mock_securechip.c | 9 ++- test/unit-test/test_keystore.c | 7 +- 10 files changed, 144 insertions(+), 92 deletions(-) diff --git a/src/atecc/atecc.c b/src/atecc/atecc.c index db026f039..e33ed88fa 100644 --- a/src/atecc/atecc.c +++ b/src/atecc/atecc.c @@ -16,7 +16,9 @@ #include "hardfault.h" #include "securechip/securechip.h" #include +#include #include +#include // disabling some warnings, as it's an external library. #pragma GCC diagnostic push @@ -75,6 +77,10 @@ static uint8_t _configuration[ATCA_ECC_CONFIG_SIZE] = { // Number of times the first kdf slot can be used. #define MONOTONIC_COUNTER_MAX_USE (730500) +// This number of KDF iterations on the 2nd kdf slot when stretching the device +// password. +#define KDF_NUM_ITERATIONS (2) + // The total individual size of the public key data slots (slots 9-15) is 72 bytes. Using encrypted // read/write it is only possible to transmit 32 bytes. The last block is therefore 8 (72 = // 32+32+8). @@ -586,9 +592,53 @@ int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out) return _atecc_kdf(ATECC_SLOT_KDF, msg, len, kdf_out); } -int atecc_kdf_rollkey(const uint8_t* msg, size_t len, uint8_t* kdf_out) +int atecc_stretch_password(const char* password, uint8_t* stretched_out) { - return _atecc_kdf(ATECC_SLOT_ROLLKEY, msg, len, kdf_out); + uint8_t password_salted_hashed[32] = {0}; + UTIL_CLEANUP_32(password_salted_hashed); + if (!salt_hash_data( + (const uint8_t*)password, + strlen(password), + "keystore_seed_access_in", + password_salted_hashed)) { + return SC_ERR_SALT; + } + + uint8_t kdf_in[32] = {0}; + UTIL_CLEANUP_32(kdf_in); + memcpy(kdf_in, password_salted_hashed, 32); + + // First KDF on rollkey increments the monotonic counter. Call only once! + int securechip_result = _atecc_kdf(ATECC_SLOT_ROLLKEY, kdf_in, 32, stretched_out); + if (securechip_result) { + return securechip_result; + } + // Second KDF does not use the counter and we call it multiple times. + for (int i = 0; i < KDF_NUM_ITERATIONS; i++) { + memcpy(kdf_in, stretched_out, 32); + securechip_result = securechip_kdf(kdf_in, 32, stretched_out); + if (securechip_result) { + return securechip_result; + } + } + + if (!salt_hash_data( + (const uint8_t*)password, + strlen(password), + "keystore_seed_access_out", + password_salted_hashed)) { + return SC_ERR_SALT; + } + if (wally_hmac_sha256( + password_salted_hashed, + sizeof(password_salted_hashed), + stretched_out, + 32, + stretched_out, + 32) != WALLY_OK) { + return SC_ERR_HASH; + } + return 0; } bool atecc_gen_attestation_key(uint8_t* pubkey_out) diff --git a/src/atecc/atecc.h b/src/atecc/atecc.h index 7566ff048..d83bf8579 100644 --- a/src/atecc/atecc.h +++ b/src/atecc/atecc.h @@ -28,7 +28,7 @@ USE_RESULT int atecc_setup(const securechip_interface_functions_t* ifs); USE_RESULT bool atecc_update_keys(void); USE_RESULT int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out); -USE_RESULT int atecc_kdf_rollkey(const uint8_t* msg, size_t len, uint8_t* kdf_out); +USE_RESULT int atecc_stretch_password(const char* password, uint8_t* stretched_out); USE_RESULT bool atecc_gen_attestation_key(uint8_t* pubkey_out); USE_RESULT bool atecc_attestation_sign(const uint8_t* challenge, uint8_t* signature_out); USE_RESULT bool atecc_monotonic_increments_remaining(uint32_t* remaining_out); diff --git a/src/keystore.c b/src/keystore.c index ef17bd488..ac7ad54eb 100644 --- a/src/keystore.c +++ b/src/keystore.c @@ -160,69 +160,6 @@ static bool _copy_bip39_seed(uint8_t* bip39_seed_out) return true; } -/** - * Stretch the user password using the securechip, putting the result in `kdf_out`, which must be 32 - * bytes. `securechip_result_out`, if not NULL, will contain the error code from `securechip_kdf()` - * if there was a secure chip error, and 0 otherwise. - */ -static keystore_error_t _stretch_password( - const char* password, - uint8_t* kdf_out, - int* securechip_result_out) -{ - if (securechip_result_out != NULL) { - *securechip_result_out = 0; - } - uint8_t password_salted_hashed[32] = {0}; - UTIL_CLEANUP_32(password_salted_hashed); - if (!salt_hash_data( - (const uint8_t*)password, - strlen(password), - "keystore_seed_access_in", - password_salted_hashed)) { - return KEYSTORE_ERR_SALT; - } - - uint8_t kdf_in[32] = {0}; - UTIL_CLEANUP_32(kdf_in); - memcpy(kdf_in, password_salted_hashed, 32); - - // First KDF on rollkey increments the monotonic counter. Call only once! - int securechip_result = securechip_kdf_rollkey(kdf_in, 32, kdf_out); - if (securechip_result) { - if (securechip_result_out != NULL) { - *securechip_result_out = securechip_result; - } - return KEYSTORE_ERR_SECURECHIP; - } - // Second KDF does not use the counter and we call it multiple times. - for (int i = 0; i < KDF_NUM_ITERATIONS; i++) { - memcpy(kdf_in, kdf_out, 32); - securechip_result = securechip_kdf(kdf_in, 32, kdf_out); - if (securechip_result) { - if (securechip_result_out != NULL) { - *securechip_result_out = securechip_result; - } - return KEYSTORE_ERR_SECURECHIP; - } - } - - if (!salt_hash_data( - (const uint8_t*)password, - strlen(password), - "keystore_seed_access_out", - password_salted_hashed)) { - return KEYSTORE_ERR_SALT; - } - if (wally_hmac_sha256( - password_salted_hashed, sizeof(password_salted_hashed), kdf_out, 32, kdf_out, 32) != - WALLY_OK) { - return KEYSTORE_ERR_HASH; - } - - return KEYSTORE_OK; -} - /** * Retrieves the encrypted seed and attempts to decrypt it using the password. * @@ -243,9 +180,12 @@ static keystore_error_t _get_and_decrypt_seed( } uint8_t secret[32]; UTIL_CLEANUP_32(secret); - keystore_error_t result = _stretch_password(password, secret, securechip_result_out); - if (result != KEYSTORE_OK) { - return result; + int stretch_result = securechip_stretch_password(password, secret); + if (securechip_result_out != NULL) { + *securechip_result_out = stretch_result; + } + if (stretch_result) { + return KEYSTORE_ERR_SECURECHIP; } if (encrypted_len < 49) { Abort("_get_and_decrypt_seed: underflow / zero size"); @@ -307,9 +247,8 @@ keystore_error_t keystore_encrypt_and_store_seed( } uint8_t secret[32] = {0}; UTIL_CLEANUP_32(secret); - keystore_error_t res = _stretch_password(password, secret, NULL); - if (res != KEYSTORE_OK) { - return res; + if (securechip_stretch_password(password, secret)) { + return KEYSTORE_ERR_SECURECHIP; } size_t encrypted_seed_len = seed_length + 64; diff --git a/src/optiga/optiga.c b/src/optiga/optiga.c index c12440129..501061472 100644 --- a/src/optiga/optiga.c +++ b/src/optiga/optiga.c @@ -23,8 +23,10 @@ #include #include #include +#include #include #include +#include // Set this to 1 for a more convenience during development. // Factory setup will be performed in the normal firmware, which makes it easier to tinker with the @@ -62,6 +64,10 @@ // See Solution Reference Manual Table 79 "Data structure arbitrary data object". #define ARBITRARY_DATA_OBJECT_TYPE_3_MAX_SIZE 140 +// This number of KDF iterations on the external kdf slot when stretching the device +// password. +#define KDF_NUM_ITERATIONS (2) + // Struct stored in the arbitrary data object. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpacked" @@ -1168,7 +1174,7 @@ int optiga_kdf_external(const uint8_t* msg, size_t len, uint8_t* mac_out) return 0; } -int optiga_kdf_internal(const uint8_t* msg, size_t len, uint8_t* kdf_out) +static int _kdf_internal(const uint8_t* msg, size_t len, uint8_t* kdf_out) { if (len != 32) { return SC_ERR_INVALID_ARGS; @@ -1201,6 +1207,55 @@ int optiga_kdf_internal(const uint8_t* msg, size_t len, uint8_t* kdf_out) return 0; } +int optiga_stretch_password(const char* password, uint8_t* stretched_out) +{ + uint8_t password_salted_hashed[32] = {0}; + UTIL_CLEANUP_32(password_salted_hashed); + if (!salt_hash_data( + (const uint8_t*)password, + strlen(password), + "optiga_password_stretch_in", + password_salted_hashed)) { + return SC_ERR_SALT; + } + + uint8_t kdf_in[32] = {0}; + UTIL_CLEANUP_32(kdf_in); + memcpy(kdf_in, password_salted_hashed, 32); + + // First KDF on internal key increments the monotonic counter. Call only once! + int securechip_result = _kdf_internal(kdf_in, 32, stretched_out); + if (securechip_result) { + return securechip_result; + } + // Second KDF does not use the counter and we call it multiple times. + for (int i = 0; i < KDF_NUM_ITERATIONS; i++) { + memcpy(kdf_in, stretched_out, 32); + securechip_result = optiga_kdf_external(kdf_in, 32, stretched_out); + if (securechip_result) { + return securechip_result; + } + } + + if (!salt_hash_data( + (const uint8_t*)password, + strlen(password), + "optiga_password_stretch_out", + password_salted_hashed)) { + return SC_ERR_SALT; + } + if (wally_hmac_sha256( + password_salted_hashed, + sizeof(password_salted_hashed), + stretched_out, + 32, + stretched_out, + 32) != WALLY_OK) { + return SC_ERR_HASH; + } + return 0; +} + bool optiga_gen_attestation_key(uint8_t* pubkey_out) { ABORT_IF_NULL(_crypt); diff --git a/src/optiga/optiga.h b/src/optiga/optiga.h index 19545ccb2..6a53c313c 100644 --- a/src/optiga/optiga.h +++ b/src/optiga/optiga.h @@ -28,7 +28,7 @@ USE_RESULT int optiga_setup(const securechip_interface_functions_t* ifs); USE_RESULT bool optiga_update_keys(void); USE_RESULT int optiga_kdf_external(const uint8_t* msg, size_t len, uint8_t* mac_out); -USE_RESULT int optiga_kdf_internal(const uint8_t* msg, size_t len, uint8_t* mac_out); +USE_RESULT int optiga_stretch_password(const char* password, uint8_t* stretched_out); USE_RESULT bool optiga_gen_attestation_key(uint8_t* pubkey_out); USE_RESULT bool optiga_attestation_sign(const uint8_t* challenge, uint8_t* signature_out); USE_RESULT bool optiga_monotonic_increments_remaining(uint32_t* remaining_out); diff --git a/src/securechip/securechip.c b/src/securechip/securechip.c index 91463a857..4e60ef6ed 100644 --- a/src/securechip/securechip.c +++ b/src/securechip/securechip.c @@ -23,7 +23,7 @@ typedef struct { int (*setup)(const securechip_interface_functions_t* fns); bool (*update_keys)(void); int (*kdf)(const uint8_t* msg, size_t msg_len, uint8_t* kdf_out); - int (*kdf_rollkey)(const uint8_t* msg, size_t msg_len, uint8_t* kdf_out); + int (*stretch_password)(const char* password, uint8_t* stretched_out); bool (*gen_attestation_key)(uint8_t* pubkey_out); bool (*attestation_sign)(const uint8_t* challenge, uint8_t* signature_out); bool (*monotonic_increments_remaining)(uint32_t* remaining_out); @@ -47,7 +47,7 @@ bool securechip_init(void) _fns.setup = optiga_setup; _fns.update_keys = optiga_update_keys; _fns.kdf = optiga_kdf_external; - _fns.kdf_rollkey = optiga_kdf_internal; + _fns.stretch_password = optiga_stretch_password; _fns.gen_attestation_key = optiga_gen_attestation_key; _fns.attestation_sign = optiga_attestation_sign; _fns.monotonic_increments_remaining = optiga_monotonic_increments_remaining; @@ -65,7 +65,7 @@ bool securechip_init(void) _fns.setup = atecc_setup; _fns.update_keys = atecc_update_keys; _fns.kdf = atecc_kdf; - _fns.kdf_rollkey = atecc_kdf_rollkey; + _fns.stretch_password = atecc_stretch_password; _fns.gen_attestation_key = atecc_gen_attestation_key; _fns.attestation_sign = atecc_attestation_sign; _fns.monotonic_increments_remaining = atecc_monotonic_increments_remaining; @@ -107,10 +107,10 @@ int securechip_kdf(const uint8_t* msg, size_t msg_len, uint8_t* mac_out) return _fns.kdf(msg, msg_len, mac_out); } -int securechip_kdf_rollkey(const uint8_t* msg, size_t msg_len, uint8_t* mac_out) +int securechip_stretch_password(const char* password, uint8_t* stretched_out) { - ABORT_IF_NULL(kdf_rollkey); - return _fns.kdf_rollkey(msg, msg_len, mac_out); + ABORT_IF_NULL(stretch_password); + return _fns.stretch_password(password, stretched_out); } bool securechip_gen_attestation_key(uint8_t* pubkey_out) diff --git a/src/securechip/securechip.h b/src/securechip/securechip.h index 4925a82ee..56f1368eb 100644 --- a/src/securechip/securechip.h +++ b/src/securechip/securechip.h @@ -27,6 +27,8 @@ typedef enum { SC_ERR_IFS = -1, SC_ERR_INVALID_ARGS = -2, SC_ERR_CONFIG_MISMATCH = -3, + SC_ERR_SALT = -4, + SC_ERR_HASH = -5, // Errors specific to the ATECC SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG = -100, @@ -85,6 +87,7 @@ USE_RESULT bool securechip_update_keys(void); /** * Perform KDF using the key in kdf slot with the input msg. + * This must not increment a monotonic counter. * @param[in] msg Use this msg as input * @param[in] len Must be <= 127. * @param[out] kdf_out Must have size 32. Result of the kdf will be stored here. @@ -95,7 +98,7 @@ USE_RESULT bool securechip_update_keys(void); USE_RESULT int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out); /** - * Perform KDF using the key in rollkey slot with the input msg. + * Stretch password using secrets in the secure chip. * Calling this function increments the monotonic counter. * @param[in] msg Use this msg as input * @param[in] len Must be <= 127. @@ -104,7 +107,7 @@ USE_RESULT int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out); * @return 0 on success. Values of `securechip_error_t` if negative. If positive, values of * `ATCA_STATUS` for ATECC, values of optiga_lib_return_codes.h for Optiga. */ -USE_RESULT int securechip_kdf_rollkey(const uint8_t* msg, size_t len, uint8_t* kdf_out); +USE_RESULT int securechip_stretch_password(const char* password, uint8_t* stretched_out); /** * Generates a new attestation device key and outputs the public key. diff --git a/test/simulator/framework/mock_securechip.c b/test/simulator/framework/mock_securechip.c index 208459ebc..f088961ef 100644 --- a/test/simulator/framework/mock_securechip.c +++ b/test/simulator/framework/mock_securechip.c @@ -29,11 +29,6 @@ bool securechip_update_keys(void) return true; } -// Mocked contents of the secure chip rollkey slot. -static const uint8_t _rollkey[32] = - "\x9d\xd1\x34\x1f\x6b\x4b\x26\xb1\x72\x89\xa1\xa3\x92\x71\x5c\xf0\xd0\x57\x8c\x84\xdb\x9a\x51" - "\xeb\xde\x14\x24\x06\x69\xd1\xd0\x5e"; - // Mocked contents of the securechip kdf slot. static const uint8_t _kdfkey[32] = "\xd2\xe1\xe6\xb1\x8b\x6c\x6b\x08\x43\x3e\xdb\xc1\xd1\x68\xc1\xa0\x04\x37\x74\xa4\x22\x18\x77" @@ -44,9 +39,9 @@ int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out) wally_hmac_sha256(_kdfkey, 32, msg, len, kdf_out, 32); return 0; } -int securechip_kdf_rollkey(const uint8_t* msg, size_t len, uint8_t* kdf_out) +int securechip_stretch_password(const char* password, uint8_t* stretched_out) { - wally_hmac_sha256(_rollkey, 32, msg, len, kdf_out, 32); + memset(stretched_out, 0, 32); return 0; } diff --git a/test/unit-test/framework/mock_securechip.c b/test/unit-test/framework/mock_securechip.c index ca1c98a42..09ed1c6d2 100644 --- a/test/unit-test/framework/mock_securechip.c +++ b/test/unit-test/framework/mock_securechip.c @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -50,7 +51,13 @@ int securechip_kdf_rollkey(const uint8_t* msg, size_t len, uint8_t* kdf_out) wally_hmac_sha256(_rollkey, 32, msg, len, kdf_out, 32); return 0; } - +int securechip_stretch_password(const char* password, uint8_t* stretched_out) +{ + uint8_t key[9] = "unit-test"; + wally_hmac_sha256( + key, sizeof(key), (const uint8_t*)password, strlen(password), stretched_out, 32); + return 0; +} bool securechip_u2f_counter_set(uint32_t counter) { _u2f_counter = counter; diff --git a/test/unit-test/test_keystore.c b/test/unit-test/test_keystore.c index 1dc109a2f..624dd663a 100644 --- a/test/unit-test/test_keystore.c +++ b/test/unit-test/test_keystore.c @@ -86,9 +86,12 @@ const uint8_t _expected_retained_bip39_seed_secret[32] = "\x85\x6d\x9a\x8c\x1e\xa4\x2a\x69\xae\x76\x32\x42\x44\xac\xe6\x74\x39\x7f\xf1\x36\x0a\x4b\xa4" "\xc8\x5f\xfb\xd4\x2c\xee\x8a\x7f\x29"; +// Same as Python: +// import hmac, hashlib; hmac.digest(b"unit-test", b"password", hashlib.sha256).hex() +// See also: securechip_mock.c static uint8_t _expected_secret[32] = - "\xa8\xf4\xfe\x54\x33\x0e\x1a\xb7\xa0\xe3\xbe\x8a\x8d\x75\xd2\x22\xb2\xae\xc2\xb3\xab\x41\xca" - "\x2a\x04\x0e\xa0\x08\x60\x6b\xaf\xce"; + "\xe5\x6d\xe4\x48\xf5\xf1\xd2\x9c\xdc\xc0\xe0\x09\x90\x07\x30\x9a\xfe\x4d\x5a\x3e\xf2\x34\x9e" + "\x99\xdc\xc4\x18\x40\xad\x98\x40\x9e"; int __real_secp256k1_anti_exfil_sign( const secp256k1_context* ctx, From be2ea728b5c6e6a23df19cb4b0fbf7506ebd5c86 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Sat, 14 Dec 2024 23:16:28 +0100 Subject: [PATCH 4/5] securechip/optiga: limit unlock to 10 failed attempts The MCU limits unlock attempts to 10 before resetting. The ATECC securechip further limits the total unlock attempts (successful or failed) to a large monotonic counter, ~730k. In the Optiga we have the same, but with a lower limit due to the chip spec (~600k). This commit additionally adds a small counter that limits the unlocks to 10 failed attempts, same as the MCU. When the counte reaches the limit, no further attempts are possible until reset. When the correct password is entered, the small counter resets to 0. To achieve this, three new slots are added that specifically deal with this small counter. The PASSWORD_SECRET is a key included in the password key stretch, so overwriting/resetting invalidates the password and makes it impossible to unlock or brute force. The PASSWORD_SECRET is initialized to a random value when a password is set. It also authorizes changing the PASSWORD object and the PASSWORD_COUNTER small counter in order to reset it. It can only be read when authorized using the PASSWORD object, i.e. when entering the correct password, which allows us to reset the counter and also to use the PASSWORD_SECRET in the password stretching. The approach is inspired by Trezor, which has a similar setup a small monotonic counter: https://github.com/trezor/trezor-firmware/blob/78cce0ba04436b2ec8a72d00d157a1dd37055572/core/embed/sec/optiga/optiga.c#L35 securechip_init_new_password and securechip_reset_keys are added to the securechip interface to set the new password and to reset all keys involved in the key stretch and the password. When the small counter threshold is reached, further attempts always fail until reset. The MCU also keeps track of the 10 attempts and resets after 10 failed ones, so the securechip error condition is never exercised, similar to the large lifetime counter. --- src/atecc/atecc.c | 25 +- src/atecc/atecc.h | 3 +- src/factorysetup.c | 6 +- src/keystore.c | 18 +- src/optiga/optiga.c | 558 ++++++++++++++++++++- src/optiga/optiga.h | 3 +- src/reset.c | 8 +- src/securechip/securechip.c | 27 +- src/securechip/securechip.h | 29 +- test/simulator/framework/mock_securechip.c | 10 +- test/unit-test/framework/mock_securechip.c | 10 +- 11 files changed, 619 insertions(+), 78 deletions(-) diff --git a/src/atecc/atecc.c b/src/atecc/atecc.c index e33ed88fa..27fa8a209 100644 --- a/src/atecc/atecc.c +++ b/src/atecc/atecc.c @@ -522,14 +522,6 @@ static ATCA_STATUS _update_kdf_key(void) ATECC_SLOT_KDF, 0, new_key, encryption_key, ATECC_SLOT_ENCRYPTION_KEY, nonce_contribution); } -bool atecc_update_keys(void) -{ - if (_rollkey() != ATCA_SUCCESS) { - return false; - } - return _update_kdf_key() == ATCA_SUCCESS; -} - static int _atecc_kdf(atecc_slot_t slot, const uint8_t* msg, size_t len, uint8_t* kdf_out) { if (len > 127 || (slot != ATECC_SLOT_ROLLKEY && slot != ATECC_SLOT_KDF)) { @@ -592,6 +584,15 @@ int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out) return _atecc_kdf(ATECC_SLOT_KDF, msg, len, kdf_out); } +int atecc_init_new_password(const char* password) +{ + (void)password; + if (!atecc_reset_keys()) { + return SC_ATECC_ERR_RESET_KEYS; + } + return 0; +} + int atecc_stretch_password(const char* password, uint8_t* stretched_out) { uint8_t password_salted_hashed[32] = {0}; @@ -641,6 +642,14 @@ int atecc_stretch_password(const char* password, uint8_t* stretched_out) return 0; } +bool atecc_reset_keys(void) +{ + if (_rollkey() != ATCA_SUCCESS) { + return false; + } + return _update_kdf_key() == ATCA_SUCCESS; +} + bool atecc_gen_attestation_key(uint8_t* pubkey_out) { ATCA_STATUS result = _authorize_key(); diff --git a/src/atecc/atecc.h b/src/atecc/atecc.h index d83bf8579..6155d0ef3 100644 --- a/src/atecc/atecc.h +++ b/src/atecc/atecc.h @@ -26,9 +26,10 @@ #include USE_RESULT int atecc_setup(const securechip_interface_functions_t* ifs); -USE_RESULT bool atecc_update_keys(void); USE_RESULT int atecc_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out); +USE_RESULT int atecc_init_new_password(const char* password); USE_RESULT int atecc_stretch_password(const char* password, uint8_t* stretched_out); +USE_RESULT bool atecc_reset_keys(void); USE_RESULT bool atecc_gen_attestation_key(uint8_t* pubkey_out); USE_RESULT bool atecc_attestation_sign(const uint8_t* challenge, uint8_t* signature_out); USE_RESULT bool atecc_monotonic_increments_remaining(uint32_t* remaining_out); diff --git a/src/factorysetup.c b/src/factorysetup.c index a112e4b3f..39ba36773 100644 --- a/src/factorysetup.c +++ b/src/factorysetup.c @@ -277,12 +277,12 @@ static void _api_msg(const uint8_t* input, size_t in_len, uint8_t* output, size_ screen_print_debug("DONE", 0); break; case OP_SC_ROLLKEYS: - if (!securechip_update_keys()) { - screen_print_debug("rollkeys: failed", 0); + if (!securechip_reset_keys()) { + screen_print_debug("resetting securechip keys: failed", 0); result = ERR_FAILED; break; } - screen_print_debug("rollkeys: success", 100); + screen_print_debug("resetting securechip keys: success", 100); if (!securechip_u2f_counter_set(0)) { screen_print_debug("reset u2f counter", 0); result = ERR_FAILED; diff --git a/src/keystore.c b/src/keystore.c index ac7ad54eb..c6d5b0949 100644 --- a/src/keystore.c +++ b/src/keystore.c @@ -181,10 +181,17 @@ static keystore_error_t _get_and_decrypt_seed( uint8_t secret[32]; UTIL_CLEANUP_32(secret); int stretch_result = securechip_stretch_password(password, secret); - if (securechip_result_out != NULL) { - *securechip_result_out = stretch_result; - } if (stretch_result) { + if (stretch_result == SC_ERR_INCORRECT_PASSWORD) { + // Our Optiga securechip implementation fails password stretching if the password is + // wrong, so we can early-abort here. The ATECC stretches the password without checking + // if the password is correct, and we determine if it is correct in the seed decryption + // step below. + return KEYSTORE_ERR_INCORRECT_PASSWORD; + } + if (securechip_result_out != NULL) { + *securechip_result_out = stretch_result; + } return KEYSTORE_ERR_SECURECHIP; } if (encrypted_len < 49) { @@ -239,10 +246,7 @@ keystore_error_t keystore_encrypt_and_store_seed( if (!_validate_seed_length(seed_length)) { return KEYSTORE_ERR_SEED_SIZE; } - // Update the two kdf keys before setting a new password. This already - // happens on a device reset, but we do it here again anyway so the keys are - // initialized also on first use, reducing trust in the factory setup. - if (!securechip_update_keys()) { + if (securechip_init_new_password(password)) { return KEYSTORE_ERR_SECURECHIP; } uint8_t secret[32] = {0}; diff --git a/src/optiga/optiga.c b/src/optiga/optiga.c index 501061472..a57e9783a 100644 --- a/src/optiga/optiga.c +++ b/src/optiga/optiga.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -34,11 +35,15 @@ // Must be 0 for the production firmware releases. #define FACTORY_DURING_PROD 0 -// Number of times the first kdf slot can be used. +// Number of times the first kdf slot can be used over the lifetime of the device. // The maxmimum does not seem to be specified, so we use something a little below the endurance // indication of 600000 updates. See Solution Reference Manual Figure 32. #define MONOTONIC_COUNTER_MAX_USE (590000) +// Number of times the password can be entered incorrectly before further password stretching fails. +// The counter is reset when the correct password is entered. +#define SMALL_MONOTONIC_COUNTER_MAX_USE (MAX_UNLOCK_ATTEMPTS) + // The Data Object IDs we use. // Stores a shared secret used for a shielded connection. Is is used to encrypt @@ -61,6 +66,21 @@ // computations return an error. #define OID_COUNTER 0xE120 +// The three objects below (`OID_PASSWORD_SECRET`, `OID_PASSWORD`, `OID_COUNTER_PASSWORD`) deal with +// implementing the small monotonic counter that limits the number of unlocks to a small number. + +// A random shared key which authorizes updating `OID_PASSWORD` and `OID_COUNTER_PASSWORD`. +// It is also part of the password stretching. +#define OID_PASSWORD_SECRET 0xF1D2 +// A hmac digest of the device password. +// Authorizes reading `OID_PASSWORD`. Monotonic counter `OID_COUNTER_PASSWORD` attached to +// authorization, which limits the number of unlock attempts to a small number. +#define OID_PASSWORD 0xF1D3 +// Monotonic counter with a small limit. Every password stretch increments the counter. A correct +// password resets the counter. When the threshold `SMALL_MONOTONIC_COUNTER_MAX_USE` is reached, +// further password stretches return an error. +#define OID_COUNTER_PASSWORD 0xE121 + // See Solution Reference Manual Table 79 "Data structure arbitrary data object". #define ARBITRARY_DATA_OBJECT_TYPE_3_MAX_SIZE 140 @@ -310,6 +330,133 @@ static const uint8_t _counter_metadata[] = { 0x01, 0x00, }; + +static const uint8_t _password_secret_metadata[] = { + // Metadata tag in the data object + 0x20, + // Number of bytes that follow + 25, + // Set LcsO. Refer to macro to see the value or some more notes. + 0xC0, + 0x01, + FINAL_LCSO_STATE, + // Allow writes, enforce shielded connection. + 0xD0, + 0x03, + 0x20, + 0xE1, + 0x40, + // Allow reads, auth referencing 0xF1D3 (`OID_PASSWORD`), enforce shielded connection + 0xD1, + 0x07, + 0x23, + 0xF1, + 0xD3, + // && + 0xFD, + 0x20, + 0xE1, + 0x40, + // Execute: enforce shielded connection + 0xD3, + 0x03, + 0x20, + 0xE1, + 0x40, + // Data object type set to AUTOREF + 0xE8, + 0x01, + 0x31, +}; + +static const uint8_t _password_metadata[] = { + // Metadata tag in the data object + 0x20, + // Number of bytes that follow + 27, + // Set LcsO. Refer to macro to see the value or some more notes. + 0xC0, + 0x01, + FINAL_LCSO_STATE, + // Allow writes, auth referencing 0xF1D2 (`OID_PASSWORD_SECRET`), enforce shielded connection. + 0xD0, + 0x07, + 0x23, + 0xF1, + 0xD2, + // && + 0xFD, + 0x20, + 0xE1, + 0x40, + // Disallow reads + 0xD1, + 0x01, + 0xFF, + // Attach execution to counter at 0xE121 (`OID_COUNTER_PASSWORD`) and enforce shielded + // connection. + 0xD3, + 0x07, + 0x40, + 0xE1, + 0x21, + // && + 0xFD, + // Enforce shielded connection. According to 4.4.1.7 "EncryptSym" shielded protection is + // enforced anyway, but better to be explicit. + 0x20, + 0xE1, + 0x40, + // Data object type set to AUTOREF + 0xE8, + 0x01, + 0x31, +}; + +#if SMALL_MONOTONIC_COUNTER_MAX_USE > 255 +#error Max unlock attempts does not fit in one byte +#endif +// The intial/reset buffer for the small monotonic counter. Initial value: 0, threshold: +// `SMALL_MONOTONIC_COUNTER_MAX_USE`. Table "Common data structures" -> "Counter": +// https://github.com/Infineon/optiga-trust-m-overview/blob/98b2b9c178f0391b1ab26b52082899704dab688a/docs/OPTIGA%E2%84%A2%20Trust%20M%20Solution%20Reference%20Manual.md#link24b48059_db81_40f5_8b65_7afca4918ab1 +// Bytes 0-3 are the initial counter value, set to 0. +// Bytes 4-7 are the threshold. +// Ints are encoded as uint32 big endian. +static const uint8_t _counter_password_reset_buf[8] = + {0, 0, 0, 0, 0, 0, 0, SMALL_MONOTONIC_COUNTER_MAX_USE}; + +static const uint8_t _counter_password_metadata[] = { + // Metadata tag in the data object + 0x20, + // Number of bytes that follow + 20, + // Set LcsO. Refer to macro to see the value or some more notes. + 0xC0, + 0x01, + FINAL_LCSO_STATE, + // Allow writes, auth referencing 0xF1D2 (`OID_PASSWORD_SECRET`), enforce shielded connection. + 0xD0, + 0x07, + 0x23, + 0xF1, + 0xD2, + // && + 0xFD, + 0x20, + 0xE1, + 0x40, + // Allow reads, enforce shielded connection + 0xD1, + 0x03, + 0x20, + 0xE1, + 0x40, + // Allow exe + 0xD3, + 0x01, + 0x00, +}; + // // Sync wrappers around optiga util/crypt functions // @@ -547,6 +694,85 @@ static optiga_lib_status_t _optiga_crypt_symmetric_generate_key_sync( return res; } +static optiga_lib_status_t _optiga_crypt_generate_auth_code_sync( + optiga_crypt_t* me, + optiga_rng_type_t rng_type, + const uint8_t* optional_data, + uint16_t optional_data_length, + uint8_t* random_data, + uint16_t random_data_length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_crypt_generate_auth_code( + me, rng_type, optional_data, optional_data_length, random_data, random_data_length); + _WAIT(res, _optiga_lib_status); + return res; +} + +static optiga_lib_status_t _optiga_crypt_clear_auto_state_sync(optiga_crypt_t* me, uint16_t secret) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_crypt_clear_auto_state(me, secret); + _WAIT(res, _optiga_lib_status); + return res; +} + +static optiga_lib_status_t _optiga_crypt_hmac_verify_sync( + optiga_crypt_t* me, + optiga_hmac_type_t type, + uint16_t secret, + const uint8_t* input_data, + uint32_t input_data_length, + const uint8_t* hmac, + uint32_t hmac_length) +{ + ABORT_IF_NULL(me); + + _optiga_lib_status = OPTIGA_LIB_BUSY; + optiga_lib_status_t res = optiga_crypt_hmac_verify( + me, type, secret, input_data, input_data_length, hmac, hmac_length); + _WAIT(res, _optiga_lib_status); + return res; +} + +static int _authorize(uint16_t oid_auth, const uint8_t* auth_secret, size_t auth_secret_len) +{ + optiga_lib_status_t res; + + uint8_t random_data[32] = {0}; + + res = _optiga_crypt_generate_auth_code_sync( + _crypt, OPTIGA_RNG_TYPE_TRNG, NULL, 0, random_data, sizeof(random_data)); + if (res != OPTIGA_CRYPT_SUCCESS) { + util_log("generate auth code failed: %x", res); + return res; + } + + uint8_t hmac[32] = {0}; + if (wally_hmac_sha256( + auth_secret, auth_secret_len, random_data, sizeof(random_data), hmac, sizeof(hmac)) != + WALLY_OK) { + return 1; + } + res = _optiga_crypt_hmac_verify_sync( + _crypt, + OPTIGA_HMAC_SHA_256, + oid_auth, + random_data, + sizeof(random_data), + hmac, + sizeof(hmac)); + if (res != OPTIGA_CRYPT_SUCCESS) { + util_log("auth failed: %x %x", oid_auth, res); + return res; + } + return 0; +} + #if APP_U2F == 1 || FACTORYSETUP == 1 static bool _read_arbitrary_data(arbitrary_data_t* data_out) { @@ -859,6 +1085,61 @@ static int _configure_object_attestation(void) _util, oid, _attestation_metadata, sizeof(_attestation_metadata)); } +static int _configure_object_password_secret(void) +{ + const uint16_t oid = OID_PASSWORD_SECRET; + + uint8_t lcso = 0; + optiga_lib_status_t res = _read_lcso_of_object(oid, &lcso, false); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + if (lcso >= LCSO_STATE_OPERATIONAL) { + util_log("_configure_object_password_secret: already setup"); + return 0; + } + util_log("_configure_object_password_secret: setting up"); + return _optiga_util_write_metadata_sync( + _util, oid, _password_secret_metadata, sizeof(_password_secret_metadata)); +} + +static int _configure_object_password(void) +{ + const uint16_t oid = OID_PASSWORD; + + uint8_t lcso = 0; + optiga_lib_status_t res = _read_lcso_of_object(oid, &lcso, false); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + if (lcso >= LCSO_STATE_OPERATIONAL) { + util_log("_configure_object_password: already setup"); + return 0; + } + util_log("_configure_object_password: setting up"); + return _optiga_util_write_metadata_sync( + _util, oid, _password_metadata, sizeof(_password_metadata)); +} + +static int _configure_object_counter_password(void) +{ + const uint16_t oid = OID_COUNTER_PASSWORD; + + uint8_t lcso = 0; + optiga_lib_status_t res = _read_lcso_of_object(oid, &lcso, false); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + if (lcso >= LCSO_STATE_OPERATIONAL) { + util_log("_configure_object_counter_password: already setup"); + return 0; + } + util_log("_configure_object_counter_password: setting up"); + + return _optiga_util_write_metadata_sync( + _util, oid, _counter_password_metadata, sizeof(_counter_password_metadata)); +} + static int _factory_write_config(void) { int res_shielded = _setup_shielded_communication(); @@ -893,6 +1174,21 @@ static int _factory_write_config(void) return res; } + res = _configure_object_password_secret(); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + + res = _configure_object_password(); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + + res = _configure_object_counter_password(); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + util_log("write config OK"); return 0; @@ -993,6 +1289,167 @@ static int _verify_metadata( return 0; } +static int _set_password( + const uint8_t* password_secret, + size_t password_secret_len, + const uint8_t* data, + size_t data_len) +{ + optiga_lib_status_t res = _authorize(OID_PASSWORD_SECRET, password_secret, password_secret_len); + if (res != OPTIGA_UTIL_SUCCESS) { + goto cleanup; + } + + res = _optiga_util_write_data_sync( + _util, OID_PASSWORD, OPTIGA_UTIL_ERASE_AND_WRITE, 0x00, data, data_len); + if (res != OPTIGA_UTIL_SUCCESS) { + goto cleanup; + } + + res = _optiga_util_write_data_sync( + _util, + OID_COUNTER_PASSWORD, + OPTIGA_UTIL_ERASE_AND_WRITE, + 0, + _counter_password_reset_buf, + sizeof(_counter_password_reset_buf)); + if (res != OPTIGA_LIB_SUCCESS) { + goto cleanup; + } + +cleanup: { + optiga_lib_status_t res_clear = + _optiga_crypt_clear_auto_state_sync(_crypt, OID_PASSWORD_SECRET); + if (res != OPTIGA_UTIL_SUCCESS) { + return res; + } + return res_clear; +} +} + +int optiga_init_new_password(const char* password) +{ + ABORT_IF_NULL(_ifs); + ABORT_IF_NULL(_ifs->random_32_bytes); + + // Set new hmac key. + uint8_t new_hmac_key[32] = {0}; + _ifs->random_32_bytes(new_hmac_key); + optiga_lib_status_t res = _optiga_util_write_data_sync( + _util, OID_HMAC, OPTIGA_UTIL_ERASE_AND_WRITE, 0x00, new_hmac_key, sizeof(new_hmac_key)); + if (res != OPTIGA_UTIL_SUCCESS) { + util_log("failed updating the hmac key: %x", res); + return res; + } + + // Set new symmetric key. + optiga_key_id_t keyid = OPTIGA_KEY_ID_SECRET_BASED; + res = _optiga_crypt_symmetric_generate_key_sync( + _crypt, OPTIGA_SYMMETRIC_AES_256, OPTIGA_KEY_USAGE_ENCRYPTION, false, &keyid); + if (res != OPTIGA_UTIL_SUCCESS) { + util_log("failed updating the sym key: %x", res); + return res; + } + + uint8_t password_secret[32] = {0}; + _ifs->random_32_bytes(password_secret); + + res = _optiga_util_write_data_sync( + _util, + OID_PASSWORD_SECRET, + OPTIGA_UTIL_ERASE_AND_WRITE, + 0x00, + password_secret, + sizeof(password_secret)); + if (res != OPTIGA_UTIL_SUCCESS) { + return res; + } + + uint8_t password_salted_hashed[32] = {0}; + UTIL_CLEANUP_32(password_salted_hashed); + if (!salt_hash_data( + (const uint8_t*)password, + strlen(password), + "optiga_password", + password_salted_hashed)) { + return 1; + } + + res = _set_password( + password_secret, + sizeof(password_secret), + password_salted_hashed, + sizeof(password_salted_hashed)); + if (res) { + return res; + } + return 0; +} + +bool optiga_reset_keys(void) +{ + // This resets the OID_AES_SYMKEY and OID_HMAC keys, as well as the OID_PASSWORD_SECRET and + // OID_PASSWORD keys. A password is needed because updating the OID_PASSWORD key requires + // auth using the OID_PASSWORD_SECRET key, but any password is fine for the purpose of resetting + // the keys. + return optiga_init_new_password("") == 0; +} + +static int _optiga_verify_password(const char* password, uint8_t* password_secret_out) +{ + uint8_t password_salted_hashed[32] = {0}; + UTIL_CLEANUP_32(password_salted_hashed); + if (!salt_hash_data( + (const uint8_t*)password, + strlen(password), + "optiga_password", + password_salted_hashed)) { + return 1; + } + + optiga_lib_status_t res = + _authorize(OID_PASSWORD, password_salted_hashed, sizeof(password_salted_hashed)); + if (res != OPTIGA_LIB_SUCCESS) { + goto cleanup; + } + + uint16_t password_secret_size = 32; + res = _optiga_util_read_data_sync( + _util, OID_PASSWORD_SECRET, 0, password_secret_out, &password_secret_size); + if (res != OPTIGA_LIB_SUCCESS || password_secret_size != 32) { + goto cleanup; + } + + res = _authorize(OID_PASSWORD_SECRET, password_secret_out, password_secret_size); + if (res != OPTIGA_LIB_SUCCESS) { + goto cleanup; + } + + res = _optiga_util_write_data_sync( + _util, + OID_COUNTER_PASSWORD, + OPTIGA_UTIL_ERASE_AND_WRITE, + 0, + _counter_password_reset_buf, + sizeof(_counter_password_reset_buf)); + if (res != OPTIGA_LIB_SUCCESS) { + goto cleanup; + } + +cleanup: { + optiga_lib_status_t res_clear1 = _optiga_crypt_clear_auto_state_sync(_crypt, OID_PASSWORD); + optiga_lib_status_t res_clear2 = + _optiga_crypt_clear_auto_state_sync(_crypt, OID_PASSWORD_SECRET); + if (res != OPTIGA_UTIL_SUCCESS) { + return res; + } + if (res_clear1) { + return res_clear1; + } + return res_clear2; +} +} + static int _verify_config(void) { int res; @@ -1086,6 +1543,62 @@ static int _verify_config(void) return res; } } + { + const uint8_t check_tags[] = {0xC0, 0xD0, 0xD1, 0xD3, 0xE8}; + res = _verify_metadata( + OID_PASSWORD_SECRET, + _password_secret_metadata, + sizeof(_password_secret_metadata), + check_tags, + sizeof(check_tags)); + if (res) { + return res; + } + } + { + const uint8_t check_tags[] = {0xC0, 0xD0, 0xD1, 0xD3, 0xE8}; + res = _verify_metadata( + OID_PASSWORD, + _password_metadata, + sizeof(_password_metadata), + check_tags, + sizeof(check_tags)); + if (res) { + return res; + } + } + { + const uint8_t check_tags[] = {0xC0, 0xD0, 0xD1, 0xD3}; + res = _verify_metadata( + OID_COUNTER_PASSWORD, + _counter_password_metadata, + sizeof(_counter_password_metadata), + check_tags, + sizeof(check_tags)); + if (res) { + return res; + } + } + + /* int pw_res = optiga_init_new_password("password"); */ + /* if (pw_res) { */ + /* util_log("set_password failed: %d", pw_res); */ + /* } else { */ + /* util_log("set password OK"); */ + /* } */ + /* res = _optiga_verify_password("password"); */ + /* if (res) { */ + /* util_log("verify_password failed: %x", (unsigned int)res); */ + /* } */ + + uint8_t buf[4] = {0}; + uint16_t size = sizeof(buf); + res = _optiga_util_read_data_sync(_util, OID_COUNTER_PASSWORD, 0, buf, &size); + if (res != OPTIGA_LIB_SUCCESS) { + return res; + } + util_log("password ctr: %d %d %d %d", buf[0], buf[1], buf[2], buf[3]); + return 0; } @@ -1123,32 +1636,6 @@ int optiga_setup(const securechip_interface_functions_t* ifs) return _verify_config(); } -bool optiga_update_keys(void) -{ - ABORT_IF_NULL(_ifs); - ABORT_IF_NULL(_ifs->random_32_bytes); - - uint8_t new_key[32] = {0}; - _ifs->random_32_bytes(new_key); - - optiga_lib_status_t res = _optiga_util_write_data_sync( - _util, OID_HMAC, OPTIGA_UTIL_ERASE_AND_WRITE, 0x00, new_key, sizeof(new_key)); - if (res != OPTIGA_UTIL_SUCCESS) { - util_log("failed updating the hmac key: %x", res); - return false; - } - - optiga_key_id_t keyid = OPTIGA_KEY_ID_SECRET_BASED; - res = _optiga_crypt_symmetric_generate_key_sync( - _crypt, OPTIGA_SYMMETRIC_AES_256, OPTIGA_KEY_USAGE_ENCRYPTION, false, &keyid); - if (res != OPTIGA_UTIL_SUCCESS) { - util_log("failed updating the sym key: %x", res); - return false; - } - - return true; -} - int optiga_kdf_external(const uint8_t* msg, size_t len, uint8_t* mac_out) { if (len != 32) { @@ -1237,6 +1724,23 @@ int optiga_stretch_password(const char* password, uint8_t* stretched_out) } } + // Verify password incrementing the small monotonic counter. + // We do this after the above KDF stretch so the big monotonic counter is also incremented. + uint8_t password_secret[32] = {0}; + int res = _optiga_verify_password(password, password_secret); + if (res) { + if (res == 0x802F) { + return SC_ERR_INCORRECT_PASSWORD; + } + return res; + } + + if (wally_hmac_sha256( + password_secret, sizeof(password_secret), stretched_out, 32, stretched_out, 32) != + WALLY_OK) { + return SC_ERR_HASH; + } + if (!salt_hash_data( (const uint8_t*)password, strlen(password), diff --git a/src/optiga/optiga.h b/src/optiga/optiga.h index 6a53c313c..3a4900ae0 100644 --- a/src/optiga/optiga.h +++ b/src/optiga/optiga.h @@ -26,9 +26,10 @@ #include USE_RESULT int optiga_setup(const securechip_interface_functions_t* ifs); -USE_RESULT bool optiga_update_keys(void); USE_RESULT int optiga_kdf_external(const uint8_t* msg, size_t len, uint8_t* mac_out); +USE_RESULT int optiga_init_new_password(const char* password); USE_RESULT int optiga_stretch_password(const char* password, uint8_t* stretched_out); +USE_RESULT bool optiga_reset_keys(void); USE_RESULT bool optiga_gen_attestation_key(uint8_t* pubkey_out); USE_RESULT bool optiga_attestation_sign(const uint8_t* challenge, uint8_t* signature_out); USE_RESULT bool optiga_monotonic_increments_remaining(uint32_t* remaining_out); diff --git a/src/reset.c b/src/reset.c index c843ba49d..feb52cd8b 100644 --- a/src/reset.c +++ b/src/reset.c @@ -48,14 +48,14 @@ void reset_reset(bool status) { keystore_lock(); #if !defined(TESTING) - bool sc_result_update_keys = false; + bool sc_result_reset_keys = false; for (int retries = 0; retries < 5; retries++) { - sc_result_update_keys = securechip_update_keys(); - if (sc_result_update_keys) { + sc_result_reset_keys = securechip_reset_keys(); + if (sc_result_reset_keys) { break; } } - if (!sc_result_update_keys) { + if (!sc_result_reset_keys) { Abort("Could not reset secure chip."); } #if APP_U2F == 1 diff --git a/src/securechip/securechip.c b/src/securechip/securechip.c index 4e60ef6ed..11937554a 100644 --- a/src/securechip/securechip.c +++ b/src/securechip/securechip.c @@ -21,9 +21,10 @@ typedef struct { int (*setup)(const securechip_interface_functions_t* fns); - bool (*update_keys)(void); int (*kdf)(const uint8_t* msg, size_t msg_len, uint8_t* kdf_out); + int (*init_new_password)(const char* password); int (*stretch_password)(const char* password, uint8_t* stretched_out); + bool (*reset_keys)(void); bool (*gen_attestation_key)(uint8_t* pubkey_out); bool (*attestation_sign)(const uint8_t* challenge, uint8_t* signature_out); bool (*monotonic_increments_remaining)(uint32_t* remaining_out); @@ -45,9 +46,10 @@ bool securechip_init(void) switch (memory_get_securechip_type()) { case MEMORY_SECURECHIP_TYPE_OPTIGA: _fns.setup = optiga_setup; - _fns.update_keys = optiga_update_keys; _fns.kdf = optiga_kdf_external; + _fns.init_new_password = optiga_init_new_password; _fns.stretch_password = optiga_stretch_password; + _fns.reset_keys = optiga_reset_keys; _fns.gen_attestation_key = optiga_gen_attestation_key; _fns.attestation_sign = optiga_attestation_sign; _fns.monotonic_increments_remaining = optiga_monotonic_increments_remaining; @@ -63,9 +65,10 @@ bool securechip_init(void) case MEMORY_SECURECHIP_TYPE_ATECC: default: _fns.setup = atecc_setup; - _fns.update_keys = atecc_update_keys; _fns.kdf = atecc_kdf; + _fns.init_new_password = atecc_init_new_password; _fns.stretch_password = atecc_stretch_password; + _fns.reset_keys = atecc_reset_keys; _fns.gen_attestation_key = atecc_gen_attestation_key; _fns.attestation_sign = atecc_attestation_sign; _fns.monotonic_increments_remaining = atecc_monotonic_increments_remaining; @@ -95,24 +98,30 @@ int securechip_setup(const securechip_interface_functions_t* ifs) return _fns.setup(ifs); } -bool securechip_update_keys(void) -{ - ABORT_IF_NULL(update_keys); - return _fns.update_keys(); -} - int securechip_kdf(const uint8_t* msg, size_t msg_len, uint8_t* mac_out) { ABORT_IF_NULL(kdf); return _fns.kdf(msg, msg_len, mac_out); } +int securechip_init_new_password(const char* password) +{ + ABORT_IF_NULL(init_new_password); + return _fns.init_new_password(password); +} + int securechip_stretch_password(const char* password, uint8_t* stretched_out) { ABORT_IF_NULL(stretch_password); return _fns.stretch_password(password, stretched_out); } +bool securechip_reset_keys(void) +{ + ABORT_IF_NULL(reset_keys); + return _fns.reset_keys(); +} + bool securechip_gen_attestation_key(uint8_t* pubkey_out) { ABORT_IF_NULL(gen_attestation_key); diff --git a/src/securechip/securechip.h b/src/securechip/securechip.h index 56f1368eb..9e3476891 100644 --- a/src/securechip/securechip.h +++ b/src/securechip/securechip.h @@ -29,6 +29,10 @@ typedef enum { SC_ERR_CONFIG_MISMATCH = -3, SC_ERR_SALT = -4, SC_ERR_HASH = -5, + // Currently only used by Optiga, but it is in the common errors so that the API of the + // securechip is consistent and the caller does not need to distinguish between the chips at the + // callsite. + SC_ERR_INCORRECT_PASSWORD = -6, // Errors specific to the ATECC SC_ATECC_ERR_ZONE_UNLOCKED_CONFIG = -100, @@ -36,6 +40,7 @@ typedef enum { SC_ATECC_ERR_SLOT_UNLOCKED_IO = -103, SC_ATECC_ERR_SLOT_UNLOCKED_AUTH = -104, SC_ATECC_ERR_SLOT_UNLOCKED_ENC = -105, + SC_ATECC_ERR_RESET_KEYS = -106, // Errors specific to the Optiga SC_OPTIGA_ERR_CREATE = -201, @@ -77,14 +82,6 @@ USE_RESULT bool securechip_init(void); */ USE_RESULT int securechip_setup(const securechip_interface_functions_t* ifs); -/** - * Updates the two KDF keys (rollkey and kdf key). The previous keys are lost - * and cannot be restored. Calling this function does not increment the - * monotonic counter. - * @return true on success. - */ -USE_RESULT bool securechip_update_keys(void); - /** * Perform KDF using the key in kdf slot with the input msg. * This must not increment a monotonic counter. @@ -97,6 +94,16 @@ USE_RESULT bool securechip_update_keys(void); */ USE_RESULT int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out); +/** + * Prepare the securechip for a new password: re-initialize keys used in the derivation, + * set up monotonic counters, etc. + * @param[in] password The user password. + * @return For ATECC: values of `atecc_error_t` if negative, values of `ATCA_STATUS` if positive, 0 + * on success. For Optiga: values of `optiga_error_t` if negative, values of + * optiga_lib_return_codes.h if positive, 0 on success. + */ +USE_RESULT int securechip_init_new_password(const char* password); + /** * Stretch password using secrets in the secure chip. * Calling this function increments the monotonic counter. @@ -109,6 +116,12 @@ USE_RESULT int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out); */ USE_RESULT int securechip_stretch_password(const char* password, uint8_t* stretched_out); +/** + * Reset the securechip objects involved in the password stretching. + * @return true on success, false on failure. + */ +USE_RESULT bool securechip_reset_keys(void); + /** * Generates a new attestation device key and outputs the public key. * @param[out] pubkey_out diff --git a/test/simulator/framework/mock_securechip.c b/test/simulator/framework/mock_securechip.c index f088961ef..4bc0e5c4c 100644 --- a/test/simulator/framework/mock_securechip.c +++ b/test/simulator/framework/mock_securechip.c @@ -24,11 +24,6 @@ static uint32_t _u2f_counter; -bool securechip_update_keys(void) -{ - return true; -} - // Mocked contents of the securechip kdf slot. static const uint8_t _kdfkey[32] = "\xd2\xe1\xe6\xb1\x8b\x6c\x6b\x08\x43\x3e\xdb\xc1\xd1\x68\xc1\xa0\x04\x37\x74\xa4\x22\x18\x77" @@ -39,6 +34,11 @@ int securechip_kdf(const uint8_t* msg, size_t len, uint8_t* kdf_out) wally_hmac_sha256(_kdfkey, 32, msg, len, kdf_out, 32); return 0; } +int securechip_init_new_password(const char* password) +{ + (void)password; + return 0; +} int securechip_stretch_password(const char* password, uint8_t* stretched_out) { memset(stretched_out, 0, 32); diff --git a/test/unit-test/framework/mock_securechip.c b/test/unit-test/framework/mock_securechip.c index 09ed1c6d2..1a5e14118 100644 --- a/test/unit-test/framework/mock_securechip.c +++ b/test/unit-test/framework/mock_securechip.c @@ -26,11 +26,6 @@ static uint32_t _u2f_counter; -bool securechip_update_keys(void) -{ - return true; -} - // Mocked contents of the secure chip rollkey slot. static const uint8_t _rollkey[32] = "\x9d\xd1\x34\x1f\x6b\x4b\x26\xb1\x72\x89\xa1\xa3\x92\x71\x5c\xf0\xd0\x57\x8c\x84\xdb\x9a\x51" @@ -51,6 +46,11 @@ int securechip_kdf_rollkey(const uint8_t* msg, size_t len, uint8_t* kdf_out) wally_hmac_sha256(_rollkey, 32, msg, len, kdf_out, 32); return 0; } +int securechip_init_new_password(const char* password) +{ + (void)password; + return 0; +} int securechip_stretch_password(const char* password, uint8_t* stretched_out) { uint8_t key[9] = "unit-test"; From fdab4c31a8e30f3d409a85a33aab22cdcca2ba74 Mon Sep 17 00:00:00 2001 From: asi345 Date: Wed, 18 Dec 2024 23:03:25 +0100 Subject: [PATCH 5/5] src: change password functionality This commit adds changing password feature. The feature can only be used when the device is initialized. It shares a lot of the functionality with set password feature, but it requires the user to enter the old password correctly, and also does some seed handling to not cause any data loss. Signed-off-by: asi345 --- CHANGELOG.md | 1 + messages/bitbox02_system.proto | 3 + messages/hww.proto | 1 + py/bitbox02/bitbox02/bitbox02/bitbox02.py | 11 +++ .../generated/bitbox02_system_pb2.py | 4 +- .../generated/bitbox02_system_pb2.pyi | 6 ++ .../communication/generated/hww_pb2.py | 8 +-- .../communication/generated/hww_pb2.pyi | 10 ++- py/send_message.py | 7 ++ src/keystore.c | 68 ++++++++++++++++--- src/keystore.h | 8 +++ src/rust/bitbox02-rust/src/hww/api.rs | 3 + .../src/hww/api/change_password.rs | 34 ++++++++++ .../bitbox02-rust/src/shiftcrypto.bitbox02.rs | 7 +- src/rust/bitbox02-sys/build.rs | 1 + src/rust/bitbox02/src/keystore.rs | 12 ++++ test/unit-test/test_keystore_functional.c | 30 ++++++++ 17 files changed, 197 insertions(+), 17 deletions(-) create mode 100644 src/rust/bitbox02-rust/src/hww/api/change_password.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index af749713d..f46af7f25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately. - Ethereum: remove deprecated Goerli network - SD card: solve backup bug when sd card is re-inserted - Cardano: allow serialization using 258-tagged sets +- Add change password functionality ### 9.21.0 - Bitcoin: add support for sending to silent payment (BIP-352) addresses diff --git a/messages/bitbox02_system.proto b/messages/bitbox02_system.proto index a51aceef1..dd6dbfba8 100644 --- a/messages/bitbox02_system.proto +++ b/messages/bitbox02_system.proto @@ -56,3 +56,6 @@ message SetDeviceNameRequest { message SetPasswordRequest { bytes entropy = 1; } + +message ChangePasswordRequest { +} diff --git a/messages/hww.proto b/messages/hww.proto index 54c34f6d4..4e3ee8388 100644 --- a/messages/hww.proto +++ b/messages/hww.proto @@ -67,6 +67,7 @@ message Request { ElectrumEncryptionKeyRequest electrum_encryption_key = 26; CardanoRequest cardano = 27; BIP85Request bip85 = 28; + ChangePasswordRequest change_password = 29; } } diff --git a/py/bitbox02/bitbox02/bitbox02/bitbox02.py b/py/bitbox02/bitbox02/bitbox02/bitbox02.py index 768a657e7..20f9d88fb 100644 --- a/py/bitbox02/bitbox02/bitbox02/bitbox02.py +++ b/py/bitbox02/bitbox02/bitbox02/bitbox02.py @@ -190,6 +190,17 @@ def set_password(self, entropy_size: int = 32) -> bool: raise return True + def change_password(self) -> bool: + request = hww.Request() + request.change_password.CopyFrom(bitbox02_system.ChangePasswordRequest()) + try: + self._msg_query(request, expected_response="success") + except Bitbox02Exception as err: + if err.code == ERR_GENERIC: + return False + raise + return True + def create_backup(self) -> bool: """ Returns True if the backup was created successfully. diff --git a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py index 4dbeb63fc..c00bfe3ab 100644 --- a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py +++ b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x62itbox02_system.proto\x12\x14shiftcrypto.bitbox02\"\x14\n\x12\x43heckSDCardRequest\"\'\n\x13\x43heckSDCardResponse\x12\x10\n\x08inserted\x18\x01 \x01(\x08\"\x13\n\x11\x44\x65viceInfoRequest\"\xaf\x01\n\x12\x44\x65viceInfoResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0binitialized\x18\x02 \x01(\x08\x12\x0f\n\x07version\x18\x03 \x01(\t\x12#\n\x1bmnemonic_passphrase_enabled\x18\x04 \x01(\x08\x12&\n\x1emonotonic_increments_remaining\x18\x05 \x01(\r\x12\x18\n\x10securechip_model\x18\x06 \x01(\t\"\x9b\x01\n\x19InsertRemoveSDCardRequest\x12L\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32<.shiftcrypto.bitbox02.InsertRemoveSDCardRequest.SDCardAction\"0\n\x0cSDCardAction\x12\x0f\n\x0bREMOVE_CARD\x10\x00\x12\x0f\n\x0bINSERT_CARD\x10\x01\"\x0e\n\x0cResetRequest\",\n\x18SetDeviceLanguageRequest\x12\x10\n\x08language\x18\x01 \x01(\t\"$\n\x14SetDeviceNameRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"%\n\x12SetPasswordRequest\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x62itbox02_system.proto\x12\x14shiftcrypto.bitbox02\"\x14\n\x12\x43heckSDCardRequest\"\'\n\x13\x43heckSDCardResponse\x12\x10\n\x08inserted\x18\x01 \x01(\x08\"\x13\n\x11\x44\x65viceInfoRequest\"\xaf\x01\n\x12\x44\x65viceInfoResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0binitialized\x18\x02 \x01(\x08\x12\x0f\n\x07version\x18\x03 \x01(\t\x12#\n\x1bmnemonic_passphrase_enabled\x18\x04 \x01(\x08\x12&\n\x1emonotonic_increments_remaining\x18\x05 \x01(\r\x12\x18\n\x10securechip_model\x18\x06 \x01(\t\"\x9b\x01\n\x19InsertRemoveSDCardRequest\x12L\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32<.shiftcrypto.bitbox02.InsertRemoveSDCardRequest.SDCardAction\"0\n\x0cSDCardAction\x12\x0f\n\x0bREMOVE_CARD\x10\x00\x12\x0f\n\x0bINSERT_CARD\x10\x01\"\x0e\n\x0cResetRequest\",\n\x18SetDeviceLanguageRequest\x12\x10\n\x08language\x18\x01 \x01(\t\"$\n\x14SetDeviceNameRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"%\n\x12SetPasswordRequest\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\"\x17\n\x15\x43hangePasswordRequestb\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'bitbox02_system_pb2', globals()) @@ -40,4 +40,6 @@ _SETDEVICENAMEREQUEST._serialized_end=565 _SETPASSWORDREQUEST._serialized_start=567 _SETPASSWORDREQUEST._serialized_end=604 + _CHANGEPASSWORDREQUEST._serialized_start=606 + _CHANGEPASSWORDREQUEST._serialized_end=629 # @@protoc_insertion_point(module_scope) diff --git a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi index 6b6829e2c..15365b09b 100644 --- a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi +++ b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi @@ -124,3 +124,9 @@ class SetPasswordRequest(google.protobuf.message.Message): ) -> None: ... def ClearField(self, field_name: typing_extensions.Literal["entropy",b"entropy"]) -> None: ... global___SetPasswordRequest = SetPasswordRequest + +class ChangePasswordRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + def __init__(self, + ) -> None: ... +global___ChangePasswordRequest = ChangePasswordRequest diff --git a/py/bitbox02/bitbox02/communication/generated/hww_pb2.py b/py/bitbox02/bitbox02/communication/generated/hww_pb2.py index 4606d1d94..9084469f0 100644 --- a/py/bitbox02/bitbox02/communication/generated/hww_pb2.py +++ b/py/bitbox02/bitbox02/communication/generated/hww_pb2.py @@ -23,7 +23,7 @@ from . import perform_attestation_pb2 as perform__attestation__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thww.proto\x12\x14shiftcrypto.bitbox02\x1a\x0c\x63ommon.proto\x1a\x15\x62\x61\x63kup_commands.proto\x1a\x15\x62itbox02_system.proto\x1a\tbtc.proto\x1a\rcardano.proto\x1a\teth.proto\x1a\x0ekeystore.proto\x1a\x0emnemonic.proto\x1a\x0csystem.proto\x1a\x19perform_attestation.proto\"&\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\"\t\n\x07Success\"\xfd\r\n\x07Request\x12\x41\n\x0b\x64\x65vice_name\x18\x02 \x01(\x0b\x32*.shiftcrypto.bitbox02.SetDeviceNameRequestH\x00\x12I\n\x0f\x64\x65vice_language\x18\x03 \x01(\x0b\x32..shiftcrypto.bitbox02.SetDeviceLanguageRequestH\x00\x12>\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32\'.shiftcrypto.bitbox02.DeviceInfoRequestH\x00\x12@\n\x0cset_password\x18\x05 \x01(\x0b\x32(.shiftcrypto.bitbox02.SetPasswordRequestH\x00\x12\x42\n\rcreate_backup\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.CreateBackupRequestH\x00\x12\x42\n\rshow_mnemonic\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ShowMnemonicRequestH\x00\x12\x36\n\x07\x62tc_pub\x18\x08 \x01(\x0b\x32#.shiftcrypto.bitbox02.BTCPubRequestH\x00\x12\x41\n\rbtc_sign_init\x18\t \x01(\x0b\x32(.shiftcrypto.bitbox02.BTCSignInitRequestH\x00\x12\x43\n\x0e\x62tc_sign_input\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignInputRequestH\x00\x12\x45\n\x0f\x62tc_sign_output\x18\x0b \x01(\x0b\x32*.shiftcrypto.bitbox02.BTCSignOutputRequestH\x00\x12O\n\x14insert_remove_sdcard\x18\x0c \x01(\x0b\x32/.shiftcrypto.bitbox02.InsertRemoveSDCardRequestH\x00\x12@\n\x0c\x63heck_sdcard\x18\r \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckSDCardRequestH\x00\x12\x64\n\x1fset_mnemonic_passphrase_enabled\x18\x0e \x01(\x0b\x32\x39.shiftcrypto.bitbox02.SetMnemonicPassphraseEnabledRequestH\x00\x12@\n\x0clist_backups\x18\x0f \x01(\x0b\x32(.shiftcrypto.bitbox02.ListBackupsRequestH\x00\x12\x44\n\x0erestore_backup\x18\x10 \x01(\x0b\x32*.shiftcrypto.bitbox02.RestoreBackupRequestH\x00\x12N\n\x13perform_attestation\x18\x11 \x01(\x0b\x32/.shiftcrypto.bitbox02.PerformAttestationRequestH\x00\x12\x35\n\x06reboot\x18\x12 \x01(\x0b\x32#.shiftcrypto.bitbox02.RebootRequestH\x00\x12@\n\x0c\x63heck_backup\x18\x13 \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckBackupRequestH\x00\x12/\n\x03\x65th\x18\x14 \x01(\x0b\x32 .shiftcrypto.bitbox02.ETHRequestH\x00\x12\x33\n\x05reset\x18\x15 \x01(\x0b\x32\".shiftcrypto.bitbox02.ResetRequestH\x00\x12Q\n\x15restore_from_mnemonic\x18\x16 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.RestoreFromMnemonicRequestH\x00\x12\x43\n\x0b\x66ingerprint\x18\x18 \x01(\x0b\x32,.shiftcrypto.bitbox02.RootFingerprintRequestH\x00\x12/\n\x03\x62tc\x18\x19 \x01(\x0b\x32 .shiftcrypto.bitbox02.BTCRequestH\x00\x12U\n\x17\x65lectrum_encryption_key\x18\x1a \x01(\x0b\x32\x32.shiftcrypto.bitbox02.ElectrumEncryptionKeyRequestH\x00\x12\x37\n\x07\x63\x61rdano\x18\x1b \x01(\x0b\x32$.shiftcrypto.bitbox02.CardanoRequestH\x00\x12\x33\n\x05\x62ip85\x18\x1c \x01(\x0b\x32\".shiftcrypto.bitbox02.BIP85RequestH\x00\x42\t\n\x07requestJ\x04\x08\x01\x10\x02J\x04\x08\x17\x10\x18\"\xbf\x07\n\x08Response\x12\x30\n\x07success\x18\x01 \x01(\x0b\x32\x1d.shiftcrypto.bitbox02.SuccessH\x00\x12,\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x1b.shiftcrypto.bitbox02.ErrorH\x00\x12?\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32(.shiftcrypto.bitbox02.DeviceInfoResponseH\x00\x12\x30\n\x03pub\x18\x05 \x01(\x0b\x32!.shiftcrypto.bitbox02.PubResponseH\x00\x12\x42\n\rbtc_sign_next\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignNextResponseH\x00\x12\x41\n\x0clist_backups\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ListBackupsResponseH\x00\x12\x41\n\x0c\x63heck_backup\x18\x08 \x01(\x0b\x32).shiftcrypto.bitbox02.CheckBackupResponseH\x00\x12O\n\x13perform_attestation\x18\t \x01(\x0b\x32\x30.shiftcrypto.bitbox02.PerformAttestationResponseH\x00\x12\x41\n\x0c\x63heck_sdcard\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.CheckSDCardResponseH\x00\x12\x30\n\x03\x65th\x18\x0b \x01(\x0b\x32!.shiftcrypto.bitbox02.ETHResponseH\x00\x12\x44\n\x0b\x66ingerprint\x18\x0c \x01(\x0b\x32-.shiftcrypto.bitbox02.RootFingerprintResponseH\x00\x12\x30\n\x03\x62tc\x18\r \x01(\x0b\x32!.shiftcrypto.bitbox02.BTCResponseH\x00\x12V\n\x17\x65lectrum_encryption_key\x18\x0e \x01(\x0b\x32\x33.shiftcrypto.bitbox02.ElectrumEncryptionKeyResponseH\x00\x12\x38\n\x07\x63\x61rdano\x18\x0f \x01(\x0b\x32%.shiftcrypto.bitbox02.CardanoResponseH\x00\x12\x34\n\x05\x62ip85\x18\x10 \x01(\x0b\x32#.shiftcrypto.bitbox02.BIP85ResponseH\x00\x42\n\n\x08responseJ\x04\x08\x03\x10\x04\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thww.proto\x12\x14shiftcrypto.bitbox02\x1a\x0c\x63ommon.proto\x1a\x15\x62\x61\x63kup_commands.proto\x1a\x15\x62itbox02_system.proto\x1a\tbtc.proto\x1a\rcardano.proto\x1a\teth.proto\x1a\x0ekeystore.proto\x1a\x0emnemonic.proto\x1a\x0csystem.proto\x1a\x19perform_attestation.proto\"&\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\"\t\n\x07Success\"\xc5\x0e\n\x07Request\x12\x41\n\x0b\x64\x65vice_name\x18\x02 \x01(\x0b\x32*.shiftcrypto.bitbox02.SetDeviceNameRequestH\x00\x12I\n\x0f\x64\x65vice_language\x18\x03 \x01(\x0b\x32..shiftcrypto.bitbox02.SetDeviceLanguageRequestH\x00\x12>\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32\'.shiftcrypto.bitbox02.DeviceInfoRequestH\x00\x12@\n\x0cset_password\x18\x05 \x01(\x0b\x32(.shiftcrypto.bitbox02.SetPasswordRequestH\x00\x12\x42\n\rcreate_backup\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.CreateBackupRequestH\x00\x12\x42\n\rshow_mnemonic\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ShowMnemonicRequestH\x00\x12\x36\n\x07\x62tc_pub\x18\x08 \x01(\x0b\x32#.shiftcrypto.bitbox02.BTCPubRequestH\x00\x12\x41\n\rbtc_sign_init\x18\t \x01(\x0b\x32(.shiftcrypto.bitbox02.BTCSignInitRequestH\x00\x12\x43\n\x0e\x62tc_sign_input\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignInputRequestH\x00\x12\x45\n\x0f\x62tc_sign_output\x18\x0b \x01(\x0b\x32*.shiftcrypto.bitbox02.BTCSignOutputRequestH\x00\x12O\n\x14insert_remove_sdcard\x18\x0c \x01(\x0b\x32/.shiftcrypto.bitbox02.InsertRemoveSDCardRequestH\x00\x12@\n\x0c\x63heck_sdcard\x18\r \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckSDCardRequestH\x00\x12\x64\n\x1fset_mnemonic_passphrase_enabled\x18\x0e \x01(\x0b\x32\x39.shiftcrypto.bitbox02.SetMnemonicPassphraseEnabledRequestH\x00\x12@\n\x0clist_backups\x18\x0f \x01(\x0b\x32(.shiftcrypto.bitbox02.ListBackupsRequestH\x00\x12\x44\n\x0erestore_backup\x18\x10 \x01(\x0b\x32*.shiftcrypto.bitbox02.RestoreBackupRequestH\x00\x12N\n\x13perform_attestation\x18\x11 \x01(\x0b\x32/.shiftcrypto.bitbox02.PerformAttestationRequestH\x00\x12\x35\n\x06reboot\x18\x12 \x01(\x0b\x32#.shiftcrypto.bitbox02.RebootRequestH\x00\x12@\n\x0c\x63heck_backup\x18\x13 \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckBackupRequestH\x00\x12/\n\x03\x65th\x18\x14 \x01(\x0b\x32 .shiftcrypto.bitbox02.ETHRequestH\x00\x12\x33\n\x05reset\x18\x15 \x01(\x0b\x32\".shiftcrypto.bitbox02.ResetRequestH\x00\x12Q\n\x15restore_from_mnemonic\x18\x16 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.RestoreFromMnemonicRequestH\x00\x12\x43\n\x0b\x66ingerprint\x18\x18 \x01(\x0b\x32,.shiftcrypto.bitbox02.RootFingerprintRequestH\x00\x12/\n\x03\x62tc\x18\x19 \x01(\x0b\x32 .shiftcrypto.bitbox02.BTCRequestH\x00\x12U\n\x17\x65lectrum_encryption_key\x18\x1a \x01(\x0b\x32\x32.shiftcrypto.bitbox02.ElectrumEncryptionKeyRequestH\x00\x12\x37\n\x07\x63\x61rdano\x18\x1b \x01(\x0b\x32$.shiftcrypto.bitbox02.CardanoRequestH\x00\x12\x33\n\x05\x62ip85\x18\x1c \x01(\x0b\x32\".shiftcrypto.bitbox02.BIP85RequestH\x00\x12\x46\n\x0f\x63hange_password\x18\x1d \x01(\x0b\x32+.shiftcrypto.bitbox02.ChangePasswordRequestH\x00\x42\t\n\x07requestJ\x04\x08\x01\x10\x02J\x04\x08\x17\x10\x18\"\xbf\x07\n\x08Response\x12\x30\n\x07success\x18\x01 \x01(\x0b\x32\x1d.shiftcrypto.bitbox02.SuccessH\x00\x12,\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x1b.shiftcrypto.bitbox02.ErrorH\x00\x12?\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32(.shiftcrypto.bitbox02.DeviceInfoResponseH\x00\x12\x30\n\x03pub\x18\x05 \x01(\x0b\x32!.shiftcrypto.bitbox02.PubResponseH\x00\x12\x42\n\rbtc_sign_next\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignNextResponseH\x00\x12\x41\n\x0clist_backups\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ListBackupsResponseH\x00\x12\x41\n\x0c\x63heck_backup\x18\x08 \x01(\x0b\x32).shiftcrypto.bitbox02.CheckBackupResponseH\x00\x12O\n\x13perform_attestation\x18\t \x01(\x0b\x32\x30.shiftcrypto.bitbox02.PerformAttestationResponseH\x00\x12\x41\n\x0c\x63heck_sdcard\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.CheckSDCardResponseH\x00\x12\x30\n\x03\x65th\x18\x0b \x01(\x0b\x32!.shiftcrypto.bitbox02.ETHResponseH\x00\x12\x44\n\x0b\x66ingerprint\x18\x0c \x01(\x0b\x32-.shiftcrypto.bitbox02.RootFingerprintResponseH\x00\x12\x30\n\x03\x62tc\x18\r \x01(\x0b\x32!.shiftcrypto.bitbox02.BTCResponseH\x00\x12V\n\x17\x65lectrum_encryption_key\x18\x0e \x01(\x0b\x32\x33.shiftcrypto.bitbox02.ElectrumEncryptionKeyResponseH\x00\x12\x38\n\x07\x63\x61rdano\x18\x0f \x01(\x0b\x32%.shiftcrypto.bitbox02.CardanoResponseH\x00\x12\x34\n\x05\x62ip85\x18\x10 \x01(\x0b\x32#.shiftcrypto.bitbox02.BIP85ResponseH\x00\x42\n\n\x08responseJ\x04\x08\x03\x10\x04\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'hww_pb2', globals()) @@ -35,7 +35,7 @@ _SUCCESS._serialized_start=245 _SUCCESS._serialized_end=254 _REQUEST._serialized_start=257 - _REQUEST._serialized_end=2046 - _RESPONSE._serialized_start=2049 - _RESPONSE._serialized_end=3008 + _REQUEST._serialized_end=2118 + _RESPONSE._serialized_start=2121 + _RESPONSE._serialized_end=3080 # @@protoc_insertion_point(module_scope) diff --git a/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi index df9aeacf1..4809bbd60 100644 --- a/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi +++ b/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi @@ -68,6 +68,7 @@ class Request(google.protobuf.message.Message): ELECTRUM_ENCRYPTION_KEY_FIELD_NUMBER: builtins.int CARDANO_FIELD_NUMBER: builtins.int BIP85_FIELD_NUMBER: builtins.int + CHANGE_PASSWORD_FIELD_NUMBER: builtins.int @property def device_name(self) -> bitbox02_system_pb2.SetDeviceNameRequest: """removed: RandomNumberRequest random_number = 1;""" @@ -124,6 +125,8 @@ class Request(google.protobuf.message.Message): def cardano(self) -> cardano_pb2.CardanoRequest: ... @property def bip85(self) -> keystore_pb2.BIP85Request: ... + @property + def change_password(self) -> bitbox02_system_pb2.ChangePasswordRequest: ... def __init__(self, *, device_name: typing.Optional[bitbox02_system_pb2.SetDeviceNameRequest] = ..., @@ -152,10 +155,11 @@ class Request(google.protobuf.message.Message): electrum_encryption_key: typing.Optional[keystore_pb2.ElectrumEncryptionKeyRequest] = ..., cardano: typing.Optional[cardano_pb2.CardanoRequest] = ..., bip85: typing.Optional[keystore_pb2.BIP85Request] = ..., + change_password: typing.Optional[bitbox02_system_pb2.ChangePasswordRequest] = ..., ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["bip85",b"bip85","btc",b"btc","btc_pub",b"btc_pub","btc_sign_init",b"btc_sign_init","btc_sign_input",b"btc_sign_input","btc_sign_output",b"btc_sign_output","cardano",b"cardano","check_backup",b"check_backup","check_sdcard",b"check_sdcard","create_backup",b"create_backup","device_info",b"device_info","device_language",b"device_language","device_name",b"device_name","electrum_encryption_key",b"electrum_encryption_key","eth",b"eth","fingerprint",b"fingerprint","insert_remove_sdcard",b"insert_remove_sdcard","list_backups",b"list_backups","perform_attestation",b"perform_attestation","reboot",b"reboot","request",b"request","reset",b"reset","restore_backup",b"restore_backup","restore_from_mnemonic",b"restore_from_mnemonic","set_mnemonic_passphrase_enabled",b"set_mnemonic_passphrase_enabled","set_password",b"set_password","show_mnemonic",b"show_mnemonic"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["bip85",b"bip85","btc",b"btc","btc_pub",b"btc_pub","btc_sign_init",b"btc_sign_init","btc_sign_input",b"btc_sign_input","btc_sign_output",b"btc_sign_output","cardano",b"cardano","check_backup",b"check_backup","check_sdcard",b"check_sdcard","create_backup",b"create_backup","device_info",b"device_info","device_language",b"device_language","device_name",b"device_name","electrum_encryption_key",b"electrum_encryption_key","eth",b"eth","fingerprint",b"fingerprint","insert_remove_sdcard",b"insert_remove_sdcard","list_backups",b"list_backups","perform_attestation",b"perform_attestation","reboot",b"reboot","request",b"request","reset",b"reset","restore_backup",b"restore_backup","restore_from_mnemonic",b"restore_from_mnemonic","set_mnemonic_passphrase_enabled",b"set_mnemonic_passphrase_enabled","set_password",b"set_password","show_mnemonic",b"show_mnemonic"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["request",b"request"]) -> typing.Optional[typing_extensions.Literal["device_name","device_language","device_info","set_password","create_backup","show_mnemonic","btc_pub","btc_sign_init","btc_sign_input","btc_sign_output","insert_remove_sdcard","check_sdcard","set_mnemonic_passphrase_enabled","list_backups","restore_backup","perform_attestation","reboot","check_backup","eth","reset","restore_from_mnemonic","fingerprint","btc","electrum_encryption_key","cardano","bip85"]]: ... + def HasField(self, field_name: typing_extensions.Literal["bip85",b"bip85","btc",b"btc","btc_pub",b"btc_pub","btc_sign_init",b"btc_sign_init","btc_sign_input",b"btc_sign_input","btc_sign_output",b"btc_sign_output","cardano",b"cardano","change_password",b"change_password","check_backup",b"check_backup","check_sdcard",b"check_sdcard","create_backup",b"create_backup","device_info",b"device_info","device_language",b"device_language","device_name",b"device_name","electrum_encryption_key",b"electrum_encryption_key","eth",b"eth","fingerprint",b"fingerprint","insert_remove_sdcard",b"insert_remove_sdcard","list_backups",b"list_backups","perform_attestation",b"perform_attestation","reboot",b"reboot","request",b"request","reset",b"reset","restore_backup",b"restore_backup","restore_from_mnemonic",b"restore_from_mnemonic","set_mnemonic_passphrase_enabled",b"set_mnemonic_passphrase_enabled","set_password",b"set_password","show_mnemonic",b"show_mnemonic"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["bip85",b"bip85","btc",b"btc","btc_pub",b"btc_pub","btc_sign_init",b"btc_sign_init","btc_sign_input",b"btc_sign_input","btc_sign_output",b"btc_sign_output","cardano",b"cardano","change_password",b"change_password","check_backup",b"check_backup","check_sdcard",b"check_sdcard","create_backup",b"create_backup","device_info",b"device_info","device_language",b"device_language","device_name",b"device_name","electrum_encryption_key",b"electrum_encryption_key","eth",b"eth","fingerprint",b"fingerprint","insert_remove_sdcard",b"insert_remove_sdcard","list_backups",b"list_backups","perform_attestation",b"perform_attestation","reboot",b"reboot","request",b"request","reset",b"reset","restore_backup",b"restore_backup","restore_from_mnemonic",b"restore_from_mnemonic","set_mnemonic_passphrase_enabled",b"set_mnemonic_passphrase_enabled","set_password",b"set_password","show_mnemonic",b"show_mnemonic"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["request",b"request"]) -> typing.Optional[typing_extensions.Literal["device_name","device_language","device_info","set_password","create_backup","show_mnemonic","btc_pub","btc_sign_init","btc_sign_input","btc_sign_output","insert_remove_sdcard","check_sdcard","set_mnemonic_passphrase_enabled","list_backups","restore_backup","perform_attestation","reboot","check_backup","eth","reset","restore_from_mnemonic","fingerprint","btc","electrum_encryption_key","cardano","bip85","change_password"]]: ... global___Request = Request class Response(google.protobuf.message.Message): diff --git a/py/send_message.py b/py/send_message.py index 89821c534..971882e95 100755 --- a/py/send_message.py +++ b/py/send_message.py @@ -184,6 +184,12 @@ def _change_name_workflow(self, name: Optional[str] = None) -> None: info = self._device.device_info() print(f"New device name: {info['name']}") + def _change_password_worklow(self) -> None: + if not self._device.change_password(): + print("Failed to change password") + return + print("Successfully changed password") + def _setup_workflow(self) -> None: """TODO: Document""" self._change_name_workflow() @@ -1407,6 +1413,7 @@ def _menu_init(self) -> None: choices = ( ("List device info", self._list_device_info), ("Change device name", self._change_name_workflow), + ("Change device password", self._change_password_worklow), ("Get root fingerprint", self._get_root_fingerprint), ("Retrieve zpub of first account", self._display_zpub), ("Retrieve a BTC address", self._btc_address), diff --git a/src/keystore.c b/src/keystore.c index c6d5b0949..de21f1041 100644 --- a/src/keystore.c +++ b/src/keystore.c @@ -234,18 +234,11 @@ static bool _verify_seed( return true; } -keystore_error_t keystore_encrypt_and_store_seed( +static keystore_error_t _set_new_password( const uint8_t* seed, size_t seed_length, const char* password) { - if (memory_is_initialized()) { - return KEYSTORE_ERR_MEMORY; - } - keystore_lock(); - if (!_validate_seed_length(seed_length)) { - return KEYSTORE_ERR_SEED_SIZE; - } if (securechip_init_new_password(password)) { return KEYSTORE_ERR_SECURECHIP; } @@ -277,6 +270,21 @@ keystore_error_t keystore_encrypt_and_store_seed( return KEYSTORE_OK; } +keystore_error_t keystore_encrypt_and_store_seed( + const uint8_t* seed, + size_t seed_length, + const char* password) +{ + if (memory_is_initialized()) { + return KEYSTORE_ERR_MEMORY; + } + keystore_lock(); + if (!_validate_seed_length(seed_length)) { + return KEYSTORE_ERR_SEED_SIZE; + } + return _set_new_password(seed, seed_length, password); +} + keystore_error_t keystore_create_and_store_seed( const char* password, const uint8_t* host_entropy, @@ -380,6 +388,50 @@ static void _delete_retained_seeds(void) _retained_bip39_seed_encrypted_len = 0; } +keystore_error_t keystore_change_password(const char* old_password, const char* new_password) +{ + uint8_t bip39_seed[64]; + UTIL_CLEANUP_64(bip39_seed); + if (!_copy_bip39_seed(bip39_seed)) { + return KEYSTORE_ERR_MEMORY; + } + + uint8_t encrypted_seed_and_hmac[96]; + UTIL_CLEANUP_32(encrypted_seed_and_hmac); + uint8_t encrypted_len; + if (!memory_get_encrypted_seed_and_hmac(encrypted_seed_and_hmac, &encrypted_len)) { + return KEYSTORE_ERR_MEMORY; + } + + uint8_t secret[32]; + UTIL_CLEANUP_32(secret); + if (securechip_stretch_password(old_password, secret)) { + return KEYSTORE_ERR_SECURECHIP; + } + + size_t decrypted_len = encrypted_len - 48; + uint8_t decrypted[decrypted_len]; + if (!cipher_aes_hmac_decrypt( + encrypted_seed_and_hmac, encrypted_len, decrypted, &decrypted_len, secret)) { + return KEYSTORE_ERR_INCORRECT_PASSWORD; + } + + keystore_error_t result = _set_new_password(decrypted, decrypted_len, new_password); + if (result != KEYSTORE_OK) { + return result; + } + + keystore_error_t retain_seed_result = _retain_seed(decrypted, decrypted_len); + if (retain_seed_result != KEYSTORE_OK) { + return retain_seed_result; + } + if (!_retain_bip39_seed(bip39_seed)) { + return KEYSTORE_ERR_MEMORY; + } + + return KEYSTORE_OK; +} + keystore_error_t keystore_unlock( const char* password, uint8_t* remaining_attempts_out, diff --git a/src/keystore.h b/src/keystore.h index ba2f37f9b..1c7106464 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -77,6 +77,14 @@ USE_RESULT keystore_error_t keystore_create_and_store_seed( const uint8_t* host_entropy, size_t host_entropy_size); +/** + * Changes the password of the keystore. + * @param[in] old_password The old password. + * @param[in] new_password The new password. + */ +USE_RESULT keystore_error_t +keystore_change_password(const char* old_password, const char* new_password); + /** Unlocks the keystore seed or checks the password: * If the keystore is locked, it decrypts and loads the seed, unlocking the keystore: * 1) loads the stored seed and tries to decrypt using password. diff --git a/src/rust/bitbox02-rust/src/hww/api.rs b/src/rust/bitbox02-rust/src/hww/api.rs index ba528f22a..d898fc6da 100644 --- a/src/rust/bitbox02-rust/src/hww/api.rs +++ b/src/rust/bitbox02-rust/src/hww/api.rs @@ -27,6 +27,7 @@ mod cardano; mod backup; mod bip85; +mod change_password; mod device_info; mod electrum; mod reset; @@ -115,6 +116,7 @@ fn can_call(request: &Request) -> bool { Request::CheckSdcard(_) => true, Request::InsertRemoveSdcard(_) => true, Request::ListBackups(_) => true, + Request::ChangePassword(_) => true, Request::SetPassword(_) => matches!(state, State::Uninitialized | State::Seeded), Request::RestoreBackup(_) => matches!(state, State::Uninitialized | State::Seeded), Request::RestoreFromMnemonic(_) => matches!(state, State::Uninitialized | State::Seeded), @@ -145,6 +147,7 @@ async fn process_api(request: &Request) -> Result { Request::DeviceInfo(_) => device_info::process(), Request::DeviceName(ref request) => set_device_name::process(request).await, Request::SetPassword(ref request) => set_password::process(request).await, + Request::ChangePassword(_) => change_password::process().await, Request::Reset(_) => reset::process().await, Request::SetMnemonicPassphraseEnabled(ref request) => { set_mnemonic_passphrase_enabled::process(request).await diff --git a/src/rust/bitbox02-rust/src/hww/api/change_password.rs b/src/rust/bitbox02-rust/src/hww/api/change_password.rs new file mode 100644 index 000000000..332d53cea --- /dev/null +++ b/src/rust/bitbox02-rust/src/hww/api/change_password.rs @@ -0,0 +1,34 @@ +// Copyright 2024 Shift Crypto AG +// +// 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. + +use super::Error; +use crate::pb; + +use crate::workflow::{password, status, unlock}; +use bitbox02::keystore; +use pb::response::Response; + +pub async fn process() -> Result { + let old_password = password::enter("Old password", false, unlock::CanCancel::Yes).await?; + + if keystore::unlock(&old_password).is_err() { + status::status("Wrong password", false).await; + return Err(Error::Generic); + } + + let new_password = password::enter_twice().await?; + keystore::change_password(&old_password, &new_password).unwrap(); + + Ok(Response::Success(pb::Success {})) +} diff --git a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs index b30b7eaf1..1237d5b32 100644 --- a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs +++ b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs @@ -194,6 +194,9 @@ pub struct SetPasswordRequest { pub entropy: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct ChangePasswordRequest {} +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AntiKleptoHostNonceCommitment { #[prost(bytes = "vec", tag = "1")] @@ -1815,7 +1818,7 @@ pub struct Success {} pub struct Request { #[prost( oneof = "request::Request", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29" )] pub request: ::core::option::Option, } @@ -1878,6 +1881,8 @@ pub mod request { Cardano(super::CardanoRequest), #[prost(message, tag = "28")] Bip85(super::Bip85Request), + #[prost(message, tag = "29")] + ChangePassword(super::ChangePasswordRequest), } } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs index 7bc80b41e..d1174077a 100644 --- a/src/rust/bitbox02-sys/build.rs +++ b/src/rust/bitbox02-sys/build.rs @@ -63,6 +63,7 @@ const ALLOWLIST_FNS: &[&str] = &[ "keystore_bip39_mnemonic_to_seed", "keystore_bip85_bip39", "keystore_bip85_ln", + "keystore_change_password", "keystore_copy_seed", "keystore_create_and_store_seed", "keystore_encode_xpub_at_keypath", diff --git a/src/rust/bitbox02/src/keystore.rs b/src/rust/bitbox02/src/keystore.rs index 53076f686..76b51a4ff 100644 --- a/src/rust/bitbox02/src/keystore.rs +++ b/src/rust/bitbox02/src/keystore.rs @@ -106,6 +106,18 @@ pub fn create_and_store_seed(password: &SafeInputString, host_entropy: &[u8]) -> } } +pub fn change_password( + old_password: &SafeInputString, + new_password: &SafeInputString, +) -> Result<(), Error> { + match unsafe { + bitbox02_sys::keystore_change_password(old_password.as_cstr(), new_password.as_cstr()) + } { + keystore_error_t::KEYSTORE_OK => Ok(()), + err => Err(err.into()), + } +} + pub fn copy_seed() -> Result>, ()> { let mut seed = zeroize::Zeroizing::new([0u8; MAX_SEED_LENGTH].to_vec()); let mut seed_len: usize = 0; diff --git a/test/unit-test/test_keystore_functional.c b/test/unit-test/test_keystore_functional.c index 9de48edc4..4a5c46a98 100644 --- a/test/unit-test/test_keystore_functional.c +++ b/test/unit-test/test_keystore_functional.c @@ -262,6 +262,35 @@ static void _test_fixtures(void** state) } } +static void _test_passwords(void** state) +{ + _smarteeprom_reset(); + assert_true(keystore_is_locked()); + + will_return(__wrap_memory_is_initialized, false); + assert_int_equal(keystore_encrypt_and_store_seed(_seed, 32, _some_password), KEYSTORE_OK); + + uint8_t remaining_attempts; + will_return(__wrap_memory_is_seeded, true); + assert_int_equal(KEYSTORE_OK, keystore_unlock(_some_password, &remaining_attempts, NULL)); + assert_true(keystore_unlock_bip39("")); + + assert_int_equal(KEYSTORE_OK, keystore_change_password(_some_password, _some_other_password)); + + keystore_lock(); + assert_true(keystore_is_locked()); + + will_return(__wrap_memory_is_seeded, true); + assert_int_equal( + KEYSTORE_ERR_INCORRECT_PASSWORD, + keystore_unlock(_some_password, &remaining_attempts, NULL)); + + will_return(__wrap_memory_is_seeded, true); + assert_int_equal(KEYSTORE_OK, keystore_unlock(_some_other_password, &remaining_attempts, NULL)); + + keystore_lock(); +} + int main(void) { mock_memory_set_salt_root(_salt_root); @@ -269,6 +298,7 @@ int main(void) const struct CMUnitTest tests[] = { cmocka_unit_test(_test_seeds), cmocka_unit_test(_test_fixtures), + cmocka_unit_test(_test_passwords), }; return cmocka_run_group_tests(tests, NULL, NULL); }