Skip to content

Commit 9e10f60

Browse files
committed
Add PKCS11 token support with tests and documentation
1 parent 72d588a commit 9e10f60

File tree

4 files changed

+53
-28
lines changed

4 files changed

+53
-28
lines changed

docs/devices/pkcs11.rst

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,9 @@ On Windows, you'll need:
2323
2. OpenSSL development headers
2424
- Download from: https://slproweb.com/products/Win32OpenSSL.html
2525
- Choose the "Win64 OpenSSL" version
26-
- During installation, select "Copy OpenSSL DLLs to Windows system directory"
27-
28-
3. The PKCS#11 library for your HSM (usually a .dll file)
29-
- Place the .dll file in a system path (e.g., C:\Windows\System32)
30-
- Or specify its path using the PKCS11_LIB_PATH environment variable
31-
32-
Installation Steps for Windows:
26+
- Ensure the OpenSSL bin directory is on PATH (avoid copying DLLs into Windows system directories)3. The PKCS#11 library for your HSM (usually a .dll file)
27+
- Prefer specifying its absolute path via PKCS11_LIB_PATH or placing it alongside the application.
28+
- Avoid copying into C:\Windows\System32 to reduce DLL hijack and servicing risk.Installation Steps for Windows:
3329

3430
1. Install the prerequisites in the order listed above
3531

@@ -50,10 +46,11 @@ The following environment variables can be used to configure the PKCS#11 device:
5046
- ``PKCS11_TOKEN_LABEL``: Label of the token to use (default: "Bitcoin")
5147

5248
Usage
53-
-----
54-
55-
1. Set up your environment variables:
49+
- ``PKCS11_LIB_PATH``: Path to the PKCS#11 library (required)
50+
- ``PKCS11_TOKEN_LABEL``: Label of the token to use (default: "Bitcoin")
51+
- ``PKCS11_PIN``: User PIN for token login (optional; prefer interactive prompt over env for security)
5652

53+
CLI flags, when provided, should take precedence over environment variables.
5754
.. code-block:: powershell
5855
# On Windows (PowerShell):
5956
$env:PKCS11_LIB_PATH = "C:\path\to\your\pkcs11\library.dll"

hwilib/commands.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -593,22 +593,37 @@ def install_udev_rules(source: str, location: str) -> Dict[str, bool]:
593593
return {"success": UDevInstaller.install(source, location)}
594594
raise NotImplementedError("udev rules are not needed on your platform")
595595

596+
from .key import ExtendedKey
597+
from .errors import HWWError
598+
596599
class PKCS11Client(HardwareWalletClient):
597-
def __init__(self, path: str, password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN) -> None:
600+
def __init__(
601+
self,
602+
path: str,
603+
password: Optional[str] = None,
604+
expert: bool = False,
605+
chain: Chain = Chain.MAIN,
606+
token_label: str = "Bitcoin",
607+
master_key_label: str = "MASTER_KEY"
608+
) -> None:
598609
super(PKCS11Client, self).__init__(path, password, expert, chain)
599610

600-
# Initialize PKCS11 library and token
601-
self.lib = pkcs11.lib(path) # path should point to the PKCS11 library
602-
self.token = self.lib.get_token(token_label='YOUR_TOKEN_LABEL')
603-
self.session = self.token.open(user_pin=password)
604-
605-
# Find the master key
606-
self.master_key = self.session.get_key(
607-
object_class=ObjectClass.PRIVATE_KEY,
608-
key_type=KeyType.EC,
609-
label='MASTER_KEY'
610-
)
611-
611+
try:
612+
# Initialize PKCS11 library and token
613+
self.lib = pkcs11.lib(path)
614+
self.token = self.lib.get_token(token_label=token_label)
615+
self.session = self.token.open(user_pin=password)
616+
617+
# Find the master key
618+
self.master_key = self.session.get_key(
619+
object_class=ObjectClass.PRIVATE_KEY,
620+
key_type=KeyType.EC,
621+
label=master_key_label
622+
)
623+
except Exception as e:
624+
if hasattr(self, 'session'):
625+
self.session.close()
626+
raise HWWError(f"Failed to initialize PKCS11 client: {e}")
612627
def get_pubkey_at_path(self, bip32_path: str) -> ExtendedKey:
613628
# Implement BIP32 path derivation and get public key
614629
# You'll need to implement BIP32 path derivation logic

test/run_tests.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
from test_jade import jade_test_suite
1919
from test_bitbox02 import bitbox02_test_suite
2020
from test_udevrules import TestUdevRulesInstaller
21-
from test_pkcs11 import TestPKCS11Client
21+
try:
22+
from test_pkcs11 import TestPKCS11Client
23+
HAS_PKCS11_TESTS = True
24+
except ImportError:
25+
TestPKCS11Client = None
26+
HAS_PKCS11_TESTS = False
2227

2328
parser = argparse.ArgumentParser(description='Setup the testing environment and run automated tests')
2429
trezor_group = parser.add_mutually_exclusive_group()
@@ -81,7 +86,11 @@
8186
success = True
8287
suite = unittest.TestSuite()
8388
if not args.device_only:
84-
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestDescriptor))
89+
if args.pkcs11:
90+
if HAS_PKCS11_TESTS:
91+
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestPKCS11Client))
92+
else:
93+
print("Skipping PKCS11 tests: test_pkcs11 or dependencies not available.", file=sys.stderr)
8594
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestSegwitAddress))
8695
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestPSBT))
8796
suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(TestBase58))

test/test_pkcs11.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,22 @@ def test_initialization(self, mock_pkcs11):
5353
def test_get_pubkey_at_path(self, mock_pkcs11):
5454
"""Test getting public key at BIP32 path."""
5555
mock_pkcs11.lib.return_value = self.mock_lib
56+
# Provide pkcs11.Attribute constants used by the client
57+
mock_pkcs11.Attribute = MagicMock(EC_POINT='EC_POINT', EC_PARAMS='EC_PARAMS')
5658

5759
# Mock key attributes
5860
self.mock_session.get_key.return_value = MagicMock(
59-
get_attribute=lambda x: b'test_pubkey' if x == 'EC_POINT' else b'test_chaincode'
61+
get_attribute=lambda attr: {
62+
'EC_POINT': b'\x02' + b'\x11' * 32, # compressed secp256k1 pubkey
63+
'EC_PARAMS': b'\x06\x05\x2b\x81\x04\x00\x0a', # secp256k1 OID
64+
}.get(attr)
6065
)
6166

6267
client = PKCS11Client(self.path, self.password, self.expert, self.chain)
6368
result = client.get_pubkey_at_path("m/44'/0'/0'/0/0")
6469

6570
self.assertIsInstance(result, ExtendedKey)
66-
self.assertEqual(result.key_data, b'test_pubkey')
67-
71+
self.assertEqual(result.key, b'\x02' + b'\x11' * 32)
6872
@patch('hwilib.devices.pkcs11.pkcs11')
6973
def test_sign_tx(self, mock_pkcs11):
7074
"""Test transaction signing."""

0 commit comments

Comments
 (0)