diff --git a/stm32/bootloader/ae.c b/stm32/bootloader/ae.c index cb93ebf4..e891cde6 100644 --- a/stm32/bootloader/ae.c +++ b/stm32/bootloader/ae.c @@ -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; ipairing_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; diff --git a/stm32/bootloader/ae.h b/stm32/bootloader/ae.h index 7355dae1..bcb941c9 100644 --- a/stm32/bootloader/ae.h +++ b/stm32/bootloader/ae.h @@ -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 diff --git a/stm32/bootloader/ae_config.h b/stm32/bootloader/ae_config.h index e6bf2ed1..113c00c3 100644 --- a/stm32/bootloader/ae_config.h +++ b/stm32/bootloader/ae_config.h @@ -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 diff --git a/stm32/bootloader/basics.h b/stm32/bootloader/basics.h index 3d6bd69f..ccd84343 100644 --- a/stm32/bootloader/basics.h +++ b/stm32/bootloader/basics.h @@ -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), diff --git a/stm32/bootloader/dispatch.c b/stm32/bootloader/dispatch.c index 14bf4eda..0daf3210 100644 --- a/stm32/bootloader/dispatch.c +++ b/stm32/bootloader/dispatch.c @@ -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; diff --git a/stm32/bootloader/keylayout.py b/stm32/bootloader/keylayout.py index d50d1500..80d19052 100755 --- a/stm32/bootloader/keylayout.py +++ b/stm32/bootloader/keylayout.py @@ -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 diff --git a/stm32/bootloader/link-script.ld b/stm32/bootloader/link-script.ld index 245c27f9..73f86250 100644 --- a/stm32/bootloader/link-script.ld +++ b/stm32/bootloader/link-script.ld @@ -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) : diff --git a/stm32/bootloader/mathcheck.py b/stm32/bootloader/mathcheck.py index 0af6ffd1..88039a4a 100644 --- a/stm32/bootloader/mathcheck.py +++ b/stm32/bootloader/mathcheck.py @@ -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) - diff --git a/stm32/bootloader/oled.c b/stm32/bootloader/oled.c index 5d7969c8..a868acc6 100644 --- a/stm32/bootloader/oled.c +++ b/stm32/bootloader/oled.c @@ -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 diff --git a/stm32/bootloader/oled.h b/stm32/bootloader/oled.h index 2455ed52..8ea4f696 100644 --- a/stm32/bootloader/oled.h +++ b/stm32/bootloader/oled.h @@ -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 diff --git a/stm32/bootloader/pins.c b/stm32/bootloader/pins.c index f27f87bc..eba8b042 100644 --- a/stm32/bootloader/pins.c +++ b/stm32/bootloader/pins.c @@ -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; diff --git a/stm32/bootloader/stm32l4xx_hal_rcc_ex.c b/stm32/bootloader/stm32l4xx_hal_rcc_ex.c index e7789327..fb95fb2d 100644 --- a/stm32/bootloader/stm32l4xx_hal_rcc_ex.c +++ b/stm32/bootloader/stm32l4xx_hal_rcc_ex.c @@ -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 */ { diff --git a/stm32/bootloader/storage.c b/stm32/bootloader/storage.c index be05738e..0c27ab0b 100644 --- a/stm32/bootloader/storage.c +++ b/stm32/bootloader/storage.c @@ -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; iWPR = 0xff; } +#endif // record_highwater_version() // diff --git a/stm32/bootloader/storage.h b/stm32/bootloader/storage.h index f2dd7a19..f827b89d 100644 --- a/stm32/bootloader/storage.h +++ b/stm32/bootloader/storage.h @@ -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