Initial backup of root workspace files
Glue files not covered by subproject repos: top-level docs, logo, keysat-design-system, and crosscheck tests. Subproject folders are gitignored (each has its own Gitea remote).
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
"""Produce LIC1-...-... keys the service would accept — used to verify the
|
||||
SDK parsers round-trip correctly.
|
||||
|
||||
Emits two fixtures now: v1 (legacy fixed-74 payload) and v2 (variable-length
|
||||
payload with expires_at + entitlements). SDKs must accept both.
|
||||
|
||||
Byte layout (matches licensing-service/src/crypto/mod.rs exactly):
|
||||
|
||||
v1 (74 bytes, fixed):
|
||||
[0] version = 1
|
||||
[1] flags
|
||||
[2..18] product_id (UUID BE, 16 bytes)
|
||||
[18..34] license_id (UUID BE, 16 bytes)
|
||||
[34..42] issued_at (u64 BE, unix seconds)
|
||||
[42..74] fingerprint_hash (SHA-256, zero if unbound)
|
||||
|
||||
v2 (83 bytes + variable entitlements):
|
||||
[0] version = 2
|
||||
[1] flags
|
||||
[2..18] product_id
|
||||
[18..34] license_id
|
||||
[34..42] issued_at (u64 BE)
|
||||
[42..50] expires_at (u64 BE, 0 = perpetual)
|
||||
[50..82] fingerprint_hash (SHA-256, zero if unbound)
|
||||
[82] num_entitlements (u8)
|
||||
[83..] for each: [u8 len][len bytes of UTF-8 slug]
|
||||
|
||||
Signature: 64 bytes over the raw payload bytes.
|
||||
Encoding: LIC1-<base32(payload)>-<base32(signature)>
|
||||
RFC 4648 base32, uppercase, no padding.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||
from cryptography.hazmat.primitives.serialization import (
|
||||
Encoding, PrivateFormat, PublicFormat, NoEncryption,
|
||||
)
|
||||
|
||||
KEY_VERSION_V1 = 1
|
||||
KEY_VERSION_V2 = 2
|
||||
|
||||
FLAG_FINGERPRINT_BOUND = 0b0000_0001
|
||||
FLAG_TRIAL = 0b0000_0010
|
||||
|
||||
|
||||
def b32nopad(b: bytes) -> str:
|
||||
return base64.b32encode(b).decode("ascii").rstrip("=")
|
||||
|
||||
|
||||
def make_payload_v1(
|
||||
flags: int,
|
||||
product_id: bytes, license_id: bytes,
|
||||
issued_at: int, fp_hash: bytes,
|
||||
) -> bytes:
|
||||
assert len(product_id) == 16
|
||||
assert len(license_id) == 16
|
||||
assert len(fp_hash) == 32
|
||||
payload = (
|
||||
bytes([KEY_VERSION_V1, flags])
|
||||
+ product_id
|
||||
+ license_id
|
||||
+ issued_at.to_bytes(8, "big")
|
||||
+ fp_hash
|
||||
)
|
||||
assert len(payload) == 74, f"v1 payload is {len(payload)} bytes, expected 74"
|
||||
return payload
|
||||
|
||||
|
||||
def make_payload_v2(
|
||||
flags: int,
|
||||
product_id: bytes, license_id: bytes,
|
||||
issued_at: int, expires_at: int,
|
||||
fp_hash: bytes, entitlements: list[str],
|
||||
) -> bytes:
|
||||
assert len(product_id) == 16
|
||||
assert len(license_id) == 16
|
||||
assert len(fp_hash) == 32
|
||||
assert 0 <= len(entitlements) <= 255
|
||||
tail = bytearray()
|
||||
for slug in entitlements:
|
||||
encoded = slug.encode("utf-8")
|
||||
assert len(encoded) <= 255, f"entitlement '{slug}' too long"
|
||||
tail.append(len(encoded))
|
||||
tail.extend(encoded)
|
||||
head = (
|
||||
bytes([KEY_VERSION_V2, flags])
|
||||
+ product_id
|
||||
+ license_id
|
||||
+ issued_at.to_bytes(8, "big")
|
||||
+ expires_at.to_bytes(8, "big")
|
||||
+ fp_hash
|
||||
+ bytes([len(entitlements)])
|
||||
)
|
||||
assert len(head) == 83
|
||||
return head + bytes(tail)
|
||||
|
||||
|
||||
def sign_and_encode(sk: Ed25519PrivateKey, payload: bytes) -> str:
|
||||
sig = sk.sign(payload)
|
||||
assert len(sig) == 64
|
||||
return f"LIC1-{b32nopad(payload)}-{b32nopad(sig)}"
|
||||
|
||||
|
||||
def main():
|
||||
# Deterministic test vector — fixed seeds so the output is stable.
|
||||
sk = Ed25519PrivateKey.from_private_bytes(bytes(range(32)))
|
||||
pk = sk.public_key()
|
||||
pub_pem = pk.public_bytes(
|
||||
Encoding.PEM, PublicFormat.SubjectPublicKeyInfo
|
||||
).decode()
|
||||
|
||||
product_id = uuid.UUID("6f46a4f6-0d2f-4a28-b6aa-3e8fbf6f28f0").bytes
|
||||
license_id_v1 = uuid.UUID("11111111-2222-3333-4444-555555555555").bytes
|
||||
license_id_v2 = uuid.UUID("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee").bytes
|
||||
issued_at = 1_700_000_000
|
||||
expires_at_v2 = 1_900_000_000 # ~2030
|
||||
fingerprint_raw = "test-machine-fingerprint"
|
||||
fp_hash = hashlib.sha256(fingerprint_raw.encode()).digest()
|
||||
entitlements_v2 = ["pro", "multi-device"]
|
||||
|
||||
# v1: legacy fingerprint-bound key.
|
||||
v1_payload = make_payload_v1(
|
||||
FLAG_FINGERPRINT_BOUND,
|
||||
product_id, license_id_v1, issued_at, fp_hash,
|
||||
)
|
||||
v1_key = sign_and_encode(sk, v1_payload)
|
||||
|
||||
# v2: trial + fingerprint-bound, with entitlements and expiry.
|
||||
v2_flags = FLAG_FINGERPRINT_BOUND | FLAG_TRIAL
|
||||
v2_payload = make_payload_v2(
|
||||
v2_flags,
|
||||
product_id, license_id_v2, issued_at, expires_at_v2,
|
||||
fp_hash, entitlements_v2,
|
||||
)
|
||||
v2_key = sign_and_encode(sk, v2_payload)
|
||||
|
||||
# v2 perpetual, unbound, no entitlements — the "happy path" for a normal
|
||||
# permanent license purchase.
|
||||
v2_plain_payload = make_payload_v2(
|
||||
0,
|
||||
product_id, license_id_v2, issued_at, 0,
|
||||
bytes(32), [],
|
||||
)
|
||||
v2_plain_key = sign_and_encode(sk, v2_plain_payload)
|
||||
|
||||
out = {
|
||||
"publicKeyPem": pub_pem,
|
||||
"v1": {
|
||||
"licenseKey": v1_key,
|
||||
"expected": {
|
||||
"version": 1,
|
||||
"productUuid": "6f46a4f6-0d2f-4a28-b6aa-3e8fbf6f28f0",
|
||||
"licenseUuid": "11111111-2222-3333-4444-555555555555",
|
||||
"issuedAt": issued_at,
|
||||
"expiresAt": 0,
|
||||
"flags": FLAG_FINGERPRINT_BOUND,
|
||||
"isFingerprintBound": True,
|
||||
"isTrial": False,
|
||||
"entitlements": [],
|
||||
"fingerprintRaw": fingerprint_raw,
|
||||
"fingerprintHashHex": fp_hash.hex(),
|
||||
},
|
||||
},
|
||||
"v2": {
|
||||
"licenseKey": v2_key,
|
||||
"expected": {
|
||||
"version": 2,
|
||||
"productUuid": "6f46a4f6-0d2f-4a28-b6aa-3e8fbf6f28f0",
|
||||
"licenseUuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
"issuedAt": issued_at,
|
||||
"expiresAt": expires_at_v2,
|
||||
"flags": v2_flags,
|
||||
"isFingerprintBound": True,
|
||||
"isTrial": True,
|
||||
"entitlements": entitlements_v2,
|
||||
"fingerprintRaw": fingerprint_raw,
|
||||
"fingerprintHashHex": fp_hash.hex(),
|
||||
},
|
||||
},
|
||||
"v2_perpetual_unbound": {
|
||||
"licenseKey": v2_plain_key,
|
||||
"expected": {
|
||||
"version": 2,
|
||||
"productUuid": "6f46a4f6-0d2f-4a28-b6aa-3e8fbf6f28f0",
|
||||
"licenseUuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
"issuedAt": issued_at,
|
||||
"expiresAt": 0,
|
||||
"flags": 0,
|
||||
"isFingerprintBound": False,
|
||||
"isTrial": False,
|
||||
"entitlements": [],
|
||||
"fingerprintRaw": None,
|
||||
"fingerprintHashHex": "00" * 32,
|
||||
},
|
||||
},
|
||||
}
|
||||
json.dump(out, sys.stdout, indent=2)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user