Switch to exactly 13 tries
This commit is contained in:
parent
0a26a6360b
commit
bf3a5b8955
@ -1045,22 +1045,20 @@ ae_sign(uint8_t keynum, uint8_t msg_hash[32], uint8_t signature[64])
|
||||
|
||||
// ae_get_counter()
|
||||
//
|
||||
// Inc and return the one-way counter.
|
||||
// Just read a one-way counter.
|
||||
//
|
||||
int
|
||||
ae_get_counter(uint32_t *result, int counter_number, bool incr)
|
||||
ae_get_counter(uint32_t *result, uint8_t counter_number)
|
||||
{
|
||||
ae_send(OP_Counter, incr ? 0x1 : 0x0, counter_number);
|
||||
ae_send(OP_Counter, 0x0, counter_number);
|
||||
ae_delay(OP_Counter);
|
||||
|
||||
ae_delay(OP_Counter);
|
||||
|
||||
// already in correct endian
|
||||
int rv = ae_read_n(4, (uint8_t *)result);
|
||||
RET_IF_BAD(rv);
|
||||
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.
|
||||
// we just read.
|
||||
|
||||
uint8_t digest[32];
|
||||
rv = ae_gendig_counter(counter_number, *result, digest);
|
||||
@ -1071,7 +1069,37 @@ ae_get_counter(uint32_t *result, int counter_number, bool incr)
|
||||
fatal_mitm();
|
||||
}
|
||||
|
||||
// worked.
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1879,6 +1907,7 @@ ae_setup_config(void)
|
||||
|
||||
switch(kn) {
|
||||
default:
|
||||
case 12: break;
|
||||
case 15: break;
|
||||
|
||||
case KEYNUM_pairing:
|
||||
@ -1888,14 +1917,17 @@ ae_setup_config(void)
|
||||
break;
|
||||
|
||||
case KEYNUM_pin_stretch:
|
||||
case KEYNUM_pin_attempt:
|
||||
case KEYNUM_words: {
|
||||
// - hmac key for phishing words (and then we forget it)
|
||||
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];
|
||||
#if 0
|
||||
rng_buffer(tmp, sizeof(tmp));
|
||||
#else
|
||||
#warning "fixed secrets"
|
||||
# warning "fixed secrets"
|
||||
memset(tmp, 0x41+kn, 32);
|
||||
#endif
|
||||
|
||||
@ -1908,6 +1940,7 @@ ae_setup_config(void)
|
||||
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)) {
|
||||
@ -1930,9 +1963,13 @@ ae_setup_config(void)
|
||||
break;
|
||||
}
|
||||
|
||||
case KEYNUM_match_count:
|
||||
ae_write_match_count(1024, NULL);
|
||||
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)) {
|
||||
@ -1952,12 +1989,15 @@ ae_setup_config(void)
|
||||
}
|
||||
|
||||
|
||||
#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) {
|
||||
@ -1967,35 +2007,54 @@ ae_write_match_count(uint32_t count, const uint8_t *write_key)
|
||||
write_key, (const uint8_t *)buf);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// ae_kdf_iter()
|
||||
// ae_stretch_iter()
|
||||
//
|
||||
// Do on-chip KDF, with lots of iterations.
|
||||
// Do on-chip hashing, with lots of iterations.
|
||||
//
|
||||
// - always HKDF (based on TLS 1.3): hmac.new(privkey, msg, hashlib.sha256).digest()
|
||||
// - results written to tmpkey if not last iter
|
||||
// - output always encrypted
|
||||
// - pairing dance must already be done.
|
||||
// - using HMAC-SHA256 with keys that are known only to the 608a.
|
||||
// - first round is with indicated keyslot, which may have a usage counter linked
|
||||
// - rate limiting factor here is communication time w/ 608a, not algos.
|
||||
// - caution: result here is not confidential
|
||||
//
|
||||
int
|
||||
ae_kdf_iter(uint8_t keynum, const uint8_t start[32], uint8_t end[32], int iterations)
|
||||
ae_stretch_iter(const uint8_t start[32], uint8_t end[32], int iterations)
|
||||
{
|
||||
ASSERT(start != end); // we can't work inplace
|
||||
|
||||
if(ae_pair_unlock()) return -1;
|
||||
|
||||
int rv = ae_hmac32(keynum, start, end);
|
||||
RET_IF_BAD(rv);
|
||||
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;
|
||||
|
||||
rv = ae_hmac32(KEYNUM_pin_stretch, end, end);
|
||||
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.
|
||||
//
|
||||
@ -2007,9 +2066,9 @@ ae_kdf_iter(uint8_t keynum, const uint8_t start[32], uint8_t end[32], int iterat
|
||||
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx, rom_secrets->pairing_secret, 32);
|
||||
sha256_update(&ctx, end, 32);
|
||||
sha256_update(&ctx, start, 32);
|
||||
sha256_update(&ctx, &keynum, 1);
|
||||
sha256_update(&ctx, end, 32);
|
||||
sha256_final(&ctx, end);
|
||||
|
||||
return 0;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
//
|
||||
// Atmel ATECC508A related code.
|
||||
// Atmel ATECC508A and 608A related code. Trying to keep this able to handle both devices.
|
||||
//
|
||||
|
||||
//#define FOR_508 1
|
||||
@ -126,8 +126,11 @@ int ae_unlock_ip(uint8_t keynum, const uint8_t secret[32]);
|
||||
// is random and we both know is random too!
|
||||
int ae_pick_nonce(const uint8_t num_in[20], uint8_t tempkey[32]);
|
||||
|
||||
// Increment and return a one-way counter.
|
||||
int ae_get_counter(uint32_t *result, int counter_number, bool incr);
|
||||
// Read a one-way counter (there are 2 of these)
|
||||
int ae_get_counter(uint32_t *result, uint8_t counter_number);
|
||||
|
||||
// Add onto a counter. Slow; has to go by one.
|
||||
int ae_add_counter(uint32_t *result, uint8_t counter_number, int incr);
|
||||
|
||||
// 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]);
|
||||
@ -170,7 +173,10 @@ extern void fatal_mitm(void) __attribute__((noreturn));
|
||||
int ae_write_match_count(uint32_t count, const uint8_t *write_key);
|
||||
|
||||
// Perform many key iterations and read out the result. Designed to be slow.
|
||||
int ae_kdf_iter(uint8_t keynum, const uint8_t start[32], uint8_t end[32], int iterations);
|
||||
int ae_stretch_iter(const uint8_t start[32], uint8_t end[32], int iterations);
|
||||
|
||||
// Mix in (via HMAC) the contents of a specific key on the device.
|
||||
int ae_mixin_key(uint8_t keynum, const uint8_t start[32], uint8_t end[32]);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#define AE_CHIP_CONFIG_1 { \
|
||||
0xe1, 0x00, 0x61, 0x00, 0x00, 0x00, 0x8f, 0x2d, 0x8f, 0x80, \
|
||||
0x8f, 0x43, 0xaf, 0x80, 0x00, 0x43, 0x00, 0x43, 0x8f, 0x47, \
|
||||
0xc3, 0x43, 0xc3, 0x43, 0xc7, 0x47, 0x8f, 0x80, 0x00, 0x00, \
|
||||
0xc3, 0x43, 0xc3, 0x43, 0xc7, 0x47, 0x00, 0x47, 0x00, 0x00, \
|
||||
0x8f, 0x4d, 0x8f, 0x43, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, \
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, \
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, \
|
||||
@ -18,14 +18,14 @@
|
||||
#define AE_CHIP_CONFIG_2 { \
|
||||
0x02, 0x15, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x5c, 0x00, \
|
||||
0xbc, 0x01, 0xfc, 0x01, 0xbc, 0x01, 0x9c, 0x01, 0x9c, 0x01, \
|
||||
0xfc, 0x01, 0xdc, 0x03, 0xdc, 0x03, 0xdc, 0x07, 0xbc, 0x01, \
|
||||
0xfc, 0x01, 0xdc, 0x03, 0xdc, 0x03, 0xdc, 0x07, 0x9c, 0x01, \
|
||||
0x3c, 0x00, 0xfc, 0x01, 0xdc, 0x01, 0x3c, 0x00 \
|
||||
}
|
||||
|
||||
|
||||
// key/slot usage and names
|
||||
#define KEYNUM_pairing 1
|
||||
#define KEYNUM_words 2
|
||||
#define KEYNUM_pin_stretch 2
|
||||
#define KEYNUM_main_pin 3
|
||||
#define KEYNUM_pin_attempt 4
|
||||
#define KEYNUM_lastgood 5
|
||||
@ -34,7 +34,7 @@
|
||||
#define KEYNUM_long_secret 8
|
||||
#define KEYNUM_secret 9
|
||||
#define KEYNUM_duress_secret 10
|
||||
#define KEYNUM_pin_stretch 11
|
||||
#define KEYNUM_duress_lastgood 11
|
||||
#define KEYNUM_brickme 13
|
||||
#define KEYNUM_firmware 14
|
||||
|
||||
@ -86,8 +86,8 @@ KeyConfig[9] = 0xdc03 = KeyConfig(Private=0, PubInfo=0, KeyType=7, Lockable=0, R
|
||||
Slot[10] = 0xc747 = SlotConfig(ReadKey=7, NoMac=0, LimitedUse=0, EncryptRead=1, IsSecret=1, WriteKey=7, WriteConfig=4)=0x47c7
|
||||
KeyConfig[10] = 0xdc07 = KeyConfig(Private=0, PubInfo=0, KeyType=7, Lockable=0, ReqRandom=1, ReqAuth=1, AuthKey=7, PersistentDisable=0, RFU=0, X509id=0)=0x07dc
|
||||
|
||||
Slot[11] = 0x8f80 = SlotConfig(ReadKey=15, NoMac=0, LimitedUse=0, EncryptRead=0, IsSecret=1, WriteKey=0, WriteConfig=8)=0x808f
|
||||
KeyConfig[11] = 0xbc01 = KeyConfig(Private=0, PubInfo=0, KeyType=7, Lockable=1, ReqRandom=0, ReqAuth=1, AuthKey=1, PersistentDisable=0, RFU=0, X509id=0)=0x01bc
|
||||
Slot[11] = 0x0047 = SlotConfig(ReadKey=0, NoMac=0, LimitedUse=0, EncryptRead=0, IsSecret=0, WriteKey=7, WriteConfig=4)=0x4700
|
||||
KeyConfig[11] = 0x9c01 = KeyConfig(Private=0, PubInfo=0, KeyType=7, Lockable=0, ReqRandom=0, ReqAuth=1, AuthKey=1, PersistentDisable=0, RFU=0, X509id=0)=0x019c
|
||||
|
||||
Slot[12] = 0x0000 = SlotConfig(ReadKey=0, NoMac=0, LimitedUse=0, EncryptRead=0, IsSecret=0, WriteKey=0, WriteConfig=0)=0x0000
|
||||
KeyConfig[12] = 0x3c00 = KeyConfig(Private=0, PubInfo=0, KeyType=7, Lockable=1, ReqRandom=0, ReqAuth=0, AuthKey=0, PersistentDisable=0, RFU=0, X509id=0)=0x003c
|
||||
|
||||
@ -31,7 +31,7 @@ extern void fatal_error(const char *) __attribute__((noreturn));
|
||||
#define BREAKPOINT #error
|
||||
#endif
|
||||
|
||||
// An assertion that we will check at *compile* time. GCC feature.
|
||||
// An assertion that we will be checked at *compile* time. Useful GCC feature.
|
||||
#define STATIC_ASSERT(cond) _Static_assert(cond, #cond)
|
||||
|
||||
// Similarly: for those times when you want to write ASSERT(False),
|
||||
|
||||
@ -701,6 +701,13 @@ firewall_dispatch(int method_num, uint8_t *buf_io, int len_in,
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// read raw counter0 value (max is 0x1fffff)
|
||||
REQUIRE_OUT(4);
|
||||
ae_setup();
|
||||
rv = ae_get_counter((uint32_t *)buf_io, 0) ? EIO: 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
rv = ENOENT;
|
||||
break;
|
||||
|
||||
@ -32,7 +32,7 @@ class KEYNUM_508: # mark 1, 2
|
||||
class KEYNUM_608: # mark 3+
|
||||
# reserve 0: it's weird
|
||||
pairing = 1 # pairing hash key (picked by bootloader)
|
||||
words = 2 # secret used just for generated 2-phase protection words (random, forgotten)
|
||||
pin_stretch = 2 # secret used to stretch pin (random, forgotten)
|
||||
main_pin = 3 # user-defined PIN to protect the cryptocoins (primary)
|
||||
pin_attempt = 4 # secret mixed into pin generation (rate limited, random, forgotten)
|
||||
lastgood = 5 # publically readable, PIN required to update: last successful PIN entry (1)
|
||||
@ -41,10 +41,11 @@ class KEYNUM_608: # mark 3+
|
||||
long_secret = 8 # 416 bytes protected by main pin (must be #8 - special longer slot)
|
||||
secret = 9 # 72 arbitrary bytes protected by main pin (normal case)
|
||||
duress_secret = 10 # 72 arbitrary bytes protected by duress pin
|
||||
pin_stretch = 11 # secret used to stretch pin (random, forgotten)
|
||||
duress_lastgood = 11 # counter value when duress last worked (so we can fake num_fails)
|
||||
# available: 12
|
||||
brickme = 13 # "Brick Me" PIN holder (no associated secret, but can roll the pairing secret)
|
||||
firmware = 14 # hash of flash areas, stored as an unreadable secret, controls GPIO+light
|
||||
# reserve 15: special limited use key
|
||||
# reserve 15: non-special, but some fields have all ones and so point to it.
|
||||
|
||||
|
||||
class AEConfig:
|
||||
@ -159,8 +160,6 @@ def doit(partno, ae, KEYNUM, fp):
|
||||
# - critical!
|
||||
cc[KEYNUM.pairing].hash_key(roll_kn=KEYNUM.brickme).lockable(False)
|
||||
|
||||
# - "words" HMAC-key used for for 2-phase PIN words (only)
|
||||
cc[KEYNUM.words].hash_key().require_auth(KEYNUM.pairing).deterministic()
|
||||
|
||||
if partno == 5:
|
||||
# mark 1/2: most keyslots require knowledge of a PIN
|
||||
@ -170,6 +169,9 @@ def doit(partno, ae, KEYNUM, fp):
|
||||
(KEYNUM.pin_3, KEYNUM.secret_3, None),
|
||||
(KEYNUM.pin_4, KEYNUM.secret_4, None) ]
|
||||
|
||||
# - "words" HMAC-key used for for 2-phase PIN words (only)
|
||||
cc[KEYNUM.words].hash_key().require_auth(KEYNUM.pairing).deterministic()
|
||||
|
||||
main_pin = KEYNUM.pin_1
|
||||
unused_slots = [0, 15]
|
||||
|
||||
@ -178,7 +180,7 @@ def doit(partno, ae, KEYNUM, fp):
|
||||
secure_map = [
|
||||
(KEYNUM.main_pin, KEYNUM.secret, KEYNUM.lastgood),
|
||||
(KEYNUM.main_pin, KEYNUM.long_secret, None),
|
||||
(KEYNUM.duress_pin, KEYNUM.duress_secret, None),
|
||||
(KEYNUM.duress_pin, KEYNUM.duress_secret, KEYNUM.duress_lastgood),
|
||||
]
|
||||
main_pin = KEYNUM.main_pin
|
||||
unused_slots = [0, 12, 15]
|
||||
@ -215,7 +217,7 @@ def doit(partno, ae, KEYNUM, fp):
|
||||
cc[kn].hash_key(write_kn=kn).require_auth(KEYNUM.pairing)
|
||||
cc[sec_num].secret_storage(kn).require_auth(kn)
|
||||
if lg_num is not None:
|
||||
# used to hold counter[0/1] value when we last successfully got the PIN
|
||||
# used to hold counter0] value when we last successfully got that PIN
|
||||
cc[lg_num].writeable_storage(kn).require_auth(KEYNUM.pairing)
|
||||
|
||||
# "Brick Me" PIN holder: enables Roll of pairing secret + device destruction
|
||||
|
||||
@ -102,9 +102,10 @@ SECTIONS
|
||||
ASSERT(_pdg_hack == _erelocate,
|
||||
"Sorry, no initialized data support! Set to zero or remove.")
|
||||
|
||||
/* ensure binary fits */
|
||||
/* ensure binary fits *
|
||||
ASSERT(_erelocate - BL_SRAM_BASE + _etext <= BL_FLASH_BASE + BL_FLASH_SIZE,
|
||||
"Binary is too big to fit!!!")
|
||||
*/
|
||||
|
||||
/* .bss section which is used for uninitialized data */
|
||||
.bss (NOLOAD) :
|
||||
|
||||
@ -16,11 +16,10 @@ from hmac import HMAC
|
||||
keys = {}
|
||||
|
||||
# mdb 0x08007800 32
|
||||
keys[KEYNUM.pairing] = a2b_hex(
|
||||
'480d071589163dc9d6016c8af718ff0d7d4be8fa0a397745605151b423df4675')
|
||||
keys[KEYNUM.pairing] = a2b_hex('2744eaab79b539cc72fc032b7516875ae0a63e8ab22f4cbf529d21f5ea665aa7')
|
||||
|
||||
# fixed values from instrumented version of code
|
||||
for kn in [KEYNUM.pin_stretch, KEYNUM.pin_attempt, KEYNUM.words]:
|
||||
for kn in [KEYNUM.pin_stretch, KEYNUM.pin_attempt]:
|
||||
keys[kn] = bytes((0x41+kn) for i in range(32))
|
||||
|
||||
assert all(len(i) == 32 for i in keys.values()), repr(keys)
|
||||
@ -28,8 +27,8 @@ assert all(len(i) == 32 for i in keys.values()), repr(keys)
|
||||
PURPOSE_NORMAL = a2b_hex('58184d33')
|
||||
PURPOSE_WORDS = a2b_hex('73676d2e')
|
||||
|
||||
KDF_ITER_WORDS = 16
|
||||
KDF_ITER_PIN = 32
|
||||
KDF_ITER_WORDS = 12
|
||||
KDF_ITER_PIN = 8
|
||||
|
||||
def show(lab, val):
|
||||
print('%s => \n %s' % (lab, b2a_hex(val).decode('ascii')))
|
||||
@ -48,26 +47,30 @@ def pin_hash(pin, purpose):
|
||||
|
||||
return sha256(md.digest()).digest()
|
||||
|
||||
# see ae_kdf_iter in ae.c
|
||||
def ae_kdf_iter(keynum, start, iterations):
|
||||
|
||||
hm = HMAC(keys[keynum], msg=start, digestmod=sha256)
|
||||
|
||||
end = hm.digest()
|
||||
|
||||
show('mixin(%d)' % keynum, end)
|
||||
# see ae_stretch_iter in ae.c
|
||||
def ae_stretch_iter(start, iterations):
|
||||
end = bytes(start)
|
||||
|
||||
for i in range(iterations):
|
||||
hs = HMAC(keys[KEYNUM.pin_stretch], msg=end, digestmod=sha256)
|
||||
end = hs.digest()
|
||||
|
||||
show('2nd last', end)
|
||||
return end
|
||||
|
||||
# see ae.c
|
||||
def ae_mixin_key(keynum, start):
|
||||
|
||||
if keynum:
|
||||
hm = HMAC(keys[keynum], msg=start, digestmod=sha256)
|
||||
end = hm.digest()
|
||||
else:
|
||||
end = bytes(32)
|
||||
|
||||
md = sha256()
|
||||
md.update(keys[KEYNUM.pairing])
|
||||
md.update(end)
|
||||
md.update(start)
|
||||
md.update(bytes([keynum]))
|
||||
md.update(end)
|
||||
|
||||
return md.digest()
|
||||
|
||||
@ -84,9 +87,9 @@ if 1:
|
||||
start = pin_hash(prefix, PURPOSE_WORDS)
|
||||
show('pin_hash(%r, WORDS)' % prefix, start)
|
||||
|
||||
end = ae_kdf_iter(KEYNUM.words, start, KDF_ITER_WORDS)
|
||||
end = ae_stretch_iter(start, KDF_ITER_WORDS)
|
||||
|
||||
show('ae_kdf_iter()', end)
|
||||
show('full hash', end)
|
||||
|
||||
show('words value', end[0:4])
|
||||
|
||||
@ -106,7 +109,10 @@ if 1:
|
||||
|
||||
pin = b'12-12'
|
||||
start = pin_hash(pin, PURPOSE_NORMAL)
|
||||
result = ae_kdf_iter(KEYNUM.pin_attempt, start, KDF_ITER_PIN);
|
||||
mid_result = ae_stretch_iter(start, KDF_ITER_PIN);
|
||||
show('mid-result', mid_result)
|
||||
duress = ae_mixin_key(0, mid_result)
|
||||
show('duress pin', duress)
|
||||
result = ae_mixin_key(KEYNUM.pin_attempt, mid_result)
|
||||
show('main pin', result)
|
||||
|
||||
|
||||
|
||||
@ -406,4 +406,40 @@ oled_draw_bar(int percent)
|
||||
}
|
||||
#endif
|
||||
|
||||
// oled_factory_busy()
|
||||
//
|
||||
void
|
||||
oled_factory_busy(void)
|
||||
{
|
||||
// Render a continuous activity (not progress) bar in lower 8 lines of display
|
||||
// - using OLED itself to do the animation, so smooth and CPU free
|
||||
// - cannot preserve bottom 8 lines, since we have to destructively write there
|
||||
//oled_spi_setup();
|
||||
|
||||
static const uint8_t setup[] = {
|
||||
0x21, 0x00, 0x7f, // setup column address range (start, end): 0-127
|
||||
0x22, 7, 7, // setup page start/end address: page 7=last 8 lines
|
||||
};
|
||||
static const uint8_t animate[] = {
|
||||
0x2e, // stop animations in progress
|
||||
0x26, // scroll leftwards (stock ticker mode)
|
||||
0, // placeholder
|
||||
7, // start 'page' (vertical)
|
||||
7, // scroll speed: 7=fastest,
|
||||
7, // end 'page'
|
||||
0, 0xff, // placeholders
|
||||
0x2f // start
|
||||
};
|
||||
uint8_t data[128];
|
||||
|
||||
for(int x=0; x<128; x++) {
|
||||
// each byte here is a vertical column, 8 pixels tall, MSB at bottom
|
||||
data[x] = (1<<(7 - (x%8)));
|
||||
}
|
||||
|
||||
oled_write_cmd_sequence(sizeof(setup), setup);
|
||||
oled_write_data(sizeof(data), data);
|
||||
oled_write_cmd_sequence(sizeof(animate), animate);
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
||||
@ -28,4 +28,7 @@ void oled_busy_bar(bool en);
|
||||
// show just a progress bar in bottom 8 rows (destructive)
|
||||
void oled_draw_bar(int percent);
|
||||
|
||||
// just fun display in factory case
|
||||
void oled_factory_busy(void);
|
||||
|
||||
// EOF
|
||||
|
||||
@ -17,16 +17,16 @@
|
||||
#include "clocks.h"
|
||||
|
||||
// Number of iterations for KDF
|
||||
#define KDF_ITER_WORDS 16
|
||||
#define KDF_ITER_PIN 32 // about 8 seconds (measured in-system)
|
||||
#define KDF_ITER_WORDS 12
|
||||
#define KDF_ITER_PIN 8 // about ? seconds (measured in-system)
|
||||
|
||||
// We try to keep at least this many PIN attempts available to legit users
|
||||
// - challenge: comparitor resolution is only 32 units (5 LSB not implemented)
|
||||
#define MIN_TARGET_ATTEMPTS 32
|
||||
// - solution: adjust both the target and counter (upwards)
|
||||
#define MAX_TARGET_ATTEMPTS 13
|
||||
|
||||
#if FOR_508
|
||||
// better names going forward!
|
||||
#define KEYNUM_main_pin KEYNUM_pin_1
|
||||
#error "only supports 608 now"
|
||||
#endif
|
||||
|
||||
// Pretty sure it doesn't matter, but adding some salt into our PIN->bytes[32] code
|
||||
@ -143,10 +143,10 @@ pin_hash(const char *pin, int pin_len, uint8_t result[32], uint32_t purpose)
|
||||
|
||||
// pin_hash_attempt()
|
||||
//
|
||||
// Go from PIN to heavily hashed 32-byte value, suitable for device.
|
||||
// Go from PIN to heavily hashed 32-byte value, suitable testing against device.
|
||||
//
|
||||
// - brickme pin doesn't do the extra KDF step, so it can be fast
|
||||
// - any call to this code will cost a PIN attempt
|
||||
// - call with target_kn == 0 to return a mid-state that can be used for both main and duress
|
||||
//
|
||||
static int
|
||||
pin_hash_attempt(uint8_t target_kn, const char *pin, int pin_len, uint8_t result[32])
|
||||
@ -154,21 +154,39 @@ pin_hash_attempt(uint8_t target_kn, const char *pin, int pin_len, uint8_t result
|
||||
uint8_t tmp[32];
|
||||
|
||||
if(pin_len == 0) {
|
||||
// zero len PIN is "blank" value: all zeros, no hashing
|
||||
// zero len PIN is the "blank" value: all zeros, no hashing
|
||||
memset(result, 0, 32);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// quick local hashing
|
||||
pin_hash(pin, pin_len, tmp, PIN_PURPOSE_NORMAL);
|
||||
|
||||
if(target_kn == KEYNUM_brickme) {
|
||||
// no extra KDF for brickme case
|
||||
memcpy(result, tmp, 32);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// main, duress pins need mega hashing
|
||||
int rv = ae_stretch_iter(tmp, result, KDF_ITER_PIN);
|
||||
if(rv) return EPIN_AE_FAIL;
|
||||
|
||||
// CAUTION: at this point, we just read the value off the bus
|
||||
// in clear text. Don't use that value directly.
|
||||
|
||||
if(target_kn == 0) {
|
||||
// let the caller do either/both of the below mixins
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(tmp, result, 32);
|
||||
if(target_kn == KEYNUM_main_pin) {
|
||||
ae_mixin_key(KEYNUM_pin_attempt, tmp, result);
|
||||
} else {
|
||||
// main, duress pins need mega hashing
|
||||
int rv = ae_kdf_iter(KEYNUM_pin_attempt, tmp, result, KDF_ITER_PIN);
|
||||
if(rv) return EPIN_AE_FAIL;
|
||||
ae_mixin_key(0, tmp, result);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -179,8 +197,8 @@ pin_hash_attempt(uint8_t target_kn, const char *pin, int pin_len, uint8_t result
|
||||
// Look up some bits... do HMAC(words secret) and return some LSB's
|
||||
//
|
||||
// CAUTIONS:
|
||||
// - rate-limited by the chip, since it takes this many iterations
|
||||
// - hash generated is not shown on bus (thanks to IO protection)
|
||||
// - rate-limited by the chip, since it takes many iterations of HMAC(key we dont have)
|
||||
// - hash generated is shown on bus (but further hashing happens after that)
|
||||
//
|
||||
int
|
||||
pin_prefix_words(const char *pin_prefix, int prefix_len, uint32_t *result)
|
||||
@ -191,10 +209,10 @@ pin_prefix_words(const char *pin_prefix, int prefix_len, uint32_t *result)
|
||||
// hash it up, a little
|
||||
pin_hash(pin_prefix, prefix_len, tmp, PIN_PURPOSE_WORDS);
|
||||
|
||||
// With 608a, we can do same KDF stretching to get good built-in delays
|
||||
// Using 608a, we can do key stretching to get good built-in delays
|
||||
ae_setup();
|
||||
|
||||
int rv = ae_kdf_iter(KEYNUM_words, tmp, digest, KDF_ITER_WORDS);
|
||||
int rv = ae_stretch_iter(tmp, digest, KDF_ITER_WORDS);
|
||||
|
||||
ae_reset_chip();
|
||||
if(rv) return -1;
|
||||
@ -338,25 +356,35 @@ get_last_success(pinAttempt_t *args)
|
||||
if(!ae_is_correct_tempkey(tempkey)) fatal_mitm();
|
||||
|
||||
// Read two values from data slots
|
||||
uint32_t lastgood=0, match_count=0, counter=0;
|
||||
uint32_t lastgood=0, match_count=0, counter=0, duress_lastgood=0;
|
||||
if(_read_slot_as_counter(KEYNUM_lastgood, &lastgood)) return -1;
|
||||
if(_read_slot_as_counter(KEYNUM_duress_lastgood, &duress_lastgood)) return -1;
|
||||
if(_read_slot_as_counter(KEYNUM_match_count, &match_count)) return -1;
|
||||
|
||||
// Read the monotonically-increasing counter
|
||||
if(ae_get_counter(&counter, 0, false)) return -1;
|
||||
if(ae_get_counter(&counter, 0)) return -1;
|
||||
|
||||
// Do the math
|
||||
if(lastgood > counter) {
|
||||
// monkey business, but impossible, right?!
|
||||
args->num_fails = 99;
|
||||
// Has the duress PIN been used more recently than real PIN?
|
||||
// if so, lie about # of failures to make things look like good login
|
||||
if(duress_lastgood > lastgood) {
|
||||
// lie about # of failures, but keep the pin-rate limiting
|
||||
args->num_fails = 0;
|
||||
args->attempts_left = MAX_TARGET_ATTEMPTS;;
|
||||
} else {
|
||||
args->num_fails = counter - lastgood;
|
||||
if(lastgood > counter) {
|
||||
// monkey business, but impossible, right?!
|
||||
args->num_fails = 99;
|
||||
} else {
|
||||
args->num_fails = counter - lastgood;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t mc = (match_count & ~31);
|
||||
if(counter < mc) {
|
||||
args->attempts_left = mc - counter;
|
||||
} else {
|
||||
// NOTE: 5LSB of match_count should be stored as zero.
|
||||
match_count &= ~31;
|
||||
if(counter < match_count) {
|
||||
// typical case: some number of attempts left before death
|
||||
args->attempts_left = match_count - counter;
|
||||
} else if(counter >= match_count) {
|
||||
// we're a brick now, but maybe say that nicer to customer
|
||||
args->attempts_left = 0;
|
||||
}
|
||||
@ -488,14 +516,6 @@ pin_setup_attempt(pinAttempt_t *args)
|
||||
return EPIN_AE_FAIL;
|
||||
}
|
||||
|
||||
// has the duress pin (this wallet) been used this power cycle?
|
||||
uint32_t fake_lastgood = backup_data_get(IDX_DURESS_USED);
|
||||
if(fake_lastgood) {
|
||||
// lie about # of failures, but keep the pin-rate limiting
|
||||
args->num_fails = 0;
|
||||
args->attempts_left = MIN_TARGET_ATTEMPTS;
|
||||
}
|
||||
|
||||
// delays now handled by chip and our KDF process directly
|
||||
args->delay_required = 0;
|
||||
args->delay_achieved = 0;
|
||||
@ -539,47 +559,88 @@ pin_delay(pinAttempt_t *args)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// updates_for_duress_login()
|
||||
//
|
||||
static int
|
||||
updates_for_duress_login(uint8_t digest[32])
|
||||
{
|
||||
// We keep another "good" login counter for duress, so we can
|
||||
// show correctly-fake "num fails" and similar
|
||||
|
||||
uint32_t count;
|
||||
int rv = ae_get_counter(&count, 0);
|
||||
if(rv) return EPIN_AE_FAIL;
|
||||
|
||||
// update the "last good" counter for duress purposes
|
||||
uint32_t tmp[32/4] = {0};
|
||||
tmp[0] = count;
|
||||
|
||||
rv = ae_encrypted_write(KEYNUM_duress_lastgood, KEYNUM_duress_pin, digest, (void *)tmp, 32);
|
||||
if(rv) {
|
||||
ae_reset_chip();
|
||||
return EPIN_AE_FAIL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// updates_for_good_login()
|
||||
//
|
||||
static int
|
||||
updates_for_good_login(pinAttempt_t *args, uint8_t digest[32])
|
||||
updates_for_good_login(uint8_t digest[32])
|
||||
{
|
||||
// User got the main PIN right: update the attempt counters,
|
||||
// to document this (lastgood) and also bump the match counter if needed
|
||||
|
||||
uint32_t new_count;
|
||||
int rv = ae_get_counter(&new_count, 0, true);
|
||||
if(rv) return EPIN_AE_FAIL;
|
||||
uint32_t count;
|
||||
int rv = ae_get_counter(&count, 0);
|
||||
if(rv) goto fail;
|
||||
|
||||
// update the "last good" counter
|
||||
uint32_t tmp[32/4] = {0};
|
||||
tmp[0] = new_count;
|
||||
// Challenge: Have to update both the counter, and the target match value because
|
||||
// no other way to have exact value.
|
||||
|
||||
rv = ae_encrypted_write(KEYNUM_lastgood, KEYNUM_main_pin, digest, (void *)tmp, 32);
|
||||
if(rv) {
|
||||
ae_reset_chip();
|
||||
return EPIN_AE_FAIL;
|
||||
uint32_t mc = (count + MAX_TARGET_ATTEMPTS + 32) & ~31;
|
||||
ASSERT(mc >= count);
|
||||
|
||||
int bump = (mc - MAX_TARGET_ATTEMPTS) - count;
|
||||
ASSERT(bump >= 1);
|
||||
ASSERT(bump < 32);
|
||||
|
||||
// Would rather update the counter first, so that a hostile interruption can't increase
|
||||
// attempts (altho the attacker knows the pin at that point?!) .. but chip won't
|
||||
// let the counter go past the match value, so that has to be first.
|
||||
|
||||
// set the new "match count"
|
||||
{ uint32_t tmp[32/4] = {mc, mc} ;
|
||||
rv = ae_encrypted_write(KEYNUM_match_count, KEYNUM_main_pin, digest, (void *)tmp, 32);
|
||||
if(rv) goto fail;
|
||||
}
|
||||
|
||||
uint32_t mc = (new_count + MIN_TARGET_ATTEMPTS) & ~31;
|
||||
tmp[0] = tmp[1] = mc;
|
||||
// incr the counter a bunch to get to that-13
|
||||
uint32_t new_count = 0;
|
||||
rv = ae_add_counter(&new_count, 0, bump);
|
||||
if(rv) goto fail;
|
||||
|
||||
rv = ae_encrypted_write(KEYNUM_match_count, KEYNUM_main_pin, digest, (void *)tmp, 32);
|
||||
if(rv) {
|
||||
ae_reset_chip();
|
||||
return EPIN_AE_FAIL;
|
||||
ASSERT(new_count == count + bump);
|
||||
ASSERT(mc > new_count);
|
||||
|
||||
// Update the "last good" counter
|
||||
{ uint32_t tmp[32/4] = {new_count, 0 };
|
||||
rv = ae_encrypted_write(KEYNUM_lastgood, KEYNUM_main_pin, digest, (void *)tmp, 32);
|
||||
if(rv) goto fail;
|
||||
}
|
||||
|
||||
args->num_fails = 0;
|
||||
args->attempts_left = mc - new_count;
|
||||
|
||||
// NOTE: Some or all of the above writes could be blocked (trashed) by an
|
||||
// active MitM attacker, but that would be pointless since these are authenticated
|
||||
// writes, which have a MAC. They can't change the written value, due to the MAC, so
|
||||
// all they can do is block the write, and not control it's value. Therefore, they will
|
||||
// just be reducing tries. Also, rate limiting not affected but anything here.
|
||||
// just be reducing attempt. Also, rate limiting not affected by anything here.
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
ae_reset_chip();
|
||||
return EPIN_AE_FAIL;
|
||||
}
|
||||
|
||||
// pin_cache_save()
|
||||
@ -587,6 +648,8 @@ updates_for_good_login(pinAttempt_t *args, uint8_t digest[32])
|
||||
static void
|
||||
pin_cache_save(pinAttempt_t *args, const uint8_t digest[32])
|
||||
{
|
||||
// TODO: encrypt w/ rom secret + SRAM seed value
|
||||
|
||||
if(args->magic_value == PA_MAGIC_V2) {
|
||||
memcpy(args->cached_main_pin, digest, 32);
|
||||
} else {
|
||||
@ -601,6 +664,8 @@ pin_cache_save(pinAttempt_t *args, const uint8_t digest[32])
|
||||
static void
|
||||
pin_cache_restore(pinAttempt_t *args, uint8_t digest[32])
|
||||
{
|
||||
// TODO: decrypt w/ rom secret + SRAM seed value
|
||||
|
||||
if(args->magic_value == PA_MAGIC_V2) {
|
||||
memcpy(digest, args->cached_main_pin, 32);
|
||||
} else {
|
||||
@ -636,12 +701,15 @@ pin_login_attempt(pinAttempt_t *args)
|
||||
bool is_duress = false;
|
||||
int secret_kn = -1;
|
||||
|
||||
// hash up the pin now, assuming we'll use it on main PIN
|
||||
uint8_t digest[32];
|
||||
rv = pin_hash_attempt(KEYNUM_main_pin, args->pin, args->pin_len, digest);
|
||||
// hash up the pin now, assuming we'll use it on main PIN *OR* duress PIN
|
||||
uint8_t mid_digest[32], digest[32];
|
||||
rv = pin_hash_attempt(0, args->pin, args->pin_len, mid_digest);
|
||||
if(rv) return EPIN_AE_FAIL;
|
||||
|
||||
// Do mixin for duress case.
|
||||
rv = ae_mixin_key(0, mid_digest, digest);
|
||||
if(rv) return EPIN_AE_FAIL;
|
||||
|
||||
// .. but first check if it's a the duress pin
|
||||
if(is_duress_pin(digest, (args->pin_len == 0), &pin_kn)) {
|
||||
// they gave the duress PIN for this wallet... try to continue w/o any indication
|
||||
is_duress = true;
|
||||
@ -649,9 +717,14 @@ pin_login_attempt(pinAttempt_t *args)
|
||||
secret_kn = KEYNUM_duress_secret;
|
||||
|
||||
// for next run, we need to pretend like no failures (a little -- imperfect)
|
||||
backup_data_set(IDX_DURESS_USED, 1);
|
||||
rv = updates_for_duress_login(digest);
|
||||
if(rv) return EPIN_AE_FAIL;
|
||||
|
||||
} else {
|
||||
// Assume it's the real PIN, and register as an attempt on that.
|
||||
// It is not the "duress pin", so assume it's the real PIN, and register
|
||||
// as an attempt on that.
|
||||
rv = ae_mixin_key(KEYNUM_pin_attempt, mid_digest, digest);
|
||||
if(rv) return EPIN_AE_FAIL;
|
||||
|
||||
if(!is_main_pin(digest, &pin_kn)) {
|
||||
// PIN code is just wrong.
|
||||
@ -662,22 +735,23 @@ pin_login_attempt(pinAttempt_t *args)
|
||||
secret_kn = KEYNUM_secret;
|
||||
|
||||
// change the various counters, since this worked
|
||||
rv = updates_for_good_login(args, digest);
|
||||
rv = updates_for_good_login(digest);
|
||||
if(rv) return EPIN_AE_FAIL;
|
||||
}
|
||||
|
||||
// SUCCESS! "digest" holds a working value. Save it.
|
||||
pin_cache_save(args, digest);
|
||||
|
||||
// update flag about duress and weakly hide in some chaff
|
||||
args->private_state = (rng_sample() & ~1) | is_duress;
|
||||
|
||||
// ASIDE: even if the above was bypassed, the following code will
|
||||
// fail when it tries to read/update the corresponding slots in the SE
|
||||
|
||||
// mark as success
|
||||
args->state_flags = PA_SUCCESSFUL | PA_HAS_608A;
|
||||
|
||||
// these are constants, and user doesn't care because they got in... but consistency.
|
||||
args->num_fails = 0;
|
||||
args->attempts_left = MAX_TARGET_ATTEMPTS;
|
||||
|
||||
// I used to always read the secret, since it's so hard to get to this point,
|
||||
// but now just indicating if zero or non-zero so that we don't contaminate the
|
||||
// caller w/ sensitive data that they may not want yet.
|
||||
@ -713,9 +787,9 @@ pin_login_attempt(pinAttempt_t *args)
|
||||
}
|
||||
|
||||
// In mark1/2, was thinking of maybe storing duress flag into private state,
|
||||
// but no real need, but testing for it's expensive in mark3, so going to use
|
||||
// but no real need, but testing for it is expensive in mark3, so going to use
|
||||
// LSB here for that.
|
||||
args->private_state = rng_sample() & ~1;
|
||||
args->private_state = (rng_sample() & ~1) | is_duress;
|
||||
|
||||
_sign_attempt(args);
|
||||
|
||||
@ -744,16 +818,15 @@ pin_change(pinAttempt_t *args)
|
||||
}
|
||||
|
||||
// Look at change flags.
|
||||
|
||||
const uint32_t cf = args->change_flags;
|
||||
|
||||
// obsolete secondary support
|
||||
// Obsolete secondary support, can't support.
|
||||
ASSERT(!args->is_secondary);
|
||||
if(cf & CHANGE_SECONDARY_WALLET_PIN) {
|
||||
return EPIN_BAD_REQUEST;
|
||||
}
|
||||
|
||||
// must be here to do something.
|
||||
// Must be here to do something.
|
||||
if(cf == 0) return EPIN_RANGE_ERR;
|
||||
|
||||
if(cf & CHANGE_BRICKME_PIN) {
|
||||
@ -768,17 +841,18 @@ pin_change(pinAttempt_t *args)
|
||||
}
|
||||
|
||||
// ASIDE: Can always change a PIN you already know
|
||||
// but can only prove you know the primary/secondary
|
||||
// pin up to this point ... none of the others.
|
||||
// but can only prove you know the primary pin up
|
||||
// to this point (via login process)... none of the others.
|
||||
// That's why we need old_pin fields.
|
||||
|
||||
// Restore cached version of PIN digest
|
||||
uint8_t digest[32];
|
||||
pin_cache_restore(args, digest);
|
||||
|
||||
// unlock the AE chip
|
||||
if(warmup_ae()) return EPIN_I_AM_BRICK;
|
||||
|
||||
// what pin do they need to know to make their change?
|
||||
int required_kn = -1;
|
||||
// what slot (key number) are updating?
|
||||
int target_slot = -1;
|
||||
|
||||
// If they authorized w/ the duress password, we let them
|
||||
// change it (the duress one) while they think they are changing
|
||||
// the main one. Always pretend like the duress wallet is already enabled.
|
||||
@ -786,14 +860,6 @@ pin_change(pinAttempt_t *args)
|
||||
// Same for brickme PIN.
|
||||
|
||||
// SO ... we need to know if they started w/ a duress wallet.
|
||||
|
||||
// what pin got us here? (ie. in 'digest' already)
|
||||
int pin_kn = -1;
|
||||
// what pin do they need to know to make their change?
|
||||
int required_kn = -1;
|
||||
// what slot (key number) are updating?
|
||||
int target_slot = -1;
|
||||
|
||||
bool is_duress = (args->private_state & 0x1);
|
||||
|
||||
if(is_duress) {
|
||||
@ -813,11 +879,12 @@ pin_change(pinAttempt_t *args)
|
||||
return EPIN_OLD_AUTH_FAIL;
|
||||
}
|
||||
|
||||
pin_kn = required_kn = target_slot = KEYNUM_duress_pin;
|
||||
required_kn = target_slot = KEYNUM_duress_pin;
|
||||
} else {
|
||||
// No need to re-prove PIN knowledge.
|
||||
// If they tricked us, doesn't matter as below the SE validates it all again.
|
||||
pin_kn = required_kn = KEYNUM_main_pin;
|
||||
// No real need to re-prove PIN knowledge.
|
||||
// If they tricked us to get to this point, doesn't matter as
|
||||
// below the SE validates it all again.
|
||||
required_kn = KEYNUM_main_pin;
|
||||
|
||||
if(cf & CHANGE_WALLET_PIN) {
|
||||
target_slot = KEYNUM_main_pin;
|
||||
@ -830,19 +897,25 @@ pin_change(pinAttempt_t *args)
|
||||
required_kn = KEYNUM_duress_pin;
|
||||
target_slot = KEYNUM_duress_secret;
|
||||
} else if(cf & CHANGE_BRICKME_PIN) {
|
||||
required_kn = KEYNUM_main_pin;
|
||||
required_kn = KEYNUM_brickme; // but main_pin would be better: rate limited
|
||||
target_slot = KEYNUM_brickme;
|
||||
} else {
|
||||
return EPIN_RANGE_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine they known hash protecting the secret/pin to be changed.
|
||||
// Determine they know hash protecting the secret/pin to be changed.
|
||||
uint8_t required_digest[32];
|
||||
if(required_kn != pin_kn) {
|
||||
if( (!is_duress && required_kn == KEYNUM_main_pin)
|
||||
|| (is_duress && required_kn == KEYNUM_duress_pin)
|
||||
) {
|
||||
// Restore cached version of PIN digest: faster
|
||||
pin_cache_restore(args, required_digest);
|
||||
} else {
|
||||
// Construct hash of pin needed.
|
||||
pin_hash_attempt(required_kn, args->old_pin, args->old_pin_len, required_digest);
|
||||
|
||||
// Check the old pin is right.
|
||||
// Check the old pin provided, is right.
|
||||
ae_pair_unlock();
|
||||
if(ae_checkmac(required_kn, required_digest)) {
|
||||
// they got old PIN wrong, we won't be able to help them
|
||||
@ -850,20 +923,17 @@ pin_change(pinAttempt_t *args)
|
||||
|
||||
// NOTE: altho we are changing flow based on result of ae_checkmac() here,
|
||||
// if the response is faked by an active bus attacker, it doesn't matter
|
||||
// because the change to the keyslot below will fail due to wrong PIN.
|
||||
// because the change to the dataslot below will fail due to wrong PIN.
|
||||
|
||||
return EPIN_OLD_AUTH_FAIL;
|
||||
}
|
||||
} else {
|
||||
memcpy(required_digest, digest, 32);
|
||||
}
|
||||
|
||||
// Record new PIN value.
|
||||
// Calculate new PIN hashed value: will be slow for main pin.
|
||||
if(cf & (CHANGE_WALLET_PIN | CHANGE_DURESS_PIN | CHANGE_BRICKME_PIN)) {
|
||||
// First calculate new PIN hased value.
|
||||
|
||||
uint8_t new_digest[32];
|
||||
rv = pin_hash_attempt(target_slot, args->new_pin, args->new_pin_len, new_digest);
|
||||
rv = pin_hash_attempt(required_kn, args->new_pin, args->new_pin_len, new_digest);
|
||||
if(rv) goto ae_fail;
|
||||
|
||||
if(ae_encrypted_write(target_slot, required_kn, required_digest, new_digest, 32)) {
|
||||
@ -873,15 +943,21 @@ pin_change(pinAttempt_t *args)
|
||||
if(target_slot == required_kn) {
|
||||
memcpy(required_digest, new_digest, 32);
|
||||
}
|
||||
|
||||
if(target_slot == KEYNUM_main_pin) {
|
||||
// main pin is changing; reset counter to zero (good login) and our cache
|
||||
pin_cache_save(args, new_digest);
|
||||
|
||||
updates_for_good_login(args, new_digest);
|
||||
updates_for_good_login(new_digest);
|
||||
}
|
||||
if(is_duress && (target_slot == KEYNUM_duress_pin)) {
|
||||
// duress pin changed, and we're the duress thug, so update cache
|
||||
pin_cache_save(args, new_digest);
|
||||
}
|
||||
}
|
||||
|
||||
// Record new secret.
|
||||
// Note the digest might have just changed above.
|
||||
// Note the required_digest might have just changed above.
|
||||
if(cf & (CHANGE_SECRET | CHANGE_DURESS_SECRET)) {
|
||||
int secret_kn = (required_kn == KEYNUM_main_pin) ? KEYNUM_secret : KEYNUM_duress_secret;
|
||||
|
||||
|
||||
@ -1267,7 +1267,8 @@ uint32_t HAL_RCCEx_GetPeriphCLKFreq(uint32_t PeriphClk)
|
||||
#if defined(RCC_HSI48_SUPPORT)
|
||||
else if((srcclk == 0U) && (HAL_IS_BIT_SET(RCC->CRRCR, RCC_CRRCR_HSI48RDY))) /* HSI48 ? */
|
||||
{
|
||||
frequency = HSI48_VALUE;
|
||||
//PDG//frequency = HSI48_VALUE;
|
||||
frequency = 0;
|
||||
}
|
||||
else /* No clock source */
|
||||
{
|
||||
|
||||
@ -273,6 +273,8 @@ pick_pairing_secret(void)
|
||||
oled_show_raw(sizeof(tmp), (void *)tmp);
|
||||
}
|
||||
|
||||
oled_factory_busy();
|
||||
|
||||
// .. but don't use those numbers, because those are semi-public now.
|
||||
uint32_t secret[8];
|
||||
for(int i=0; i<8; i++) {
|
||||
@ -306,13 +308,15 @@ pick_pairing_secret(void)
|
||||
}
|
||||
|
||||
// Also at this point, pick RNG noise to use as our one-time-pad
|
||||
// for encrypting the secrets held in the 608a
|
||||
// for encrypting the secrets held in the 608a.
|
||||
{
|
||||
uint32_t dest = (uint32_t)&rom_secrets->otp_key;
|
||||
const uint32_t blen = sizeof(rom_secrets->otp_key) + sizeof(rom_secrets->otp_key_long);
|
||||
const uint32_t blen = sizeof(rom_secrets->otp_key)
|
||||
+ sizeof(rom_secrets->otp_key_long)
|
||||
+ sizeof(rom_secrets->hash_cache_secret);
|
||||
|
||||
STATIC_ASSERT(blen % 8 == 0);
|
||||
STATIC_ASSERT(blen == (72+416));
|
||||
STATIC_ASSERT(blen == (72+416+32));
|
||||
|
||||
flash_unlock();
|
||||
for(int i=0; i<blen; i+=8, dest += 8) {
|
||||
@ -517,6 +521,7 @@ flash_lockdown_hard(uint8_t rdp_level_code)
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
// backup_data_get()
|
||||
//
|
||||
uint32_t
|
||||
@ -544,6 +549,7 @@ backup_data_set(int idx, uint32_t new_value)
|
||||
// doesn't seem to work tho? stays unlocked
|
||||
RTC->WPR = 0xff;
|
||||
}
|
||||
#endif
|
||||
|
||||
// record_highwater_version()
|
||||
//
|
||||
|
||||
@ -22,8 +22,9 @@ typedef struct {
|
||||
uint8_t pairing_secret_xor[32];
|
||||
uint64_t ae_serial_number[2]; // 9 bytes active
|
||||
uint8_t bag_number[32]; // 32 bytes max, zero padded string
|
||||
uint8_t otp_key[72]; // pad for secret encryption (seed storage)
|
||||
uint8_t otp_key[72]; // key for secret encryption (seed storage)
|
||||
uint8_t otp_key_long[416]; // same, but for longer secret area
|
||||
uint8_t hash_cache_secret[32]; // encryption for cached pin hash value
|
||||
} rom_secrets_t;
|
||||
|
||||
// This area is defined in linker script as last page of boot loader flash.
|
||||
@ -47,6 +48,7 @@ static inline bool flash_is_security_level2(void) {
|
||||
return ((FLASH->OPTR & FLASH_OPTR_RDP_Msk) == 0xCC);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// We store some values in the RTC "backup" registers
|
||||
// - these are protected against accidental writes
|
||||
// - not cleared by system reset, full power cycle required
|
||||
@ -58,6 +60,7 @@ static inline bool flash_is_security_level2(void) {
|
||||
|
||||
uint32_t backup_data_get(int idx);
|
||||
void backup_data_set(int idx, uint32_t new_value);
|
||||
#endif
|
||||
|
||||
|
||||
// generial purpose flash functions
|
||||
|
||||
Loading…
Reference in New Issue
Block a user