Technical manual — chapter list

22. Licensing: key format and trust model

This note documents how GridSim verifies a licence key offline, the on-the-wire key format, and where enforcement is wired. The user-facing side (tiers, installing a key) is in docs/manual/23-licensing-and-activation.md.

Trust model: signed, offline, honest-effort

A licence key is signed, not encrypted. It carries its fields in the clear (they are base64-decodable) but they cannot be altered or forged without the private signing key. This is the correct model for offline licensing of source-available software: the fields cannot be hidden in a binary the customer can read anyway, so we protect integrity and authenticity, not secrecy.

  • Asymmetric. An ECDSA P-256 keypair. The private key lives only in the out-of-repo forge (D:\Work\GridSimLicensing\signing-key.pem) and is never committed. The public key is embedded in GridSim.Core (Licensing/LicencePublicKey.cs, a base64 SubjectPublicKeyInfo constant) and can only verify.
  • Offline. Verification is a local signature check - no network call, ever. Air-gapped machines activate normally.
  • Dependency-free. Everything uses System.Security.Cryptography from the BCL, so GridSim.Core stays NuGet-free (no BouncyCastle/NSec). P-256 is in-box; Ed25519 is not, which is why P-256 was chosen.
  • Honest-effort. Because the source is available, a determined user can patch the check out. LICENSE.md §2 forbids that; we do not build invasive anti-tamper.

Key format (v1)

One line, SSH-authorized-keys style:

GSK1 <base64url-blob>

The blob is:

magic "GSK1" (4 bytes) | version 0x01 (1) | payloadLen u16 big-endian (2) | payload | signature (64)

The payload is canonical, pipe-delimited UTF-8 (fields escape \ and |):

v1 | email | org | type | buslimit | issuedDays | expiresDays | licenceId
  • type is the tier wire code: personal | academic | code | deployment | operator.
  • buslimit is per-key; 0 means unlimited (commercial/operator tiers).
  • issuedDays / expiresDays are integer days since 1970-01-01 UTC (locale-free, matching the codebase's InvariantCulture discipline).
  • licenceId is a short opaque id, shown in licence status for support/provenance.

The signature is ECDSA P-256 / SHA-256 in IEEE-P1363 fixed-field form (a fixed 64 bytes), computed over every byte before it (magic | version | payloadLen | payload). Base64url (unpadded, URL-safe) keeps the token to one clean line - typically ~180-260 characters, SSH-key length.

Code map (src/GridSim.Core/Licensing/)

File Role
LicenceTier.cs The tier enum + wire-code/display helpers. Named to avoid the DNO Licence collision.
Entitlement.cs A decoded, verified licence (fields + IsExpired, IsUnlimited, FreeDefault).
LicenceKey.cs Encode (forge) and TryParse (verify) - the format above; never throws on bad input.
LicencePublicKey.cs The embedded public SPKI. Replace on key rotation.
Base64Url.cs URL-safe unpadded base64.
LicenceGate.cs Resolve (precedence), Check/Enforce (bus limit + expiry), Describe, Install.
LicenceException.cs User-facing failure (rejected key; a front-end hard-block).

Enforcement seams

Bus count is the only licensed quantity (GridModel.BusCount). Enforcement happens at each front-end's model-load choke point; Bundled Networks are passed bundled: true and bypass the check.

  • CLI (GridSim.Cli): a Gated helper wraps every user model load (Gated.Dir / Gated.Matpower); bundled-ness is decided by path (anything under a data/ tree is a bundled case). The licence verb (LicenceCli.cs) does status/import/path, and the --importLicence <file.gridsim> global switch installs a key (consumed before flag validation).
  • Web (GridSim.Web): the LicenceGate is a DI singleton; CaseCatalog.LoadModel enforces using CaseKind (built-ins and the MATPOWER library are bundled; only gda-* JSON models are licensed). The REST API surfaces a denial as HTTP 403; the dashboard shows the tier in a footer badge.
  • WPF (GridSim.Wpf): App.Licence is resolved in OnStartup; MainWindow.SwitchTo checks after load, and the tier is shown in the title bar.

The forge

D:\Work\GridSimLicensing (out of this repo) is a small console tool that links these same Licensing/*.cs files - so the issuer and the verifier can never drift on the format. keygen mints the keypair; issue signs a key; verify round-trips one. See that folder's README.md.

Rotating the signing key

Run the forge's keygen --force, paste the new public SPKI into LicencePublicKey.cs, and rebuild. Keys signed by the old private key stop verifying, so rotate deliberately.