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.
472 lines
11 KiB
472 lines
11 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
|
|
//
|
|
/*
|
|
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
|
* and is covered by GPLv3 license found in COPYING.
|
|
*
|
|
* SPDX-FileCopyrightText: 2020 Foundation Devices, Inc. <hello@foundationdevices.com>
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include "stm32h7xx_hal.h"
|
|
#include "stm32h7xx_hal_uart.h"
|
|
#include "stm32h7xx_hal_uart_ex.h"
|
|
|
|
#include "utils.h"
|
|
#include "pprng.h"
|
|
#include "sha256.h"
|
|
#include "secrets.h"
|
|
#include "se.h"
|
|
#include "se-config.h"
|
|
|
|
#include "se-atecc608a.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 2020- by Foundati Inc.";
|
|
|
|
// keep this in place.
|
|
#define RET_IF_BAD(rv) do { if(rv) return rv; } while(0)
|
|
|
|
bool se_probe()
|
|
{
|
|
int chk;
|
|
|
|
se_sleep();
|
|
se_wake();
|
|
|
|
// Expect 0x11
|
|
chk = se_read1();
|
|
if (chk != SE_AFTER_WAKE)
|
|
return false;
|
|
se_sleep();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Do Info(p1=2) command, and return result.
|
|
//
|
|
uint16_t se_get_info(void)
|
|
{
|
|
int rc;
|
|
|
|
// not doing error checking here
|
|
se_write(OP_Info, 0x2, 0, NULL, 0);
|
|
// note: always returns 4 bytes, but most are garbage and unused.
|
|
uint8_t tmp[4];
|
|
rc = se_read(tmp, 4);
|
|
se_sleep();
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
return (tmp[0] << 8) | tmp[1];
|
|
}
|
|
|
|
// Load Tempkey with a specific value. Resulting Tempkey cannot be
|
|
// used with many commands/keys, but is needed for signing.
|
|
//
|
|
int se_load_nonce(uint8_t *nonce)
|
|
{
|
|
uint8_t rc;
|
|
|
|
// p1=3
|
|
se_write(OP_Nonce, 3, 0, nonce, 32); // 608a ok
|
|
rc = se_read1();
|
|
se_sleep();
|
|
if (rc != 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
// Sign a message (already digested)
|
|
//
|
|
int se_sign(uint8_t keynum, uint8_t msg_hash[32], uint8_t signature[64])
|
|
{
|
|
int rc;
|
|
|
|
rc = se_load_nonce(msg_hash);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
se_write(OP_Sign, 0x80, keynum, NULL, 0);
|
|
rc = se_read(signature, 64);
|
|
se_sleep();
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// 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 increamenting 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;
|
|
}
|
|
|
|
// Use old SHA256 command from 508A, but with new flags.
|
|
//
|
|
int se_hmac32(uint8_t keynum, uint8_t msg[32], uint8_t digest[32])
|
|
{
|
|
int rc;
|
|
|
|
// Start SHA w/ HMAC setup
|
|
se_write(OP_SHA, 4, keynum, NULL, 0); // 4 = HMAC_Init
|
|
rc = se_read1();
|
|
if (rc != 0)
|
|
return -1;
|
|
|
|
// send the contents to be hashed
|
|
se_write(OP_SHA, (3<<6) | 2, 32, msg, 32); // 2 = Finalize, 3=Place output
|
|
rc = se_read(digest, 32);
|
|
se_sleep();
|
|
return rc;
|
|
}
|
|
|
|
// Return the serial number: it's 9 bytes, altho 3 are fixed.
|
|
//
|
|
int se_get_serial(uint8_t serial[6])
|
|
{
|
|
int rc;
|
|
uint8_t temp[32];
|
|
|
|
se_write(OP_Read, 0x80, 0x0, NULL, 0);
|
|
rc = se_read(temp, 32);
|
|
se_sleep();
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
#if 0
|
|
se_keep_alive();
|
|
#endif
|
|
// 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;
|
|
}
|
|
|
|
|
|
int se_encrypted_read32(int data_slot, int blk,
|
|
int read_kn, const uint8_t read_key[32], uint8_t data[32])
|
|
{
|
|
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[32], 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int se_destroy_key(int keynum)
|
|
{
|
|
int rc;
|
|
uint8_t numin[20];
|
|
|
|
// Load tempkey with a known (random) nonce value
|
|
rng_buffer(numin, sizeof(numin));
|
|
se_write(OP_Nonce, 0, 0, numin, 20);
|
|
|
|
// 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];
|
|
rc = se_read(randout, 32);
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
// do a "DeriveKey" operation, based on that!
|
|
se_write(OP_DeriveKey, 0x00, keynum, NULL, 0);
|
|
rc = se_read1();
|
|
se_sleep();
|
|
if (rc != 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
// 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 se_pair_unlock
|
|
//
|
|
int se_stretch_iter(
|
|
const uint8_t *start,
|
|
uint8_t *end,
|
|
int iterations
|
|
)
|
|
{
|
|
#ifdef FIXME
|
|
ASSERT(start != end); // we can't work inplace
|
|
#endif
|
|
memcpy(end, start, 32);
|
|
|
|
for (int i = 0; i < iterations; i++) {
|
|
// must unlock again, because pin_stretch is an auth'd key
|
|
if (se_pair_unlock())
|
|
return -2;
|
|
|
|
int rv = se_hmac32(KEYNUM_pin_stretch, end, end);
|
|
if (rv < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Apply HMAC using secret in chip as a HMAC key, then encrypt
|
|
// the result a little because read in clear over bus.
|
|
//
|
|
int se_mixin_key(
|
|
uint8_t keynum,
|
|
uint8_t *start,
|
|
uint8_t *end
|
|
)
|
|
{
|
|
int rc;
|
|
|
|
#ifdef FIXME
|
|
ASSERT(start != end); // we can't work in place
|
|
#endif
|
|
rc = se_pair_unlock();
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
if (keynum != 0) {
|
|
rc = se_hmac32(keynum, start, end);
|
|
if (rc < 0)
|
|
return -1;
|
|
} 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;
|
|
}
|
|
|