Metadata-Version: 2.1
Name: licensespring_hardware_id_generator
Version: 1.8.0
Summary: LicenseSpring hardware ID generator
License: LicenseSpring Hardware ID Generator License Agreement (LHIGLA)
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Description-Content-Type: text/markdown
License-File: LICENSE

# LicenseSpring Hardware ID Generator

The LicenseSpring Hardware ID Generator offers stable cross-platform hardware ID generation.

This Python wrapper, and the library provided with it are licensed under the LicenseSpring Hardware ID Generator License Agreement (LHIGLA).

## Installation

Install the `licensespring_hardware_id_generator` library:

```
pip install licensespring_hardware_id_generator
```

## Requirements

`python>=3.8`

## Examples

The library exposes one Enum and six functions:

### Enum
- `HardwareIdAlgorithm`: Defines the available hardware ID generation algorithms.
### Functions
- `get_hardware_id`: Computes and returns the hardware identifier using the specified algorithm.
- `get_logs`: Returns a list of log strings and clears the log buffer.
- `get_version`: Retrieves the version of the hardware ID generator library.
- `is_key_store_available`: Reports whether the device's TPM / Secure Enclave can be used by this process.
- `create_key_pair`: Returns the public key of the device's P-256 signing key pair, creating it if needed.
- `sign`: Signs a message with the device's P-256 signing key.

```python
from licensespring_hardware_id_generator import HardwareIdAlgorithm, get_hardware_id, get_logs, get_version

version = get_version()
hardware_id = get_hardware_id(HardwareIdAlgorithm.Default)
logs = get_logs()

print("Version:", version)
print("Hardware ID:", hardware_id)
print("Logs:")
for log_line in logs:
    print(log_line)
```

### Device signing key

For binding a license to specific hardware more strongly than a hardware ID
alone, the library can create a P-256 key pair inside the device's TPM (Windows,
Linux) or Secure Enclave (macOS, iOS) and sign with it. The private key is
generated in, and never leaves, that hardware.

Always check `is_key_store_available()` first: it returns `False` when there is
no TPM or Secure Enclave, when the TPM is present but unusable, or when the host
process is not entitled to use the key store. On macOS it is expected to be
`False` for a plain Python interpreter, since persisting a Secure Enclave key
requires the *host process* to be code-signed with a `keychain-access-groups`
entitlement backed by an embedded provisioning profile.

```python
import base64

from licensespring_hardware_id_generator import (create_key_pair, get_logs,
                                                is_key_store_available, sign)

if not is_key_store_available():
    print("No device key store available:")
    for log_line in get_logs():
        print(" ", log_line)
else:
    # Base64 DER SubjectPublicKeyInfo. Register this with your backend once.
    public_key = create_key_pair()
    print("Public key:", public_key)

    # Accepts bytes, or a str which is encoded as UTF-8. The payload is
    # length-delimited, so it may contain NUL bytes anywhere.
    signature = sign(b"nonce-from-your-backend")
    print("Signature:", signature)
    assert len(base64.b64decode(signature)) == 64  # raw r || s, not DER
```

`create_key_pair()` returns the same public key on every call for as long as the
key material lives. It changes if the TPM is cleared, the keychain entry is
removed, or the disk is moved to different hardware. `sign()` returns the raw
`r || s` signature, base64-encoded, each half 32 bytes big-endian and
zero-padded, over `SHA-256(message)`.

Both raise `RuntimeError` on failure; call `get_logs()` for the reason. `sign()`
raises `ValueError` for an empty message rather than signing an empty payload.

## Freezing with PyInstaller

The native library is loaded with `ctypes` from a path computed at import time,
which PyInstaller's static analysis cannot see. The package ships a PyInstaller
hook, registered through the `pyinstaller40` entry point, that collects the
binary into `licensespring_hardware_id_generator/lib/` inside the bundle.

Entry-point hooks need PyInstaller 4.0 or newer. On anything older, bundle the
binary explicitly instead:

```
pyinstaller --collect-binaries licensespring_hardware_id_generator app.py
```

If a bundle was built before this hook existed and collected the library by
hand, the loader still finds it: it falls back to searching the bundle root
(`sys._MEIPASS`) when the package-relative `lib/` directory is absent.
