Entender SAML

SAML en detalle

Los mensajes, firmas y comprobaciones que debe realizar una aplicación, con lo que hace ID-Kit en cada paso.

1. Roles y vocabulario

SAML 2.0 (2005) es un estándar de OASIS para la federación de identidades. El proveedor de identidad (IdP) autentica al usuario y emite aserciones; el proveedor de servicio (SP) las consume en la dirección llamada ACS (Assertion Consumer Service). Cada parte tiene un identificador único, el Entity ID, a menudo una URL que no necesita ser accesible.

2. Metadatos

Un documento XML describe a cada parte: su Entity ID, sus endpoints, sus certificados. Importarlos evita la mayoría de los errores de copia.

<md:EntityDescriptor entityID="https://idp.example/idp">
  <md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <md:KeyDescriptor use="signing">...<ds:X509Certificate>MIIC...</ds:X509Certificate>...</md:KeyDescriptor>
    <md:SingleSignOnService Binding="...HTTP-Redirect" Location="https://idp.example/sso"/>
    <md:SingleSignOnService Binding="...HTTP-POST" Location="https://idp.example/sso"/>
  </md:IDPSSODescriptor>
</md:EntityDescriptor>
  • KeyDescriptor use="signing": el certificado cuya clave pública se usa para verificar las firmas. use="encryption" es para el cifrado.
  • SingleSignOnService (IdP): adónde enviar una solicitud, con el binding aceptado.
  • AssertionConsumerService (SP): dónde recibir la respuesta. WantAssertionsSigned indica que el SP exige una aserción firmada.
  • Renovación del certificado: publique ambos certificados (antiguo y nuevo) durante la transición. Un certificado caducado sin sustituto publicado bloquea todos los inicios de sesión a la vez.

3. Los dos escenarios de inicio de sesión

Iniciado por la aplicación (SP-initiated), el más habitual

El usuario abre la aplicación, que envía un AuthnRequest. La respuesta cita el ID de esa solicitud en InResponseTo: la aplicación puede comprobar que realmente pidió esta respuesta. El parámetro RelayState permite al usuario volver a la página que pidió originalmente.

Iniciado por el proveedor de identidad (IdP-initiated)

El usuario hace clic en un icono del portal de su organización. No hubo solicitud previa: la respuesta llega «no solicitada» y no tiene InResponseTo. Es cómodo pero menos seguro (la aplicación no puede vincular la respuesta con una solicitud) y algunas aplicaciones lo rechazan.

4. Bindings: cómo viajan los mensajes

El navegador lleva los mensajes. Un binding describe cómo:

BindingPrincipeUsage
HTTP-RedirectEl XML se comprime (deflate), se codifica en base64 y se coloca en la URL (?SAMLRequest=…). Si la solicitud está firmada, la firma queda fuera del XML, en los parámetros SigAlg y Signature, y cubre la cadena SAMLRequest=…&RelayState=…&SigAlg=… tal como se envió.Solicitudes (tamaño limitado por la URL)
HTTP-POSTEl XML se codifica en base64 en un formulario HTML que el navegador envía automáticamente. La firma está en el XML.Respuestas (casi siempre) y solicitudes
HTTP-ArtifactSolo se envía una referencia; el contenido se obtiene directamente de servidor a servidor.Rare

5. La solicitud (AuthnRequest)

<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    ID="_a1b2c3"                                   <!-- identifiant unique, repris dans la réponse -->
    IssueInstant="2026-11-01T10:00:00Z"
    Destination="https://idp.example/sso"          <!-- à qui la demande est adressée -->
    AssertionConsumerServiceURL="https://app.example/saml/acs"   <!-- où renvoyer la réponse -->
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    Version="2.0">
  <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://app.example/metadata</saml:Issuer>  <!-- l'Entity ID de l'application -->
  <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
</samlp:AuthnRequest>

Opciones útiles: ForceAuthn (reautenticar aunque exista sesión), IsPassive (no mostrar nada, fallar si el usuario no ha iniciado sesión ya), NameIDPolicy (formato de identificador deseado), RequestedAuthnContext (nivel de autenticación exigido, por ejemplo dos factores).

6. La respuesta y la aserción

<samlp:Response Destination="https://app.example/saml/acs" InResponseTo="_a1b2c3" ...>
  <saml:Issuer>https://idp.example/idp</saml:Issuer>
  <samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
  <saml:Assertion ID="_z9y8" IssueInstant="..." Version="2.0">
    <saml:Issuer>https://idp.example/idp</saml:Issuer>
    <ds:Signature>...</ds:Signature>                        <!-- le sceau -->
    <saml:Subject>
      <saml:NameID Format="...emailAddress">jane@example.com</saml:NameID>
      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:SubjectConfirmationData InResponseTo="_a1b2c3" Recipient="https://app.example/saml/acs" NotOnOrAfter="..."/>
      </saml:SubjectConfirmation>
    </saml:Subject>
    <saml:Conditions NotBefore="..." NotOnOrAfter="...">
      <saml:AudienceRestriction><saml:Audience>https://app.example/metadata</saml:Audience></saml:AudienceRestriction>
    </saml:Conditions>
    <saml:AuthnStatement AuthnInstant="..." SessionIndex="_z9y8">...</saml:AuthnStatement>
    <saml:AttributeStatement>
      <saml:Attribute Name="groups"><saml:AttributeValue>admins</saml:AttributeValue></saml:Attribute>
    </saml:AttributeStatement>
  </saml:Assertion>
</samlp:Response>

Dos niveles: la Response (el sobre: estado, destino) y la Assertion (el contenido que importa). El Status dice si la identificación ha tenido éxito; si falla, no hay aserción.

7. La firma XML

  • Qué firmar: la Response, la Assertion o ambas. Firmar la aserción es el mínimo razonable: es lo que lleva la identidad.
  • «Enveloped»: la firma se coloca dentro del elemento que firma (justo después del Issuer) y se excluye del cálculo.
  • Canonicalización (exc-c14n): antes de calcular el resumen, el XML se normaliza (orden de atributos, espacios de nombres, escapes) para que diferencias inocuas, como un salto de línea o un prefijo distinto, no invaliden la firma.
  • Dos cálculos: un resumen del elemento y, después, la firma RSA del bloque SignedInfo que contiene ese resumen.
  • Algoritmos: RSA-SHA256 es el estándar actual. SHA-1 está obsoleto: consérvelo solo para sistemas heredados.
  • El certificado incluido en la firma no prueba nada: un atacante puede poner el suyo. La aplicación debe compararlo con el certificado de confianza obtenido de los metadatos del IdP.

ID-Kit puede firmar la aserción, la respuesta o ambas, con SHA-1/256/384/512, y simular casos defectuosos para comprobar que su aplicación los rechaza.

8. Comprobaciones en el lado de la aplicación

Por orden, una aplicación seria comprueba:

  1. El estado es Success.
  2. La firma es válida, hecha con un certificado de confianza, y cubre realmente el elemento que se leerá (protección contra el «signature wrapping»).
  3. Destination : la ACS URL de la aplicación.
  4. InResponseTo: corresponde a una solicitud enviada y aún no consumida (salvo que se permita el inicio iniciado por el IdP).
  5. Issuer : el IdP esperado.
  6. Audiencia : el Entity ID de la aplicación.
  7. NotBefore / NotOnOrAfter: válido ahora, con una pequeña tolerancia de reloj (unos minutos).
  8. SubjectConfirmation: método bearer, Recipient = URL del ACS, no caducado.
  9. Antirreproducción: el ID de la aserción no se ha usado ya.
  10. Solo entonces: leer el NameID y los atributos.

El SP de pruebas de ID-Kit realiza todas estas comprobaciones y las muestra una a una, con la corrección que hay que aplicar.

9. NameID y atributos

  • Formatos de NameID: emailAddress (legible, pero la dirección puede cambiar), persistent (identificador estable propio del par IdP/SP, preferible como clave de cuenta), transient (cambia en cada inicio de sesión), unspecified.
  • Atributos: pares nombre/valor, a veces multivalor (grupos). El nombre exacto importa (email, groups o un nombre largo estilo URI en Microsoft). El IdP decide qué atributos se envían y la aplicación debe mapearlos.

10. Cifrado y cierre de sesión

  • EncryptedAssertion: el IdP puede cifrar la aserción con el certificado de cifrado del SP. Útil cuando la respuesta pasa por un intermediario que usted no controla. Dentro de una conexión HTTPS de extremo a extremo, la firma suele bastar.
  • Single Logout (SLO): LogoutRequest/LogoutResponse entre SP e IdP, emparejados por el SessionIndex. En la práctica, es la parte menos y peor implementada del estándar.

11. Seguridad: las trampas clásicas

  • XML Signature Wrapping: el atacante cuela una aserción falsa junto a la auténtica firmada. Defensa: comprobar que la firma referencia exactamente el elemento que se lee y que los ID son únicos.
  • XXE: un analizador XML que sigue entidades externas puede leer archivos. Defensa: prohibir DOCTYPE y ENTITY (ID-Kit lo hace).
  • Replay: se reutiliza una aserción robada. Defensa: vida corta, InResponseTo, memoria de los ID consumidos.
  • Firma ausente aceptada: una aplicación que «verifica la firma si existe» pero no exige que esté presente puede ser eludida.
  • Audience y Recipient ignorados: se aceptaría una aserción emitida para otra aplicación.
  • RelayState sin comprobar: redirección abierta. Defensa: aceptar solo direcciones internas.

Para probar estos comportamientos en la práctica, use las simulaciones de error del IdP de ID-Kit: su aplicación debe rechazar cada una.

¿Quiere verlo en directo? ID-Kit hace el papel que le falta y le muestra cada paso.