Skip to content
← Todas las publicaciones
· Delta1 Labs Licenciamiento.NETAnálisis a fondo

Una licencia, cuatro runtimes: verificar la misma clave de Keyright desde .NET, Node, Python y Java

Una línea de producto rara vez es un solo lenguaje —una app de escritorio .NET, una CLI de Node, una herramienta de datos en Python, un servicio JVM, todo vendido como un producto—. La respuesta perezosa da a cada uno su propio licenciamiento y deja al cliente haciendo malabares con cuatro claves. La buena: un único tenant firma una licencia con una clave privada RSA, cada componente incrusta la misma clave pública y cuatro SDK verifican offline el formato idéntico de licencia y lease firmados. Esta entrada muestra el mismo flujo de inicializar-validar-restringir en C#, JavaScript, Python y Java en paralelo, explica por qué la huella de máquina es compatible byte a byte entre todos ellos para que un asiento cubra cada componente en una máquina, y cubre las dos cosas que son genuinamente por componente —la caché local del lease y la ruta de resolución de licencia— además de cómo una rotación de clave recorre toda la línea a la vez.

La mayoría de los productos que la gente llama “una app” son en realidad varios programas en una gabardina. Lo que un cliente compra como Acme Suite puede ser una app de escritorio WPF en C#, una herramienta de línea de comandos de dotnet y npm en Node, una biblioteca de machaque de datos en Python y un servicio de larga vida en la JVM —cuatro runtimes, una compra—. La pregunta que decide si licenciar ese producto es agradable o miserable es simple: ¿obtiene el cliente una licencia que cada componente honra, o cuatro activaciones separadas que se desincronizan la primera vez que alguien toca la página de precios?

La respuesta de Keyright es que los cuatro SDK —.NET, Node.js, Python y Java— verifican exactamente el mismo formato firmado de licencia y lease contra la misma clave pública. Un tenant firma; cuatro runtimes verifican; el cliente ve una clave. Esta entrada muestra ese flujo en los cuatro lenguajes a la vez, y luego dedica su tiempo a las dos cosas que son genuinamente compartidas (el modelo de firma y la huella de máquina) y las dos que son genuinamente por componente (la caché del lease y la ruta de resolución), porque ahí es donde un despliegue políglota triunfa o tropieza.

El modelo compartido, una vez

El modelo es idéntico sin importar qué runtime pregunta. Emites una clave de licencia del lado servidor; tu tenant la firma con una clave privada RSA que nunca sale del servidor. Cada componente de tu producto incrusta solo la clave pública correspondiente —una cadena base64 SubjectPublicKeyInfo que no es un secreto y solo puede verificar, nunca acuñar—. Una comprobación rutinaria verifica la clave offline contra esa clave pública: firma, slug de producto, node-lock, caducidad, revocación, estado de manipulación del reloj. Cualquier cosa que no cuadre se resuelve a la edición Free en lugar de lanzar una excepción. Falla cerrado, en todas partes, de la misma forma.

tenant · servidorclave privada RSAfirma · nunca salelicencia firmada+ clave pública.NET — C#Node.js — JSPythonJava — JVMuna máquinamisma huella= un asiento

El mismo flujo en cuatro lenguajes

Aquí está toda la historia del cliente —inicializar una vez, validar offline, restringir una función según un entitlement— en cada SDK. Observa que nada salvo el lenguaje cambia: el mismo slug de producto, la misma clave pública, el mismo entitlement export.

.NET — validate() devuelve un LicenseInfo; la activación es async:

using Keyright.Client;

static readonly KeyrightClient License = KeyrightClient.Initialize(new KeyrightOptions
{
    Product         = "acme-suite",                        // one slug for the whole product line
    PublicKeyBase64 = "MIIBIjANBgkq...",                   // the same public key in every component
    ServiceUrl      = "https://licensing.acme.example",    // your issuing service
});

LicenseInfo info = License.Validate();                     // offline, never throws, fails closed to Free
if (info.IsPaid && License.IsEnabled("export"))
    EnableExport();

Node.js — validate() devuelve { info, source }; activate es async:

const { KeyrightClient } = require('keyright');

const license = KeyrightClient.initialize({
  product:         'acme-suite',
  publicKeyBase64: 'MIIBIjANBgkq...',
  serviceUrl:      'https://licensing.acme.example',
});

const { info } = license.validate();                       // synchronous, fails closed to free
if (info.isPaid && info.entitlements.isEnabled('export'))
  enableExport();

Python — validate() devuelve una tupla (info, source), con validate_info() para solo la info:

from keyright import KeyrightClient, KeyrightOptions

license = KeyrightClient.initialize(KeyrightOptions(
    product="acme-suite",
    public_key_base64="MIIBIjANBgkq...",
    service_url="https://licensing.acme.example",
))

info = license.validate_info()                             # fails closed to Free
if info.is_paid and info.entitlements.is_enabled("export"):
    enable_export()

Java — validate() devuelve un Result con campos públicos info/source; la activación es síncrona:

import com.delta1labs.keyright.*;

KeyrightClient client = KeyrightClient.initialize(
    new KeyrightOptions()
        .product("acme-suite")
        .publicKeyBase64("MIIBIjANBgkq...")
        .serviceUrl("https://licensing.acme.example"));

LicenseInfo info = client.validateInfo();                  // fails closed to Free
if (info.isPaid() && info.entitlements.isEnabled("export"))
    enableExport();

Cuatro runtimes, un comportamiento. Las únicas diferencias son de idioma —PascalCase frente a camelCase frente a snake_case, una tupla frente a un objeto frente a un parámetro out para el source de resolución, async en C# y Node contra llamadas síncronas en Python y Java—. La verificación RSA, la garantía de fallar cerrado a Free y la semántica de entitlements son byte a byte iguales.

La recompensa: un asiento cubre toda la máquina

La función que hace esto más que una comodidad es el node-locking. Cada SDK deriva una huella de máquina —anclada en el GUID de máquina del SO más señales blandas como el nombre de host y el SO— y esa huella es compatible byte a byte entre los cuatro SDK. La documentación del SDK de Java lo dice claro: su huella es “compatible byte a byte con los otros SDK”. La consecuencia práctica es grande.

Cuando cualquier componente se activa, envía machineId = MachineFingerprint.current().toBoundString() (la misma cadena en cada lenguaje) y el servidor vincula un asiento a (key, machineId). Como el id de máquina es idéntico entre la app .NET, la CLI de Node, la herramienta Python y el servicio JVM en una máquina, y como reactivar una máquina ya vinculada es idempotente, los cuatro componentes en esa máquina se resuelven al mismo asiento. Una licencia de tres asientos son tres máquinas, no doce activaciones de componente. Tu cliente instala la suite en tres portátiles y queda completamente licenciado —exactamente el modelo mental que ya tiene, y exactamente lo que un contador por componente habría hecho mal.

Para obtener el id de una máquina para una llamada de soporte o una activación air-gapped, cada SDK ofrece lo mismo en su propio idioma —MachineFingerprint.current().toBoundString() en .NET y Java, .to_bound_string() en Python, MachineFingerprint.current().toBoundString() en Node— y una forma corta de visualización KR-XXXX-XXXX para humanos.

Qué es genuinamente por componente

Dos cosas no cruzan fronteras de componente automáticamente, y un despliegue limpio planifica para ambas.

La caché del lease es local a cada SDK. Cuando un componente se activa, cachea el lease firmado en su propia ruta de app-data del SO (.../Keyright/<product>/lease.json y un archivo de lease hermano). La caché de la app .NET no es la caché de la CLI de Node. Así que en la configuración por defecto cada componente se activa una vez —el primero hace un viaje de ida y vuelta real y consume el asiento; cada componente posterior en la misma máquina reactiva, cuadra con el asiento existente gratis, y cachea su propio lease—. Eso es inofensivo pero son N activaciones. Si prefieres activar una vez para toda la suite, tienes dos opciones limpias:

  • Apunta cada componente a un único archivo de licencia mediante la variable de entorno compartida. Los cuatro SDK honran KEYRIGHT_LICENSE como fuente de resolución (Node la lee cuando fijas envVarName: 'KEYRIGHT_LICENSE'; los demás la usan por defecto), así que un instalador que deja un archivo de licencia activado y exporta KEYRIGHT_LICENSE licencia todo el paquete sin viaje de ida y vuelta por componente.
  • O deja que un componente sea dueño de la activación y escriba su lease en una ruta que los otros leen mediante configLicensePath/LicenseFilePath.

El orden de resolución es propio de cada SDK. Cada SDK busca una licencia en la misma precedencia —una cadena de licencia explícita, luego una ruta de archivo explícita, luego la variable de entorno KEYRIGHT_LICENSE, luego una ruta de config, luego el app-data por defecto del SO, luego el lease en caché— pero recorre esa lista en su proceso, contra sus rutas configuradas. Eso es una característica: un servicio JVM alojado en contenedor puede recibir su licencia por KEYRIGHT_LICENSE desde su almacén de secretos mientras la app de escritorio C# en el portátil del mismo cliente usa una activación interactiva y su lease en caché, y ambos verifican el documento firmado idéntico.

Rotar la clave por toda la línea

Como hay un par de claves detrás de todo el producto, una rotación de clave de firma es una sola versión coordinada en lugar de cuatro desbarajustes independientes. Cada SDK expone la misma salida de emergencia: junto a la clave pública nueva listas la saliente, y las licencias y leases firmados por cualquiera siguen validando durante la transición.

// .NET
AdditionalPublicKeysBase64 = { "<previous public key>" },
// Node
additionalPublicKeysBase64: ['<previous public key>'],
# Python
additional_public_keys_base64=["<previous public key>"],
// Java (public field, no fluent setter)
opts.additionalPublicKeysBase64.add("<previous public key>");

Envía cada componente con la clave nueva primaria y la clave vieja en la lista adicional, deja que el campo se drene a medida que caducan los leases viejos, luego quita la clave vieja en una versión posterior. La entrada rotar tus claves de firma cubre el calendario; la única arruga políglota es que “enviar una versión” ahora significa cuatro versiones portando las mismas dos claves.

La conclusión

Una línea de producto en cuatro lenguajes no necesita cuatro sistemas de licenciamiento —necesita un formato firmado y cuatro verificadores de él, que es exactamente lo que son los SDK de Keyright—. El modelo de firma es compartido (una clave privada firma, una clave pública verifica, en todas partes), la huella de máquina es compartida (compatible byte a byte, así que un asiento cubre cada componente en una máquina), y la semántica de entitlements es compartida (los mismos flags y límites, leídos fail-closed del mismo claim). Lo que es local —la caché del lease y la ruta de resolución— es local por diseño, y la variable de entorno KEYRIGHT_LICENSE lo colapsa a una sola activación cuando quieres eso. El cliente compra una licencia; C#, JavaScript, Python y la JVM todos la honran; y tú mantienes un tenant en lugar de cuatro.

Véanse las guías por lenguaje para la superficie completa del cliente —.NET, Node.js, Python y Java— y licenciar servicios en contenedor para el componente de la suite que escala horizontalmente.

Prueba Nebula.NET

Endurece tu código .NET en minutos — empieza con la edición gratuita.