Skip to content

Commit f599d96

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

File tree

20 files changed

+681
-253
lines changed

20 files changed

+681
-253
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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
class lib:
10+
"""Main entry point for PKCS#11 library."""
11+
12+
so: str
13+
manufacturer_id: str
14+
library_description: str
15+
initialized: bool
16+
17+
def __init__(self, so: str) -> None: ...
18+
@property
19+
def library_version(self) -> tuple[int, int]: ...
20+
@property
21+
def cryptoki_version(self) -> tuple[int, int]: ...
22+
def initialize(self) -> None: ...
23+
def finalize(self) -> None: ...
24+
def reinitialize(self) -> None: ...
25+
def unload(self) -> None: ...
26+
def get_slots(self, token_present: bool = False) -> list[Slot]: ...
27+
def get_tokens(
28+
self,
29+
token_label: str | None = None,
30+
token_serial: bytes | None = None,
31+
token_flags: Any | None = None,
32+
slot_flags: Any | None = None,
33+
mechanisms: Any | None = None,
34+
) -> Iterator[Token]: ...
35+
def get_token(self, **kwargs: Any) -> Token: ...
36+
def wait_for_slot_event(self, blocking: bool = True) -> Slot: ...
37+
def __str__(self) -> str: ...
38+
def __repr__(self) -> str: ...

pkcs11/attributes.py

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,46 @@
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, ObjectClass
109
from pkcs11.mechanisms import KeyType, Mechanism
1110

11+
# Type aliases for pack/unpack function pairs
12+
PackFunc = Callable[[Any], bytes]
13+
UnpackFunc = Callable[[bytes], Any]
14+
Handler = tuple[PackFunc, UnpackFunc]
15+
1216
# (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 = (
17+
_bool_struct = Struct("?")
18+
_ulong_struct = Struct("L")
19+
20+
handle_bool: Handler = (
21+
_bool_struct.pack,
22+
lambda v: False if len(v) == 0 else _bool_struct.unpack(v)[0],
23+
)
24+
handle_ulong: Handler = (_ulong_struct.pack, lambda v: _ulong_struct.unpack(v)[0])
25+
handle_str: Handler = (lambda s: s.encode("utf-8"), lambda b: b.decode("utf-8"))
26+
handle_date: Handler = (
1727
lambda s: s.strftime("%Y%m%d").encode("ascii"),
1828
lambda s: datetime.strptime(s.decode("ascii"), "%Y%m%d").date(),
1929
)
20-
handle_bytes = (bytes, bytes)
30+
handle_bytes: Handler = (bytes, bytes)
2131
# The PKCS#11 biginteger type is an array of bytes in network byte order.
2232
# If you have an int type, wrap it in biginteger()
23-
handle_biginteger = handle_bytes
33+
handle_biginteger: Handler = handle_bytes
2434

2535

26-
def _enum(type_):
36+
def _enum(type_: type[IntEnum]) -> Handler:
2737
"""Factory to pack/unpack ints into IntEnums."""
2838
pack, unpack = handle_ulong
2939

3040
return (lambda v: pack(int(v)), lambda v: type_(unpack(v)))
3141

3242

33-
ATTRIBUTE_TYPES = {
43+
ATTRIBUTE_TYPES: dict[Attribute, Handler] = {
3444
Attribute.ALWAYS_AUTHENTICATE: handle_bool,
3545
Attribute.ALWAYS_SENSITIVE: handle_bool,
3646
Attribute.APPLICATION: handle_str,
@@ -96,7 +106,7 @@ def _enum(type_):
96106
Map of attributes to (serialize, deserialize) functions.
97107
"""
98108

99-
ALL_CAPABILITIES = (
109+
ALL_CAPABILITIES: Final[tuple[Attribute, ...]] = (
100110
Attribute.ENCRYPT,
101111
Attribute.DECRYPT,
102112
Attribute.WRAP,
@@ -107,20 +117,29 @@ def _enum(type_):
107117
)
108118

109119

110-
def _apply_common(template, id_, label, store):
120+
def _apply_common(
121+
template: dict[Attribute, Any],
122+
id_: bytes | None,
123+
label: str | None,
124+
store: bool,
125+
) -> None:
111126
if id_:
112127
template[Attribute.ID] = id_
113128
if label:
114129
template[Attribute.LABEL] = label
115130
template[Attribute.TOKEN] = bool(store)
116131

117132

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

122141

123-
_capa_attr_to_mechanism_flag = {
142+
_capa_attr_to_mechanism_flag: Final[dict[Attribute, MechanismFlag]] = {
124143
Attribute.ENCRYPT: MechanismFlag.ENCRYPT,
125144
Attribute.DECRYPT: MechanismFlag.DECRYPT,
126145
Attribute.WRAP: MechanismFlag.WRAP,
@@ -136,7 +155,12 @@ class AttributeMapper:
136155
Class mapping PKCS#11 attributes to and from Python values.
137156
"""
138157

139-
def __init__(self):
158+
attribute_types: dict[Attribute, Handler]
159+
default_secret_key_template: dict[Attribute, Any]
160+
default_public_key_template: dict[Attribute, Any]
161+
default_private_key_template: dict[Attribute, Any]
162+
163+
def __init__(self) -> None:
140164
self.attribute_types = dict(ATTRIBUTE_TYPES)
141165
self.default_secret_key_template = {
142166
Attribute.CLASS: ObjectClass.SECRET_KEY,
@@ -158,33 +182,33 @@ def __init__(self):
158182
Attribute.SENSITIVE: True,
159183
}
160184

161-
def register_handler(self, key, pack, unpack):
185+
def register_handler(self, key: Attribute, pack: PackFunc, unpack: UnpackFunc) -> None:
162186
self.attribute_types[key] = (pack, unpack)
163187

164-
def _handler(self, key):
188+
def _handler(self, key: Attribute) -> Handler:
165189
try:
166190
return self.attribute_types[key]
167191
except KeyError as e:
168192
raise NotImplementedError(f"Can't handle attribute type {hex(key)}.") from e
169193

170-
def pack_attribute(self, key, value):
194+
def pack_attribute(self, key: Attribute, value: Any) -> bytes:
171195
"""Pack a Attribute value into a bytes array."""
172196
pack, _ = self._handler(key)
173197
return pack(value)
174198

175-
def unpack_attributes(self, key, value):
199+
def unpack_attributes(self, key: Attribute, value: bytes) -> Any:
176200
"""Unpack a Attribute bytes array into a Python value."""
177201
_, unpack = self._handler(key)
178202
return unpack(value)
179203

180204
def public_key_template(
181205
self,
182206
*,
183-
capabilities,
184-
id_,
185-
label,
186-
store,
187-
):
207+
capabilities: MechanismFlag | int,
208+
id_: bytes | None,
209+
label: str | None,
210+
store: bool,
211+
) -> dict[Attribute, Any]:
188212
template = self.default_public_key_template
189213
_apply_capabilities(
190214
template, (Attribute.ENCRYPT, Attribute.WRAP, Attribute.VERIFY), capabilities
@@ -195,11 +219,11 @@ def public_key_template(
195219
def private_key_template(
196220
self,
197221
*,
198-
capabilities,
199-
id_,
200-
label,
201-
store,
202-
):
222+
capabilities: MechanismFlag | int,
223+
id_: bytes | None,
224+
label: str | None,
225+
store: bool,
226+
) -> dict[Attribute, Any]:
203227
template = self.default_private_key_template
204228
_apply_capabilities(
205229
template,
@@ -212,11 +236,11 @@ def private_key_template(
212236
def secret_key_template(
213237
self,
214238
*,
215-
capabilities,
216-
id_,
217-
label,
218-
store,
219-
):
239+
capabilities: MechanismFlag | int,
240+
id_: bytes | None,
241+
label: str | None,
242+
store: bool,
243+
) -> dict[Attribute, Any]:
220244
return self.generic_key_template(
221245
self.default_secret_key_template,
222246
capabilities=capabilities,
@@ -227,13 +251,13 @@ def secret_key_template(
227251

228252
def generic_key_template(
229253
self,
230-
base_template,
254+
base_template: dict[Attribute, Any],
231255
*,
232-
capabilities,
233-
id_,
234-
label,
235-
store,
236-
):
256+
capabilities: MechanismFlag | int,
257+
id_: bytes | None,
258+
label: str | None,
259+
store: bool,
260+
) -> dict[Attribute, Any]:
237261
template = dict(base_template)
238262
_apply_capabilities(template, ALL_CAPABILITIES, capabilities)
239263
_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)