Skip to main content

Offline RSA-signed license management — CLI, GUI, and Python API.

Project description

py-Rizmi Licensing

py-Rizmi Licensing

CI PyPI Python >=3.12 MIT

Offline software licensing toolkit using RSA-signed JWT tokens.
Generate license keys, validate them against machine fingerprints (HWID),
and manage the full lifecycle — via PyQt6 GUI, CLI, or Python API.


Table of Contents

  1. Overview
  2. Features
  3. Architecture
  4. Quick Start
  5. GUI Usage Guide
  6. CLI Scripts
  7. Integration Workflow
  8. Testing
  9. Building an Executable
  10. Project Structure
  11. Contributing
  12. License

Overview

py-Rizmi Licensing enables you to issue, validate, and inspect offline RSA-signed license files using JWT tokens. It is built for deployment environments where internet access is unavailable — the license is validated locally using a public key and a hardware fingerprint (HWID).

The entire core layer is pure Python with zero GUI dependencies, making it suitable for integration into any Python application or web backend.


Features

  • RSA Keypair Management — Generate (2048/3072/4096-bit), load, paste, and validate RSA keypairs. Verify that private and public keys match.
  • Machine Fingerprinting — Deterministic SHA-256 hardware ID based on the OS-level machine identifier. Pluggable via FingerprintProvider Protocol.
  • License Issuance — Sign arbitrary payload fields into a JWT token and save as a .lic file with schema_version for future-proofing.
  • License Validation — Verify signature, expiration, and HWID match on any machine.
  • License Viewer — Decode and inspect any .lic file with the matching public key — no private key needed.
  • Integration Guide — In-app rendered README with Python API docs and backend integration examples.
  • Public API — Curated __all__ surface (LicenseValidator, LicenseIssuer, KeyPair, MachineFingerprint, LicensePayload) covered by SemVer.
  • PyQt6 GUI — Sidebar-navigated desktop application for cross-platform use.
  • CLI — Headless issuance, key generation, validation, and HWID retrieval via rizmi commands (Typer + Rich).
  • Backend Module — Drop-in validation function for app-server integration.
  • Fully Tested — 36+ pytest tests with Hypothesis property tests, contract tests, and ruff + mypy enforcement.

Architecture

py_rizmi/core/          ← Pure Python. No GUI. 100% testable.
py_rizmi/models/        ← LicensePayload dataclass with schema_version.
py_rizmi/integrations/  ← Server-side validation helper.
py_rizmi/gui/           ← PyQt6 widgets. Depends on core/. Optional [gui] extra.
py_rizmi/cli/           ← Typer CLI (rizmi command). Depends on core/.
py_rizmi/_internal/     ← Private implementation — never import directly.
scripts/                ← Deprecated CLI wrappers (use rizmi command instead).
tests/                  ← pytest suite. Imports only core/.

Fingerprint sources implement the FingerprintProvider Protocol, making the fingerprinting layer extensible without modifying core code.

Every payload field is a bound input widget — there is zero hard-coded payload data anywhere in the codebase.


Quick Start

# Recommended — with uv (uses the lockfile for reproducible installs)
uv sync --extra dev          # development (core + test tools)
uv sync --extra gui          # with GUI support
uv sync --extra all          # everything

# With pip (manually resolve dependencies)
pip install py-rizmi          # core only
pip install py-rizmi[gui]     # with GUI
pip install -e ".[dev]"       # local development

You have two ways to use the toolkit:

🖥️ GUI Mode

Launch the full desktop application with sidebar navigation:

python main.py

All features — key generation, license issuance, viewer, and the integration guide — are accessible through the interface.

⌨️ CLI Mode

The rizmi command (Typer + Rich) provides headless access to all features:

# Generate an RSA keypair
rizmi keys generate \
  --private-out keys/private_key.pem \
  --public-out keys/public_key.pem \
  --key-size 2048

# Get this machine's hardware fingerprint
rizmi machine-id

# Issue a signed license file
rizmi license issue \
  --private-key keys/private_key.pem \
  --output license.lic \
  --client "Acme Corp" \
  --license-id "deploy-001" \
  --hwid "<paste-the-hwid-here>" \
  --features billing reports \
  --max-clients 10 \
  --grace-days 14 \
  --exp-days 365

# Validate and inspect a license
rizmi license validate license.lic --public-key keys/public_key.pem
rizmi license inspect license.lic --public-key keys/public_key.pem

Legacy scripts: The old python scripts/*.py wrappers still work but are deprecated. Use the rizmi command instead.


GUI Usage Guide

The application opens a window with a sidebar on the left and a content area on the right. Click any navigation item to switch views.

Machine ID

Navigate to Machine ID in the sidebar, then:

  1. Click Generate Machine ID.
  2. The raw fingerprint and SHA-256 hash are displayed.
  3. Click Copy HWID and send this hash to your license issuer.

Key Management

Navigate to Key Management in the sidebar. Generate, load, and validate RSA keypairs.

  1. ① Generate Keypair — Select key size (2048, 3072, or 4096) and click Generate. The private and public PEM are displayed in read-only text areas. Use Save and Copy to export or copy each key.
  2. ② Load Keys — Browse for existing .pem files on disk, or Paste PEM content from the clipboard.
  3. ③ Validate Pair — Click Validate Keypair to confirm both PEMs belong together. Result shows key size or a mismatch error.

License Generation

Navigate to License Generation in the sidebar.

  1. ① Signing Key — Browse for an existing private key .pem file.
  2. ② License Payload — Fill in every field:
    • Client / Deployment (required)
    • License ID (required)
    • HWID (required — click ← Tab 1 to pull from Machine ID view)
    • Features (add/remove dynamically)
    • Max Clients, Mode, Server URL, Grace Days
    • Issued At (iat) and Expiration (exp) — auto or manual.
  3. Click Preview Payload (JSON) to inspect the data before signing.
  4. Click Generate License and save the .lic file.

License Viewer

Navigate to License Viewer in the sidebar.

  1. Select the matching public key .pem file.
  2. Select the license file .lic to inspect.
  3. Click Decode & View — all fields are displayed read-only, along with expiry status and days remaining.

Integration Guide

In-app rendered view of this README — Python API docs, CLI usage, and backend integration instructions.


CLI Commands

The rizmi CLI is the recommended interface for headless operations. All commands support --help:

rizmi --help
rizmi keys --help
rizmi license --help
rizmi machine-id --help

Deprecated Scripts

The old scripts in scripts/ are frozen and will be removed in a future release. Migrate to the rizmi commands above:

Old script New command
python scripts/gen_keypair.py rizmi keys generate
python scripts/get_machine_id.py rizmi machine-id
python scripts/issue_license.py rizmi license issue

Integration Workflow — From Start to Finish

The recommended path is to use the GUI (python main.py) for interactive tasks and fall back to CLI commands (rizmi ...) when you need to automate or work on a headless server.

Step 1 — Generate an RSA Keypair

GUI (recommended): Open the app → Key Management view → pick a key size → click GenerateSave both .pem files.

CLI (headless/automation):

rizmi keys generate \
  --private-out keys/private_key.pem \
  --public-out keys/public_key.pem \
  --key-size 2048

🔒 private_key.pem stays on your authoring machine — never ship it.

Step 2 — Get the Machine HWID

Run this on the target machine where the licensed app will be deployed.

GUI (recommended): Open the app → Machine ID view → click Generate Machine IDCopy HWID.

CLI (headless server):

rizmi machine-id
# HWID (SHA-256): fb50b7767d233a9ecc952dd9c11760586b3bd1a40d6bfbec051a312f0b51c77c

Send this hash to your license author.

Step 3 — Issue a License

GUI (recommended): Open the app → License Generation view → select the private key, fill in the payload fields (including the HWID from Step 2), add features, set dates → Generate License.

CLI (headless/automation):

rizmi license issue \
  --private-key keys/private_key.pem \
  --output license.lic \
  --client "Acme Corp" \
  --license-id "deploy-001" \
  --hwid "<paste-hwid-here>" \
  --features billing reports \
  --max-clients 10 \
  --exp-days 365

Deliver public_key.pem and license.lic to the developer integrating the app.

Step 4 — Integrate Validation Into Your App

The developer embeds public_key.pem and license.lic and validates at startup — no GUI or script needed here, just the Python API:

from py_rizmi import LicenseValidator

validator = LicenseValidator.from_file("path/to/public_key.pem")

try:
    payload = validator.validate("path/to/license.lic")
    print(f"Licensed to {payload.client}")
except ValueError as e:
    print(f"License invalid: {e}")

Step 5 — Server-Side Drop-In (Optional)

For apps with a validation server:

from py_rizmi.integrations.validation import validate_license

try:
    payload = validate_license("/path/to/config/dir")
    # config dir must contain public_key.pem and license.lic
    print(payload["client"], payload["features"])
except ValueError as e:
    print(f"License invalid: {e}")

Testing

# Fast unit tests (no GUI dependencies)
uv run pytest -p no:qt tests/unit -v

# Full test suite (requires PyQt6 + system libEGL)
uv run pytest -v

# Linting & type checking
uv run ruff check .
uv run mypy src

# With pip (no lockfile)
pip install -e ".[dev,gui]"
pytest -v

All core tests cover the public API without any GUI dependencies.


Building an Executable

This project uses Nuitka to compile the Python code into a standalone native executable for Linux or Windows.

Prerequisites

# Nuitka is a dev dependency
uv sync --extra dev

# Linux: gcc / g++ must be installed
sudo apt install gcc g++ python3-dev  # Debian / Ubuntu

# Windows: Download and install MSVC from Visual Studio Build Tools

Build

# Standalone folder (recommended — faster build, easier debugging)
bash build.sh standalone

# Single executable (longer build, larger file)
bash build.sh onefile

Output goes to dist/py-rizmi/.

Cross-platform note: Build on each target OS separately. Linux builds produce Linux binaries, Windows builds produce .exe. Use GitHub Actions with matrix runners (ubuntu, windows) to automate this.

What Gets Bundled

Resource How Why
media/logo.png --include-data-dir Window icon & in-app logo
README.md --include-data-file Integration Guide view
PyQt6, qdarktheme, markdown, PyJWT, cryptography Auto-detected by Nuitka Runtime dependencies

Project Structure

py-rizmi/
├── main.py                          # GUI entry point
├── pyproject.toml                   # Hatchling + hatch-vcs build config
├── build.sh                         # Nuitka build script
├── CHANGELOG.md                     # Keep-a-Changelog
├── CONTRIBUTING.md                  # Development guide
├── docs/
│   ├── api-stability.md             # SemVer policy
│   └── adr/
│       └── 0001-pyqt6-licensing.md  # Architecture Decision Record
├── .github/workflows/ci.yml         # CI: lint + fast tests + full tests
├── media/
│   └── logo.png                     # Application logo
├── src/
│   └── py_rizmi/
│       ├── __init__.py              # Public API (__all__)
│       ├── _version.py              # hatch-vcs generated (gitignored)
│       ├── core/                    # Pure logic — no GUI
│       │   ├── crypto.py            # RSA primitives
│       │   ├── hwid.py              # Machine fingerprint + FingerprintProvider Protocol
│       │   ├── keypair.py           # RSA keypair management
│       │   ├── license_issuer.py    # Token signing
│       │   └── license_validator.py # Token validation + decode
│       ├── models/
│       │   └── license_payload.py   # LicensePayload dataclass with schema_version
│       ├── gui/                     # PyQt6 GUI (optional [gui] extra)
│       │   ├── app.py               # Main window + sidebar
│       │   ├── theme.py             # Styling & theming
│       │   ├── views/
│       │   │   ├── hwid_view.py
│       │   │   ├── keymanager_view.py
│       │   │   ├── generate_view.py
│       │   │   ├── viewer_view.py
│       │   │   └── guide_view.py
│       │   └── widgets/
│       │       ├── step_card.py
│       │       └── dynamic_list.py
│       ├── integrations/
│       │   └── validation.py        # Server-side validation helper
│       ├── cli/                     # Typer CLI (rizmi command)
│       │   ├── app.py
│       │   └── commands/
│       └── _internal/               # Private — never import directly
│           └── logging.py
├── scripts/                         # Legacy CLI wrappers (deprecated)
│   ├── gen_keypair.py
│   ├── get_machine_id.py
│   └── issue_license.py
├── keys/                            # Generated keys (gitignored)
│   └── .gitkeep
└── tests/                           # pytest suite
    ├── conftest.py
    ├── test_hwid.py
    ├── test_keypair.py
    ├── test_license_issuer.py
    ├── test_license_validator.py
    ├── unit/models/test_license_payload.py
    └── gui/

Contributing

See CONTRIBUTING.md for the full development guide — setup, project layout, code style (ruff + mypy), testing, and the deprecation-shim pattern for public API changes.

Reporting Issues

  • Use the GitHub issue tracker.
  • Include the full error output and steps to reproduce.
  • Mention your Python version and operating system.

Ideas for Contributions

See Phase 11 in the roadmap for the planned post-1.0 features: key rotation, online validation, certificate revocation lists, and tamper-evident audit logs.


License

This project is provided under the MIT License. See the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_rizmi-1.0.1rc1.tar.gz (207.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_rizmi-1.0.1rc1-py3-none-any.whl (36.1 kB view details)

Uploaded Python 3

File details

Details for the file py_rizmi-1.0.1rc1.tar.gz.

File metadata

  • Download URL: py_rizmi-1.0.1rc1.tar.gz
  • Upload date:
  • Size: 207.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for py_rizmi-1.0.1rc1.tar.gz
Algorithm Hash digest
SHA256 ce205b72103b3948bebb573b21619e411978b380db387698d64a15bfbbb9a34c
MD5 e7be970498f0fa0f0ec55273d7379a41
BLAKE2b-256 1de974b4097d80d6e5a2ebf1a758574d118148ae4d1a5d0a4a6637aa056782f9

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_rizmi-1.0.1rc1.tar.gz:

Publisher: release.yml on Ramzi-Hadrouk/py-rizmi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file py_rizmi-1.0.1rc1-py3-none-any.whl.

File metadata

  • Download URL: py_rizmi-1.0.1rc1-py3-none-any.whl
  • Upload date:
  • Size: 36.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for py_rizmi-1.0.1rc1-py3-none-any.whl
Algorithm Hash digest
SHA256 fb87bb2e5d68547a78c6d4b986bc41dc46d32a6cbc039aedcaf59e29cacb971c
MD5 420b3c896b67068ab871ebb737738cea
BLAKE2b-256 de535c5ba4d13b77e0a90b30004dd882a563b8b66758b8d9256730bc8a35605d

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_rizmi-1.0.1rc1-py3-none-any.whl:

Publisher: release.yml on Ramzi-Hadrouk/py-rizmi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page