How to set up an OAuth2 provider with ssoadm

The ssoadm command line tool is a quite powerful ally if you want to set up your OpenAM environment without performing any operation through the user interface, i.e. when you just want to script everything.

Whilst the tool itself allows you to do almost anything with the OpenAM configuration, finding the right set of commands for performing a certain task, is not always that straightforward… In today’s example we will try to figure out which commands to use to set up an OAuth2 provider.

When using the Common Tasks wizard to set up an OAuth2 Provider we can see that there are essentially two things that the wizard does for us:

  • Configure a realm level service for the OAuth2 Provider
  • Set up a policy to control access to the /oauth2/authorize endpoint

Setting up a realm level service

Well, we know that we are looking for something that sets up a service, so let’s see what command could help us:

$ openam/bin/ssoadm | grep -i service -B 1
...
--
    add-svc-realm
        Add service to a realm. 
--
...

Well, we want to add a service to a realm, so add-svc-realm sounds like a good fit. Let’s see what parameters it has:

$ openam/bin/ssoadm add-svc-realm
...
Options:
    --realm, -e
        Name of realm.

    --servicename, -s
        Service Name.

    --attributevalues, -a
        Attribute values e.g. homeaddress=here.

    --datafile, -D
        Name of file that contains attribute values data.

Alright, so the realm is straightforward, but what should we use for servicename and datafile?

Each service in OpenAM has a service schema that describes what kind of attributes can that service contain and with what syntaxes/formats/etc. Since all the default service definitions can be found in ~/openam/config/xml directory, let’s have a look around and see if there is anything OAuth2 related:

$ ls ~/openam/config/xml
... OAuth2Provider.xml ...

After opening up OAuth2Provider.xml we can find the service name under the name attribute of the Service element (happens to be OAuth2Provider).

So the next question is what attributes should you use to populate the service? All the attributes are defined in the very same service definition XML file, so it’s not too difficult to figure out what to do now:

$ echo "forgerock-oauth2-provider-authorization-code-lifetime=60
forgerock-oauth2-provider-refresh-token-lifetime=600
forgerock-oauth2-provider-access-token-lifetime=600" > attrs.txt
$ openam/bin/ssoadm add-svc-realm -e / -s OAuth2Provider -u amadmin -f .pass -D attrs.txt

Creating a policy

Creating a policy is a bit more complex since 12.0.0 and the introduction of XACML policies, but let’s see what we can do about that.

Using ssoadm create-xacml

The XACML XML format is not really pleasant for the eyes, so I would say that you better create that policy using the policy editor first, and then export that policy in XACML format, so that you can automate this flow.

Once you have the policy in XACML format, the ssoadm command itself would look something like this:

$ openam/bin/ssoadm create-xacml -e / -X oauth2-policy.xml -u amadmin -f .pass

Using the REST API

The policy REST endpoints introduced with 12.0.0 are probably a lot more friendly for creating policies, so let’s see how to do that:

$ echo '{
   "resources" : [
      "http://openam.example.com:8080/openam/oauth2/authorize?*"
   ],
   "subject" : {
      "type" : "AuthenticatedUsers"
   },
   "active" : true, 
   "name" : "OAuth2ProviderPolicy",
   "description" : "", 
   "applicationName" : "iPlanetAMWebAgentService",
   "actionValues" : {
      "POST" : true, 
      "GET" : true
   }
}' > policy.json
$ curl -v -H "iplanetdirectorypro: AQIC5wM...*" -H "Content-Type: application/json" -d @policy.json http://openam.example.com:8080/openam/json/policies?_action=create

Hope you found this useful.

OpenAM Security Advisory #201506

Security vulnerabilities have been discovered in OpenAM components. These issues are present in versions of OpenAM including 12.0.x and 11.0.x.

This advisory provides guidance on how to ensure your deployments can be secured. Workarounds or patches are available for all of the issues, which are also included in the 12.0.2 maintenance release.

The maximum severity of issues in this advisory is Critical. Deployers should take immediate steps as outlined in this advisory and apply the relevant update(s) at the earliest opportunity.

The recommendation is to upgrade to OpenAM 12.0.2 or deploy the relevant patches. Patch bundles are available for the following versions:

  • 11.0.3
  • 12.0.0
  • 12.0.1

Customers can obtain these patch bundles from BackStage.

Issue #201506-01: Thread-safety issues with CTS when encryption is enabled

Product: OpenAM
Affected versions: 11.0.0-11.0.3 and 12.0.0-12.0.1
Fixed versions: 12.0.2
Component: Core Server, Server Only
Severity: Critical

When the Core Token Service token encryption is enabled and the system is under a heavy load, it is possible that incorrect session/SAML/OAuth2 tokens are returned by the CTS.

Workaround:

Disable token encryption by setting the following property to false:

com.sun.identity.session.repository.enableEncryption

in the OpenAM console via Configuration -> Servers and Sites -> Default Server Settings -> Advanced or via ssoadm:

ssoadm update-server-cfg --servername default --adminid amadmin --password-file /tmp/pwd.txt --attributevalues com.sun.identity.session.repository.enableEncryption=false

This setting is false by default.

Note:

By changing this setting, any existing encrypted tokens stored in CTS will become unreadable by OpenAM.

Resolution:
Use the workaround or update/upgrade to a fixed version or deploy the relevant patch bundle.

Issue #201506-02: Possible user impersonation when using OpenAM as an OAuth2/OIDC Provider

Product: OpenAM
Affected versions: 10.1.0-Xpress, 11.0.0-11.0.3, 12.0.0-12.0.1
Fixed versions: 12.0.2
Component: Core Server, Server Only
Severity: High

When using multiple realms, it is possible for an authenticated user in realmA to acquire OAuth2 and OpenID Connect tokens that correspond to realmB.

Workaround:

None.

Resolution:
Update/upgrade to a fixed version or deploy the relevant patch bundle.

LDAPS or StartTLS? That is the question…

Due to the various security issues around the different SSL implementations, I’ve seen an increasing demand for OpenAM’s StartTLS support even though OpenAM perfectly supported LDAPS. In this post I’m going to show you how to set up both StartTLS and LDAPS in a dummy OpenDJ 2.6.2 environment, and then I’ll attempt to compare them from a security point of view.

NOTE: the instructions provided here for setting up secure connections are by no means the most accurate ones, as I only provide them for demonstration purposes. You should always consult the product documentation for much more detailed, and precise information.

Common Steps

Both LDAPS and StartTLS requires a private key, I’m just going to assume you know how to generate/convert (or obtain from a trusted CA) it.
Once you have your JKS file ready first make sure that it contains a PrivateKeyEntry:

$ keytool -list  -keystore server.jks 
Enter keystore password:  

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

1, 2015.04.22., PrivateKeyEntry, 
Certificate fingerprint (SHA1): AA:30:0D:8E:DE:4C:F9:AB:AC:FA:61:E7:B4:F5:56:EF:3E:F4:F6:4A

After verifying the keystore, copy it into the config folder with the name “keystore” and also create a keystore.pin file that contains the keystore’s password.

In order to make this JKS available for OpenDJ you’ll need to run the following dsconfig command (for this demo’s purpose we are going to reuse the existing Key/Trust Manager Providers):

dsconfig set-key-manager-provider-prop 
          --provider-name JKS 
          --set enabled:true 
          --hostname localhost 
          --port 4444 
          --trustStorePath /path/to/opendj/config/admin-truststore 
          --bindDN cn=Directory Manager 
          --bindPassword ****** 
          --no-prompt

Since key management always goes hand-in-hand with trust management, we need to set up the Trust Management Provider as well. For this, you’ll need to create a JKS keystore which only contains the public certificate and place it into the config folder with the name “truststore“, and again, you’ll need to create a truststore.pin file containing the truststore’s password.

dsconfig set-trust-manager-provider-prop 
          --provider-name JKS 
          --set enabled:true 
          --set trust-store-pin-file:config/truststore.pin 
          --hostname localhost 
          --port 4444 
          --trustStorePath /path/to/opendj/config/admin-truststore 
          --bindDN cn=Directory Manager 
          --bindPassword ****** 
          --no-prompt

LDAPS

From protocol point of view LDAPS is not too different from HTTPS actually: in order to establish a connection to the directory, the client MUST perform an SSL/TLS Handshake with the server first, hence all the LDAP protocol messages are transported on a secure, encrypted channel.
Configuring and enabling the LDAPS Connection Handler in OpenDJ doesn’t really take too much effort:

dsconfig set-connection-handler-prop 
          --handler-name LDAPS Connection Handler 
          --set enabled:true 
          --set listen-port:1636 
          --hostname localhost 
          --port 4444 
          --trustStorePath /path/to/opendj/config/admin-truststore 
          --bindDN cn=Directory Manager 
          --bindPassword ****** 
          --no-prompt

After this you will need to restart the directory server, during the startup you should see the following message:

[22/04/2015:20:47:26 +0100] category=PROTOCOL severity=NOTICE msgID=2556180 msg=Started listening for new connections on LDAPS Connection Handler 0.0.0.0 port 1636

To test the connection, you can just run a simple ldapsearch command:

$ bin/ldapsearch -Z -h localhost -p 1636 -D "cn=Directory Manager" -w ****** -b dc=example,dc=com "uid=user.0" "*"
The server is using the following certificate: 
    Subject DN:  EMAILADDRESS=peter.major@forgerock.com, CN=aldaris.sch.bme.hu, OU=Sustaining, O=ForgeRock Ltd, L=Bristol, C=GB
    Issuer DN:  EMAILADDRESS=peter.major@forgerock.com, CN=aldaris.sch.bme.hu, OU=Sustaining, O=ForgeRock Ltd, L=Bristol, C=GB
    Validity:  Wed Apr 22 19:43:22 BST 2015 through Thu Apr 21 19:43:22 BST 2016
Do you wish to trust this certificate and continue connecting to the server?
Please enter "yes" or "no":

As you can see I’ve been prompted to accept my self-signed certificate, and after entering “yes”, I could see the entry I’ve been looking for.

StartTLS

StarTLS for LDAP is slightly different from LDAPS, the main difference being, that first the client needs to establish an unencrypted connection with the directory server. At any point in time after establishing the connection (as long as there are no outstanding LDAP operations on the connection), the StartTLS extended operation shall be sent across to the server. Once a successful extended operation response has been received, the client can initiate the TLS handshake over the existing connection. Once the handshake is done, all future LDAP operations will be transmitted on the now secure, encrypted channel.
Personally my concerns with StartTLS are:

  • You must have a plain LDAP port open on the network.
  • Even after a client connects to the directory there is absolutely nothing preventing the user from sending BIND or any other kind of requests on the unencrypted channel before actually performing the StartTLS extended operation.

Now let’s see how to set up StartTLS:

dsconfig set-connection-handler-prop 
          --handler-name LDAP Connection Handler 
          --set allow-start-tls:true 
          --set key-manager-provider:JKS 
          --set trust-manager-provider:JKS 
          --hostname localhost 
          --port 4444 
          --trustStorePath /path/to/opendj/config/admin-truststore 
          --bindDN cn=Directory Manager 
          --bindPassword ****** 
          --no-prompt

Restart the server, and then verify that the connection works, run:

$ bin/ldapsearch -q -h localhost -p 1389 -D "cn=Directory Manager" -w ****** -b dc=example,dc=com "uid=user.0" "*"
The server is using the following certificate: 
    Subject DN:  EMAILADDRESS=peter.major@forgerock.com, CN=aldaris.sch.bme.hu, OU=Sustaining, O=ForgeRock Ltd, L=Bristol, C=GB
    Issuer DN:  EMAILADDRESS=peter.major@forgerock.com, CN=aldaris.sch.bme.hu, OU=Sustaining, O=ForgeRock Ltd, L=Bristol, C=GB
    Validity:  Wed Apr 22 19:43:22 BST 2015 through Thu Apr 21 19:43:22 BST 2016
Do you wish to trust this certificate and continue connecting to the server?
Please enter "yes" or "no":

Again, you can see that the entry is returned just fine after accepting the server certificate. For the sake of testing you can remove the “-q” (–useStartTLS) parameter from the ldapsearch command and you should still see the entry being returned, but this time around the connection was not encrypted at all.

So how does one prevent clients from using the connection without actually performing the StartTLS extended operation?
There is no real solution for this (based on my limited understanding of ACIs), because I couldn’t really find anything in the available list of permissions that would match BIND operations. Actually I’ve tried to set up an ACI like this:

aci: (target="ldap:///dc=example,dc=com")(version 3.0;acl "Prevent plain LDAP operations"; deny (all)(ssf<="1");)
 

but the BIND operations were still successful over plain LDAP. Whilst it was good that I couldn't really perform other LDAP operations, I think the worst has already happened, the user's password was transferred over an insecure network connection.
For more details on ssf by the way, feel free to check out the documentation. ;)

UPDATE!
Chris Ridd let me know that there is a way to enforce secure connections for BIND operations as well by configuring the password policy. To set up the password policy just run the following command:

dsconfig set-password-policy-prop 
          --policy-name Default Password Policy 
          --set require-secure-authentication:true 
          --hostname localhost 
          --port 4444 
          --trustStorePath /path/to/opendj/config/admin-truststore 
          --bindDN cn=Directory Manager 
          --bindPassword ****** 
          --no-prompt

Future BIND operations on unsecured LDAP connection will result in the following error:

[23/04/2015:10:04:27 +0100] BIND RES conn=2 op=0 msgID=1 result=49 authFailureID=197124 authFailureReason="Rejecting a simple bind request because the password policy requires secure authentication" authDN="uid=user.0,ou=people,dc=example,dc=com" etime=1

The problem is though that again, nothing actually prevented the user from sending the password over the unsecured connection...

Common misconceptions

I think the following misconceptions are causing the most problems around security:

  • StartTLS is more secure, because it has TLS in the name: WRONG! StartTLS allows just as well the usage of SSL(v2/v3) protocols, it is definitely not limited to TLS v1.x protocols by any means! Hopefully my explanation above makes it clearer that StartTLS is probably less secure than LDAPS.
  • LDAPS is less secure, because it has the ugly S (thinking it stands for SSL, but actually it stands for Secure): WRONG! as always, the actual security you can gain by using LDAPS connections is all matter of configuration. A badly configured LDAPS can still result in unsafe communication, yes, but LDAPS can just as well leverage the (currently considered safe) TLSv1.2 protocol and be perfectly safe.

I think I just can't emphasize this enough: use LDAPS if possible.

Understanding the login process

As authentication is pretty much the core functionality of OpenAM, I believe it is helpful to have a good understanding of how it works really. For starters let’s have a look at the different concepts around authentication.

Authentication modules

Authentication modules are just a simple piece of functionality that is meant to identify the user by some means. Depending on your requirements an authentication module could verify user credentials, or perform some kind of two factor verification process. For more complex use-cases you could use the auth module to collect informations about the end-user and with the help of a fraud-detection system determine if the current login attempt is “risky”.

In any case, the authentication modules are performing some (customizable) logic, and at the end of the module processing you can either ignore the current authentication module (neither a success nor a failure), OR succeed with a logged in user, OR just simply fail (invalid credentials, etc).

The authentication module implementations are JAAS based (with some abstraction on top of plain JAAS), so it is all based on callbacks (think of callbacks as input fields) that needs to be “handled” and submitted. When the callbacks are submitted, the AMLoginModule’s #process gets invoked with the callbacks. This is the time when the authentication module can start to process the submitted data and determine if the authentication attempt was successful. Since an authentication process can involve multiple steps (more than one set of callbacks to submit: for example requesting username, and than some verification code), the #process method needs to return a number that represents the next state (there are special numbers like ISAuthConstants.LOGIN_SUCCEED that represent successful authentication result, i.e. no further need to present callbacks), which then will be used to determine the next set of callbacks to display on the UI. Assuming that the authentication finished successfully, we need to return the magic LOGIN_SUCCEED state.

So how does OpenAM really know who the user is?

Once the authentication is successful, the auth framework will call AMLoginModule#getPrincipal which needs to return the authenticated user’s principal. #getPrincipal has a key role in the authentication process, so make sure its implemented correctly (or when using built-in modules, make sure they are configured correctly).

Authentication chains

The next sensible building blocks are the authentication chains. The auth chains can be considered as combinations of various authentication modules to present a single authentication procedure for the end-users. Following the previous example, one could think that checking some verification code after providing a username and password may not be actually the job of a single authentication module, and probably should be implemented separately. In that case one could implement one module for username/password login, and then implement an another module for code verification. To make sure the user logs in using both auth modules, one could create an authentication chain that includes both of them, and then the user will just need to authenticate against that chain.

Since the modules are JAAS-based, it makes sense to set up the chain configurations similarly to JAAS as well, but I’m not going to go into too much details on that front, instead you should just read about JAAS a bit more (especially about the “flags”).

Profile lookup

Once the user has successfully authenticated, there is a thing called “profile lookup”. This bit is all about trying to find the logged in user in one of the configured data stores, and then ensure that the user-specific settings (things like custom idle/max timeout values, or session quota even) are all applied for the just-to-be-created session. There are other additional checks as well, like ensuring that the logged in user actually has an active status in the system (e.g. doesn’t have a locked account).

To make things a bit more clearer let’s talk about User Profile Mode now (Access controlrealmAuthenticationAll Core Settings). The user profile mode tells what should happen at the profile lookup stage of the authentication, and these are the possible modes:

  • Required – this is the default mode, which just means that the user profile MUST exist in the configured data store.
  • Ignored – the user profile does not have to exist, the user profile will not be looked up as part of the authentication process.
  • Dynamic – the profile will be looked up, but if it doesn’t already exist, it will be dynamically created in the data store.
  • Dynamic with User Alias – this is similar to Dynamic, but it also appears to store user alias attributes in the newly created entries (I must admit I don’t fully understand this mode yet).

I believe it is important to stop here a bit and emphasize the following:
The authentication module may interact with arbitrary external components during the authentication phase, however when it comes to profile lookup, that will be always performed against the configured data stores. If you are running into the infamous “User has no profile in this organization” error message, then that means, that the authentication was successful, but the profile lookup failed, since OpenAM was unable to find the user in the configured data stores.

The profile lookup itself is performed based on the return value of #getPrincipal, so this is why it is really important to ensure that the module works correctly. The returned principal can be simply a username like “helloworld”, but it can also have a DN format like “uid=helloworld,ou=people,dc=example,dc=com” (see LDAP module’s Return User DN to DataStore setting). When the returned value is a DN, then the RDN value will be used for the data store search (so helloworld), hence it is important to ensure that the data store has been configured to search users based on the attribute that is expected to have the value of helloworld.

The idea behind all of this is that in #getPrincipal the username returned should be unique across the user base, so even if let’s say you allow someone authenticating with “John Smith”, you should still return a bit more meaningful username (like jsmith123) to the backend. That way you can ensure that when you ask for “John Smith”‘s user details, you will get the right set of values.

Post authentication actions

After a successful profile lookup there are various additional things that OpenAM does, but I’m not really going to go into the nifty details for those. Here’s a small list of things that normally happens:

  • Check if user account is active.
  • Check if the account is locked (using OpenAM’s built-in Account Lockout feature).
  • Check if there are user-specific session settings configured for the user, and apply those values for the newly created session.
  • When the user session is created, check if the session quota has been exhausted and run the corresponding quota exhaustion action if yes.
  • Execute the Post Authentication Processing plugins.
  • Determine the user’s success login URL and also ensure that the goto URL is validated.

Summary

Whilst we only discussed portions of the actual authentication process, I think the main concepts for authentication are laid out, so hopefully when you need to configure OpenAM the next time around, you can reuse the things learned here. :)

Jenkins and the mysterious Accept timed out error

Not so long ago I’ve been trying to set up a new Jenkins job on a freshly created Jenkins slave. Unfortunately for me the first attempt of running the Maven build resulted in a failure:

ERROR: Aborted Maven execution for InterruptedIOException
java.net.SocketTimeoutException: Accept timed out
 at java.net.PlainSocketImpl.socketAccept(Native Method)
 at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:398)
 at java.net.ServerSocket.implAccept(ServerSocket.java:530)
 at java.net.ServerSocket.accept(ServerSocket.java:498)
 at hudson.maven.AbstractMavenProcessFactory$SocketHandler$AcceptorImpl.accept(AbstractMavenProcessFactory.java:211)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:606)
 at hudson.remoting.RemoteInvocationHandler$RPCRequest.perform(RemoteInvocationHandler.java:309)
 at hudson.remoting.RemoteInvocationHandler$RPCRequest.call(RemoteInvocationHandler.java:290)
 at hudson.remoting.RemoteInvocationHandler$RPCRequest.call(RemoteInvocationHandler.java:249)
 at hudson.remoting.UserRequest.perform(UserRequest.java:118)
 at hudson.remoting.UserRequest.perform(UserRequest.java:48)
 at hudson.remoting.Request$2.run(Request.java:328)
 at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:72)
 at java.util.concurrent.FutureTask.run(FutureTask.java:262)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
 at java.lang.Thread.run(Thread.java:745)

Jenkins normally kicks off Maven builds by forking a Maven process on the system and then using Maven extensions to become part of the build process. So let’s have a look at an example command Jenkins runs for a Maven build:

[OpenAM] $ /jdk8/bin/java -cp /jenkins/maven31-agent.jar:/apache-maven-3.2.3/boot/plexus-classworlds-2.5.1.jar:/apache-maven-3.2.3/conf/logging jenkins.maven3.agent.Maven31Main /apache-maven-3.2.3 /jenkins/slave.jar /jenkins/maven31-interceptor.jar /jenkins/maven3-interceptor-commons.jar 57186

Here, that last parameter, 57186, looks quite suspiciously like a random port number. Could it be that Jenkins tries to connect to this local port and this connection fails resulting in the above stacktrace? Well, easy to test, let’s run the following iptables command on the box:

iptables -I INPUT -i lo -j ACCEPT

Try to rerun the build, and voilà, the build progresses further. Lesson learned. ;)

How to determine NSS/NSPR versions on Linux

Reverse engineering is a quite important skill to have when working with OpenAM, and this is even more the case for the web policy agents. Determining the version of the NSS and NSPR libraries may prove important when trying to build the agents, so here is a trick I’ve used in the past to determine the version of the bundled libraries.

To determine the version for NSPR, create nspr.c with the following content:

#include <dlfcn.h>
#include <stdio.h>

int main() {
        void* lib = dlopen("/opt/web_agents/apache24_agent/lib/libnspr4.so", RTLD_NOW);
        const char* (*func)() = dlsym(lib, "PR_GetVersion");
        printf("%sn", func());

        dlclose(lib);
        return 0;
}

Compile it using the following command (of course, make sure the path to the library is actually correct), then run the command:

$ gcc nspr.c -ldl
$ ./a.out
4.10.6

Here is the equivalent source for NSS, saved under nss.c:

#include <dlfcn.h>
#include <stdio.h>

int main() {
        void* lib = dlopen("/opt/web_agents/apache24_agent/lib/libnss3.so", RTLD_NOW);
        const char* (*func)() = dlsym(lib, "NSS_GetVersion");
        printf("%sn", func());

        dlclose(lib);
        return 0;
}

And an example output would look like:

$ gcc nss.c -ldl
$ ./a.out
3.16.3 Basic ECC

To determine the correct symbol names I’ve been using the following command:

nm -D libns{s,p}*.so | grep -i version

NOTE: While this code works nicely on Linux, for Windows and Solaris you will probably need a few adjustments, or there are potentially other/better ways to get information on the libraries.

How to compile 3.3.3 web agents on Linux

Trying to build the web policy agents may be a little frustrating at times, especially because it is generally not that well documented. As part of the 3.3.3 agent release I had the joy of going through the build process of the Linux web agents, so here you go, here are the steps I’ve gone through to build the agents on a freshly installed CentOS 5.10 (32 & 64 bit). For the very keen, I’ve also aggregated all the commands into shell scripts, you can find them at the bottom of this post.

Packages

On an out of the box CentOS 5.10 installation gcc is already installed, but there are some other components you should make sure you have installed before attempting to compile the agents:

  • subversion: To check out the source code of the agents.
  • ant: To perform the build, the agents are using Ant scripts to define the build steps.
  • zlib-devel: Compilation time dependency.
  • gcc-c++: C++ compiler.
  • rpm-build: RPM packages are being generated as part of the build process.
  • gcc44: Building NSS requires a newer GCC than the default one.
  • java-1.6.0-openjdk-devel: JDK to be able to build the agent installers (and run the Ant scripts).

The agent dependencies

The agents rely on the following external libraries:

  • NSS 3.16.3 & NSPR 4.10.6
    On 32 bit the component should be built with:

    make BUILD_OPT=1 NS_USE_GCC=1 NSS_ENABLE_ECC=1 NSDISTMODE="copy" CC="/usr/bin/gcc44 -Wl,-R,'$$ORIGIN/../lib' -Wl,-R,'$$ORIGIN'" nss_build_all

    On 64 bit the command is:

    make USE_64=1 BUILD_OPT=1 NS_USE_GCC=1 NSS_ENABLE_ECC=1 NSDISTMODE="copy" CC="/usr/bin/gcc44 -Wl,-R,'$$ORIGIN/../lib' -Wl,-R,'$$ORIGIN'" nss_build_all
  • PCRE 8.31
    On 32 bit:

    ./configure --with-pic --enable-newline-is-anycrlf --disable-cpp

    On 64 bit:

    CC="gcc -m64" CXX="g++ -m64" ./configure --with-pic --enable-newline-is-anycrlf --disable-cpp
  • libxml2 2.9.1
    On either platform:

    ./configure --prefix=/opt/libxml2 --with-threads --with-pic --without-iconv --without-python --without-readline
  • The header files of various web containers

Once all the libraries are in place, you can run the build on 32 bit:

ant nightly

on 64 bit:

ant nightly -Dbuild.type=64

And here are the shell scripts:
32 bit
64 bit

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 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. ;)