firmware/stm32/mk4-bootloader/se2.c
2025-04-10 09:49:37 -04:00

1347 lines
38 KiB
C

/*
* (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC.
*
* se2.c -- Talk to DS28C36B chip which is our second secure element.
*
*/
#include "basics.h"
#include "main.h"
#include "se2.h"
#include "ae.h"
#include "ae_config.h"
#include "aes.h"
#include "secrets.h"
#include "verify.h"
#include "psram.h"
#include "faster_sha256.h"
#include "oled.h"
#include SCREENS_H
#include "console.h"
#include "constant_time.h"
#include "misc.h"
#include "rng.h"
#include "gpio.h"
#include "delay.h"
#include "pins.h"
#include "storage.h"
#include <string.h>
#include "micro-ecc/uECC.h"
#include <setjmp.h>
static I2C_HandleTypeDef i2c_port;
static jmp_buf error_env;
#define CALL_CHECK(cond) if((cond)) longjmp(error_env, __LINE__)
#define CHECK_RIGHT(cond) if(!(cond)) longjmp(error_env, __LINE__)
// fixed value for DS28C36B part
static const uint8_t DEV_MANID[2] = { 0x00, 0x80 };
// DEBUG / setup time.
static se2_secrets_t _tbd;
#define SE2_SECRETS (rom_secrets->se2.pairing[0] == 0xff ? &_tbd : &rom_secrets->se2)
// HAL API requires shift here.
#define I2C_ADDR (0x1b << 1)
// Result codes from chip
// - the meaning depends on the command
#define RC_SUCCESS 0xAA
#define RC_BAD_PARAM 0x77
#define RC_PROTECTED 0x55
#define RC_INVALID_SEQ 0x33
#define RC_NO_ACK 0x0f // mine: no ack on something
#define RC_WRONG_SIZE 0x1f // mine: response wrong size
#define RC_WRITE_FAIL 0x2f // mine: sending command failed
#define RC_READ_FAIL 0x3f // mine: reading response failed
// page numbers (Table 1)
#define PGN_PUBKEY_A 16 // also +1
#define PGN_PUBKEY_B 18 // also +1
#define PGN_PUBKEY_C 20 // also +1
#define PGN_PRIVKEY_A 22
#define PGN_PRIVKEY_B 23
#define PGN_PRIVKEY_C 24
#define PGN_SECRET_A 25
#define PGN_SECRET_B 26
#define PGN_DEC_COUNTER 27
#define PGN_ROM_OPTIONS 28
#define PGN_GPIO 29
#define PGN_PUBKEY_S 30 // also 31, volatile
// our page allocations: mostly for trick pins+their data
#define PGN_TRICK(n) (n)
#define PGN_LAST_TRICK NUM_TRICKS-1
#define PGN_SE2_EASY_KEY 14
#define PGN_SE2_HARD_KEY 15
// page protection bitmask (Table 11)
#define PROT_RP 0x01
#define PROT_WP 0x02
#define PROT_EM 0x04
#define PROT_APH 0x08
#define PROT_EPH 0x10
#define PROT_AUTH 0x20
#define PROT_ECH 0x40 // requires ECW too
#define PROT_ECW 0x80
// Debug output interferes with "--metal" testing mode.
//#ifndef RELEASE
#if 0
# define DEBUG(s) puts(s)
# define DEBUG_OUTPUT 1
#else
# define DEBUG(s)
#endif
// forward defs...
void se2_read_encrypted(uint8_t page_num, uint8_t data[32], int keynum, const uint8_t *secret);
static bool se2_read_hard_secret(uint8_t hard_key[32], const uint8_t pin_digest[32]);
// se2_write1()
//
static bool
se2_write1(uint8_t cmd, uint8_t arg)
{
uint8_t data[3] = { cmd, 1, arg };
HAL_StatusTypeDef rv = HAL_I2C_Master_Transmit(&i2c_port, I2C_ADDR,
data, sizeof(data), HAL_MAX_DELAY);
return (rv != HAL_OK);
}
// se2_write2()
//
static bool
se2_write2(uint8_t cmd, uint8_t arg1, uint8_t arg2)
{
uint8_t data[4] = { cmd, 2, arg1, arg2 };
HAL_StatusTypeDef rv = HAL_I2C_Master_Transmit(&i2c_port, I2C_ADDR,
data, sizeof(data), HAL_MAX_DELAY);
return (rv != HAL_OK);
}
// se2_write_n()
//
static bool
se2_write_n(uint8_t cmd, uint8_t *param1, const uint8_t *data_in, uint8_t len)
{
uint8_t data[2 + (param1?1:0) + len], *p = data;
*(p++) = cmd;
*(p++) = sizeof(data) - 2;
if(param1) {
*(p++) = *param1;
}
if(len) {
memcpy(p, data_in, len);
}
HAL_StatusTypeDef rv = HAL_I2C_Master_Transmit(&i2c_port, I2C_ADDR,
data, sizeof(data), HAL_MAX_DELAY);
return (rv != HAL_OK);
}
// se2_read_n()
//
static uint8_t
se2_read_n(uint8_t len, uint8_t *rx)
{
// Response time of the chip varies from 0ms (read buffer), is typically
// 3ms for simple things, and peaks at 200ms for complex ECC stuff.
// Poll until chip provides an answer.
ASSERT(len >= 2);
for(int tries=0; tries<300; tries++) {
HAL_StatusTypeDef rv = HAL_I2C_Master_Receive(&i2c_port, I2C_ADDR, rx, len, HAL_MAX_DELAY);
if(rv == HAL_OK) {
if(rx[0] != len-1) {
return RC_WRONG_SIZE;
}
return rx[1];
}
delay_ms(1);
}
// timeout
return RC_NO_ACK;
}
// se2_read1()
//
static uint8_t
se2_read1(void)
{
// many commands return a single (framed) status byte, where 0xAA == success
uint8_t rx[2];
return se2_read_n(2, rx);
}
// se2_write_buffer()
//
static void
se2_write_buffer(const uint8_t *data, int len)
{
// no response to this command, just blindly write it
CALL_CHECK(se2_write_n(0x87, NULL, data, len));
}
// se2_write_page()
//
// Caution: Can be read and/or intercepted.
//
void
se2_write_page(uint8_t page_num, const uint8_t data[32])
{
CALL_CHECK(se2_write_n(0x96, &page_num, data, 32));
CHECK_RIGHT(se2_read1() == RC_SUCCESS);
}
// se2_pick_keypair()
//
void
se2_pick_keypair(uint8_t pubkey_num, bool lock)
{
// use device RNG to pick a keypair
ASSERT(pubkey_num < 2);
int wpe = lock ? 0x1 : 0x0;
CALL_CHECK(se2_write1(0xcb, (wpe <<6) | pubkey_num));
CHECK_RIGHT(se2_read1() == RC_SUCCESS);
}
// se2_verify_page()
//
bool
se2_verify_page(uint8_t page_num, uint8_t data[32], int keynum, const uint8_t *secret)
{
// "Compute and Read Page Authentication" using HMAC secret A or S
// .. pick a nonce
uint8_t chal[32];
rng_buffer(chal, sizeof(chal));
se2_write_buffer(chal, sizeof(chal));
// .. do it (HMAC method, not ECDSA)
CALL_CHECK(se2_write1(0xa5, (keynum<<5) | page_num));
uint8_t check[34];
CHECK_RIGHT(se2_read_n(sizeof(check), check) == RC_SUCCESS);
// .. see if we can arrive at same HMAC result.
HMAC_CTX ctx;
hmac_sha256_init(&ctx);
// msg = self.rom_id + expected + chal + bytes([page_num]) + self.manid
hmac_sha256_update(&ctx, SE2_SECRETS->romid, 8);
hmac_sha256_update(&ctx, data, 32);
hmac_sha256_update(&ctx, chal, 32);
hmac_sha256_update(&ctx, &page_num, 1);
hmac_sha256_update(&ctx, DEV_MANID, 2);
uint8_t expect[32];
hmac_sha256_final(&ctx, secret, expect);
return check_equal(expect, check+2, 32);
}
// se2_read_page()
//
// Must always authenticate what we read, because just normal read (no encryption)
// does not have any MiTM protection at all.
//
void
se2_read_page(uint8_t page_num, uint8_t data[32], bool verify)
{
CALL_CHECK(se2_write1(0x69, page_num));
uint8_t rx[2+32];
CHECK_RIGHT(se2_read_n(sizeof(rx), rx) == RC_SUCCESS);
CHECK_RIGHT(rx[0] == 33);
CHECK_RIGHT(rx[1] == RC_SUCCESS);
memcpy(data, rx+2, 32);
if(!verify) return;
CHECK_RIGHT(se2_verify_page(page_num, data, 0, SE2_SECRETS->pairing));
}
// se2_write_encrypted()
//
// - encrypt and write a value.
// - needs existing value to pass auth challenge (so we re-read it)
// - so cannot be used on read-protected pages like keys
//
void
se2_write_encrypted(uint8_t page_num, const uint8_t data[32], int keynum, const uint8_t *secret)
{
// only supporting secret A or S.
ASSERT((keynum == 0) || (keynum == 2));
// need old value to for authentication purposes
uint8_t old_data[32];
se2_read_encrypted(page_num, old_data, keynum, secret);
uint8_t PGDV = page_num | 0x80;
// pick a nonce
// (hmac auth + chal) will be written to the "buffer"
uint8_t chal_check[32+8];
rng_buffer(&chal_check[32], 8);
HMAC_CTX ctx;
hmac_sha256_init(&ctx);
// msg = chal + self.rom_id + PGDV + self.manid
hmac_sha256_update(&ctx, &chal_check[32], 8);
hmac_sha256_update(&ctx, SE2_SECRETS->romid, 8);
hmac_sha256_update(&ctx, &PGDV, 1);
hmac_sha256_update(&ctx, DEV_MANID, 2);
ASSERT(ctx.num_pending == 19);
uint8_t otp[32];
hmac_sha256_final(&ctx, secret, otp);
// encrypt new value
uint8_t tmp[32];
memcpy(tmp, data, 32);
xor_mixin(tmp, otp, 32);
// "tmp" now encrypted, but also need right auth value in buffer
// msg2 = self.rom_id + old_data + new_data + PGDV + self.manid
hmac_sha256_init(&ctx);
hmac_sha256_update(&ctx, SE2_SECRETS->romid, 8);
hmac_sha256_update(&ctx, old_data, 32);
hmac_sha256_update(&ctx, data, 32);
hmac_sha256_update(&ctx, &PGDV, 1);
hmac_sha256_update(&ctx, DEV_MANID, 2);
ASSERT(ctx.num_pending == 75);
hmac_sha256_final(&ctx, secret, chal_check);
// send chip both our nonce (challenge) and also HMAC auth check value
se2_write_buffer(chal_check, sizeof(chal_check));
// send encrypted data now
uint8_t pn = (keynum << 6) | page_num;
CALL_CHECK(se2_write_n(0x99, &pn, tmp, 32));
CHECK_RIGHT(se2_read1() == RC_SUCCESS);
}
// se2_read_encrypted()
//
// - use key to read, but must also do verify because no replay protection otherwise
// - page must be protected with EPH or ECH, and of course !RP
//
void
se2_read_encrypted(uint8_t page_num, uint8_t data[32], int keynum, const uint8_t *secret)
{
// only supporting secret A or S.
ASSERT((keynum == 0) || (keynum == 2));
CALL_CHECK(se2_write1(0x4b, (keynum << 6) | page_num));
uint8_t rx[2+8+32];
CHECK_RIGHT(se2_read_n(sizeof(rx), rx) == RC_SUCCESS);
CHECK_RIGHT(rx[1] == RC_SUCCESS);
// .. decrypt result.
uint8_t *chal = rx+2;
memcpy(data, rx+2+8, 32);
HMAC_CTX ctx;
hmac_sha256_init(&ctx);
// msg = chal + self.rom_id + bytes([page_num]) + self.manid
hmac_sha256_update(&ctx, chal, 8);
hmac_sha256_update(&ctx, SE2_SECRETS->romid, 8);
hmac_sha256_update(&ctx, &page_num, 1);
hmac_sha256_update(&ctx, DEV_MANID, 2);
uint8_t otp[32];
hmac_sha256_final(&ctx, secret, otp);
xor_mixin(data, otp, 32);
// CRITICAL: verify right result using a nonce we pick!
CHECK_RIGHT(se2_verify_page(page_num, data, keynum, secret));
}
// se2_get_protection()
//
// Caution: Use only in a controlled environment! No MiTM protection.
//
uint8_t
se2_get_protection(uint8_t page_num)
{
CALL_CHECK(se2_write1(0xaa, page_num));
return se2_read1();
}
// se2_set_protection()
//
// Caution: Use only in a controlled environment! No MiTM protection.
//
void
se2_set_protection(uint8_t page_num, uint8_t flags)
{
if(se2_get_protection(page_num) == flags) {
return;
}
CALL_CHECK(se2_write2(0xc3, page_num, flags));
CHECK_RIGHT(se2_read1() == RC_SUCCESS);
}
// se2_probe()
//
void
se2_probe(void)
{
// error handling.
if(setjmp(error_env)) {
oled_show(screen_se2_issue);
LOCKUP_FOREVER();
// not reached
}
// See what's attached. Read serial number and verify it using shared secret
rng_delay();
if(rom_secrets->se2.pairing[0] == 0xff) {
// chip not setup yet, ok in factory
} else {
// This is also verifying the pairing secret, effectively.
uint8_t tmp[32];
se2_read_page(PGN_ROM_OPTIONS, tmp, true);
CHECK_RIGHT(check_equal(&tmp[24], rom_secrets->se2.romid, 8));
}
}
// se2_clear_volatile()
//
// No command to reset the volatile state on this chip! Could
// be sensitive at times. 608 has a watchdog for this!!
//
void
se2_clear_volatile(void)
{
// funny business means MitM?
if(setjmp(error_env)) fatal_mitm();
uint8_t z32[32] = {0};
se2_write_page(PGN_PUBKEY_S+0, z32);
se2_write_page(PGN_PUBKEY_S+1, z32);
se2_write_buffer(z32, 32);
// rotate the secret S ... not ideal but only way I've got to change it
// - also clears ECDH_SECRET_S flag
CALL_CHECK(se2_write2(0x3c, (2<<6), 0));
CHECK_RIGHT(se2_read1() == RC_SUCCESS);
}
// se2_set_counter()
//
// Can only be done once. Trusted env.
//
static void
se2_set_counter(uint32_t val)
{
uint8_t tmp[32];
se2_read_page(PGN_DEC_COUNTER, tmp, false);
// datasheet says will read as "random data" if not yet set, but
// observed 0xff, 0xff, 0xff, 0...0 (which is an illegal value, since only 17 bits)
if(tmp[2] == 0xff) {
tmp[0] = val & 0x0ff;
tmp[1] = (val >> 8) & 0x0ff;
tmp[2] = (val >> 16) & 0x01;
se2_write_page(PGN_DEC_COUNTER, tmp);
} else {
puts("ctr set?"); // not expected, but keep going
}
}
// se2_setup_config()
//
// One-time config and lockdown of the SE2 chip.
//
// CONCERN: Must not be possible to call this function after replacing
// the chip deployed originally. But key secrets would have been lost
// by then anyway... looks harmless, and regardless once the slots
// are locked, none of this code will work... but:
//
// IMPORTANT: If they blocked the real chip, and provided a blank one for
// us to write the (existing) pairing secret into, they would see the pairing
// secret in cleartext. They could then restore original chip and access freely.
//
// But once started, we assume operation in a safe trusted environment
// (ie. the Coinkite factory in Toronto).
//
void
se2_setup_config(void)
{
// error handling.
if((setjmp(error_env))) {
oled_show(screen_se2_issue);
LOCKUP_FOREVER();
}
if(rom_secrets->se2.pairing[0] != 0xff) {
// we've been here, so nothing more to do / anything we do will fail, etc.
return;
}
// Global (ram) copy of values to be writen, so we can use them during setup
memset(&_tbd, 0xff, sizeof(_tbd));
// pick internal keys
rng_buffer(_tbd.tpin_key, 32);
// capture serial of device
uint8_t tmp[32];
se2_read_page(PGN_ROM_OPTIONS, tmp, false);
ASSERT(tmp[1] == 0x00); // check ANON is not set
memcpy(_tbd.romid, tmp+24, 8);
// forget a secret - B (will not be saved)
rng_buffer(tmp, 32);
se2_write_page(PGN_SECRET_B, tmp);
// have chip pick a keypair, record public part for later
se2_pick_keypair(0, true);
se2_read_page(PGN_PUBKEY_A, &_tbd.pubkey_A[0], false);
se2_read_page(PGN_PUBKEY_A+1, &_tbd.pubkey_A[32], false);
// Burn privkey B with garbage. Invalid ECC key like this cannot
// be used (except to make errors)
memset(tmp, 0, 32);
se2_write_page(PGN_PRIVKEY_B, tmp);
se2_write_page(PGN_PRIVKEY_B+1, tmp);
se2_write_page(PGN_PUBKEY_B, tmp);
se2_write_page(PGN_PUBKEY_B+1, tmp);
// pick a paring secret (A)
do {
rng_buffer(_tbd.pairing, 32);
} while(_tbd.pairing[0] == 0xff);
se2_write_page(PGN_SECRET_A, _tbd.pairing);
// called the "easy" key, this one requires only SE2 pairing to read/write
// - so we can wipe it anytime as part of bricking (maybe)
// - but also so that more than just the paired pubkey w/ SE1 is needed
rng_buffer(tmp, 32);
se2_write_page(PGN_SE2_EASY_KEY, tmp);
// wipe all trick pins and data slots
memset(tmp, 0, 32);
for(int pn=0; pn <= PGN_LAST_TRICK; pn++) {
se2_write_page(pn, tmp);
}
// save the shared secrets for ourselves, in flash
flash_save_se2_data(&_tbd);
// Now safe to lock down the SE2; failures up to this point could be
// recovered by picking new values. After this, if main flash corrupt, no
// way to read these values back, nor replace them with new ones.
se2_set_protection(PGN_SECRET_A, PROT_WP);
se2_set_protection(PGN_SECRET_B, PROT_WP);
se2_set_protection(PGN_PUBKEY_A, PROT_WP);
se2_set_protection(PGN_PUBKEY_B, PROT_WP);
se2_set_protection(PGN_SE2_EASY_KEY, PROT_EPH);
for(int pn=0; pn <= PGN_LAST_TRICK; pn++) {
se2_set_protection(pn, PROT_EPH);
}
se2_set_protection(PGN_ROM_OPTIONS, PROT_APH); // not planning to change
// Need known value in counter, write once.
se2_set_counter(128);
// NOTE: PGN_SE2_HARD_KEY and PUBKEY_C not yet known
}
// se2_save_auth_pubkey()
//
// Record and enable an ECC pubkey for joining purposes.
// - trusted env. so no need for encrypted comms
//
void
se2_save_auth_pubkey(const uint8_t pubkey[64])
{
if(setjmp(error_env)) fatal_mitm();
ASSERT(check_all_ones(rom_secrets->se2.auth_pubkey, 64));
memcpy(&_tbd, &rom_secrets->se2, sizeof(_tbd));
// pick the "hard" key now
uint8_t tmp[32];
rng_buffer(tmp, 32);
se2_write_page(PGN_SE2_HARD_KEY, tmp);
// save SE1 pubkey into "pubkey C"
se2_write_page(PGN_PUBKEY_C, &pubkey[0]);
se2_write_page(PGN_PUBKEY_C+1, &pubkey[32]);
memcpy(_tbd.auth_pubkey, pubkey, 64);
// commit pubkey to mcu flash
flash_save_se2_data(&_tbd);
// lock it all up
se2_set_protection(PGN_SE2_HARD_KEY, PROT_WP | PROT_ECH | PROT_ECW);
se2_set_protection(PGN_PUBKEY_C, PROT_WP | PROT_RP | PROT_AUTH);
}
// se2_clear_tricks()
//
// Wipe all the trick PIN's and their data.
//
void
se2_clear_tricks(void)
{
se2_setup();
// funny business means MitM?
if(setjmp(error_env)) fatal_mitm();
// wipe with all zeros
uint8_t tmp[32] = {0};
for(int pn=0; pn <= PGN_LAST_TRICK; pn++) {
se2_write_encrypted(pn, tmp, 0, SE2_SECRETS->pairing);
}
}
// se2_read_trick_data()
//
// Read 1 or 2 data slots that immediately follow a trick PIN slot.
//
void
se2_read_trick_data(int slot_num, uint16_t tc_flags, uint8_t data[64])
{
if(setjmp(error_env)) fatal_mitm();
se2_setup();
se2_read_encrypted(slot_num+1, &data[0], 0, SE2_SECRETS->pairing);
if(tc_flags & TC_XPRV_WALLET) {
se2_read_encrypted(slot_num+2, &data[32], 0, SE2_SECRETS->pairing);
}
}
// se2_test_trick_pin()
//
// Search if this PIN code should trigger a "trick"
// - if not in safety mode, the side-effect (brick, etc) will have happened before this returns
// - will always check all slots so bus traffic doesn't change based on result.
//
bool
se2_test_trick_pin(const char *pin, int pin_len, trick_slot_t *found_slot, bool safety_mode)
{
se2_setup();
// error handling.
if(setjmp(error_env)) {
// remember messing w/ i2c bus during operation could lead here.
if(!safety_mode) fatal_mitm();
return false;
}
// zero-length pin not allowed
if(!pin_len) return false;
uint8_t tpin_hash[32];
trick_pin_hash(pin, pin_len, tpin_hash);
// always read all data first, and without any time differences
uint8_t slots[NUM_TRICKS][32];
int pn = PGN_TRICK(0);
for(int i=0; i<NUM_TRICKS; i++, pn++) {
se2_read_encrypted(pn, slots[i], 0, SE2_SECRETS->pairing);
}
se2_clear_volatile();
// Look for matches
int found = -1;
uint32_t blank = 0;
for(int i=0; i<NUM_TRICKS; i++) {
uint8_t *here = &slots[i][0];
if(check_equal(here, tpin_hash, 28)) {
// we have a winner... but keep checking
found = i;
}
blank |= (!!check_all_zeros(here, 32)) << i;
}
rng_delay();
memset(found_slot, 0, sizeof(trick_slot_t));
if(safety_mode) {
// tell them which slots are available, iff working after main pin is set
found_slot->blank_slots = blank;
}
if(found >= 0) {
// match found
found_slot->slot_num = found;
// 28 bytes are the PIN hash, last 4 bytes is our meta-data.
// - xor to pin value to hide flag/arg values (impt for deltamode)
// - following slot(s) may hold wallet data (32-64 bytes)
uint8_t meta[4];
memcpy(meta, &slots[found][28], 4);
xor_mixin(meta, &tpin_hash[28], 4);
memcpy(&found_slot->tc_flags, &meta[0], 2);
memcpy(&found_slot->tc_arg, &meta[2], 2);
uint16_t todo = found_slot->tc_flags;
// hmm: don't need this data if safety is off.. but we have it anyway
if(todo & TC_WORD_WALLET) {
// it's a 12/24-word BIP-39 seed phrase, un-encrypted.
if(found+1 < NUM_TRICKS) {
memcpy(found_slot->xdata, &slots[found+1][0], 32);
}
} else if(todo & TC_XPRV_WALLET) {
// it's an xprv-based wallet
if(found+2 < NUM_TRICKS) {
memcpy(&found_slot->xdata[0], &slots[found+1][0], 32);
memcpy(&found_slot->xdata[32], &slots[found+2][0], 32);
}
}
if(!safety_mode && todo) {
#ifdef DEBUG_OUTPUT
puts2("Trick activated: ");
puthex4(todo);
putchar(' ');
#endif
// code here to brick or wipe
if(todo & TC_WIPE) {
// wipe keys - useful to combine with other stuff
// .. see below if not combined w/ a fatal action
mcu_key_clear(NULL);
DEBUG("wiped");
if(todo == TC_WIPE) {
// we wiped, but no faking it out or rebooting, so
// show attacker we are wiped, and die.
// - need to still allow WIPE+WALLET case
oled_show(screen_wiped);
LOCKUP_FOREVER();
}
}
if(todo & TC_BRICK) {
fast_brick();
// NOT REACHED; unit locks up w/ msg shown
}
if(todo & TC_REBOOT) {
// just reboot, but might look like we wiped secret
NVIC_SystemReset();
}
if(todo & TC_FAKE_OUT) {
// Pretend PIN was not found...
// - probably combined w/ wipe above.
DEBUG("fakeout");
goto fake_out;
}
// TC_DELTA_MODE implemented by caller
}
return true;
} else {
fake_out:
// do similar work?
found_slot->slot_num = -1;
rng_delay();
return false;
}
}
// se2_save_trick()
//
// Save trick setup. T if okay
//
int
se2_save_trick(const trick_slot_t *config)
{
se2_setup();
if(setjmp(error_env)) {
return EPIN_SE2_FAIL;
}
if((config->slot_num < 0) || (config->slot_num >= NUM_TRICKS) ) {
return EPIN_RANGE_ERR;
}
if((config->slot_num >= NUM_TRICKS-1) && (config->tc_flags & TC_WORD_WALLET) ) {
// last slot cannot hold a seed-word wallet.
return EPIN_RANGE_ERR;
}
if((config->slot_num >= NUM_TRICKS-2) && (config->tc_flags & TC_XPRV_WALLET) ) {
// last slot cannot hold an xprv wallet.
return EPIN_RANGE_ERR;
}
if(config->pin_len > sizeof(config->pin)) {
return EPIN_RANGE_ERR;
}
if(config->blank_slots) {
// blank indicated slots
uint8_t zeros[32] = { 0 };
for(int i=0; i<NUM_TRICKS; i++) {
uint32_t mask = (1 << i);
if(mask & config->blank_slots) {
se2_write_encrypted(PGN_TRICK(i), zeros, 0, SE2_SECRETS->pairing);
}
}
} else {
// construct data to save
uint8_t tpin_digest[32];
trick_pin_hash(config->pin, config->pin_len, tpin_digest);
// save meta data, xor'd with pin hash
uint8_t meta[4];
memcpy(&meta[0], &config->tc_flags, 2);
memcpy(&meta[2], &config->tc_arg, 2);
xor_mixin(&tpin_digest[28], meta, 4);
// write into SE2
se2_write_encrypted(PGN_TRICK(config->slot_num), tpin_digest, 0, SE2_SECRETS->pairing);
if(config->tc_flags & (TC_WORD_WALLET | TC_XPRV_WALLET)) {
se2_write_encrypted(PGN_TRICK(config->slot_num+1), &config->xdata[0],
0, SE2_SECRETS->pairing);
}
if(config->tc_flags & TC_XPRV_WALLET) {
se2_write_encrypted(PGN_TRICK(config->slot_num+2), &config->xdata[32],
0, SE2_SECRETS->pairing);
}
}
return 0;
}
// se2_handle_bad_pin()
//
// Attacker (or confused owner) has just given a wrong PIN code (didn't match true
// PIN nor any trick PIN)... maybe do something special.
//
void
se2_handle_bad_pin(int num_fails)
{
trick_slot_t slot;
bool is_trick = se2_test_trick_pin("!p", 2, &slot, true);
if(!is_trick) return;
// Are we configured to do something in this case?
if(num_fails >= slot.tc_arg) {
if(slot.tc_flags & TC_WIPE) {
// Wipe keys and stop. They can power cycle and keep trying
// so only do this if a valid key currently exists.
if(slot.tc_flags & TC_BRICK) {
// special case TC_WIPE|TC_BRICK
bool valid;
const mcu_key_t *cur = mcu_key_get(&valid);
if(valid) {
mcu_key_clear(cur);
oled_show(screen_wiped);
LOCKUP_FOREVER();
}
// else fall-thru if no keys to wipe and WIPE|BRICK mode, will now brick
// used in "Last Chance" mode
} else {
mcu_key_clear(NULL); // does valid key check
if(slot.tc_flags == TC_WIPE) {
oled_show(screen_wiped);
LOCKUP_FOREVER();
}
}
}
if(slot.tc_flags & TC_BRICK) {
// Not mutually exclusive: if both flags are set, the first
// time it's triggered the seed will be wiped (and then lockup)
// Next wrong pin will not have a seed to clear, and so this
// brick code will happen.
fast_brick();
}
if(slot.tc_flags & TC_REBOOT) {
NVIC_SystemReset();
}
//if(slot.tc_flags & TC_FAKE_OUT) {//nothing to do here - Silent Wipe}
// only used together with TC_WIPE. At this point we are already wiped
// EPIN_AUTH_FAIL handled by caller
}
}
// trick_pin_hash()
//
// Do our hashing of a possible PIN code. Must be:
// - unique per device
// - unrelated to hashing of any other PIN codes
// - so doing hmac-sha256 with unique key
//
void
trick_pin_hash(const char *pin, int pin_len, uint8_t tpin_hash[32])
{
ASSERT(pin_len >= 0); // 12-12 typical, but empty = blank PIN
HMAC_CTX ctx;
hmac_sha256_init(&ctx);
hmac_sha256_update(&ctx, (uint8_t *)pin, pin_len);
hmac_sha256_final(&ctx, SE2_SECRETS->tpin_key, tpin_hash);
// and a double SHA for good measure
sha256_single(tpin_hash, 32, tpin_hash);
sha256_single(tpin_hash, 32, tpin_hash);
}
// rng_for_uECC()
//
static int
rng_for_uECC(uint8_t *dest, unsigned size)
{
/* The RNG function should fill 'size' random bytes into 'dest'. It should return 1 if
'dest' was filled with random data, or 0 if the random data could not be generated.
The filled-in values should be either truly random, or from a cryptographically-secure PRNG.
typedef int (*uECC_RNG_Function)(uint8_t *dest, unsigned size);
*/
rng_buffer(dest, size);
return 1;
}
// p256_gen_keypair()
//
void
p256_gen_keypair(uint8_t privkey[32], uint8_t pubkey[64])
{
uECC_set_rng(rng_for_uECC);
int ok = uECC_make_key(pubkey, privkey, uECC_secp256r1());
ASSERT(ok == 1);
}
#if 0
// p256_sign()
//
void
p256_sign(const uint8_t privkey[32], const uint8_t digest[32], uint8_t signature[64])
{
uECC_set_rng(rng_for_uECC);
int ok = uECC_sign(privkey, digest, 32, signature, uECC_secp256r1());
ASSERT(ok == 1);
}
// p256_verify()
//
bool
p256_verify(const uint8_t pubkey[64], const uint8_t digest[32], const uint8_t signature[64])
{
return uECC_verify(pubkey, digest, 32, signature, uECC_secp256r1());
}
#endif
// ps256_ecdh()
//
void
ps256_ecdh(const uint8_t pubkey[64], const uint8_t privkey[32], uint8_t result[32])
{
uECC_set_rng(rng_for_uECC);
int ok = uECC_shared_secret(pubkey, privkey, result, uECC_secp256r1());
ASSERT(ok == 1);
}
// se2_setup()
//
void
se2_setup(void)
{
if(i2c_port.Instance == I2C2) {
return;
}
STATIC_ASSERT(sizeof(trick_slot_t) == 128);
STATIC_ASSERT(offsetof(trick_slot_t, slot_num) == 0);
STATIC_ASSERT(offsetof(trick_slot_t, tc_flags) == 4);
STATIC_ASSERT(offsetof(trick_slot_t, tc_arg) == 6);
STATIC_ASSERT(offsetof(trick_slot_t, xdata) == 8);
STATIC_ASSERT(offsetof(trick_slot_t, pin) == 8+64);
STATIC_ASSERT(offsetof(trick_slot_t, pin_len) == 8+64+16);
STATIC_ASSERT(offsetof(trick_slot_t, blank_slots) == 8+64+16+4);
STATIC_ASSERT(offsetof(trick_slot_t, spare) == 8+64+16+4+4);
// unlikely we need:
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_I2C2_CLK_ENABLE();
// I2C2 bus is dedicated to our DS28C36B part.
// - B13 and B14
GPIO_InitTypeDef setup = {
.Pin = GPIO_PIN_13 | GPIO_PIN_14,
.Mode = GPIO_MODE_AF_OD,
.Pull = GPIO_NOPULL,
.Speed = GPIO_SPEED_FREQ_VERY_HIGH,
.Alternate = GPIO_AF4_I2C2,
};
HAL_GPIO_Init(GPIOB, &setup);
// Setup HAL device
memset(&i2c_port, 0, sizeof(i2c_port));
i2c_port.Instance = I2C2;
// see I2C_InitTypeDef
i2c_port.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
//i2c_port.Init.Timing = 0x0050174f; // ie. 1Mhz "fast mode plus" in CubeMX @ 120Mhz
i2c_port.Init.Timing = 0x00b03fb8; // 400khz "fast mode" in CubeMX @ 120Mhz (measured ok)
//i2c_port.Init.Timing = 0xf01075ff; // 40khz "std mode" in CubeMX @ 120Mhz (works)
i2c_port.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_StatusTypeDef rv = HAL_I2C_Init(&i2c_port);
ASSERT(rv == HAL_OK);
// compile time checks
STATIC_ASSERT(offsetof(rom_secrets_t, se2) % 8 == 0);
STATIC_ASSERT(PGN_LAST_TRICK < PGN_SE2_EASY_KEY);
}
// se2_read_hard_secret()
//
static bool
se2_read_hard_secret(uint8_t hard_key[32], const uint8_t pin_digest[32])
{
if(setjmp(error_env)) {
DEBUG("se2_read_hard_secret");
return true;
}
// To read the "hard" key from SE1, we need to prove we know the
// pubkey_C private key by signing a message. That message is the pubkey
// we want to used for ECDH on our side. Doing ECDH with SE1 will
// generate a shared secret for decryption (held in "secret S" of SE2).
// SE1 holds the auth keypair (private part), and that slot is authorized
// by main pin.
//
// - tell se2 the pubkey for this op
// - sign a 64 byte msg, which includes that pubkey
// - use the pubkey A value (from SE1) to do ECDH => shared secret
// - use shared secret to read slot w/ the hard key in it.
//
SHA256_CTX ctx;
// pick a temp key pair, share public part w/ SE2
uint8_t tmp_privkey[32], tmp_pubkey[64];
p256_gen_keypair(tmp_privkey, tmp_pubkey);
// - this can be mitm-ed, but we sign it next so doesn't matter
se2_write_page(PGN_PUBKEY_S, &tmp_pubkey[0]);
se2_write_page(PGN_PUBKEY_S+1, &tmp_pubkey[32]);
// pick nonce
uint8_t chal[32+32];
rng_buffer(chal, sizeof(chal));
se2_write_buffer(chal, sizeof(chal));
// md = ngu.hash.sha256s(T_pubkey + chal[0:32])
sha256_init(&ctx);
sha256_update(&ctx, tmp_pubkey, 64);
sha256_update(&ctx, chal, 32); // only first 32 bytes
uint8_t md[32];
sha256_final(&ctx, md);
// Get that digest signed by SE1 now, and doing that requires
// the main pin, because the required slot requires auth by that key.
// - this is the critical step attackers would not be able to emulate w/o SE1 contents
// - fails here if PIN wrong
uint8_t signature[64];
int arc = ae_sign_authed(KEYNUM_joiner_key, md, signature, KEYNUM_main_pin, pin_digest);
CHECK_RIGHT(arc == 0);
// "Authenticate ECDSA Public Key" = 0xA8
// cs_offset=32 ecdh_keynum=0=pubA ECDH=1 WR=0
uint8_t param = ((32-1) << 3) | (0 << 2) | 0x2;
se2_write_n(0xA8, &param, signature, 64);
CHECK_RIGHT(se2_read1() == RC_SUCCESS);
uint8_t shared_x[32], shared_secret[32];
ps256_ecdh(rom_secrets->se2.pubkey_A, tmp_privkey, shared_x);
// shared secret S will be SHA over X of shared ECDH point + chal[32:]
// s = ngu.hash.sha256s(x + chal[32:])
sha256_init(&ctx);
sha256_update(&ctx, shared_x, 32);
sha256_update(&ctx, &chal[32], 32); // second half
sha256_final(&ctx, shared_secret);
se2_read_encrypted(PGN_SE2_HARD_KEY, hard_key, 2, shared_secret);
// CONCERN: secret "S" is retained in SE2's sram. No API to clear it.
// - but you'd need to see our copy of that value to make use of it
// - and PIN checked already to get here, so you could re-do anyway
se2_clear_volatile();
return false;
}
// se2_calc_seed_key()
//
static bool
se2_calc_seed_key(uint8_t aes_key[32], const mcu_key_t *mcu_key, const uint8_t pin_digest[32])
{
// Gather key parts from all over. Combine them w/ HMAC into a AES-256 key
uint8_t se1_easy_key[32], se1_hard_key[32];
se2_read_encrypted(PGN_SE2_EASY_KEY, se1_easy_key, 0, rom_secrets->se2.pairing);
if(se2_read_hard_secret(se1_hard_key, pin_digest)) return true;
HMAC_CTX ctx;
hmac_sha256_init(&ctx);
hmac_sha256_update(&ctx, mcu_key->value, 32);
hmac_sha256_update(&ctx, se1_hard_key, 32);
hmac_sha256_update(&ctx, se1_easy_key, 32);
// combine them all using anther MCU key via HMAC-SHA256
hmac_sha256_final(&ctx, rom_secrets->mcu_hmac_key, aes_key);
hmac_sha256_init(&ctx); // clear secrets
return false;
}
// se2_encrypt_secret()
//
bool
se2_encrypt_secret(const uint8_t secret[], int secret_len, int offset,
uint8_t main_slot[], uint8_t *check_value,
const uint8_t pin_digest[32])
{
se2_setup();
bool is_valid;
const mcu_key_t *cur = mcu_key_get(&is_valid);
if(!is_valid) {
if(!check_value) {
// problem: we are not writing the check value but it would be changed.
// ie: change long secret before real secret--unlikely
return true;
}
// pick a fresh MCU key if we don't have one; can do that
// because we are encryption and saving (presumably for first time)
// - will become a brick if no more slots
cur = mcu_key_pick();
}
uint8_t aes_key[32];
if(se2_calc_seed_key(aes_key, cur, pin_digest)) return true;
uint8_t nonce[16];
memcpy(nonce, rom_secrets->mcu_hmac_key, sizeof(nonce)-1);
nonce[15] = offset / AES_BLOCK_SIZE;
// encrypt the secret
AES_CTX ctx;
aes_init(&ctx);
aes_add(&ctx, secret, secret_len);
aes_done(&ctx, main_slot, secret_len, aes_key, nonce);
if(check_value) {
// encrypt the check value: 32 zeros
aes_init(&ctx);
ctx.num_pending = 32;
aes_done(&ctx, check_value, 32, aes_key, nonce);
}
return false;
}
// se2_decrypt_secret()
//
void
se2_decrypt_secret(uint8_t secret[], int secret_len, int offset,
const uint8_t main_slot[], const uint8_t *check_value,
const uint8_t pin_digest[32], bool *is_valid)
{
se2_setup();
const mcu_key_t *cur = mcu_key_get(is_valid);
if(!*is_valid) {
// no key set? won't be able to decrypt.
return;
}
int line_num;
if((line_num = setjmp(error_env))) {
// internal failures / broken i2c buses will come here
*is_valid = false;
return;
}
AES_CTX ctx;
uint8_t aes_key[32];
if(se2_calc_seed_key(aes_key, cur, pin_digest)) {
// key fetch, perhaps pin digest wrong?
*is_valid = false;
return;
}
uint8_t nonce[16];
memcpy(nonce, rom_secrets->mcu_hmac_key, sizeof(nonce)-1);
nonce[15] = offset / AES_BLOCK_SIZE;
if(check_value) {
// decrypt the check value
aes_init(&ctx);
aes_add(&ctx, check_value, 32);
uint8_t got[32];
aes_done(&ctx, got, 32, aes_key, nonce);
// does it work?
if(!check_all_zeros(got, 32)) {
DEBUG("bad chk");
*is_valid = false;
return;
}
}
// decrypt the real data
aes_init(&ctx);
aes_add(&ctx, main_slot, secret_len);
aes_done(&ctx, secret, secret_len, aes_key, nonce);
}
// Key-stretching iteration count. Targeting 1s to rate-limit pin attempts
// 150 => 881ms
// 170 => 999.2ms
// 175 => 1028ms
// 180 => 1058ms
// 200 => 1175ms
#define SE2_STRETCH_ITER 170
// se2_pin_hash()
//
// Hash up a PIN code for login attempt: to tie it into SE2's contents.
//
void
se2_pin_hash(uint8_t digest_io[32], uint32_t purpose)
{
if(purpose != PIN_PURPOSE_NORMAL) {
// do nothing except for real PIN case (ie. not for prefix words)
return;
}
se2_setup();
if((setjmp(error_env))) {
oled_show(screen_se2_issue);
LOCKUP_FOREVER();
}
uint8_t rx[34]; // 2 bytes of len+status, then 32 bytes of data
uint8_t tmp[32];
HMAC_CTX ctx;
// HMAC(key=tpin_key, msg=given hash so far)
hmac_sha256_init(&ctx);
hmac_sha256_update(&ctx, digest_io, 32);
hmac_sha256_update(&ctx, (uint8_t *)&purpose, 4);
hmac_sha256_final(&ctx, SE2_SECRETS->tpin_key, tmp);
// NOTE: exposed as cleartext here
se2_write_buffer(tmp, 32);
for(int i=0; i<SE2_STRETCH_ITER; i++) {
if(i) {
se2_write_buffer(rx+2, 32);
}
// HMAC(key=secret-B, msg=consts+easy_key+buffer+consts)
// - result put in secret-S (ram)
CALL_CHECK(se2_write2(0x3c, (2<<6) | (1<<4) | PGN_SE2_EASY_KEY, 0));
CHECK_RIGHT(se2_read1() == RC_SUCCESS);
// HMAC(key=S, msg=counter+junk), so we have something to read out
// - not 100% clear what contents of 'buffer' are here, but seems
// to be deterministic and unchanged from prev command
CALL_CHECK(se2_write1(0xa5, (2<<5) | PGN_DEC_COUNTER));
CHECK_RIGHT(se2_read_n(sizeof(rx), rx) == RC_SUCCESS);
CHECK_RIGHT(rx[1] == RC_SUCCESS);
}
// one final HMAC because we had to read cleartext from bus
hmac_sha256_init(&ctx);
hmac_sha256_update(&ctx, rx+2, 32);
hmac_sha256_update(&ctx, digest_io, 32);
hmac_sha256_final(&ctx, SE2_SECRETS->tpin_key, digest_io);
}
// se2_read_rng()
//
// Read some random bytes, which we know cannot be MitM'ed.
//
void
se2_read_rng(uint8_t value[8])
{
// funny business means MitM here
se2_setup();
if(setjmp(error_env)) fatal_mitm();
// read a field with "RPS" bytes, and verify those were read true
uint8_t tmp[32];
se2_read_page(PGN_ROM_OPTIONS, tmp, true);
memcpy(value, &tmp[4], 8);
}
// EOF