Secure cryptographic offline-first licensing client library for Icare licensing system
Project description
Icare Licensing Client
icare-liecensing-client is an offline-first, cryptographically secure licensing client library for Python applications. It integrates seamlessly with the Icare licensing server using RSA-SHA256 signatures, device hardware bindings, and automated offline-resilient sync policies.
Features
- Stable Hardware Binding (HWID): Generates a stable machine signature.
- Offline Cryptographic Validation: Validates license state offline using RSA-SHA256.
- 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.
1. Installation
Install the package from your PyPI registry:
pip install icare-licensing
2. License Storage & File Location Policy
By default, the client library writes license.lic and license_metadata.json relative to the Current Working Directory (CWD) of the executing process.
Critical Production Guidelines
For desktop deployments or system-wide CLI tools, running relative to the CWD can lead to:
- Permission Denials (
PermissionError): If the app is run from a read-only directory (e.g.Program Filesor/usr/local/bin). - Missing Licenses: If the user starts the application from a different terminal directory, the client will look in the wrong path and report the license is missing.
Recommendation: Use Absolute Paths
Always initialize the client with an absolute path pointing to a user-writable application directory (such as the user's home folder or standard application data directory).
import os
from licensing_client import LicensingClient
# Determine a safe system path (e.g., ~/.config/my_app/ or %APPDATA%/my_app/)
app_folder = os.path.join(os.path.expanduser("~"), ".my_app")
os.makedirs(app_folder, exist_ok=True)
license_path = os.path.join(app_folder, "license.lic")
# Initialize client
client = LicensingClient(
server_url="https://licensing.mycompany.com",
license_file_path=license_path
)
Note: The metadata file (license_metadata.json) will automatically be generated in the same target folder.
3. Core Developer API
Licensing States
The library exposes six core status constants:
STATUS_VALID: License signature is cryptographically valid, matches machine HWID, is not expired, and is synced.STATUS_MISSING: Nolicense.licfile is present (Device needs activation).STATUS_EXPIRED: The local or server expiration date has been exceeded.STATUS_INVALID: The license cryptographic signature is corrupt, has been modified, or has been actively revoked/deleted on the server.STATUS_REQUIRES_SYNC: The application has been running offline for more than 3 days and requires internet to refresh.STATUS_UNKNOWN: State is not yet resolved.
4. Integration Template
Copy this robust integration template into your project's main entry point:
import os
import sys
from licensing_client 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), make sure
to route UI-altering updates safely to the main thread.
"""
print(f"[Licensing] Status changed to: {new_status}")
if new_status == STATUS_VALID:
print("Premium features unlocked!")
# Proceed to launch normal application logic
elif new_status == STATUS_REQUIRES_SYNC:
print("Warning: internet connection required to verify license.")
# Optional: Show a warning banner, but keep standard features unlocked
elif new_status in (STATUS_MISSING, STATUS_INVALID, STATUS_EXPIRED):
print("Security Lock: No valid license active.")
# Action: Redirect user to your license activation overlay/screen
# Lock down core software capabilities
def main():
# 1. Setup secure storage paths
app_folder = os.path.join(os.path.expanduser("~"), ".my_app")
os.makedirs(app_folder, exist_ok=True)
license_path = os.path.join(app_folder, "license.lic")
# 2. Instantiate Client
# max_offline_days determines how long the app can run offline (default: 3 days)
client = LicensingClient(
server_url="http://localhost:8081",
license_file_path=license_path,
status_callback=on_license_status_change,
max_offline_days=3
)
# 3. Handle Initial State
# Force a check immediately on startup
client._run_check()
if client.current_status == STATUS_MISSING:
print("Application is unactivated.")
# Example: prompt user for activation key in CLI
# key = input("Enter Activation Key: ")
# success, msg = client.activate_device(key)
# 4. Start Background Protection Thread
# Validates locally and checks online every 5 minutes (300 seconds)
client.start_monitoring(interval_seconds=300)
if __name__ == "__main__":
main()
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file icare_licensing-1.0.0.tar.gz.
File metadata
- Download URL: icare_licensing-1.0.0.tar.gz
- Upload date:
- Size: 9.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78356d40b281fd0734e545cfdac45bdc2b4dac889f97bc68abfa3a1a9f577a89
|
|
| MD5 |
a1f88a74d15495983d0206a01a46882a
|
|
| BLAKE2b-256 |
64693523440b241b4f8aae0d5a6344c65c11ba1651f5d5a0028999282d797344
|
File details
Details for the file icare_licensing-1.0.0-py3-none-any.whl.
File metadata
- Download URL: icare_licensing-1.0.0-py3-none-any.whl
- Upload date:
- Size: 9.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28bbe2968dcd3c1f3ea63ad5a106cc30cf2981d10264e9c9a426f82f37b1f606
|
|
| MD5 |
876318a002987ce393d051c63fdf98aa
|
|
| BLAKE2b-256 |
7d3fd101899cf6b6d8aa8fb0920baf68522a49cfdd6c64a163ec4a34f6d46612
|