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 keys

  • just 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

HSM_MODULE

Path to PKCS#11 library

/usr/lib/softhsm/libsofthsm2.so

HSM_TOKEN_LABEL

Token label

hsmkey-test

HSM_PIN

User PIN

12345678

EDDSA_AVAILABLE

Whether EdDSA keys are available

true

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_MODULE to the Kryoptic library path

  • Sets EDDSA_AVAILABLE=false to skip EdDSA tests

  • Ignores 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:

  1. Lint - Runs type checking

  2. Test with SoftHSM2 - Full test suite with SoftHSM2

  3. Test with Kryoptic - Test suite with Kryoptic (EdDSA tests skipped)

  4. 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

  1. Fork the repository

  2. Create a feature branch

  3. Make your changes

  4. Run tests: just test

  5. Run linter: just lint

  6. Submit a pull request

Ensure all tests pass and code follows the project style before submitting.