Skip to main content

Offline-first, cryptographically secure licensing client for Python apps using the Icare Licensing Server

Project description

Icare Licensing Client

icare-licensing is an offline-first, cryptographically secure licensing client library for Python applications. It integrates seamlessly with the Icare Licensing Server using RSA-SHA256 signatures, OS-native stable hardware bindings, and automated offline-resilient sync policies.

PyPI version Python License: MIT


Features

  • OS-Native Stable Hardware Binding (HWID): Generates a stable, reproducible machine signature using OS-level identifiers — not fragile MAC addresses.
  • Offline Cryptographic Validation: Validates license state offline using RSA-SHA256 without calling the server on every startup.
  • Automated Sync Daemon: Runs in the background and pings the verification endpoint every 24 hours.
  • 3-Day Offline Grace Period: Remains fully functional offline for up to 3 days before requiring internet.
  • Instant Revocation Purge: Deletes local signatures and metadata immediately upon server-side license revocation or deletion to prevent offline bypass.

Installation

pip install icare-licensing

Hardware ID (HWID) Strategy

The library generates a stable, cross-platform HWID using the following priority chain:

Priority Platform Source
1st Windows HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid — set once at OS install
2nd Linux /etc/machine-id — set once at first boot
3rd macOS IOPlatformSerialNumber from ioreg
4th All (fallback) MAC address — only if hardware-backed (not a randomly generated address)

Why not uuid.getnode()? Python's uuid.getnode() silently returns a random 48-bit number when no hardware network interface is found (e.g. in VMs, Docker containers, or systems with only virtual NICs). This would cause a legitimately activated license to become invalid on the next restart. This library detects and rejects randomly-generated MAC addresses automatically.


License Storage & File Location Policy

By default, the client writes license.lic and license_metadata.json relative to the Current Working Directory (CWD).

Critical Production Guidelines

For desktop or system-wide deployments, always initialize with an absolute path pointing to a user-writable directory:

import os
from icare_licensing import LicensingClient

app_folder = os.path.join(os.path.expanduser("~"), ".my_app")
os.makedirs(app_folder, exist_ok=True)

client = LicensingClient(
    server_url="https://licensing.mycompany.com",
    license_file_path=os.path.join(app_folder, "license.lic")
)

The metadata file (license_metadata.json) is automatically created in the same directory as license.lic.


Licensing States

Constant Meaning
STATUS_VALID Cryptographically valid, correct HWID, not expired, synced
STATUS_MISSING No license.lic file found — device needs activation
STATUS_EXPIRED License expiration date has passed
STATUS_INVALID Signature corrupt, HWID mismatch, or actively revoked on server
STATUS_REQUIRES_SYNC Offline for longer than max_offline_days — internet required
STATUS_UNKNOWN Initial unresolved state

Core Developer API

LicensingClient(server_url, license_file_path, status_callback, max_offline_days)

Parameter Type Default Description
server_url str required Base URL of the Icare Licensing Server
license_file_path str "license.lic" Absolute path to store the signed license key
status_callback callable None Function called when license status transitions
max_offline_days int 3 Days the app can run offline before REQUIRES_SYNC

Methods

Method Description
get_hardware_id() Returns the stable OS-native HWID for this machine
activate_device(key) Binds an activation key to this machine's HWID. Returns (bool, str)
start_monitoring(interval_seconds) Starts the background daemon thread (default: every 5 minutes)
stop_monitoring() Stops the background daemon thread cleanly
runCheck() Manually triggers a verification check

Integration Template

import os
import sys
from icare_licensing import (
    LicensingClient,
    STATUS_VALID,
    STATUS_REQUIRES_SYNC,
    STATUS_MISSING,
    STATUS_INVALID,
    STATUS_EXPIRED
)

def on_license_status_change(new_status):
    """
    Callback triggered when the license status transitions.
    NOTE: If running a GUI application (e.g., Tkinter/PyQt), route
    UI-altering updates safely to the main thread.
    """
    if new_status == STATUS_VALID:
        print("License valid — premium features unlocked.")

    elif new_status == STATUS_REQUIRES_SYNC:
        print("Warning: internet connection required to re-verify license.")

    elif new_status in (STATUS_MISSING, STATUS_INVALID, STATUS_EXPIRED):
        print("No valid license. Redirecting to activation screen.")
        # Lock down core software capabilities here

def main():
    # 1. Setup secure, writable storage path
    app_folder = os.path.join(os.path.expanduser("~"), ".my_app")
    os.makedirs(app_folder, exist_ok=True)

    # 2. Instantiate client
    client = LicensingClient(
        server_url="http://localhost:8081",
        license_file_path=os.path.join(app_folder, "license.lic"),
        status_callback=on_license_status_change,
        max_offline_days=3
    )

    # 3. Force an immediate check on startup
    client._run_check()

    if client.current_status == STATUS_MISSING:
        key = input("Enter Activation Key: ")
        success, msg = client.activate_device(key)
        print(msg)

    # 4. Start background protection thread (checks every 5 minutes)
    client.start_monitoring(interval_seconds=300)

if __name__ == "__main__":
    main()

Changelog

v1.3.0

  • [Breaking Fix] Replaced uuid.getnode()-based HWID with OS-native stable machine identifiers:
    • Windows: HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid
    • Linux: /etc/machine-id
    • macOS: IOPlatformSerialNumber
    • Fallback: Hardware-backed MAC address only (random MACs are detected and rejected)
  • Removed platform.processor() and platform.machine() from HWID computation (they add no uniqueness)
  • Added explicit RuntimeError instead of silently returning an unstable HWID

v1.2.0

  • Initial public release
  • RSA-SHA256 offline signature verification
  • 24h background sync daemon
  • 3-day offline grace period
  • Instant revocation purge on server rejection
  • 5-retry online sync with 10s backoff

License

MIT — see LICENSE 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

icare_licensing-1.3.0.tar.gz (13.3 kB view details)

Uploaded Source

Built Distribution

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

icare_licensing-1.3.0-py3-none-any.whl (10.5 kB view details)

Uploaded Python 3

File details

Details for the file icare_licensing-1.3.0.tar.gz.

File metadata

  • Download URL: icare_licensing-1.3.0.tar.gz
  • Upload date:
  • Size: 13.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for icare_licensing-1.3.0.tar.gz
Algorithm Hash digest
SHA256 af4676a6f397a22cd8134882ae852ad9373b01ed8657219511c738562eb312f7
MD5 f3c0ba3eb431a29f14602bf1fc0886e7
BLAKE2b-256 e799299e418718aecf027513f075dbe3b28ab67492af87d57ef58f0689602004

See more details on using hashes here.

File details

Details for the file icare_licensing-1.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for icare_licensing-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 69c7beb5a72d7091615a88055fdc33e5efd595b250c82db7c988711c62cd16bc
MD5 5c928f01d6b8ee9df65ec21da7619cc0
BLAKE2b-256 f76d6ab99a29cddb25664a1af6280ee1072a85cd48b4c7e1da67766a533ad27c

See more details on using hashes here.

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