Tag: REST

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. 😉

Proper way of changing password in OpenAM

There is always a point, when an OpenAM user ends up asking the question: ‘but how can my users change their passwords?’. The answer is quite tricky: there are several ways to do this, but you’re going to have problems with almost each one of them. But first things first, let’s see what do we want exactly:

  • Show an own form for the user, where he/she can give the old password, new password / confirmation of the new password combo and press the button, which changes the password with help of OpenAM.
  • Change the users password in LDAP without enabling the force-reset password LDAP control. (Note this is only important if you use LDAP datastore, JDBC datastore is in early access stage, this article will not cover that).

This second point is not mandatory, but this feature is used often (by the LDAP module for example).
Note: your Directory Server configuration should contain a force-change-on-reset option with true value. The force-change-on-reset mode means, that if the password was resetted by an LDAP administrator, then the user HAS TO change his/her password to be able to do anything.
Note 2: if your DS uses this option, and you’re using the Datastore module (so something that’s not based on the LDAP module), then your module will genuinely fail, if you’re not handling this special case.

So what’s the proper way?

The proper way is when you have the old password, you validate it (by doing a BIND request) to make sure, that the user is who he tell he is. If the BIND was successful, then we execute a MODIFY command in the name of the just logged in user to modify the userPassword field. This is the proper way, because with this logic you are not resetting your password with the help of an administrator, so the force-reset stays false.

The REST API way

The REST API does not have a specific changepassword function, but it does have an update function as I mentioned in my earlier post. This also means, that because you don’t supply the old password, you just can’t reset the password as yourself, it has to run in the LDAP admins name, so this is a force-reset always! So the magic here is that you call the following REST function:

https://<FQDNSSO>/openam/identity/update?identity_name=aldaris&
identity_realm=/&identity_attribute_names=userPassword&
identity_attribute_values_userPassword=newPass

Doing this as the logged in user

If you do have the users sessionid, then you could call this resource as the user, IF your DS does not forbid modifying the userPassword for example with an ACI. OpenDS by default has the following global-aci:

(targetattr="audio||authPassword||description||displayName||givenName||
homePhone||homePostalAddress||initials||jpegPhoto||labeledURI||mobile||
pager||postalAddress||postalCode||preferredLanguage||telephoneNumber||
userPassword")(version 3.0; acl "Self entry modification"; allow (write)
userdn="ldap:///self";)

This will enable you to modify your own password.

Doing this as admin

Since you’re doing this as admin, the password will be certainly overwritten.

The WebService way

The WebService way and the REST API way does not differ from each other, since they are both using the exact same code.

The ClientSDK way

The ClientSDK has several way accessing an Identity, for example using AMIdentityRepository:

SSOToken token = //getting an SSOToken object
AMIdentityRepository idRepo = new AMIdentityRepository(token, "/realm");
IdSearchResults results = idRepo.searchIdentities(IdType.USER, "aldaris",
    new IdSearchControl());
Set identities = results.getSearchResults();
AMIdentity identity = (AMIdentity) identities.iterator().next();

Using AMIdentity#store

The idea here is to use the setAttributes method to update the desirable attributes and then call the store, something like this:

AMIdentity identity = ...
Map attrs = new HashMap();
Set values = new HashSet(1);
values.add(newPass);
attrs.put("userPassword", values);
identity.setAttributes(attrs);
identity.store();

The problem with AMIdentity#store is the same as with the update function: you don’t supply the old password for the function, so there is no way, that this could end up without a force-reset. Also this solution worked for me with an IdRepo created with user token.

Using AMIdentity#changePassword

The AMIdentity class also has a changePassword method, which does exactly what we want, so let’s see some code:

AMIdentity identity = ...
identity.changePassword(oldPass, newPass);

but I had one problem with this: when I accessed the AMIdentity from an IdRepo with a usertoken, then I had the following error message:

Permission to perform the read operation denied to
id=aldaris,ou=user,dc=opensso,dc=java,dc=net

For me this does not make any sense, since I’m giving the old password and the new one, the username is supplied by the AMIdentity itself, then why do I need read permission and for what?? Anyway, when I did this with admintoken, then everything was working great without force-reset.

The OpenAM console way

There is also a way to change your password with help of OpenAM console. Just point your browser to

https://<FQDNSSO>/openam/user/UMChangeUserPassword

I tried it many times, but the Old password field was always disabled for me, so this password change method also sets the force-reset bit…

Summary

As you can see there are many ways to change a users password, but the best way I think is to use ClientSDK with AMIdentity#changePassword or LDAP API directly. For testing the pwdReset flag I’ve used the following command:

ldapsearch -D "cn=Directory Manager" -b "ou=forgerock,o=org" -h localhost
-p 1389 -w password "uid=aldaris" "pwdReset"

If you don’t need the force-change-on-reset functionality, then probably you can disable it in your Directory Server. If you want to hack, then maybe you can try out to delete the pwdReset attribute with the REST API, maybe it will work, I haven’t tried it.

Using the REST API for Identity Management

It’s not that well known, but OpenAM has a REST interface for login/logging/authorization and also for basic identity Management too. This post will describe the IDM functionality of the REST API, so it’s about creating, updating and deleting users in the DataStores.

When you google the term ‘OpenAM REST’, you won’t find much thing, because it’s not really well documented part of OpenAM, but if you google hard enough you will find this link to Docteger’s blog. This post is just GREAT, everything in one place, but it’s missing the answer for the ‘how-can-I-handle-realms-with-this’ type of question. So here is my result of few hours reading of IdentityServicesImpl:

Create Identity

https://<FQDNSSO>/openam/identity/create?identity_name=username&identity_realm=/&
identity_type=user&identity_attribute_names=cn&identity_attribute_values_cn=
MyNewCn&identity_attribute_names=userPassword&
identity_attribute_values_userPassword=password

Gotcha #1:
The password length needs to be at least 8 characters by default, if you want to change this, read this mail.

Read Identity

https://<FQDNSSO>/openam/identity/read?name=username&attributes_names=realm&
attributes_values_realm=/

Tip #1:
You could use the attributes_names parameter to ask specific parameters of the given identity.

Update Identity

https://<FQDNSSO>/openam/identity/update?identity_name=username&identity_realm=/&
identity_attribute_names=cn&identity_attribute_values_cn=NewerCn

Here you only have to add the parameters to the query, which are actually changed.
Gotcha #2:
If you want to change the password like this, then you need an authenticated admin token, since the user can’t (always?) update it’s own password.

Delete Identity

https://<FQDNSSO>/openam/identity/delete?identity_name=username&identity_realm=/&
identity_type=user

Gotcha #3:
There’s no really Gotcha here, you just have to specify, that the deletable item is actually a user.

Conclusion

This is great and everything, but you can’t do these stuff without login & search the user, so here are these calls too:

Authenticate

https://<FQDNSSO>/openam/identity/authenticate?username=username&
password=password&uri=realm%3D/%26service%3DldapService

Gotcha #4:
Note the %3D (‘=’) and %26 (‘&’) characters, they are url-encoded, since it’s a single value for the ‘uri’ param.

Search Identity

https://<FQDNSSO>/openam/identity/search?filter=*)(|(inetUserStatus=Active)&
attributes_names=realm&attributes_values_realm=/

Tip #2:
You can use the filter with some dirty hack to give OR filters too as the previous URL shows.

Summary

The REST interface is great and FAST, so use it whenever you have the chance. The only problem with it this weird parameter-handling, one time it’s ‘identity_name’, another time it’s ‘username’, so you probably going to need a few parser for using it, but I think it’s worth it. If I heard right, it’s going to use JSON-format parameters in the future, so it’s going to be much better. 🙂