Category: IdRepo

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.

Fun with Active Directory

As we all know Active Directory can cause quite a few headscratching moments in general, now I’m trying to conclude some of my findings so maybe others won’t suffer from the same thing.

Creating user fails with LDAP 53

LDAP 53 is UNWILLING_TO_PERFORM, now there is two main candidate reason for this to happen during an ADD operation:

  • SSL isn’t used. AD will not process LDAP operations that involves setting/modifying the unicodePwd attribute if the request was made on a non-secure connection. Always make sure you use LDAPS when connecting to AD.
  • The provided password does not satisfy the password policies configured in AD. This is a bit harder to identify, but for the sake of testing try to use complex passwords and see if the user gets created like that.

Changing password fails

As stated already: AD requires SSL connection when sending the unicodePwd attribute. If this wouldn’t be already enough you have to also enclose the password value with double quote characters and the password should be in a UTF-16LE encoded format. A sample code snippet would look something like:

public byte[] encodePassword(String password) {
    return (""" + password + """).getBytes(Charset.forName("UTF-16LE"));
}

There is two main way to change passwords by the way:

  • A non-administrative password change: you BIND as the user whose password needs to be changed, and then you send a ModifyRequest with a DELETE and an ADD ModificationItem containing the old and the new password respectively (in the encoded format of course).
  • Administrative password reset: in this case you BIND as an admin user and then send a ModifyRequest with a REPLACE ModificationItem only containing the new password value.

Easy, right?

UPDATE

It appears that the DELETE/ADD combination can fail with the following error:

Constraint Violation: 0000052D: AtrErr: DSID-03190F80, #1:
	0: 0000052D: DSID-03190F80, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 9005a (unicodePwd)

In our case the root cause was the default password minimum age policy in AD (1 days). Once I’ve ran the following command from an Administrator CMD:

net accounts /MINPWAGE:0

AD started to like my ModifyRequests.

Identity Repositories

Identity Repositories (or Data Stores if you’d like) are almost the most important part of OpenAM. They enable you to manage identities within your organization (to a certain degree), and are involved in many processes in general:

  • during authentication it is often necessary to contact the IdRepo to get informations about the user (like the e-mail address, so an OTP code can be sent out)
  • after authentication takes place the user’s profile is looked up (for example to check that the user account is not inactivated)
  • once you have a session you can easily configure a PA to retrieve profile attributes in HTTP headers
  • when you authenticate via SAML, the IdRepo is contacted to retrieve the values for the SAML attributes in the assertion
  • etc

Some (boring) background about IdRepos

As you can see IdRepo is quite an important part of the product, so let’s try to get a better understanding of it.
In general, a given identity repository basically represents a single source of identity data. In this case identity can mean users, groups (and with ODSEE it can even mean roles and filtered roles). While it is common to use a regular relational database to store application data, in enterprise systems it is much more common to use some LDAP-capable directory server (OpenDJ, Active Directory, etc) instead. Since OpenAM is meant to be used as an enterprise solution it has been designed to work mainly with directory servers, but it is not limited to those (there is also Database IdRepo implementation, and with some patience any other sort of implementation can be written just as well). To make it simple let’s only talk about directory servers for now…

So as you already can guess, OpenAM has an LDAPv3Repo named IdRepo implementation which basically should be able to communicate with any LDAPv3 compliant directory server. While this sounds great, this also means that the implementation has grown quite big with time. There are parts for example where special handling is required for a given directory server type (yes, I’m talking about AD)..
In case of versions prior to 10.2.0, the LDAPv3Repo implementation has been using the quite old Netscape LDAP SDK to perform the LDAP operations. Although this solution works, I must say that support for the Netscape SDK is pretty much non-existent (and on top of that the whole SDK has been basically repackaged within OpenAM), so when it comes to debugging, it can become quite difficult to tackle down a given problem (again, we are talking about legacy code here). Because of this, and the fact that the OpenDJ team has started to develop an own LDAP SDK (which by the way ROCKS!), we decided to drop Netscape from the product. The good news is that this change already starts with 10.2.0 (though unfortunately there will be still parts of OpenAM using the old SDK, but hopefully not for too long).

So how do IdRepos work?

You can define any amount of Identity Repositories (Data Stores) within a realm, the only caveat is that all of the configured IdRepos will be contacted whenever an operation needs to be performed. You may ask why, well here it is:
OpenAM uses IdRepos to abstract away from identity repository differences, and within a realm it uses AMIdentityRepository to abstract away from the multiple IdRepo concept. This means basically that OpenAM only knows about the single AMIdentityRepository, which then knows everything about the different IdRepo configurations within the realm. To complicate things AMIdentityRepository is using IdServices to access all the IdRepo implementations, so let’s see the 4 different IdServices implementation for reference:

  • IdServicesImpl – the most basic implementation, this one just looks through the IdRepo list and executes the given operation on all of them. At the end it combines the results from the different IdRepos.
  • IdCachedServicesImpl – this implementation stores operation results in a cache, and when a given requested data cannot be found in the cache (which by the way is invalidated based on persistent search results), it just asks IdServicesImpl to contact the IdRepos for it.
  • IdRemoteServicesImpl – a remote implementation which is using JAX-RPC to contact an OpenAM server (which in turn will use Id(Cached)ServicesImpl) to perform operations remotely
  • IdRemoteCachedServicesImpl – as its name suggests: caches on the client side and asks OpenAM via IdRemoteServicesImpl if there is a cache miss.

Because IdRepos are implemented this way, you can hopefully see why we say that OpenAM wasn’t meant to be used for identity management. Creating/reading/updating/deleting entries from all the configured IdRepos cannot always fulfill all the requirements, and the expected behavior is poorly defined (for example what happens if you run a delete on an entry that only exists in one IdRepo?).

Alright, alright, but what’s new in OpenAM 10.2.0?

We have implemented a new IdRepo implementation, which is only using OpenDJ LDAP SDK to perform LDAP operations, keeping in mind that the behavior should be similar to the old Netscape-based IdRepo implementation. The main changes however are:

  • The LDAP failover code has been rewritten, and it should be much more reliable with the new release.
  • In the old version the user search attribute was misused, and it was basically handled as the naming attribute of the entry (i.e. first attribute name in the DN), this has been changed, so now the user search attribute is only used when performing search operations in the IdRepo, and the already existing naming attribute setting is used as naming attribute now.
  • Two new option has been introduced: heartbeat interval and heartbeat timeunit. These settings control the interval for sending out heartbeat search requests for idle connections. This means that if the connection is not idle, there won’t be any heartbeat request either.
  • Because of this the idle timeout setting has been removed. When there is a network component closing idle connections after a certain period of time, you should just configure the heartbeat interval correctly, and that should basically prevent the connection becoming idle.
  • Data store level cache has been removed (not implemented).

I think we can all agree on the fact that 10.2.0 will be an exciting release. ๐Ÿ™‚

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. ๐Ÿ™‚