Casos de uso
Dar soporte a clientes offline y aislados
Licencia máquinas sin internet: archivos de licencia offline independientes, leases aislados de larga duración, importación de un bloque de cobertura firmado por archivo, y cuándo elegir offline en lugar de la activación en línea.
Objetivo. Licenciar una máquina que no puede — o no quiere — llegar a tu servicio: una estación de trabajo desconectada, una red empresarial securizada, un equipo embebido. Todo lo que el cliente necesita llega como un archivo firmado, se verifica localmente y nada «llama a casa».
Cuándo usarlo
- Úsalo si algunos clientes operan en redes aisladas, o vendes en entornos regulados donde las conexiones salientes están prohibidas.
- Úsalo si un cliente normalmente en línea debe sobrevivir a una interrupción larga y la ventana de lease de 14 días no es suficiente.
- No lo uses si tus clientes están conectados habitualmente — la activación en línea normal ya funciona offline entre renovaciones y te da aplicación de puestos y revocación en vivo. El licenciamiento offline renuncia a eso a cambio de independencia de la red.
Hay tres bloques de construcción offline, y son independientes — elige lo que la situación necesite:
| Bloque de construcción | Qué es | ¿Puestos? | ¿Revocable? |
|---|---|---|---|
Archivo de licencia offline (license.json) | Una licencia firmada independiente — nunca hay un viaje de ida y vuelta de activación | Sin seguimiento de puestos | Solo mediante una lista de revocación distribuida |
| Lease offline (aislado) | Un lease firmado ordinario, acuñado para una máquina, con un TTL largo | Sí — confirma un puesto al emitirse | En la siguiente comprobación (ninguna, si nunca está en línea) |
| Archivo de bloque de cobertura | El bloque firmado de cobertura de versiones, entregado como archivo | — | Se sustituye por un archivo más nuevo |
Los tres se verifican con la misma clave pública integrada que tu aplicación ya lleva, usando las mismas comprobaciones RSA/SHA-256 que los leases en línea — de modo que «offline» cambia solo cómo se entrega el documento firmado, nunca cómo se confía en él. La profundidad del modelo de lease/anti-retroceso vive en Seguridad y el modelo de leases; esta página es la tarea.
Cuándo elegir offline frente a en línea
Recurre a la activación en línea por defecto — te da aplicación de puestos y revocación en vivo, y aun así funciona offline durante toda la ventana del lease entre renovaciones. Elige una ruta offline cuando:
- La máquina puede llegar a ti ocasionalmente pero no de forma fiable → la activación en línea con un TTL de lease ampliado suele bastar; pasa a un lease aislado (365 días por defecto) solo si puede quedarse a oscuras más tiempo del que cualquier ventana de lease te resulte cómoda.
- La máquina nunca llegará a ti → un lease aislado (conserva la vinculación por máquina y el recuento de puestos) o un archivo de licencia independiente (sin red ni para ti ni para el cliente, sin seguimiento de puestos — lo mejor para despliegues embebidos/verdaderamente aislados).
Qué haces tú (lado del proveedor)
Lease offline (aislado) — vinculado a una máquina
El cliente te envía su id de máquina; tú acuñas un lease de larga duración para ella:
# the customer reads their machine id first: keyright machine-id (or MachineFingerprint.Current().ToBoundString())
curl -X POST $BASE/admin/licenses/LIC-…/offline-lease -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"machineId":"KRM1:…","days":365}' > offline-lease.json
# -> { "machineId":"KRM1:…", "ttlDays":365, "lease":"{…signed lease JSON…}" }
Acuñar un lease offline confirma un puesto para esa máquina, así que las activaciones aisladas siguen contando contra el límite de puestos — no puedes acuñar leases offline ilimitados más allá del número de puestos. El lease está vinculado a esa única huella, de modo que el archivo es inútil en cualquier otra máquina (MachineMismatch).
En el panel esto es la acción License file (archivo de licencia) de la licencia — firma el lease offline para el id de máquina que indiques y te entrega el JSON para que lo hagas llegar:
offline-lease de arriba — que entregas por archivo al equipo aislado.Archivo de licencia offline independiente — sin servicio alguno
Para despliegues genuinamente aislados o embebidos, firma un archivo de licencia con la CLI y distribúyelo con el producto — sin activación, nunca:
keyright license --sign --licensee "Acme Corp" --product acme-app --tier pro \
--private-key acme-app.private.json --expiry 2027-12-31 --out license.json
En este modo no hay seguimiento de puestos; es lo adecuado para equipos aislados y embebidos. Para cortar una compilación así, distribuyes una lista de revocación firmada (GET /admin/revocation-list) con tu siguiente actualización.
Archivo de bloque de cobertura — para máquinas aisladas que aplican la cobertura de versiones
Una máquina desconectada no puede recibir su bloque de cobertura en la activación, así que entrégalo como archivo:
# one license
curl $BASE/admin/licenses/LIC-…/coverage/block -H "X-Admin-Token: $TOKEN"
# -> { "block":"…signed envelope…" }
# every license of a product, for bulk air-gapped delivery
curl $BASE/admin/products/acme-app/coverage/export -H "X-Admin-Token: $TOKEN"
# -> { "product":"acme-app", "count":5, "items":[ { "licenseId":"…", "coverageBlock":"…" }, … ] }
Una renovación para un cliente aislado es simplemente un archivo de cobertura más nuevo: lleva un número de secuencia mayor, así que sustituye al que está en caché, mientras que un archivo más antiguo se rechaza (anti-retroceso). La exportación respeta tu derecho exactamente igual que la activación, así que nunca puede dejar fuera a nadie.
En tu código
Todo lo que sigue se verifica localmente, sin red, contra la clave pública que el cliente ya integra. Las tres rutas se muestran en cada SDK (la importación del bloque de cobertura es hoy una llamada de .NET — los demás SDK usan la ruta del lease, o verifican el sobre por HTTP en bruto):
.NET
using Keyright.Client;
using Keyright.Coverage;
// (A) Standalone license file / env var: just validate what's present — Validate() discovers it by
// explicit path, the KEYRIGHT_LICENSE env var, ConfigLicensePath, or the OS app-data location.
LicenseInfo info = License.Validate(); // offline, never throws; fails closed to Free on any problem
if (info.IsPaid) { /* licensed */ }
// (B) Air-gapped lease: import the file the vendor signed for THIS machine. This one DOES throw if invalid.
try
{
LicenseInfo leased = License.ImportOfflineLease(File.ReadAllText("offline-lease.json"));
// leased.Status == Valid, fully offline; wrong machine/product/signature/expiry -> throws
}
catch (InvalidOperationException ex) { ShowOfflineLeaseError(ex.Message); }
// (C) Coverage block file: import the signed envelope; verifies, binds to the license, enforces anti-rollback.
CoverageVerifyResult r = License.ImportCoverageBlock(File.ReadAllText("acme.coverage.json"));
if (!r.Ok)
{
// r.Status: Malformed | UnknownKey | BadSignature | RolledBack | WrongBinding | UnsupportedVersion
ShowImportError(r.Status);
}
Node.js
const fs = require('fs');
// (A) Offline validate — synchronous, no network, never throws; failures resolve to 'free'.
const { info } = license.validate();
if (info.isPaid) { /* licensed */ }
// (B) Air-gapped lease: import the file the vendor signed for THIS machine. Throws if invalid.
try {
const leased = license.importOfflineLease(fs.readFileSync('offline-lease.json', 'utf8'));
// leased.isValid === true, fully offline
} catch (err) { showOfflineLeaseError(err.message); }
// (C) Coverage block file: no SDK call in Node — import it with the .NET SDK, or verify the
// signed envelope yourself over raw HTTP (below).
Python
# (A) Offline validate — no network, never raises; failures resolve to Edition.FREE.
info = license.validate_info()
if info.is_paid:
... # licensed
# (B) Air-gapped lease: import the file the vendor signed for THIS machine. Raises if invalid.
try:
with open("offline-lease.json", encoding="utf-8") as f:
leased = license.import_offline_lease(f.read()) # leased.is_valid, fully offline
except ValueError as ex:
show_offline_lease_error(str(ex))
# (C) Coverage block file: no SDK call in Python — use the .NET SDK, or verify the envelope over raw HTTP.
Java
import java.nio.file.Files;
import java.nio.file.Path;
// (A) Offline validate — no network, never throws; failures resolve to Edition.FREE.
LicenseInfo info = client.validateInfo();
if (info.isPaid()) { /* licensed */ }
// (B) Air-gapped lease: import the file the vendor signed for THIS machine. Throws if invalid.
try {
LicenseInfo leased = client.importOfflineLease(Files.readString(Path.of("offline-lease.json")));
} catch (IllegalArgumentException | IllegalStateException ex) {
showOfflineLeaseError(ex.getMessage());
}
// (C) Coverage block file: no SDK call in Java — use the .NET SDK, or verify the envelope over raw HTTP.
Cualquier lenguaje (HTTP en bruto)
# (A/B) An offline lease is an ordinary signed lease with a long leaseExpiresUtc — verify it with the
# EXACT same code as an online lease: rebuild the canonical payload, RSA-verify, then check
# product / machine / expiry, failing closed on anything. See "Use the HTTP API directly".
# (Offline leases are never marked trial, so no |trial suffix applies.)
# (C) A coverage block file carries its own signed envelope and a monotonic sequence number:
# verify the signature the same way, bind it to the license, and reject a sequence number
# lower than the one the machine already holds (anti-rollback).
Algunas cosas que el SDK garantiza aquí, para que tu UX sea correcta:
Validate()y las comprobaciones de cobertura nunca lanzan excepciones por condiciones offline/de red;ImportOfflineLeasesí lanza una excepción ante un lease inválido, así que envuélvelo.- Un bloque ausente nunca es «sin restricciones». Eliminar un bloque de cobertura de una máquina produce
UnavailableOffline(en una compilación que aplica cobertura) o ninguna cobertura — nunca un pase silencioso.CheckCoverageAsyncdevuelveUnavailableOfflinecuando no hay nada en caché y no se puede llegar al servicio — la señal para mostrar «importa un archivo de licencia actualizado». - El anti-retroceso es un número de secuencia. Una importación
RolledBacksignifica que el archivo es más antiguo que lo que la máquina ya tiene y se ignoró;WrongBindingsignifica que se emitió para otra licencia/producto;UnknownKeysignifica una clave que tu compilación no integra (durante una rotación de claves, conserva la clave pública anterior enAdditionalPublicKeysBase64).
Los consumidores de HTTP en bruto verifican un lease offline con exactamente el mismo código que un lease en línea — es un lease firmado ordinario con un leaseExpiresUtc largo — usando la receta de carga útil canónica de Usar la API HTTP directamente.
Anti-retroceso y el modelo de leases, en breve
Cada documento offline está vinculado a la huella de una máquina (dentro de la carga útil firmada, de modo que una copia a otra máquina falla con MachineMismatch), y los bloques de cobertura llevan un número de secuencia monótono para que un archivo antiguo no pueda reproducirse para restaurar una cobertura caducada. El vencimiento forma parte de los bytes firmados — editar una fecha rompe la firma — y retrasar el reloj lo detecta la detección de manipulación del reloj en pruebas y licencias por plazo. Ese es el resumen; el tratamiento completo, incluido por qué una licencia de un solo puesto no puede ejecutarse en dos máquinas ni siquiera offline, está en Seguridad y el modelo de leases.
Cloud frente a autoalojado
- Los flujos offline son idénticos — mismos endpoints, mismos archivos, misma verificación local. La diferencia es solo
$BASE/$TOKEN. - Los archivos de licencia independientes mediante la CLI necesitan la clave privada del producto para firmar; eso es natural en autoalojado (tú la tienes), y en Cloud normalmente usarías en su lugar la ruta del lease aislado (acuñado en el servidor), ya que la clave privada nunca sale del servicio alojado.
- Una nota sobre la desactivación offline: una máquina aislada no puede devolver su puesto por sí misma. Libéralo del lado del proveedor una vez que tu backend tenga conectividad, o emite leases offline con un TTL lo bastante corto para que el puesto de una máquina retirada vuelva por sí solo.
Verificar
- Archivo de licencia: firma un
license.json, apuntaKEYRIGHT_LICENSEa él en una máquina desconectada, ejecuta la aplicación —Validate()devuelve licenciada sin red. - Lease aislado: lee el id de una máquina de prueba, acuña un lease offline de 365 días para ella, ejecuta
ImportOfflineLeaseen esa máquina —Valid, offline. Copia el archivo a una segunda máquina —MachineMismatch. - Archivo de cobertura: exporta el bloque de cobertura de una licencia, impórtalo con
ImportCoverageBlocken una compilación que aplique cobertura, confirma que la versión cubierta se ejecuta y que una versión fuera de rango muestra el mensaje de importar/renovar. - Anti-retroceso: importa un archivo de cobertura más antiguo después de uno más nuevo — espera
RolledBacky ningún cambio. - Contabilidad de puestos: confirma que acuñar el lease offline consumió un puesto (el contador
usedde la licencia subió), demostrando que offline no es una escapatoria de puestos.
Véase también
- Activación y desactivación — los pasos completos de activación offline/aislada y la liberación de puestos del lado del proveedor.
- Seguridad y el modelo de leases — bloqueo por equipo, anti-retroceso y los formatos firmados en profundidad.
- Cobertura de versiones — exportar e importar bloques de cobertura para máquinas aisladas.
- Usar la API HTTP directamente — verificar un lease offline en cualquier lenguaje.
- Integración del SDK de .NET — configuración del cliente y rotación de claves.