rymga ← Volver a Licenses

Verificar la firma

Esta es la parte que debes implementar correctamente. El backend firma cada respuesta con la clave privada de tu tenant y tu software la verifica con la clave pública que integras. Un veredicto que no puedes verificar no es un veredicto: trátalo como no válido, diga lo que diga el cuerpo.

Por qué importa

Sin comprobar la firma, cualquiera que pueda interponerse entre tu aplicación y la red —un proxy, una entrada DNS modificada o un archivo hosts local o una compilación manipulada— puede entregar a tu aplicación un { "valid": true } falso y desbloquearlo todo. La firma cierra esa brecha: la respuesta solo es fiable si la produjo tu clave privada, que nunca sale del backend.

Deniega ante cualquier fallo. Una cabecera ausente, una clave incorrecta, un cuerpo malformado o un error de verificación significan siempre no válido. Solo se acepta una respuesta cuya firma se haya verificado.

Las cabeceras

X-SignatureBase64 Firma RSA (SHA256withRSA) sobre los bytes exactos del cuerpo de la respuesta.
X-KidIdentificador de la clave de firma, para poder gestionar su rotación (consulta más abajo).

Pasos

  1. Lee los bytes sin procesar del cuerpo de la respuesta. No vuelvas a serializar el JSON: recodificarlo cambia los bytes y rompe la firma.
  2. Decodifica desde Base64 X-Signature.
  3. Verifícala con la clave pública de tu tenant (X.509 SubjectPublicKeyInfo, Base64) mediante SHA256withRSA sobre esos bytes sin procesar.
  4. Solo si la verificación tiene éxito, procesa el JSON y lee el veredicto.

Ejemplos

Verifica la firma Base64 sobre los bytes exactos de la respuesta con la clave pública integrada. Java, Node, C# y Go verifican RSA con su biblioteca estándar, sin dependencias adicionales. Python no incluye un verificador RSA en su biblioteca estándar, por lo que el ejemplo usa el conocido paquete cryptography (pip install cryptography).

// PUBLIC_KEY_B64 = your tenant public key (X.509, Base64), embedded as a constant.
byte[] der = Base64.getDecoder().decode(PUBLIC_KEY_B64);
PublicKey pub = KeyFactory.getInstance("RSA")
        .generatePublic(new X509EncodedKeySpec(der));

Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pub);
sig.update(rawBody);                                  // exact response bytes
boolean ok = sig.verify(Base64.getDecoder().decode(xSignature));
if (!ok) throw new SecurityException("SIGNATURE_INVALID");
import { createVerify } from "node:crypto";

// PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\n...";
const ok = createVerify("RSA-SHA256")
  .update(rawBody)                        // Buffer of the exact response bytes
  .verify(PUBLIC_KEY_PEM, xSignature, "base64");

if (!ok) throw new Error("SIGNATURE_INVALID");
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
import base64

pub = serialization.load_pem_public_key(PUBLIC_KEY_PEM)
try:
    pub.verify(base64.b64decode(x_signature),
               raw_body,                 # exact response bytes
               padding.PKCS1v15(), hashes.SHA256())
except Exception:
    raise ValueError("SIGNATURE_INVALID")
using System.Security.Cryptography;

using var rsa = RSA.Create();
rsa.ImportFromPem(PUBLIC_KEY_PEM);       // or ImportSubjectPublicKeyInfo(der)

bool ok = rsa.VerifyData(
    rawBody,                             // exact response bytes
    Convert.FromBase64String(xSignature),
    HashAlgorithmName.SHA256,
    RSASignaturePadding.Pkcs1);
if (!ok) throw new CryptographicException("SIGNATURE_INVALID");
block, _ := pem.Decode([]byte(publicKeyPEM))
pub, _ := x509.ParsePKIXPublicKey(block.Bytes)
sig, _ := base64.StdEncoding.DecodeString(xSignature)

sum := sha256.Sum256(rawBody)           // exact response bytes
if err := rsa.VerifyPKCS1v15(
    pub.(*rsa.PublicKey), crypto.SHA256, sum[:], sig); err != nil {
    return errors.New("SIGNATURE_INVALID")
}

Obtener la clave

Descarga del panel la clave pública de tu tenant, o recupérala una vez y fíjala:

GET https://licenses.rymga.com/t/{slug}/api/v1/pubkey

Intégrala como constante en tu compilación. No la obtengas en tiempo de ejecución mediante HTTP sin cifrar: quien pueda sustituir la clave también podrá sustituir el veredicto. La finalidad es fijar la clave en el binario.

Rotación de claves

Cada respuesta incluye X-Kid, el id de la clave que la firmó. Si rotas el par de claves de firma, publica una compilación que conozca tanto la clave pública antigua como la nueva, indexadas por X-Kid, para que las instalaciones existentes sigan verificando durante la transición.