Skip to content

Commit e004840

Browse files
committed
feat: adds typing
Now we have typing information added to the project and also enabled checking in CI.
1 parent 2c4c6c7 commit e004840

File tree

18 files changed

+572
-248
lines changed

18 files changed

+572
-248
lines changed

.github/workflows/quality.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,10 @@ jobs:
3030
run: uv run ruff format --diff .
3131

3232
- name: ruff check
33-
run: uv run ruff check --diff .
33+
run: uv run ruff check --diff .
34+
35+
- name: Install project dependencies for type checking
36+
run: uv pip install asn1crypto
37+
38+
- name: ty type check
39+
run: uv run ty check pkcs11/

MANIFEST.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
graft extern/
2-
include pkcs11/*.pxd
2+
include pkcs11/*.pxd
3+
include pkcs11/py.typed

pkcs11/__init__.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@
22
:mod:`pkcs11` defines a high-level, "Pythonic" interface to PKCS#11.
33
"""
44

5+
from __future__ import annotations
6+
7+
from typing import TYPE_CHECKING, Any
8+
59
from pkcs11.constants import * # noqa: F403
610
from pkcs11.exceptions import * # noqa: F403
711
from pkcs11.mechanisms import * # noqa: F403
812
from pkcs11.types import * # noqa: F403
913
from pkcs11.util import dh, dsa, ec, rsa, x509 # noqa: F401
1014

11-
_loaded = {}
15+
if TYPE_CHECKING:
16+
from pkcs11._pkcs11 import lib as _lib_type
17+
18+
19+
_loaded: dict[str, Any] = {}
1220

1321

14-
def lib(so):
22+
def lib(so: str) -> _lib_type:
1523
"""
1624
Wrap the main library call coming from Cython with a preemptive
1725
dynamic loading.
@@ -34,7 +42,7 @@ def lib(so):
3442
return _lib
3543

3644

37-
def unload(so):
45+
def unload(so: str) -> None:
3846
global _loaded
3947
try:
4048
loaded_lib = _loaded[so]

pkcs11/_pkcs11.pyi

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Type stubs for the _pkcs11 Cython extension module."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any, Iterator
6+
7+
from pkcs11.types import Slot, Token
8+
9+
10+
class lib:
11+
"""Main entry point for PKCS#11 library."""
12+
13+
so: str
14+
manufacturer_id: str
15+
library_description: str
16+
initialized: bool
17+
18+
def __init__(self, so: str) -> None: ...
19+
20+
@property
21+
def library_version(self) -> tuple[int, int]: ...
22+
23+
@property
24+
def cryptoki_version(self) -> tuple[int, int]: ...
25+
26+
def initialize(self) -> None: ...
27+
def finalize(self) -> None: ...
28+
def reinitialize(self) -> None: ...
29+
def unload(self) -> None: ...
30+
31+
def get_slots(self, token_present: bool = False) -> list[Slot]: ...
32+
def get_tokens(
33+
self,
34+
token_label: str | None = None,
35+
token_serial: bytes | None = None,
36+
token_flags: Any | None = None,
37+
slot_flags: Any | None = None,
38+
mechanisms: Any | None = None,
39+
) -> Iterator[Token]: ...
40+
def get_token(self, **kwargs: Any) -> Token: ...
41+
def wait_for_slot_event(self, blocking: bool = True) -> Slot: ...
42+
43+
def __str__(self) -> str: ...
44+
def __repr__(self) -> str: ...

pkcs11/attributes.py

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,47 @@
1+
from __future__ import annotations
2+
13
from datetime import datetime
4+
from enum import IntEnum
25
from struct import Struct
6+
from typing import Any, Callable, Final
37

4-
from pkcs11.constants import (
5-
Attribute,
6-
CertificateType,
7-
MechanismFlag,
8-
ObjectClass,
9-
)
8+
from pkcs11.constants import (Attribute, CertificateType, MechanismFlag,
9+
ObjectClass)
1010
from pkcs11.mechanisms import KeyType, Mechanism
1111

12+
# Type aliases for pack/unpack function pairs
13+
PackFunc = Callable[[Any], bytes]
14+
UnpackFunc = Callable[[bytes], Any]
15+
Handler = tuple[PackFunc, UnpackFunc]
16+
1217
# (Pack Function, Unpack Function) functions
13-
handle_bool = (Struct("?").pack, lambda v: False if len(v) == 0 else Struct("?").unpack(v)[0])
14-
handle_ulong = (Struct("L").pack, lambda v: Struct("L").unpack(v)[0])
15-
handle_str = (lambda s: s.encode("utf-8"), lambda b: b.decode("utf-8"))
16-
handle_date = (
18+
_bool_struct = Struct("?")
19+
_ulong_struct = Struct("L")
20+
21+
handle_bool: Handler = (
22+
_bool_struct.pack,
23+
lambda v: False if len(v) == 0 else _bool_struct.unpack(v)[0],
24+
)
25+
handle_ulong: Handler = (_ulong_struct.pack, lambda v: _ulong_struct.unpack(v)[0])
26+
handle_str: Handler = (lambda s: s.encode("utf-8"), lambda b: b.decode("utf-8"))
27+
handle_date: Handler = (
1728
lambda s: s.strftime("%Y%m%d").encode("ascii"),
1829
lambda s: datetime.strptime(s.decode("ascii"), "%Y%m%d").date(),
1930
)
20-
handle_bytes = (bytes, bytes)
31+
handle_bytes: Handler = (bytes, bytes)
2132
# The PKCS#11 biginteger type is an array of bytes in network byte order.
2233
# If you have an int type, wrap it in biginteger()
23-
handle_biginteger = handle_bytes
34+
handle_biginteger: Handler = handle_bytes
2435

2536

26-
def _enum(type_):
37+
def _enum(type_: type[IntEnum]) -> Handler:
2738
"""Factory to pack/unpack ints into IntEnums."""
2839
pack, unpack = handle_ulong
2940

3041
return (lambda v: pack(int(v)), lambda v: type_(unpack(v)))
3142

3243

33-
ATTRIBUTE_TYPES = {
44+
ATTRIBUTE_TYPES: dict[Attribute, Handler] = {
3445
Attribute.ALWAYS_AUTHENTICATE: handle_bool,
3546
Attribute.ALWAYS_SENSITIVE: handle_bool,
3647
Attribute.APPLICATION: handle_str,
@@ -96,7 +107,7 @@ def _enum(type_):
96107
Map of attributes to (serialize, deserialize) functions.
97108
"""
98109

99-
ALL_CAPABILITIES = (
110+
ALL_CAPABILITIES: Final[tuple[Attribute, ...]] = (
100111
Attribute.ENCRYPT,
101112
Attribute.DECRYPT,
102113
Attribute.WRAP,
@@ -107,20 +118,29 @@ def _enum(type_):
107118
)
108119

109120

110-
def _apply_common(template, id_, label, store):
121+
def _apply_common(
122+
template: dict[Attribute, Any],
123+
id_: bytes | None,
124+
label: str | None,
125+
store: bool,
126+
) -> None:
111127
if id_:
112128
template[Attribute.ID] = id_
113129
if label:
114130
template[Attribute.LABEL] = label
115131
template[Attribute.TOKEN] = bool(store)
116132

117133

118-
def _apply_capabilities(template, possible_capas, capabilities):
134+
def _apply_capabilities(
135+
template: dict[Attribute, Any],
136+
possible_capas: tuple[Attribute, ...],
137+
capabilities: MechanismFlag | int,
138+
) -> None:
119139
for attr in possible_capas:
120140
template[attr] = _capa_attr_to_mechanism_flag[attr] & capabilities
121141

122142

123-
_capa_attr_to_mechanism_flag = {
143+
_capa_attr_to_mechanism_flag: Final[dict[Attribute, MechanismFlag]] = {
124144
Attribute.ENCRYPT: MechanismFlag.ENCRYPT,
125145
Attribute.DECRYPT: MechanismFlag.DECRYPT,
126146
Attribute.WRAP: MechanismFlag.WRAP,
@@ -136,7 +156,12 @@ class AttributeMapper:
136156
Class mapping PKCS#11 attributes to and from Python values.
137157
"""
138158

139-
def __init__(self):
159+
attribute_types: dict[Attribute, Handler]
160+
default_secret_key_template: dict[Attribute, Any]
161+
default_public_key_template: dict[Attribute, Any]
162+
default_private_key_template: dict[Attribute, Any]
163+
164+
def __init__(self) -> None:
140165
self.attribute_types = dict(ATTRIBUTE_TYPES)
141166
self.default_secret_key_template = {
142167
Attribute.CLASS: ObjectClass.SECRET_KEY,
@@ -158,33 +183,33 @@ def __init__(self):
158183
Attribute.SENSITIVE: True,
159184
}
160185

161-
def register_handler(self, key, pack, unpack):
186+
def register_handler(self, key: Attribute, pack: PackFunc, unpack: UnpackFunc) -> None:
162187
self.attribute_types[key] = (pack, unpack)
163188

164-
def _handler(self, key):
189+
def _handler(self, key: Attribute) -> Handler:
165190
try:
166191
return self.attribute_types[key]
167192
except KeyError as e:
168193
raise NotImplementedError(f"Can't handle attribute type {hex(key)}.") from e
169194

170-
def pack_attribute(self, key, value):
195+
def pack_attribute(self, key: Attribute, value: Any) -> bytes:
171196
"""Pack a Attribute value into a bytes array."""
172197
pack, _ = self._handler(key)
173198
return pack(value)
174199

175-
def unpack_attributes(self, key, value):
200+
def unpack_attributes(self, key: Attribute, value: bytes) -> Any:
176201
"""Unpack a Attribute bytes array into a Python value."""
177202
_, unpack = self._handler(key)
178203
return unpack(value)
179204

180205
def public_key_template(
181206
self,
182207
*,
183-
capabilities,
184-
id_,
185-
label,
186-
store,
187-
):
208+
capabilities: MechanismFlag | int,
209+
id_: bytes | None,
210+
label: str | None,
211+
store: bool,
212+
) -> dict[Attribute, Any]:
188213
template = self.default_public_key_template
189214
_apply_capabilities(
190215
template, (Attribute.ENCRYPT, Attribute.WRAP, Attribute.VERIFY), capabilities
@@ -195,11 +220,11 @@ def public_key_template(
195220
def private_key_template(
196221
self,
197222
*,
198-
capabilities,
199-
id_,
200-
label,
201-
store,
202-
):
223+
capabilities: MechanismFlag | int,
224+
id_: bytes | None,
225+
label: str | None,
226+
store: bool,
227+
) -> dict[Attribute, Any]:
203228
template = self.default_private_key_template
204229
_apply_capabilities(
205230
template,
@@ -212,11 +237,11 @@ def private_key_template(
212237
def secret_key_template(
213238
self,
214239
*,
215-
capabilities,
216-
id_,
217-
label,
218-
store,
219-
):
240+
capabilities: MechanismFlag | int,
241+
id_: bytes | None,
242+
label: str | None,
243+
store: bool,
244+
) -> dict[Attribute, Any]:
220245
return self.generic_key_template(
221246
self.default_secret_key_template,
222247
capabilities=capabilities,
@@ -227,13 +252,13 @@ def secret_key_template(
227252

228253
def generic_key_template(
229254
self,
230-
base_template,
255+
base_template: dict[Attribute, Any],
231256
*,
232-
capabilities,
233-
id_,
234-
label,
235-
store,
236-
):
257+
capabilities: MechanismFlag | int,
258+
id_: bytes | None,
259+
label: str | None,
260+
store: bool,
261+
) -> dict[Attribute, Any]:
237262
template = dict(base_template)
238263
_apply_capabilities(template, ALL_CAPABILITIES, capabilities)
239264
_apply_common(template, id_, label, store)

pkcs11/constants.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
use these classes.
66
"""
77

8+
from __future__ import annotations
9+
810
from enum import IntEnum, IntFlag, unique
11+
from typing import Final
912

10-
DEFAULT = object()
13+
DEFAULT: Final[object] = object()
1114
"""Sentinel value used in templates.
1215
1316
Not all pkcs11 attribute sets are accepted by HSMs.
@@ -55,11 +58,11 @@ class ObjectClass(IntEnum):
5558

5659
_VENDOR_DEFINED = 0x80000000
5760

58-
def __repr__(self):
61+
def __repr__(self) -> str:
5962
return "<ObjectClass.%s>" % self.name
6063

6164

62-
_ARRAY_ATTRIBUTE = 0x40000000
65+
_ARRAY_ATTRIBUTE: Final[int] = 0x40000000
6366
"""Attribute consists of an array of values."""
6467

6568

@@ -343,7 +346,7 @@ class Attribute(IntEnum):
343346

344347
_VENDOR_DEFINED = 0x80000000
345348

346-
def __repr__(self):
349+
def __repr__(self) -> str:
347350
return "<Attribute.%s>" % self.name
348351

349352

0 commit comments

Comments
 (0)