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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keylet
3
- Version: 0.5.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 with a passphrase, and verify using the saved public key
50
- $ keylet --passphrase hunter2 pubkey --output pub.key
51
- $ keylet --passphrase hunter2 sign README.md
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 --passphrase hunter2 --digest 186bcf6 sign README.md
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 the [API Reference](https://jku.github.io/keylet/api/) for more details.
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 with a passphrase, and verify using the saved public key
32
- $ keylet --passphrase hunter2 pubkey --output pub.key
33
- $ keylet --passphrase hunter2 sign README.md
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 --passphrase hunter2 --digest 186bcf6 sign README.md
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 the [API Reference](https://jku.github.io/keylet/api/) for more details.
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.5.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
 
@@ -1,5 +1,6 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  # Copyright (c) 2026 keylet authors
3
3
 
4
- # ruff: noqa: F401
5
4
  from keylet.tkey_sign import SignApp, TKeySign
5
+
6
+ __all__ = ["SignApp", "TKeySign"]
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 keylet authors
@@ -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=args.passphrase) as signer:
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",
@@ -0,0 +1,4 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 keylet authors
3
+
4
+ # Package marker for resource files
@@ -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
- MU_SIZE = (64).to_bytes(4, byteorder="little")
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 BLAKE2s-256 hex digest of the application binary."""
52
- return hashlib.blake2s(self.binary, digest_size=32).hexdigest()
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.blake2s(binary, digest_size=32).hexdigest()
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 BLAKE2s-256 hex digest (or prefix) of the target binary.
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, ("tk1", "pqsn"), 2420, 1312)
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 4096K size limit to signing payloads.
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 BLAKE2s-256 hex digest (or prefix) of the target binary.
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, ("tk1", "sign"), 64, 32)
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 4096K. This limitation does
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
- # pqsn = ML-DSA signer, pqnt = no-touch ML-DSA test signer
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) > 4096:
297
- raise ValueError(f"Payload too large {len(payload)} > 4096]")
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