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.
Las cabeceras
| X-Signature | Base64 Firma RSA (SHA256withRSA) sobre los bytes exactos del cuerpo de la respuesta. |
| X-Kid | Identificador de la clave de firma, para poder gestionar su rotación (consulta más abajo). |
Pasos
- Lee los bytes sin procesar del cuerpo de la respuesta. No vuelvas a serializar el JSON: recodificarlo cambia los bytes y rompe la firma.
- Decodifica desde Base64
X-Signature. - Verifícala con la clave pública de tu tenant (X.509 SubjectPublicKeyInfo, Base64) mediante
SHA256withRSAsobre esos bytes sin procesar. - 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/pubkeyInté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.