Skip to content

Commit 68c850d

Browse files
authored
Merge pull request #1109 from Spinbazz/feat/hsm_login_before_import_pubkey
feat: allow hsm login before import pubkey (pubkey may be inaccessibl…
2 parents b0763f1 + 86e5d13 commit 68c850d

2 files changed

Lines changed: 66 additions & 5 deletions

File tree

securesystemslib/signer/_hsm_signer.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ def PYKCS11LIB(): # type: ignore[no-untyped-def] # noqa: N802
7474
return _PYKCS11LIB
7575

7676

77+
class KeyNotFoundError(ValueError):
78+
pass
79+
80+
7781
class HSMSigner(Signer):
7882
"""Hardware Security Module (HSM) Signer.
7983
@@ -220,7 +224,7 @@ def _find_key(
220224
]
221225
)
222226
if not keys:
223-
raise ValueError(f"could not find {_KEY_TYPE_ECDSA} key for {keyid}")
227+
raise KeyNotFoundError(f"could not find {_KEY_TYPE_ECDSA} key for {keyid}")
224228

225229
if len(keys) > 1:
226230
raise ValueError(f"found more than one {_KEY_TYPE_ECDSA} key for {keyid}")
@@ -266,6 +270,7 @@ def import_(
266270
cls,
267271
hsm_keyid: int | None = None,
268272
token_filter: dict[str, str] | None = None,
273+
pin_handler: SecretsHandler | None = None,
269274
) -> tuple[str, SSlibKey]:
270275
"""Import public key and signer details from HSM.
271276
@@ -305,9 +310,15 @@ def import_(
305310
token_filter = cls._build_token_filter()
306311

307312
uri = f"{cls.SCHEME}:{hsm_keyid}?{parse.urlencode(token_filter)}"
308-
309313
with cls._get_session(token_filter) as session:
310-
params, point = cls._find_key_values(session, hsm_keyid)
314+
try:
315+
params, point = cls._find_key_values(session, hsm_keyid)
316+
except KeyNotFoundError as e:
317+
if pin_handler is not None:
318+
session.login(pin_handler(cls.SECRETS_HANDLER_MSG))
319+
params, point = cls._find_key_values(session, hsm_keyid)
320+
else:
321+
raise e
311322

312323
if params.chosen.native not in _CURVE_NAMES:
313324
raise ValueError(

tests/test_hsm_signer.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import shutil
55
import tempfile
66
import unittest
7+
from unittest.mock import Mock
78

89
from asn1crypto.keys import (
910
ECDomainParameters,
@@ -31,18 +32,22 @@ class TestHSM(unittest.TestCase):
3132
hsm_keyid = 1
3233
hsm_keyid_default = 2
3334
hsm_keyid_odd = 258
35+
hsm_keyid_priv = 512
3436
hsm_user_pin = "123456"
3537

3638
@staticmethod
37-
def _generate_key_pair(session, keyid, curve):
39+
def _generate_key_pair(session, keyid, curve, is_pubkey_private=False):
3840
"Create ecdsa key pair on hsm"
3941
params = ECDomainParameters(name="named", value=NamedCurve(curve.name)).dump()
4042

4143
cka_id = list(keyid.to_bytes((keyid.bit_length() + 7) // 8 or 1, "big"))
4244

4345
public_template = [
4446
(PyKCS11.CKA_CLASS, PyKCS11.CKO_PUBLIC_KEY),
45-
(PyKCS11.CKA_PRIVATE, PyKCS11.CK_FALSE),
47+
(
48+
PyKCS11.CKA_PRIVATE,
49+
PyKCS11.CK_TRUE if is_pubkey_private else PyKCS11.CK_FALSE,
50+
),
4651
(PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
4752
(PyKCS11.CKA_ENCRYPT, PyKCS11.CK_FALSE),
4853
(PyKCS11.CKA_VERIFY, PyKCS11.CK_TRUE),
@@ -104,6 +109,9 @@ def setUpClass(cls):
104109
cls._generate_key_pair(session, cls.hsm_keyid, SECP256R1)
105110
cls._generate_key_pair(session, cls.hsm_keyid_default, SECP384R1)
106111
cls._generate_key_pair(session, cls.hsm_keyid_odd, SECP256R1)
112+
cls._generate_key_pair(
113+
session, cls.hsm_keyid_priv, SECP256R1, is_pubkey_private=True
114+
)
107115

108116
session.logout()
109117
session.closeSession()
@@ -147,6 +155,48 @@ def test_hsm_uri(self):
147155
with self.assertRaises(UnverifiedSignatureError):
148156
key.verify_signature(sig, b"NOT DATA")
149157

158+
def test_hsm_private_pubkey(self):
159+
"""Test PIN"""
160+
161+
# Test pub key not found without pin
162+
with self.assertRaises(Exception) as context:
163+
_, _ = HSMSigner.import_(self.hsm_keyid_priv, self.token_filter)
164+
self.assertTrue("could not fin" in context.exception)
165+
166+
# or with bad pin
167+
bad_pinh = Mock(return_value="987")
168+
with self.assertRaises(Exception) as context:
169+
_, _ = HSMSigner.import_(
170+
self.hsm_keyid_priv, self.token_filter, pin_handler=bad_pinh
171+
)
172+
self.assertTrue("could not fin" in context.exception)
173+
self.assertTrue(bad_pinh.assert_called_once)
174+
175+
# And check sign ok cases (good pin or not required pin)
176+
for hsm_keyid in [
177+
self.hsm_keyid,
178+
self.hsm_keyid_default,
179+
self.hsm_keyid_odd,
180+
self.hsm_keyid_priv,
181+
]:
182+
good_pinh = Mock(return_value=self.hsm_user_pin)
183+
_, key = HSMSigner.import_(
184+
hsm_keyid, self.token_filter, pin_handler=good_pinh
185+
)
186+
# Pin handler must be called only if public key is private
187+
if hsm_keyid == self.hsm_keyid_priv:
188+
good_pinh.assert_called_once()
189+
else:
190+
good_pinh.assert_not_called()
191+
192+
signer = HSMSigner(hsm_keyid, self.token_filter, key, good_pinh)
193+
194+
sig = signer.sign(b"DATA")
195+
key.verify_signature(sig, b"DATA")
196+
197+
with self.assertRaises(UnverifiedSignatureError):
198+
key.verify_signature(sig, b"NOT DATA")
199+
150200

151201
# Run the unit tests.
152202
if __name__ == "__main__":

0 commit comments

Comments
 (0)