Changing the password for an account stored in an LDAP directory server can sometimes be a race against time, especially for accounts that are used by applications. For example, let’s say that you’ve got a web application that uses a directory server to authenticate users and store their profile information. That application probably has its own account that it uses to authenticate to the directory server, and you’ve probably got several instances of that same application running on different servers all sharing that same account. If you need to change the password for that application account, then you risk breaking any instances of the application that need to authenticate to the server between the time that you change the password and the time that you can update the application with the new password.
It would be nice if there were some kind of grace period around password changes, in which the new password is immediately available to use, but the old password still works for a limited period of time. It just so happens that the Ping Identity Directory Server provides this capability through a feature that we call password retirement. It’s disabled by default, but you can enable it by adding one or more values for the password-retirement-behavior property in the password policy that governs the desired user account. The allowed values for this property are:
- retire-on-self-change — Indicates that the server should automatically retire a user’s previous password whenever they change their own password.
- retire-on-administrative-reset — Indicates that the server should automatically retire a user’s previous password whenever an administrator resets their password.
- retire-on-request-with-control — Indicates that the server should retire a user’s previous password whenever the operation used to change the password includes the retire password request control.
The password policy also offers a max-retired-password-age configuration property, which specifies the length of time that a retired password should be considered valid.
As an example, let’s say that you want to enable automatic password retirement whenever a user changes their own password and when a client issues a request that includes the retire password request control, and you want the previous password to remain valid for one hour. If you want to make that change in the default password policy, the command to do that would be:
dsconfig set-password-policy-prop \ --policy-name "Default Password Policy" \ --set password-retirement-behavior:retire-on-self-change \ --set password-retirement-behavior:retire-on-request-with-control \ --set "max-retired-password-age:1 h"
Note that if you successfully authenticate with a retired password, the server will include the password expiring request control (as described in draft-vchu-ldap-pwd-policy-00.txt) in the bind response. This response control indicates that the password is only valid for a limited period of time, and its value specifies the number of seconds that the password will remain valid.
The Retire Password Request Control
The retire password request control can be included in either an LDAP modify request or in a password modify extended request. It explicitly indicates that the server should retire the user’s old password so that it can continue to be used for a limited period of time. The control has an OID of “1.3.6.1.4.1.30221.2.5.31”, and it does not take a value. The UnboundID LDAP SDK for Java provides support for this control via the RetirePasswordRequestControl class, but since it doesn’t require a value, it’s easy to use in any other LDAP API using just the OID.
For the server to honor the retire password request control, the target user’s password policy does need to be configured with retire-on-request-with-control as one of the values for the password-retirement-behavior property. If the password policy’s retirement behavior would have automatically retired the former password anyway, then including the retire password request control in the request used to change the password isn’t necessary, but it won’t hurt anything.
The Purge Password Request Control
The purge password request control can also be included in either an LDAP modify request or in a password modify extended request. It explicitly indicates that the server should purge the user’s former password when setting a new one. This can be useful, for example, if you suspect that the user’s password might have been compromised and you don’t want to allow it to be used after the password change. The purge password request control has an OID of “1.3.6.1.4.1.30221.2.5.32”, and it does not require a value. The UnboundID LDAP SDK for Java provides support for this control through the PurgePasswordRequestControl class, but it’s easy to use the control in other LDAP APIs with just the request OID.
If it is present in a request, then the purge password request control will override the password-retirement-behavior configuration in the password policy. You can use it to ensure that the former password won’t be retired, even if the server would have automatically retired the password without this control.
Using the Retire and Purge Password Controls With ldapmodify or ldappasswordmodify
Both the ldapmodify tool (which allows for requesting add, delete, modify, and modify DN operations) and the ldappasswordmodify tool (which allows for requesting the password modify extended operation) support both the retire password request control and the purge password request control. The controls can be included in applicable requests using the --retireCurrentPassword or --purgeCurrentPassword arguments, respectively.
For example, let’s say that the user “uid=jdoe,ou=People,dc=example,dc=com” currently has a password of “originalPassword”. If we want to use an LDAP modify operation to perform a self-password change to make it “secondPassword”, and if we want to include the retire password request control in the modify request, then we can do that with the following command:
$ bin/ldapmodify --hostname ldap.example.com \ --port 636 \ --useSSL \ --trustStorePath config/truststore \ --bindDN "uid=jdoe,ou=People,dc=example,dc=com" \ --bindPassword originalPassword \ --retireCurrentPassword # Successfully connected to ldap.example.com:636. dn: uid=jdoe,ou=People,dc=example,dc=com changetype: modify delete: userPassword userPassword: originalPassword - add: userPassword userPassword: secondPassword - # Modifying entry uid=jdoe,ou=People,dc=example,dc=com ... # Result Code: 0 (success)
We can use the ldapsearch tool to verify that the user can now use either the new password or the former password to authenticate:
$ bin/ldapsearch --hostname ldap.example.com \ --port 636 \ --useSSL \ --trustStorePath config/truststore \ --bindDN "uid=jdoe,ou=People,dc=example,dc=com" \ --bindPassword secondPassword \ --baseDN "dc=example,dc=com" \ --scope base \ "(objectClass=*)" dn: dc=example,dc=com objectClass: top objectClass: domain dc: example # Result Code: 0 (success) # Number of Entries Returned: 1 $ bin/ldapsearch --hostname ldap.example.com \ --port 636 \ --useSSL \ --trustStorePath config/truststore \ --bindDN "uid=jdoe,ou=People,dc=example,dc=com" \ --bindPassword originalPassword \ --baseDN "dc=example,dc=com" \ --scope base \ "(objectClass=*)" # Bind Result: # Result Code: 0 (success) # Password Expiring Response Control: # OID: 2.16.840.1.113730.3.4.5 # Seconds Until Expiration: 3317 dn: dc=example,dc=com objectClass: top objectClass: domain dc: example # Result Code: 0 (success) # Number of Entries Returned: 1
As you can see, in this second case when we used the original password rather than the new one, the server returned the password expiring response control indicating that the former password was only valid for another 3317 seconds.
If we wanted to perform another self-change, this time using the password modify extended operation to use a self-change for a new password of “thirdPassword”, and we wanted to include the purge password request control, we could accomplish that as follows:
$ bin/ldappasswordmodify --hostname ldap.example.com \ --port 636 \ --useSSL \ --trustStorePath config/truststore \ --authzID "dn:uid=jdoe,ou=People,dc=example,dc=com" \ --currentPassword "secondPassword" \ --newPassword "thirdPassword" \ --purgeCurrentPassword The LDAP password modify operation was successful
After this, ldapsearch shows that we can successfully authenticate with the new password, but not with either of the previous old passwords because they have been purged:
$ bin/ldapsearch --hostname ldap.example.com --port 636 \ --useSSL \ --trustStorePath config/truststore \ --bindDN "uid=jdoe,ou=People,dc=example,dc=com" \ --bindPassword thirdPassword \ --baseDN "dc=example,dc=com" \ --scope base \ "(objectClass=*)" dn: dc=example,dc=com objectClass: top objectClass: domain dc: example # Result Code: 0 (success) # Number of Entries Returned: 1 $ bin/ldapsearch --hostname ldap.example.com --port 636 \ --useSSL \ --trustStorePath config/truststore \ --bindDN "uid=jdoe,ou=People,dc=example,dc=com" \ --bindPassword secondPassword \ --baseDN "dc=example,dc=com" \ --scope base \ "(objectClass=*)" # Bind Result: # Result Code: 49 (invalid credentials) # An error occurred while attempting to create a connection pool to communicate with the directory server: # LDAPException(resultCode=49 (invalid credentials), errorMessage='invalid credentials', ldapSDKVersion=4.0.10, # revision=c8659b0364e0ccaec7a4925f47c184907557a5db) $ bin/ldapsearch --hostname ldap.example.com --port 636 \ --useSSL \ --trustStorePath config/truststore \ --bindDN "uid=jdoe,ou=People,dc=example,dc=com" \ --bindPassword originalPassword \ --baseDN "dc=example,dc=com" \ --scope base \ "(objectClass=*)" # Bind Result: # Result Code: 49 (invalid credentials) # An error occurred while attempting to create a connection pool to communicate with the directory server: # LDAPException(resultCode=49 (invalid credentials), errorMessage='invalid credentials', ldapSDKVersion=4.0.10, # revision=c8659b0364e0ccaec7a4925f47c184907557a5db)