Add signature response for valid license keys
This commit is contained in:
49
README.md
49
README.md
@@ -18,6 +18,7 @@ POSTGRESQL_PASSWORD="PG_CHANGEME"
|
||||
API_KEY="API_CHANGEME"
|
||||
NUM_KEY_CHUNKS=5
|
||||
KEY_CHUNK_LENGTH=5
|
||||
SIGN_KEY=true
|
||||
```
|
||||
|
||||
### 3. Replace the placeholder secrets in `.env` with secure random values using `openssl rand`:
|
||||
@@ -29,7 +30,7 @@ sed -i \
|
||||
.env
|
||||
```
|
||||
|
||||
You can change `NUM_KEY_CHUNKS` to adjust how many chunks a license key consists of, and `KEY_CHUNK_LENGTH` to change how many characters appear in each chunk. This only changes how *new* keys are generated, so previous keys won't become invalidated.
|
||||
You can change `NUM_KEY_CHUNKS` to adjust how many chunks a license key consists of, and `KEY_CHUNK_LENGTH` to change how many characters appear in each chunk. This only changes how *new* keys are generated, so previous keys won't become invalidated. If `SIGN_KEY` is enabled the server will generate an Ed25519 key pair on first start and place it in the `keys/` directory (mounted into the container); subsequent restarts reuse those files.
|
||||
|
||||
### 4. Run `docker-compose up -d` in the cloned repository folder to start the server.
|
||||
You will then have the server exposed on port 8000, if you need a different port, change it in the docker-compose file, then run `docker-compose up -d` again.
|
||||
@@ -41,12 +42,13 @@ All authenticated endpoints expect a `Bearer` token via the `Authorization` head
|
||||
| Method | Path | Auth | Description | Key Request Parameters | Response Highlights |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `GET` | `/` | No | Returns server metadata. | – | `{"version": "Micro License Server vX.Y.Z"}` |
|
||||
| `POST` | `/license` | Yes | Generates a new license key. | Query: `is_active` (bool, default `true`), `expiration_date` (ISO 8601, optional), `info` (string, optional metadata) | `license_key`, `expiration_timestamp`, `is_active`, `info` |
|
||||
| `GET` | `/is_valid` | No | Validates a license key and records last usage time. | Query: `license_key` (required) | Boolean validity |
|
||||
| `POST` | `/license` | Yes | Generates a new license key. | Query: `is_active` (bool, default `true`), `expiration_date` (ISO 8601, optional), `info` (string, optional metadata) | Unsigned response includes `license_key`, `expiration_timestamp`, `is_active`, `info`. With signing enabled the response becomes `{ "license": { "license_key", "expiration_timestamp" }, "signature" }`. |
|
||||
| `GET` | `/is_valid` | No | Validates a license key and records last usage time. | Query: `license_key` (required) | Always returns `{ "valid": bool }`; with signing enabled and valid key also includes `license` payload and `signature`. |
|
||||
| `POST` | `/license/{license_key}/disable` | Yes | Deactivates a license key. | Path: `license_key` | `license_key`, `is_active: false` |
|
||||
| `POST` | `/license/{license_key}/enable` | Yes | Reactivates a license key. | Path: `license_key` | `license_key`, `is_active: true` |
|
||||
| `POST` | `/license/{license_key}/expiration` | Yes | Sets or clears a license key expiration. | Path: `license_key`; Query: `expiration_date` (ISO 8601 or omit to clear) | `license_key`, `expiration_timestamp` (nullable) |
|
||||
| `GET` | `/license/export` | Yes | Exports license inventory as CSV ordered by issue time. | – | CSV with `license_key,issue_timestamp,expiration_timestamp,info,is_active` |
|
||||
| `GET` | `/public-key` | No (when signing enabled) | Returns the base64-encoded Ed25519 public key used for signatures. | – | `{ "public_key": "..." }` |
|
||||
| `GET` | `/history/export` | Yes | Exports audit history as CSV. | Query: `token` (optional substring filter) | CSV with `action,timestamp` |
|
||||
|
||||
## Usage Examples
|
||||
@@ -106,3 +108,44 @@ curl -H "Authorization: Bearer $API_KEY" \
|
||||
"$BASE_URL/history/export?token=PUT-LICENSE-HERE" \
|
||||
-o history_export.csv
|
||||
```
|
||||
|
||||
|
||||
## Verifying Signed Responses
|
||||
|
||||
When `SIGN_KEY=true`, successful `/is_valid` responses include a signature covering the license payload. The `/public-key` endpoint exposes the Ed25519 public key as base64-encoded bytes. The example below shows how to verify a response in Python using the `ecdsa` package.
|
||||
|
||||
```python
|
||||
import base64
|
||||
import json
|
||||
import requests
|
||||
from ecdsa import Ed25519, VerifyingKey
|
||||
|
||||
BASE_URL = "http://127.0.0.1:8000"
|
||||
LICENSE_KEY = "PUT-YOUR-LICENSE-HERE"
|
||||
|
||||
# Fetch and cache the base64 public key once.
|
||||
public_key_b64 = requests.get(f"{BASE_URL}/public-key", timeout=5).json()["public_key"]
|
||||
public_key_bytes = base64.b64decode(public_key_b64)
|
||||
verifying_key = VerifyingKey.from_string(public_key_bytes, curve=Ed25519)
|
||||
|
||||
# Validate the license key.
|
||||
validation = requests.get(
|
||||
f"{BASE_URL}/is_valid",
|
||||
params={"license_key": LICENSE_KEY},
|
||||
timeout=5,
|
||||
).json()
|
||||
|
||||
if not validation.get("valid"):
|
||||
raise RuntimeError("License is not valid")
|
||||
|
||||
if "signature" not in validation:
|
||||
raise RuntimeError("Signing is disabled on the server")
|
||||
|
||||
license_payload = validation["license"]
|
||||
message = json.dumps(license_payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
||||
signature = base64.b64decode(validation["signature"])
|
||||
|
||||
# Raises ecdsa.BadSignatureError if verification fails.
|
||||
verifying_key.verify(signature, message)
|
||||
print("Signature verified")
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user