Development Guide
This guide covers setting up a development environment for hsmkey, running tests, and understanding the test infrastructure.
Prerequisites
Before starting development, ensure you have the following installed:
Python 3.10 or later
uv - Fast Python package manager
just - Command runner
OpenSSL - For generating test keys
One of the following HSM backends: - SoftHSM2 (recommended for development) - Kryoptic (Rust-based PKCS#11 token)
Installing Prerequisites
Ubuntu/Debian:
# Install system packages
sudo apt-get update
sudo apt-get install -y softhsm2 opensc openssl
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install just
cargo install just
# Or use the package manager
sudo apt-get install -y just
Fedora/RHEL:
sudo dnf install softhsm opensc openssl
curl -LsSf https://astral.sh/uv/install.sh | sh
cargo install just
macOS:
brew install softhsm openssl just
curl -LsSf https://astral.sh/uv/install.sh | sh
Setting Up the Development Environment
1. Clone the Repository
git clone https://github.com/kushaldas/hsmkey
cd hsmkey
2. Create Virtual Environment and Install Dependencies
Using uv, create a virtual environment and install all dependencies:
# Sync all dependencies including dev extras
uv sync --extra dev
This installs:
Core dependencies (
cryptography,python-pkcs11)JWCrypto integration (
jwcrypto)Development tools (
pytest,pytest-cov,ruff,ty)
3. Configure SoftHSM2
Create the SoftHSM2 configuration:
# Create configuration directory
mkdir -p ~/.config/softhsm
mkdir -p ~/.local/share/softhsm/tokens
# Create configuration file
cat > ~/.config/softhsm/softhsm2.conf << EOF
directories.tokendir = $HOME/.local/share/softhsm/tokens
objectstore.backend = file
log.level = INFO
slots.removable = false
EOF
# Set environment variable (add to ~/.bashrc for persistence)
export SOFTHSM2_CONF="$HOME/.config/softhsm/softhsm2.conf"
4. Generate Test Keys and Initialize HSM
Use the provided justfile commands:
# Full setup: generate keys and import to HSM
just setup
This runs:
just recreate-keys- Generates RSA, EC, and EdDSA test keysjust import-keys- Imports keys to SoftHSM2
Running Tests
Basic Test Commands
# Run all tests
just test
# Run tests with verbose output
uv run pytest tests/ -v
# Run specific test file
uv run pytest tests/test_rsa.py -v
# Run specific test class
uv run pytest tests/test_jws_hsm.py::TestJWSRSASigning -v
# Run specific test
uv run pytest tests/test_jws_hsm.py::TestJWSRSASigning::test_rs256_sign -v
Test Coverage
# Run tests with coverage report
just test-cov
# Or directly with pytest
uv run pytest tests/ -v --cov=src/hsmkey --cov-report=term-missing
# Generate HTML coverage report
uv run pytest tests/ --cov=src/hsmkey --cov-report=html
# Open htmlcov/index.html in browser
Test Structure
The test suite is organized as follows:
tests/
├── conftest.py # Pytest fixtures and configuration
├── test_rsa.py # RSA key tests
├── test_ec.py # Elliptic curve key tests
├── test_ed25519.py # Ed25519 key tests
├── test_ed448.py # Ed448 key tests
├── test_jws_hsm.py # JWS signing/verification tests
├── test_jwe_hsm.py # JWE encryption/decryption tests
├── test_jwt_hsm.py # JWT token tests
└── test_integration.py # Integration and interoperability tests
Test Fixtures
The conftest.py file provides shared fixtures:
# HSM session fixture - provides authenticated session
def test_example(hsm_session):
key = HSMJWK.from_hsm(hsm_session, key_label="rsa-2048")
# ... use key
# Session pool fixture - for connection management tests
def test_pool(session_pool):
with session_pool.session() as session:
# ... use session
Environment Variables
Tests can be configured via environment variables:
Variable |
Description |
Default |
|---|---|---|
|
Path to PKCS#11 library |
|
|
Token label |
|
|
User PIN |
|
|
Whether EdDSA keys are available |
|
Test Markers
Custom pytest markers are used to conditionally skip tests:
@pytest.mark.requires_eddsa
def test_ed25519_signing(hsm_session):
"""This test is skipped when EDDSA_AVAILABLE=false."""
key = HSMJWK.from_hsm(hsm_session, key_label="ed25519")
# ...
The requires_eddsa marker automatically skips tests when EDDSA_AVAILABLE=false,
which is useful for testing with HSM backends that don’t support EdDSA.
Testing with Kryoptic
Kryoptic is an alternative Rust-based PKCS#11 software token. It requires additional setup.
Building Kryoptic
# Clone Kryoptic
cd ~
git clone https://github.com/latchset/kryoptic.git
cd kryoptic
# Check your OpenSSL version
openssl version
For systems with OpenSSL 3.2+ (Fedora 40+, etc.):
# Build with full EdDSA support
cargo build --release --no-default-features \
--features "sqlitedb,aes,ecdsa,ecdh,eddsa,ffdh,hash,hmac,hkdf,pbkdf2,rsa,sp800_108,dynamic"
With EdDSA support enabled, you can run the full test suite without skipping EdDSA tests.
For systems with OpenSSL < 3.2 (Ubuntu 24.04, Ubuntu 22.04, RHEL 9, etc.):
# Build without EdDSA
cargo build --release --no-default-features \
--features "sqlitedb,aes,ecdsa,ecdh,ffdh,hash,hmac,hkdf,pbkdf2,rsa,sp800_108,dynamic"
Note: Ubuntu 24.04 ships with OpenSSL 3.0.x, which does not support EdDSA in Kryoptic.
Setting Up Kryoptic
# Create configuration
just ensure-kryoptic-config
# Initialize token and import keys
just setup-kryoptic
Running Tests with Kryoptic
If you built Kryoptic with EdDSA support (OpenSSL 3.2+):
# Setup Kryoptic with RSA/EC keys
just setup-kryoptic
# Import EdDSA keys
just import-eddsa-kryoptic
# Run full test suite
just test-kryoptic-full
If you built Kryoptic without EdDSA support:
# Run tests against Kryoptic (skips EdDSA tests)
just test-kryoptic
The just test-kryoptic command automatically:
Sets
HSM_MODULEto the Kryoptic library pathSets
EDDSA_AVAILABLE=falseto skip EdDSA testsIgnores standalone EdDSA test files
Kryoptic and EdDSA Support
EdDSA (Ed25519/Ed448) support in Kryoptic depends on your OpenSSL version:
Distribution |
OpenSSL Version |
EdDSA Support |
|---|---|---|
Fedora 40+ |
3.2+ |
Full support |
Ubuntu 24.04 |
3.0.13 |
Not supported |
Ubuntu 22.04 |
3.0.x |
Not supported |
RHEL 9 / Rocky 9 |
3.0.x |
Not supported |
To check your OpenSSL version:
openssl version
If you have OpenSSL 3.2+, rebuild Kryoptic with the eddsa feature to enable
full EdDSA support.
Available Justfile Commands
Run just to see all available commands:
just # List all commands
Key Management:
just recreate-keys # Generate test keys on disk
just init-hsm # Initialize SoftHSM2 token
just import-keys # Import keys to SoftHSM2
just list-keys # List keys in HSM
just delete-hsm # Delete the test token
just setup # Full setup (recreate-keys + import-keys)
just reset # Delete token and reimport keys
Testing:
just test # Run all tests
just test-cov # Run tests with coverage
just lint # Run type checker
Kryoptic:
just ensure-kryoptic-config # Create Kryoptic config
just init-kryoptic # Initialize Kryoptic token
just import-keys-kryoptic # Import RSA/EC keys to Kryoptic
just import-eddsa-kryoptic # Import EdDSA keys (requires OpenSSL 3.2+)
just list-keys-kryoptic # List keys in Kryoptic
just setup-kryoptic # Full Kryoptic setup (without EdDSA)
just test-kryoptic # Run tests without EdDSA
just test-kryoptic-full # Run full tests with EdDSA (OpenSSL 3.2+)
just clean-kryoptic # Clean Kryoptic storage
Documentation:
just docs # Build Sphinx documentation
just docs-serve # Build and serve docs locally
Cleanup:
just clean # Clean generated files
Code Style and Linting
Type Checking
# Run type checker
just lint
# Or directly
uv run ty check src/
Note: The python-pkcs11 library lacks type stubs, so some type errors are expected.
Code Formatting
The project uses ruff for linting and formatting:
# Check formatting
uv run ruff check src/ tests/
# Auto-fix issues
uv run ruff check --fix src/ tests/
# Format code
uv run ruff format src/ tests/
Building Documentation
The documentation uses Sphinx with the Read the Docs theme:
# Install docs dependencies
uv sync --extra docs
# Build HTML documentation
just docs
# Or manually
cd docs && uv run make html
# View locally
open docs/_build/html/index.html
Building the Package
To build the package for distribution:
# Build wheel and sdist
uv build
# Output in dist/
ls dist/
Continuous Integration
The project uses GitHub Actions for CI. The workflow:
Lint - Runs type checking
Test with SoftHSM2 - Full test suite with SoftHSM2
Test with Kryoptic - Test suite with Kryoptic (EdDSA tests skipped)
Build - Builds the package
CI Configuration
The CI automatically handles:
Installing SoftHSM2 or building Kryoptic
Setting up HSM tokens
Importing test keys
Running tests with appropriate environment variables
For Kryoptic tests, EDDSA_AVAILABLE=false is set to skip EdDSA tests.
Troubleshooting
Common Issues
“HSM module not found”
# Check SoftHSM2 installation
ls -la /usr/lib/softhsm/libsofthsm2.so
# or
ls -la /usr/lib64/softhsm/libsofthsm2.so
# Set correct path
export HSM_MODULE="/path/to/libsofthsm2.so"
“Token not found”
# Ensure SOFTHSM2_CONF is set
export SOFTHSM2_CONF="$HOME/.config/softhsm/softhsm2.conf"
# List available tokens
softhsm2-util --show-slots
# Reinitialize if needed
just reset
“Key not found”
# List keys in token
just list-keys
# Reimport keys
just import-keys
“EdDSA tests failing with Kryoptic”
EdDSA (Ed25519/Ed448) requires OpenSSL 3.2+ with Kryoptic. On older systems:
# Run with EdDSA tests skipped
EDDSA_AVAILABLE=false just test-kryoptic
Contributing
Fork the repository
Create a feature branch
Make your changes
Run tests:
just testRun linter:
just lintSubmit a pull request
Ensure all tests pass and code follows the project style before submitting.