Month: May 2014

Cross-Domain Single Sign-On

A couple of blog posts ago I’ve been detailing how regular cookie based Single Sign-On (SSO) works. I believe now it is time to have a look at this again, but make it a bit more difficult: let’s see what happens if you want to achieve SSO across different domains.

So let’s say that you have an OpenAM instance on the login.example.com domain and the goal is to achieve SSO with a site on the cool.application.com domain. As you can see right away, the domains are different, which means that regular SSO won’t be enough here (if you don’t see why, have a read of my SSO post).

Cross-Domain Single Sign-On

We already know now that in the regular SSO flow the point is that the application is in the same domain as OpenAM, hence it has access to the session cookie, which in turn allows the application to identify the user. Since in the cross-domain scenario the session cookie isn’t accessible, our goal is going to be to somehow get the session cookie from the OpenAM domain to the application’s domain, more precisely:
We need to have a mechanism that is implemented by both OpenAM and the application, which allows us to transfer the session cookie from one place to the other.
Whilst this sounds great, it would be quite cumbersome for all the different applications to implement this cookie sharing mechanism, so what else can we do?

Policy Agents to the rescue

For CDSSO OpenAM has its own proprietary implementation to transfer the session ID across domains. It’s proprietary, as it does not follow any SSO standard (for example SAML), but it does attempt to mimic them to some degree.
OpenAM has the concept of Policy Agents (PA) which are (relatively) easily installable modules for web servers (or Java EE containers) that can add extra security to your applications. It does so by ensuring that the end-users are always properly authenticated and authorized to view your protected resources.
As the Policy Agents are OpenAM components, they implement this proprietary CDSSO mechanism in order to simplify SSO integrations.

Without further ado, let’s have a look at the CDSSO sequence diagram:

cdsso-web-sequence

A bit more detail about each step:

  1. The user attempts to access a resource that is protected by the Policy Agent.
  2. The PA is unable to find the user’s session cookie, so it redirects the user to…
  3. …the cdcservlet. (The Cross-Domain Controller Servlet is the component that will eventually share the session ID value with the PA.)
  4. When the user accesses the cdcservlet, OpenAM is able to detect whether the user has an active session (the cdcservlet is on OpenAM’s domain, hence a previously created session cookie should be visible there), and…
  5. when the token is invalid…
  6. we redirect to…
  7. …the sign in page,
  8. which will be displayed to the user…
  9. …and then the user submits her credentials.
  10. If the credentials were correct, we redirect the user back to…
  11. …the cdcservlet, which will…
  12. …ensure…
  13. …that the user’s session ID is actually valid, and then…
  14. …displays a self-submitting HTML form to the user, which contains a huge Base64 encoded XML that holds the user’s session ID.
  15. The user then auto-submits the form to the PA, where…
  16. …the PA checks the validity…
  17. …of the session ID extracted from the POST payload…
  18. …and then performs the necessary authorization checks…
  19. …to ensure that the user is allowed to access the protected resource…
  20. …and then the PA creates the session cookie for the application’s domain, and the user either sees the requested content or an HTTP 403 Forbidden page. For subsequent requests the PA will see the session cookie on the application domain, hence this whole authentication / authorization process will become much simpler.

An example CDSSO LARES (Liberty Alliance RESponse) response that gets submitted to PA (like in step 15 above) looks just like this:

<lib:AuthnResponse xmlns:lib="http://projectliberty.org/schemas/core/2002/12" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ResponseID="sb976ed48177fd6c052e2241229cca5dee6b62617"  InResponseTo="s498ed3a335122c67461c145b2349b68e5e08075d" MajorVersion="1" MinorVersion="0" IssueInstant="2014-05-22T20:29:46Z"><samlp:Status>
<samlp:StatusCode Value="samlp:Success">
</samlp:StatusCode>
</samlp:Status>
<saml:Assertion  xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:lib="http://projectliberty.org/schemas/core/2002/12"  id="s7e0dad257500d7b92aca165f258a88caadcc3e9801" MajorVersion="1" MinorVersion="0" AssertionID="s7e0dad257500d7b92aca165f258a88caadcc3e9801" Issuer="https://login.example.com:443/openam/cdcservlet" IssueInstant="2014-05-22T20:29:46Z" InResponseTo="s498ed3a335122c67461c145b2349b68e5e08075d" xsi:type="lib:AssertionType">
<saml:Conditions  NotBefore="2014-05-22T20:29:46Z" NotOnOrAfter="2014-05-22T20:30:46Z" >
<saml:AudienceRestrictionCondition>
<saml:Audience>https://cool.application.com:443/?Realm=%2F</saml:Audience>
</saml:AudienceRestrictionCondition>
</saml:Conditions>
<saml:AuthenticationStatement  AuthenticationMethod="vir" AuthenticationInstant="2014-05-22T20:29:46Z" ReauthenticateOnOrAfter="2014-05-22T20:30:46Z" xsi:type="lib:AuthenticationStatementType"><saml:Subject   xsi:type="lib:SubjectType"><saml:NameIdentifier NameQualifier="https://login.example.com:443/openam/cdcservlet">AQIC5wM2LY4SfcxADqjyMPRB8ohce%2B6kH4VGD408SnVyfUI%3D%40AAJTSQACMDEAAlNLABQtMzk5MDEwMTM3MzUzOTY5MTcyOQ%3D%3D%23</saml:NameIdentifier>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
<lib:IDPProvidedNameIdentifier  NameQualifier="https://login.example.com:443/openam/cdcservlet" >AQIC5wM2LY4SfcxADqjyMPRB8ohce%2B6kH4VGD408SnVyfUI%3D%40AAJTSQACMDEAAlNLABQtMzk5MDEwMTM3MzUzOTY5MTcyOQ%3D%3D%23</lib:IDPProvidedNameIdentifier>
</saml:Subject><saml:SubjectLocality  IPAddress="127.0.0.1" DNSAddress="localhost" /><lib:AuthnContext><lib:AuthnContextClassRef>http://www.projectliberty.org/schemas/authctx/classes/Password</lib:AuthnContextClassRef><lib:AuthnContextStatementRef>http://www.projectliberty.org/schemas/authctx/classes/Password</lib:AuthnContextStatementRef></lib:AuthnContext></saml:AuthenticationStatement></saml:Assertion>
<lib:ProviderID>https://login.example.com:443/openam/cdcservlet</lib:ProviderID></lib:AuthnResponse>

If you watch carefully you can see the important bit right in the middle:

<saml:NameIdentifier NameQualifier="https://login.example.com:443/openam/cdcservlet">AQIC5wM2LY4SfcxADqjyMPRB8ohce%2B6kH4VGD408SnVyfUI%3D%40AAJTSQACMDEAAlNLABQtMzk5MDEwMTM3MzUzOTY5MTcyOQ%3D%3D%23</saml:NameIdentifier>

As you can see the value of the NameIdentifier element is the user’s session ID. Once the PA creates the session cookie on the application’s domain you’ve pretty much achieved single sign-on across domains, well done! πŸ˜‰

P.S.: If you are looking for a guide on how to set up Policy Agents for CDSSO, check out the documentation.

Certificate authentication over REST

A little bit of background

Amongst the various different authentication mechanisms that OpenAM supports, there is one particular module that always proves to be difficult to get correctly working: Client certificate authentication, or Certificate authentication module as defined in OpenAM. The setup is mainly complex due to the technology (SSL/TLS) itself, and quite frankly in most of the cases the plain concept of SSL is just simply not well understood by users.

Disclaimer: I have to admit I’m certainly not an expert on SSL, so I’m not going to deep dive into the details of how client certificate authentication itself works, instead, I’m just going to try to highlight the important bits that everyone should know who wants to set up a simple certificate based authentication.

The main thing to understand is that client cert authentication happens as part of the SSL handshake. That is it… It will NOT work if you access your site over HTTP. The authentication MUST happen at the network component that provides HTTPS for the end users.
Again, due to SSL’s complexity there are several possibilities: it could be that SSL is provided by the web container itself, but it is also possible that there is a network component (like a load balancer or a reverse proxy) where SSL is terminated. In the latter case it is quite a common thing for these components to embed the authenticated certificate in a request header for the underlying application (remember: the client cert authentication is part of the SSL handshake, so by the time OpenAM is hit, authentication WAS already performed by the container).

Now this is all nice, but how do you actually authenticate using your client certificate over REST?

Setting it all up

Now some of this stuff may look a bit familiar to you, but for the sake of simplicity let me repeat the exact steps of setting this up:

  • Go to Access Control – realm – Authentication page and Add a new Module Instance called cert with type Certificate
  • Open the Certificate module configuration, and make sure the LDAP Server Authentication User/Password settings are correct.
  • Generate a new self signed certificate by following this guide, but make sure that in the CSR you set the CN to “demo”. The resulting certificate and private key for me was client.crt and client.key respectively.
  • Create PKCS#12 keystore for the freshly generated private key and certificate:
    openssl pkcs12 -export -inkey client.key.org -in client.crt -out client.p12
  • Install the PKCS#12 keystore in your browser (Guide for Firefox)
  • Enable Client Authentication on the container. For example on Tomcat 7.0.53 you would have to edit conf/server.xml and set up the SSL connector like this:
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
     maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
     keystoreFile="/Users/aldaris/server.jks" keystorePass="changeit"
     truststoreFile="/Users/aldaris/trust.jks" truststorePass="changeit"
     clientAuth="true" sslProtocol="TLS" />
    
  • Add your user’s public certificate into the truststore (since the container performs the authentication, it needs to trust the client):
    keytool -import -keystore /Users/aldaris/trust.jks -file client.crt -alias demo

    In cases when the truststoreFile attribute is missing from the Connector settings, Tomcat by default falls back to the JVM’s truststore, so make sure you update the correct truststore.

  • Restart the container

Now if you did everything correctly, you should be able to go to /openam/UI/Login?module=cert and the browser should ask for the client certificate. After selecting the correct certificate you should get access to OpenAM right away.

It’s time to test it via REST

For my tests I’m going to use curl, I must say though that curl on OSX Mavericks (7.30) isn’t really capable to do client authentication, hence I would suggest installing curl from MacPorts (7.36) instead.
To perform the login via REST one would:

$ curl -X POST -v -k --cert-type pem --cert client.crt --key client.key.org "https://openam.example.com:8443/openam/json/authenticate?authIndexType=module&authIndexValue=cert"

And if you want to authenticate into a subrealm:

$ curl -X POST -v -k --cert-type pem --cert client.crt --key client.key.org "https://openam.example.com:8443/openam/json/subrealm/authenticate?authIndexType=module&authIndexValue=cert"

Simple as that. πŸ˜‰