You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1149 lines
29 KiB
1149 lines
29 KiB
// SPDX-FileCopyrightText: 2020 Foundation Devices, Inc. <hello@foundationdevices.com>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
// SPDX-FileCopyrightText: 2018 Coinkite, Inc. <coldcardwallet.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
//
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
|
|
#include "stm32h7xx_hal.h"
|
|
#include "stm32h7xx_hal_uart.h"
|
|
#include "stm32h7xx_hal_uart_ex.h"
|
|
|
|
#include "delay.h"
|
|
#include "pprng.h"
|
|
#include "sha256.h"
|
|
#include "secrets.h"
|
|
#include "se.h"
|
|
#include "se-config.h"
|
|
#include "utils.h"
|
|
|
|
#ifndef PASSPORT_BOOTLOADER
|
|
#include "display.h"
|
|
#endif /* PASSPORT_BOOTLOADER */
|
|
|
|
/*
|
|
* This bit should be defined in the STM32H7 header files but it is not...
|
|
* somehow was missed. It is a valid bit in the interrupt status register
|
|
* so we'll define it here so as not to mess with the micropython HAL
|
|
* installation.
|
|
*/
|
|
#define UART_FLAG_RTOF ((uint32_t)0x00000800U)
|
|
|
|
// "one wire" is on PA0 aka. UART4
|
|
#define MY_UART UART4
|
|
|
|
/* SE error codes */
|
|
#define SE_SUCCESS 0x00
|
|
#define SE_CHECKMAC_MISCOMPARE 0x01
|
|
#define SE_PARSE_ERROR 0x03
|
|
#define SE_ECC_FAULT 0x05
|
|
#define SE_SELF_TEST_ERROR 0x07
|
|
#define SE_EXECUTION_ERROR 0x0F
|
|
#define SE_WAKE_ACK 0x11
|
|
#define SE_WATCHDOG_EXPIRING 0xEE
|
|
#define SE_COMMS_ERROR 0xFF
|
|
|
|
/* SE extended error codes */
|
|
#define SE_EX_RETRY_OUT 0xE0
|
|
|
|
#define STATS(x)
|
|
|
|
static uint8_t last_error;
|
|
|
|
uint32_t crc_errors;
|
|
uint32_t not_ready_n;
|
|
uint32_t short_error;
|
|
uint32_t len_error;
|
|
uint32_t len_error_two;
|
|
uint32_t ln_retry;
|
|
uint32_t retry_out;
|
|
uint32_t wdgtimeout;
|
|
|
|
uint32_t rtof;
|
|
uint32_t rxne;
|
|
uint32_t notrxne;
|
|
|
|
const char *copyright_msg = "(C) 2020 Foundation Devices Inc.";
|
|
|
|
static seopcode_t current_opcode;
|
|
|
|
// Bit patterns to be sent
|
|
#define BIT0 0x7d
|
|
#define BIT1 0x7f
|
|
|
|
// These control the direction of the single wire bus
|
|
typedef enum {
|
|
IOFLAG_CMD = 0x77,
|
|
IOFLAG_TX = 0x88,
|
|
IOFLAG_IDLE = 0xBB,
|
|
IOFLAG_SLEEP = 0xCC,
|
|
} ioflag_t;
|
|
|
|
#ifndef PASSPORT_BOOTLOADER
|
|
static char se_error[23];
|
|
static char *error_to_str(uint8_t error)
|
|
{
|
|
switch (error)
|
|
{
|
|
case SE_SUCCESS: strcpy(se_error, "SE_SUCCESS"); break;
|
|
case SE_CHECKMAC_MISCOMPARE: strcpy(se_error, "SE_CHECKMAC_MISCOMPARE"); break;
|
|
case SE_PARSE_ERROR: strcpy(se_error, "SE_PARSE_ERROR"); break;
|
|
case SE_ECC_FAULT: strcpy(se_error, "SE_ECC_FAULT"); break;
|
|
case SE_SELF_TEST_ERROR: strcpy(se_error, "SE_SELF_TEST_ERROR"); break;
|
|
case SE_EXECUTION_ERROR: strcpy(se_error, "SE_EXECUTION_ERROR"); break;
|
|
case SE_WAKE_ACK: strcpy(se_error, "SE_WAKE_ACK"); break;
|
|
case SE_WATCHDOG_EXPIRING: strcpy(se_error, "SE_WATCHDOG_EXPIRING"); break;
|
|
case SE_COMMS_ERROR: strcpy(se_error, "SE_COMMS_ERROR"); break;
|
|
case SE_EX_RETRY_OUT: strcpy(se_error, "SE_EX_RETRY_OUT"); break;
|
|
default: strcpy(se_error, "unknown error"); break;
|
|
}
|
|
return se_error;
|
|
}
|
|
#endif /* PASSPORT_BOOTLOADER */
|
|
|
|
uint8_t se_show_error(void)
|
|
{
|
|
uint8_t error = last_error;
|
|
#ifndef PASSPORT_BOOTLOADER
|
|
printf("[%s] last SE error: %s, (%02X)\n", __func__, error_to_str(last_error), last_error);
|
|
#endif /* PASSPORT_BOOTLOADER */
|
|
last_error = 0;
|
|
return error;
|
|
}
|
|
|
|
static inline void _send_byte(uint8_t ch)
|
|
{
|
|
// reset timeout timer (Systick)
|
|
uint32_t ticks = 0;
|
|
SysTick->VAL = 0;
|
|
|
|
while (!(MY_UART->ISR & UART_FLAG_TXE)) {
|
|
// busy-wait until able to send (no fifo?)
|
|
if (SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) {
|
|
// failsafe timeout
|
|
ticks += 1;
|
|
if(ticks > 10) break;
|
|
}
|
|
}
|
|
MY_UART->TDR = ch;
|
|
}
|
|
|
|
static void _send_bits(uint8_t tx)
|
|
{
|
|
// serialize and send one byte
|
|
uint8_t mask = 0x1;
|
|
|
|
for (int i=0; i<8; i++, mask <<= 1) {
|
|
uint8_t h = (tx & mask) ? BIT1 : BIT0;
|
|
|
|
_send_byte(h);
|
|
}
|
|
}
|
|
|
|
static void _send_serialized(const uint8_t *buf, int len)
|
|
{
|
|
for (int i=0; i<len; i++) {
|
|
_send_bits(buf[i]);
|
|
}
|
|
}
|
|
|
|
// Return -1 in case of timeout, else one byte.
|
|
//
|
|
static inline int _read_byte(void)
|
|
{
|
|
uint32_t ticks = 0;
|
|
|
|
// reset timeout timer (Systick)
|
|
SysTick->VAL = 0;
|
|
|
|
while (!(MY_UART->ISR & UART_FLAG_RXNE) && !(MY_UART->ISR & UART_FLAG_RTOF)) {
|
|
// busy-waiting
|
|
|
|
if (SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) {
|
|
ticks += 1;
|
|
if (ticks >= 5) {
|
|
// a full Xms has been wasted; give up.
|
|
|
|
// NOTE: this is a failsafe long timeout, not reached in
|
|
// practise because the bit-time timeout from UART (RTOF)
|
|
++notrxne;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MY_UART->ISR & UART_FLAG_RXNE) {
|
|
++rxne;
|
|
return MY_UART->RDR & 0x7f;
|
|
}
|
|
if (MY_UART->ISR & UART_FLAG_RTOF) {
|
|
// "fast" timeout reached, clear flag
|
|
++rtof;
|
|
MY_UART->ICR = USART_ICR_RTOCF;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Return a deserialized byte, or -1 for timeout.
|
|
//
|
|
static void deserialize(const uint8_t *from, int from_len, uint8_t *into, int max_into)
|
|
{
|
|
while (from_len > 0) {
|
|
uint8_t rv = 0, mask = 0x1;
|
|
|
|
for (int i=0; i<8; i++, mask <<= 1) {
|
|
#if 1
|
|
if ((from[i] ^ 0x7F) < 2)
|
|
rv |= mask;
|
|
#else
|
|
if (from[i] == BIT1) {
|
|
rv |= mask;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
*(into++) = rv;
|
|
from += 8;
|
|
from_len -= 8;
|
|
|
|
max_into --;
|
|
if (max_into <= 0) break;
|
|
}
|
|
}
|
|
|
|
static inline void _flush_rx(void)
|
|
{
|
|
// reset timeout timer (Systick)
|
|
SysTick->VAL = 0;
|
|
|
|
while (!(MY_UART->ISR & UART_FLAG_TC)) {
|
|
// wait for last bit(byte) to be serialized and sent
|
|
|
|
if (SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) {
|
|
// full 1ms has passed -- timeout.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We actualy need this delay here!
|
|
for (int i=0; i<48; i++) {
|
|
__NOP();
|
|
}
|
|
|
|
// clear junk in rx buffer
|
|
MY_UART->RQR = USART_RQR_RXFRQ;
|
|
|
|
// clear overrun error
|
|
// clear rx timeout flag
|
|
// clear framing error
|
|
MY_UART->ICR = USART_ICR_ORECF | USART_ICR_RTOCF | USART_ICR_FECF;
|
|
}
|
|
|
|
// Read upto N bytes of response. Suspress echo of 0x88 and
|
|
// return actual number of (deserialized) bytes received.
|
|
// We ignore extra bytes not expected, and always read until a timeout.
|
|
// Cmds to chip can be up to 155 bytes, but not clear what max len for responses.
|
|
//
|
|
static int se_read_response(uint8_t *buf, int max_len)
|
|
{
|
|
int max_expect = (max_len+1) * 8;
|
|
uint8_t raw[max_expect];
|
|
|
|
// tell chip to write stuff to bus
|
|
_send_bits(IOFLAG_TX);
|
|
|
|
// kill first byte which we expect to be IOFLAG_TX echo (0x88)
|
|
_flush_rx();
|
|
|
|
// It takes between 64 and 131us (tTURNAROUND) for the chip to recover
|
|
// and start sending bits to us. We're blocked on reading
|
|
// them anyway, so no need to delay. Also a danger of overruns here.
|
|
|
|
int actual = 0;
|
|
for (uint8_t *p = raw; ; actual++) {
|
|
int ch = _read_byte();
|
|
if (ch < 0) {
|
|
break;
|
|
}
|
|
|
|
if (actual < max_expect) {
|
|
*(p++) = ch;
|
|
}
|
|
}
|
|
|
|
// Sometimes our framing is not perfect.
|
|
// We might get a spurious bit at the leading edge (perhaps an echo
|
|
// of part of the 0x88??) or junk at the end.
|
|
actual &= ~7;
|
|
deserialize(raw, actual, buf, max_len);
|
|
|
|
return actual / 8;
|
|
}
|
|
|
|
static bool check_crc(const uint8_t *data, uint8_t length)
|
|
{
|
|
uint8_t obs[2] = { 0, 0 };
|
|
|
|
if (data[0] != length) {
|
|
// length is wrong
|
|
return false;
|
|
}
|
|
|
|
se_crc16_chain(length-2, data, obs);
|
|
|
|
return (obs[0] == data[length-2] && obs[1] == data[length-1]);
|
|
}
|
|
|
|
void se_write(seopcode_t opcode, uint8_t p1, uint16_t p2, uint8_t *data, uint8_t data_len)
|
|
{
|
|
// all commands will have this fixed header, which includes just one layer of framing
|
|
struct {
|
|
uint8_t ioflag;
|
|
uint8_t framed_len;
|
|
uint8_t op;
|
|
uint8_t p1;
|
|
uint8_t p2_lsb;
|
|
uint8_t p2_msb;
|
|
} known = {
|
|
.ioflag = IOFLAG_CMD,
|
|
.framed_len = (data_len + 7), // 7 = (1 len) + (4 bytes of msg) + (2 crc)
|
|
.op = opcode,
|
|
.p1 = p1,
|
|
.p2_lsb = p2 & 0xff,
|
|
.p2_msb = (p2 >> 8) & 0xff,
|
|
};
|
|
STATS(last_op = opcode);
|
|
STATS(last_p1 = p1);
|
|
STATS(last_p2 = p2);
|
|
|
|
current_opcode = opcode;
|
|
|
|
/*
|
|
* Wake up the chip...
|
|
* If it was in sleep mode it starts the watchdog.
|
|
* If it was in idle mode it resumes the watchdog.
|
|
*/
|
|
se_wake();
|
|
|
|
_send_serialized((const uint8_t *)&known, sizeof(known));
|
|
|
|
// CRC will start from frame_len onwards
|
|
uint8_t crc[2] = {0, 0};
|
|
se_crc16_chain(sizeof(known)-1, &known.framed_len, crc);
|
|
|
|
// insert a variable-length body area (sometimes)
|
|
if (data_len) {
|
|
_send_serialized(data, data_len);
|
|
|
|
se_crc16_chain(data_len, data, crc);
|
|
}
|
|
|
|
// send final CRC bytes
|
|
_send_serialized(crc, 2);
|
|
}
|
|
|
|
int se_read(uint8_t *data, uint8_t len)
|
|
{
|
|
uint8_t tmp[1 + len + 2]; /* msg length + data length + checksum length */
|
|
int retry;
|
|
|
|
for (retry=100; retry >= 0; retry--) {
|
|
int actual;
|
|
|
|
actual = se_read_response(tmp, len+3);
|
|
if (actual < 4) {
|
|
if (actual == 0) {
|
|
/* No data...probably still processing the command */
|
|
ERR("not ready2");
|
|
not_ready_n++;
|
|
} else {
|
|
// a weird short-read? probably fatal, but retry
|
|
ERR("too short");
|
|
short_error++;
|
|
}
|
|
goto try_again;
|
|
}
|
|
|
|
/*
|
|
* The OP_Info response does not follow the normal response
|
|
* format that includes a length and checksum. So we'll bypass
|
|
* the length and checksum processing for the info command.
|
|
*/
|
|
if (current_opcode != OP_Info)
|
|
{
|
|
uint8_t resp_len = tmp[0];
|
|
if (resp_len != (len + 3)) {
|
|
len_error++;
|
|
if (resp_len == 4) {
|
|
/* Error code returned */
|
|
ERRV(tmp[1], "se errcode");
|
|
len_error_two++;
|
|
|
|
if (tmp[1] == 0xEE)
|
|
wdgtimeout++;
|
|
|
|
last_error = tmp[1];
|
|
goto out;
|
|
}
|
|
ERRV(tmp[0], "wr len");
|
|
goto try_again;
|
|
}
|
|
|
|
if (!check_crc(tmp, actual)) {
|
|
ERR("bad crc");
|
|
crc_errors++;
|
|
last_error = SE_COMMS_ERROR;
|
|
goto try_again;
|
|
}
|
|
}
|
|
|
|
memcpy(data, tmp + 1, actual - 3);
|
|
|
|
/*
|
|
* Pause the watchdog in case there's more to do
|
|
* NOTE: Requires a wake commmand to resume!
|
|
*/
|
|
_send_bits(IOFLAG_IDLE);
|
|
|
|
return 0;
|
|
|
|
try_again:
|
|
ln_retry++;
|
|
}
|
|
retry_out++;
|
|
last_error = SE_EX_RETRY_OUT;
|
|
|
|
out:
|
|
se_show_error();
|
|
return -1;
|
|
}
|
|
|
|
int se_read1(void)
|
|
{
|
|
int rc;
|
|
uint8_t data;
|
|
|
|
rc = se_read(&data, 1);
|
|
if (rc < 0)
|
|
return -1;
|
|
return data;
|
|
}
|
|
|
|
int se_read_data_slot(int slot_num, uint8_t *data, int len)
|
|
{
|
|
int rc;
|
|
int rval = 0;
|
|
#ifdef FIXME
|
|
ASSERT((len == 4) || (len == 32) || (len == 72));
|
|
#endif
|
|
// zone => data
|
|
// only reading first block of 32 bytes. ignore the rest
|
|
se_write(OP_Read, (len == 4 ? 0x00 : 0x80) | 2, (slot_num<<3), NULL, 0);
|
|
rc = se_read(data, (len == 4) ? 4 : 32);
|
|
if (rc < 0)
|
|
{
|
|
rval = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (len == 72) {
|
|
// read second block
|
|
se_write(OP_Read, 0x82, (1<<8) | (slot_num<<3), NULL, 0);
|
|
rc = se_read(data+32, 32);
|
|
if (rc < 0)
|
|
{
|
|
rval = -1;
|
|
goto out;
|
|
}
|
|
|
|
// read third block, but only using part of it
|
|
uint8_t tmp[32];
|
|
se_write(OP_Read, 0x82, (2<<8) | (slot_num<<3), NULL, 0);
|
|
rc = se_read(tmp, 32);
|
|
if (rc < 0)
|
|
{
|
|
rval = -1;
|
|
goto out;
|
|
}
|
|
|
|
memcpy(data+64, tmp, 72-64);
|
|
}
|
|
|
|
out:
|
|
se_sleep();
|
|
return rval;
|
|
}
|
|
|
|
void se_crc16_chain(uint8_t length, const uint8_t *data, uint8_t crc[2])
|
|
{
|
|
uint8_t counter;
|
|
uint16_t crc_register = 0;
|
|
uint16_t polynom = 0x8005;
|
|
uint8_t shift_register;
|
|
uint8_t data_bit, crc_bit;
|
|
|
|
crc_register = (((uint16_t) crc[0]) & 0x00FF) | (((uint16_t) crc[1]) << 8);
|
|
|
|
for (counter = 0; counter < length; counter++) {
|
|
for (shift_register = 0x01; shift_register > 0x00; shift_register <<= 1) {
|
|
data_bit = (data[counter] & shift_register) ? 1 : 0;
|
|
crc_bit = crc_register >> 15;
|
|
|
|
// Shift CRC to the left by 1.
|
|
crc_register <<= 1;
|
|
|
|
if ((data_bit ^ crc_bit) != 0)
|
|
crc_register ^= polynom;
|
|
}
|
|
}
|
|
|
|
crc[0] = (uint8_t) (crc_register & 0x00FF);
|
|
crc[1] = (uint8_t) (crc_register >> 8);
|
|
}
|
|
|
|
void se_sleep(void)
|
|
{
|
|
_send_bits(IOFLAG_SLEEP);
|
|
}
|
|
|
|
void se_idle(void)
|
|
{
|
|
_send_bits(IOFLAG_IDLE);
|
|
}
|
|
|
|
int se_wake(void)
|
|
{
|
|
// send zero (all low), delay 2.5ms
|
|
_send_byte(0x00);
|
|
|
|
#ifdef PASSPORT_BOOTLOADER
|
|
delay_us(2500);
|
|
#else
|
|
delay_us(1250);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
void se_keep_alive(void)
|
|
{
|
|
se_idle();
|
|
}
|
|
|
|
void se_reset_chip(void)
|
|
{
|
|
_send_bits(IOFLAG_SLEEP);
|
|
}
|
|
|
|
int se_config_read(uint8_t *config)
|
|
{
|
|
int rc;
|
|
int rval = 0;
|
|
|
|
for (int blk=0; blk<4; blk++) {
|
|
/* Read 32 bytes (aligned) from config "zone" */
|
|
se_write(OP_Read, 0x80, blk<<3, NULL, 0);
|
|
|
|
rc = se_read(&config[32 * blk], 32);
|
|
if (rc < 0)
|
|
{
|
|
rval = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
_send_bits(IOFLAG_SLEEP);
|
|
return rval;
|
|
}
|
|
|
|
// Load Tempkey with a nonce value that we both know, but
|
|
// is random and we both know is random! Tricky!
|
|
//
|
|
int se_pick_nonce(
|
|
uint8_t *num_in,
|
|
uint8_t *tempkey
|
|
)
|
|
{
|
|
int rc;
|
|
|
|
// We provide some 20 bytes of randomness to chip
|
|
// The chip must provide 32-bytes of random-ness,
|
|
// so no choice in args to OP.Nonce here (due to ReqRandom).
|
|
se_write(OP_Nonce, 0, 0, num_in, 20);
|
|
|
|
// Nonce command returns the RNG result, but not contents of TempKey
|
|
uint8_t randout[32];
|
|
rc = se_read(randout, 32);
|
|
se_sleep();
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
// Hash stuff appropriately to get same number as chip did.
|
|
// TempKey on the chip will be set to the output of SHA256 over
|
|
// a message composed of my challenge, the RNG and 3 bytes of constants:
|
|
//
|
|
// return sha256(rndout + num_in + b'\x16\0\0').digest()
|
|
//
|
|
SHA256_CTX ctx;
|
|
|
|
sha256_init(&ctx);
|
|
sha256_update(&ctx, randout, 32);
|
|
sha256_update(&ctx, num_in, 20);
|
|
const uint8_t fixed[3] = { 0x16, 0, 0 };
|
|
sha256_update(&ctx, fixed, 3);
|
|
sha256_final(&ctx, tempkey);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int se_gendig_slot(
|
|
int slot_num,
|
|
const uint8_t *slot_contents,
|
|
uint8_t *digest
|
|
)
|
|
{
|
|
// Construct a digest on the device (and here) that depends on the secret
|
|
// contents of a specific slot.
|
|
uint8_t num_in[20], tempkey[32];
|
|
int rc;
|
|
|
|
rng_buffer(num_in, sizeof(num_in));
|
|
rc = se_pick_nonce(num_in, tempkey);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
//using Zone=2="Data" => "KeyID specifies a slot in the Data zone"
|
|
se_write(OP_GenDig, 0x2, slot_num, NULL, 0);
|
|
|
|
rc = se_read1();
|
|
se_sleep();
|
|
if (rc != 0)
|
|
return -1;
|
|
|
|
// we now have to match the digesting (hashing) that has happened on
|
|
// the chip. No feedback at this point if it's right tho.
|
|
//
|
|
// msg = hkey + b'\x15\x02' + ustruct.pack("<H", slot_num)
|
|
// msg += b'\xee\x01\x23' + (b'\0'*25) + challenge
|
|
// assert len(msg) == 32+1+1+2+1+2+25+32
|
|
//
|
|
SHA256_CTX ctx;
|
|
sha256_init(&ctx);
|
|
|
|
uint8_t args[7] = { OP_GenDig, 2, slot_num, 0, 0xEE, 0x01, 0x23 };
|
|
uint8_t zeros[25] = { 0 };
|
|
|
|
sha256_update(&ctx, slot_contents, 32);
|
|
sha256_update(&ctx, args, sizeof(args));
|
|
sha256_update(&ctx, zeros, sizeof(zeros));
|
|
sha256_update(&ctx, tempkey, 32);
|
|
|
|
sha256_final(&ctx, digest);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Check that TempKey is holding what we think it does. Uses the MAC
|
|
// command over contents of Tempkey and our shared secret.
|
|
//
|
|
bool se_is_correct_tempkey(
|
|
const uint8_t *expected_tempkey
|
|
)
|
|
{
|
|
const uint8_t mode = (1<<6) // include full serial number
|
|
| (0<<2) // TempKey.SourceFlag == 0 == 'rand'
|
|
| (0<<1) // first 32 bytes are the shared secret
|
|
| (1<<0); // second 32 bytes are tempkey
|
|
uint8_t resp[32];
|
|
int rc;
|
|
|
|
se_write(OP_MAC, mode, KEYNUM_pairing_secret, NULL, 0);
|
|
rc = se_read(resp, 32);
|
|
se_sleep();
|
|
if (rc < 0)
|
|
return false;
|
|
|
|
// Duplicate the hash process, and then compare.
|
|
SHA256_CTX ctx;
|
|
|
|
sha256_init(&ctx);
|
|
sha256_update(&ctx, rom_secrets->pairing_secret, 32);
|
|
sha256_update(&ctx, expected_tempkey, 32);
|
|
|
|
const uint8_t fixed[16] = { OP_MAC, mode, KEYNUM_pairing_secret, 0x0,
|
|
0,0,0,0, 0,0,0,0, // eight zeros
|
|
0,0,0, // three zeros
|
|
0xEE };
|
|
sha256_update(&ctx, fixed, sizeof(fixed));
|
|
sha256_update(&ctx, ((const uint8_t *)rom_secrets->se_serial_number)+4, 4);
|
|
sha256_update(&ctx, ((const uint8_t *)rom_secrets->se_serial_number)+0, 4);
|
|
|
|
uint8_t actual[32];
|
|
sha256_final(&ctx, actual);
|
|
|
|
return check_equal(actual, resp, 32);
|
|
}
|
|
|
|
// Do a dance that unlocks access to the private key for signing.
|
|
// Purpose is to show we are a pair of chips that belong together.
|
|
//
|
|
int se_pair_unlock()
|
|
{
|
|
int rc;
|
|
int attempts = 3;
|
|
for (int i = 0; i < attempts; i++) {
|
|
rc = se_checkmac(KEYNUM_pairing_secret, rom_secrets->pairing_secret);
|
|
if (rc == 0)
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// CAUTION: The result from this function could be modified by an
|
|
// active attacker on the bus because the one-byte response from the chip
|
|
// is easily replaced. This command is useful for us to authorize actions
|
|
// inside the 508a/608a, like use of a specific key, but not for us to
|
|
// authenticate the 508a/608a or its contents/state.
|
|
//
|
|
int se_checkmac(
|
|
uint8_t keynum,
|
|
const uint8_t *secret
|
|
)
|
|
{
|
|
int rc;
|
|
|
|
// Since this is part of the hash, we want random bytes
|
|
// for our "other data". Also a number for "numin" of nonce
|
|
uint8_t od[32], numin[20];
|
|
|
|
rng_buffer(od, sizeof(od));
|
|
rng_buffer(numin, sizeof(numin));
|
|
|
|
// - load tempkey with a known nonce value
|
|
uint8_t zeros[8] = {0};
|
|
uint8_t tempkey[32];
|
|
rc = se_pick_nonce(numin, tempkey);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
// - hash nonce and lots of other bits together
|
|
SHA256_CTX ctx;
|
|
sha256_init(&ctx);
|
|
|
|
// shared secret is 32 bytes from flash
|
|
sha256_update(&ctx, secret, 32);
|
|
sha256_update(&ctx, tempkey, 32);
|
|
sha256_update(&ctx, &od[0], 4);
|
|
sha256_update(&ctx, zeros, 8);
|
|
sha256_update(&ctx, &od[4], 3);
|
|
|
|
uint8_t sn8 = 0xEE;
|
|
sha256_update(&ctx, &sn8, 1);
|
|
sha256_update(&ctx, &od[7], 4);
|
|
|
|
uint8_t sn01[2] = { 0x01, 0x23 };
|
|
sha256_update(&ctx, sn01, 2);
|
|
sha256_update(&ctx, &od[11], 2);
|
|
|
|
// format the request body
|
|
struct {
|
|
uint8_t ch3[32]; // not actually used, but has to be there
|
|
uint8_t resp[32];
|
|
uint8_t od[13];
|
|
} req;
|
|
|
|
// content doesn't matter, but nice and visible:
|
|
memcpy(req.ch3, copyright_msg, 32);
|
|
|
|
sha256_final(&ctx, req.resp);
|
|
memcpy(req.od, od, 13);
|
|
|
|
// Give our answer to the chip. The 0x01 means that TempKey holds
|
|
// the second 32 byte value. First 32 byte value is in key slot 1 (pairing secret).
|
|
se_write(OP_CheckMac, 0x01, keynum, (uint8_t *)&req, sizeof(req));
|
|
rc = se_read1();
|
|
se_sleep();
|
|
if (rc != 0) {
|
|
// did it work?! No.
|
|
if (rc == SE_CHECKMAC_FAIL)
|
|
ERR("CM fail"); // typical case: our hashs don't match
|
|
else
|
|
ERRV(rc, "CheckMac");
|
|
return -2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Check the chip produces a hash over various things the same way we would
|
|
// meaning that we both know the shared secret and the state of stuff in
|
|
// the 508a is what we expect.
|
|
//
|
|
int se_checkmac_hard(
|
|
uint8_t keynum,
|
|
const uint8_t *secret
|
|
)
|
|
{
|
|
int rc;
|
|
uint8_t digest[32];
|
|
|
|
rc = se_gendig_slot(keynum, secret, digest);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
// NOTE: we use this sometimes when we know the value is wrong, like
|
|
// checking for blank pin codes... so not a huge error/security issue
|
|
// if wrong here.
|
|
if (!se_is_correct_tempkey(digest))
|
|
return -2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int se_encrypted_read32(
|
|
int data_slot,
|
|
int blk,
|
|
int read_kn,
|
|
const uint8_t *read_key,
|
|
uint8_t *data
|
|
)
|
|
{
|
|
int rc;
|
|
uint8_t digest[32];
|
|
|
|
rc = se_pair_unlock();
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
rc = se_gendig_slot(read_kn, read_key, digest);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
// read nth 32-byte "block"
|
|
se_write(OP_Read, 0x82, (blk << 8) | (data_slot<<3), NULL, 0);
|
|
rc = se_read(data, 32);
|
|
se_sleep();
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
xor_mixin(data, digest, 32);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int se_encrypted_read(
|
|
int data_slot,
|
|
int read_kn,
|
|
const uint8_t *read_key,
|
|
uint8_t *data,
|
|
int len
|
|
)
|
|
{
|
|
int rc;
|
|
#ifdef FIXME
|
|
// not clear if chip supports 4-byte encrypted reads
|
|
ASSERT((len == 32) || (len == 72));
|
|
#endif
|
|
rc = se_encrypted_read32(data_slot, 0, read_kn, read_key, data);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
if (len == 32)
|
|
return 0;
|
|
|
|
rc = se_encrypted_read32(data_slot, 1, read_kn, read_key, data+32);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
uint8_t tmp[32];
|
|
rc = se_encrypted_read32(data_slot, 2, read_kn, read_key, tmp);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
memcpy(data+64, tmp, 72-64);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int se_encrypted_write32(
|
|
int data_slot,
|
|
int blk,
|
|
int write_kn,
|
|
const uint8_t *write_key,
|
|
const uint8_t *data)
|
|
{
|
|
int rc;
|
|
uint8_t digest[32];
|
|
|
|
rc = se_pair_unlock();
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
// generate a hash over shared secret and rng
|
|
rc = se_gendig_slot(write_kn, write_key, digest);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
// encrypt the data to be written, and append an authenticating MAC
|
|
uint8_t body[32 + 32];
|
|
|
|
for (int i=0; i<32; i++) {
|
|
body[i] = data[i] ^ digest[i];
|
|
}
|
|
|
|
// make auth-mac to go with
|
|
// SHA-256(TempKey, Opcode, Param1, Param2, SN<8>, SN<0:1>, <25 bytes of zeros>, PlainTextData)
|
|
// msg = (dig
|
|
// + ustruct.pack('<bbH', OP.Write, args['p1'], args['p2'])
|
|
// + b'\xee\x01\x23'
|
|
// + (b'\0'*25)
|
|
// + new_value)
|
|
// assert len(msg) == 32+1+1+2+1+2+25+32
|
|
//
|
|
SHA256_CTX ctx;
|
|
sha256_init(&ctx);
|
|
|
|
uint8_t p1 = 0x80|2; // 32 bytes into a data slot
|
|
uint8_t p2_lsb = (data_slot << 3);
|
|
uint8_t p2_msb = blk;
|
|
|
|
uint8_t args[7] = { OP_Write, p1, p2_lsb, p2_msb, 0xEE, 0x01, 0x23 };
|
|
uint8_t zeros[25] = { 0 };
|
|
|
|
sha256_update(&ctx, digest, 32);
|
|
sha256_update(&ctx, args, sizeof(args));
|
|
sha256_update(&ctx, zeros, sizeof(zeros));
|
|
sha256_update(&ctx, data, 32);
|
|
|
|
sha256_final(&ctx, &body[32]);
|
|
|
|
se_write(OP_Write, p1, (p2_msb << 8) | p2_lsb, body, sizeof(body));
|
|
rc = se_read1();
|
|
se_sleep();
|
|
if (rc != 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int se_encrypted_write(
|
|
int data_slot,
|
|
int write_kn,
|
|
const uint8_t *write_key,
|
|
const uint8_t *data,
|
|
int len
|
|
)
|
|
{
|
|
for (int blk=0; blk<3 && len>0; blk++, len-=32) {
|
|
int here = MIN(32, len);
|
|
|
|
// be nice and don't read past end of input buffer
|
|
uint8_t tmp[32] = { 0 };
|
|
memcpy(tmp, data+(32*blk), here);
|
|
|
|
int rv = se_encrypted_write32(data_slot, blk, write_kn, write_key, tmp);
|
|
if (rv < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void se_dump_stats(void)
|
|
{
|
|
uint32_t tmp;
|
|
|
|
tmp = crc_errors;
|
|
tmp += not_ready_n;
|
|
tmp += short_error;
|
|
tmp += len_error;
|
|
tmp += len_error_two;
|
|
tmp += ln_retry;
|
|
tmp += retry_out;
|
|
tmp += rxne;
|
|
tmp += rtof;
|
|
tmp += notrxne;
|
|
if (tmp > 0)
|
|
tmp = 0;
|
|
}
|
|
|
|
#define SE_BAUDRATE 230400U
|
|
|
|
void se_setup(void)
|
|
{
|
|
/*
|
|
* MY_UART is pointer to USART_Typedef struct
|
|
*/
|
|
GPIO_InitTypeDef gpiosetup = {0};
|
|
uint32_t uartdiv;
|
|
uint32_t uart_clock_prescaler = 0;
|
|
|
|
/* Calculate the baud rate divisor */
|
|
uartdiv = (uint16_t)(UART_DIV_SAMPLING16(HAL_RCC_GetPCLK1Freq(), SE_BAUDRATE, uart_clock_prescaler));
|
|
|
|
#ifdef DEV_STATS
|
|
memset(&stats, 0, sizeof(stats));
|
|
#endif
|
|
// configure pin A0 to be AF8_UART4, PULL_NONE
|
|
// configure pin D15 to be INPUT, PULL_NONE, OD for output
|
|
gpiosetup.Pin = GPIO_PIN_15;
|
|
gpiosetup.Mode = GPIO_MODE_INPUT;
|
|
gpiosetup.Pull = GPIO_NOPULL;
|
|
gpiosetup.Speed = GPIO_SPEED_FREQ_MEDIUM;
|
|
HAL_GPIO_Init(GPIOD, &gpiosetup);
|
|
|
|
gpiosetup.Pin = GPIO_PIN_0;
|
|
gpiosetup.Mode = GPIO_MODE_AF_OD;
|
|
gpiosetup.Pull = GPIO_NOPULL;
|
|
gpiosetup.Speed = GPIO_SPEED_FREQ_MEDIUM;
|
|
gpiosetup.Alternate = GPIO_AF8_UART4;
|
|
HAL_GPIO_Init(GPIOA, &gpiosetup);
|
|
|
|
// enable clock to that part of chip
|
|
__HAL_RCC_UART4_CLK_ENABLE();
|
|
|
|
// copy config values from a running system, setup by mpy code
|
|
// - except disable all interrupts
|
|
// - mpy code will have to clean this up, see ...reinit() member func
|
|
//
|
|
// For max clock error insensitivity:
|
|
// OVER8==0, ONEBIT=1
|
|
|
|
// disable UART so some other bits can be set (only while disabled)
|
|
|
|
MY_UART->CR1 = 0;
|
|
MY_UART->CR1 = 0x1000002d & ~(0
|
|
| USART_CR1_PEIE
|
|
| USART_CR1_TXEIE
|
|
| USART_CR1_TCIE
|
|
| USART_CR1_RXNEIE
|
|
| USART_CR1_IDLEIE
|
|
| USART_CR1_OVER8
|
|
| USART_CR1_UE);
|
|
|
|
MY_UART->RTOR = 24; // timeout in bit periods: 3 chars or so
|
|
MY_UART->CR2 = USART_CR2_RTOEN; // rx timeout enable
|
|
MY_UART->CR3 = USART_CR3_HDSEL | USART_CR3_ONEBIT;
|
|
MY_UART->BRR = uartdiv; // 0x00000052; // Value from HAL calcualtion above for 230400 bps
|
|
|
|
// clear rx timeout flag
|
|
MY_UART->ICR = USART_ICR_RTOCF;
|
|
|
|
// finally enable UART
|
|
MY_UART->CR1 |= USART_CR1_UE;
|
|
}
|
|
|
|
// Just read a one-way counter.
|
|
//
|
|
int se_get_counter(uint32_t *result, uint8_t counter_number)
|
|
{
|
|
int rc;
|
|
|
|
se_write(OP_Counter, 0x0, counter_number, NULL, 0);
|
|
rc = se_read((uint8_t *)result, 4);
|
|
se_sleep();
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
// IMPORTANT: Always verify the counter's value because otherwise
|
|
// nothing prevents an active MitM changing the value that we think
|
|
// we just read.
|
|
uint8_t digest[32];
|
|
rc = se_gendig_counter(counter_number, *result, digest);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
if (!se_is_correct_tempkey(digest))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Add-to and return a one-way counter's value. Have to go up in
|
|
// single-unit steps, but can we loop.
|
|
//
|
|
int se_add_counter(uint32_t *result, uint8_t counter_number, int incr)
|
|
{
|
|
int rc;
|
|
int rval = 0;
|
|
|
|
for (int i = 0; i < incr; i++) {
|
|
se_write(OP_Counter, 0x1, counter_number, NULL, 0);
|
|
rc = se_read((uint8_t *)result, 4);
|
|
if (rc < 0)
|
|
{
|
|
rval = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
// IMPORTANT: Always verify the counter's value because otherwise
|
|
// nothing prevents an active MitM changing the value that we think
|
|
// we just read. They could also stop us from incrementing the counter.
|
|
|
|
uint8_t digest[32];
|
|
rc = se_gendig_counter(counter_number, *result, digest);
|
|
if (rc < 0)
|
|
{
|
|
rval = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!se_is_correct_tempkey(digest))
|
|
rval = -1;
|
|
|
|
out:
|
|
se_sleep();
|
|
return rval;
|
|
}
|
|
|
|
// Construct a digest over one of the two counters. Track what we think
|
|
// the digest should be, and ask the chip to do the same. Verify we match
|
|
// using MAC command (done elsewhere).
|
|
//
|
|
int se_gendig_counter(int counter_num, const uint32_t expected_value, uint8_t digest[32])
|
|
{
|
|
int rc;
|
|
uint8_t num_in[20], tempkey[32];
|
|
|
|
rng_buffer(num_in, sizeof(num_in));
|
|
|
|
rc = se_pick_nonce(num_in, tempkey);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
//using Zone=4="Counter" => "KeyID specifies the monotonic counter ID"
|
|
se_write(OP_GenDig, 0x4, counter_num, NULL, 0);
|
|
rc = se_read1();
|
|
se_sleep();
|
|
if (rc != 0)
|
|
return -1;
|
|
|
|
// we now have to match the digesting (hashing) that has happened on
|
|
// the chip. No feedback at this point if it's right tho.
|
|
//
|
|
// msg = hkey + b'\x15\x02' + ustruct.pack("<H", slot_num)
|
|
// msg += b'\xee\x01\x23' + (b'\0'*25) + challenge
|
|
// assert len(msg) == 32+1+1+2+1+2+25+32
|
|
//
|
|
SHA256_CTX ctx;
|
|
sha256_init(&ctx);
|
|
|
|
uint8_t zeros[32] = { 0 };
|
|
uint8_t args[8] = { OP_GenDig, 0x4, counter_num, 0, 0xEE, 0x01, 0x23, 0x0 };
|
|
|
|
sha256_update(&ctx, zeros, 32);
|
|
sha256_update(&ctx, args, sizeof(args));
|
|
sha256_update(&ctx, (const uint8_t *)&expected_value, 4);
|
|
sha256_update(&ctx, zeros, 20);
|
|
sha256_update(&ctx, tempkey, 32);
|
|
sha256_final(&ctx, digest);
|
|
|
|
return 0;
|
|
}
|
|
|