keylet 0.6.0__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {keylet-0.6.0 → keylet-1.0.0}/PKG-INFO +17 -10
- {keylet-0.6.0 → keylet-1.0.0}/README.md +16 -9
- keylet-1.0.0/pyproject.toml +87 -0
- keylet-0.6.0/pyproject.toml → keylet-1.0.0/pyproject.toml.orig +3 -2
- keylet-1.0.0/src/keylet/__init__.py +29 -0
- keylet-1.0.0/src/keylet/bin/__init__.py +2 -0
- {keylet-0.6.0 → keylet-1.0.0}/src/keylet/bin/cli.py +20 -7
- {keylet-0.6.0/src/keylet → keylet-1.0.0/src/keylet/resources}/__init__.py +1 -2
- {keylet-0.6.0 → keylet-1.0.0}/src/keylet/tkey.py +4 -0
- {keylet-0.6.0 → keylet-1.0.0}/src/keylet/tkey_sign.py +71 -13
- keylet-0.6.0/src/keylet/bin/__init__.py +0 -0
- keylet-0.6.0/src/keylet/resources/__init__.py +0 -1
- {keylet-0.6.0 → keylet-1.0.0}/LICENSE +0 -0
- {keylet-0.6.0 → keylet-1.0.0}/src/keylet/_serial_hack.py +0 -0
- {keylet-0.6.0 → keylet-1.0.0}/src/keylet/py.typed +0 -0
- {keylet-0.6.0 → keylet-1.0.0}/src/keylet/resources/ed25519signer_v3.bin +0 -0
- {keylet-0.6.0 → keylet-1.0.0}/src/keylet/resources/pqsigner_v3.bin +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: keylet
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Client application for Tillitis TKey hardware ML-DSA/Ed25519 signer
|
|
5
5
|
Keywords: ML-DSA,TKey,Tillitis,PQC,sign,signer
|
|
6
6
|
Author: Jussi Kukkonen
|
|
@@ -46,15 +46,15 @@ The package installs a `keylet` command-line tool for signing and verification.
|
|
|
46
46
|
$ keylet sign README.md
|
|
47
47
|
$ keylet verify README.md
|
|
48
48
|
|
|
49
|
-
# Get public key, sign
|
|
50
|
-
$ keylet
|
|
51
|
-
$ keylet
|
|
49
|
+
# Get public key, sign, and verify using the saved public key
|
|
50
|
+
$ keylet pubkey --output pub.key
|
|
51
|
+
$ keylet sign README.md
|
|
52
52
|
$ keylet verify --pubkey pub.key README.md
|
|
53
53
|
|
|
54
54
|
# When using keylet long-term, remember to specify device app digest (keylet default
|
|
55
55
|
# app version may change, but you will need a specific application to keep using the
|
|
56
56
|
# same key)
|
|
57
|
-
$ keylet --
|
|
57
|
+
$ keylet --digest 2cd8741 sign README.md
|
|
58
58
|
|
|
59
59
|
```
|
|
60
60
|
|
|
@@ -67,9 +67,9 @@ from keylet import TKeySign, SignApp
|
|
|
67
67
|
app = SignApp.load_mldsa()
|
|
68
68
|
digest = app.digest
|
|
69
69
|
|
|
70
|
-
# Initialize the signer with a passphrase
|
|
70
|
+
# Initialize the signer with a passphrase, sign a payload
|
|
71
71
|
with TKeySign(app=app, secret="hunter2") as signer:
|
|
72
|
-
|
|
72
|
+
pubkey = signer.get_pubkey()
|
|
73
73
|
signature = signer.sign(b"my payload")
|
|
74
74
|
```
|
|
75
75
|
|
|
@@ -80,13 +80,14 @@ is always used for a specific key:
|
|
|
80
80
|
# Load application with a digest stored earlier
|
|
81
81
|
app = SignApp.load_mldsa(digest=digest)
|
|
82
82
|
|
|
83
|
-
# Initialize the signer with a passphrase
|
|
83
|
+
# Initialize the signer with a passphrase, sign a payload
|
|
84
84
|
with TKeySign(app=app, secret="hunter2") as signer:
|
|
85
|
-
|
|
85
|
+
if signer.get_pubkey() != pubkey:
|
|
86
|
+
exit("Unexpected signing key: maybe incorrect password?")
|
|
86
87
|
signature = signer.sign(b"my payload")
|
|
87
88
|
```
|
|
88
89
|
|
|
89
|
-
See API Reference in [documentation](https://jku.github.io/keylet/) for more details.
|
|
90
|
+
See API Reference in [documentation](https://jku.github.io/keylet/) for more details.
|
|
90
91
|
|
|
91
92
|
## Development
|
|
92
93
|
|
|
@@ -108,3 +109,9 @@ make test
|
|
|
108
109
|
# run tests, including on-device tests
|
|
109
110
|
make test-device
|
|
110
111
|
```
|
|
112
|
+
|
|
113
|
+
### Releasing
|
|
114
|
+
|
|
115
|
+
* If a major or minor version bump is needed run `uv version --bump=[major|minor]`
|
|
116
|
+
* run `make release` to create version bump commit, release tag and dev version bump commit
|
|
117
|
+
* Push the commits and tag to trigger release workflow: `git push --tags origin main`
|
|
@@ -28,15 +28,15 @@ The package installs a `keylet` command-line tool for signing and verification.
|
|
|
28
28
|
$ keylet sign README.md
|
|
29
29
|
$ keylet verify README.md
|
|
30
30
|
|
|
31
|
-
# Get public key, sign
|
|
32
|
-
$ keylet
|
|
33
|
-
$ keylet
|
|
31
|
+
# Get public key, sign, and verify using the saved public key
|
|
32
|
+
$ keylet pubkey --output pub.key
|
|
33
|
+
$ keylet sign README.md
|
|
34
34
|
$ keylet verify --pubkey pub.key README.md
|
|
35
35
|
|
|
36
36
|
# When using keylet long-term, remember to specify device app digest (keylet default
|
|
37
37
|
# app version may change, but you will need a specific application to keep using the
|
|
38
38
|
# same key)
|
|
39
|
-
$ keylet --
|
|
39
|
+
$ keylet --digest 2cd8741 sign README.md
|
|
40
40
|
|
|
41
41
|
```
|
|
42
42
|
|
|
@@ -49,9 +49,9 @@ from keylet import TKeySign, SignApp
|
|
|
49
49
|
app = SignApp.load_mldsa()
|
|
50
50
|
digest = app.digest
|
|
51
51
|
|
|
52
|
-
# Initialize the signer with a passphrase
|
|
52
|
+
# Initialize the signer with a passphrase, sign a payload
|
|
53
53
|
with TKeySign(app=app, secret="hunter2") as signer:
|
|
54
|
-
|
|
54
|
+
pubkey = signer.get_pubkey()
|
|
55
55
|
signature = signer.sign(b"my payload")
|
|
56
56
|
```
|
|
57
57
|
|
|
@@ -62,13 +62,14 @@ is always used for a specific key:
|
|
|
62
62
|
# Load application with a digest stored earlier
|
|
63
63
|
app = SignApp.load_mldsa(digest=digest)
|
|
64
64
|
|
|
65
|
-
# Initialize the signer with a passphrase
|
|
65
|
+
# Initialize the signer with a passphrase, sign a payload
|
|
66
66
|
with TKeySign(app=app, secret="hunter2") as signer:
|
|
67
|
-
|
|
67
|
+
if signer.get_pubkey() != pubkey:
|
|
68
|
+
exit("Unexpected signing key: maybe incorrect password?")
|
|
68
69
|
signature = signer.sign(b"my payload")
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
See API Reference in [documentation](https://jku.github.io/keylet/) for more details.
|
|
72
|
+
See API Reference in [documentation](https://jku.github.io/keylet/) for more details.
|
|
72
73
|
|
|
73
74
|
## Development
|
|
74
75
|
|
|
@@ -90,3 +91,9 @@ make test
|
|
|
90
91
|
# run tests, including on-device tests
|
|
91
92
|
make test-device
|
|
92
93
|
```
|
|
94
|
+
|
|
95
|
+
### Releasing
|
|
96
|
+
|
|
97
|
+
* If a major or minor version bump is needed run `uv version --bump=[major|minor]`
|
|
98
|
+
* run `make release` to create version bump commit, release tag and dev version bump commit
|
|
99
|
+
* Push the commits and tag to trigger release workflow: `git push --tags origin main`
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "keylet"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Client application for Tillitis TKey hardware ML-DSA/Ed25519 signer"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"cryptography>=48.0.0",
|
|
11
|
+
"pyserial>=3.5",
|
|
12
|
+
]
|
|
13
|
+
keywords = [
|
|
14
|
+
"ML-DSA",
|
|
15
|
+
"TKey",
|
|
16
|
+
"Tillitis",
|
|
17
|
+
"PQC",
|
|
18
|
+
"sign",
|
|
19
|
+
"signer",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[[project.authors]]
|
|
23
|
+
name = "Jussi Kukkonen"
|
|
24
|
+
email = "jkukkonen@google.com"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
keylet = "keylet.bin.cli:main"
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Documentation = "https://jku.github.io/keylet/"
|
|
31
|
+
Homepage = "https://github.com/jku/keylet"
|
|
32
|
+
Issues = "https://github.com/jku/keylet/issues"
|
|
33
|
+
Source = "https://github.com/jku/keylet"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"mypy",
|
|
38
|
+
"pytest",
|
|
39
|
+
"ruff",
|
|
40
|
+
"zizmor",
|
|
41
|
+
"zensical",
|
|
42
|
+
"mkdocstrings[python]",
|
|
43
|
+
"types-pyserial",
|
|
44
|
+
]
|
|
45
|
+
uv = ["uv"]
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["uv_build>=0.12.0,<0.13.0"]
|
|
49
|
+
build-backend = "uv_build"
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
select = ["ALL"]
|
|
53
|
+
ignore = [
|
|
54
|
+
"BLE001",
|
|
55
|
+
"PLR2004",
|
|
56
|
+
"COM812",
|
|
57
|
+
"D",
|
|
58
|
+
"EM",
|
|
59
|
+
"TRY003",
|
|
60
|
+
"PYI019",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.per-file-ignores]
|
|
64
|
+
"src/keylet/bin/**" = ["T201"]
|
|
65
|
+
"tests/**" = [
|
|
66
|
+
"S101",
|
|
67
|
+
"S105",
|
|
68
|
+
"S106",
|
|
69
|
+
"SLF001",
|
|
70
|
+
"ARG002",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.mypy]
|
|
74
|
+
python_version = "3.10"
|
|
75
|
+
pretty = true
|
|
76
|
+
strict = true
|
|
77
|
+
strict_equality = true
|
|
78
|
+
disallow_any_unimported = true
|
|
79
|
+
disallow_untyped_calls = true
|
|
80
|
+
disallow_untyped_defs = true
|
|
81
|
+
warn_redundant_casts = true
|
|
82
|
+
warn_return_any = true
|
|
83
|
+
warn_unreachable = true
|
|
84
|
+
warn_unused_ignores = true
|
|
85
|
+
|
|
86
|
+
[tool.pytest]
|
|
87
|
+
markers = ["device: tests that require a physical TKey device"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "keylet"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "1.0.0"
|
|
4
4
|
description = "Client application for Tillitis TKey hardware ML-DSA/Ed25519 signer"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -39,7 +39,7 @@ dev = [
|
|
|
39
39
|
uv = ["uv"]
|
|
40
40
|
|
|
41
41
|
[build-system]
|
|
42
|
-
requires = ["uv_build>=0.
|
|
42
|
+
requires = ["uv_build>=0.12.0,<0.13.0"]
|
|
43
43
|
build-backend = "uv_build"
|
|
44
44
|
|
|
45
45
|
[tool.ruff.lint]
|
|
@@ -61,6 +61,7 @@ ignore = [
|
|
|
61
61
|
"tests/**" = [
|
|
62
62
|
"S101", # assert is ok in tests
|
|
63
63
|
"S105", # hardcoded password is ok in tests
|
|
64
|
+
"S106", # hardcoded password is ok in tests
|
|
64
65
|
"SLF001", # Private member access is ok in tests
|
|
65
66
|
"ARG002", # Unused method argument (common with mocks)
|
|
66
67
|
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 keylet authors
|
|
3
|
+
|
|
4
|
+
from keylet.tkey import (
|
|
5
|
+
TKeyAppError,
|
|
6
|
+
TKeyDeviceBusyError,
|
|
7
|
+
TKeyError,
|
|
8
|
+
TKeyIOError,
|
|
9
|
+
TKeyNOKError,
|
|
10
|
+
TKeyNotFoundError,
|
|
11
|
+
TKeyNotInFirmwareModeError,
|
|
12
|
+
TKeyProtocolError,
|
|
13
|
+
TKeyUnexpectedAppError,
|
|
14
|
+
)
|
|
15
|
+
from keylet.tkey_sign import SignApp, TKeySign
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"SignApp",
|
|
19
|
+
"TKeyAppError",
|
|
20
|
+
"TKeyDeviceBusyError",
|
|
21
|
+
"TKeyError",
|
|
22
|
+
"TKeyIOError",
|
|
23
|
+
"TKeyNOKError",
|
|
24
|
+
"TKeyNotFoundError",
|
|
25
|
+
"TKeyNotInFirmwareModeError",
|
|
26
|
+
"TKeyProtocolError",
|
|
27
|
+
"TKeySign",
|
|
28
|
+
"TKeyUnexpectedAppError",
|
|
29
|
+
]
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Copyright (c) 2026 keylet authors
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
|
+
import getpass
|
|
5
6
|
import sys
|
|
6
7
|
from collections.abc import Generator
|
|
7
8
|
from contextlib import contextmanager
|
|
@@ -11,18 +12,18 @@ from cryptography.exceptions import InvalidSignature
|
|
|
11
12
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
12
13
|
from cryptography.hazmat.primitives.asymmetric.mldsa import MLDSA44PublicKey
|
|
13
14
|
|
|
14
|
-
from keylet
|
|
15
|
-
from keylet.tkey_sign import SignApp, TKeySign
|
|
15
|
+
from keylet import SignApp, TKeyNotFoundError, TKeySign, TKeyUnexpectedAppError
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@contextmanager
|
|
19
19
|
def _app_signer(args: argparse.Namespace) -> Generator[TKeySign, None, None]:
|
|
20
20
|
try:
|
|
21
|
+
secret = getpass.getpass("Enter passphrase (press Enter for none): ")
|
|
21
22
|
if args.type == "ed25519":
|
|
22
23
|
app = SignApp.load_ed25519(digest=args.digest)
|
|
23
24
|
else:
|
|
24
25
|
app = SignApp.load_mldsa(digest=args.digest)
|
|
25
|
-
with TKeySign(app, secret=
|
|
26
|
+
with TKeySign(app, secret=secret or None) as signer:
|
|
26
27
|
print(f"Using {args.type} device app with digest {app.digest[:7]}")
|
|
27
28
|
yield signer
|
|
28
29
|
except TKeyNotFoundError as e:
|
|
@@ -46,10 +47,19 @@ def cmd_sign(args: argparse.Namespace) -> None:
|
|
|
46
47
|
if not file_path.exists():
|
|
47
48
|
sys.exit(f"Error: File {args.file} does not exist")
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
pubkey = None
|
|
51
|
+
if args.pubkey is not None:
|
|
52
|
+
key_path = Path(args.pubkey)
|
|
53
|
+
if not key_path.exists():
|
|
54
|
+
sys.exit(f"Error: Public key {args.pubkey} does not exist")
|
|
55
|
+
pubkey = key_path.read_bytes()
|
|
56
|
+
# Note that we do not compare the given pubkey to the pubkey from the device
|
|
57
|
+
# A real application would want to do that to check for passphrase typos etc.
|
|
58
|
+
|
|
50
59
|
with _app_signer(args) as signer:
|
|
51
60
|
print("Please touch the TKey device when it flashes to sign...")
|
|
52
|
-
|
|
61
|
+
with file_path.open("rb") as f:
|
|
62
|
+
signature = signer.sign(f, pubkey)
|
|
53
63
|
|
|
54
64
|
sig_path = file_path.with_suffix(file_path.suffix + ".signature")
|
|
55
65
|
sig_path.write_bytes(signature)
|
|
@@ -76,8 +86,8 @@ def cmd_verify(args: argparse.Namespace) -> None:
|
|
|
76
86
|
if args.pubkey:
|
|
77
87
|
pubkey_bytes = Path(args.pubkey).read_bytes()
|
|
78
88
|
else:
|
|
89
|
+
print("Retrieving public key from device...")
|
|
79
90
|
with _app_signer(args) as signer:
|
|
80
|
-
print("Retrieving public key from device...")
|
|
81
91
|
pubkey_bytes = signer.get_pubkey()
|
|
82
92
|
|
|
83
93
|
try:
|
|
@@ -101,7 +111,6 @@ def main() -> None:
|
|
|
101
111
|
parser.add_argument(
|
|
102
112
|
"--digest", help="Optional digest of the device application to use"
|
|
103
113
|
)
|
|
104
|
-
parser.add_argument("--passphrase")
|
|
105
114
|
parser.add_argument(
|
|
106
115
|
"-t",
|
|
107
116
|
"--type",
|
|
@@ -119,6 +128,10 @@ def main() -> None:
|
|
|
119
128
|
# sign command
|
|
120
129
|
parser_sign = subparsers.add_parser("sign", help="Sign a file")
|
|
121
130
|
parser_sign.add_argument("file", help="File to sign")
|
|
131
|
+
parser_sign.add_argument(
|
|
132
|
+
"--pubkey",
|
|
133
|
+
help="Optional public key file (retrieved from device if not specified)",
|
|
134
|
+
)
|
|
122
135
|
|
|
123
136
|
# verify command
|
|
124
137
|
parser_verify = subparsers.add_parser("verify", help="Verify a signature")
|
|
@@ -138,6 +138,10 @@ class TKeyUnexpectedAppError(TKeyAppError):
|
|
|
138
138
|
"""Raised when TKey is already running a different application."""
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
class TKeyNotInFirmwareModeError(TKeyAppError):
|
|
142
|
+
"""Raised when the TKey is not in firmware mode and firmware mode was required."""
|
|
143
|
+
|
|
144
|
+
|
|
141
145
|
class TKey:
|
|
142
146
|
"""Base TKey Client
|
|
143
147
|
|
|
@@ -8,14 +8,56 @@ from __future__ import annotations
|
|
|
8
8
|
import hashlib
|
|
9
9
|
import importlib.resources
|
|
10
10
|
import logging
|
|
11
|
+
from collections.abc import Iterable, Iterator
|
|
11
12
|
from dataclasses import dataclass
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
from typing import Protocol, TypeAlias, runtime_checkable
|
|
14
|
+
|
|
15
|
+
from keylet.tkey import (
|
|
16
|
+
Cmd,
|
|
17
|
+
LenIdx,
|
|
18
|
+
Rsp,
|
|
19
|
+
TKey,
|
|
20
|
+
TKeyError,
|
|
21
|
+
TKeyNotInFirmwareModeError,
|
|
22
|
+
TKeyUnexpectedAppError,
|
|
23
|
+
)
|
|
14
24
|
|
|
15
25
|
logger = logging.getLogger(__name__)
|
|
16
26
|
|
|
17
|
-
MU_SIZE = (64).to_bytes(4, byteorder="little")
|
|
18
27
|
MAX_PAYLOAD_SIZE = 4096
|
|
28
|
+
_STREAM_CHUNK_SIZE = 64 * 1024 # 64 KiB buffer for streaming
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@runtime_checkable
|
|
32
|
+
class BinaryReader(Protocol):
|
|
33
|
+
"""Protocol for binary streams supporting chunked reads."""
|
|
34
|
+
|
|
35
|
+
def read(self, size: int = -1, /) -> bytes: ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
SignableMessage: TypeAlias = bytes | BinaryReader | Iterable[bytes]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _iter_chunks(message: SignableMessage) -> Iterator[bytes]:
|
|
42
|
+
"""Yield chunks of bytes from bytes, a binary reader, or an iterable of bytes."""
|
|
43
|
+
if isinstance(message, bytes):
|
|
44
|
+
yield message
|
|
45
|
+
elif isinstance(message, BinaryReader):
|
|
46
|
+
while chunk := message.read(_STREAM_CHUNK_SIZE):
|
|
47
|
+
yield chunk
|
|
48
|
+
else: # iterable
|
|
49
|
+
yield from message
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _read_bounded(message: SignableMessage, max_size: int) -> bytes:
|
|
53
|
+
"""Read stream into memory up to max_size, failing fast on overflow."""
|
|
54
|
+
buf = bytearray()
|
|
55
|
+
for chunk in _iter_chunks(message):
|
|
56
|
+
buf.extend(chunk)
|
|
57
|
+
if len(buf) > max_size:
|
|
58
|
+
raise ValueError(f"Payload size exceeds maximum {max_size} bytes")
|
|
59
|
+
return bytes(buf)
|
|
60
|
+
|
|
19
61
|
|
|
20
62
|
# Static registry of signer binaries (filename, version)
|
|
21
63
|
# First binary in each list is the default binary.
|
|
@@ -186,12 +228,15 @@ class TKeySign(TKey):
|
|
|
186
228
|
app: SignApp,
|
|
187
229
|
device: str | None = None,
|
|
188
230
|
secret: str | None = None,
|
|
231
|
+
*,
|
|
232
|
+
require_firmware_mode: bool = False,
|
|
189
233
|
) -> None:
|
|
190
234
|
"""Initialize the TKey signing client.
|
|
191
235
|
|
|
192
236
|
If the TKey device is in firmware mode, this will automatically load the
|
|
193
|
-
application binary. If the device is already running an application
|
|
194
|
-
verifies that the running application
|
|
237
|
+
application binary. If the device is already running an application (and
|
|
238
|
+
require_firmware_mode is not set), verifies that the running application
|
|
239
|
+
matches the expected name and version.
|
|
195
240
|
|
|
196
241
|
Args:
|
|
197
242
|
app: The SignApp configuration containing the binary and metadata.
|
|
@@ -199,9 +244,13 @@ class TKeySign(TKey):
|
|
|
199
244
|
the port is auto-detected.
|
|
200
245
|
secret: Optional User Supplied Secret (passphrase) used as a seed
|
|
201
246
|
for key derivation.
|
|
247
|
+
require_firmware_mode: If True, fail if the device is not in firmware
|
|
248
|
+
mode instead of attempting to use an already running application.
|
|
202
249
|
|
|
203
250
|
Raises:
|
|
204
251
|
TKeyNotFoundError: If the TKey device cannot be found.
|
|
252
|
+
TKeyNotInFirmwareModeError: when require_firmware_mode is set and the
|
|
253
|
+
device is not in firmware mode.
|
|
205
254
|
TKeyUnexpectedAppError: If loading the application fails or the device is
|
|
206
255
|
running a mismatched application.
|
|
207
256
|
TKeyError: For other connection or initialization failures.
|
|
@@ -213,6 +262,11 @@ class TKeySign(TKey):
|
|
|
213
262
|
|
|
214
263
|
try:
|
|
215
264
|
if not self.load_app(app.binary, secret):
|
|
265
|
+
if require_firmware_mode:
|
|
266
|
+
raise TKeyNotInFirmwareModeError(
|
|
267
|
+
"TKey is not in firmware mode but require_firmware_mode was set"
|
|
268
|
+
)
|
|
269
|
+
|
|
216
270
|
# TKey is not in firmware mode: Query application name and version
|
|
217
271
|
rx = self.send(SignCmd.GET_NAMEVERSION)
|
|
218
272
|
name = (
|
|
@@ -256,22 +310,25 @@ class TKeySign(TKey):
|
|
|
256
310
|
|
|
257
311
|
return bytes(pubkey)
|
|
258
312
|
|
|
259
|
-
def sign(self, message:
|
|
313
|
+
def sign(self, message: SignableMessage, pub_key: bytes | None = None) -> bytes:
|
|
260
314
|
"""Sign a payload.
|
|
261
315
|
|
|
262
316
|
Sends payload to device and retrieves the signature.
|
|
263
317
|
|
|
264
318
|
For ML-DSA, the FIPS 204 external mu is computed using the message
|
|
265
319
|
and public key: the mu is sent to device instead of payload.
|
|
320
|
+
Streaming messages of arbitrary size are supported for ML-DSA.
|
|
321
|
+
For Ed25519, the message (or stream) must not exceed 4096 bytes.
|
|
266
322
|
|
|
267
323
|
Note:
|
|
268
324
|
This method blocks and waits (up to 60 seconds) for the user to touch
|
|
269
325
|
the physical TKey device when it flashes.
|
|
270
326
|
|
|
271
327
|
Args:
|
|
272
|
-
message: The raw bytes
|
|
273
|
-
|
|
274
|
-
not apply to ML-DSA as FIPS 204 external mu
|
|
328
|
+
message: The raw bytes, binary reader (file-like object), or chunk iterable
|
|
329
|
+
to sign. For Ed25519, the total message size cannot exceed 4096B.
|
|
330
|
+
This limitation does not apply to ML-DSA as FIPS 204 external mu
|
|
331
|
+
is used.
|
|
275
332
|
pub_key: The public key bytes (only needed for ML-DSA). If not provided,
|
|
276
333
|
key is retrieved from device.
|
|
277
334
|
|
|
@@ -288,13 +345,14 @@ class TKeySign(TKey):
|
|
|
288
345
|
if pub_key is None:
|
|
289
346
|
pub_key = self.get_pubkey()
|
|
290
347
|
tr = hashlib.shake_256(pub_key).digest(64)
|
|
291
|
-
|
|
348
|
+
shake = hashlib.shake_256(tr + b"\x00\x00")
|
|
349
|
+
for chunk in _iter_chunks(message):
|
|
350
|
+
shake.update(chunk)
|
|
351
|
+
payload = shake.digest(64)
|
|
292
352
|
else:
|
|
293
|
-
payload = message
|
|
353
|
+
payload = _read_bounded(message, MAX_PAYLOAD_SIZE)
|
|
294
354
|
|
|
295
355
|
# Set size
|
|
296
|
-
if len(payload) > MAX_PAYLOAD_SIZE:
|
|
297
|
-
raise ValueError(f"Payload too large {len(payload)} > {MAX_PAYLOAD_SIZE}]")
|
|
298
356
|
self.send(SignCmd.SET_SIZE, len(payload).to_bytes(4, byteorder="little"))
|
|
299
357
|
|
|
300
358
|
# Load data in chunks
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# Package marker for resource files
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|