Offline Verification

Verify licenses locally in air-gapped or high-security offline environments using asymmetric cryptography.

Keymint validates offline licenses using Ed25519 signatures. To prevent signature tampering, your application must store trusted public keys locally and match them against the Key ID (kid) specified in the license payload.

mermaid
Rendering diagram...

Implementation Workflow

Step 1: Issue the Offline License

Generate a signed license file from the Keymint Dashboard:

  1. Navigate to Licenses and open the target license.
  2. In the activations pane, click the Offline License modal.
  3. Enter the target device's hardware fingerprint (Machine Code) and a custom TTL (Time-To-Live).
  4. Download the generated .lic file.

The downloaded file is JSON. Its signedKey field is the Ed25519-signed JWT, and keyId/publicKeyFingerprint identify the trusted public key to use for verification:

json
{
  "signedDate": "2026-06-21T12:00:00.000Z",
  "signedKey": "eyJhbGciOiJFZERTQSIsImtpZCI6IkFiQzEyM1h5WiJ9...",
  "keyId": "AbC123XyZ",
  "publicKeyFingerprint": "AbC123XyZ"
}

Step 2: Bundle Trusted Public Keys

Map trusted key IDs to their public keys within your application.

typescript
import { importSPKI } from 'jose';

const trustedKeys = new Map<string, string>([
  ['AbC123XyZ', '-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAr7Z8s2Z...\n-----END PUBLIC KEY-----']
]);

Step 3: Parse and Verify the License

Extract the Key ID from the token header, retrieve the matching public key, and verify the signature and claims using jose.

typescript
import { importSPKI, jwtVerify, decodeProtectedHeader } from 'jose';
import * as fs from 'fs';

export async function verifyLicense(licensePath: string, productId: string, machineCode: string) {
  const { signedKey } = JSON.parse(fs.readFileSync(licensePath, 'utf8'));

  // 1. Extract Key ID from the JWT header
  const { kid } = decodeProtectedHeader(signedKey);
  if (!kid) throw new Error('Invalid token header: missing kid');

  // 2. Fetch the corresponding public key
  const pem = trustedKeys.get(kid);
  if (!pem) throw new Error('Untrusted signature key ID');
  const publicKey = await importSPKI(pem, 'EdDSA');

  // 3. Verify signature and standard claims
  const { payload } = await jwtVerify(signedKey, publicKey, {
    algorithms: ['EdDSA']
  });

  // 4. Validate product and hardware bounds
  if (payload.productId !== productId) throw new Error('Product mismatch');
  if (payload.machineCode !== machineCode) throw new Error('Hardware mismatch');

  return payload;
}

Security Best Practices

  • Embed Multiple Keys: Include backup public keys in your source bundle to allow seamless key rotation.
  • Enforce Node-Locking: Always require a machine hardware fingerprint claim when generating offline keys.
  • Disable Dynamic Algorithms: Accept only EdDSA (Ed25519) signatures; reject tokens that use symmetric encryption (HS256) or debug keys.