Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ This is a Python package for interacting with the Tillitis TKey.

The goal is to implement the TKey protocol, including a small client.

**Update:** As of 2024-05-08, the currently documented TKey protocol has been
implemented.
**Updates:**

- 2026-03-19: Breaking change in commit
ac7f7e5dbd76bed997da60680b4047c03f7a754c. The User Supplied Secret
is now hashed with BLAKE2s and the resulting digest sent when
loading an app.

**NOTE WELL**: The change means any program using this module with
an USS will now generate different TKey key material compared to
earlier versions.

- As of 2024-05-08, the currently documented TKey protocol has been
implemented.

## TODO

Expand Down
6 changes: 6 additions & 0 deletions client.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@
dest='secret',
help='Provide a user-supplied secret (USS)',
)
cmd_load.add_argument(
'--force-full-uss',
action='store_true',
dest='force_full_uss',
help='Force sending a 32 byte USS digest. Default is 31 bytes.',
)
cmd_load.add_argument('file', type=str)
cmd_load.set_defaults(func=cmds.create_handler(cmds.load_app))

Expand Down
2 changes: 1 addition & 1 deletion cmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def load_app(args):
secret = getpass.getpass(prompt='Enter secret: ')

with TKey(get_device(args), connect=True) as tk:
tk.load_app(args.file, secret)
tk.load_app(args.file, secret, args.force_full_uss)
logger.info('Application loaded: %s' % args.file)


Expand Down
17 changes: 13 additions & 4 deletions tests/tkeyclient/test_tkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,10 @@ def test_load_app_file_size_exception(mock_getsize, monkeypatch):

@patch('os.path.getsize')
@patch('builtins.open', new_callable=mock_open, read_data=b'1337')
def test_load_app_with_user_secret(mock_fopen, mock_getsize, monkeypatch):
@pytest.mark.parametrize('force_full_uss', (False, True))
def test_load_app_with_user_secret(
mock_fopen, mock_getsize, monkeypatch, force_full_uss
):
"""
Assert that TKey.load_app() correctly loads the user secret

Expand All @@ -333,14 +336,20 @@ def test_load_app_with_user_secret(mock_fopen, mock_getsize, monkeypatch):

monkeypatch.setattr(tkey, '_load_app_data', load_app_data)

tkey.load_app('/tmp/dummy_file', secret=secret)
tkey.load_app('/tmp/dummy_file', secret=secret, force_full_uss=force_full_uss)

data = send_command.call_args.args[4]

secret_data = data[5:][: len(secret)]
secret_data = data[5 : 5 + 32]

secret_digest = blake2s(secret.encode('utf-8')).digest()
if force_full_uss:
expected_secret_data = secret_digest
else:
expected_secret_data = secret_digest[1:] + b'\x00'

assert len(data) == 127
assert secret == bytes(secret_data).decode('utf-8')
assert expected_secret_data == secret_data


@patch('os.path.getsize')
Expand Down
13 changes: 10 additions & 3 deletions tkeyclient/tkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,15 @@ def get_udi_string(self) -> str:
reserved, vendor, pid, revision, serial
)

def load_app(self, file: str, secret: Optional[str] = None) -> None:
def load_app(
self, file: str, secret: Optional[str] = None, force_full_uss: bool = False
) -> None:
"""Load an application onto the device.

Args:
file: Path to a binary file to load.
secret: A user-supplied secret (USS) to send with the binary.
secret: A user-supplied secret (USS).
force_full_uss: Force sending a 32 byte USS digest. The default is 31 bytes.

Raises:
TKeyLoadError:
Expand Down Expand Up @@ -279,7 +282,11 @@ def load_app(self, file: str, secret: Optional[str] = None) -> None:
data[4] = 0
if secret is not None:
data[4] = 1
data[5 : 5 + len(secret)] = bytes(secret, encoding='utf8')
secret_digest = blake2s(bytes(secret, encoding='utf-8')).digest()
if force_full_uss:
data[5 : 5 + 32] = secret_digest
else:
data[5 : 5 + 32] = secret_digest[1:] + b'\x00'

response = proto.send_command(
self.conn, proto.cmdLoadApp, proto.ENDPOINT_FW, 2, data
Expand Down