wandres.dev
IDENTIDAD SIN SERVIDOR · DID y delegación

Autorización delegada: la cadena que llevas encima

En vez de que un servidor consulte una tabla de permisos, aportas un certificado que demuestra que alguien con autoridad te la cedió, y esas cesiones se encadenan bajo una regla que solo permite estrechar.

⏱ 22 min

Tenemos un nombre no falsificable para cada participante y ninguna forma todavía de decir qué puede hacer. Esta lección cubre esa distancia, y lo hace con una inversión que conviene enunciar antes de entrar en el detalle: el sistema deja de responder a la pregunta quién tiene permiso sobre este recurso y pasa a responder a la pregunta qué prueba puede aportar quien llama. La primera pregunta exige una tabla, y una tabla exige un lugar donde vivir. La segunda no exige nada, porque la respuesta viaja con la petición. La estructura que la transporta es una cadena de certificados donde cada eslabón es una cesión firmada de autoridad, cada eslabón apunta al anterior como prueba y la cadena entera termina necesariamente en alguien que declaraba autoridad directa sobre el recurso. Entender por qué esa cadena no puede fabricarse, por qué solo puede estrecharse y por qué se separa el momento de ceder del momento de usar es entender el modelo completo.

🎯 Al terminar esta lección sabrás
  • Leer una cadena de delegación e identificar el emisor, la audiencia, el sujeto y la prueba de cada eslabón.
  • Aplicar la regla de atenuación y reconocer cuándo una delegación es inválida por ampliar autoridad.
  • Distinguir con precisión la delegación de la invocación y saber qué papel juega cada agente.
  • Valorar el coste real de delegar la capacidad total y aplicar el principio de mínima autoridad.

De la tabla al certificado

Un certificado de autorización es, en su núcleo, una declaración firmada con tres partes: quién cede, a quién cede y qué cede. La especificación de UCAN nombra estos papeles con el símil del correo postal: el emisor y la audiencia se conceptualizan como el remitente y el destinatario de una carta, y todo certificado debe ir firmado con la clave privada asociada al identificador que figura en el campo de emisor. A esos dos se añade un tercero que es el que hace posible todo lo demás: el sujeto, el principal del que trata la cadena, que salvo indicación explícita coincide con el recurso.

La especificación define la carga útil obligatoria con precisión, y merece la pena verla porque cada campo responde a un problema concreto que hemos ido encontrando en el track. Son obligatorios el emisor, la audiencia, el sujeto, el comando que se podrá invocar, los argumentos que deberán estar presentes en la invocación, un valor de un solo uso y la caducidad, que puede ser explícitamente nula si el certificado no expira nunca. Son opcionales los metadatos y el instante a partir del cual empieza a ser válido.

{
  "iss": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
  "aud": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
  "sub": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
  "cmd": "/crud/read",
  "nbf": 1529496683,
  "exp": 1575606941
}

Todo el certificado va dentro de un sobre cuya forma también está fijada: la primera posición contiene los bytes de la firma que el emisor produjo sobre el contenido, y la segunda contiene ese contenido firmado, que a su vez incluye una cabecera autodescriptiva de firma variable y la carga útil propiamente dicha. La codificación canónica para firmar es DAG-CBOR, aunque el certificado pueda presentarse o almacenarse en otros formatos y convertirse a esa codificación en el momento de validar. Y cada certificado tiene un identificador de contenido bajo una configuración fija —versión 1, base58 con alfabeto de Bitcoin, resumen SHA-256 y códec DAG-CBOR—, lo que hace que todos ellos empiecen visiblemente por los caracteres zdpu.

ℹ️
Direccionar por contenido no es un adorno: es lo que hace posible encadenar

Que cada certificado se nombre por el resumen de su propio contenido no es una preferencia estética heredada del mundo de los sistemas distribuidos. Es lo que permite que un eslabón referencie al anterior sin ambigüedad y sin poder ser confundido con otro: el enlace apunta a un valor exacto, no a una versión de algo que podría haber cambiado. Es también lo que permite memoizar la validación por identificador, lo que hace que la revocación pueda expresarse como un conjunto de identificadores prohibidos, y lo que impide la repetición de invocaciones, que la especificación exige prevenir requiriendo que todo certificado tenga un identificador único. Es, en resumen, la misma técnica que estudiamos al direccionar por contenido puesta a trabajar sobre la autoridad en lugar de sobre los datos.

La regla que sostiene la cadena: atenuación

Una cadena de cesiones firmadas sería inútil si cada eslabón pudiera conceder más de lo que recibió, porque entonces cualquiera podría emitirse a sí mismo un certificado con autoridad total y presentarlo como prueba. La regla que lo impide se llama atenuación y su enunciado es breve: cada delegación directa debe o bien reafirmar exactamente sus capacidades o bien disminuirlas. Nunca ampliarlas. Con esa única restricción, la autoridad efectiva de una cadena es como mucho la del eslabón más restrictivo, y la validez global se comprueba examinando eslabones adyacentes de dos en dos.

La especificación describe una capacidad como la asociación de una habilidad a un sujeto, formalmente el producto del sujeto por el comando por la política, siendo obligatorios el sujeto y el comando. Los comandos son mensajes concretos que el sujeto debe poder interpretar sin ambigüedad, deben ir en minúsculas, empezar por barra, separar segmentos por barra y no llevar barra final. Ejemplos válidos que la propia especificación enumera son /, /crud, /crud/create, /stack/pop y /crypto/sign.

La estructura de segmentos no es decorativa: los comandos más cortos prueban los más largos. La especificación lo ejemplifica con precisión quirúrgica: /crypto puede servir de prueba para /crypto/sign, pero no puede probar /stack/pop ni tampoco /cryptocurrency. Ese segundo contraejemplo es el más instructivo, porque revela que la comparación es por segmentos completos y no por prefijo de cadena, que es exactamente el error que produce escaladas de privilegio en sistemas construidos a la ligera.

// La comparacion es por segmentos, nunca por prefijo textual
function pruebaComando(delegado, invocado) {
  const a = delegado.split("/").filter(Boolean);
  const b = invocado.split("/").filter(Boolean);
  if (a.length > b.length) return false;
  return a.every((seg, i) => seg === b[i]);
}

pruebaComando("/crypto", "/crypto/sign");   // true
pruebaComando("/crypto", "/cryptocurrency"); // false, y por prefijo habria dado true
flowchart RL
E4[Erin invoca] --> E3[Dan cede a Erin: crud read]
E3 --> E2[Carol cede a Dan: crud]
E2 --> E1[Bob cede a Carol: crud]
E1 --> R[raiz: Alice cede a Bob sobre su propio sujeto]
R --> A[Alice controla el recurso]
style R fill:#a6e3a1,color:#11111b
style A fill:#a6e3a1,color:#11111b
style E4 fill:#89b4fa,color:#11111b

La cadena termina siempre en el mismo tipo de eslabón, y esta propiedad es la que cierra el sistema. Toda cadena de delegación debe estar enraizada en una delegación donde el emisor y el sujeto coinciden, es decir, donde alguien cede autoridad sobre sí mismo. La especificación llama a los emisores de capacidad raíz raíces de confianza verificables y distribuidas, y observa que la cadena de delegación es por definición un registro de procedencia: no solo dice que tienes autoridad, dice exactamente por qué manos ha pasado hasta llegar a ti. En el caso más común, el identificador del propietario raíz es la única identidad globalmente única que existe para ese recurso.

Delegar no es invocar

Aquí aparece la separación que más confusión causa y que más rendimiento da cuando se entiende. El ciclo de vida tiene cuatro componentes de los cuales dos son obligatorios: la delegación, que pasa, atenúa y asegura autoridad de forma tolerante a particiones, y la invocación, que ejerce la autoridad recibida a través de uno o más delegados. Los otros dos son recomendados: la promesa, que permite esperar el resultado de una invocación dentro de otra, y la revocación, que deshace una delegación rompiendo la cadena.

Que sean momentos distintos tiene una consecuencia práctica inmediata. Recibir una delegación es idempotente y no requiere hacer nada; la especificación señala precisamente por eso que la repetición no es una preocupación al delegar. Invocar, en cambio, es un acto: pide que algo ocurra, y por tanto debe estar protegido contra la repetición, para lo cual la especificación exige que toda invocación tenga un identificador de contenido único que el agente ejecutor comprueba contra un almacén local de resúmenes no caducados.

Los papeles que puede asumir un agente se reparten con precisión entre esos dos momentos, y conviene tenerlos claros porque la especificación los usa constantemente: el emisor es el principal del certificado actual, la audiencia es aquel a quien se delega, el sujeto es aquel cuya autoridad se delega o invoca, el invocador pide que se ejecute una acción usando su autoridad, el ejecutor es quien realmente la realiza, el validador es cualquier agente que interpreta el certificado para determinar si es válido y qué concede, y el revocador es un emisor que figura en la cadena de pruebas y decide invalidarla.

⚠️
Una cadena criptográficamente impecable puede seguir siendo semánticamente inválida

Es un error frecuente creer que si las firmas encadenan y la atenuación se respeta, la operación está autorizada. La especificación advierte de lo contrario de forma explícita: una cadena estructural y criptográficamente válida puede ser semánticamente inválida, y el ejecutor debe verificar la propiedad de cualquier recurso externo en el momento de la ejecución. Dicho de otro modo, un certificado prueba que alguien te cedió algo, no que ese alguien lo tuviera de verdad en el instante en que lo usas. La recomendación de la especificación para cerrar esa brecha, cuando el caso lo permite, es que el ejecutor sea el propio recurso, de modo que no haya distancia entre lo que se afirma y lo que se comprueba.

La capacidad total y el principio de mínima autoridad

El comando / a secas tiene nombre propio en la especificación: es la capacidad superior, la comodín, la que concede acceso a todas las demás capacidades sobre el sujeto indicado y a través de todos los espacios de nombres posibles. Es útil, y la especificación dice para qué exactamente: para enlazar agentes, es decir, para delegar todo el acceso a otro dispositivo controlado por el mismo usuario que debe comportarse como el mismo agente. Ese caso de uso es precisamente el de añadir un dispositivo, que trataremos en la última lección.

Su poder debe entenderse en concreto y no en abstracto, porque la especificación enumera lo que permite y la lista asusta lo justo: quien la recibe puede actualizar el documento mutable del sujeto —cambiar sus claves—, revocar delegaciones y usar cualquier recurso que terceros hayan delegado al sujeto. No es casi todo: es todo, incluida la capacidad de reescribir quién es el sujeto.

Frente a eso, la disciplina recomendada es la de siempre. La especificación recomienda aplicar el principio de mínima autoridad al delegar, entendido como minimizar el tiempo de validez del certificado y reducir la autoridad al mínimo imprescindible para que el delegado complete su tarea, y añade una razón que se olvida: ese delegado debería recibir la menor confianza practicable, porque puede subdelegar a terceros sin avisar a su delegador. La capacidad de revocar existe como último recurso, no como plan.

🪪

Emisor y audiencia

Remitente y destinatario del certificado; la firma debe corresponder a la clave privada del identificador que figura como emisor.

🎯

Sujeto y comando

De quién trata la cadena y qué se podrá invocar; ambos obligatorios, y los comandos cortos prueban los largos por segmentos.

📉

Atenuación

Cada eslabón reafirma o disminuye, nunca amplía; por eso la autoridad efectiva es la del eslabón más restrictivo.

⏱️

Caducidad

Campo obligatorio que puede ser nulo; acortar la ventana de validez es la defensa barata frente a la revocación, que es la cara.

La cadena no transporta permiso: transporta una demostración, y por eso puede viajar sola

Hay una manera de mirar la cadena de delegación que la vuelve obvia y que rara vez se enuncia, porque exige cambiar de disciplina. Un permiso, en el sentido clásico, es un hecho: existe una fila que dice que puedes leer ese fichero, y alguien tiene que custodiar esa fila. Un certificado encadenado no es un hecho, es una demostración: una secuencia finita de pasos donde cada uno se sigue del anterior por una regla fija, y que termina en un axioma. El axioma es la delegación raíz, ese eslabón donde emisor y sujeto coinciden y que no necesita justificación porque afirma autoridad directa. La regla de inferencia es la atenuación, que solo deja pasar a lo igual o a lo más estrecho. Y la firma criptográfica es lo que hace que cada paso sea comprobable por cualquiera y falsificable por nadie. Una vez visto así, todas las propiedades del sistema dejan de ser características independientes y se convierten en corolarios de una sola. ¿Por qué funciona sin conexión? Porque comprobar una demostración es un cómputo local: no necesitas el entorno donde se escribió, solo el texto y las reglas. ¿Por qué escala sin coordinación? Porque verificar demostraciones distintas no comparte estado, y la especificación puede prometer una autorización sin estado y por tanto altamente escalable. ¿Por qué la validación es memoizable? Porque, salvo revocación, es idempotente: una demostración correcta lo es para siempre, y marcar un identificador como válido evita recorrer la estructura entera de nuevo. ¿Por qué la atenuación tiene que ser una regla y no una convención? Porque es la única regla de inferencia del sistema, y una lógica donde puedes concluir más de lo que tienes en las premisas es una lógica inconsistente, donde todo es demostrable y por tanto nada significa nada. Y ahora la parte incómoda, que explica el nivel entero. Las demostraciones tienen una propiedad que las hace poderosas y que aquí se paga cara: son monótonas. Añadir premisas nunca invalida una conclusión ya obtenida, y eso es exactamente lo que hace que puedas verificarlas sin consultar a nadie: no hay información ausente capaz de tumbar tu resultado. Pero revocar es precisamente eso —introducir información que invalida una conclusión previamente correcta—, y por tanto la revocación es no monótona por naturaleza y no cabe dentro del sistema de demostración. No es que la especificación no la haya resuelto bien; es que no puede vivir ahí dentro. Tiene que ser un canal separado, un conjunto externo de identificadores prohibidos que crece y nunca decrece, entregado por otros medios y consultado aparte. Cuando en la última lección veas que las revocaciones se parecen a listas de bloqueo, que deben ser inmutables e irreversibles y que se recomienda tratarlas como un conjunto monótonamente creciente, reconocerás la razón profunda: es el mínimo posible para que una excepción no monótona conviva con un sistema monótono sin destruir su propiedad esencial, que es poder verificar sin preguntar.

⚔️ Encadena, atenúa y rompe
  1. Construye a mano una cadena de tres eslabones donde cada uno delega a un identificador distinto y el último invoca un comando concreto.
  2. Implementa la comprobación de atenuación por segmentos y demuestra con un caso que la comparación por prefijo textual sería explotable.
  3. Modifica el segundo eslabón para que amplíe la autoridad recibida y verifica que tu validador rechaza la cadena entera.
  4. Separa en tu código el momento de delegar del de invocar y añade un almacén de identificadores usados para impedir la repetición.
  5. Recorre los permisos de tu aplicación y anota cuáles podrías expresar como comandos jerárquicos y cuáles no encajan en esa forma.