Reversible Examples¶
To provide better orientation, we highlight specific properties of each example, namely:
- Reversibility,
- Model compatibility,
- Level of complexity (π£ -> π€ -> π).
The source code for the examples can be found in the examples/
directory and also within this document inside "Source code" boxes.
Tip
You can copy the example code using the icon located in the top-right corner of each source code listing.
lt_ex_hello_world.c¶
- β Reversible
- β Compatible with the model
- π£ Level: Basic
This example demonstrates the basic libtropic API and can be used to verify that the chip works correctly. In this example, you will learn about the following functions:
lt_init()
: function used to initialize context for communication with the TROPIC01,lt_verify_chip_and_start_secure_session()
: helper function to start Secure Session and allow L3 communication,lt_ping()
: L3 command to verify communication with the TROPIC01,lt_session_abort()
: L3 command to abort Secure Session,lt_deinit()
: function used to deinitialize context.
Source code
/**
* @file lt_ex_hello_world.c
* @brief Establishes Secure Session and executes Ping L3 command.
* @author Tropic Square s.r.o.
*
* @license For the license see file LICENSE.txt file in the root directory of this source tree.
*/
#include "libtropic.h"
#include "libtropic_common.h"
#include "libtropic_examples.h"
#include "libtropic_logging.h"
/** @brief Message to send with Ping L3 command. */
#define PING_MSG "This is Hello World message from TROPIC01!!"
/** @brief Size of the Ping message, including '\0'. */
#define PING_MSG_SIZE 44
int lt_ex_hello_world(lt_handle_t *h)
{
LT_LOG_INFO("======================================");
LT_LOG_INFO("==== TROPIC01 Hello World Example ====");
LT_LOG_INFO("======================================");
lt_ret_t ret;
LT_LOG_INFO("Initializing handle");
ret = lt_init(h);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to initialize handle, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Starting Secure Session with key %d", (int)TR01_PAIRING_KEY_SLOT_INDEX_0);
ret = lt_verify_chip_and_start_secure_session(h, sh0priv, sh0pub, TR01_PAIRING_KEY_SLOT_INDEX_0);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to start Secure Session with key %d, ret=%s", (int)TR01_PAIRING_KEY_SLOT_INDEX_0,
lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_LINE();
uint8_t recv_buf[PING_MSG_SIZE];
LT_LOG_INFO("Sending Ping command with message:");
LT_LOG_INFO("\t\"%s\"", PING_MSG);
ret = lt_ping(h, (const uint8_t *)PING_MSG, recv_buf, PING_MSG_SIZE);
if (LT_OK != ret) {
LT_LOG_ERROR("Ping command failed, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_LINE();
LT_LOG_INFO("Message received from TROPIC01:");
LT_LOG_INFO("\t\"%s\"", recv_buf);
LT_LOG_LINE();
LT_LOG_INFO("Aborting Secure Session");
ret = lt_session_abort(h);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to abort Secure Session, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Deinitializing handle");
ret = lt_deinit(h);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to deinitialize handle, ret=%s", lt_ret_verbose(ret));
return -1;
}
return 0;
}
lt_ex_show_chip_id_and_fwver.c¶
- β Reversible
- β Compatible with the model
- π£ Level: Basic
Demonstrates how to read and display the chipβs unique ID and firmware version information (bootloader, application and SPECT firmware versions). You will learn about following functions:
lt_reboot()
: L2 request to reboot to either application or maintenance mode,lt_get_info_riscv_fw_ver()
,lt_get_info_spect_fw_ver()
: L2 requests to read CPU and SPECT firmware versions,lt_get_info_chip_id()
: L2 request to read chip identification (e.g., serial number),
Source code
/**
* @file lt_ex_show_chip_id_and_fwver.c
* @name Show chip ID and firmware versions
* @brief This example shows how to read TROPIC01's chip ID and firmware versions
* @note We recommend reading TROPIC01's datasheet before diving into this example!
*
* @author Tropic Square s.r.o.
*
* @license For the license see file LICENSE.txt file in the root directory of this source tree.
*/
#include <inttypes.h>
#include "libtropic.h"
#include "libtropic_common.h"
#include "libtropic_examples.h"
#include "libtropic_logging.h"
#include "string.h"
int lt_ex_show_chip_id_and_fwver(lt_handle_t *h)
{
LT_LOG_INFO("=============================================================");
LT_LOG_INFO("==== TROPIC01 show chip ID and firmware versions example ====");
LT_LOG_INFO("=============================================================");
// This variable is reused on more places in this example to store different firmware versions
uint8_t fw_ver[TR01_L2_GET_INFO_RISCV_FW_SIZE] = {0};
lt_ret_t ret = lt_init(h);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to initialize handle, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
// First we check versions of both updateable firmwares. For this we need to be in APPLICATION mode.
// If there are valid firmwares, chip will execute them on boot. In any case we will try to reboot into application,
// in case chip would be in maintenance mode (executing bootloader)
LT_LOG_INFO("Rebooting into APPLICATION mode to check FW versions");
ret = lt_reboot(h, TR01_REBOOT);
if (ret != LT_OK) {
LT_LOG_ERROR("lt_reboot() failed, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
if (h->l2.mode == LT_TR01_APP_MODE) {
// App runs so we can see what firmwares are running
// Getting RISCV app firmware version
LT_LOG_INFO("Reading RISC-V FW version");
ret = lt_get_info_riscv_fw_ver(h, fw_ver);
if (ret == LT_OK) {
LT_LOG_INFO("Chip is executing RISC-V application FW version: %02" PRIX8 ".%02" PRIX8 ".%02" PRIX8
" (+ .%02" PRIX8 ")",
fw_ver[3], fw_ver[2], fw_ver[1], fw_ver[0]);
}
else {
LT_LOG_ERROR("Failed to get RISC-V FW version, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Reading SPECT FW version");
ret = lt_get_info_spect_fw_ver(h, fw_ver);
if (ret == LT_OK) {
LT_LOG_INFO("Chip is executing SPECT firmware version: %02" PRIX8 ".%02" PRIX8 ".%02" PRIX8
" (+ .%02" PRIX8 ")",
fw_ver[3], fw_ver[2], fw_ver[1], fw_ver[0]);
}
else {
LT_LOG_ERROR("Failed to get SPECT firmware version, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
}
else {
LT_LOG_ERROR("Chip couldn't get into APP mode, APP and SPECT firmwares in fw banks are not valid");
}
LT_LOG_LINE();
LT_LOG_INFO("Rebooting into MAINTENANCE mode to check bootloader version and fw bank headers");
ret = lt_reboot(h, TR01_MAINTENANCE_REBOOT);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to reboot into MAINTENANCE mode, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
if (h->l2.mode == LT_TR01_MAINTENANCE_MODE) {
LT_LOG_INFO("Reading RISC-V FW version (during maintenance chip actually returns bootloader version):");
ret = lt_get_info_riscv_fw_ver(h, fw_ver);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to get RISC-V FW version, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Bootloader version: %02" PRIX8 ".%02" PRIX8 ".%02" PRIX8 " (+ .%02" PRIX8 ")", fw_ver[3] & 0x7f,
fw_ver[2], fw_ver[1], fw_ver[0]);
LT_LOG_LINE();
LT_LOG_INFO("Reading and printing headers of all 4 FW banks:");
ret = lt_print_fw_header(h, TR01_FW_BANK_FW1, printf);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to print TR01_FW_BANK_FW1 header, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
ret = lt_print_fw_header(h, TR01_FW_BANK_FW2, printf);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to print TR01_FW_BANK_FW2 header, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
ret = lt_print_fw_header(h, TR01_FW_BANK_SPECT1, printf);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to print TR01_FW_BANK_SPECT1 header, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
ret = lt_print_fw_header(h, TR01_FW_BANK_SPECT2, printf);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to print TR01_FW_BANK_SPECT2 header, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
}
else {
LT_LOG_ERROR("Chip couldn't get into MAINTENANCE mode");
lt_deinit(h);
return -1;
}
LT_LOG_LINE();
struct lt_chip_id_t chip_id = {0};
LT_LOG_INFO("Reading Chip ID:");
ret = lt_get_info_chip_id(h, &chip_id);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to get chip ID, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
ret = lt_print_chip_id(&chip_id, printf);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to print chip ID, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_LINE();
LT_LOG_INFO("Deinitializing handle");
ret = lt_deinit(h);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to deinitialize handle, ret=%s", lt_ret_verbose(ret));
return -1;
}
return 0;
}
lt_ex_hello_world_separate_API.c¶
- β Reversible
- β Compatible with the model
- π€ Level: Moderate
Functionally similar to lt_ex_hello_world.c
, but it uses distinct API calls for incoming and outgoing data. This approach is useful for secure, tunneled communication, such as during chip provisioning in a factory.
You will learn about low-level API functions used to process outgoing and incoming data. For example:
lt_out__session_start()
: prepare Handshake_Req L2 request (for Secure Session establishment),lt_l2_send()
: send L2 request,lt_l2_receive()
: receive L2 response.lt_in__session_start()
: process L2 response to the Handshake_Req,
Source code
/**
* @file lt_ex_hello_world_separate_API.c
* @brief Establishes Secure Session and executes Ping L3 command using separated API.
* @author Tropic Square s.r.o.
*
* @license For the license see file LICENSE.txt file in the root directory of this source tree.
*/
#include "libtropic.h"
#include "libtropic_examples.h"
#include "libtropic_l2.h"
#include "libtropic_l3.h"
#include "libtropic_logging.h"
/** @brief Message to send with Ping L3 command. */
#define PING_MSG "This is Hello World message from TROPIC01!!"
/** @brief Size of the Ping message, including '\0'. */
#define PING_MSG_SIZE 44
int lt_ex_hello_world_separate_API(lt_handle_t *h)
{
LT_LOG_INFO("=========================================================");
LT_LOG_INFO("==== TROPIC01 Hello World with Separate API Example ====");
LT_LOG_INFO("=========================================================");
lt_ret_t ret;
LT_LOG_INFO("Initializing handle");
ret = lt_init(h);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to initialize handle, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Getting Certificate Store from TROPIC01");
uint8_t cert1[TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE], cert2[TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE],
cert3[TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE], cert4[TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE];
struct lt_cert_store_t store
= {.certs = {cert1, cert2, cert3, cert4},
.buf_len = {TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE, TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE,
TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE, TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE}};
ret = lt_get_info_cert_store(h, &store);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to get Certificate Store, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
// Get only stpub, we don't verify certificate chain here
LT_LOG_INFO("Getting stpub key from Certificate Store");
uint8_t stpub[TR01_STPUB_LEN];
ret = lt_get_st_pub(&store, stpub);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to get stpub key, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_LINE();
//---------------------------------------------------------------------------------------//
// Separated API calls for starting a secure session:
lt_host_eph_keys_t host_eph_keys = {0};
// Initialize session from a server side by creating host_eph_keys->ehpriv and host_eph_keys->ehpub,
// l2 request is prepared into handle's buffer (h->l2_buff)
LT_LOG_INFO("Executing lt_out__session_start()...");
ret = lt_out__session_start(h, TR01_PAIRING_KEY_SLOT_INDEX_0, &host_eph_keys);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_out__session_start() failed, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
// handle's buffer (h->l2_buff) now contains data which must be transferred over tunnel to TROPIC01
// Following l2 functions are called on remote host
LT_LOG_INFO("Executing lt_l2_send()...");
ret = lt_l2_send(&h->l2);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_l2_send() failed, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Executing lt_l2_receive()...");
ret = lt_l2_receive(&h->l2);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_l2_receive() failed, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
// Handle's buffer (h->l2_buff) now contains data which must be transferred over tunnel back to the server
// Once data are back on server's side, bytes are copied into h->l2_buff
// Then following l2 function is called on server side
// This function establishes gcm contexts for a session
LT_LOG_INFO("Executing lt_in__session_start()...");
ret = lt_in__session_start(h, stpub, TR01_PAIRING_KEY_SLOT_INDEX_0, sh0priv, sh0pub, &host_eph_keys);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_in__session_start failed, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_LINE();
// Now we can use lt_ping() to send a message to TROPIC01 and receive a response, this is done with separate API
// calls
uint8_t recv_buf[PING_MSG_SIZE];
LT_LOG_INFO("Executing lt_out__ping() with message:");
LT_LOG_INFO("\t\"%s\"", PING_MSG);
ret = lt_out__ping(h, (const uint8_t *)PING_MSG, PING_MSG_SIZE);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_out__ping failed, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Executing lt_l2_send_encrypted_cmd()...");
ret = lt_l2_send_encrypted_cmd(&h->l2, h->l3.buff, h->l3.buff_len);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_l2_send_encrypted_cmd failed, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Executing lt_l2_recv_encrypted_res()...");
ret = lt_l2_recv_encrypted_res(&h->l2, h->l3.buff, h->l3.buff_len);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_l2_recv_encrypted_res failed, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Executing lt_in__ping()...");
ret = lt_in__ping(h, recv_buf, PING_MSG_SIZE);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_in__ping failed, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Message received from TROPIC01:");
LT_LOG_INFO("\t\"%s\"", recv_buf);
LT_LOG_LINE();
LT_LOG_INFO("Aborting Secure Session");
ret = lt_session_abort(h);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to abort Secure Session, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Deinitializing handle");
ret = lt_deinit(h);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to deinitialize handle, ret=%s", lt_ret_verbose(ret));
return -1;
}
return 0;
}
lt_ex_macandd.c¶
- β Reversible
- β Compatible with the model
- π Level: Advanced
This example illustrates the MAC-and-destroy feature. You will learn about the following functions:
lt_hmac_sha256()
: function to compute HMAC based on SHA256,lt_mac_and_destroy()
: L3 command to process MAC-and-Destroy operation,lt_r_mem_data_erase()
: L3 command to erase R-memory data slot,lt_r_mem_data_write()
: L3 command to write R-memory data slot,lt_r_mem_data_read()
: L3 command to read R-memory data slot,lt_random_bytes()
: function to generate random number using platform's RNG (not TROPIC01's),
In this example, we also define two functions to implement PIN verification functionality: lt_PIN_set()
and lt_PIN_check()
. You can use these functions as inspiration for your project.
You can find more information in the application note and code comments.
Source code
/**
* @file lt_ex_macandd.c
* @brief Example usage of TROPIC01 flagship feature - 'Mac And Destroy' PIN verification engine.
* For more info please refer to ODN_TR01_app_002_pin_verif.pdf
* @author Tropic Square s.r.o.
*
* @license For the license see file LICENSE.txt file in the root directory of this source tree.
*/
#include <inttypes.h>
#include "libtropic.h"
#include "libtropic_examples.h"
#include "libtropic_logging.h"
#include "string.h"
// Needed to access to lt_random_bytes()
#include "lt_random.h"
// Needed to access HMAC_SHA256
#include "lt_hmac_sha256.h"
/** @brief Last slot in User memory used for storing of M&D related data (only in this example). */
#define R_MEM_DATA_SLOT_MACANDD (511)
/** @brief The size of random data passed during MAC-and-Destroy PIN set */
#define TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE 32u
/** @brief Size of the print buffer. */
#define PRINT_BUFF_SIZE 196
#ifndef MACANDD_ROUNDS
#define MACANDD_ROUNDS 12
#endif
#if (MACANDD_ROUNDS > 12)
#error \
"MACANDD_ROUNDS must be less than 12 here, or generally than TR01_MACANDD_ROUNDS_MAX. Read explanation at the beginning of this file"
#endif
/** @brief Minimal size of MAC-and-Destroy additional data */
#define MAC_AND_DESTROY_ADD_SIZE_MIN 0
/** @brief Maximal size of MAC-and-Destroy additional data */
#define MAC_AND_DESTROY_ADD_SIZE_MAX 128u
/** @brief Minimal size of MAC-and-Destroy PIN input */
#define MAC_AND_DESTROY_PIN_SIZE_MIN 4u
/** @brief Maximal size of MAC-and-Destroy PIN input */
#define MAC_AND_DESTROY_PIN_SIZE_MAX 8u
/**
* @brief This structure holds data used by host during MAC and Destroy sequence
* Content of this struct must be stored in non-volatile memory, because it is used
* between power cycles
*/
struct lt_macandd_nvm_t {
uint8_t i;
uint8_t ci[MACANDD_ROUNDS * TR01_MAC_AND_DESTROY_DATA_SIZE];
uint8_t t[LT_HMAC_SHA256_HASH_LEN];
} __attribute__((__packed__));
/**
* @brief Simple XOR "encryption" function. Replace with another encryption algorithm if needed.
*
* @param data 32B of data to be encrypted
* @param key 32B key used for encryption
* @param destination Buffer into which 32B of encrypted data will be placed
*/
static void encrypt(const uint8_t *data, const uint8_t *key, uint8_t *destination)
{
for (uint8_t i = 0; i < 32; i++) {
destination[i] = data[i] ^ key[i];
}
}
/**
* @brief Simple XOR "decryption" function. Replace with another decryption algorithm if needed.
*
* @param data 32B of data to be decrypted
* @param key 32B key used for decryption
* @param destination Buffer into which 32B of decrypted data will be placed
*/
static void decrypt(const uint8_t *data, const uint8_t *key, uint8_t *destination)
{
for (uint8_t i = 0; i < 32; i++) {
destination[i] = data[i] ^ key[i]; //*(data + i) ^= *(key + i);
}
}
/**
* @brief Example function for setting PIN with Mac And Destroy.
*
* @details The New PIN Setup procedure takes the user PIN, add data and high entropy master_secret as an input,
* initializes the scheme slots and returns a 32-byte key final_key as derivative of the master_secret.
*
* The MAC-and-Destroy PIN veriο¬cation scheme uses slots located in the TROPIC01βs ο¬ash memory β one slot per
* PIN entry attempt. These slots are ο¬rst initialized when a new PIN is being set up.
* The slots are then invalidated (destroyed) one by one with each PIN entry attempt. When the correct PIN is
* entered, the slots are initialized again, therefore the PIN entry limit is reset.
* PIN entry attempt fails if:
* * PIN is invalid
* * The current slot is not initialized for a given PIN
* * The current slot is destroyed by previous invalid PIN entry attempt.
*
* There are more ways how to implement Mac And Destroy 'PIN set' functionality, differences could be in way of
* handling nvm data, number of tries, algorithm used for encryption, etc. This function is just one of the possible
* implementations of "PIN set".
*
* Take it as an inspiration, copy it into your project and adapt it to your specific hw resources.
*
* @param h Device's handle
* @param master_secret 32 bytes of random data (determines final_key)
* @param PIN Array of bytes (size between MAC_AND_DESTROY_PIN_SIZE_MIN and MAC_AND_DESTROY_PIN_SIZE_MAX)
* representing PIN
* @param PIN_size Length of the PIN field
* @param add Additional data to be used in M&D sequence (size between MAC_AND_DESTROY_ADD_SIZE_MIN and
* MAC_AND_DESTROY_ADD_SIZE_MAX). Pass NULL if no additional data should be used.
* @param add_size Length of additional data
* @param final_key Buffer into which final key will be placed when all went successfully
* @return lt_ret_t LT_OK if correct, otherwise LT_FAIL
*/
static lt_ret_t lt_new_PIN_setup(lt_handle_t *h, const uint8_t *master_secret, const uint8_t *PIN,
const uint8_t PIN_size, const uint8_t *add, const uint8_t add_size, uint8_t *final_key)
{
if (!h || !master_secret || !PIN || (PIN_size < MAC_AND_DESTROY_PIN_SIZE_MIN)
|| (PIN_size > MAC_AND_DESTROY_PIN_SIZE_MAX) || (add_size > MAC_AND_DESTROY_ADD_SIZE_MAX) || !final_key) {
// add parameter is not checked for NULL, because it can be NULL (handled in the lines below)
return LT_PARAM_ERR;
}
if (h->l3.session_status != LT_SECURE_SESSION_ON) {
return LT_HOST_NO_SESSION;
}
uint8_t add_size_checked = add_size;
if (!add) {
add_size_checked = 0;
}
// Clear variable for released final_key so there is known data (zeroes) in case this function ended sooner then
// final_key was prepared
memset(final_key, 0, TR01_MAC_AND_DESTROY_DATA_SIZE);
// Variable used during a process of getting a encryption key k_i
uint8_t v[LT_HMAC_SHA256_HASH_LEN] = {0};
// Variable used during a process of getting a encryption key k_i
uint8_t w_i[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
// Encryption key
uint8_t k_i[LT_HMAC_SHA256_HASH_LEN] = {0};
// Variable used to initialize slot(s)
uint8_t u[LT_HMAC_SHA256_HASH_LEN] = {0};
// This organizes data which will be stored into nvm
struct lt_macandd_nvm_t nvm = {0};
// User is expected to pass not only PIN, but might also pass another data (e.g. HW ID, ...)
// Both arrays are concatenated and used together as an input for KDF
uint8_t kdf_input_buff[MAC_AND_DESTROY_PIN_SIZE_MAX + MAC_AND_DESTROY_ADD_SIZE_MAX];
memcpy(kdf_input_buff, PIN, PIN_size);
if (!add || add_size_checked == 0) {
LT_LOG_INFO("No additional data will be used in the following M&D sequence");
}
else {
memcpy(kdf_input_buff + PIN_size, add, add_size_checked);
}
// Erase a slot in R memory, which will be used as a storage for NVM data
LT_LOG_INFO("Erasing R_Mem User slot %d...", R_MEM_DATA_SLOT_MACANDD);
lt_ret_t ret = lt_r_mem_data_erase(h, R_MEM_DATA_SLOT_MACANDD);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to erase User slot, ret=%s", lt_ret_verbose(ret));
goto exit;
}
LT_LOG_INFO("\tOK");
// Store number of attempts
nvm.i = MACANDD_ROUNDS;
// Compute tag t = KDF(s, 0x00), save into nvm struct
// Tag will be later used during lt_PIN_entry_check() to verify validity of final_key
lt_hmac_sha256(master_secret, TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE, (uint8_t[]){0x00}, 1, nvm.t);
// Compute u = KDF(s, 0x01)
// This value will be sent through M&D sequence to initialize a slot
lt_hmac_sha256(master_secret, TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE, (uint8_t[]){0x01}, 1, u);
// Compute v = KDF(0, PIN||A) where 0 is all zeroes key
const uint8_t zeros[32] = {0};
lt_hmac_sha256(zeros, sizeof(zeros), kdf_input_buff, PIN_size + add_size_checked, v);
for (int i = 0; i < nvm.i; i++) {
uint8_t ignore[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
// This call of a M&D sequence results in initialization of one slot
LT_LOG_INFO("Doing M&D sequence to initialize a slot...");
ret = lt_mac_and_destroy(h, i, u, ignore);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed while doing M&D sequence, ret=%s", lt_ret_verbose(ret));
goto exit;
}
LT_LOG_INFO("\tOK");
// This call of a M&D sequence overwrites a previous slot, but key w is returned.
// This key is later used to derive k_i (used to encrypt precious final_key)
LT_LOG_INFO("Doing M&D sequence to overwrite previous slot...");
ret = lt_mac_and_destroy(h, i, v, w_i);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed while doing M&D sequence, ret=%s", lt_ret_verbose(ret));
goto exit;
}
LT_LOG_INFO("\tOK");
// Now the slot is initialized again by calling M&D sequence again with 'u'
LT_LOG_INFO("Doing M&D sequence again to initialize a slot...");
ret = lt_mac_and_destroy(h, i, u, ignore);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed while doing M&D sequence, ret=%s", lt_ret_verbose(ret));
goto exit;
}
LT_LOG_INFO("\tOK");
// Derive k_i = KDF(w_i, PIN||A); k_i will be used to encrypt master_secret
lt_hmac_sha256(w_i, sizeof(w_i), kdf_input_buff, PIN_size + add_size_checked, k_i);
// Encrypt master_secret using k_i as a key and store ciphertext into non volatile storage
encrypt(master_secret, k_i, nvm.ci + (i * TR01_MAC_AND_DESTROY_DATA_SIZE));
}
// Persistently save nvm data into TROPIC01's R memory slot
LT_LOG_INFO("Writing NVM data into R_Mem User slot %d...", R_MEM_DATA_SLOT_MACANDD);
ret = lt_r_mem_data_write(h, R_MEM_DATA_SLOT_MACANDD, (uint8_t *)&nvm, sizeof(nvm));
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to write User slot, ret=%s", lt_ret_verbose(ret));
goto exit;
}
// final_key is released to the caller
lt_hmac_sha256(master_secret, TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE, (uint8_t *)"2", 1, final_key);
// Cleanup all sensitive data from memory
exit:
memset(kdf_input_buff, 0, PIN_size + add_size_checked);
memset(u, 0, sizeof(u));
memset(v, 0, sizeof(v));
memset(w_i, 0, sizeof(w_i));
memset(k_i, 0, sizeof(k_i));
return ret;
}
/**
* @brief Example function for checking PIN with Mac And Destroy.
*
* @details The Pin Entry Check procedure takes the PIN and additional add data entered by the user as an input, and
* checks the PIN. If successful, the correct key k is returned.
*
* There are more ways how to implement Mac And Destroy 'PIN check' functionality, differences could be in way
* of handling nvm data, number of tries, algorithm used for decryption, etc. This function is just one of the possible
* implementations of "PIN check".
*
* Take it as an inspiration, copy it into your project and adapt it to your specific hw resources.
*
* @param h Device's handle
* @param PIN Array of bytes (size between MAC_AND_DESTROY_PIN_SIZE_MIN and MAC_AND_DESTROY_PIN_SIZE_MAX)
* representing PIN
* @param PIN_size Length of the PIN field
* @param add Additional data to be used in M&D sequence (size between MAC_AND_DESTROY_ADD_SIZE_MIN and
* MAC_AND_DESTROY_ADD_SIZE_MAX). Pass NULL if no additional data should be used.
* @param add_size Length of additional data
* @param final_key Buffer into which final_key will be saved
* @return lt_ret_t LT_OK if correct, otherwise LT_FAIL
*/
static lt_ret_t lt_PIN_entry_check(lt_handle_t *h, const uint8_t *PIN, const uint8_t PIN_size, const uint8_t *add,
const uint8_t add_size, uint8_t *final_key)
{
if (!h || !PIN || (PIN_size < MAC_AND_DESTROY_PIN_SIZE_MIN) || (PIN_size > MAC_AND_DESTROY_PIN_SIZE_MAX)
|| (add_size > MAC_AND_DESTROY_ADD_SIZE_MAX) || !final_key) {
// add parameter is not checked for NULL, because it can be NULL (handled in the lines below)
return LT_PARAM_ERR;
}
if (h->l3.session_status != LT_SECURE_SESSION_ON) {
return LT_HOST_NO_SESSION;
}
uint8_t add_size_checked = add_size;
if (!add) {
add_size_checked = 0;
}
// Clear variable for released final_key so there is known data (zeroes) in case this function ended sooner then
// final_key was prepared
memset(final_key, 0, TR01_MAC_AND_DESTROY_DATA_SIZE);
// Variable used during a process of getting a decryption key k_i
uint8_t v_[LT_HMAC_SHA256_HASH_LEN] = {0};
// Variable used during a process of getting a decryption key k_i
uint8_t w_i[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
// Decryption key
uint8_t k_i[LT_HMAC_SHA256_HASH_LEN] = {0};
// Secret
uint8_t s_[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
// Tag
uint8_t t_[LT_HMAC_SHA256_HASH_LEN] = {0};
// Value used to initialize Mac And Destroy's slot after a correct PIN try
uint8_t u[LT_HMAC_SHA256_HASH_LEN] = {0};
// This organizes data which will be read from nvm
struct lt_macandd_nvm_t nvm = {0};
// User might pass not only PIN, but also another data(e.g. HW ID, ...) if needed
// Both arrays are concatenated and used together as an input for KDF
uint8_t kdf_input_buff[MAC_AND_DESTROY_PIN_SIZE_MAX + MAC_AND_DESTROY_ADD_SIZE_MAX];
memcpy(kdf_input_buff, PIN, PIN_size);
if (!add || add_size_checked == 0) {
LT_LOG_INFO("No additional data will be used in the following M&D sequence");
}
else {
memcpy(kdf_input_buff + PIN_size, add, add_size_checked);
}
// Load M&D data from TROPIC01's R memory
LT_LOG_INFO("Reading M&D data from R_Mem User slot %d...", R_MEM_DATA_SLOT_MACANDD);
uint16_t read_size;
lt_ret_t ret = lt_r_mem_data_read(h, R_MEM_DATA_SLOT_MACANDD, (uint8_t *)&nvm, sizeof(nvm), &read_size);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to read User slot, ret=%s", lt_ret_verbose(ret));
goto exit;
}
LT_LOG_INFO("\tOK");
// if i == 0: FAIL (no attempts remaining)
LT_LOG_INFO("Checking if nvm.i != 0...");
if (nvm.i == 0) {
LT_LOG_ERROR("nvm.i == 0");
goto exit;
}
LT_LOG_INFO("\tOK");
// Decrement variable which holds number of tries
// Let i = i - 1
nvm.i--;
// and store M&D data back to TROPIC01's R memory
LT_LOG_INFO("Writing back M&D data into R_Mem User slot %d (erase, then write)...", R_MEM_DATA_SLOT_MACANDD);
ret = lt_r_mem_data_erase(h, R_MEM_DATA_SLOT_MACANDD);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to erase User slot, ret=%s", lt_ret_verbose(ret));
goto exit;
}
ret = lt_r_mem_data_write(h, R_MEM_DATA_SLOT_MACANDD, (uint8_t *)&nvm, sizeof(nvm));
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to write User slot, ret=%s", lt_ret_verbose(ret));
goto exit;
}
LT_LOG_INFO("\tOK");
// Compute vβ = KDF(0, PINβ||A).
const uint8_t zeros[32] = {0};
lt_hmac_sha256(zeros, sizeof(zeros), kdf_input_buff, PIN_size + add_size_checked, v_);
// Execute wβ = MACANDD(i, vβ)
LT_LOG_INFO("Doing M&D sequence...");
ret = lt_mac_and_destroy(h, nvm.i, v_, w_i);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed while doing M&D sequence, ret=%s", lt_ret_verbose(ret));
goto exit;
}
LT_LOG_INFO("\tOK");
// Compute kβ_i = KDF(wβ, PINβ||A)
lt_hmac_sha256(w_i, sizeof(w_i), kdf_input_buff, PIN_size + add_size_checked, k_i);
// Read the ciphertext c_i and tag t from NVM,
// decrypt c_i with kβ_i as the key and obtain s_
decrypt(nvm.ci + (nvm.i * TR01_MAC_AND_DESTROY_DATA_SIZE), k_i, s_);
// Compute tag t = KDF(s_, "0x00")
lt_hmac_sha256(s_, sizeof(s_), (uint8_t[]){0x00}, 1, t_);
// If tβ != t: FAIL
if (memcmp(nvm.t, t_, sizeof(t_)) != 0) {
ret = LT_FAIL;
goto exit;
}
// Pin is correct, now initialize macandd slots again:
// Compute u = KDF(sβ, "0x01")
lt_hmac_sha256(s_, sizeof(s_), (uint8_t[]){0x01}, 1, u);
for (int x = nvm.i; x < MACANDD_ROUNDS - 1; x++) {
uint8_t ignore[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
LT_LOG_INFO("Doing M&D sequence...");
ret = lt_mac_and_destroy(h, x, u, ignore);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed while doing M&D sequence, ret=%s", lt_ret_verbose(ret));
goto exit;
}
LT_LOG_INFO("\tOK");
}
// Set variable which holds number of tries back to initial state MACANDD_ROUNDS
nvm.i = MACANDD_ROUNDS;
// Store NVM data for future use
LT_LOG_INFO("Writing M&D data into R_Mem User slot %d for future use (erase, then write)...",
R_MEM_DATA_SLOT_MACANDD);
ret = lt_r_mem_data_erase(h, R_MEM_DATA_SLOT_MACANDD);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to erase User slot, ret=%s", lt_ret_verbose(ret));
goto exit;
}
ret = lt_r_mem_data_write(h, R_MEM_DATA_SLOT_MACANDD, (uint8_t *)&nvm, sizeof(nvm));
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to write User slot, ret=%s", lt_ret_verbose(ret));
goto exit;
}
LT_LOG_INFO("\tOK");
// Calculate final_key and store it into passed array
lt_hmac_sha256(s_, sizeof(s_), (uint8_t *)"2", 1, final_key);
// Cleanup all sensitive data from memory
exit:
memset(kdf_input_buff, 0, PIN_size + add_size_checked);
memset(w_i, 0, sizeof(w_i));
memset(k_i, 0, sizeof(k_i));
memset(v_, 0, sizeof(v_));
return ret;
}
int lt_ex_macandd(lt_handle_t *h)
{
LT_LOG_INFO("==========================================");
LT_LOG_INFO("==== TROPIC01 Mac and Destroy Example ====");
LT_LOG_INFO("==========================================");
lt_ret_t ret;
LT_LOG_INFO("Initializing handle");
ret = lt_init(h);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to initialize handle, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Starting Secure Session with key %d", (int)TR01_PAIRING_KEY_SLOT_INDEX_0);
ret = lt_verify_chip_and_start_secure_session(h, sh0priv, sh0pub, TR01_PAIRING_KEY_SLOT_INDEX_0);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to start Secure Session with key %d, ret=%s", (int)TR01_PAIRING_KEY_SLOT_INDEX_0,
lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
// This variable stores final_key which is released to the user after successful PIN check or PIN set
uint8_t final_key_initialized[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
// Additional data passed by user besides PIN - this is optional, but recommended
uint8_t additional_data[]
= {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
// User's PIN
uint8_t pin[] = {1, 2, 3, 4};
uint8_t pin_wrong[] = {2, 2, 3, 4};
LT_LOG_LINE();
LT_LOG_INFO("Initializing Mac And Destroy");
LT_LOG_INFO("Generating random master_secret...");
uint8_t master_secret[TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE] = {0};
ret = lt_random_bytes(h, master_secret, TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE);
if (ret != LT_OK) {
LT_LOG_ERROR("Failed to get random bytes, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_INFO("\tOK");
char print_buff[PRINT_BUFF_SIZE];
ret = lt_print_bytes(master_secret, sizeof(master_secret), print_buff, PRINT_BUFF_SIZE);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_print_bytes failed, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Generated master_secret: %s", print_buff);
// Set the PIN and log out the final_key
LT_LOG("Setting the user PIN...");
ret = lt_new_PIN_setup(h, master_secret, pin, sizeof(pin), NULL, sizeof(additional_data), final_key_initialized);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to set the user PIN, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_INFO("\tOK");
ret = lt_print_bytes(final_key_initialized, sizeof(final_key_initialized), print_buff, PRINT_BUFF_SIZE);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_print_bytes failed, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Initialized final_key: %s", print_buff);
LT_LOG_LINE();
uint8_t final_key_exported[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
LT_LOG_INFO("Doing %d PIN check attempts with wrong PIN...", MACANDD_ROUNDS);
for (int i = 1; i < MACANDD_ROUNDS; i++) {
LT_LOG_INFO("\tInputting wrong PIN -> slot #%d destroyed", i);
ret = lt_PIN_entry_check(h, pin_wrong, sizeof(pin_wrong), NULL, sizeof(additional_data), final_key_exported);
if (LT_FAIL != ret) {
LT_LOG_ERROR("Return value is not LT_FAIL, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
ret = lt_print_bytes(final_key_exported, sizeof(final_key_exported), print_buff, PRINT_BUFF_SIZE);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_print_bytes failed, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_INFO("\tSecret: %s", print_buff);
}
LT_LOG_INFO("\tOK");
LT_LOG_INFO("Doing Final PIN attempt with correct PIN, slots are reinitialized again...");
ret = lt_PIN_entry_check(h, pin, sizeof(pin), NULL, sizeof(additional_data), final_key_exported);
if (LT_OK != ret) {
LT_LOG_ERROR("Attempt with correct PIN failed, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
ret = lt_print_bytes(final_key_exported, sizeof(final_key_exported), print_buff, PRINT_BUFF_SIZE);
if (LT_OK != ret) {
LT_LOG_ERROR("lt_print_bytes failed, ret=%s", lt_ret_verbose(ret));
lt_session_abort(h);
lt_deinit(h);
return -1;
}
LT_LOG_INFO("\tExported final_key: %s", print_buff);
LT_LOG_INFO("\tOK");
LT_LOG_LINE();
if (memcmp(final_key_initialized, final_key_exported, sizeof(final_key_initialized))) {
LT_LOG_ERROR("final_key and final_key_exported DO NOT MATCH");
lt_session_abort(h);
lt_deinit(h);
return -1;
}
else {
LT_LOG_INFO("final_key and final_key_exported MATCH");
}
LT_LOG_INFO("Aborting Secure Session");
ret = lt_session_abort(h);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to abort Secure Session, ret=%s", lt_ret_verbose(ret));
lt_deinit(h);
return -1;
}
LT_LOG_INFO("Deinitializing handle");
ret = lt_deinit(h);
if (LT_OK != ret) {
LT_LOG_ERROR("Failed to deinitialize handle, ret=%s", lt_ret_verbose(ret));
return -1;
}
return 0;
}