From f687b176454d9396e108afd07f8c40effefb6052 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Tue, 27 Feb 2024 10:54:19 -0500 Subject: [PATCH] upgradddes --- unix/variant/ckcc.py | 9 +- unix/variant/sim_secel.py | 404 ++++++++++++++++---------------------- 2 files changed, 180 insertions(+), 233 deletions(-) diff --git a/unix/variant/ckcc.py b/unix/variant/ckcc.py index 39a43887..e32cb0b4 100644 --- a/unix/variant/ckcc.py +++ b/unix/variant/ckcc.py @@ -19,6 +19,10 @@ led_pipe = open(int(sys.argv[3]), 'wb') led_pipe.write(b'\xff\x01') # all off, except SE1 green genuine_led = True +# State of SE1/SE2/bootrom +from sim_secel import SEState +SE_STATE = SEState() + # Provide a way to dump few hundred/4k bytes of data from QR or NFC simulated read data_pipe = uasyncio.StreamReader(open(int(sys.argv[4]), 'rb')) @@ -77,8 +81,7 @@ def gate(method, buf_io, arg2): return pin_prefix(buf_io[0:arg2], buf_io) if method == 18: - from sim_secel import pin_stuff - return pin_stuff(arg2, buf_io) + return SE_STATE.pin_stuff(arg2, buf_io) if method == 4: # control the green/red light @@ -127,7 +130,7 @@ def gate(method, buf_io, arg2): if method == 20: # read 608 config bytes (128) assert len(buf_io) == 128 - buf_io[:] = b'\x01#\xbf\x0b\x00\x00`\x03CP,\xbf\xeeap\x00\xe1\x00a\x00\x00\x00\x8f-\x8f\x80\x8fC\xaf\x80\x00C\x00C\x8fG\xc3C\xc3C\xc7G\x00G\x00\x00\x8fM\x8fC\x00\x00\x00\x00\x1f\xff\x00\x1a\x00\x1a\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xea\xff\x02\x15\x00\x00\x00\x00<\x00\\\x00\xbc\x01\xfc\x01\xbc\x01\x9c\x01\x9c\x01\xfc\x01\xdc\x03\xdc\x03\xdc\x07\x9c\x01<\x00\xfc\x01\xdc\x01<\x00' + buf_io[:] = b'\x01#\xbf\x0b\x00\x00`\x04CP,\xbf\xeeap\x00\xe1\x00a\x00\x00\x00\x8f-\x8f\x80\x8fC\xaf\x80\x00C\x00C\x8fG\xc3C\xc3C\xc7G\x00G\x00\x00\x8fM\x8fC\x00\x00\x00\x00\x1f\xff\x00\x1a\x00\x1a\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xea\xff\x02\x15\x00\x00\x00\x00<\x00\\\x00\xbc\x01\xfc\x01\xbc\x01\x9c\x01\x9c\x01\xfc\x01\xdc\x03\xdc\x03\xdc\x07\x9c\x01<\x00\xfc\x01\xdc\x01<\x00' return 0 if method == 22 and version.has_se2: diff --git a/unix/variant/sim_secel.py b/unix/variant/sim_secel.py index d5c525fe..9548ecd7 100644 --- a/unix/variant/sim_secel.py +++ b/unix/variant/sim_secel.py @@ -2,9 +2,7 @@ # # Fake PIN login stuff # -# - any pin starting with '77' will delay for # of seconds defined in suffix of PIN # - data not really stored anywhere, except global "SECRETS" in this file -# - do any pin with '88' prefix, and will jump to just 2 chances left # import ustruct, version from ubinascii import hexlify as b2a_hex @@ -14,6 +12,7 @@ import utime as time from uerrno import * ERANGE = const(34) +# used by test cases global SECRETS SECRETS = {} @@ -33,81 +32,74 @@ EPIN_AUTH_FAIL = const(-112) EPIN_OLD_AUTH_FAIL = const(-113) EPIN_PRIMARY_ONLY = const(-114) +class SEState: + def __init__(self): + self.num_fails = 0 + self.attempts_left = 13 -def pin_stuff(submethod, buf_io): - from pincodes import (PIN_ATTEMPT_SIZE, PIN_ATTEMPT_FMT_V1, PA_ZERO_SECRET, - PIN_ATTEMPT_SIZE_V1, CHANGE_LS_OFFSET, - PA_SUCCESSFUL, PA_IS_BLANK, PA_HAS_DURESS, PA_HAS_BRICKME, - CHANGE_WALLET_PIN, CHANGE_DURESS_PIN, CHANGE_BRICKME_PIN, - AE_LONG_SECRET_LEN, - CHANGE_SECRET, CHANGE_DURESS_SECRET, CHANGE_SECONDARY_WALLET_PIN ) + def force_fails(self, num_fails): + # For testing purposes (untested) + # sim_exec('ckcc.SE_STATE.force_fails(n)') + assert 0 <= num_fails <= 13 + self.num_fails = num_fails + self.attempts_left = (13 - num_fails) - if len(buf_io) < (PIN_ATTEMPT_SIZE if version.has_608 else PIN_ATTEMPT_SIZE_V1): - return ERANGE + def pin_stuff(self, submethod, buf_io): + from pincodes import (PIN_ATTEMPT_SIZE, PIN_ATTEMPT_FMT_V1, PA_ZERO_SECRET, + PIN_ATTEMPT_SIZE_V1, CHANGE_LS_OFFSET, + PA_SUCCESSFUL, PA_IS_BLANK, PA_HAS_DURESS, PA_HAS_BRICKME, + CHANGE_WALLET_PIN, CHANGE_DURESS_PIN, CHANGE_BRICKME_PIN, + AE_LONG_SECRET_LEN, + CHANGE_SECRET, CHANGE_DURESS_SECRET, CHANGE_SECONDARY_WALLET_PIN ) - global SECRETS - after_buf = None + if len(buf_io) < (PIN_ATTEMPT_SIZE if version.has_608 else PIN_ATTEMPT_SIZE_V1): + return ERANGE - (magic, is_secondary, - pin, pin_len, - delay_achieved, - delay_required, - num_fails, - attempts_left, - state_flags, - private_state, - hmac, - change_flags, - old_pin, old_pin_len, - new_pin, new_pin_len, - secret) = ustruct.unpack_from(PIN_ATTEMPT_FMT_V1, buf_io) + global SECRETS - # NOTE: ignoring mk2 additions for now, we have no need for it. + rv = 0 - # NOTE: using strings here, not bytes; real bootrom & API, uses bytes - pin = pin[0:pin_len].decode() - old_pin = old_pin[0:old_pin_len].decode() - new_pin = new_pin[0:new_pin_len].decode() + (magic, is_secondary, + pin, pin_len, + delay_achieved, + delay_required, + IGNORED_num_fails, + IGNORED_attempts_left, + state_flags, + private_state, + hmac, + change_flags, + old_pin, old_pin_len, + new_pin, new_pin_len, + secret) = ustruct.unpack_from(PIN_ATTEMPT_FMT_V1, buf_io) - kk = '_pin1' if not is_secondary else '_pin2' + # NOTE: ignoring mk2 additions for now, we have no need for it. - if submethod == 0: - # setup - state_flags = (PA_SUCCESSFUL | PA_IS_BLANK) if not SECRETS.get(kk, False) else 0 + # NOTE: using strings here, not bytes; real bootrom & API, uses bytes + pin = pin[0:pin_len].decode() + old_pin = old_pin[0:old_pin_len].decode() + new_pin = new_pin[0:new_pin_len].decode() - if pin.startswith('77'): - delay_required = int(pin.split('-')[1]) * 2 - num_fails = 3 - if pin.startswith('88'): - attempts_left = 2 - num_fails = 11 + assert not is_secondary # mk1-3 concept not supported anymore + kk = '_pin1' - if version.has_608: - attempts_left = 13 - num_fails = 0 - if 0: # XXX test - num_fails = 10 - attempts_left = 3 + if submethod == 0: + # setup + state_flags = (PA_SUCCESSFUL | PA_IS_BLANK) if not SECRETS.get(kk, False) else 0 - elif submethod == 1: - # delay - mk2 concept, obsolete - time.sleep(0.05) - delay_achieved += 1 + elif submethod == 1: + # delay - mk2 concept, obsolete + time.sleep(0.05) + delay_achieved += 1 - elif submethod == 2: - # Login - from sim_se2 import SE2 + elif submethod == 2: + # Login + from sim_se2 import SE2 + ts = None - expect = SECRETS.get(kk, '') - if pin == expect: - state_flags = PA_SUCCESSFUL - delay_required, delay_achieved = (0,0) + expect = SECRETS.get(kk, '') - ts = a2b_hex(SECRETS.get(kk+'_secret', '00'*72)) - - elif version.mk_num >= 4: - - got = SE2.try_trick_login(pin, num_fails) + got = SE2.try_trick_login(pin, self.num_fails) if got != None: # good login, but it's a trick ts = SE2.wallet @@ -116,197 +108,149 @@ def pin_stuff(submethod, buf_io): delay_required = flags delay_achieved = arg state_flags = PA_SUCCESSFUL + elif pin == expect: + # good normal login + state_flags = PA_SUCCESSFUL + delay_required, delay_achieved = (0,0) + self.attempts_left = 13 + self.num_fails = 0 + + ts = a2b_hex(SECRETS.get(kk+'_secret', '00'*72)) else: # failed both true PIN and trick pins (or so it seems, see FAKE_OUT) - num_fails += 1 - attempts_left -= 1 + self.num_fails += 1 + self.attempts_left -= 1 - return EPIN_AUTH_FAIL + rv = EPIN_AUTH_FAIL + + time.sleep(0.05) + + if ts == bytes(72): + state_flags |= PA_ZERO_SECRET + + del ts + + elif submethod == 3: + # CHANGE pin and/or wallet secrets + + cf = change_flags + + # NOTE: this logic copied from real deal + + # must be here to do something. + if cf == 0: return EPIN_RANGE_ERR; + + if cf & (CHANGE_BRICKME_PIN | CHANGE_DURESS_SECRET | CHANGE_SECONDARY_WALLET_PIN): + # obsolete mk1-3 stuff + return EPIN_BAD_REQUEST - else: - # obsolete paths - assert version.mk_num < 4 - if pin == SECRETS.get(kk + '_duress', None): - state_flags = PA_SUCCESSFUL + # what PIN will we change + pk = kk - ts = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72)) + if pk != None: + # Must match old pin correctly, if it is defined. + if SECRETS.get(pk, '') != old_pin: + print("secel: wrong OLD pin (expect %s, got %s)" % (SECRETS[pk], old_pin)) + return EPIN_OLD_AUTH_FAIL - else: - if version.has_608: - num_fails += 1 - attempts_left -= 1 - else: - state_flags = 0 + # make change + SECRETS[pk] = new_pin - return EPIN_AUTH_FAIL + if not new_pin and pk != kk: + del SECRETS[pk] - time.sleep(0.05) - - if ts == bytes(72): - state_flags |= PA_ZERO_SECRET - - if version.mk_num < 4: - # mk1-3 concepts - if kk+'_duress' in SECRETS: - state_flags |= PA_HAS_DURESS - if kk+'_brickme' in SECRETS: - state_flags |= PA_HAS_BRICKME - - del ts - - elif submethod == 3: - # CHANGE pin and/or wallet secrets - - cf = change_flags - - # NOTE: this logic copied from real deal - - # must be here to do something. - if cf == 0: return EPIN_RANGE_ERR; - - if cf & CHANGE_BRICKME_PIN: - if is_secondary: - # only main PIN holder can define brickme PIN - return EPIN_PRIMARY_ONLY - if cf != CHANGE_BRICKME_PIN: - # only pin can be changed, nothing else. + if change_flags & CHANGE_SECRET: + SECRETS[kk+'_secret'] = str(b2a_hex(secret), 'ascii') + if change_flags & CHANGE_DURESS_SECRET: + # obsolete mk1-3 stuff return EPIN_BAD_REQUEST - if (cf & CHANGE_DURESS_SECRET) and (cf & CHANGE_SECRET): - # can't change two secrets at once. - return EPIN_BAD_REQUEST + time.sleep(0.05) - if (cf & CHANGE_SECONDARY_WALLET_PIN): - if is_secondary: - # only main user uses this call - return EPIN_BAD_REQUEST; + elif submethod == 4: + # Fetch secrets + from sim_se2 import SE2 + duress_pin = SECRETS.get(kk+'_duress') - if (cf != CHANGE_SECONDARY_WALLET_PIN): - # only changing PIN, no secret-setting - return EPIN_BAD_REQUEST; + secret = None - kk = '_pin2' - - - # what PIN will we change - pk = None - if change_flags & (CHANGE_WALLET_PIN | CHANGE_SECONDARY_WALLET_PIN): - pk = kk - if change_flags & CHANGE_DURESS_PIN: - pk = kk+'_duress' - if change_flags & CHANGE_BRICKME_PIN: - pk = kk+'_brickme' - - if pin == SECRETS.get(kk + '_duress', None): - # acting duress mode... let them only change duress pin - if pk == kk: - pk = kk+'_duress' - else: - return EPIN_OLD_AUTH_FAIL - - if pk != None: - # Must match old pin correctly, if it is defined. - if SECRETS.get(pk, '') != old_pin: - print("secel: wrong OLD pin (expect %s, got %s)" % (SECRETS[pk], old_pin)) - return EPIN_OLD_AUTH_FAIL - - # make change - SECRETS[pk] = new_pin - - if not new_pin and pk != kk: - del SECRETS[pk] - - if change_flags & CHANGE_SECRET: - SECRETS[kk+'_secret'] = str(b2a_hex(secret), 'ascii') - if change_flags & CHANGE_DURESS_SECRET: - SECRETS[kk+'_duress_secret'] = str(b2a_hex(secret), 'ascii') - - time.sleep(0.05) - - elif submethod == 4: - # Fetch secrets - from sim_se2 import SE2 - duress_pin = SECRETS.get(kk+'_duress') - - secret = None - - if SE2.wallet: - if SE2.wallet == 'delta': - secret = a2b_hex(SECRETS.get(kk+'_secret', '00'*72)) - else: - secret = SE2.wallet - elif pin == duress_pin: - secret = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72)) - else: - if change_flags & CHANGE_DURESS_SECRET: - # wants the duress secret - expect = SECRETS.get(kk, '') - if pin == expect: - secret = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72)) - else: - # main/secondary secret - expect = SECRETS.get(kk, '') - if pin == expect: + if SE2.wallet: + if SE2.wallet == 'delta': secret = a2b_hex(SECRETS.get(kk+'_secret', '00'*72)) + else: + secret = SE2.wallet + elif pin == duress_pin: + secret = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72)) + else: + if change_flags & CHANGE_DURESS_SECRET: + # wants the duress secret + expect = SECRETS.get(kk, '') + if pin == expect: + secret = a2b_hex(SECRETS.get(kk+'_duress_secret', '00'*72)) + else: + # main/secondary secret + expect = SECRETS.get(kk, '') + if pin == expect: + secret = a2b_hex(SECRETS.get(kk+'_secret', '00'*72)) - if secret is None: - return EPIN_AUTH_FAIL + if secret is None: + return EPIN_AUTH_FAIL - elif submethod == 5: - # greenlight firmware - from ckcc import genuine_led, led_pipe - genuine_led = True - led_pipe.write(b'\x01\x01') + elif submethod == 5: + # greenlight firmware + from ckcc import genuine_led, led_pipe + genuine_led = True + led_pipe.write(b'\x01') - elif submethod == 6: - if not version.has_608: - return ENOENT + elif submethod == 6: + if not version.has_608: + return ENOENT - # long secret read/change. - cf = change_flags - assert CHANGE_LS_OFFSET == 0xf00 - blk = (cf >> 8) & 0xf - if blk > 13: return EPIN_RANGE_ERR - off = blk * 32 + # long secret read/change. + cf = change_flags + assert CHANGE_LS_OFFSET == 0xf00 + blk = (cf >> 8) & 0xf + if blk > 13: return EPIN_RANGE_ERR + off = blk * 32 - if 'ls' not in SECRETS: - SECRETS['ls'] = bytearray(416) + if 'ls' not in SECRETS: + SECRETS['ls'] = bytearray(416) + + if (cf & CHANGE_SECRET): + # len(secret)==72 here, only using 32 bytes of it + SECRETS['ls'][off:off+32] = secret[0:32] + else: + # Mk3 and earlier only will use this + secret = SECRETS['ls'][off:off+32] + + elif submethod == 7: + # pin_firmware_upgrade(args) process for mk4 + if version.mk_num < 4: + return ENOENT + + # not implemented in simulator + pass + + elif submethod == 8: + # new mk4 api for long-secret fetch + if version.mk_num < 4: + return ENOENT + + assert len(buf_io) == PIN_ATTEMPT_SIZE + AE_LONG_SECRET_LEN, len(buf_io) + buf_io[-AE_LONG_SECRET_LEN:] = SECRETS.get('ls', bytes(AE_LONG_SECRET_LEN)) - if (cf & CHANGE_SECRET): - # len(secret)==72 here, only using 32 bytes of it - SECRETS['ls'][off:off+32] = secret[0:32] else: - # Mk3 and earlier only will use this - secret = SECRETS['ls'][off:off+32] - - elif submethod == 7: - # pin_firmware_upgrade(args) process for mk4 - if version.mk_num < 4: + # bogus submethod return ENOENT - # not implemented in simulator - pass - elif submethod == 8: - # new mk4 api for long-secret fetch - if version.mk_num < 4: - return ENOENT + hmac = b'69'*16 - assert len(buf_io) == PIN_ATTEMPT_SIZE + AE_LONG_SECRET_LEN, len(buf_io) - buf_io[-AE_LONG_SECRET_LEN:] = SECRETS.get('ls', bytes(AE_LONG_SECRET_LEN)) + ustruct.pack_into(PIN_ATTEMPT_FMT_V1, buf_io, 0, magic, is_secondary, + pin.encode(), pin_len, delay_achieved, delay_required, + self.num_fails, self.attempts_left, + state_flags, private_state, hmac, + change_flags, old_pin.encode(), old_pin_len, new_pin.encode(), new_pin_len, secret) - else: - # bogus submethod - return ENOENT - - - hmac = b'69'*16 - - ustruct.pack_into(PIN_ATTEMPT_FMT_V1, buf_io, 0, magic, is_secondary, - pin.encode(), pin_len, delay_achieved, delay_required, - num_fails, attempts_left, - state_flags, private_state, hmac, - change_flags, old_pin.encode(), old_pin_len, new_pin.encode(), new_pin_len, secret) - - return 0 + return rv