83 lines
2.6 KiB
Python
83 lines
2.6 KiB
Python
"""Offline signature verification — the bulk of the value of the SDK."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from dataclasses import dataclass
|
|
|
|
from cryptography.exceptions import InvalidSignature
|
|
|
|
from .errors import LicensingError
|
|
from .key import LicensePayload, parse_license_key
|
|
from .pubkey import PublicKey
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class VerifyOk:
|
|
"""Result of a successful offline `Verifier.verify()` call."""
|
|
|
|
license_id: uuid.UUID
|
|
product_id: uuid.UUID
|
|
issued_at: int
|
|
expires_at: int # 0 = perpetual
|
|
is_trial: bool
|
|
is_fingerprint_bound: bool
|
|
fingerprint_hash: bytes # 32 bytes; all-zero if unbound
|
|
entitlements: list[str]
|
|
payload: LicensePayload # the full parsed payload
|
|
|
|
|
|
class Verifier:
|
|
"""Offline license-key verifier.
|
|
|
|
Given the issuer's PEM-encoded public key, verifies the cryptographic
|
|
integrity of a license key string with no network access. Suitable
|
|
for boot-time license checks where the licensing server may be
|
|
unreachable.
|
|
|
|
Usage::
|
|
|
|
verifier = Verifier(PublicKey.from_pem(ISSUER_PUBKEY_PEM))
|
|
ok = verifier.verify(key_from_user)
|
|
# raises LicensingError on bad signature / bad format
|
|
|
|
For revocation and fingerprint binding, layer the online
|
|
`Client.validate(...)` on top of this — but only AFTER offline
|
|
verification has passed.
|
|
"""
|
|
|
|
def __init__(self, public_key: PublicKey):
|
|
self._pubkey = public_key
|
|
|
|
def verify(self, key: str) -> VerifyOk:
|
|
"""Verify a `LIC1-...-...` key.
|
|
|
|
On success, returns a `VerifyOk` with all parsed fields. On
|
|
failure, raises `LicensingError`:
|
|
|
|
- `kind="bad_format"`: the key string is malformed.
|
|
- `kind="bad_signature"`: signature didn't verify against
|
|
the issuer's public key (key was edited, fabricated, or
|
|
issued by a different server).
|
|
"""
|
|
parsed = parse_license_key(key)
|
|
try:
|
|
self._pubkey.underlying.verify(parsed.signature, parsed.payload_bytes)
|
|
except InvalidSignature as e:
|
|
raise LicensingError(
|
|
"bad_signature",
|
|
"signature did not verify against the issuer's public key",
|
|
) from e
|
|
p = parsed.payload
|
|
return VerifyOk(
|
|
license_id=p.license_id,
|
|
product_id=p.product_id,
|
|
issued_at=p.issued_at,
|
|
expires_at=p.expires_at,
|
|
is_trial=p.is_trial,
|
|
is_fingerprint_bound=p.is_fingerprint_bound,
|
|
fingerprint_hash=p.fingerprint_hash,
|
|
entitlements=list(p.entitlements),
|
|
payload=p,
|
|
)
|