Permit: Signed Product Rights
| (require permit) | package: permit |
Permit is a framework-neutral protocol and Racket reference implementation for answering one product question: what may this person, organization, or installation use right now? Long-lived entitlements describe commercial rights, optional short-lived activation leases bind a right to an installation, and local first-run trials are their own convenience state. Everything is verified offline against an Ed25519 trust store that ships inside the application; issuance and rotation are online concerns that never change the verification protocol.
Permit is deliberately not anti-tamper DRM, and a permanently offline credential cannot learn about a revocation that happened elsewhere — see the threat model for the honest boundaries. The normative protocol lives in Protocol v1; this manual documents the Racket surface.
1 Trust stores
Applications ship public keys only. A trust store is JSON application configuration, delivered through the application’s authenticated release path:
{ "protocol": "permit-trust-store", "version": 1, "issuer": "com.example", "keys": { "license-2027": { "algorithm": "Ed25519", "public_key": "<Base64 SubjectPublicKeyInfo DER>", "status": "active", "purposes": ["entitlement", "trial", "trial-extension"], "not_before": "2027-01-01T00:00:00Z", "not_after": null } } }
Key statuses are next (pre-distributed, must have not_before), active (normal issuance), retired (no new issuance, must have an not_after issuance cutoff — old credentials stay authentic), and revoked (every credential under the key fails). Each key declares the credential types it may sign; an activation-signing key therefore cannot mint a perpetual entitlement. The time windows constrain the credential’s authenticated issued_at, not the wall clock at verification — retiring a key never kills a perpetual entitlement.
2 Verifying credentials
A credential is a small JSON envelope whose payload is base64 bytes signed exactly with Ed25519. Verification authenticates those bytes first and only then parses authorization claims — the verifier never re-serializes JSON, which removes canonicalization ambiguity across languages.
procedure
source : (or/c path-string? input-port?) store : trust-store?
3 Evaluating policy
Policy evaluation is total and offline: every check takes the verified credential, an evaluation instant ((current-rfc3339) by default), and optional binding parameters.
procedure
(credential-status c [ #:at at #:installation-id installation-id #:activation activation #:trial-extensions trial-extensions]) → (hash/c symbol? any/c) c : verified-credential? at : string? = (current-rfc3339) installation-id : (or/c string? #f) = #f activation : (or/c verified-credential? #f) = #f trial-extensions : (listof verified-credential?) = '()
procedure
(activation-valid? entitlement activation installation-id [ #:at at]) → boolean? entitlement : verified-credential? activation : verified-credential? installation-id : string? at : string? = (current-rfc3339)
procedure
(trial-extension-valid? trial extension [ #:at at]) → boolean? trial : verified-credential? extension : verified-credential? at : string? = (current-rfc3339)
procedure
(edition-entitled? c product edition [ #:at at #:product-family product-family #:version version #:installation-id installation-id #:activation activation #:trial-extensions trial-extensions]) → boolean? c : verified-credential? product : string? edition : string? at : string? = (current-rfc3339) product-family : (or/c string? #f) = #f version : (or/c string? #f) = #f installation-id : (or/c string? #f) = #f activation : (or/c verified-credential? #f) = #f trial-extensions : (listof verified-credential?) = '()
procedure
(feature-entitled? c product feature [ #:published-at published-at #:at at ...]) → boolean? c : verified-credential? product : string? feature : string? published-at : (or/c string? #f) = #f at : string? = (current-rfc3339)
procedure
(update-entitled? c product update-published-at [ #:at at ...]) → boolean? c : verified-credential? product : string? update-published-at : string? at : string? = (current-rfc3339)
procedure
(capability-value c product capability [ #:default default #:at at ...]) → any/c c : verified-credential? product : string? capability : symbol? default : any/c = #f at : string? = (current-rfc3339)
procedure
(limit-value c limit [ #:default default #:at at ...]) → any/c c : verified-credential? limit : symbol? default : any/c = #f at : string? = (current-rfc3339)
procedure
(select-latest-entitlement credentials) → verified-credential?
credentials : (and/c (listof verified-credential?) pair?)
4 Issuing credentials
| (require permit/issuer) | package: permit |
The issuer surface is an explicit opt-in; the client facade above never exports private-key operations.
procedure
(issue-credential payload private-key key-id store) → issued-credential? payload : bytes? private-key : pk-key? key-id : string? store : trust-store?
procedure
(write-issued-credential credential destination) → void? credential : issued-credential? destination : (or/c path-string? output-port?)
5 Command line
raco permit keygen --private k.der --public pub.der \ |
--trust-store trust.json --key-id license-2027 \ |
--issuer com.example |
raco permit issue --key k.der --key-id license-2027 \ |
--trust-store trust.json --payload ent.json \ |
--output customer.permit.json |
raco permit verify --trust-store trust.json \ |
--credential customer.permit.json |
raco permit check --trust-store trust.json \ |
--credential customer.permit.json \ |
--product example-app --edition pro |
raco permit inspect --credential customer.permit.json |
inspect is deliberately labelled unverified; never authorize a feature from inspected claims alone.
6 Local trials and installation identity
procedure
(make-local-trial [ #:duration-seconds duration-seconds #:at at #:id id]) → local-trial? duration-seconds : exact-positive-integer? = (* 14 24 60 60) at : string? = (current-rfc3339) id : string? = (generate-local-trial-id)
procedure
(evaluate-local-trial trial [ #:at at #:extensions extensions #:issuer issuer #:rollback-tolerance-seconds tolerance]) → local-trial-evaluation? trial : local-trial? at : string? = (current-rfc3339) extensions : (listof verified-credential?) = '() issuer : (or/c string? #f) = #f tolerance : exact-nonnegative-integer? = 300
procedure
→ (or/c exact-integer? #f) r : local-trial-evaluation?