Validación de licencias sin conexión con claves firmadas, explicada
Cómo funciona la validación de licencias sin conexión: firmas RSA firmadas en el servidor y verificadas en el cliente contra una clave pública embebida, bloqueo por equipo mediante huella de la máquina, caducidad, listas de revocación y por qué importa fallar en cerrado.
La validación de licencias sin conexión es la capacidad de confirmar que una licencia es auténtica y sigue siendo válida sin ninguna llamada de red, y funciona gracias a una asimetría de la criptografía de clave pública: tu servidor guarda una clave privada que puede crear firmas, mientras que tu aplicación guarda solo la clave pública correspondiente, que puede verificar una firma pero nunca falsificarla. Esa única propiedad permite que un cliente demuestre que una licencia fue emitida por ti, y que no ha sido manipulada, enteramente en su propia máquina. Este artículo explica cómo funciona todo esto de principio a fin —firmas, bloqueo por equipo, caducidad, revocación y ventanas de gracia— y por qué el conjunto tiene que fallar en cerrado para que valga de algo.
Por qué una firma basta para confiar en una licencia sin conexión
Imagina que tu aplicación solo comprobara si un archivo de licencia dice "tier": "pro". Se derrota trivialmente: cualquiera puede editar un archivo de texto. La firma es lo que hace que el archivo sea digno de confianza.
Cuando emites una licencia, el servidor toma la carga útil de la licencia (titular, producto, nivel, puestos, caducidad, derechos) y calcula una firma RSA sobre ella con una clave privada. Esa firma se adjunta a la licencia. Tu aplicación embebe la clave pública correspondiente y, en el momento de la validación, rehace la comprobación: ¿corresponde esta firma a esta carga útil exacta bajo esta clave pública? Si es así, quedan demostradas dos cosas a la vez: la licencia fue firmada con tu clave privada (autenticidad) y no ha cambiado ni un solo byte desde entonces (integridad). Cambia un carácter en el campo del nivel y la firma deja de coincidir; la validación falla.
La razón por la que esto no necesita servidor es que la verificación es matemática pura sobre datos que la aplicación ya tiene. La clave pública no es un secreto y puede viajar dentro de tu binario: decompila la aplicación, lee la clave, publícala, y un atacante aún no podrá acuñar una licencia, porque acuñarla requiere la clave privada que nunca salió de tu servidor. Es la misma asimetría que hay detrás de TLS y de la firma de código, aplicada al licenciamiento.
Las comprobaciones que se ejecutan tras la firma
Una firma válida solo demuestra que la licencia es genuina y no ha sido alterada. A continuación, la validación sin conexión lee los campos ya confiables y los hace cumplir localmente.
Bloqueo por equipo mediante huella de la máquina
Para impedir que una licencia se copie a mil máquinas, una licencia puede estar bloqueada por equipo: vinculada a una huella que la aplicación deriva de atributos estables del hardware y del sistema operativo. En la validación, la aplicación recalcula la huella y la compara con la que viene grabada en la licencia. Si no coinciden, la licencia no aplica aquí.
La sutileza es la tolerancia. Si defines la huella de forma demasiado estricta, un cliente que cambie una tarjeta de red o actualice un disco quedará bloqueado. Por eso una buena implementación permite una pequeña deriva —el SDK de Keyright usa un NodeLockTolerance configurable (por defecto 1) para que un cambio corriente de hardware no dispare el bloqueo— y te deja controlar qué componentes identifican a una máquina en entornos poco habituales como VM y ejecutores de CI.
Caducidad y manipulación del reloj
Una suscripción o una prueba lleva un expiryUtc, y la validación sin conexión simplemente lo compara con la hora actual. Lo que plantea el ataque obvio: atrasa el reloj del sistema y una prueba nunca termina. La validación sin conexión tiene que defenderse de eso sin un servidor de tiempo. El enfoque estándar es recordar la última hora que la aplicación ha visto legítimamente y tratar un reloj que salta hacia atrás de forma significativa como manipulación. El SDK de Keyright hace exactamente esto: atrasar el reloj más allá de una ventana ClockTamperToleranceHours (por defecto 24 h) en una licencia con límite temporal produce un estado ClockTampered y se niega a validar hasta que se corrija la hora. Las licencias perpetuas, al no tener caducidad, no están sujetas a esta comprobación.
Revocación, sin conexión
Aquí es donde el enfoque exclusivamente sin conexión muestra su única limitación real, y cómo cerrarla. Una licencia firmada el año pasado no sabe nada de un reembolso o una filtración ocurridos la semana pasada: la carga útil firmada queda fijada en el momento de la emisión. En línea, la revocación es una marca del servidor que se recoge en la siguiente actualización de activación. Sin conexión, distribuyes una lista de revocación firmada: una lista de identificadores de licencias revocadas, firmada a su vez con tu clave privada para que el cliente pueda confiar en ella, que se entrega con una actualización de la aplicación. Un cliente puramente sin conexión compara las licencias entrantes con esa lista y descarta las revocadas. No es instantáneo —avanza al ritmo de tus versiones— pero significa que una aplicación desconectada no está indefensa frente a una clave que se sabe comprometida. Keyright puede construir y firmar una lista de revocación distribuible que entregas con RevocationListJson / RevocationListPath.
Prioridad a lo offline, con una ventana de gracia en línea opcional
La validación puramente sin conexión es perfecta para instalaciones aisladas (air-gapped) y para un control local rápido, pero no puede contar puestos —un cliente no tiene ni idea de cuántas otras máquinas ejecutan la misma clave— y no puede reflejar una revocación más reciente que la última actualización. La respuesta común y honesta es prioridad a lo offline con una comprobación en línea opcional.
El modelo de Keyright funciona así: tu aplicación puede validar un archivo de licencia sin conexión entregado con cero red, o puede activar en línea una vez —intercambiando la clave de licencia por un lease firmado de corta duración vinculado a esta máquina—. Ese lease se verifica a su vez sin conexión contra tu clave pública embebida y se almacena en caché localmente, de modo que tras una única activación la aplicación sigue validando sin conexión durante toda la vida del lease. Solo cuando el lease está cerca de caducar necesita volver a contactar con el servidor, momento en el que se reconcilian los recuentos de puestos y las revocaciones. Si el servidor está brevemente inalcanzable, un lease en caché aún válido cubre el hueco —una ventana de gracia— para que una caída no deje fuera a un usuario de pago honesto. Obtienes resiliencia sin conexión y puestos y revocación impuestos por el servidor desde el mismo sistema, en lugar de tener que elegir uno.
Por qué “fallar en cerrado” lo es todo
Todas las comprobaciones anteriores comparten una regla de diseño, y es la regla que hace que la validación sin conexión sea realmente protectora en lugar de decorativa: ante cualquier problema, resuelve hacia el estado sin licencia; no una excepción, y nunca uno desbloqueado.
Considera la alternativa. Si una comprobación de licencia fallara en abierto —tratando un error como “permitir”—, la grieta más fácil del mundo sería provocar el error a propósito: borra el archivo de licencia, corrompe un byte, bloquea una llamada, y disfruta del software. Fallar en cerrado invierte eso: una firma incorrecta, un producto equivocado, una clave caducada o revocada, un reloj manipulado, un archivo ausente o ilegible; cada uno de ellos termina en la edición gratuita/sin licencia. El resultado seguro es el resultado predeterminado, así que romper la comprobación no le da nada al atacante.
El SDK de Keyright está construido en torno a esto. Validate() nunca lanza excepciones; ante cualquier fallo devuelve un LicenseInfo en la edición Free que lleva un Status (NoLicense, SignatureInvalid, Expired, MachineMismatch, Revoked, ClockTampered, …) y un mensaje legible que puedes mostrar en un diálogo de “Registrar”. ActivateAsync() en línea sigue la misma regla para sus rutas de fallo ordinarias —clave incorrecta, puestos agotados, sin conexión, revocada— y devuelve un resultado que falla en cerrado en lugar de lanzar una excepción. Tu rama if (info.IsPaid) es lo único que desbloquea algo, y solo se ejecuta cuando todo ha pasado la comprobación de verdad.
Siendo honestos sobre los límites
La validación sin conexión demuestra autenticidad e integridad de forma magnífica, pero no es magia. Como la comprobación, a fin de cuentas, se ejecuta en la máquina del usuario, un atacante decidido con un decompilador y un depurador puede parchearla para eliminarla: la misma verdad que se aplica a cualquier protección del lado del cliente. Eso no es razón para omitirla; es razón para ser precisos sobre su función. La validación sin conexión es la herramienta correcta y sólida para despliegues aislados (air-gapped) y empresariales, y una puerta local rápida y resiliente en todos los demás casos. Cuando necesitas hacer cumplir —contar puestos, anular pronto una clave filtrada—, la combinas con activación en línea para que esas decisiones vivan en un servidor que el atacante no controla, y haces que el cliente sea difícil de parchear con ofuscación. La validación sin conexión y la activación en línea no son rivales; son dos mitades de un sistema de licenciamiento que es a la vez resiliente y aplicable.
Dónde encaja Keyright
Keyright es, por diseño, prioritario en lo offline: cada inquilino obtiene su propia clave de firma RSA (la mitad privada se almacena cifrada en el servidor, nunca se expone), emite archivos de licencia sin conexión firmados y leases de activación en línea, bloquea por equipo con tolerancia, se defiende de la manipulación del reloj, entrega listas de revocación firmadas, y falla en cerrado a lo largo de sus SDK de .NET, Node, Python y Java. Si quieres la mecánica completa con código de SDK real, lee la documentación de Keyright: las guías de primeros pasos e integración con .NET recorren las rutas exactas de Validate() y ActivateAsync() descritas aquí.
Prueba Nebula.NET
Endurece tu código .NET en minutos — empieza con la edición gratuita.