keylet 0.5.0__tar.gz → 0.7.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.5.0 → keylet-0.7.0}/PKG-INFO +12 -6
- {keylet-0.5.0 → keylet-0.7.0}/README.md +11 -5
- keylet-0.7.0/pyproject.toml +87 -0
- keylet-0.5.0/pyproject.toml → keylet-0.7.0/pyproject.toml.orig +3 -1
- {keylet-0.5.0 → keylet-0.7.0}/src/keylet/__init__.py +2 -1
- keylet-0.7.0/src/keylet/bin/__init__.py +2 -0
- {keylet-0.5.0 → keylet-0.7.0}/src/keylet/bin/cli.py +4 -3
- keylet-0.7.0/src/keylet/resources/__init__.py +4 -0
- {keylet-0.5.0 → keylet-0.7.0}/src/keylet/tkey.py +1 -4
- {keylet-0.5.0 → keylet-0.7.0}/src/keylet/tkey_sign.py +15 -16
- keylet-0.5.0/src/keylet/bin/__init__.py +0 -0
- keylet-0.5.0/src/keylet/resources/__init__.py +0 -1
- {keylet-0.5.0 → keylet-0.7.0}/LICENSE +0 -0
- {keylet-0.5.0 → keylet-0.7.0}/src/keylet/_serial_hack.py +0 -0
- {keylet-0.5.0 → keylet-0.7.0}/src/keylet/py.typed +0 -0
- {keylet-0.5.0 → keylet-0.7.0}/src/keylet/resources/ed25519signer_v3.bin +0 -0
- {keylet-0.5.0 → keylet-0.7.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: 0.7.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
|
|
|
@@ -86,7 +86,7 @@ with TKeySign(app=app, secret="hunter2") as signer:
|
|
|
86
86
|
signature = signer.sign(b"my payload")
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
See
|
|
89
|
+
See API Reference in [documentation](https://jku.github.io/keylet/) for more details.
|
|
90
90
|
|
|
91
91
|
## Development
|
|
92
92
|
|
|
@@ -108,3 +108,9 @@ make test
|
|
|
108
108
|
# run tests, including on-device tests
|
|
109
109
|
make test-device
|
|
110
110
|
```
|
|
111
|
+
|
|
112
|
+
### Releasing
|
|
113
|
+
|
|
114
|
+
* If a major or minor version bump is needed run `uv version --bump=[major|minor]`
|
|
115
|
+
* run `make release` to create version bump commit, release tag and dev version bump commit
|
|
116
|
+
* 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
|
|
|
@@ -68,7 +68,7 @@ with TKeySign(app=app, secret="hunter2") as signer:
|
|
|
68
68
|
signature = signer.sign(b"my payload")
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
See
|
|
71
|
+
See API Reference in [documentation](https://jku.github.io/keylet/) for more details.
|
|
72
72
|
|
|
73
73
|
## Development
|
|
74
74
|
|
|
@@ -90,3 +90,9 @@ make test
|
|
|
90
90
|
# run tests, including on-device tests
|
|
91
91
|
make test-device
|
|
92
92
|
```
|
|
93
|
+
|
|
94
|
+
### Releasing
|
|
95
|
+
|
|
96
|
+
* If a major or minor version bump is needed run `uv version --bump=[major|minor]`
|
|
97
|
+
* run `make release` to create version bump commit, release tag and dev version bump commit
|
|
98
|
+
* 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 = "0.7.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.11.23,<0.12.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 = "0.7.0"
|
|
4
4
|
description = "Client application for Tillitis TKey hardware ML-DSA/Ed25519 signer"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -61,6 +61,8 @@ 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
|
|
65
|
+
"SLF001", # Private member access is ok in tests
|
|
64
66
|
"ARG002", # Unused method argument (common with mocks)
|
|
65
67
|
]
|
|
66
68
|
|
|
@@ -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
|
|
@@ -18,11 +19,12 @@ from keylet.tkey_sign import SignApp, TKeySign
|
|
|
18
19
|
@contextmanager
|
|
19
20
|
def _app_signer(args: argparse.Namespace) -> Generator[TKeySign, None, None]:
|
|
20
21
|
try:
|
|
22
|
+
secret = getpass.getpass("Enter passphrase (press Enter for none): ")
|
|
21
23
|
if args.type == "ed25519":
|
|
22
24
|
app = SignApp.load_ed25519(digest=args.digest)
|
|
23
25
|
else:
|
|
24
26
|
app = SignApp.load_mldsa(digest=args.digest)
|
|
25
|
-
with TKeySign(app, secret=
|
|
27
|
+
with TKeySign(app, secret=secret or None) as signer:
|
|
26
28
|
print(f"Using {args.type} device app with digest {app.digest[:7]}")
|
|
27
29
|
yield signer
|
|
28
30
|
except TKeyNotFoundError as e:
|
|
@@ -76,8 +78,8 @@ def cmd_verify(args: argparse.Namespace) -> None:
|
|
|
76
78
|
if args.pubkey:
|
|
77
79
|
pubkey_bytes = Path(args.pubkey).read_bytes()
|
|
78
80
|
else:
|
|
81
|
+
print("Retrieving public key from device...")
|
|
79
82
|
with _app_signer(args) as signer:
|
|
80
|
-
print("Retrieving public key from device...")
|
|
81
83
|
pubkey_bytes = signer.get_pubkey()
|
|
82
84
|
|
|
83
85
|
try:
|
|
@@ -101,7 +103,6 @@ def main() -> None:
|
|
|
101
103
|
parser.add_argument(
|
|
102
104
|
"--digest", help="Optional digest of the device application to use"
|
|
103
105
|
)
|
|
104
|
-
parser.add_argument("--passphrase")
|
|
105
106
|
parser.add_argument(
|
|
106
107
|
"-t",
|
|
107
108
|
"--type",
|
|
@@ -150,10 +150,7 @@ class TKey:
|
|
|
150
150
|
* [Framing protocol](https://dev.tillitis.se/protocol/#framing-protocol)
|
|
151
151
|
"""
|
|
152
152
|
|
|
153
|
-
def __init__(
|
|
154
|
-
self,
|
|
155
|
-
device: str | None,
|
|
156
|
-
) -> None:
|
|
153
|
+
def __init__(self, device: str | None) -> None:
|
|
157
154
|
"""Initialize serial connection to TKey device.
|
|
158
155
|
|
|
159
156
|
Args:
|
|
@@ -14,18 +14,18 @@ from keylet.tkey import Cmd, LenIdx, Rsp, TKey, TKeyError, TKeyUnexpectedAppErro
|
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
MAX_PAYLOAD_SIZE = 4096
|
|
19
18
|
|
|
20
19
|
# Static registry of signer binaries (filename, version)
|
|
21
20
|
# First binary in each list is the default binary.
|
|
22
21
|
_EMBEDDED_MLDSA_BINS: list[tuple[str, int]] = [
|
|
23
22
|
("pqsigner_v3.bin", 3),
|
|
24
23
|
]
|
|
25
|
-
|
|
26
24
|
_EMBEDDED_ED25519_BINS: list[tuple[str, int]] = [
|
|
27
25
|
("ed25519signer_v3.bin", 3),
|
|
28
26
|
]
|
|
27
|
+
_MLDSA_APP_NAME = ("tk1", "pqsn")
|
|
28
|
+
_ED25519_APP_NAME = ("tk1", "sign")
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
@dataclass
|
|
@@ -48,8 +48,8 @@ class SignApp:
|
|
|
48
48
|
|
|
49
49
|
@property
|
|
50
50
|
def digest(self) -> str:
|
|
51
|
-
"""Return the
|
|
52
|
-
return hashlib.
|
|
51
|
+
"""Return the SHA-512 hex digest of the application binary."""
|
|
52
|
+
return hashlib.sha512(self.binary).hexdigest()
|
|
53
53
|
|
|
54
54
|
@classmethod
|
|
55
55
|
def _find_binary(
|
|
@@ -65,7 +65,7 @@ class SignApp:
|
|
|
65
65
|
continue
|
|
66
66
|
|
|
67
67
|
binary = resources_dir.joinpath(filename).read_bytes()
|
|
68
|
-
file_digest = hashlib.
|
|
68
|
+
file_digest = hashlib.sha512(binary).hexdigest()
|
|
69
69
|
|
|
70
70
|
# Filter by digest if requested
|
|
71
71
|
if digest is not None and not file_digest.startswith(digest.lower()):
|
|
@@ -105,7 +105,7 @@ class SignApp:
|
|
|
105
105
|
|
|
106
106
|
Args:
|
|
107
107
|
version: The version of the signer application to load.
|
|
108
|
-
digest: A
|
|
108
|
+
digest: A SHA-512 hex digest (or prefix) of the target binary.
|
|
109
109
|
|
|
110
110
|
Returns:
|
|
111
111
|
An instance of SignApp configured with the loaded binary.
|
|
@@ -116,7 +116,7 @@ class SignApp:
|
|
|
116
116
|
"""
|
|
117
117
|
|
|
118
118
|
binary, version = cls._find_binary(version, digest, _EMBEDDED_MLDSA_BINS)
|
|
119
|
-
return cls(binary, version,
|
|
119
|
+
return cls(binary, version, _MLDSA_APP_NAME, 2420, 1312)
|
|
120
120
|
|
|
121
121
|
@classmethod
|
|
122
122
|
def load_ed25519(
|
|
@@ -132,11 +132,11 @@ class SignApp:
|
|
|
132
132
|
specific key must provide the binary digest.
|
|
133
133
|
|
|
134
134
|
Warning:
|
|
135
|
-
When Ed25519 is used, there is a
|
|
135
|
+
When Ed25519 is used, there is a 4096B size limit to signing payloads.
|
|
136
136
|
|
|
137
137
|
Args:
|
|
138
138
|
version: The version of the signer application to load.
|
|
139
|
-
digest: A
|
|
139
|
+
digest: A SHA-512 hex digest (or prefix) of the target binary.
|
|
140
140
|
|
|
141
141
|
Returns:
|
|
142
142
|
An instance of SignApp configured with the loaded binary.
|
|
@@ -146,7 +146,7 @@ class SignApp:
|
|
|
146
146
|
is ambiguous (matches multiple binaries).
|
|
147
147
|
"""
|
|
148
148
|
binary, version = cls._find_binary(version, digest, _EMBEDDED_ED25519_BINS)
|
|
149
|
-
return cls(binary, version,
|
|
149
|
+
return cls(binary, version, _ED25519_APP_NAME, 64, 32)
|
|
150
150
|
|
|
151
151
|
|
|
152
152
|
class SignRsp:
|
|
@@ -269,7 +269,7 @@ class TKeySign(TKey):
|
|
|
269
269
|
|
|
270
270
|
Args:
|
|
271
271
|
message: The raw bytes of the message/payload to sign. When Ed25519 keys
|
|
272
|
-
are used, there is a max message size of
|
|
272
|
+
are used, there is a max message size of 4096B. This limitation does
|
|
273
273
|
not apply to ML-DSA as FIPS 204 external mu is used.
|
|
274
274
|
pub_key: The public key bytes (only needed for ML-DSA). If not provided,
|
|
275
275
|
key is retrieved from device.
|
|
@@ -282,8 +282,7 @@ class TKeySign(TKey):
|
|
|
282
282
|
TKeyIOError: If writing or reading from the serial port fails.
|
|
283
283
|
TKeyProtocolError: If there is a framing or protocol mismatch.
|
|
284
284
|
"""
|
|
285
|
-
|
|
286
|
-
if self.name in [("tk1", "pqsn"), ("tk1", "pqnt")]:
|
|
285
|
+
if self.name == _MLDSA_APP_NAME:
|
|
287
286
|
# Compute FIPS 204 external mu
|
|
288
287
|
if pub_key is None:
|
|
289
288
|
pub_key = self.get_pubkey()
|
|
@@ -293,8 +292,8 @@ class TKeySign(TKey):
|
|
|
293
292
|
payload = message
|
|
294
293
|
|
|
295
294
|
# Set size
|
|
296
|
-
if len(payload) >
|
|
297
|
-
raise ValueError(f"Payload too large {len(payload)} >
|
|
295
|
+
if len(payload) > MAX_PAYLOAD_SIZE:
|
|
296
|
+
raise ValueError(f"Payload too large {len(payload)} > {MAX_PAYLOAD_SIZE}]")
|
|
298
297
|
self.send(SignCmd.SET_SIZE, len(payload).to_bytes(4, byteorder="little"))
|
|
299
298
|
|
|
300
299
|
# 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
|