2071 lines
49 KiB
C
2071 lines
49 KiB
C
/*
|
|
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
|
*/
|
|
#include "basics.h"
|
|
#include "ae.h"
|
|
#include "rng.h"
|
|
#include "delay.h"
|
|
#include "sha256.h"
|
|
#include "constant_time.h"
|
|
#include "gpio.h"
|
|
#include "storage.h"
|
|
#include "stm32l4xx_hal.h"
|
|
#include "ae_config.h"
|
|
#include "oled.h"
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
// Selectable debug level; keep them as comments regardless
|
|
#if 0
|
|
// break on any error: not helpful since some are normal
|
|
# define ERR(msg) BREAKPOINT;
|
|
# define ERRV(val, msg) BREAKPOINT;
|
|
#else
|
|
# define ERR(msg)
|
|
# define ERRV(val, msg)
|
|
#endif
|
|
|
|
// Must be exactly 32 chars:
|
|
static const char *copyright_msg = "Copyright 2018- by Coinkite Inc.";
|
|
|
|
// "one wire" is on PA0 aka. UART4
|
|
#define MY_UART UART4
|
|
|
|
// keep this in place.
|
|
#define RET_IF_BAD(rv) do { if(rv) return rv; } while(0)
|
|
|
|
#define AE_CHIP_IS_SETUP 0x35d25d63
|
|
static uint32_t ae_chip_is_setup;
|
|
|
|
// Forward refs...
|
|
static void crc16_chain(uint8_t length, const uint8_t *data, uint8_t crc[2]);
|
|
static void ae_delay(aeopcode_t opcode);
|
|
static void ae_wake(void);
|
|
|
|
// Enable some powerful debug features.
|
|
#if 0
|
|
#define DEV_STATS
|
|
|
|
static struct {
|
|
int crc_error;
|
|
int len_error;
|
|
int crc_len_error;
|
|
int short_error;
|
|
int not_ready, not_ready_n;
|
|
int l1_retry;
|
|
int ln_retry;
|
|
int extra_bits;
|
|
aeopcode_t last_op;
|
|
uint8_t last_resp1, last_p1;
|
|
uint16_t last_p2;
|
|
uint8_t last_n_data[32];
|
|
uint8_t last_n_len;
|
|
uint16_t was_locked;
|
|
} stats;
|
|
#define STATS(x) stats. x;
|
|
#else
|
|
#define STATS(x)
|
|
#endif
|
|
|
|
// 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;
|
|
|
|
// _send_byte()
|
|
//
|
|
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;
|
|
}
|
|
|
|
// _send_bits()
|
|
//
|
|
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);
|
|
}
|
|
}
|
|
|
|
// _send_serialized()
|
|
//
|
|
static void
|
|
_send_serialized(const uint8_t *buf, int len)
|
|
{
|
|
for(int i=0; i<len; i++) {
|
|
_send_bits(buf[i]);
|
|
}
|
|
}
|
|
|
|
// _read_byte()
|
|
//
|
|
// 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)
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(MY_UART->ISR & UART_FLAG_RXNE) {
|
|
return MY_UART->RDR & 0x7f;
|
|
}
|
|
if(MY_UART->ISR & UART_FLAG_RTOF) {
|
|
// "fast" timeout reached, clear flag
|
|
MY_UART->ICR = USART_ICR_RTOCF;
|
|
return -1;
|
|
}
|
|
INCONSISTENT("rxf");
|
|
return -1;
|
|
}
|
|
|
|
// deserialize()
|
|
//
|
|
// 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(from[i] == BIT1) {
|
|
rv |= mask;
|
|
}
|
|
}
|
|
|
|
*(into++) = rv;
|
|
from += 8;
|
|
from_len -= 8;
|
|
|
|
max_into --;
|
|
if(max_into <= 0) break;
|
|
}
|
|
}
|
|
|
|
// _flush_rx()
|
|
//
|
|
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!
|
|
__NOP();
|
|
__NOP();
|
|
__NOP();
|
|
__NOP();
|
|
__NOP();
|
|
__NOP();
|
|
__NOP();
|
|
__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;
|
|
}
|
|
|
|
// ae_read_response()
|
|
//
|
|
// 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
|
|
ae_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;
|
|
}
|
|
|
|
// ae_wake()
|
|
//
|
|
// Do not call this causually: it may cause next read to return 0x11 (After Wake,
|
|
// Prior to First Command) as an error to any on-going/attempted operation.
|
|
//
|
|
//
|
|
static void
|
|
ae_wake(void)
|
|
{
|
|
// send zero (all low), delay 2.5ms
|
|
_send_byte(0x00);
|
|
|
|
delay_us(2500); // measured: ~2.9ms
|
|
|
|
_flush_rx();
|
|
}
|
|
|
|
// ae_send_sleep()
|
|
//
|
|
static void
|
|
ae_send_sleep(void)
|
|
{
|
|
// "The ATECC508A goes into the low power sleep mode and ignores all
|
|
// subsequent I/O transitions until the next wake flag. The entire volatile
|
|
// state of the device is reset"
|
|
ae_wake();
|
|
|
|
_send_bits(IOFLAG_SLEEP);
|
|
}
|
|
|
|
// ae_send_idle()
|
|
//
|
|
static void
|
|
ae_send_idle(void)
|
|
{
|
|
// "The ATECC508A goes into the idle mode and ignores all subsequent
|
|
// I/O transitions until the next wake flag. The contents of TempKey
|
|
// and RNG Seed registers are retained."
|
|
ae_wake();
|
|
|
|
_send_bits(IOFLAG_IDLE);
|
|
}
|
|
|
|
// ae_reset_chip()
|
|
//
|
|
void
|
|
ae_reset_chip(void)
|
|
{
|
|
if(ae_chip_is_setup == AE_CHIP_IS_SETUP) {
|
|
// "The ATECC508A goes into the low power sleep mode and ignores all
|
|
// subsequent I/O transitions until the next wake flag. The entire volatile
|
|
// state of the device is reset"
|
|
_send_bits(IOFLAG_SLEEP);
|
|
} else {
|
|
// we may not have a working UART, and we probably didn't
|
|
// talk to the chip, so skip it.
|
|
}
|
|
}
|
|
|
|
|
|
// ae_setup()
|
|
//
|
|
// Configure pins. Do not attempt to talk to chip.
|
|
//
|
|
void
|
|
ae_setup(void)
|
|
{
|
|
#ifdef DEV_STATS
|
|
memset(&stats, 0, sizeof(stats));
|
|
#endif
|
|
|
|
// 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 = 0x0000015b; // 230400 bps
|
|
|
|
// clear rx timeout flag
|
|
MY_UART->ICR = USART_ICR_RTOCF;
|
|
|
|
// finally enable UART
|
|
MY_UART->CR1 |= USART_CR1_UE;
|
|
|
|
// configure pin A0 to be AF8_UART4, PULL_NONE
|
|
gpio_setup();
|
|
|
|
// mark it as ready
|
|
ae_chip_is_setup = AE_CHIP_IS_SETUP;
|
|
}
|
|
|
|
// ae_probe()
|
|
//
|
|
const char *
|
|
ae_probe(void)
|
|
{
|
|
// Make it sleep / wake it up.
|
|
ae_send_sleep();
|
|
|
|
// Wake it again (to reset state)
|
|
ae_wake();
|
|
|
|
// do a real read w/ CRC
|
|
// with no command happening, expect 0x11: "After Wake, prior to first command"
|
|
ae_read1();
|
|
|
|
uint8_t chk = ae_read1();
|
|
if(chk != AE_AFTER_WAKE) return "wk fl";
|
|
|
|
// read the serial number one time
|
|
uint8_t serial[6];
|
|
if(ae_get_serial(serial)) return "no ser";
|
|
|
|
// put into a low-power mode, might be a bit before we come back
|
|
ae_send_sleep();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ae_keep_alive()
|
|
//
|
|
void
|
|
ae_keep_alive(void)
|
|
{
|
|
// To reset the watchdog, (1) put it into idle mode, then (2) wake it.
|
|
ae_send_idle();
|
|
|
|
// no need to wake: next transaction will do that
|
|
//ae_wake();
|
|
}
|
|
|
|
// Originally from Libraries/ecc108_library/ecc108_helper.c
|
|
|
|
/** This function calculates CRC.
|
|
*
|
|
* crc_register is initialized with *crc, so it can be chained to
|
|
* calculate CRC from large array of data.
|
|
*
|
|
* For the first calculation or calculation without chaining, crc[0]
|
|
* and crc[1] values must be initialized to 0 by the caller.
|
|
*
|
|
* \param[in] length number of bytes in buffer
|
|
* \param[in] data pointer to data for which CRC should be calculated
|
|
* \param[out] crc pointer to 16-bit CRC
|
|
*/
|
|
static void
|
|
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);
|
|
}
|
|
|
|
// ae_check_crc()
|
|
//
|
|
static bool
|
|
ae_check_crc(const uint8_t *data, uint8_t length)
|
|
{
|
|
uint8_t obs[2] = { 0, 0 };
|
|
|
|
if(data[0] != length) {
|
|
// length is wrong
|
|
STATS(crc_len_error++);
|
|
return false;
|
|
}
|
|
|
|
crc16_chain(length-2, data, obs);
|
|
|
|
return (obs[0] == data[length-2] && obs[1] == data[length-1]);
|
|
}
|
|
|
|
// ae_read1()
|
|
//
|
|
// Read a one-byte status/error code response from chip. It's wrapped as 4 bytes:
|
|
// (len=4) (value) (crc16) (crc16)
|
|
//
|
|
int
|
|
ae_read1(void)
|
|
{
|
|
uint8_t msg[4];
|
|
|
|
for(int retry=7; retry >= 0; retry--) {
|
|
// tell it we want to read a response, read it, and deserialize
|
|
int rv = ae_read_response(msg, 4);
|
|
|
|
if(rv == 0) {
|
|
// nothing heard, it's probably still processing
|
|
ERR("not rdy");
|
|
STATS(not_ready++);
|
|
|
|
delay_ms(5);
|
|
goto try_again;
|
|
}
|
|
|
|
if(rv != 4) {
|
|
ERR("rx len");
|
|
STATS(len_error++);
|
|
goto try_again;
|
|
}
|
|
|
|
// Check length and CRC bytes. we will retry a few times
|
|
// if they are wrong.
|
|
if(!ae_check_crc(msg, 4)) {
|
|
ERR("bad crc");
|
|
STATS(crc_error++);
|
|
goto try_again;
|
|
}
|
|
|
|
STATS(last_resp1 = msg[1]);
|
|
|
|
// done, and it worked; return the one byte.
|
|
return msg[1];
|
|
|
|
try_again:
|
|
STATS(l1_retry++);
|
|
}
|
|
|
|
// fail.
|
|
return -1;
|
|
}
|
|
|
|
// ae_read_n()
|
|
//
|
|
// Read and check CRC over N bytes, wrapped in 3-bytes of framing overhead.
|
|
//
|
|
int
|
|
ae_read_n(uint8_t len, uint8_t *body)
|
|
{
|
|
uint8_t tmp[1+len+2];
|
|
|
|
for(int retry=7; retry >= 0; retry--) {
|
|
|
|
int actual = ae_read_response(tmp, len+3);
|
|
if(actual < 4) {
|
|
|
|
if(actual == 0) {
|
|
// nothing heard, it's probably still processing
|
|
delay_ms(5);
|
|
ERR("not ready2");
|
|
STATS(not_ready_n++);
|
|
} else {
|
|
// a weird short-read? probably fatal, but retry
|
|
ERR("too short");
|
|
STATS(short_error++);
|
|
}
|
|
goto try_again;
|
|
}
|
|
|
|
uint8_t resp_len = tmp[0];
|
|
if(resp_len != (len + 3)) {
|
|
STATS(len_error++);
|
|
if(resp_len == 4) {
|
|
// Probably an unexpected error. But no way to return a short read, so
|
|
// just print out debug info.
|
|
ERRV(tmp[1], "ae errcode");
|
|
STATS(last_resp1 = tmp[1]);
|
|
|
|
return -1;
|
|
}
|
|
ERRV(tmp[0], "wr len"); // wrong length
|
|
goto try_again;
|
|
}
|
|
|
|
if(!ae_check_crc(tmp, actual)) {
|
|
ERR("bad crc");
|
|
STATS(crc_error++);
|
|
goto try_again;
|
|
}
|
|
|
|
// normal case: copy out body of message w/o framing
|
|
memcpy(body, tmp+1, actual-3);
|
|
|
|
#ifdef DEV_STATS
|
|
memcpy(stats.last_n_data, body, MIN(32, actual-3));
|
|
stats.last_n_len = actual-3;
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
try_again:
|
|
STATS(ln_retry++);
|
|
ae_wake();
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// ae_send()
|
|
//
|
|
void
|
|
ae_send(aeopcode_t opcode, uint8_t p1, uint16_t p2)
|
|
{
|
|
ae_send_n(opcode, p1, p2, NULL, 0);
|
|
}
|
|
|
|
// ae_send_n()
|
|
//
|
|
void
|
|
ae_send_n(aeopcode_t opcode, uint8_t p1, uint16_t p2, const 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,
|
|
};
|
|
|
|
STATIC_ASSERT(sizeof(known) == 6);
|
|
|
|
STATS(last_op = opcode);
|
|
STATS(last_p1 = p1);
|
|
STATS(last_p2 = p2);
|
|
|
|
// important to wake chip at this point.
|
|
ae_wake();
|
|
|
|
_send_serialized((const uint8_t *)&known, sizeof(known));
|
|
|
|
// CRC will start from frame_len onwards
|
|
uint8_t crc[2] = {0, 0};
|
|
crc16_chain(sizeof(known)-1, &known.framed_len, crc);
|
|
|
|
// insert a variable-length body area (sometimes)
|
|
if(data_len) {
|
|
_send_serialized(data, data_len);
|
|
|
|
crc16_chain(data_len, data, crc);
|
|
}
|
|
|
|
// send final CRC bytes
|
|
_send_serialized(crc, 2);
|
|
}
|
|
|
|
// ae_delay()
|
|
//
|
|
// Delay for worse-case time. Except new data sheets says it's
|
|
// not worst-case or even typical time! Useless.
|
|
//
|
|
void
|
|
ae_delay(aeopcode_t opcode)
|
|
{
|
|
// no longer required -- instead we just want for chip
|
|
// to give us a response (by polling it)
|
|
}
|
|
|
|
#if 0
|
|
// RISKY - Easy for Mitm to control value.
|
|
|
|
// ae_random()
|
|
//
|
|
// Get a fresh random number.
|
|
//
|
|
int
|
|
ae_random(uint8_t randout[32])
|
|
{
|
|
int rv;
|
|
|
|
ae_send(OP_Random, 0, 0);
|
|
|
|
ae_delay(OP_Random);
|
|
|
|
rv = ae_read_n(32, randout);
|
|
RET_IF_BAD(rv);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
// ae_get_info()
|
|
//
|
|
// Do Info(p1=2) command, and return result.
|
|
//
|
|
uint16_t
|
|
ae_get_info(void)
|
|
{
|
|
// not doing error checking here
|
|
ae_send(OP_Info, 0x2, 0);
|
|
|
|
ae_delay(OP_Info);
|
|
|
|
// note: always returns 4 bytes, but most are garbage and unused.
|
|
uint8_t tmp[4];
|
|
ae_read_n(4, tmp);
|
|
|
|
return (tmp[0] << 8) | tmp[1];
|
|
}
|
|
|
|
#if 0
|
|
// ae_delay_time()
|
|
//
|
|
// Returns time in MS for max exec time of each command.
|
|
//
|
|
int
|
|
ae_delay_time(aeopcode_t opcode)
|
|
{
|
|
// worse case delay times.
|
|
switch(opcode) {
|
|
case OP_CheckMac: // 0x28
|
|
return 13;
|
|
case OP_Counter: // 0x24
|
|
return 20;
|
|
case OP_DeriveKey: // 0x1C
|
|
return 50;
|
|
case OP_ECDH: // 0x43
|
|
return 58;
|
|
case OP_GenDig: // 0x15
|
|
return 11;
|
|
case OP_GenKey: // 0x40
|
|
return 115;
|
|
#if FOR_508
|
|
case OP_HMAC: // 0x11
|
|
return 23;
|
|
case OP_Pause: // 0x01
|
|
return 3;
|
|
#endif
|
|
case OP_Info: // 0x30
|
|
return 2; // officially 1, but marginal
|
|
case OP_Lock: // 0x17
|
|
return 32;
|
|
case OP_MAC: // 0x08
|
|
return 14;
|
|
case OP_Nonce: // 0x16
|
|
return 30; // officially 7, but need 30 for real
|
|
case OP_PrivWrite: // 0x46
|
|
return 48;
|
|
case OP_Random: // 0x1B
|
|
return 23;
|
|
case OP_Read: // 0x02
|
|
return 1;
|
|
case OP_Sign: // 0x41
|
|
return 50;
|
|
case OP_SHA: // 0x47
|
|
return 9;
|
|
case OP_UpdateExtra: // 0x20
|
|
return 10;
|
|
case OP_Verify: // 0x45
|
|
return 58;
|
|
case OP_Write: // 0x12
|
|
return 26;
|
|
|
|
#if FOR_608
|
|
case OP_AES: // 0x51
|
|
return 3;
|
|
case OP_KDF: // 0x56
|
|
return 115;
|
|
case OP_SecureBoot: // 0x80
|
|
return 35;
|
|
case OP_SelftTest: // 0x77
|
|
return 200;
|
|
#endif
|
|
}
|
|
|
|
return 100;
|
|
}
|
|
#endif
|
|
|
|
// ae_load_nonce()
|
|
//
|
|
// Load Tempkey with a specific value. Resulting Tempkey cannot be
|
|
// used with many commands/keys, but is needed for signing.
|
|
//
|
|
int
|
|
ae_load_nonce(const uint8_t nonce[32])
|
|
{
|
|
// p1=3
|
|
ae_send_n(OP_Nonce, 3, 0, nonce, 32); // 608a ok
|
|
|
|
ae_delay(OP_Nonce);
|
|
|
|
return ae_read1();
|
|
}
|
|
|
|
// ae_pick_nonce()
|
|
//
|
|
// Load Tempkey with a nonce value that we both know, but
|
|
// is random and we both know is random! Tricky!
|
|
//
|
|
int
|
|
ae_pick_nonce(const uint8_t num_in[20], uint8_t tempkey[32])
|
|
{
|
|
// 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).
|
|
ae_send_n(OP_Nonce, 0, 0, num_in, 20);
|
|
|
|
ae_delay(OP_Nonce);
|
|
|
|
// Nonce command returns the RNG result, but not contents of TempKey
|
|
uint8_t randout[32];
|
|
int rv = ae_read_n(32, randout);
|
|
RET_IF_BAD(rv);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// ae_is_correct_tempkey()
|
|
//
|
|
// Check that TempKey is holding what we think it does. Uses the MAC
|
|
// command over contents of Tempkey and our shared secret.
|
|
//
|
|
bool
|
|
ae_is_correct_tempkey(const uint8_t expected_tempkey[32])
|
|
{
|
|
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
|
|
|
|
ae_send(OP_MAC, mode, KEYNUM_pairing);
|
|
|
|
ae_delay(OP_MAC);
|
|
|
|
// read chip's answer
|
|
uint8_t resp[32];
|
|
int rv = ae_read_n(32, resp);
|
|
if(rv) return false;
|
|
|
|
ae_keep_alive();
|
|
|
|
// 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, 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->ae_serial_number)+4, 4);
|
|
sha256_update(&ctx, ((const uint8_t *)rom_secrets->ae_serial_number)+0, 4);
|
|
|
|
#if 0
|
|
// this verifies no problem.
|
|
ASSERT(ctx.datalen + (ctx.bitlen/8) == 32+32+1+1+2+8+3+1+4+2+2); // == 88
|
|
#endif
|
|
|
|
uint8_t actual[32];
|
|
sha256_final(&ctx, actual);
|
|
|
|
return check_equal(actual, resp, 32);
|
|
}
|
|
|
|
// ae_checkmac_hard()
|
|
//
|
|
// 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
|
|
ae_checkmac_hard(uint8_t keynum, const uint8_t secret[32])
|
|
{
|
|
uint8_t digest[32];
|
|
|
|
int rv = ae_gendig_slot(keynum, secret, digest);
|
|
RET_IF_BAD(rv);
|
|
|
|
// 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(!ae_is_correct_tempkey(digest)) return -2;
|
|
|
|
// worked.
|
|
return 0;
|
|
}
|
|
|
|
// ae_pair_unlock()
|
|
//
|
|
// 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
|
|
ae_pair_unlock()
|
|
{
|
|
return ae_checkmac(KEYNUM_pairing, rom_secrets->pairing_secret);
|
|
}
|
|
|
|
// ae_checkmac()
|
|
//
|
|
// 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
|
|
ae_checkmac(uint8_t keynum, const uint8_t secret[32])
|
|
{
|
|
int rv;
|
|
|
|
// 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));
|
|
|
|
// need this one, want to reset watchdog to this point.
|
|
ae_keep_alive();
|
|
|
|
// - load tempkey with a known nonce value
|
|
uint8_t zeros[8] = {0};
|
|
uint8_t tempkey[32];
|
|
rv = ae_pick_nonce(numin, tempkey);
|
|
RET_IF_BAD(rv);
|
|
|
|
// - 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 ee = 0xEE;
|
|
sha256_update(&ctx, &ee, 1);
|
|
sha256_update(&ctx, &od[7], 4);
|
|
|
|
uint8_t snp[2] = { 0x01, 0x23 };
|
|
sha256_update(&ctx, snp, 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);
|
|
|
|
#if 0
|
|
// this verifies no problem.
|
|
int l = (ctx.blocks * 64) + ctx.npartial;
|
|
ASSERT(l == 32+32+4+8+3+1+4+2+2); // == 88
|
|
#endif
|
|
|
|
sha256_final(&ctx, req.resp);
|
|
memcpy(req.od, od, 13);
|
|
|
|
STATIC_ASSERT(sizeof(req) == 32 + 32 + 13);
|
|
|
|
// Give our answer to the chip.
|
|
ae_send_n(OP_CheckMac, 0x01, keynum, (uint8_t *)&req, sizeof(req));
|
|
|
|
ae_delay(OP_CheckMac);
|
|
|
|
rv = ae_read1();
|
|
if(rv != 0) {
|
|
// did it work?! No.
|
|
if(rv == AE_CHECKMAC_FAIL) {
|
|
ERR("CM fail"); // typical case: our hashs don't match
|
|
} else {
|
|
ERRV(rv, "CheckMac");
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
#if 0
|
|
// double check?
|
|
uint16_t ii = ae_get_info();
|
|
// expect 0x005d for key 11
|
|
|
|
if(!I_AuthValid(ii) || (I_AuthKey(ii) != keynum)) {
|
|
ERR("Info r/b");
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
// just in case ... always restart watchdog timer.
|
|
ae_keep_alive();
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_sign()
|
|
//
|
|
// Sign a message (already digested)
|
|
//
|
|
int
|
|
ae_sign(uint8_t keynum, uint8_t msg_hash[32], uint8_t signature[64])
|
|
{
|
|
int rv = ae_load_nonce(msg_hash);
|
|
RET_IF_BAD(rv);
|
|
|
|
ae_send_n(OP_Sign, 0x80, keynum, NULL, 0);
|
|
|
|
ae_delay(OP_Sign);
|
|
|
|
rv = ae_read_n(64, signature);
|
|
RET_IF_BAD(rv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_get_counter()
|
|
//
|
|
// Just read a one-way counter.
|
|
//
|
|
int
|
|
ae_get_counter(uint32_t *result, uint8_t counter_number)
|
|
{
|
|
ae_send(OP_Counter, 0x0, counter_number);
|
|
ae_delay(OP_Counter);
|
|
|
|
int rv = ae_read_n(4, (uint8_t *)result);
|
|
RET_IF_BAD(rv);
|
|
|
|
// 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];
|
|
rv = ae_gendig_counter(counter_number, *result, digest);
|
|
RET_IF_BAD(rv);
|
|
|
|
if(!ae_is_correct_tempkey(digest)) {
|
|
// no legit way for this to happen, so just die.
|
|
fatal_mitm();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_add_counter()
|
|
//
|
|
// Add-to and return a one-way counter's value. Have to go up in
|
|
// single-unit steps, but can we loop.
|
|
//
|
|
int
|
|
ae_add_counter(uint32_t *result, uint8_t counter_number, int incr)
|
|
{
|
|
for(int i=0; i<incr; i++) {
|
|
ae_send(OP_Counter, 0x1, counter_number);
|
|
ae_delay(OP_Counter);
|
|
int rv = ae_read_n(4, (uint8_t *)result);
|
|
RET_IF_BAD(rv);
|
|
}
|
|
|
|
// 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 increamenting the counter.
|
|
|
|
uint8_t digest[32];
|
|
int rv = ae_gendig_counter(counter_number, *result, digest);
|
|
RET_IF_BAD(rv);
|
|
|
|
if(!ae_is_correct_tempkey(digest)) {
|
|
// no legit way for this to happen, so just die.
|
|
fatal_mitm();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
// unused code, and not supported directly on 608a
|
|
// ae_hmac()
|
|
//
|
|
// Perform HMAC on the chip, using a particular key.
|
|
//
|
|
int
|
|
ae_hmac(uint8_t keynum, const uint8_t *msg, uint16_t msg_len, uint8_t digest[32])
|
|
{
|
|
// setup SHA in HMAC mode.
|
|
ae_send(OP_SHA, 0x04, keynum);
|
|
|
|
ae_delay(OP_SHA);
|
|
|
|
int rv = ae_read1();
|
|
if(rv != AE_COMMAND_OK) return -1;
|
|
|
|
// send full blocks, if any.
|
|
|
|
while(msg_len >= 64) {
|
|
ae_send_n(OP_SHA, 0x01, 64, msg, 64);
|
|
ae_delay(OP_SHA);
|
|
|
|
rv = ae_read1();
|
|
if(rv != AE_COMMAND_OK) return -1;
|
|
|
|
msg += 64;
|
|
msg_len -= 64;
|
|
}
|
|
|
|
// finalize, with final 0 to 63 bytes
|
|
ae_send_n(OP_SHA, 0x02, msg_len, msg, msg_len);
|
|
RET_IF_BAD(rv);
|
|
|
|
ae_delay(OP_SHA);
|
|
|
|
rv = ae_read_n(32, digest);
|
|
RET_IF_BAD(rv);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
// ae_hmac32()
|
|
//
|
|
// 508a: Different opcode, OP_HMAC does exactly 32 bytes w/ less steps.
|
|
// 608a: Use old SHA256 command, but with new flags.
|
|
//
|
|
int
|
|
ae_hmac32(uint8_t keynum, const uint8_t msg[32], uint8_t digest[32])
|
|
{
|
|
#if FOR_508
|
|
// Load tempkey w/ message to be HMAC'ed
|
|
int rv = ae_load_nonce(msg);
|
|
RET_IF_BAD(rv);
|
|
|
|
// Ask for HMAC using specific key
|
|
ae_send(OP_HMAC, (1<<2) | (1<<6), keynum);
|
|
|
|
ae_delay(OP_HMAC);
|
|
|
|
return ae_read_n(32, digest);
|
|
#endif
|
|
|
|
#if FOR_608
|
|
// Start SHA w/ HMAC setup
|
|
ae_send(OP_SHA, 4, keynum); // 4 = HMAC_Init
|
|
|
|
// expect zero, meaning "ready"
|
|
ae_delay(OP_SHA);
|
|
int rv = ae_read1();
|
|
RET_IF_BAD(rv);
|
|
|
|
// send the contents to be hashed
|
|
ae_send_n(OP_SHA, (3<<6) | 2, 32, msg, 32); // 2 = Finalize, 3=Place output
|
|
|
|
ae_delay(OP_SHA);
|
|
|
|
// read result
|
|
return ae_read_n(32, digest);
|
|
#endif
|
|
}
|
|
|
|
// ae_get_serial()
|
|
//
|
|
// Return the serial number: it's 9 bytes, altho 3 are fixed.
|
|
//
|
|
int
|
|
ae_get_serial(uint8_t serial[6])
|
|
{
|
|
ae_send(OP_Read, 0x80, 0x0);
|
|
|
|
ae_delay(OP_Read);
|
|
|
|
uint8_t temp[32];
|
|
int rv = ae_read_n(32, temp);
|
|
RET_IF_BAD(rv);
|
|
|
|
// reformat to 9 bytes.
|
|
uint8_t ts[9];
|
|
memcpy(ts, &temp[0], 4);
|
|
memcpy(&ts[4], &temp[8], 5);
|
|
|
|
// check the hard-coded values
|
|
if((ts[0] != 0x01) || (ts[1] != 0x23) || (ts[8] != 0xEE)) return 1;
|
|
|
|
// save only the unique bits.
|
|
memcpy(serial, ts+2, 6);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
// ae_slot_locks()
|
|
//
|
|
// Read a 16-bit bitmask of which data slots are presently locked.
|
|
//
|
|
int
|
|
ae_slot_locks(void)
|
|
{
|
|
// Bytes 88, 89 in the Config zone is a bitmap of
|
|
// which slots are locked. Have to read 4 bytes here tho
|
|
ae_send(OP_Read, 0x00, 88/4);
|
|
|
|
ae_delay(OP_Read);
|
|
|
|
uint8_t tmp[4];
|
|
int rv = ae_read_n(4, tmp);
|
|
if(rv) return -2;
|
|
|
|
// returns positive 16-bit number on success
|
|
return (tmp[1] << 8) | tmp[0];
|
|
}
|
|
#endif
|
|
|
|
// ae_write_data_slot()
|
|
//
|
|
// -- can also lock it.
|
|
//
|
|
int
|
|
ae_write_data_slot(int slot_num, const uint8_t *data, int len, bool lock_it)
|
|
{
|
|
ASSERT(len >= 32);
|
|
ASSERT(len <= 416);
|
|
|
|
for(int blk=0, xlen=len; xlen>0; blk++, xlen-=32) {
|
|
// have to write each "block" of 32-bytes, separately
|
|
// zone => data
|
|
ae_send_n(OP_Write, 0x80|2, (blk<<8) | (slot_num<<3), data+(blk*32), 32);
|
|
|
|
ae_delay(OP_Write);
|
|
|
|
int rv = ae_read1();
|
|
RET_IF_BAD(rv);
|
|
}
|
|
|
|
if(lock_it) {
|
|
ASSERT(slot_num != 8); // no support for mega slot 8
|
|
ASSERT(len == 32); // probably not a limitation here
|
|
|
|
// Assume 36/72-byte long slot, which will be partially written, and rest
|
|
// should be ones.
|
|
const int slot_len = (slot_num <= 7) ? 36 : 72;
|
|
uint8_t copy[slot_len];
|
|
|
|
memset(copy, 0xff, slot_len);
|
|
memcpy(copy, data, len);
|
|
|
|
// calc expected CRC
|
|
uint8_t crc[2] = {0, 0};
|
|
crc16_chain(slot_len, copy, crc);
|
|
|
|
// do the lock
|
|
ae_send(OP_Lock, 2 | (slot_num << 2), (crc[1]<<8) | crc[0]);
|
|
|
|
ae_delay(OP_Lock);
|
|
|
|
int rv = ae_read1();
|
|
RET_IF_BAD(rv);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_gendig_slot()
|
|
//
|
|
int
|
|
ae_gendig_slot(int slot_num, const uint8_t slot_contents[32], uint8_t digest[32])
|
|
{
|
|
// 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];
|
|
|
|
rng_buffer(num_in, sizeof(num_in));
|
|
int rv = ae_pick_nonce(num_in, tempkey);
|
|
RET_IF_BAD(rv);
|
|
|
|
//using Zone=2="Data" => "KeyID specifies a slot in the Data zone"
|
|
ae_send(OP_GenDig, 0x2, slot_num);
|
|
|
|
ae_delay(OP_GenDig);
|
|
|
|
rv = ae_read1();
|
|
RET_IF_BAD(rv);
|
|
|
|
ae_keep_alive();
|
|
|
|
// 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;
|
|
}
|
|
|
|
// ae_gendig_counter()
|
|
//
|
|
// 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
|
|
ae_gendig_counter(int counter_num, const uint32_t expected_value, uint8_t digest[32])
|
|
{
|
|
uint8_t num_in[20], tempkey[32];
|
|
|
|
rng_buffer(num_in, sizeof(num_in));
|
|
int rv = ae_pick_nonce(num_in, tempkey);
|
|
RET_IF_BAD(rv);
|
|
|
|
//using Zone=4="Counter" => "KeyID specifies the monotonic counter ID"
|
|
ae_send(OP_GenDig, 0x4, counter_num);
|
|
|
|
ae_delay(OP_GenDig);
|
|
|
|
rv = ae_read1();
|
|
RET_IF_BAD(rv);
|
|
|
|
ae_keep_alive();
|
|
|
|
// 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;
|
|
}
|
|
|
|
// ae_encrypted_read32()
|
|
//
|
|
int
|
|
ae_encrypted_read32(int data_slot, int blk,
|
|
int read_kn, const uint8_t read_key[32], uint8_t data[32])
|
|
{
|
|
uint8_t digest[32];
|
|
|
|
ae_pair_unlock();
|
|
|
|
int rv = ae_gendig_slot(read_kn, read_key, digest);
|
|
RET_IF_BAD(rv);
|
|
|
|
// read nth 32-byte "block"
|
|
ae_send(OP_Read, 0x82, (blk << 8) | (data_slot<<3));
|
|
|
|
ae_delay(OP_Read);
|
|
|
|
rv = ae_read_n(32, data);
|
|
RET_IF_BAD(rv);
|
|
|
|
xor_mixin(data, digest, 32);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_encrypted_read()
|
|
//
|
|
int
|
|
ae_encrypted_read(int data_slot, int read_kn, const uint8_t read_key[32], uint8_t *data, int len)
|
|
{
|
|
// not clear if chip supports 4-byte encrypted reads
|
|
ASSERT((len == 32) || (len == 72));
|
|
|
|
int rv = ae_encrypted_read32(data_slot, 0, read_kn, read_key, data);
|
|
RET_IF_BAD(rv);
|
|
|
|
if(len == 32) return 0;
|
|
|
|
rv = ae_encrypted_read32(data_slot, 1, read_kn, read_key, data+32);
|
|
RET_IF_BAD(rv);
|
|
|
|
uint8_t tmp[32];
|
|
rv = ae_encrypted_read32(data_slot, 2, read_kn, read_key, tmp);
|
|
RET_IF_BAD(rv);
|
|
|
|
memcpy(data+64, tmp, 72-64);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_encrypted_write()
|
|
//
|
|
int
|
|
ae_encrypted_write32(int data_slot, int blk, int write_kn,
|
|
const uint8_t write_key[32], const uint8_t data[32])
|
|
{
|
|
uint8_t digest[32];
|
|
|
|
ae_pair_unlock();
|
|
|
|
// generate a hash over shared secret and rng
|
|
int rv = ae_gendig_slot(write_kn, write_key, digest);
|
|
RET_IF_BAD(rv);
|
|
|
|
// 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]);
|
|
|
|
ae_send_n(OP_Write, p1, (p2_msb << 8) | p2_lsb, body, sizeof(body));
|
|
|
|
ae_delay(OP_Write);
|
|
|
|
return ae_read1();
|
|
}
|
|
|
|
// ae_encrypted_write()
|
|
//
|
|
int
|
|
ae_encrypted_write(int data_slot, int write_kn, const uint8_t write_key[32],
|
|
const uint8_t *data, int len)
|
|
{
|
|
ASSERT(data_slot >= 0);
|
|
ASSERT(data_slot <= 15);
|
|
|
|
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 = ae_encrypted_write32(data_slot, blk, write_kn, write_key, tmp);
|
|
RET_IF_BAD(rv);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_read_data_slot()
|
|
//
|
|
int
|
|
ae_read_data_slot(int slot_num, uint8_t *data, int len)
|
|
{
|
|
ASSERT((len == 4) || (len == 32) || (len == 72));
|
|
|
|
// zone => data
|
|
// only reading first block of 32 bytes. ignore the rest
|
|
ae_send(OP_Read, (len == 4 ? 0x00 : 0x80) | 2, (slot_num<<3));
|
|
|
|
ae_delay(OP_Read);
|
|
|
|
int rv = ae_read_n((len == 4) ? 4 : 32, data);
|
|
RET_IF_BAD(rv);
|
|
|
|
if(len == 72) {
|
|
// read second block
|
|
ae_send(OP_Read, 0x82, (1<<8) | (slot_num<<3));
|
|
|
|
ae_delay(OP_Read);
|
|
|
|
int rv = ae_read_n(32, data+32);
|
|
RET_IF_BAD(rv);
|
|
|
|
// read third block, but only using part of it
|
|
uint8_t tmp[32];
|
|
ae_send(OP_Read, 0x82, (2<<8) | (slot_num<<3));
|
|
|
|
ae_delay(OP_Read);
|
|
|
|
rv = ae_read_n(32, tmp);
|
|
RET_IF_BAD(rv);
|
|
|
|
memcpy(data+64, tmp, 72-64);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_config_write()
|
|
//
|
|
static int
|
|
ae_config_write(const uint8_t config[128])
|
|
{
|
|
// send all 128 bytes, less some that can't be written.
|
|
for(int n=16; n<128; n+= 4) {
|
|
if(n == 84) continue; // that word not writable
|
|
|
|
// Must work on words, since can't write to most of the complete blocks.
|
|
// args = write_params(block=n//32, offset=n//4, is_config=True)
|
|
// p2 = (block << 3) | offset
|
|
ae_send_n(OP_Write, 0, n/4, &config[n], 4);
|
|
|
|
ae_delay(OP_Write);
|
|
|
|
int rv = ae_read1();
|
|
if(rv) return rv;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_lock_config_zone()
|
|
//
|
|
static int
|
|
ae_lock_config_zone(const uint8_t config[128])
|
|
{
|
|
// calc expected CRC
|
|
uint8_t crc[2] = {0, 0};
|
|
|
|
crc16_chain(128, config, crc);
|
|
|
|
// do the lock: mode=0
|
|
ae_send(OP_Lock, 0x0, (crc[1]<<8) | crc[0]);
|
|
|
|
ae_delay(OP_Lock);
|
|
|
|
return ae_read1();
|
|
}
|
|
|
|
// ae_lock_data_zone()
|
|
//
|
|
static int
|
|
ae_lock_data_zone(void)
|
|
{
|
|
// NOTE: I haven't been able to calc CRC right, so not using it.
|
|
|
|
// do the lock: mode=1 (datazone) + 0x80 (no CRC check)
|
|
ae_send(OP_Lock, 0x81, 0x0000);
|
|
|
|
ae_delay(OP_Lock);
|
|
|
|
return ae_read1();
|
|
}
|
|
|
|
#if 0
|
|
// ae_sha256()
|
|
//
|
|
int
|
|
ae_sha256(const uint8_t *msg, int msg_len, uint8_t digest[32])
|
|
{
|
|
// setup
|
|
ae_send(OP_SHA, 0x00, 0);
|
|
|
|
ae_delay(OP_SHA);
|
|
|
|
int rv = ae_read1();
|
|
if(rv != AE_COMMAND_OK) return -1;
|
|
|
|
while(msg_len >= 64) {
|
|
ae_send_n(OP_SHA, 0x01, 64, msg, 64);
|
|
ae_delay(OP_SHA);
|
|
|
|
rv = ae_read1();
|
|
if(rv != AE_COMMAND_OK) return -1;
|
|
|
|
msg += 64;
|
|
msg_len -= 64;
|
|
}
|
|
|
|
// finalize, with final 0 to 63 bytes
|
|
ae_send_n(OP_SHA, 0x02, msg_len, msg, msg_len);
|
|
|
|
ae_delay(OP_SHA);
|
|
|
|
return ae_read_n(32, digest);
|
|
}
|
|
#endif
|
|
|
|
// ae_set_gpio()
|
|
//
|
|
int
|
|
ae_set_gpio(int state)
|
|
{
|
|
// 1=turn on green, 0=red light (if not yet configured to be secure)
|
|
ae_send(OP_Info, 3, 2 | (!!state));
|
|
|
|
ae_delay(OP_Info);
|
|
|
|
// "Always return the current state in the first byte followed by three bytes of 0x00"
|
|
// - simple 1/0, in LSB.
|
|
uint8_t resp[4];
|
|
|
|
int rv = ae_read_n(4, resp);
|
|
RET_IF_BAD(rv);
|
|
|
|
return (resp[0] != state) ? -1 : 0;
|
|
}
|
|
|
|
// ae_set_gpio_secure()
|
|
//
|
|
// Set the GPIO using secure hash generated somehow already.
|
|
//
|
|
int
|
|
ae_set_gpio_secure(uint8_t digest[32])
|
|
{
|
|
ae_pair_unlock();
|
|
ae_checkmac(KEYNUM_firmware, digest);
|
|
|
|
int rv = ae_set_gpio(1);
|
|
|
|
if(rv == 0) {
|
|
// We set the output, and we got a successful readback, but we can't
|
|
// trust that readback, and so do a verify that the chip has
|
|
// the digest we think it does. If MitM wanted to turn off the output,
|
|
// they can do that anytime regardless. We just don't want them to be
|
|
// able to fake it being set, and therefore bypass the
|
|
// "unsigned firmware" delay and warning.
|
|
ae_pair_unlock();
|
|
|
|
if(ae_checkmac_hard(KEYNUM_firmware, digest) != 0) {
|
|
fatal_mitm();
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
// ae_get_gpio()
|
|
//
|
|
// Do Info(p1=3) command, and return result.
|
|
//
|
|
// IMPORTANT: do not trust this result, could be MitM'ed.
|
|
//
|
|
uint8_t
|
|
ae_get_gpio(void)
|
|
{
|
|
// not doing error checking here
|
|
ae_send(OP_Info, 0x3, 0);
|
|
|
|
ae_delay(OP_Info);
|
|
|
|
// note: always returns 4 bytes, but most are garbage and unused.
|
|
uint8_t tmp[4];
|
|
ae_read_n(4, tmp);
|
|
|
|
return tmp[0];
|
|
}
|
|
|
|
// ae_read_config_byte()
|
|
//
|
|
// Read a byte from config area.
|
|
//
|
|
int
|
|
ae_read_config_byte(int offset)
|
|
{
|
|
uint8_t tmp[4];
|
|
|
|
ae_read_config_word(offset, tmp);
|
|
|
|
return tmp[offset % 4];
|
|
}
|
|
|
|
// ae_read_config_word()
|
|
//
|
|
// Read a 4-byte area from config area, or -1 if fail.
|
|
//
|
|
int
|
|
ae_read_config_word(int offset, uint8_t *dest)
|
|
{
|
|
offset &= 0x7f;
|
|
|
|
// read 32 bits (aligned)
|
|
ae_send(OP_Read, 0x00, offset/4);
|
|
|
|
ae_delay(OP_Read);
|
|
|
|
int rv = ae_read_n(4, dest);
|
|
if(rv) return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ae_destroy_key()
|
|
//
|
|
int
|
|
ae_destroy_key(int keynum)
|
|
{
|
|
uint8_t numin[20];
|
|
|
|
// Load tempkey with a known (random) nonce value
|
|
rng_buffer(numin, sizeof(numin));
|
|
ae_send_n(OP_Nonce, 0, 0, numin, 20);
|
|
|
|
ae_delay(OP_Nonce);
|
|
|
|
// Nonce command returns the RNG result, not contents of TempKey,
|
|
// but since we are destroying, no need to calculate what it is.
|
|
uint8_t randout[32];
|
|
int rv = ae_read_n(32, randout);
|
|
RET_IF_BAD(rv);
|
|
|
|
// do a "DeriveKey" operation, based on that!
|
|
ae_send(OP_DeriveKey, 0x00, keynum);
|
|
|
|
ae_delay(OP_DeriveKey);
|
|
|
|
return ae_read1();
|
|
}
|
|
|
|
// ae_config_read()
|
|
//
|
|
int
|
|
ae_config_read(uint8_t config[128])
|
|
{
|
|
for(int blk=0; blk<4; blk++) {
|
|
// read 32 bytes (aligned) from config "zone"
|
|
ae_send(OP_Read, 0x80, blk<<3);
|
|
|
|
ae_delay(OP_Read);
|
|
|
|
int rv = ae_read_n(32, &config[32*blk]);
|
|
if(rv) return EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_setup_config()
|
|
//
|
|
// One-time config and lockdown of the 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 datazone
|
|
// is 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.
|
|
//
|
|
int
|
|
ae_setup_config(void)
|
|
{
|
|
// Is data zone is locked?
|
|
// Allow rest of function to happen if it's not.
|
|
|
|
// 0x55 = unlocked; 0x00 = locked
|
|
bool data_locked = (ae_read_config_byte(86) != 0x55);
|
|
if(data_locked) return 0; // basically success
|
|
|
|
// Program the "config" area, and then lock it.
|
|
|
|
// To lock, we need a CRC over whole thing, but we
|
|
// only set a few values... plus the serial number is
|
|
// in there, so start with some readout.
|
|
uint8_t config[128];
|
|
int rv = ae_config_read(config);
|
|
if(rv) return rv;
|
|
|
|
// verify some fixed values
|
|
ASSERT(config[0] == 0x01);
|
|
ASSERT(config[1] == 0x23);
|
|
ASSERT(config[12] == 0xee);
|
|
|
|
// guess part number
|
|
int8_t partno = ((config[6]>>4)&0xf);
|
|
ASSERT(partno == 6);
|
|
|
|
uint8_t serial[9];
|
|
memcpy(serial, &config[0], 4);
|
|
memcpy(&serial[4], &config[8], 5);
|
|
|
|
if(check_all_ones(rom_secrets->ae_serial_number, 9)) {
|
|
// flash is empty; remember this serial number
|
|
flash_save_ae_serial(serial);
|
|
}
|
|
|
|
if(!check_equal(rom_secrets->ae_serial_number, serial, 9)) {
|
|
// write failed?
|
|
// we're already linked to a different chip? Write failed?
|
|
return EPERM;
|
|
}
|
|
|
|
// Setup steps:
|
|
// - write config zone data
|
|
// - lock that
|
|
// - write pairing secret (test it works)
|
|
// - pick RNG value for words secret (and forget it)
|
|
// - set all PIN values to known value (zeros)
|
|
// - set all money secrets to knonw value (zeros)
|
|
// - lock the data zone
|
|
|
|
if(config[87] == 0x55) {
|
|
// config is still unlocked
|
|
|
|
// setup "config zone" area of the chip
|
|
static const uint8_t config_1[] = AE_CHIP_CONFIG_1;
|
|
static const uint8_t config_2[] = AE_CHIP_CONFIG_2;
|
|
|
|
STATIC_ASSERT(sizeof(config_1) == 84-16);
|
|
STATIC_ASSERT(sizeof(config_2) == 128-90);
|
|
|
|
memcpy(&config[16], config_1, sizeof(config_1));
|
|
memcpy(&config[90], config_2, sizeof(config_2));
|
|
|
|
// write it.
|
|
if(ae_config_write(config)) {
|
|
INCONSISTENT("conf wr");
|
|
}
|
|
|
|
ae_keep_alive();
|
|
|
|
// lock config zone
|
|
if(ae_lock_config_zone(config)) {
|
|
INCONSISTENT("conf lock");
|
|
}
|
|
} else {
|
|
// check config is what we need? but when would that happen?
|
|
// omit this check, since only useful for debug cases
|
|
}
|
|
|
|
// Load data zone with some known values.
|
|
// The datazone still unlocked, so no encryption needed (nor possible).
|
|
|
|
// will use zeros for all PIN codes, and customer-defined-secret starting values
|
|
uint8_t zeros[72];
|
|
memset(zeros, 0, sizeof(zeros));
|
|
|
|
// slots can already locked, if we re-run any of this code... can't overwrite in
|
|
// that case.
|
|
uint16_t unlocked = config[88] | (((uint8_t)config[89])<<8);
|
|
|
|
for(int kn=0; kn<16; kn++) {
|
|
ae_keep_alive();
|
|
|
|
if(!(unlocked & (1<<kn))) {
|
|
STATS(was_locked |= (1<<kn));
|
|
continue;
|
|
}
|
|
|
|
switch(kn) {
|
|
default:
|
|
case 12: break;
|
|
case 15: break;
|
|
|
|
case KEYNUM_pairing:
|
|
if(ae_write_data_slot(kn, rom_secrets->pairing_secret, 32, false)) {
|
|
INCONSISTENT("wr pair");
|
|
}
|
|
break;
|
|
|
|
case KEYNUM_pin_stretch:
|
|
case KEYNUM_pin_attempt: {
|
|
// HMAC-SHA256 key (forgotten immediately), for:
|
|
// - phishing words
|
|
// - each pin attempt (limited by counter0)
|
|
// - stretching pin/words attempts (iterated may times)
|
|
// See mathcheck.py for details.
|
|
uint8_t tmp[32];
|
|
|
|
rng_buffer(tmp, sizeof(tmp));
|
|
//#warning "fixed secrets"
|
|
//memset(tmp, 0x41+kn, 32);
|
|
|
|
if(ae_write_data_slot(kn, tmp, 32, true)) {
|
|
INCONSISTENT("wr word");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KEYNUM_main_pin:
|
|
case KEYNUM_lastgood:
|
|
case KEYNUM_duress_pin:
|
|
case KEYNUM_duress_lastgood:
|
|
case KEYNUM_brickme:
|
|
case KEYNUM_firmware:
|
|
if(ae_write_data_slot(kn, zeros, 32, false)) {
|
|
INCONSISTENT("wr blk 32");
|
|
}
|
|
break;
|
|
|
|
case KEYNUM_secret:
|
|
case KEYNUM_duress_secret:
|
|
if(ae_write_data_slot(kn, zeros, 72, false)) {
|
|
INCONSISTENT("wr blk 72");
|
|
}
|
|
break;
|
|
|
|
case KEYNUM_long_secret: { // 416 bytes
|
|
uint8_t long_zeros[416] = {0};
|
|
if(ae_write_data_slot(kn, long_zeros, 416, false)) {
|
|
INCONSISTENT("wr blk 416");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case KEYNUM_match_count: {
|
|
uint32_t buf[32/4] = { 1024, 1024 };
|
|
if(ae_write_data_slot(KEYNUM_match_count, (const uint8_t *)buf,sizeof(buf),false)) {
|
|
INCONSISTENT("wr mc");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 0:
|
|
if(ae_write_data_slot(kn, (const uint8_t *)copyright_msg, 32, true)) {
|
|
INCONSISTENT("wr (c)");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// lock the data zone and effectively enter normal operation.
|
|
ae_keep_alive();
|
|
if(ae_lock_data_zone()) {
|
|
INCONSISTENT("data lock");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#if 0
|
|
// ae_write_match_count()
|
|
//
|
|
int
|
|
ae_write_match_count(uint32_t count, const uint8_t *write_key)
|
|
{
|
|
uint32_t buf[8] = { count, count };
|
|
|
|
// ASSERT(count & 31 == 0); // not clear, but probably should have 5LSB=0
|
|
STATIC_ASSERT(sizeof(buf) == 32); // limitation of ae_write_data_slot
|
|
|
|
if(!write_key) {
|
|
return ae_write_data_slot(KEYNUM_match_count, (const uint8_t *)buf, sizeof(buf), false);
|
|
} else {
|
|
return ae_encrypted_write32(KEYNUM_match_count, 0, KEYNUM_main_pin,
|
|
write_key, (const uint8_t *)buf);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
// ae_stretch_iter()
|
|
//
|
|
// Do on-chip hashing, with lots of iterations.
|
|
//
|
|
// - using HMAC-SHA256 with keys that are known only to the 608a.
|
|
// - rate limiting factor here is communication time w/ 608a, not algos.
|
|
// - caution: result here is not confidential
|
|
// - cost of each iteration, approximately: 8ms
|
|
// - but our time to do each iteration is limited by software SHA256 in ae_pair_unlock
|
|
//
|
|
int
|
|
ae_stretch_iter(const uint8_t start[32], uint8_t end[32], int iterations)
|
|
{
|
|
ASSERT(start != end); // we can't work inplace
|
|
memcpy(end, start, 32);
|
|
|
|
for(int i=0; i<iterations; i++) {
|
|
// must unlock again, because pin_stretch is an auth'd key
|
|
if(ae_pair_unlock()) return -2;
|
|
|
|
int rv = ae_hmac32(KEYNUM_pin_stretch, end, end);
|
|
RET_IF_BAD(rv);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ae_mixin_key()
|
|
//
|
|
// Apply HMAC using secret in chip as a HMAC key, then encrypt
|
|
// the result a little because read in clear over bus.
|
|
//
|
|
int
|
|
ae_mixin_key(uint8_t keynum, const uint8_t start[32], uint8_t end[32])
|
|
{
|
|
ASSERT(start != end); // we can't work inplace
|
|
|
|
if(ae_pair_unlock()) return -1;
|
|
|
|
if(keynum != 0) {
|
|
int rv = ae_hmac32(keynum, start, end);
|
|
RET_IF_BAD(rv);
|
|
} else {
|
|
memset(end, 0, 32);
|
|
}
|
|
|
|
// Final value was just read over bus w/o any protection, but
|
|
// we won't be using that, instead, mix in the pairing secret.
|
|
//
|
|
// Concern: what if mitm gave us some zeros or other known pattern here. We will
|
|
// use the value provided in cleartext[sic--it's not] write back shortly (to test it).
|
|
// Solution: one more SHA256, and to be safe, mixin lots of values!
|
|
|
|
SHA256_CTX ctx;
|
|
|
|
sha256_init(&ctx);
|
|
sha256_update(&ctx, rom_secrets->pairing_secret, 32);
|
|
sha256_update(&ctx, start, 32);
|
|
sha256_update(&ctx, &keynum, 1);
|
|
sha256_update(&ctx, end, 32);
|
|
sha256_final(&ctx, end);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// EOF
|