Verificar Mensajes

Cómo verificar mensajes shinkansen con JWS

Verificar Mensajes

Los mensajes que recibes via POST desde Shinkansen también vienen firmados. Para evitar ataques y fallas de seguridad debes verificar dicha firma.

Verificar Firma

Para verificar la firma, debemos realizar pasos similares a los que hicimos al generarla. El resumen es:

  1. Debes chequear la firma del JWS detached que recibes usando el certificado en la cabecera x5c del JWS junto al payload que recibiste via HTTP. Siempre debes usar el algoritmo PS256. Si el JWS especifica otro algoritmo, debes rechazarlo.

    ⚠️

    El payload usado debe ser tal cual lo recibiste via HTTP.

    Si lo parseas como JSON y luego lo vuelves a llevar a un string, es posible que modifiques el payload y la firma no sea válida

  2. Debes chequar que el certificado que recibiste en el x5c era un certificado válido para tu contraparte

    ⚠️

    Este paso es crítico.

    Si no comparas el certificado con un whitelist de certificado(s) que esperabas de tu(s) contraparte(s), cualquiera podría enviarte un JWS con su propio certificado en la cabecera x5c firmado con la llave privada correspondiente. ¡Todo será válido!. Es crucial entonces que valides que el certificado estaba en la lista blanca de certificados que aceptas como válidos de tu contraparte. Al cargar certificados en esa lista blanca en memoria, recomendamos también realizar los otros chequeos correspondientes: fecha de expiración, cadena de firmas, revocación, etc.

Ejemplos

En la práctica esto es bastante mecánico usando librerías existentes. Puedes ver el ejemplo mas abajo en Python como referencia.

O aún mejor, puede resultar en muy pocas líneas usando librerías/wrappers que hemos creado, como el ejemplo Python que usa python-shinkansen más abajo.

from shinkansen.responses import ResponseMessage
from shinkansen.common import SHINKANSEN, FinancialInstitution

detached_jws = ... # read from HTTP header
payload = ... # read from HTTP body
shinkansen_certificates = [...] # previously loaded in memory
response_message = ResponseMessage.from_json(payload)
response_message.verify(
    detached_jws, shinkansen_certificates,
    sender=SHINKANSEN,
    receiver=FinancialInstitution("MY-IDENTIFIER-AS-RECEIVER")
) # raises an exception if invalid

# We are good, we can continue reading message.header, message.responses...
from jwcrypto.jwk import JWK
from jwcrypto.jws import JWS
from base64 import b64encode, b64decode
from cryptography.hazmat.primitives import serialization
from cryptography import x509
import json

detached_jws = ... # read from HTTP header
payload = ... # read from HTTP body
certificate_whitelist = [...] # previously loaded in memory

# Parse JWS:
jws = JWS()
jws.deserialize(detached_jws)

# Find certificate:
if ("x5c" not in jws.jose_header) or (not jws.jose_header["x5c"]):
    raise ... # Reject, missing x5c certificate

# Take the first certificate and parse it:
b64_der_certificate = jws.jose_header["x5c"][0]
der_certificate = b64decode(b64_der_certificate)
certificate = x509.load_der_x509_certificate(der_certificate)

# Create a JWK public key from certificate:
jwk = JWK()
jwk.import_from_pyca(certificate.public_key())

# Verify:
jws.verify(jwk, alg="PS256",
    detached_payload=payload) # raises exception if invalid

# Make sure the certificate was whitelisted

if all(
    # Compare every certificate c in whitelist against the x5c certificate
    c.public_bytes(encoding=serialization.Encoding.DER) != der_certificate
    for c in certificate_whitelist
):
    raise ... # Reject if certificate not on the whitelist

# We are good, continue
using Jose;
using System.Linq;
using System.Text;
using System.Collections;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Reflection.PortableExecutable;
using System.Security.Cryptography.X509Certificates;

// read detached jws from HTTP header
string detached_jws = "";
if (callbackResponse.Headers.TryGetValues("Shinkansen-JWS-Signature",
    out IEnumerable<string>? signature_value))
{
    detached_jws = signature_value.First();
}

// read payload from HTTP body
string responsePayload = callbackResponse.Content.ReadAsStringAsync().Result;

// Parse JWS, find certificate and convert it to X509 Cert
IDictionary<string, object> JwtHeaders = JWT.Headers(detached_jws);
_ = JwtHeaders.TryGetValue("x5c", out object? x5cContent);
List<object> c = x5cContent as List<object>;
byte[] certBytes = Convert.FromBase64String(c.First() as string);
var x5cCert = new X509Certificate2(certBytes);

// Probably read from memory or another source.
var ShinkansenWhitelistedCert = X509Certificate2.CreateFromCertFile("shinkansen_dev_cert.pem");

// Compare certificate with whitelisted
if (ShinkansenWhitelistedCert.GetRawCertDataString() == x5cCert.GetRawCertDataString())
{
    // Verify signature
    string verifiedJson = JWT.Decode(detached_jws, x5cCert.GetRSAPublicKey(),
        JwsAlgorithm.PS256, payload: responsePayload);
    //...
}