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 Reference
  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 — 45+ pytest tests with Hypothesis property tests, contract tests, regression tests, e2e 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.
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:

# Recommended (installed package)
rizmi gui

# Alternative (run directly from repo)
python main.py

Requires the [gui] extra. If not installed, rizmi gui will print a friendly install hint instead of crashing. Install with:

pip install py-rizmi[gui]
# or with uv:
uv sync --extra gui

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 --features 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

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 Reference

The rizmi CLI is the recommended interface for all headless operations. Every command supports --help / -h:

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

Key Management — rizmi keys

Command Description
rizmi keys generate Generate a new RSA keypair (2048 / 3072 / 4096 bits)
rizmi keys inspect <key.pem> Show key type, size, and fingerprint
rizmi keys verify --private ... --public ... Verify a private/public key pair matches

License Operations — rizmi license

Command Description
rizmi license issue Sign and write a .lic token file
rizmi license validate <file.lic> Validate signature, expiry, and HWID
rizmi license inspect <file.lic> Decode and display all payload fields

Machine Fingerprint — rizmi machine-id

rizmi machine-id           # rich panel output
rizmi machine-id --raw     # plain hash only (for piping)
rizmi machine-id --copy    # copy to clipboard

GUI — rizmi gui

rizmi gui                  # launch the PyQt6 desktop application

Requires the [gui] extra (pip install py-rizmi[gui]). Without it, the command exits with code 1 and prints a clear install hint.

Full Example Workflow

# 1. Generate keys
rizmi keys generate --private-out keys/private.pem --public-out keys/public.pem

# 2. Get HWID of the target machine
rizmi machine-id --raw

# 3. Issue a license
rizmi license issue \
  --private-key keys/private.pem \
  --output license.lic \
  --client "Acme Corp" \
  --license-id "deploy-001" \
  --hwid "<hwid-from-step-2>" \
  --features billing --features reports \
  --exp-days 365

# 4. Validate on the target machine
rizmi license validate license.lic --public-key keys/public.pem

# 5. Inspect a token (author side, no HWID check)
rizmi license inspect license.lic --public-key keys/public.pem

Integration Workflow — From Start to Finish

The recommended path is to use rizmi gui (or 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 (rizmi gui) → 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 --features 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
│   └── release.yml                  # Release: build → TestPyPI → PyPI → GitHub Release
├── 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/                     # rizmi CLI (Typer + Rich)
│       │   ├── app.py               # Root app + help banner + --version
│       │   └── commands/
│       │       ├── keys.py          # keys generate / inspect / verify
│       │       ├── license_cmd.py   # license issue / validate / inspect
│       │       └── machine_id.py    # machine-id (--raw, --copy)
│       └── _internal/               # Private — never import directly
│           └── logging.py
├── keys/                            # Generated keys (gitignored)
│   └── .gitkeep
└── tests/                           # comprehensive pytest suite
    ├── unit/
    │   ├── core/                      # Core cryptography unit tests
    │   └── models/                    # Data model unit tests
    ├── integration/                 # Hypothesis property tests
    ├── contract/                    # Golden fixtures and compatibility checks
    ├── regression/                  # Specific bug repro/regression tests
    ├── e2e/                         # CLI execution smoke tests
    └── gui/                         # PyQt6 widget and workflow tests

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 the future improvements roadmap for 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.2.0.tar.gz (221.2 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.2.0-py3-none-any.whl (46.6 kB view details)

Uploaded Python 3

File details

Details for the file py_rizmi-1.2.0.tar.gz.

File metadata

  • Download URL: py_rizmi-1.2.0.tar.gz
  • Upload date:
  • Size: 221.2 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.2.0.tar.gz
Algorithm Hash digest
SHA256 8a9f362e1cb265f353726c82a01e1a33abdb425ceb2d0ca76af92bb9db6fbf1b
MD5 953c819b5341b61d4eb16a03eeefec2d
BLAKE2b-256 7644f94555800eb9d773cd68b205116dafe52caef5e63bc44abb66b77b622767

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_rizmi-1.2.0.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.2.0-py3-none-any.whl.

File metadata

  • Download URL: py_rizmi-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 46.6 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.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b0a095cbe233472bcef92064c9c906596003be7c33c8b7d7f468c8fd4414c6d5
MD5 4de5685fede64297496fa41c406587e2
BLAKE2b-256 1aa74553f1e37bf411c2574269a2fa3b682782ae0a50111bcda7cb9f97a65d74

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_rizmi-1.2.0-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