Firma
CDS usa RSA-PSS con SHA-256. Esta página describe el algoritmo en su totalidad para que cualquiera pueda implementar un firmante o verificador conforme sin leer el código fuente del SDK.
Requisitos de las claves
Sección titulada «Requisitos de las claves»| Propiedad | Valor |
|---|---|
| Algoritmo | RSA-PSS |
| Tamaño de clave | 4096 bits mínimo |
| Hash | SHA-256 |
| MGF | MGF1 con SHA-256 |
| Longitud del salt | Máxima (PSS.MAX_LENGTH) |
| Formato de clave | PKCS#8 PEM (privada), SubjectPublicKeyInfo PEM (pública) |
Serialización canónica
Sección titulada «Serialización canónica»La entrada a la función de firma es el JSON canónico del evento.
Reglas:
- Serializar el evento como JSON con las claves ordenadas alfabéticamente (
sort_keys=True) - Codificar como bytes UTF-8 (sin BOM)
- Excluir el campo
integritypor completo - Excluir el campo
ingested_atpor completo - Sin newline final, sin espacios en blanco extra
ingested_at se excluye porque se establece en el momento de la ingestión y puede diferir
entre el productor y una re-ingestión. integrity se excluye porque
contiene la propia firma.
import json
def canonical_bytes(event: CDSEvent) -> bytes: data = event.model_dump( exclude={"integrity", "ingested_at"}, mode="json", ) return json.dumps(data, sort_keys=True, ensure_ascii=False).encode("utf-8")canonicalBytes(): Buffer { const { integrity: _i, ingested_at: _ia, ...rest } = this.toJSON(); const sorted = Object.fromEntries( Object.entries(rest).sort(([a], [b]) => a.localeCompare(b)) ); return Buffer.from(JSON.stringify(sorted), "utf-8");}Firma (productor)
Sección titulada «Firma (productor)»import hashlib, base64from cryptography.hazmat.primitives import hashesfrom cryptography.hazmat.primitives.asymmetric import padding
canonical = event.canonical_bytes()
# 1. Hashhash_hex = hashlib.sha256(canonical).hexdigest()payload_hash = f"sha256:{hash_hex}"
# 2. Firmaraw_signature = private_key.sign( canonical, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA256(),)
# 3. Adjuntarevent.integrity = IntegrityMeta( hash = payload_hash, signature = base64.b64encode(raw_signature).decode(), signed_by = issuer,)Verificación (consumidor)
Sección titulada «Verificación (consumidor)»import hashlib, base64from cryptography.hazmat.primitives import hashesfrom cryptography.hazmat.primitives.asymmetric import padding
if not event.integrity: raise ValueError("Event has no integrity metadata.")
canonical = event.canonical_bytes()
# 1. Verificar el hashexpected = "sha256:" + hashlib.sha256(canonical).hexdigest()if expected != event.integrity.hash: raise ValueError(f"Hash mismatch. Expected {expected}, got {event.integrity.hash}")
# 2. Verificar la firmaraw_signature = base64.b64decode(event.integrity.signature)public_key.verify( raw_signature, canonical, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH, ), hashes.SHA256(),)# Lanza cryptography.exceptions.InvalidSignature en caso de fallo¿Por qué RSA-PSS y no ECDSA o Ed25519?
Sección titulada «¿Por qué RSA-PSS y no ECDSA o Ed25519?»CDS eligió RSA-4096 PSS por conservadurismo y compatibilidad:
- Soporte amplio. RSA-PSS es soportado nativamente por
cryptographyde Python,cryptode Node.js, AWS KMS, Azure Key Vault y todos los HSM del mercado. - Cumplimiento FIPS. Requerido por algunos consumidores empresariales.
- Sin política de selección de curvas. La elección de curva ECDSA (P-256 vs secp256k1) es contenciosa. RSA lo evita.
El compromiso es el tamaño de la clave (RSA de 4096 bits → firmas de ~512 bytes vs 64 bytes para Ed25519). Para feeds de datos donde las firmas se adjuntan a eventos JSON, esto es aceptable.
Una revisión futura de la especificación puede añadir Ed25519 como un algoritmo opcional. RSA-PSS SHA-256 seguirá siendo el mínimo requerido.
Campos de los metadatos de integridad
Sección titulada «Campos de los metadatos de integridad»{ "hash": "sha256:a1b2c3d4...", "signature": "MX6rj3qK...", "signed_by": "signed-data.org"}| Campo | Descripción |
|---|---|
hash | "sha256:" + hex(SHA256(canonical_bytes)) |
signature | base64(RSA-PSS-sign(canonical_bytes, private_key)) |
signed_by | Identificador del emisor — típicamente un nombre de dominio |
El campo hash es redundante con la firma (una firma válida implica
que el hash es correcto) pero se incluye para un rechazo rápido antes de la verificación
de la firma, que es computacionalmente costosa.
Distribución de claves
Sección titulada «Distribución de claves»El emisor DEBERÍA publicar su clave pública en:
https://{issuer}/.well-known/cds-public-key.pemPara signed-data.org:
https://signed-data.org/.well-known/cds-public-key.pemEsto sigue la misma convención que DKIM, ACME y otros esquemas URI well-known.