logo_kerberos.gif

Projects/SAMLInKerberos

From K5Wiki
Jump to: navigation, search
This is an early stage project for MIT Kerberos. It is being fleshed out by its proponents. Feel free to help flesh out the details of this project. After the project is ready, it will be presented for review and approval.



Background

Extend Kerberos to permit the inclusion of a SAML assertion in KDC-issued authorization data.

Architecture

KRB5_AUTHDATA_SAML

A new authorisation data type, KRB5_AUTHDATA_SAML, is defined. This carries a SAML assertion. When issued or vouched for by the KDC, the assertion is bound to a ticket by signing with the TGT session key and adding the Kerberos HoK subject confirmation. This behaviour is similar to AD-KDCIssued, except that XML signatures are used.

Additionally, for assertions issued by the KDC:

  • an AuthnStatement is present containing the authtime as AuthnInstant and the Kerberos AuthnContextClassRef
  • the Subject contains the client principal name
  • the Issuer contains the TGS name

The assertion may end up in the ticket in four ways:

  • issued by the KDC, in the case the KDC is an IdP (or colocated with one)
  • submitted as enc-authorization-data by the IdP in a S4U request
  • submitted as enc-authorization-data by the client in a normal TGS request
  • submitted as enc-authorization-data by the service in a S4U request

KDC as IdP

A new KDC-side authorisation data plugin assembles a SAML assertion from a user's attributes in the directory and signs it. Presently, all attributes not used by the Kerberos LDAP backend itself are propagated into the attribute statement: whilst the use of directory server-side ACLs affords some flexibility, this will be most useful when attributes can be mapped and filtered on a per-deployment and per-service basis.

Note: this will go away in a future iteration of this project, in favour of trust relationships with third-party IdPs.

SAML GSS Naming Extensions

A new GSS naming extensions plugin verifies the above authorisation data and parses it. The attribute statement is then surfaced through GSS naming extensions.

Transitive trust

It would be useful for services to validate assertions that are not issued by the KDC. I propose two mechanisms:

  • the GSS naming extensions plugin supports the verification of public key signatures and some out-of-band mechanism for binding principal names
  • the KDC can vouch for assertions issued by a third-party IdP

The assertion may be submitted in the TGS-REQ as enc-authorization-data. The KDC will copy the assertion into the resulting ticket, adding its own signature if it can vouch for it. To the application, this has identical semantics to the first approach: the issue is whether the PKI needs to be deployed to each Kerberos service or not. (See below for another approach that avoids deploying PKI to the KDC. Alternatively, a KDC that supports PKINIT could also sign assertions with its private key, but for the purpose of this discussion I'll consider PKI-less SAML as a desirable.)

There are some interesting things that can come out of this. For example, consider the following:

  • client acquires TGT as normal (or using IAKERB to IdP)
  • client authenticates to IdP with Negotiate
  • IdP issues assertion for client
  • client does TGS-REQ for a new TGT, submitting authorisation data

Now, let's first take the case where the KDC doesn't know anything about SAML. It will blindly copy the assertion into the returned TGT. If the service is SAML aware, it can verify the assertion with the IdP's public key and then interrogate the claims with naming extensions. So that gets you an authorisation service decoupled from Kerberos, with similar properties to the PAC.

The disadvantage is that PKI needs to be deployed to each service. If the KDC is SAML-aware, then after validating the ticket-assertion binding it can vouch for the assertion in future service tickets, signing them with the ticket service key. (Indeed, it might use AMA to validate the client authenticated to the IdP with Kerberos.) Regardless, the actual application doesn't need to be aware how the assertion was validated: it simply receives a set of claims it can interrogate with GSS naming extensions.

Of course, there's no reason one needs to use a TGT: the same approach would work for individual service tickets (which offers richer authorisation than treating the realm as a single SP, which negates the possibility to do per-service claims filtering unless the KDC does it itself). But I chose this example for its similarity to DCE and Windows.

To further reduce the dependence on PKI, the IdP could sign assertions with a shared secret known to it and the KDC. In this case the KDC does not need to be configured for PKI to vouch for the assertion, it simply needs to know the SAML authority service principal is trusted. The service must then use S4U2Self to request the KDC verify the assertion (because it does not know the IdP's secret key). An alternative might be for the IdP to perform S4U2Self (or S4U2Proxy) on behalf of the client and return an AP-REQ (with a ticket containing the real assertion) as the "assertion". This provides privacy protection of the assertion and preserves the property of the service not needing to contact a third-party to verify the assertion, at the expense of distorting the Kerberos authentication model somewhat.

SAML-based S4U

A variant of S4U2Self that supports identifying users with SAML assertions is proposed. One way would be to leverage the existing S4U2Self protocol exchange, redefining the semantics such that:

  • a well known principal name is used as the S4U2Self client principal
  • the assertion is submitted as enc-authorization-data in the KDC-REQ-BODY

(This is an abuse of the authorisation data field: a better approach would be to use FAST but I may use the above for prototyping to avoid extensive changes to the KDC and client library whilst prototyping. Unfortunately some architectural changes will be required to support flexible subject mapping.)

In this case, the KDC attempts to bind the assertion in the KDC-REQ authorisation data to the S4U client, rather than the service making the request.

If an assertion is submitted with a S4U2Proxy request, then it should be bound to the evidence ticket client, rather than the service making the request.

Note: in the case of S4U2Proxy, we rely on KRB5SignedData to protect the evidence ticket from server forgeries. We could add a TGS signature to the assertion, instead.

Further note: [MS-SFU] uses a AS referrals to locate the canonical realm of a user or certificate before performing a S4U2Self request. We will require the client to use SAML name mapping for the case that the subject is not a Kerberos principal name, if they want a real Kerberos principal in the returned ticket.

Name encoding

When using a SAML assertion to identify a user where the requestor does not know of the Kerberos principal name mapping, it should use the following names. This would be the case during S4U2Self or possibly PKU2U.

TBD: can we canonicalise the client principal in a non-S4U TGS-REQ?

Principal names

From draft-zhu-pku2u we import the NULL principal name.

  • The type is KRB_NT_WELLKNOWN
  • The name-string field consists of "WELLKNOWN", "NULL"

Realm names

From draft-ietf-krb-wg-naming we import the wellknown realm type.

  • The realm name is WELLKNOWN:SAML

Implementation

Preliminary code is in the users/lhoward/saml branch, which itself is a branch of the constrained delegation (users/lhoward/s4u2proxy) branch. Most of the code is to be found in src/plugins/authdata/saml_{client,server}.

OpenSAML is assumed to be installed in /usr/local.

KDC authorisation data handling

Pseudo-code for KDC logic (this is still heavily experimental):

if (kdc_req.type != tgs_req || service.attributes & no_auth_data_reqd)
  return

fromTGT ::= false
assertion ::= tgs_req.authdata.saml
if (assertion == nil)
{
    assertion ::= tgt.authdata.saml
    fromTGT ::= true
}

authenticated ::= false
if (assertion != nil)
{
    // note verify_assertion will return OK if there is no signature but
    // authenticated will be false. However, if there is a signature and it
    // fails to verify, it will raise an error.
    if (verify_assertion(assertion, fromTGT, authenticated, ...) == FAIL)
        return FAIL
}

// Can generate assertion from KDB here, if we choose to keep that functionality
// In that case authenticated == true because we issued it

if (assertion != nil)
{
    if (authenticated == true)
    {
        if (isWellKnownSamlPrincipal(principal) == false &&
             principal.realm == local_realm)
            confirm_subject(assertion.subject, principal)
        sign
    }
    encode
}

Pseudo-code for verify_assertion():

verify_assertion([in] assertion, [in] fromTGT, [out] authenticated, [out] mappedPrincipal)
{
    authenticated ::= false

    if (assertion.signature == nil)
        return OK // but do not set authenticated ::= true

    if (subject.binding.verify() == false)
        return FAIL

    // XXX can we do this just in S4U2Self or in any case?
    if (isWellKnownSamlPrincipal(principal))
        mappedPrincipal ::= attemptToMapPrincipal(principal)

    if (fromTGT == false)
    {
        if (assertion.signature.verify() == false)
           return FAIL
    }
    if (constraints.verify() == false
        return FAIL
    authenticated ::= true
    return OK
}

Key derivation

Assertions vouched for by the KDC are signed with SHA-512 using the following output of the RFC 3961 PRF with the constant "saml" appended by, for signing assertions with the TGT session key, four zero octets. More generally:

import pseudo-random from RFC 3961

object-type ::= { assertion ::= 0 }
usage ::= { tgt-session-key ::= 0, service-key ::= 1 }

saml_krb_derive_key(protocol-key, object-type, usage)
{
   char constant[8];

   constant[0] = 's'
   constant[1] = 'a'
   constant[2] = 'm'
   constant[3] = 'l'
   constant[4] = object-type
   if (usage == service-key)
       constant[5] = 0xFF
   else if (usage == tgt-session-key)
       constant[5] = 0x00
   constant[6] = 0
   constant[7] = 0

   return pseudo-random (protocol-key, constant)
}

Open issues

  • From Henry Hotz: I'm also concerned about interoperability issues. E.g. if there is also a PAC then shouldn't we guarantee a 1-to-1 correspondence between SAML assertions and group memberships? This concern extends to any other authorization data types as well. [LH: RFC 4120 specifies AD-IF-RELEVANT to address this. The auth data is honoured if it is relevant.]

Status

Current status is partial prototype only.

Examples

Testing

The following test shows a service principal acquiring a ticket for bjensen@MIT.DE.PADL.COM and displaying the returned attribute statement via naming extensions:

Protocol transition tests follow
-----------------------------------

Reading symbols for shared libraries . done
Reading symbols for shared libraries ..... done
Target name:	host/somehost.mit.de.padl.com@MIT.DE.PADL.COM
Target mech:	{ 1 2 840 113554 1 2 2 }
Source name:	bjensen@MIT.DE.PADL.COM
Source mech:	{ 1 2 840 113554 1 2 2 }
Attribute: urn:oid:0.9.2342.19200300.100.1.3 Authenticated Complete
Value: bjensen@padl.com

Attribute: urn:oid:2.5.4.4 Authenticated Complete
Value: Jensen

Attribute: urn:oid:2.5.4.42 Authenticated Complete
Value: Babs

Attribute: urn:oid:2.5.4.3 Authenticated Complete
Value: Babs Jensen

Attribute: urn:oid:2.5.4.20 Authenticated Complete
Value: +1 212 555 1234

Attribute: urn:oid:2.5.4.43 Authenticated Complete
Value: BJ

Data

An example SAML assertion follows:

<saml:Assertion
  xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'
  xmlns:xs='http://www.w3.org/2001/XMLSchema'
  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
  xmlns:x500='urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500'
  xmlns:ds='http://www.w3.org/2000/09/xmldsig#'
  ID='_3e53282d753b22b3b1273a0895dfd37c'
  Version='2.0'
  IssueInstant='2009-10-26T22:23:27Z'
>
  <saml:Issuer
    Format='urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos'
  >krbtgt/MIT.DE.PADL.COM@MIT.DE.PADL.COM</saml:Issuer>
  <saml:Subject>
    <saml:NameID
      Format='urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos'
    >bjensen@MIT.DE.PADL.COM</saml:NameID>
  </saml:Subject>
  <saml:Conditions
    NotBefore='2009-10-26T22:23:27Z'
    NotOnOrAfter='2009-10-27T22:23:27Z'
  ></saml:Conditions>
  <saml:AuthnStatement
    AuthnInstant='2009-10-26T22:23:27Z'
  >
    <saml:AuthnContext>
      <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos</saml:AuthnContextClassRef>
    </saml:AuthnContext>
  </saml:AuthnStatement>
  <saml:AttributeStatement>
    <saml:Attribute
      NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
      FriendlyName='mail'
      Name='urn:oid:0.9.2342.19200300.100.1.3'
      x500:Encoding='LDAP'
    >
      <saml:AttributeValue>bjensen@padl.com</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute
      NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
      FriendlyName='sn'
      Name='urn:oid:2.5.4.4'
      x500:Encoding='LDAP'
    >
      <saml:AttributeValue>Jensen</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute
      NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
      FriendlyName='givenName'
      Name='urn:oid:2.5.4.42'
      x500:Encoding='LDAP'
    >
      <saml:AttributeValue>Babs</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute
      NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
      FriendlyName='cn'
      Name='urn:oid:2.5.4.3'
      x500:Encoding='LDAP'
    >
      <saml:AttributeValue>Babs Jensen</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute
      NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
      FriendlyName='telephoneNumber'
      Name='urn:oid:2.5.4.20'
      x500:Encoding='LDAP'
    >
      <saml:AttributeValue>+1 212 555 1234</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute
      NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
      FriendlyName='initials'
      Name='urn:oid:2.5.4.43'
      x500:Encoding='LDAP'
    >
      <saml:AttributeValue>BJ</saml:AttributeValue>
    </saml:Attribute>
  </saml:AttributeStatement>
</saml:Assertion>