Add --reset and post-import --set-password; fix algorithm nibble parsing
Add two admin operations on the OATH protocol layer: --reset (RESET 0x04/0xDEAD) and --set-password, which sets an access password after a successful import only if the applet has none. Fix _parse_select to mask the SELECT ALGORITHM tag to its low nibble (the applet reports the full type|algo byte, e.g. 0x21), which previously broke validate on a locked applet. Add a clean top-level OathError handler.
This commit is contained in:
36
test_oath.py
36
test_oath.py
@@ -7,7 +7,7 @@ from openpyxl import Workbook
|
||||
|
||||
from apex_otp_import import (
|
||||
tlv, parse_tlvs, decode_secret, pad_key, format_cred_id,
|
||||
build_put_data, build_validate_data,
|
||||
build_put_data, build_validate_data, build_set_code_data,
|
||||
derive_key, compute_response,
|
||||
Credential, validate_input, load_credentials,
|
||||
CardChannel, OathSession, OathError, parse_memory,
|
||||
@@ -119,6 +119,18 @@ class TestValidateData(unittest.TestCase):
|
||||
self.assertEqual(data, bytes([0x75, 0x14]) + resp + bytes([0x74, 0x08]) + chal)
|
||||
|
||||
|
||||
class TestSetCodeData(unittest.TestCase):
|
||||
def test_byte_exact_set_code_payload(self):
|
||||
key = b"\x11" * 16
|
||||
chal = b"\x22" * 8
|
||||
data = build_set_code_data(key, chal)
|
||||
resp = compute_response(key, chal, HMAC_SHA1)
|
||||
expected = (tlv(0x73, bytes([0x21]) + key) # KEY: TOTP|SHA1 + 16-byte key
|
||||
+ tlv(0x74, chal) # CHALLENGE
|
||||
+ tlv(0x75, resp)) # RESPONSE = HMAC(key, challenge)
|
||||
self.assertEqual(data, expected)
|
||||
|
||||
|
||||
class TestPasswordAuth(unittest.TestCase):
|
||||
SALT = bytes([0x19, 0x83, 0x57, 0x1E, 0xC3, 0xE5, 0xAB, 0xD7])
|
||||
|
||||
@@ -222,8 +234,9 @@ def select_unlocked():
|
||||
|
||||
|
||||
def select_locked(challenge):
|
||||
# real applet reports the full type|algo byte (0x21 = TOTP|SHA1), not 0x01
|
||||
return (VERSION_TLV + tlv(0x71, DEV_ID)
|
||||
+ tlv(0x74, challenge) + tlv(0x7B, bytes([HMAC_SHA1])))
|
||||
+ tlv(0x74, challenge) + tlv(0x7B, bytes([TOTP_TYPE | HMAC_SHA1])))
|
||||
|
||||
|
||||
class TestOathSessionSelect(unittest.TestCase):
|
||||
@@ -314,5 +327,24 @@ class TestOathSessionValidate(unittest.TestCase):
|
||||
s.validate("wrong")
|
||||
|
||||
|
||||
class TestOathSessionAdmin(unittest.TestCase):
|
||||
def test_set_password_sends_set_code(self):
|
||||
from apex_otp_import import derive_key
|
||||
client = b"\x22" * 8
|
||||
conn = FakeConn([(select_unlocked(), 0x90, 0x00), (b"", 0x90, 0x00)])
|
||||
s = OathSession(CardChannel(conn), rng=lambda n: client)
|
||||
s.select()
|
||||
s.set_password("test1234")
|
||||
self.assertEqual(conn.sent[1][1], 0x03) # INS SET CODE
|
||||
key = derive_key("test1234", DEV_ID)
|
||||
self.assertEqual(bytes(conn.sent[1][5:]), build_set_code_data(key, client))
|
||||
|
||||
def test_reset_sends_reset_with_dead(self):
|
||||
conn = FakeConn([(select_unlocked(), 0x90, 0x00), (b"", 0x90, 0x00)])
|
||||
s = OathSession(CardChannel(conn)); s.select()
|
||||
s.reset()
|
||||
self.assertEqual(conn.sent[1][:4], [0x00, 0x04, 0xDE, 0xAD])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user