Skip to content

Commit 684d7cb

Browse files
author
Dominikus Gierlach
committed
Add PIN management functionality
Additional minor change: use chardet to detect character encoding of bytestrings and fallback replacement to prevent utf-8 decoding issues with specific certificates
1 parent fe57001 commit 684d7cb

File tree

5 files changed

+151
-5
lines changed

5 files changed

+151
-5
lines changed

pkcs11/_pkcs11.pyx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,96 @@ cdef class Session(HasFuncList, types.Session):
11481148
op.ingest_chunks(data)
11491149
return op.finish()
11501150

1151+
def set_pin(self, old_pin, new_pin):
1152+
cdef CK_ULONG old_pin_length
1153+
cdef CK_ULONG new_pin_length
1154+
cdef CK_OBJECT_HANDLE handle = self.handle
1155+
cdef CK_UTF8CHAR *old_pin_data
1156+
cdef CK_UTF8CHAR *new_pin_data
1157+
cdef CK_RV retval
1158+
1159+
if old_pin is None or new_pin is None:
1160+
raise ArgumentsBad("Set `user_pin`")
1161+
1162+
pin_old = old_pin.encode('utf-8')
1163+
pin_new = new_pin.encode('utf-8')
1164+
1165+
if pin_old and pin_new:
1166+
old_pin_data = pin_old
1167+
new_pin_data = pin_new
1168+
old_pin_length = len(pin_old)
1169+
new_pin_length = len(pin_new)
1170+
1171+
with nogil:
1172+
retval = self.funclist.C_SetPIN(handle, old_pin_data, old_pin_length, new_pin_data, new_pin_length)
1173+
assertRV(retval)
1174+
return True
1175+
1176+
return False
1177+
1178+
def init_pin(self, user_pin):
1179+
cdef CK_OBJECT_HANDLE handle = self.handle
1180+
cdef CK_UTF8CHAR *pin_data
1181+
cdef CK_ULONG pin_length
1182+
cdef CK_RV retval
1183+
1184+
if user_pin is None:
1185+
raise ArgumentsBad("Set `user_pin`")
1186+
1187+
pin = user_pin.encode('utf-8')
1188+
1189+
if pin:
1190+
pin_data = pin
1191+
pin_length = len(pin)
1192+
1193+
with nogil:
1194+
retval = self.funclist.C_InitPIN(handle, pin_data, pin_length)
1195+
1196+
assertRV(retval)
1197+
return True
1198+
1199+
return False
1200+
1201+
def logout(self):
1202+
cdef CK_OBJECT_HANDLE handle = self.handle
1203+
cdef CK_RV retval
1204+
1205+
with nogil:
1206+
retval = self.funclist.C_Logout(handle)
1207+
1208+
assertRV(retval)
1209+
return True
1210+
1211+
def login(self, user_pin, user_type=None):
1212+
cdef CK_OBJECT_HANDLE handle = self.handle
1213+
cdef CK_USER_TYPE final_user_type
1214+
cdef CK_ULONG pin_len
1215+
cdef CK_UTF8CHAR *pin_data
1216+
cdef CK_RV retval
1217+
1218+
pin = user_pin.encode("utf-8")
1219+
1220+
if pin is not None:
1221+
final_user_type = user_type if user_type is not None else CKU_USER
1222+
pin_data = pin
1223+
pin_len = len(pin)
1224+
1225+
print("Attempting login as:", final_user_type)
1226+
1227+
with nogil:
1228+
retval = self.funclist.C_Login(handle, final_user_type, pin_data, pin_len)
1229+
1230+
print("Login successful as:", final_user_type)
1231+
assertRV(retval)
1232+
return True
1233+
else:
1234+
pin_data = NULL
1235+
pin_len = 0
1236+
final_user_type = UserType.NOBODY
1237+
1238+
print("Logged in as:", final_user_type)
1239+
return False
1240+
11511241

11521242
cdef class ObjectHandleWrapper(HasFuncList):
11531243
"""

pkcs11/types.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from binascii import hexlify
88
from functools import cached_property
99

10+
import chardet
11+
1012
from pkcs11 import CancelStrategy
1113
from pkcs11.constants import (
1214
Attribute,
@@ -41,8 +43,17 @@ def __hash__(self):
4143

4244

4345
def _CK_UTF8CHAR_to_str(data):
44-
"""Convert CK_UTF8CHAR to string."""
45-
return data.rstrip(b"\0").decode("utf-8").rstrip()
46+
"""Convert _CK_UTF8CHAR_to_str to string using chardet for encoding detection."""
47+
cleaned_data = data.rstrip(b"\0")
48+
49+
detected_encoding = chardet.detect(cleaned_data)
50+
encoding = detected_encoding["encoding"] or "utf-8"
51+
52+
try:
53+
return cleaned_data.decode(encoding).rstrip()
54+
except UnicodeDecodeError:
55+
# If decoding fails, try utf-8 with default error replacement character as a last resort
56+
return cleaned_data.decode("utf-8", errors="replace").rstrip()
4657

4758

4859
def _CK_VERSION_to_tuple(data):
@@ -571,6 +582,18 @@ def digest(self, data, **kwargs):
571582

572583
return self._digest_generator(data, **kwargs)
573584

585+
def set_pin(self, old_pin, new_pin):
586+
raise NotImplementedError()
587+
588+
def init_pin(self, user_pin):
589+
raise NotImplementedError()
590+
591+
def login(self, user_pin, user_type=None):
592+
raise NotImplementedError()
593+
594+
def logout(self):
595+
raise NotImplementedError()
596+
574597
def _digest(self, data, mechanism=None, mechanism_param=None):
575598
raise NotImplementedError()
576599

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ classifiers = [
2323
"Programming Language :: Python :: 3.13",
2424
"Topic :: Security :: Cryptography",
2525
]
26-
dependencies = ["asn1crypto>=1.5.1"]
26+
dependencies = [
27+
"asn1crypto>=1.5.1",
28+
"chardet>=5.2.0",
29+
]
2730
license = "MIT"
2831
requires-python = ">=3.9"
2932
dynamic = ["version"]
@@ -119,4 +122,4 @@ dev = [
119122
{ include-group = "release" },
120123
]
121124

122-
[tool.setuptools_scm]
125+
[tool.setuptools_scm]

tests/test_sessions.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,20 @@ def _handler(self, key):
291291
bool_read = key[Attribute.ID]
292292
self.assertIsInstance(bool_read, bool)
293293
self.assertFalse(bool_read, False)
294+
295+
@Only.softhsm2
296+
def test_set_pin(self):
297+
old_token_pin = TOKEN_PIN
298+
new_token_pin = f"{TOKEN_PIN}56"
299+
300+
with self.token.open(rw=True, user_pin=old_token_pin) as session:
301+
self.assertTrue(session.set_pin(old_token_pin, new_token_pin))
302+
303+
with self.token.open(user_pin=new_token_pin) as session:
304+
self.assertIsInstance(session, pkcs11.Session)
305+
306+
with self.token.open(rw=True, user_pin=new_token_pin) as session:
307+
self.assertTrue(session.set_pin(new_token_pin, old_token_pin))
308+
309+
with self.token.open(user_pin=old_token_pin) as session:
310+
self.assertIsInstance(session, pkcs11.Session)

uv.lock

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)