Password Policy Recommendations for the Ping Identity Directory Server

When using an LDAP directory server to authenticate users, the vast majority of those authentications will make use of a password. Even though the Ping Identity Directory Server supports multiple options for two-factor authentication, you’re still likely to use a password as one of those factors. As such, ensuring that you have a good password policy in place is an essential element of your server’s security configuration.

Obviously, the biggest risk when using password-based authentication is that someone will provide the correct credentials for an account that isn’t theirs, and will be able to do whatever the account owner can do. Some of the most common ways that this can happen are:

  • The account has a really simple, easily guessable password. Maybe they use the word “password” or some other common value. Maybe their password is the same as their username or email address. Maybe their password is the same as your application or service.
  • The account owner used the same username and password across multiple sites, and one of those other sites got breached. When this happens, attackers often try to use the breached credentials to log into other sites.
  • An attacker managed to get access to the encoded representation of a user’s password and was able to crack it through some means.

Some of these are things that are out of your control as a website owner. For example, no matter how careful you are with your own site, you can’t prevent some other site from getting breached and its credentials exposed. However, you can take steps to ensure that your site is better protected against these kinds of things. In this post, I’ll discuss options for creating a password policy that will help keep your site as safe as possible.

While some of the recommendations I provide here are specific to the Ping Identity Directory Server, there is a lot of generic advice in here as well, and that may be applicable to other types of directory servers.

Require Secure Communication

Most LDAP authentication schemes that involve a password send that password to the server in the clear, without any kind of encoding or transformation. There are some that do try to obscure the password (e.g., salted challenge-response authentication mechanisms), but they are typically garbage that actually dramatically weaken the security of your environment and should be avoided at all costs. All of the best password-based authentication mechanisms send the password in the clear, which means that you must ensure that all of the communication happens over a secure, encrypted connection.

The server offers a few options to help ensure that this is done. They are as follows, in order of strongest protection to weakest protection:

  1. Disable any connection handlers that allow insecure communication. If you only allow LDAPS and not unencrypted LDAP, then there is no chance that someone with the ability to observe the communication will be able to decipher it.
  2. Set the “reject-insecure-requests” property to “true” in the global configuration. While this will allow clients to establish insecure connections to the server, it will reject most requests issued over those connections until the client has used the StartTLS extended operation to convert that connection from an insecure one to one that is secure. However, this option isn’t as good as simply disabling all insecure communication because even though it ensures that clients won’t be permitted to authenticate with an unencrypted bind request, it can’t prevent the client from sending that request in the first place.
  3. Set the “require-secure-authentication” and “require-secure-password-changes” properties to “true” in the password policy configuration. These properties ensure that the server will reject any requests that attempt to authenticate or change passwords that are sent over an unencrypted connection. Like option #2 above, this won’t prevent the requests from being sent, but will merely reject them if they are. But it also won’t protect other sensitive information that may be transferred over the connection.

Use a Strong Password Storage Scheme

When the Directory Server receives a password to store in a user entry, it encodes that password with a password storage scheme. This helps protect that password so that anyone who gains access to the entry (whether over LDAP, in a backup of the database, in an LDIF export, or in some other form) won’t be able to determine what the clear-text password really is. When the server receives a bind request that contains a clear-text password, it will use the password storage scheme and information contained in the encoded password to determine whether the provided clear-text password matches the one used to create the encoded password.

The server offers a number of different password storage scheme options, that fall into different categories:

  • Some of them, like those that use the UNIX crypt algorithm or versions of the MD5 or SHA-1 digest algorithms, are only intended for legacy purposes like migrating already-encoded passwords from another data store. You definitely shouldn’t use these for new passwords unless you absolutely have to maintain backward compatibility with a legacy system for some period of time.
  • Some of them, like those that use AES or triple-DES, use reversible encryption to encode the passwords in a way that allows the server to obtain their original clear-text value. These should not be used unless you absolutely have to support a legacy authentication scheme (like CRAM-MD5 or DIGEST-MD5) that requires that the server be able to determine the clear-text representation of the password.
  • Some of them offer salted variants of more modern digest algorithms, like 256-bit, 384-bit, and 512-bit SHA-2 variants. These schemes are acceptable, especially if you have configured an appropriate set of password validators that require users to have strong passwords, but they are very fast, which means that an attacker can make a lot of guesses in a short period of time.
  • Some of them use algorithms that are intentionally designed to require a lot of CPU processing or memory access so that they take longer to compute, or so that it’s harder to compute a lot of them at the same time. We currently offer support for the PBKDF2, bcrypt, and scrypt schemes, and plan to add others in the future. These are the strongest options available, and they will definitely have a dramatic impact on how long it will take an attacker to brute-force a password, but they can also dramatically impact the performance of legitimate authentication attempts, and also the performance of an LDIF import that includes clear-text passwords that need to be encoded.

Ideally, you should encode passwords with the strongest scheme that you can tolerate based on your load. If you don’t need more than a few hundred authentications or password changes per second, then you should probably consider the “expensive” schemes to make it as difficult as possible for attackers that may get access to encoded passwords. But if your performance demands are such that you can’t afford enough servers to handle the authentication load with these expensive schemes, then at least use one of the strong modern digests.

Note that if you choose a particular scheme now, or if you have existing passwords encoded with legacy schemes, you aren’t stuck with them, and you don’t have to require all of your users to change their passwords to transition to stronger encodings. For that, we offer the deprecated-password-storage-scheme property in the password policy configuration. If a user has a password encoded with a deprecated scheme, the server will automatically re-encode it using the default scheme the next time they use that password to authenticate.

Require Strong Passwords

It’s important to ensure that passwords are encoded in a secure manner, but it’s even more important that the passwords are strong to start with. If a user has a password that can be easily guessed, then it won’t take long to crack it using even the strongest encoding.

The best practices for choosing secure passwords have changed over the last several years. Things that people used to think were good ideas have turned out to be not so hot. Some of the things that you shouldn’t do are:

  • Don’t require users to change their passwords on a regular basis for no reason. That’s just annoying to your users and doesn’t improve security in any meaningful way. In fact, it’s more likely to cause people to make bad decisions, like just keeping the same basic password but adding a counter to the end. You should only require users to change their passwords if you suspect that they may have been compromised, or if they’ve been reset by an administrator (e.g., because the user forgot what their previous password was).
  • Don’t impose a ridiculous upper limit on the length of a password. There’s just no good reason for it. All of the good password storage schemes that we provide generate the same encoded password length regardless of the size of the password being encoded, so you’re not saving any space by preventing long passwords. With all other things being equal, longer passwords are stronger than shorter ones, so you want to encourage people to choose long passwords. It is true that some algorithms may take longer to encode a really long password, so maybe don’t let people choose passwords longer than a few hundred characters, but there’s never a good reason to impose an upper limit of something like ten or twenty characters.
  • Don’t require passwords to have different classes of characters. It’s entirely possible to have a very strong password that is comprised entirely of lowercase letters, just as it’s entirely possible for a password containing a mix of lowercase and uppercase letters, digits, and symbols to be very weak. You certainly shouldn’t prevent people from using a mix of character types, but you also shouldn’t require it for no good reason.
  • Don’t impose ridiculously low limits on the number of times the same character may appear consecutively in a password. Long, randomly generated passwords are very strong, and yet it’s entirely possible that such passwords may have the same character appear two or three or four times in a row. It’s very frustrating to choose what is clearly a very strong password only to have it rejected for some stupid reason like this. If you want to prevent passwords comprised of repeated characters, like “aaaaaaaaaa”, then it would probably be better to require them to have a minimum number of unique characters than limiting the number of times the same character may appear in a row.

So what should you do? Despite being a government document, NIST Special Publication 800-63B has some really good advice. And here are some good guidelines that include these and other recommendations:

  • Don’t allow users to choose obvious or weak passwords. This includes commonly used passwords, dictionary words, and words related to the service that you’re operating. To help with this, the Ping Identity Directory Server offers a dictionary password validator that can reject attempts to use passwords in a given dictionary file, and we offer a couple of instances of that validator preconfigured with dictionary files to use with it: one with over 500,000 of the most commonly used passwords, and one with over 400,000 English words. We recommend that you also configure a validator with your own custom dictionary that includes at least words related to your organization and the products or services you provide., and you may also want to use dictionaries of non-English words. Also, we’ve got plans to provide additional options in this area in the near future.
  • Impose a minimum length for passwords (but not a maximum). If a password is too short, then it’s too easy to brute force. There are a couple of ways that you can do this in the Ping Identity Directory Server:

    • You can use the length-based validator to simply impose a minimum length. NIST 800-63B suggests at least eight characters, but to be honest, even a complex eight-character password can be cheaply brute-forced in a handful of hours.
    • Another option is to use the haystack validator, which uses the concept of password haystacks to evaluate a password’s complexity as a factor of both its length and the types of characters it contains. This is perhaps more complicated to explain to users, but it can do a better job of ensuring resistance to brute force attacks.
  • Prevent users from choosing passwords that are related to other information that you have about them. For example, you should not allow them to choose a password that matches their username, email address, telephone number, date of birth, etc. The Ping Identity Directory Server offers an attribute value password validator that can be used to ensure that passwords aren’t allowed to match (or optionally contain) the values of other attributes in the entry.
  • When users are changing their passwords, they should not be permitted to choose a new password that is too similar to their current password. For example, if their current password is “ThisIsMyStrongPassword1”, it’s probably a good idea to prevent them from choosing “ThisIsMyStrongPassword2” as their new password. To achieve this, the Ping Identity Directory Server offers a similarity validator that uses the Levenshtein distance algorithm to compare a proposed new password with the user’s current password (which would have to be provided as part of the password change, since we don’t recommend configuring the server to store passwords in a reversible form) to determine the minimum number of changes (characters added, removed, or replaced).
  • As noted above, it’s not a good idea to reject passwords just because they have the same password repeated a few times in a row, but it might not be a bad idea to require that they have a minimum number of different characters. The Ping Identity Directory Server offers a unique characters password validator that can accomplish this.
  • Let the user know what the requirements are. The Ping Identity Directory Server offers a get password quality requirements extended operation that you can use to programmatically retrieve information about the validation that the server will perform, and it offers a password validation details control that you can use to determine which requirements were satisfied and which were not when trying to set a password. I wrote an earlier blog post about using these features.
  • Recommend the use of a password manager, and recommend using them to generate long random passwords. Certainly don’t do anything that would discourage their use, like prevent users from pasting text into fields on login forms, or interfere with things that might legitimately happen in long random passwords (like having the same character repeated a few times).

If your service imposes additional requirements on password quality that can’t be enforced with the above validators, then I would first recommend taking a hard look at whether they are actually good requirements. Lots of organizations have really dumb rules that either weaken security or at least don’t do anything to improve it while also annoying the end users. But if it turns out that you do have a legitimate need to impose additional types of requirements, then our Server SDK provides an API that allows you to create your own custom password validator implementations.

Require the Current Password for Password Changes

When a user wants to change their password, it’s a good idea to verify that it’s actually them and not someone who has gotten access to their account (e.g., someone using a shared computer that was left logged in). Further, if the user provides their current password when choosing a new password, it allows the server to perform additional types of validation for the new password that may not otherwise be available (e.g., making sure that the new password is not too similar to the current password).

The Ping Identity Directory Server offers a password policy setting to enforce this. If the password-change-requires-current-password property is set to true, then any user changing their own password will be required to provide their current password. This applies to both password changes that use a standard LDAP modify operation and those that use the password modify extended operation. The password modify extended operation already includes a field for the current password, so meeting this requirement for that type of operation is obvious. It’s less obvious for a standard LDAP modify operation, but the way to achieve that is to provide the clear-text representation of the current password in a delete modification, and the desired new password in an add modification, like:

dn: uid=test.user,ou=People,dc=example,dc=com
changetype: modify
delete: userPassword
userPassword: oldPassword
-
add: userPassword
userPassword: newPassword
-

Note that this does not apply to administrative password resets in which one user changes the password for another user.

Enable a Password History

Password reuse is one of the most common ways for accounts to get breached. The biggest risk is when someone uses the same password across multiple sites: if one of those sites is breached and the passwords are obtained, then attackers can try the same credentials on other sites. However, it’s also a bad idea for a user to repeatedly use the same password on the same site.

Repeatedly using the same password on the same site is most commonly an issue if that site requires people to change their password, which, as already stated, is an ill-advised strategy. If you don’t enable password expiration, then people will be less likely to arbitrarily change their passwords, and therefore will be less likely to want to reuse one that they’ve chosen before.

Another common reason for someone wanting to reuse the same password is if they have accidentally locked their account by mis-typing their current password too many times. If this happens, then it’s almost certainly the result of a bad account lockout configuration, which is something that I’ll cover below.

Even if the most common reasons that a user would want to reuse a password are the result of ill-advised configurations, it’s still a good idea to prevent someone from repeating a password that they’ve already had in the past. The server already prevents a user from choosing a new password that is the same as their current password, but if you want to prevent users from repeating passwords they’ve had in the past, then the best way to do that is by enabling a password history. The password policy configuration offers a couple of properties to do this:

  • password-history-count — Specifies the maximum number of passwords to retain in the history, regardless of how long it’s been since the passwords were in use.
  • password-history-duration — Specifies the length of time to retain previous passwords in the history, regardless of how many of them have been used during that time.

In either case, the server will maintain an operational attribute with the encoded representations of the user’s former passwords, along with a timestamp indicating when the password was added to the history.

Note that if you do enable a password history, then you may also want to prevent users from changing their passwords too frequently, which you can do with the min-password-age property. This is especially true if you have configured a maximum history count because it’s a common trick for a user who really wants to reuse a previous password to change their password multiple times in quick succession until the password they want to reuse has been flushed from the history. But even though this trick is less effective if you configure a maximum duration rather than a maximum count, it’s still a good idea to prevent frequent password changes in that case too, just to avoid unnecessarily building up a large history.

A relatively short minimum password age, like a day or perhaps even an hour, should be enough of a deterrent to someone who wants to just blow out the password history so that they can reuse a password. Note, however, that the minimum password age only applies to self-changes and not administrative resets. If a user chooses a new password and then immediately forgets it so that they cannot authenticate, then an administrator will be able to reset the password even within the minimum age. And if force-change-on-reset is true, indicating that users are required to choose a new password after an administrative reset, then the minimum age will not interfere with that, either.

Don’t Allow Pre-Encoded Passwords

As previously noted, the server does not store passwords in the clear, but instead encodes all clear-text passwords that it is asked to store (ideally in a strong, non-reversible form). While it is technically possible to send the password to the server in a form that is already encoded, this is generally a very bad idea, for many reasons. Some of them include:

  • If a password is pre-encoded, the server can’t determine its clear-text representation, and therefore can’t verify that it meets the configured password quality requirements.
  • Because there are multiple ways of encoding a single password (e.g., using a different salt), there is no way to ensure that the new password isn’t just an alternate encoding for the user’s current password, or for another password in the user’s password history.
  • The Directory Server generally supports a wide range of applications. If applications pre-encode passwords before sending them to the server, then it makes it more difficult to migrate to a stronger encoding if that becomes necessary or desirable.

The Ping Identity Directory Server offers an allow-pre-encoded-passwords configuration property that controls whether it will accept pre-encoded passwords, and it has a value of false by default. We strongly recommend leaving this as the default.

We do understand that there may be legitimate cases in which it may be necessary to send pre-encoded passwords to the server. For example, if you’re synchronizing data between two sources, and if the data being synchronized includes pre-encoded passwords, then you might not have access to the clear-text password. In that case, rather than reconfiguring the password policy to allow any client to supply passwords in a pre-encoded form, a better alternative is to use the password update behavior request control in any such applications. One of the features that this control offers is a way to indicate that a pre-encoded password should be accepted on a per-change basis. The Ping Identity Synchronization Server (which is included at no additional charge with the Ping Identity Directory Server) makes use of this control for the synchronization that it performs.

Note that the value of the allow-pre-encoded-passwords property does not have any effect on LDIF import. The server will always permit pre-encoded passwords contained in an LDIF file that is loaded into the server with the import-ldif command or with the LDIF import task.

Don’t Allow Multiple Passwords

The two most common attribute types for storing passwords, userPassword (defined in RFC 4519) and authPassword (defined in RFC 3112), are both permitted to have multiple values. In theory, this means that a user could have multiple passwords. However, this is not a good idea. If a user has multiple passwords, then there is no way to control which one is used for any given authentication attempt, so all of them will be considered valid and represents the potential opportunity for the account to be breached.

Further, password policy state is not tied specifically to any password, but rather is maintained on a per-account basis. This means that if a user is allowed to have multiple passwords, then they may be able to exploit that to circumvent certain password policy functionality. For example, if password expiration is enabled (which I’ve already mentioned is a bad idea), then a user with two passwords could just keep changing one of them and continue using the other indefinitely. The same would apply to similar restrictions, like being forced to choose a new password after an administrative reset.

The Ping Identity Directory Server provides an allow-multiple-password-values configuration property to control this behavior, and it has a value of false by default. We do not recommend changing its value to true.

Configure Failure Lockout

If an attacker can get access to the encoded representation of a password, they can throw a lot of hardware at trying to crack it. But if they can’t get access to the encoded representation (and hopefully, that’s a very difficult thing to do), they can still try to crack a password by simply repeatedly trying to log in to an application that authenticates against the server. This is much slower, but it can still be quite effective for users with weak passwords.

You can make this a lot harder by limiting the number of login attempts that someone can make before the account is locked. If an account is locked, then it doesn’t matter what password is tried because any attempt will be rejected. The Ping Identity Directory Server offers support for two types of failure lockout:

  • Permanent lockout, in which you don’t specify a maximum lockout duration. In this case, the account lockout will remain in effect until an administrator resets the user’s password.
  • Temporary lockout, in which you do specify a maximum lockout duration (via the lockout-duration configuration property). In this case, the lockout will automatically be lifted after a period of time, but it can still be unlocked earlier by a password reset.

I would strongly recommend using the temporary lockout with a relatively short lockout duration (e.g., one minute). This is sufficient to severely limit the rate at which an attacker can make guesses, but it also ensures that the lockout is short enough that an administrator shouldn’t need to get involved in the case that a user accidentally locks their account but still remembers the correct password. However, I would also strongly recommend setting the lockout failure count (via the lockout-failure-count property) to be high enough that it’s very unlikely to be reached accidentally by merely fat-fingering the password. Ten failed attempts seems like a good value for this purpose.

Note that the Ping Identity Directory Server offers an additional useful feature for helping to prevent accidental failure lockout. If the ignore-duplicate-password-failures property is set to its default value of true, the server will consider repeated attempts to use the same wrong password as just a single failed attempt. This can be helpful in cases where the password has been configured in an application, and that application hasn’t been updated after the user changes their password. If you’re concerned about this scenario, then you may also want to consider enabling password retirement, as described in a later section.

Configure Password Reset Constraints

The account’s owner should be the only one to know its password. If they forget their password and they need to have it reset, there are two basic options:

  • Have the password reset by a human administrator. In this case, the administrator can choose the password themselves, or they can allow the server to generate it for them (more on this later). Either way, the administrator will end up knowing the password, which means that it should only be usable for the purpose of allowing the user to set a password of their own choosing. The force-change-on-reset configuration property can be used to enable this, and you may also want to set a value for the max-password-reset-age property to limit how long that temporary password is valid.
  • Create an automated process that allows the user to reset their own password after providing reasonable proof of their identity. To help with this, the Ping Identity Directory Server offers a feature that we call password reset tokens. A password reset token is a single-use password that can be delivered to the user through some out-of-band mechanism (e.g., email message, text message, voice call, mobile push notification, etc.) and can be provided to the password modify extended operation to allow the user to choose a new password. See the DeliverPasswordResetTokenExtendedRequest class in the LDAP SDK for more information about this feature.

Use a Strong Password Generator

The server has the ability to generate passwords under certain circumstances. Some of them include:

  • When using the password modify extended operation. If the new password field of the request is left empty, the server can generate the new password and return it in the extended response. The password generator used for this purpose is specified using the password-generator property in the password policy configuration.
  • When using the deliver password reset token extended request, as described in the previous section. The password generator used for this purpose is specified using the password-generator property in the extended operation handler configuration.

We may also introduce additional methods for generating passwords in the future.

When generating a one-time password, password reset token, or single-use token, the time frame for the generated password is inherently very limited (typically a matter of minutes). In such cases, it may be acceptable to have the password be relatively simple. For example, something like eight alphanumeric characters should be sufficient, and you may even feel comfortable with something simpler.

When using the password modify extended operation to perform an administrative reset, the time that a generated password may be valid can be limited by the max-password-reset-age property in the password policy configuration, but there is no such limit for a generated password used for a self-change. In these cases, you may want to ensure that the generated password is substantially stronger. By default, the Ping Identity Directory Server will generate a passphrase that is at least 20 characters long and consists of at least four randomly chosen words. This generated password will hopefully be something that is both memorable and easy to type, and will also serve as an example of the kind of strong passwords that you hope users choose for themselves (although it’s still a better idea to use a password manager in most cases, and to let it generate a strong random password).

Note that the password generator is not guaranteed to generate passwords that will satisfy the configured set of password validators, but the server will accept and use the generated password even if it would have been rejected if the user had chosen it for themselves. If you have eccentric password quality requirements, you may wish to customize the password generator to be more likely to create something that the server would accept.

Consider Using Password Retirement

The Ping Identity Directory Server offers a useful feature called password retirement that allows a user to change their password, but continue using their former password as an alternative to their new password for a limited period of time. This is helpful for cases in which the password might be used in multiple places (especially if it’s configured in an application, and even more especially if there might be multiple instances of that application), because it gives the user the opportunity to update all of the things that use the password without them failing immediately as soon as it is changed. I wrote an earlier blog post about password retirement, so I won’t repeat it here, but it’s definitely something to consider, at least for application accounts.

Consider Using Account Status Notifications

Another potentially useful password policy feature that the Ping Identity Directory Server offers is account status notifications. This allows the server to perform custom processing whenever certain events occur that affect user accounts, like a user’s account has been locked as a result of too many failed attempts, an authentication attempt has failed because the account is expired or has been unused for too long, a user’s password is changed or reset by an administrator, etc.

The server includes support for a couple of different types of account status notification handlers out of the box: one that is capable of sending an email message to the end user and/or to administrators when such an event occurs, and one that simply logs a message about the event. Our Server SDK also provides support for creating custom account status notification handlers, so you can integrate with other systems that you might use in your environment. We’re also going to be substantially improving our account status notification handler subsystem in the near future.

One purpose of account status notification handlers is to provide a means of letting end users know about substantial events that affect their accounts. This is useful from a security perspective because if a user sees something that indicates something may be amiss, then they can inquire about it. But account status notification handlers can also serve as a kind of auditing mechanism for these kinds of events so that administrators are made aware of things that might suggest a problem or an attack. For example, if a large number of accounts are being locked out, or if the same account is repeatedly being locked out, then that suggests someone might be trying a password guessing attack.

Alternatives to Arbitrary Password Expiration

I’ve already pointed out on multiple occasions that arbitrary password expiration is not recommended. However, there may be reasons that you want users to change their passwords that might not be simply the result of them having the same password for too long.

For example, if you believe that one or more user accounts might have had their passwords exposed (whether in the clear or in encoded form), then it’s a very good idea to require them to choose a new password. The Ping Identity Directory Server offers a couple of features to help with this:

  • The password policy offers a require-change-by-time configuration property that allows you to require all users associated with that policy to change their passwords by a specified time. Failure to comply will cause their account to be locked until the password is reset by an administrator.
  • The password policy state extended operation provides a mechanism for manipulating the account state for a user or set of users. Using this extended operation, or the manage-account tool that provides a simple command-line interface to the operation, you could put a specified account or set of accounts in a “must change password” state that will require them to change their passwords the next time they authenticate. See my previous blog post about managing password policy state for more information.

If you’re considering password expiration as a means of ensuring that users will be required to authenticate every so often or their accounts will be locked, then we offer a better alternative to that: idle account lockout. If you update the password policy to enable last login time tracking (via the last-login-time-attribute and last-login-time-format configuration properties), then the server will maintain a record of the last time that each user successfully authenticated. If you also set a value for the idle-lockout-interval property, then a user who hasn’t authenticated in at least that length of time will be locked out of their account until the password is reset by an administrator.

If the reason for forcing a password change is because you want to upgrade the password storage scheme that you’re using, you can do that without actually requiring a password change. As noted above in the section on password storage schemes, you can simply make the desired scheme the new default and mark the old scheme as deprecated. The next time a user authenticates with a password encoded with the deprecated scheme, it will be automatically re-encoded using the new default scheme.

If you have other reasons that you were considering password expiration that aren’t covered by these options, then let us know what they are, and we’ll let you know whether there might be a suitable alternative.

Other Related Settings Outside the Password Policy

There are a few other related settings that you might want to consider setting, even though they’re not strictly part of the password policy configuration. They include:

  • You should enable data encryption in the server so that data is not stored in the clear in the database. The best way to do this is to enable encryption when setting up the server, but if you need to do it after the fact, you can use the encryption-settings tool to manage the set of encryption settings definitions that are available in the server, then update the values of the the encrypt-data, encryption-settings-cipher-stream-provider, encrypt-backups-by-default, and encrypt-ldif-exports-by-default global configuration properties. Enabling encryption can be done on the fly, and any subsequent writes will cause the target entry to be encrypted, but you may still want to export the data to LDIF and re-import to ensure that all the existing data gets encrypted.
  • You may wish to configure the server to delay the response to a failed bind operation by a specified length of time (e.g., maybe a second). This can help slow down online password guessing attacks, and it can be used either in conjunction with or as an alternative to locking an account after too many failed attempts. This setting can be configured through the failed-bind-response-delay property in the LDAP connection handler configuration.
  • You may want to configure the userPassword and authPassword attributes as sensitive attributes within the server, and ensure that they can never be retrieved over LDAP, even in encoded form, and even by root users. Check out my earlier blog post about sensitive attributes for more information on this subject.
  • Consider enabling one or more two-factor authentication mechanisms. While it’s still important to ensure that users have strong passwords, two-factor authentication adds yet another layer of protection that an attacker must overcome even if they do happen to learn a user’s password. I also wrote an earlier blog post about this topic.

Sensitive Attributes in the Ping Identity Directory Server

The Ping Identity Directory Server offers a rich access control framework that can be used to help ensure that clients are only given the level of access that they need in the server. It’s a deny-by-default mechanism that means that a user doesn’t get access to something unless there’s a rule that grants it, and our default access control configuration is very restrictive. However, there may be cases in which access control might not be good enough.

For example, let’s say that you want to absolutely ensure that clients cannot retrieve encoded passwords from user entries. To help with this, the server does offer the following rule defined in the out-of-the-box set of global ACIs:

(targetattr="userPassword || authPassword")
(version 3.0;
acl "Prevent clients from retrieving passwords from the server";
deny (read,search,compare)
userdn="ldap:///anyone";)

ACIs that deny access take precedence over those that allow it, so the above rule will ensure that no client that is subject to access control evaluation will be able to retrieve either the userPassword or authPassword attribute, even if another rule explicitly or implicitly tries to grant that access.

However, there is one significant flaw with the above access control rule: it only applies to clients that are subject to access control enforcement. If an authenticated user has the bypass-acl or bypass-read-acl privilege, then the searches they request won’t be subject to access control evaluation, and therefore the entries returned in response to those searches won’t be pared down by the access control handler.

To address this limitation, the server does offer additional forms of protection that can apply even to clients that aren’t subject to access control restrictions. For example, client connection policies can be used to indicate which types of operations are allowed. And sensitive attributes allow you to impose restrictions around access to specified attributes even for privileged users.

A sensitive attribute configuration definition includes the following properties:

  • attribute-type — The names or OIDs of the attribute types to which the sensitive attribute definition applies. At least one attribute type must be specified in each sensitive attribute definition.
  • include-default-sensitive-operational-attributes — Indicates whether the sensitive attribute definition should also apply to certain operational attributes that might include values for the target attribute. At present, this includes the ds-sync-hist attribute, which may hold current or former values for attributes in the entry for the purposes of replication conflict resolution.
  • allow-in-returned-entries — Indicates whether the specified attribute types are allowed to be included in entries that are returned to the client. The value may be one of “allow” (in which the attribute is allowed to be returned, as long as nothing else prevents it), “suppress” (in which the attribute will never be returned, even if something else permits it), or “secure-only” (which behaves like “allow” over secure connections, but “suppress” over insecure ones). If sensitive attribute values are suppressed, then they will be stripped out of entries before they are returned to the client.
  • allow-in-filter — Indicates whether the specified attribute types are allowed to be used in search filters. The value may be one of “allow” (in which the server will permit searches including a sensitive attribute type in the filter), “reject” (in which the server will reject any search request with a filter that includes a sensitive attribute type), or “secure-only” (which behaves like “allow” over secure connections, but “reject” over insecure ones).
  • allow-in-add — Indicates whether the specified attribute types may be included in add requests. The value may be one of “allow”, “reject”, or “secure-only”.
  • allow-in-compare — Indicates whether the specified attribute types may be included in compare requests. The value may be one of “allow”, “reject”, or “secure-only”.
  • allow-in-modify — Indicates whether the specified attribute types may be included in modify requests. The value may be one of “allow”, “reject”, or “secure-only”.

The server includes a few sensitive attribute definitions that are defined but not enforced by default. One of these is the “Sensitive Password Attributes” definition, which can be used to ensure that the userPassword, authPassword, and ds-pwp-retired-password attributes are never returned to clients, cannot be included in search filters or compare requests, and can only be added or modified over secure connections. There are similar definitions for TOTP shared secrets, and also for one-time passwords and other single-use tokens.

To create a new sensitive attribute definition, you can use the dsconfig command-line tool or the web-based administration console. For example, let’s say that you have an attribute named employeeSSN that holds the social security numbers for your employees, and that you wanted to ensure that those values could never be retrieved from the server but could only be targeted by LDAP compare operations, and only over secure connections. You also want to ensure that writes to that attribute are only allowed over secure connections. The following dsconfig command could be used to accomplish that:

dsconfig create-sensitive-attribute \
     --attributeName "Employee Social Security Numbers" \
     --set attribute-type:employeeSSN \
     --set include-default-sensitive-operational-attributes:true \
     --set allow-in-compare:secure-only \
     --set allow-in-add:secure-only \
     --set allow-in-modify:secure-only \
     --set allow-in-returned-entries:suppress \
     --set allow-in-filter:reject

However, merely creating a sensitive attribute definition isn’t sufficient to ensure that it will be enforced. You also need to associate it with one or more client connection policies so that it will be enforced for clients assigned to those policies. The best way to do this is to configure the sensitive attribute in the global configuration so that it will apply across all client connection policies by default. You can do this with a dsconfig command like the following:

dsconfig set-global-configuration-prop \
     --set "sensitive-attribute:Employee Social Security Numbers"

There may be cases in which you want certain clients to be exempt from these restrictions (e.g., if you want to use the Ping Identity Synchronization Server to synchronize the Directory Server with some other data store). If such a need arises, you can create a client connection policy for that application and configure that policy to exclude the sensitive attribute restriction. You can do that with a command like:

dsconfig set-client-connection-policy-prop \
     --policy-name sync-server-policy \
     --set "exclude-global-sensitive-attribute:Employee Social Security Numbers"

Alternately, it is possible to only associate a sensitive attribute definition with a specific set of client connection policies so that it will not be enforced for other policies (using the sensitive-attribute property in the client connection policy configuration), but in most cases, it’s probably better to use the global policy to enable it across all policies by default and only exclude it for a specific set of policies.

Ping Identity Directory Server 7.3.0.0

We have just released version 7.3.0.0 of the Ping Identity Directory Server, and it’s available for download now, along with the companion Directory Proxy Server, Data Synchronization Server, Metrics Engine, and Server SDK products. The release notes contain a blow-by-blow listing of the new features, enhancements, and fixes that it contains, but here are some of the highlights:

  • Added support for Red Hat Enterprise Linux 7.6, CentOS 7.6, Amazon Linux 2, and Windows Server 2019.
  • Added support for Docker 18.09.0 on Ubuntu 18.04 LTS.
  • Enable support for TLSv1.3 by default on JVMs that support it (which should be Java 11 and higher).
  • Added support for server profiles and a new manage-profile tool that can help install and manage the server using the DevOps “infrastructure as code” principle. A server profile can encapsulate the setup commands, configuration changes, Server SDK extensions, additional server root files, and other components of an installation, and it can be used in conjunction with orchestration frameworks to create a new instance with a given profile or to update an existing instance (for example, in a Blue/Green deployment) to apply a new profile.
  • Updated the server to support encrypting the contents of PIN files needed to unlock the certificate key and trust stores. If data encryption is enabled during setup, then the default PIN files will be automatically encrypted. We also updated the command-line tool framework so that files containing passwords can be encrypted, as well as the entire tools.properties file.
  • Added a cipher stream provider that can be used to protect the contents of the encryption settings database with a key from the Amazon Key Management Service.
  • Added a cipher stream provider that can be used to protect the contents of the encryption settings database with a passphrase obtained from a HashiCorp Vault server.
  • Added a pass-through authentication plugin that can make it possible to authenticate accounts with credentials from the PingOne for Customers service.
  • Added support for insignificant configuration archive attributes. Updates to the configuration that only involve one or more of these attributes may not be permanently stored in the configuration archive to prevent it from growing too large over time. For example, if last login time tracking is enabled and does not explicitly exclude root users, then any time a root user authenticated, their entry could be updated with the new last login time, and that update could have previously been stored in the configuration archive. Such updates will no longer be archived by default.
  • Enabled assured replication by default for all add, delete, and modify DN operations. Enabled assured replication by default for all modify operations that alter passwords or key password policy state attributes. With these changes, the server will now delay the response to a matching write operation until it has confirmed that the change has been replicated to all local servers, up to a maximum delay of one second.
  • Updated the server to automatically remove references to obsolete replicas. A replica is obsolete when it has been disabled and all changes from it are older than the replication purge delay.
  • Updated the changelog and replication databases to add a target-database-size configuration property that makes it possible to control purging based on the size of the database in addition to the age of the changes that they contain.
  • Updated the behavior the server exhibits when it encounters a database reference to an attribute type definition that has been removed from the schema. In the 7.2 release, the server could fail when attempting to open a backend with a reference to a nonexistent attribute type. The server will now try to prevent the removal of attribute type definitions that are referenced by one or more backends, but if it does encounter a reference to a no-longer-existent attribute type, it will raise an administrative alert and continue processing the operation under the assumption that the missing attribute type uses a directory string syntax with case-ignore matching.
  • Added an HTTP servlet extension that can be used to retrieve the server’s current availability state. It accepts GET, POST, or HEAD requests sent to a specified endpoint and returns a minimal response whose HTTP status code may be used to determine whether the server considers itself to be AVAILABLE, DEGRADED, or UNAVAILABLE. This may be useful when routing HTTP requests through a load-balancer that can use requests of this type to assess the health of backend servers. It may also be helpful for orchestration frameworks that may wish to destroy and replace instances that become unavailable.
  • Added support for new plugin types that can be used to clean up directory entries for expired or inactive PingFederate persistent sessions.
  • Updated the server to prevent creating virtual attributes that attempt to generate values for the aci or ds-cfg-global-aci attribute types, as access control rules cannot be defined as virtual attributes. Also, prevented creating virtual attributes that attempt to generate values for the member or uniqueMember attribute types, as static group membership cannot be altered using virtual attributes.
  • Added logging for DNS lookups that take longer than a warning threshold (10 seconds by default). DNS resolution timing is also available in a new monitor entry.
  • Updated the topology management framework to make it easier to diagnose connection errors, including adding monitoring information for all failed outbound connections, and raising alarms and alerts when a server fails to connect to a peer server within a configured grace period.
  • Updated the dsreplication tool so that it can work with a node that is currently out of sync with the topology master.
  • Updated the dsreplication tool to allow removing a defunct server even if that server is currently online. Also, added the ability to automatically retry a failed attempt to remove a defunct server.
  • Updated the server to automatically remove a server from the topology when dsreplication disable is used to disable replication for the last non-schema domain.
  • Updated the encrypt-file tool to display a notice recommending the use of the --decompress-input argument when decrypting a file that also appears to be GZIP-compressed.
  • Updated the result code map to make it possible to override the default result code that the server returns when a client tries to perform a password-based bind against an account that does not have a password.
  • Updated the ldapdelete command-line tool to add support for client-side subtree delete, following referrals, deleting entries that match search filters, recording failures in a rejects file, rate limiting, and a variety of additional controls.
  • Updated the HTTP configuration so that the server no longer includes stack traces in generated error pages by default.
  • Updated the “Debug Trace Logger” and “File-Based Trace Logger” log publishers so that they exclude Admin Console activity by default.
  • Updated trace log publishers to support recording events related to access token validation.
  • Updated the file retention recurring task so that it no longer logs an informational message if there are no matching files to delete.
  • Added a correlation-id-response-header property to HTTP servlet extension configuration objects that can be used to set the response header used for correlation IDs. If set for a servlet extension, this value will override the value that would have otherwise been inherited from the HTTP connection handler.
  • Added an indent-ldap-filter tool that can make it easier to visualize the structure and components of a complex search filter.
  • Updated the setup utility to add a --skipHostnameCheck argument that can be used to bypass validation of the provided server hostname.
  • Updated the docs/build-info.txt endpoint to remove version information. That version information is now available in the build-info.txt file in the server root directory.
  • Updated the Directory Proxy Server to change the default load-balancing algorithm that it uses for directing client requests to backend servers. Previously, it always used a fewest operations strategy to send each request to the server with the smallest number of outstanding requests. The new strategy still chooses the server with the fewest outstanding operations for read operations but uses a failover strategy to consistently send write operations to the same server. When combined with the Directory Server’s default use of assured replication, this load-balancing strategy can dramatically reduce the likelihood of replication or uniqueness conflicts while minimizing the performance impact of a purely failover-based approach.
  • Updated the Directory Proxy Server to reduce the default maximum connection age from one hour to ten minutes. This should help avoid problems resulting from firewalls or other networking equipment that silently close connections that have been open for too long.
  • Update the Directory Proxy Server to add an index-priming-idle-listener-timeout property to the entry-balancing request processor configuration. This property specifies the maximum length of time that the server will wait for a response to an attempt to prime the global index before it will give up and retry the attempt.
  • Updated the Directory Proxy Server to reduce the likelihood of lock contention in health checks used to check the status of replication in a backend server.
  • Updated the Data Synchronization Server to support the PingOne for Customers service as a sync source. It was already possible to use PingOne for Customers as a sync destination.
  • Updated the Data Synchronization Server’s support for PingOne for Customers as a sync destination so that it is possible to specify a default population using the name of the population as an alternative to its ID. Population names can also be used in attribute mappings.
  • Updated the Data Synchronization Server to support Apache Kafka as a sync destination. Changes are provided as JSON-formatted representations of the entries before and after the change was applied.
  • Updated the Data Synchronization Server to make it possible to impose a rate limit on a sync pipe so that it does not adversely impact the performance of the destination server.
  • Updated the Data Synchronization Server to make it easier to construct JSON objects to store in the value of a specified attribute in the sync destination.
  • Updated the Data Synchronization Server to support LDAP filters that use extensible-match or approximate-match components. The Data Synchronization Server supports the same set of matching rules as the Directory Server.
  • Updated the Data Synchronization Server to add an attribute-comparison-method configuration property to sync classes. This property can be used to indicate whether to perform syntax-based or byte-for-byte comparisons when identifying what content was updated by a change.
  • Updated the Data Synchronization Server to add base64-encode-value and base64-decode-value properties to direct attribute mappings to facilitate synchronizing binary data.
  • Updated the Delegated Administration configuration. Delegated Admin Resource Types have been removed and replaced by REST Resource Types. Delegated Administrators and Delegated Group Administrators were removed and replaced by Delegated Admin Rights and Delegated Admin Resource Rights. When updating an existing server, older definitions will automatically be converted to their appropriate new versions.
  • Updated the Server SDK to make it easier to read and write data encrypted with keys from the server’s encryption settings database, and for obtaining information about the set of encryption settings definitions available in the server.
  • Updated the Server SDK to make it possible for a pre-parse bind plugin to convert a bind request from simple to SASL, or vice-versa. Added an example SASL mechanism handler that can be used to provide details of a successful or failed bind using attachments to an internal operation. Fixed an issue in the SASL bind result factory that could prevent a matched DN from being included in the response. Added the ability to include additional text in the access log message for an operation without having that text included in the response to the client.
  • Updated the Server SDK to provide support for access token validators for all types of products. It was previously only available for the Data Governance Server.
  • Improved the diagnostic message that the server returns when rejecting a proxied authorization attempt because the target account’s password policy state does not permit that user to authenticate.
  • Fixed an issue in which the server could incorrectly reject an attempt to change a user’s password in a single modify operation that included a delete modification with no values (indicating that all existing password values should be removed) followed by an add modification to supply the desired new password.
  • Fixed an issue that could cause an error when generating an encrypted LDIF export of a data set with a very large number of non-leaf entries. In such cases, the data is written to multiple files that are merged at the end of the process, but a problem could have prevented those files from being properly merged. This did not affect the usability or integrity of the exported data; it merely required the administrator to explicitly specify each of the files in the appropriate order when performing the import.
  • Fixed an issue in the access control handler in which it could incorrectly require the “export” and “import” rights for a modify DN request that includes a newSuperior that matches the DN of the entry’s current parent (which matches the behavior it exhibited for modify DN requests that did not include the newSuperior element). The export and import rights should only be required if the entry is being moved beneath a new parent.
  • Fixed an issue that could allow a modify operation to alter an entry in a way that left it without one or more of the superior object classes that it should have.
  • Fixed an issue in which changes to a dynamic group’s memberURL attribute sometimes did not take effect until after a restart.
  • Fixed an issue in which the server may not enter lockdown mode if it is missing replication changes that are no longer available in the topology, and if it has been restarted without addressing that problem.
  • Fixed an issue that could interfere with the operation of the stop-server.bat command on Windows systems configured with a locale that uses a comma instead of a period as the decimal separator.
  • Fixed issues that could interfere with the parsability of the periodic stats logger output when the server is run on systems configured with a locale that uses a comma instead of a period as the decimal separator.
  • Fixed an issue in which certain component initialization failure messages were written with a log level that was too low to prevent them from being recorded in the server’s error log by default, making it difficult to diagnose problems with those components.
  • Fixed the ordering of the consent-service-cfg.dsconfig batch commands so that bearer token authentication is enabled after the unprivileged consent on which it depends.
  • Fixed an issue that could cause a negative etime to appear in the access log when using assured replication.
  • Fixed an issue that could prevent dsreplication disable from removing replica IDs from the topology when one or more replication domains are disabled.
  • Fixed an issue that could cause the server to report an error when enabling or disabling a backend if there were any disabled notification managers defined in the server.
  • Fixed an issue in which the Directory Proxy Server could reject add attempts if all servers in an entry-balancing backend set had a health check state of DEGRADED or UNAVAILABLE.
  • Fixed an issue in which backups of the encryption settings database could be encrypted with a key from the encryption settings database.
  • Fixed an issue that could interfere with the ability to assign privileges via the mirror virtual attribute if the values to mirror were contained in another entry and were not accessible to unauthenticated clients.
  • Fixed an issue that could interfere with the ability to delete an entry containing uncached content if the LDAP changelog was enabled and configured to record changes in reversible form.
  • Fixed an issue that could prevent JMX clients from establishing SSL-encrypted connections.
  • Fixed an issue that could prevent HTTP-based connections from being associated with a client connection policy.
  • Fixed an issue in which a SCIM client was not permitted to add a member to a groupOfNames or groupOfEntries group.
  • Fixed an issue in which the startIndex value for a SCIM request could be incorrect if the server was configured with more than one base DN in the scim-resources.xml file.
  • Fixed an issue in which the config-diff tool may not identify differences that result from changing the order of values in an order-dependent property.
  • Fixed an issue in the Data Synchronization Server in which operational attributes may not be requested from an LDAP sync source if the LDAP filter was a nested filter.
  • Fixed an issue in the Data Synchronization Server in which a resync attempt could fail against Active Directory or PingOne for Customers when run with multiple passes.
  • Fixed an issue with the client-side validation properties that the haystack password validator would return in a get password quality requirements extended response. The values were human-readable descriptions of the validation properties rather than machine-parsable values.
  • Fixed an issue that could cause the server to encounter an internal error when processing a set subtree accessibility extended operation against an empty backend.
  • Fixed an issue in which a cryptographic error could interfere with inter-server authentication for sharing mirrored configuration data.
  • Fixed an installer issue in which the Admin Console’s trust store type could be set incorrectly if it was different from the key store type.
  • Disabled the fingerprint and subject attribute to user attribute certificate mappers by default for new installations (upgrades of existing installations will not be affected). These certificate mappers are rarely used and require the server to be configured with additional indexing before they can be used, and the lack of those indexes caused internal errors to be raised in the server on startup.

Ping Identity Directory Server versions 7.2.1.1 and 7.0.1.3

Ping Identity Directory Server versions 7.2.1.1 and 7.0.1.3 have been released. These are security updates, and customers running 7.x versions are strongly encouraged to upgrade.

The most important update included in these releases is a fix for a critical security issue introduced in the 7.0.0.0 version that could cause certain passwords to be recorded in the clear on the server filesystem. There are two instances in which this could have occurred:

  • When creating an encrypted backup of the alarms, alerts, configuration, encryption settings, schema, tasks, or trust store backends, the backup descriptor was supposed to include the identifier of the encryption settings definition that was used to protect the contents of the backup. Instead of this identifier, the server would incorrectly include the password that backed that encryption settings definition. This issue did not affect backups of local DB backends (like userRoot), the LDAP-accessible changelog, or the replication database.

  • The server maintains a tool invocation log (logs/tools/tool-invocation.log), which keeps track of certain commands that are run on the system, especially those that may be used to alter the server configuration or data. Among other things, this tool includes the name of the tool and the arguments used to run it. Sensitive arguments, like those used to provide passwords, should automatically be redacted. However, if the tool is run with an argument that provides the path to a file containing a password, a bug could have caused the tool invocation log to record the contents of the first line of that file (which usually contains the password itself) rather than the path to that file. The following command-line tools were affected by this issue:

    • backup
    • create-initial-config
    • create-initial-proxy-config
    • dsreplication
    • enter-lockdown-mode
    • export-ldif
    • import-ldif
    • ldappasswordmodify
    • leave-lockdown-mode
    • manage-tasks
    • manage-topology
    • migrate-ldap-schema
    • parallel-update
    • prepare-endpoint-server
    • prepare-external-server
    • realtime-sync
    • rebuild-index
    • re-encode-entries
    • reload-http-connection-handler-certificates
    • reload-index
    • remove-defunct-server
    • restore
    • rotate-log
    • stop-server

Other tools were not affected by this second issue. Also note that this issue only involved passwords provided in files that were directly referenced as arguments on the command line. Passwords that were provided directly on the command line, and passwords that were automatically included because of their presence in a tools.properties file, were properly redacted. Because of the nature of this issue, regular user passwords are not likely to have been exposed, but the passwords of administrators that may have run commands on the server system could have been recorded.

In both issues above, the passwords were written to a file on the server filesystem with permissions that made them only accessible to the account used to run the server. Other accounts on the system should not have been able to read the contents of those files. Nevertheless, if you believe that any passwords may have been compromised, we recommend taking the following steps to mitigate the risk:

  1. Update the server to a version that includes the fix for this issue. If you’re running version a 7.2 version, then you should upgrade to the 7.2.1.1 release. If you’re running a 7.0 version, then you should upgrade to either version 7.2.1.1 or version 7.0.1.3.
  2. If you believe that any user passwords may have been exposed in the logs/tools/tool-invocation.log file, then change the passwords for those users and sanitize or delete that log file.
  3. If you believe that an encryption settings definition password may have been exposed in a backup descriptor, then create a new encryption settings definition, set it as the preferred definition for all subsequent encryption operations, export your data to LDIF, and re-import the data so that it is re-encrypted with the new definition. Create new backups, and destroy old backups with the compromised password.

In addition to fixing the bugs that led to the potential exposure of these passwords, we have added additional automated tests to help ensure that other problems like this do not occur in the future.

Other Changes Included in the 7.2.1.1 Release

The following additional fixes have been included in the 7.2.1.1 release:

  • Updated the behavior that the server exhibits if an attribute type is removed from the schema while that attribute type is still referenced by one or more server backends. In earlier releases, the server could fail to open a backend that referenced an attribute type that is no longer defined in the schema. The server will now permit the backend to be opened, but will generate an alert about any missing attribute type definitions on startup, and will also generate an alert on any access to an entry that contains a reference to a missing attribute type. The server will also attempt to prevent the removal of an attribute type that is still referenced by any of the backends.
  • Fixed an issue in which the stop-server.bat batch file may not function properly on Windows systems with a locale that uses a character other than a period as a decimal separator.
  • Fixed an issue in which the periodic stats logger output could have been difficult to parse on systems with a locale that uses a character other than the period as a decimal separator.
  • Fixed an issue that prevented creating a constructed virtual attribute for an attribute that was marked SINGLE-VALUE in the server schema.
  • Fixed an issue in which backups of the server’s encryption settings database could have been (automatically or explicitly) encrypted with a key from the encryption settings database.

Other Changes Included in the 7.0.1.3 Release

The following additional fixes have been included in the 7.0.1.3 release:

  • Added debug logging for DNS lookups that take longer than a configured length of time (10 seconds by default). A new “DNS Resolution” monitor entry is available to provide information about DNS lookups performed by the server.
  • Fixed an issue in which SCIM searches could have an incorrect startIndex value if the scim-resources.xml file was configured with multiple base DNs.
  • Fixed an issue that could cause an error while performing an encrypted LDIF export of a directory with a very large number of non-leaf entries. In such cases, the LDIF export will be split into multiple files, but the attempt to merge those files at the end of processing would fail. This error would not result in any data loss or exposure, and the exported data could still be imported by either providing all of the files to the import-ldif utility with separate –ldifFile arguments or by manually merging the files.

Ping Identity Directory Server 7.2.1.0

We have just released the Ping Identity Directory Server version 7.2.1.0, available for download at https://www.pingidentity.com/en/resources/downloads/pingdirectory-downloads.html. This is primarily a bugfix release, but it does offer a couple of significant new features. The release notes provide a pretty comprehensive overview of the changes, but the most significant updates are:

  • Fixed an issue that could cause an error during an LDIF export of a data set with a large number of non-leaf entries. In such cases, the LDIF data may be split into multiple files to make the LDIF process faster. If the data is split into multiple files, and if the LDIF export was encrypted, then an error may have prevented merging those files at the end of the export process. The exported data was still valid and could still be successfully imported, but with additional effort required.
  • Updated the LDAP pass-through authentication plugin to add an option to construct the DN to use to authenticate to the remote server from information in the local entry. Further, it is now possible to authenticate to the remote server with a bind DN value that may not be a valid LDAP distinguished name (for example, using the user principal name when passing through authentication to an Active Directory server).
  • Updated the LDAP pass-through authentication to add an included-local-entry-base-dn configuration property that makes it easier to identify which local users for which pass-through authentication may be attempted. If pass-through authentication is enabled, it will no longer be attempted by default for root users or topology administrators.
  • Fixed a number of issues in the LDAP pass-through authentication plugin. It will now use separate connections for search and bind operations. It will now make better use of multiple servers for improved availability, and can re-try a failed operation when only a single server is configured. Improved the troubleshooting information that is available when a problem is encountered during pass-through authentication processing.
  • Fixed an issue that could cause entryUUID mismatches across servers if the server is configured to automatically use entryUUD as the naming attribute for entries matching a given set of criteria.
  • Updated the server to ensure that information about missing replication changes persistent across restarts. If the server has been offline for longer than the replication purge delay, then replication will be unable to automatically bring that server back in sync with the other servers in the topology. However, if the server had been restarted after that problem was identified, the record of the missing changes could be inadvertently cleared.
  • Updated the dsreplication tool to allow enabling replication on a node whose topology information is out of sync with the topology master.
  • Updated the topology manager to make it easier to diagnose connection errors between servers in the topology.
  • Added logging for DNS lookups that take longer than expected to complete (10 seconds by default). This can make it easier to identify problems with DNS issues cause connectivity problems or slowness.
  • The delegated administration configuration has changed significantly. When updating an existing installation, the update tool will automatically convert the old configuration model to the new one.
  • The Data Synchronization Server has been updated to support bidirectional synchronization with the PingOne for Customers hosted directory service. The 7.2.0.0 release added support for the PingOne for Customers service as a sync destination. With the 7.2.1.0 release, it is now also possible to use PingOne for Customers as a sync source.

Password Retirement in the Ping Identity Directory Server

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)

The Get Password Policy State Issues Control in the Ping Identity Directory Server

In the Ping Identity Directory Server, we’re very serious when it comes to security. We make it easy to encrypt all your data, including the database contents (and the in-memory database cache), network communication, backups, LDIF exports, and even log files. We’ve got lots of password policy features, like strong password encoding, many password validation options, and ways to help thwart password guessing attempts. We offer several two-factor authentication options. We have a powerful access control subsystem that is augmented with additional features like sensitive attributes and privileges. We have lots of monitoring and alerting features so that you can be notified of any problems as soon as (or, in many cases, before) they arise so that your service remains available. Security was a key focus back when I started writing OpenDS (which is the ancestor of the Ping Identity Directory Server), and it’s still a key focus today.

One small aspect of this focus on security is that, by default, we don’t divulge any information about the reason for a failed authentication attempt. Maybe the account doesn’t exist, or maybe it’s locked or administratively disabled. Maybe the password was wrong, or maybe it’s expired. Maybe the user isn’t allowed to authenticate from that client system. In all of these cases, and for other types of authentication failures, the server will just return a bind result with a result code of invalidCredentials and no diagnostic message. The server will include the exact reason for the authentication failure in the audit log so that it’s available for administrators, but we won’t return it to the client so that a malicious user can’t use that to better craft their attack.

Now, if you don’t care about this and want the server to just go ahead and provide the message to the client, then you can do that with the following configuration change:

dsconfig set-global-configuration-prop --set return-bind-error-messages:true

However, that may not be the best option because it applies equally to all authentication requests for all clients, and because the output is human-readable but not very machine parseable. It’s not easy for a client to programmatically determine what the reason for the failure is. For that, your best option is the get password policy state issues control.

The get password policy state issues control indicates that you want the server to return information about the nature of the authentication failure, and details of the user’s password policy state that might interfere with authentication either now or in the future. This information is easy to consume programmatically, but it also contains user-friendly representations of those conditions as well. We intend for this control to be used by applications that authenticate users, and that can decide what information they want to make available to the end user.

Restrictions Around the Control’s Use

As previously mentioned, we might not always want to divulge the reason for a failed authentication attempt to the end user. As such, if we allowed just anyone to use this control, then that would get thrown out the window since a malicious client could just always include that control and get some helpful information in the response. So we don’t do that. Instead, this control will only be permitted if all of the following conditions are met:

  • The server’s access control handler must allow the get password policy state issues request control to be included in bind requests. This control is allowed in bind request by default, but you can disable it if you want to.
  • A bind request that includes the get password policy state issues request control must be received on a connection that is already authenticated as a user who has the permit-get-password-policy-state-issues privilege.

Since we intend this feature to be used by applications that authenticate users, we expect that any application that is to be authorized to use it will have an account with the necessary privilege. And since the get password policy state issues control is a proprietary feature, we expect that any application that knows how to use it can also easily include the retain identity request control in those same bind requests.

The Get Password Policy State Issues Request Control

The get password policy state issues request control is very simple: it’s got a request OID of 1.3.6.1.4.1.30221.2.5.46 and no value. This control is only intended to be included in bind requests, and it’s really just asking the server to include the corresponding response control in the bind result message.

It’s easy enough to use this request control any LDAP API, but if you’re using the UnboundID LDAP SDK for Java, then we provide the GetPasswordPolicyStateIssuesRequestControl class to make it even easier.

The Get Password Policy State Issues Response Control

The get password policy state issues response control is more complicated than the request control. It has an OID of 1.3.6.1.4.1.30221.2.5.47 and a value with the following ASN.1 encoding:

GetPasswordPolicyStateIssuesResponse ::= SEQUENCE {
     notices               [0] SEQUENCE OF SEQUENCE {
          type        INTEGER,
          name        OCTET STRING,
          message     OCTET STRING OPTIONAL } OPTIONAL,
     warnings              [1] SEQUENCE OF SEQUENCE {
          type        INTEGER,
          name        OCTET STRING,
          message     OCTET STRING OPTIONAL } OPTIONAL,
     errors                [2] SEQUENCE OF SEQUENCE {
          type        INTEGER,
          name        OCTET STRING,
          message     OCTET STRING OPTIONAL } OPTIONAL,
     authFailureReason     [3] SEQUENCE {
          type        INTEGER,
          name        OCTET STRING,
          message     OCTET STRING OPTIONAL } OPTIONAL,
     ... }

If you’re using the UnboundID LDAP SDK for Java, then you can use the GetPasswordPolicyStateIssuesResponseControl class to do all the heavy lifting for you. If you’re using some other API, then you’ll probably have to decode the value for yourself.

There are four basic components to the get password policy state issues response control:

  • A set of error conditions in the user’s password policy state that will either prevent that user from authenticating, or that will prevent them from using their account until they take some action. In the UnboundID LDAP SDK for Java, this we offer the PasswordPolicyStateAccountUsabilityError class to make it easier to interpret these errors. Possible password policy state error conditions include:

    • The account is administratively disabled.
    • The account has expired.
    • The account is not yet active.
    • The account is permanently locked (or at least until an administrator unlocks it) after too many failed authentication attempts.
    • The account is temporarily locked after too many failed authentication attempts.
    • The account is locked because it’s been idle for too long.
    • The account is locked because the password was administratively reset, but the user didn’t choose a new password quickly enough.
    • The password is expired.
    • The password is expired, but there are one or more grace logins remaining. Authenticating with a grace login will only permit them to bind for the purpose of changing the password.
    • The password has been administratively reset and must be changed before the user will be allowed to do anything else.
    • The password policy was configured so that all users governed by that policy must change their passwords by a specified time, but the user attempting to authenticate failed to do so.
  • A set of warning conditions in the user’s password policy state that won’t immediately impact their ability to use their account, but that may impact their ability to use the account in the near future unless they take some action. In the UnboundID LDAP SDK for Java, we offer the PasswordPolicyStateAccountUsabilityWarning class to make it easier to interpret these warnings. Possible password policy state warning conditions include:

    • The account will expire in the near future.
    • The password will expire in the near future.
    • The account has been idle for too long and will be locked unless they successfully authenticate in the near future.
    • The account has outstanding authentication failures and may be locked if there are too many more failed attempts.
    • The password policy was configured so that all users governed by that policy must change their password by a specified time, but the user attempting to authenticate has not yet done so.
  • A set of notice conditions that additional information about the user’s password policy state that may be helpful for applications or the end user to know. The UnboundID LDAP SDK for Java provides the PasswordPolicyStateAccountUsabilityNotice class to make it easier to interpret these notices. Possible password policy state notices include:

    • A minimum password age has been configured in the password policy governing the user, and it has been less than that length of time since the user last changed their password. The user will not be permitted to change their password again until the minimum age period has elapsed.
    • The account does not have a static password, so it will not be allowed to authenticate using any password-based authentication mechanism.
    • The account has an outstanding delivered one-time password that has not yet been consumed and is not yet expired.
    • The account has an outstanding password reset token that has not yet been consumed and is not yet expired.
    • The account has an outstanding retired password that has not yet expired and may still be used to authenticate.
  • An authentication failure reason, which provides information about the reason that the bind attempt failed. The UnboundID LDAP SDK for Java offers the AuthenticationFailureReason class to help make it easier to use this information. Possible authentication failure reasons include:

    • The server could not find the account for the user that is trying to authenticate (e.g., the user doesn’t exist, or the authentication ID does not uniquely identify the user).
    • The password or other provided credentials were not correct.
    • There was something wrong with the SASL credentials provided by the client (e.g., they were malformed or out of sequence).
    • The account isn’t configured to support the requested authentication type (e.g., they attempted a password-based bind, but the user doesn’t have a password).
    • The account is in an unusable state. The password policy error conditions should encapsulate the reasons that the account is not usable.
    • The server is configured to require the client to authenticate securely, but the authentication attempt was not secure.
    • The account is not permitted to authenticate in the requested manner (e.g., from the client address or using the attempted authentication type).
    • The bind request was rejected by the server’s access control handle.
    • The authentication attempt failed because a problem was encountered while processing one of the controls included in the bind request.
    • The server is currently in lockdown mode and will only permit a limited set of users to authenticate.
    • The server could not assign a client connection policy to the account.
    • The authentication attempt used a SASL mechanism that was implemented in a third-party extension, and that extension encountered an error while processing the bind request.
    • The server encountered an internal error while processing the bind request.

Each password policy state error, warning, and notice, as well as the authentication failure reason, is identified by a name and a numeric type, and also includes a human-readable message suitable for displaying to the user if you decide that it is appropriate.

The Password Policy State Extended Operation

Although it’s not the focus of this blog post (maybe I’ll write another one about it in the future), I should also point out that you can also use the password policy state extended operation to obtain the list of usability errors, warnings, and notices for a user, along with a heck of a lot more information about the state of the account. You can also use it to alter the state if desired. Since it’s an extended operation, you can’t use it in the course of attempting a bind to get the authentication failure reason. However, you could use it in conjunction with the get password policy state issues control if you feel like you need additional state information about the user’s account state after parsing the information in the get password policy state issues response control.

An Example Using the UnboundID LDAP SDK for Java

I’ve written a simple program that demonstrates the use of the get password policy state issues control to obtain the authentication failure reason and password policy state issues for a specified user. You can find that example at https://github.com/dirmgr/blog-example-source-code/tree/master/password-policy-state-issues.

The Retain Identity Request Control in the Ping Identity Directory Server

Many LDAP-enabled applications use a directory server to authenticate users, which often consists of a search to find the user’s entry based on the provided login ID, followed by a bind to verify the provided credentials. Some of these applications may purely use the directory server for authentication, while others may then go ahead and perform additional operations on behalf of the logged-in users.

If the application is well designed, then it will probably maintain a pool of connections that it can repeatedly reuse rather than establishing a new connection each time it needs to perform an operation in the server. Usually, the search to find a user is performed on a connection bound as an account created for that application. And if the application performs operations on behalf of the authenticated users, then it often does so while authenticated under that user same application account, using something like the proxied authorization request control to request that the server process those operations under the appropriate user’s authority.

The problem, though, is that performing a bind operation changes the authentication identity of the connection on which it is processed. If the bind is successful, then subsequent operations on that connection will be processed under the authority of the user identified by that bind request. If the bind fails, then the connection becomes unauthenticated, so subsequent requests are processed anonymously. There are a couple of common ways to work around this problem:

  • It can maintain two different connection pools: one to use just for bind operations, and the other for all other types of operations.
  • After attempting a bind to verify a user’s credentials (whether successful or not), it can re-authenticate as the application account.

In the Ping Identity Directory Server, we offer a third option: the bind request can include the retain identity request control. This control tells the server that it should perform all of the normal processing associated with the bind (verify the user’s credentials, update any password policy state information for that user, etc.), but not change the authentication identity of the underlying connection. Regardless of whether the bind succeeds or fails, the connection will end up with the same authentication/authorization identity that it had before the bind was attempted. This allows you to use just a single connection pool that stays authenticated as the application’s account, while still being able to verify credentials without fear of interfering with access control evaluation for operations following those binds.

The retain identity request control is very easy to use. If you’re using the UnboundID LDAP SDK for Java, you can just use the RetainIdentityRequestControl class, and the Javadoc includes an example demonstrating its use. If you’re using some other API, then you just need to specify an OID of “1.3.6.1.4.1.30221.2.5.3”, and you don’t need to provide a value. We recommend making the control critical so that the bind attempt will fail if the server doesn’t support it (although we added support for this control back in 2008 when it was still the UnboundID Directory Server, so it’s been around for more than a decade).

I’m not aware of any other directory server that supports the retain identity request control (aside from the LDAP SDK’s in-memory directory server), but it’s very simple and very useful, so if you’re using some other type of server you might inquire about whether they’d implement it or something similar. Of course, you could also switch to the Ping Identity Directory Server and get support for this and lots of other helpful features that other servers don’t provide.

Programmatically Retrieving Password Quality Requirements in the Ping Identity Directory Server

When changing a user’s password, most LDAP directory servers provide some way to determine whether the new password is acceptable. For example, when allowing a user to choose a new password, you might want to ensure that new password has at least some minimum number of characters, that it’s not found in a dictionary of commonly used passwords, and that it’s not too similar to the user’s current password.

It’s important to be able to tell the user what the requirements are so that they don’t keep trying things that the server will reject. And you might also want to provide some kind of password strength meter or indicator of acceptability to let them visually see how good their password is. But you don’t want to do this with hard-coded logic in the client because different sets of users might have different password quality requirements, and because the server configuration can change, so even the requirements for a given user may change over time. What you really want is a way to programmatically determine what requirements the server will impose.

Fortunately, the Ping Identity Directory Server provides a “get password quality requirements” extended operation that can provide this information. We also have a “password validation details” control that you can use when changing a password to request information about how well the proposed password satisfies those requirements. These features were added in the 5.2.0.0 release back in 2015, so they’ve been around for several years. The UnboundID LDAP SDK for Java makes it easy to use them in Java clients, but you can make use of them in other languages if you’re willing to do your own encoding and decoding.

The Get Password Quality Requirements Extended Request

The get password quality requirements extended request allows a client to ask the server what requirements it will impose when setting a user’s password. It’s best to use before prompting for a new password so that you can display the requirements to them and potentially provide client-side feedback as to whether the proposed password is acceptable.

Since the server can enforce different requirements under different conditions, you need to tell it the context for the new password. Those contexts include:

  • Adding a new entry that includes a password. You can either indicate that the new entry will use the server’s default password policy, or that it will use a specified policy.
  • A user changing their own password. It doesn’t matter whether the password change is done by a standard LDAP modify operation that targets the password attribute or with the password modify extended operation; the requirements for a self change will be the same in either case.
  • An administrator resetting another user’s password. Again, it doesn’t matter whether it’s a regular LDAP modify or a password modify extended operation. You just need to indicate which user’s password is being reset so the server can determine which requirements will be enforced.

The UnboundID LDAP SDK for Java provides support for this request through the GetPasswordQualityRequirementsExtendedRequest class, but if you need to implement support for it in some other API, it has an OID of 1.3.6.1.4.1.30221.2.6.43 and a value with the following ASN.1 encoding:

GetPasswordQualityRequirementsRequestValue ::= SEQUENCE {
     target     CHOICE {
          addWithDefaultPasswordPolicy           [0] NULL,
          addWithSpecifiedPasswordPolicy         [1] LDAPDN,
          selfChangeForAuthorizationIdentity     [2] NULL,
          selfChangeForSpecifiedUser             [3] LDAPDN,
          administrativeResetForUser             [4] LDAPDN,
          ... },
     ... }

The Get Password Quality Requirements Extended Response

The server uses the get password quality requirements extended response to tell the client what the requirements are for the target user in the indicated context. Each validator configured in a password policy can return its own password quality requirement structure, which includes the following components:

  • A human-readable description that describes the purpose of the validator in a user-friendly form. For example, “The password must contain at least 8 characters”.
  • A validation type that identifies the type of validator for client-side evaluation. For example, “length”.
  • A set of name-value pairs that provide information about the configuration of that password validator. For example, a name of “min-password-length” and a value of “8”.

A list of the validation types and corresponding properties for all the password validators included with the server is provided later in this post.

In addition to those requirements, it may provide additional information about the password change, including:

  • Whether the user will be required to provide their current password when choosing a new password. This is only applicable for a self change.
  • Whether the user will be required to choose a new password the first time they authenticate after the new password is set. This is only applicable for an add or an administrative reset, and it’s based on the password policy’s force-change-on-add or force-change-on-reset configuration.
  • The length of time that the newly set password should be considered valid. If the user will be required to change their password on the next authentication, then this will be the length of time they have before that temporary password becomes invalid. Otherwise, it specifies the length of time until the password expires.

The UnboundID LDAP SDK for Java provides support for the extended result through the GetPasswordQualityRequirementsExtendedResult class and the related PasswordQualityRequirement class. In case you need to implement support for this extended response in some other API, it has an OID of 1.3.6.1.4.1.30221.2.6.44 and a value with the following ASN.1 encoding:

GetPasswordQualityRequirementsResultValue ::= SEQUENCE {
     requirements                SEQUENCE OF PasswordQualityRequirement,
     currentPasswordRequired     [0] BOOLEAN OPTIONAL,
     mustChangePassword          [1] BOOLEAN OPTIONAL,
     secondsUntilExpiration      [2] INTEGER OPTIONAL,
     ... }

PasswordQualityRequirement ::= SEQUENCE {
     description                  OCTET STRING,
     clientSideValidationInfo     [0] SEQUENCE {
          validationType     OCTET STRING,
          properties         [0] SET OF SEQUENCE {
               name      OCTET STRING,
               value     OCTET STRING } OPTIONAL } OPTIONAL }

The Password Validation Details Request Control

As noted above, you should use the get password validation requirements extended operation before prompting a user for a new password so that they know what the requirements are in advance. But, if the server rejects the proposed password, it’s useful for the client to be able to tell exactly why it was rejected. The Ping Identity Directory Server will include helpful information in the diagnostic message, but that’s just a blob of text. You might want something more parseable so that you can provide the user with the pertinent information with better formatting. And for that, we provide the password validation details request control.

This control can be included in an add request that includes a password, a modify request that attempts to alter a password, or a password modify extended request. It tells the server that the client would like a response control (outlined below) that includes information about each of the requirements for the new password and whether that requirement was satisfied.

The UnboundID LDAP SDK for Java provides support for this request control in the PasswordValidationDetailsRequestControl class, but if you want to use it in another API, then all you need to do is to create a request control with an OID of 1.3.6.1.4.1.30221.2.5.40. The criticality can be either true or false (but it’s probably better to be false so that the server won’t reject the request if that control is not available for some reason), and it does not take a value.

The Password Validation Details Response Control

When the server processes an add, modify, or password modify request that included the password validation request control, the response that the server returns may include a corresponding password validation details response control with information about how well the proposed password satisfies each of the requirements. If present, the response control will include the following components:

  • One of the following:

    • Information about each of the requirements for the proposed password and whether that requirement was satisfied.
    • A flag that indicates that the request didn’t try to alter a password.
    • A flag that indicates that the request tried to set multiple passwords.
    • A flag that indicates that the request didn’t get to the point of trying to validate the password because some other problem was encountered first.
  • An optional flag that indicates whether the server requires the user to provide their current password when choosing a new password, but that the current password was not given. This is only applicable for self changes, and not for adds or administrative resets.
  • An optional flag that indicates whether the user will be required to change their password the next time they authenticate. This is applicable for adds and administrative resets, but not for self changes.
  • An optional value that specifies the length of time that the new password will be considered valid. If it was an add or an administrative reset and the user will be required to choose a new password the next time they authenticate, then this is the length of time that they have to do that. Otherwise, it will be the length of time until the new password expires.

The UnboundID LDAP SDK for Java provides support for this response control through the PasswordValidationDetailsResponseControl class, with the PasswordQualityRequirementValidationResult class providing information about whether each of the requirements was satisfied. If you need to implement support for this control in some other API, then it has a response OID of 1.3.6.1.4.1.30221.2.5.41 and a value with the following ASN.1 encoding:

PasswordValidationDetailsResponse ::= SEQUENCE {
     validationResult           CHOICE {
          validationDetails             [0] SEQUENCE OF
               PasswordQualityRequirementValidationResult,
          noPasswordProvided            [1] NULL,
          multiplePasswordsProvided     [2] NULL,
          noValidationAttempted         [3] NULL,
          ... },
     missingCurrentPassword     [3] BOOLEAN DEFAULT FALSE,
     mustChangePassword         [4] BOOLEAN DEFAULT FALSE,
     secondsUntilExpiration     [5] INTEGER OPTIONAL,
     ... }

PasswordQualityRequirementValidationResult ::= SEQUENCE {
     passwordRequirement      PasswordQualityRequirement,
     requirementSatisfied     BOOLEAN,
     additionalInfo           [0] OCTET STRING OPTIONAL }

Validation Types and Properties for Available Password Validators

The information included in the get password validation details extended response is enough for the client to display a user-friendly list of the requirements that will be enforced for a new password. However, it also includes information that can be used for some client-side evaluation of how well a proposed password satisfies those requirements. This can help the client tell the user when the password isn’t good enough without having to send the request to the server, or possibly provide feedback about the strength or acceptability of the new password while they’re still typing it. This is possible because of the validation type and properties components of each password quality requirement.

Of course, you can really only take advantage of this feature if you know what the possible validation type and properties are for each of the password validators. This section provides that information for each of the types of validators included with the Ping Identity Directory Server (or, at least the ones available at the time of this writing; we may add more in the future).

Also note that for some types of password validators, you may not be able to perform client-side validation. For example, if the server is configured to reject any proposed password that it finds in a dictionary of commonly used passwords, the client can’t make that determination because it doesn’t have access to that dictionary. In such cases, it’s still possible to display the requirement to the user so that they’re aware of it in advance, and it may still be possible to perform client-side validation for other types of requirements, so there’s still benefit to using this information.

The Attribute Value Password Validator

The attribute value password validator can be used to prevent the proposed password from matching the value of another attribute in a user’s entry. You can specify which attributes to check, or it can check all user attributes in the entry (which is the default). It can be configured to reject the case in which the proposed password exactly matches a value for another attribute, but it can also be configured to reject based on substring matches (for example, if an attribute value is a substring of the proposed password, or if the proposed password is a substring of an attribute value). You can also optionally test the proposed password in reversed order.

You can perform client-side checking for this password validator if you have a copy of the target user’s entry. The validation type is “attribute-value”, and it offers the following validation properties:

  • match-attribute-{counter} — The name of an attribute whose values will be checked against the proposed password. The counter value starts at 1 and will increase sequentially for each additional attribute to be checked. For example, if the validator is configured to check the proposed password against the givenName, sn, mail, and telephoneNumber attributes, you would have a match-attribute-1 property with a value of givenName, a match-attribute-2 property with a value of sn, a match-attribute-3 property with a value of mail, and a match-attribute-4 property with a value of telephoneNumber.
  • test-password-substring-of-attribute-value — Indicates whether to check to see if the proposed password matches a substring of any of the target attributes. If this property has a value of true, then this substring check will be performed. If the property has a value of false, or if it is absent, then the substring check will not be performed.
  • test-attribute-value-substring-of-password — Indicates whether to check to see if any of the target attributes matches a substring of the proposed password. If this property has a value of true, then this substring check will be performed. If the property has a value of false, or if it is absent, then the substring check will not be performed.
  • test-reversed-password — Indicates whether to check the proposed password with the order of the characters reversed in addition to the order in which they were provided. If this property has a value of true, then both the forward and reversed password will be checked. If the property has a value of false, or if it is absent, then the password will only be checked in forward order.

The Character Set Password Validator

The character set password validator can be used to ensure that passwords have a minimum number of characters from each of a specified collection of character sets. For example, you could define one set with all of the lowercase letters, one with all the uppercase letters, one with all the numeric digits, and one with a set of symbols, and require that a password have at least one character from each of those sets.

You can perform client-side checking for this password validator just using the proposed password itself. The validation type is “character-set”, and it has the following validation properties:

  • set-{counter}-characters — A set of characters for which a minimum count will be enforced. The counter value starts at 1 and will increase sequentially for each additional set of characters that is defined. For example, if you had sets of lowercase letters, uppercase letters, and numbers, then you could have a set-1-characters property with a value of abcdefghijklmnopqrstuvwxyz, a set-2-characters property with a value of ABCDEFGHIJKLMNOPQRSTUVWXYZ, and a set-3-characters property with a value of 0123456789.
  • set-{counter}-min-count — The minimum number of characters that must be present from the character set identified with the corresponding counter value (so the property with a name of set-1-min-count specifies the minimum number of characters from the set-1-characters set). This value will be an integer whose value is greater than or equal to zero (with a value of zero indicating that characters from that set are allowed, but not required; this is really only applicable if allow-unclassified-characters is false).
  • allow-unclassified-characters — Indicates whether passwords should be allowed to have any characters that are not defined in any of the character sets. If this property has a value of true, then passwords will be allowed to have unclassified characters as long as they meet the minimum number of required characters from all of the specified character sets. If this property has a value of false, then passwords will only be permitted to include characters from the given character sets.

The Dictionary Password Validator

The dictionary password validator is used to ensure that users can’t choose passwords that are found in a specified dictionary file. The Ping Identity Directory Server comes with two such files: one that contains a list of over 400,000 English words, and one that contains over 500,000 of the most commonly used passwords. You can also provide your own dictionary files.

Unless the client has a copy of the same dictionary file that the server is using, then it’s not really possible for it to perform client-side validation for this validator. Nevertheless, the validator does have a validation type of “dictionary” and the following validation properties:

  • dictionary-file — The name (without path information) of the dictionary file that the password validator is using.
  • case-sensitive-validation — Indicates whether the validation should be case sensitive or insensitive. If the value is true, then a proposed password will be rejected only if it is found with exactly the same capitalization in the dictionary file. If it is false, then differences in capitalization will be ignored.
  • test-reversed-password — Indicates whether the validation should check the proposed password with the characters in reversed order as well as in the order the client provided them. If the value is true, then both the forward and reversed password will be checked. If the value is false, then the password will be checked only as it was provided by the client.

The Haystack Password Validator

The haystack password validator is based on the concept of password haystacks as described at https://www.grc.com/haystack.htm. This algorithm judges the strength of a password based on a combination of its length and the different classes of characters that it contains. For example, a password comprised of a mix of lowercase letters, uppercase letters, numeric digits, and symbols is, in general, more resistant to brute force attacks than a password of the same length made up of only lowercase letters, but a password made up of only lowercase letters can be very secure if it is long enough (and passphrases—passwords comprised of multiple words strung together—are a great example of this). The haystack validator lets users have a simpler password if it’s long enough, or a shorter password if it’s complex enough.

As long as you have a client-side implementation of the haystack logic (which is pretty simple), you can perform client-side checking for this password validator. The validator name is “haystack”, and it has the following validation properties:

  • assumed-password-guesses-per-second — The number of guesses that an attacker is assumed to be able to make per second. This value will be an integer, although it could be a very large integer, so it’s recommended to use at least a 64-bit variable to represent it.
  • minimum-acceptable-time-to-exhaust-search-space — The minimum length of time, in seconds, that is considered acceptable for an attacker to have to keep guessing (at the rate specified by the assumed-password-guesses-per-second property) before exhausting the complete search space of all possible passwords. This will also be an integer, and it’s also recommended that you use at least a 64-bit variable to hold its value.

The Length Password Validator

The length-based password validator judges the quality of a proposed password based purely on the number of characters that it contains. Note that it counts the number of UTF-8 characters rather than the number of bytes, and a password with multi-byte characters will have fewer characters than it has bytes. You can configure either or both of a minimum required length and a maximum required length.

Client-side checking is very straightforward for this validator. It uses a validator name of “length” and the following validation properties:

  • min-password-length — The minimum number of characters that a password will be required to have. If present, the value will be an integer. If it is absent, then no minimum length will be enforced.
  • max-password-length — The maximum number of characters that a password will be permitted to have. If present, the value will be an integer. If it is absent, then no maximum length will be enforced.

The Regular Expression Password Validator

The regular expression password validator can be used to require that password match a given pattern or to reject passwords that match a given pattern. As long as the client can perform regular expression matching, then client-side validation should be pretty simple. It uses a validator name of “regular-expression” and the following validation properties:

  • match-pattern — The regular expression that will be evaluated against a proposed password.
  • match-behavior — A string that indicates the behavior that the validator should observe. A value of require-match means that the validator will reject any proposed password that does not satisfy the associated match-pattern. A value of reject-match means that the validator will reject any proposed password that does match the specified match-pattern.

The Repeated Characters Password Validator

The repeated characters password validator can be used to reject a proposed password if it contains the same character, or characters in the same set, more than a specified number of times in a row without a different type of character in between. By default, it treats each type of character separately, but you can define sets of characters that will be considered equivalent. In the former case, the validator will reject a password if it contains the same character too many times in a row, whereas in the latter case, it can reject a password if it contains too many characters of the same type in a row. For example, you could define sets of lowercase letters, uppercase letters, digits, and symbols, and prevent too many characters of each type in a row.

It should be pretty straightforward to perform client-side checking for this password validator. It uses a validator name of “repeated-characters” and the following validation properties:

  • character-set-{counter} — A set of characters that should be considered equivalent. The counter will start at 1 and increment sequentially for each additional character set. This property may be absent if each character is to be treated independently.
  • max-consecutive-length — The maximum number of times that each character (or characters from the same set) may appear in a row before a proposed password will be rejected. The value will be an integer.
  • case-sensitive-validation — Indicates whether to treat characters from the password in a case-sensitive manner. A value of true indicates that values should be case-sensitive, while a value of false indicates that values should be case-insensitive.

The Similarity Password Validator

The similarity password validator can be used to reject a proposed password if it is too similar to the user’s current password. Similarity is determined by the Levenshtein distance algorithm, which is a measure of the minimum number of character insertions, deletions, or replacements needed to transform one string into another. For example, it can prevent a user from changing the password from something like “password1” to “password2”. This validator is only active for password self changes. It does not apply to add operations or administrative resets.

Because the Ping Identity Directory Server server generally stores passwords in a non-reversible form, this can only be used if the request used to change the user’s password includes both the current password and the proposed new password. You can use the password-change-requires-current-password property in the password policy configuration to require this, and if that is configured, then the get password quality requirements extended response will indicate that the current password is required when a user is performing a self change. The password modify extended request provides a field for specifying the current password when requesting a new password, but to satisfy this requirement in an LDAP modify operation, the change should be processed as a delete of the current password (with the value provided in the clear) followed by an add of the new password (also in the clear), in the same modification, like:

dn: uid=john.doe,ou=People,dc=example,dc=com
changetype: modify
delete: userPassword
userPassword: oldPassword
-
add: userPassword
userPassword: newPassword
-

If a client has the user’s current password, the proposed new password, and an implementation of the Levenshtein distance algorithm, then it can perform client-side checking for this validator. The validation type is “similarity” and the validation properties are:

  • min-password-difference — The minimum acceptable distance, as determined by the Levenshtein distance algorithm, between the user’s current password and the proposed new password. It will be an integer.

The Unique Characters Password Validator

The unique characters password validator can be used to reject a proposed password that has too few unique characters. This can prevent users from choosing simple passwords like “aaaaaaaa” or “abcabcabcabc”.

It’s easy to perform client-side checking for this validator. It has a validator name of “unique-characters” and the following properties:

  • min-unique-characters — The minimum number of unique characters that the password must contain for it to be acceptable. This is an integer value, with zero indicating no limit (although the server will require passwords to contain at least one character).
  • case-sensitive-validation — Indicates whether the validator will treat uppercase and lowercase versions of the same letter as different characters or the same. A value of true indicates that the server will perform case-sensitive validation, while a value of false indicates case-insensitive validation.

Access Control Considerations

The get password quality requirements extended operation probably isn’t something that you’ll want to open up to the world, since it could give an attacker information that they shouldn’t have, like hints that could help them better craft their attack, or information about whether a user exists or not. Since you’ll probably want some restriction on who can use this operation, and since we at Ping Identity have no idea who that might be, the server’s access control configuration does not permit anyone (or at least anyone without the bypass-acl or the bypass-read-acl privilege) to use it. If you want to make it available, then you’ll need to add a global ACI to grant access to an appropriate set of users. The same goes for the password validation details request control.

As an example, let’s say that you want to allow members of the “cn=Password Administrators,ou=Groups,dc=example,dc=com” group to use these features. To do that, you can add the following global ACIs:

(extop="1.3.6.1.4.1.30221.2.6.43")(version 3.0; acl "Allow password administrators to use the get password quality requirements extended operation"; allow (read) groupdn="ldap:///cn=Password Administrators,ou=Groups,dc=example,dc=com";)

(targetcontrol="1.3.6.1.4.1.30221.2.5.40")(version 3.0; acl "Allow password administrators to use the password validation details request control"; allow (read) groupdn="ldap:///cn=Password Administrators,ou=Groups,dc=example,dc=com";)

Also note that while the server does make the password modify extended operation available to anyone, there are additional requirements that must be satisfied before it can be used. In order to change your own password, you need to at least have write access to the password attribute in your own entry. And in order to change someone else’s password, not only do you need write access to the password attribute in that user’s entry, but you also need the password-reset privilege. You can grant that privilege by adding the ds-privilege-name attribute to the password resetter’s entry using a change like:

dn: uid=password.admin,ou=People,dc=example,dc=com
changetype: modify
add: ds-privilege-name
ds-privilege-name: password-reset
-

Example Usage With the UnboundID LDAP SDK for Java

I’ve created a simple Java program that demonstrates the use of the get password quality requirements extended operation and the password validation details control. It’s not very flashy, and it doesn’t currently attempt to perform any client-side validation of the proposed new password before sending it to the directory server, but it’s at least a good jumping-off point for someone who wants to build this functionality into their own application.

You can find this example at https://github.com/dirmgr/blog-example-source-code/tree/master/password-quality-requirements.

Configuring Two-Factor Authentication in the Ping Identity Directory Server

Passwords aren’t going anywhere anytime soon, but they’re just not good enough on their own. It is entirely possible to choose a password that is extremely resistant to dictionary and brute force attacks, but the fact is that most people pick really bad passwords. They also tend to reuse the same passwords across multiple sites, which further increases the risk that their account could be compromised. And even those people who do choose really strong passwords might still be tricked into giving that password to a lookalike site via phishing or DNS hijacking, or they may fall victim to a keylogger. For these reasons and more, it’s always a good idea to combine a password with some additional piece of information when authenticating to a site.

The Ping Identity Directory Server has included support for two-factor authentication since 2012 (back when it was still the UnboundID Directory Server). Out of the box, we currently offer four types of two-factor authentication:

  • You can combine a static password with a time-based one-time password using the standard TOTP mechanism described in RFC 6238. These are the same kind of one-time passwords that are generated by apps like Google Authenticator or Authy.
  • You can combine a static password with a one-time password generated by a YubiKey device.
  • You can combine a static password with a one-time password that gets delivered to the user through some out-of-band mechanism like a text or email message, voice call, or app notification.
  • You can combine a static password with an X.509 certificate presented to the server during TLS negotiation.

In this post, I’ll describe the process for configuring the server to enable support for each of these types of authentication. We also provide the ability to create custom extensions to implement support for other types of authentication if desired, but that’s not going to be covered here.

Time-Based One-Time Passwords

The Ping Identity Directory Server supports time-based one-time passwords through the UNBOUNDID-TOTP SASL mechanism, which is enabled by default in the server. For a user to authenticate with this mechanism, their account must contain the ds-auth-totp-shared-secret attribute whose value is the base32-encoded representation of the shared secret that should be used to generate the one-time passwords. This shared secret must be known to both the client (or at least to an app in the client’s possession, like Google Authenticator) as well as to the server.

To facilitate generating and encoding the TOTP shared secret, the Directory Server provides a “generate TOTP shared secret” extended operation. The UnboundID LDAP SDK for Java provides support for this extended operation, and the class-level Javadoc describes the encoding for this operation in case you need to implement support for it in some other API. We also offer a generate-totp-shared-secret command-line tool that can be used for testing (or I suppose you could invoke it programmatically if you’d rather do that than use the UnboundID LDAP SDK or implement support for the extended operation yourself). For the sake of convenience, I’ll use this tool for the demonstration.

There are actually a couple of ways that you can use the generate TOTP shared secret operation: for a user to generate a shared secret for their own account (in which case the user’s static password must be provided), or for an administrator (who must have the password-reset privilege) to generate a shared secret for another user. I’d expect the most common use case to be a user generating a shared secret for their own account, so that’s the approach we’ll take for this example.

Note that while the generate TOTP shared secret extended operation is enabled out of the box, the shared secrets that it generates by default are not encrypted, which could make them easier for an attacker to steal if they got access to a copy of the user data. To prevent this, if the server is configured with data encryption enabled, then you should also enable the “Encrypt TOTP Secrets and Delivered Tokens” plugin. That can be done with the following configuration change:

dsconfig set-plugin-prop \
     --plugin-name "Encrypt TOTP Secrets and Delivered Tokens" \
     --set enabled:true

If we assume that our account has a username of “jdoe”, then the command to generate a shared secret for that user would be something like:

$ bin/generate-totp-shared-secret --hostname ds.example.com \
     --port 636 \
     --useSSL \
     --trustStorePath config/truststore \
     --authID u:jdoe \
     --promptForUserPassword
Enter the static password for user 'u:jdoe':
Successfully generated TOTP shared secret 'KATLTK5WMUSZIACLOMDP43KPSG2LUUOB'.

If we were using a nice web application to invoke the generate TOTP shared secret operation, we’d probably want to have it generate a QR code with that shared secret embedded in it so that it could be easily scanned and imported into an app like Google Authenticator (and you’d want to embed it in a URL like “otpauth://totp/jdoe%20in%20ds.example.com?secret=KATLTK5WMUSZIACLOMDP43KPSG2LUUOB”). For the sake of testing, we can either manually generate an appropriate QR code (for example, using an online utility like https://www.qr-code-generator.com), or you can just type the shared secret into the authenticator app.

Now that the account is configured for TOTP authentication, we can use the UNBOUNDID-TOTP SASL mechanism to authenticate to the server. As with the generate TOTP shared secret operation, this SASL mechanism is supported by the UnboundID LDAP SDK for Java, but most of our command-line tools should support this mechanism, so we can test it with a utility like ldapsearch. You’ll need to use the “--saslOption” command-line argument to specify a number of parameters, including “mech” (the name of the SASL mechanism to use, which should be “UNBOUNDID-TOTP”), “authID” (the authentication ID for the user that’s trying to authenticate), and “totpPassword” (for the one-time password generated by the authenticator app). For example:

$ bin/ldapsearch --hostname ds.example.com \
     --port 636 \
     --useSSL --trustStorePath config/truststore \
     --saslOption mech=UNBOUNDID-TOTP \
     --saslOption authID=u:jdoe \
     --saslOption totpPassword={one-time-password} \
     --promptForBindPassword \
     --baseDN "" \
     --scope base \
     "(objectClass=*)"
Enter the bind password:

dn:
objectClass: top
objectClass: ds-root-dse
startupUUID: 9d48d347-cd9e-428a-bedc-e6027b30b8ac
startTime: 20190107014535Z

# Result Code:  0 (success)
# Number of Entries Returned:  1

YubiKey One-Time Passwords

If you’ve got a YubiKey device capable of generating one-time passwords in the Yubico OTP format (which should be most YubiKey devices except the ones that only support FIDO authentication), then you can use that device to generate one-time passwords to use in conjunction with your static password.

The Directory Server supports this type of authentication through the UNBOUNDID-YUBIKEY-OTP SASL mechanism. To enable support for this SASL mechanism, you first need to get an API key from Yubico, which you can get for free from https://upgrade.yubico.com/getapikey/. When you do this, you’ll get a client ID and a secret key, which gives you access to use their authentication servers. Note that you only need this for the server (and you can share the same key for all server instances); end users don’t need to worry about this. Alternately, you could stand up your own authentication server if you’d rather not rely on the Yubico servers, but we won’t go into that here.

Once you’ve got the client ID and secret key, you can enable support for the SASL mechanism with the following configuration change:

dsconfig set-sasl-mechanism-handler-prop \
     --handler-name UNBOUNDID-YUBIKEY-OTP \
     --set enabled:true \
     --set yubikey-client-id:{client-id} \
     --set yubikey-api-key:{secret-key}

To be able to authenticate a user with this mechanism, you’ll need to update their account to include a ds-auth-yubikey-public-id attribute with one or more values that represent the public IDs of the YubiKey devices that you want to use (and it might not be a bad idea to have multiple devices registered for the same account, so that you have a backup key in case you lose or break the primary key).

To get the public ID for a YubiKey device, you can use it to generate a one-time password and strip off the last 32 bytes. This isn’t considered secret information, so no encryption is necessary when storing it in an entry, and you can use a simple LDAP modify operation to manage the client IDs for a user account. Alternately, you can use the “register YubiKey OTP device” extended operation (supported and documented in the UnboundID LDAP SDK for Java) or use the register-yubikey-otp-device command-line tool. In the case of the command-line tool, you can register a device like:

$ bin/register-yubikey-otp-device --hostname ds.example.com \
     --port 636 \
     --useSSL \
     --trustStorePath config/truststore \
     --authenticationID u:jdoe \
     --promptForUserPassword \
     --otp {one-time-password}
Enter the static password for user u:jdoe:
Successfully registered the specified YubiKey OTP device for user u:jdoe

Note that when using this tool (and the register YubiKey OTP device extended operation in general), you should provide a complete one-time password and not just the client ID.

That should be all that is necessary to allow the user to authenticate with a YubiKey one-time password. We can test it with ldapsearch like so:

$ bin/ldapsearch --hostname ds.example.com \
     --port 636 \
     --useSSL \
     --trustStorePath config/truststore \
     --saslOption mech=UNBOUNDID-YUBIKEY-OTP \
     --saslOption authID=u:jdoe \
     --saslOption otp={one-time-password} \
     --promptForBindPassword \
     --baseDN "" \
     --scope base \
     "(objectClass=*)"
Enter the bind password:

dn:
objectClass: top
objectClass: ds-root-dse
startupUUID: 9d48d347-cd9e-428a-bedc-e6027b30b8ac
startTime: 20190107014535Z

# Result Code:  0 (success)
# Number of Entries Returned:  1

Delivered One-Time Passwords

Delivered one-time passwords are more convenient than either time-based or YubiKey-generated one-time passwords because there’s less burden on the user. There’s no need to install an app or have any special hardware to generate one-time passwords. Instead, the server generates a one-time password and then sends it to the user through some out-of-band mechanism (that is, the user gets the one-time password through some mechanism other than LDAP). The server provides direct support for delivering these generated one-time passwords over SMS (using the Twilio service) or via email, and the UnboundID Server SDK provides an API that allows you to create your own delivery mechanisms. Note, however, that while it may be more convenient to use, it’s also generally considered less secure (especially if you’re using SMS).

There’s also more effort involved in enabling support for delivered one-time passwords than either time-based or YubiKey-generated one-time passwords. The first thing you should do is configure the server to ensure that the generated one-time password values will be encrypted (unless you already did it above for encrypting TOTP shared secrets), which you can do as follows:

dsconfig set-plugin-prop \
     --plugin-name "Encrypt TOTP Secrets and Delivered Tokens" \
     --set enabled:true

Next, we need to configure one or more delivery mechanisms. These are configured in the “OTP Delivery Mechanism” section of dsconfig. For example, to configure a delivery mechanism for email, you could use something like:

dsconfig create-otp-delivery-mechanism \
     --mechanism-name Email \
     --type email \
     --set enabled:true \
     --set 'sender-address:otp@example.com' \
     --set "message-text-before-otp:Your one-time password is ‘" \
     --set "message-text-after-otp:’."

If you’re using email, you’ll also need to configure one or more SMTP external servers and set the smtp-server property in the global configuration.

Alternately, if you’re using SMS, then you’ll need to have a Twilio account and fill in the appropriate values for the SID, auth token, and phone number fields, like:

dsconfig create-otp-delivery-mechanism \
     --mechanism-name SMS \
     --type twilio \
     --set enabled:true \
     --set twilio-account-sid:{sid} \
     --set twilio-auth-token:{auth-token} \
     --set sender-phone-number:{phone-number} \
     --set "message-text-before-otp:Your one-time password is '" \
     --set "message-text-after-otp:'."

Once the delivery mechanism(s) are configured, you can enable the delivered one-time password SASL mechanism handler as follows:

dsconfig create-sasl-mechanism-handler \
     --handler-name UNBOUNDID-DELIVERED-OTP \
     --type unboundid-delivered-otp \
     --set enabled:true \
     --set "identity-mapper:Exact Match"

You’ll also need to enable support for the deliver one-time password extended operation, which is used to request that the server generate and deliver a one-time password for a user. You can do that like:

dsconfig create-extended-operation-handler \
     --handler-name "Deliver One-Time Passwords" \
     --type deliver-otp \
     --set enabled:true \
     --set "identity-mapper:Exact Match" \
     --set "password-generator:One-Time Password Generator" \
     --set default-otp-delivery-mechanism:Email
     --set default-otp-delivery-mechanism:SMS

The process for authenticating with a delivered one-time password involves two steps. In the first step, you need to request that the server generate and deliver a one-time password, which can be accomplished with the “deliver one-time password” extended operation, which is supported and documented in the UnboundID LDAP SDK for Java and can be tested with the deliver-one-time-password command-line tool. Then, once you have that one-time password, you can use the UNBOUNDID-DELIVERED-OTP SASL mechanism to complete the authentication.

If you have multiple delivery mechanisms configured in the server, then there are several ways that the server can decide which one to use to send a one-time password to a user.

  • The server will only attempt to use a delivery mechanism that applies to the target user. For example, if a user entry has an email address but not a mobile phone number, then it won’t try to deliver a one-time password to that user via SMS.
  • The deliver one-time password extended request can be used to indicate which delivery mechanism(s) should be attempted, and in which order they should be attempted. If you’re using the deliver-one-time-password command-line tool, then you can use the –deliveryMechanism argument to specify this.
  • If the extended request doesn’t indicate which mechanisms to use, then the server will check the user’s entry to see if it has a ds-auth-preferred-otp-delivery-mechanism operational attribute. If so, then it will be used to specify the desired delivery mechanism.
  • If nothing else, then the server will use the order specified in the default-otp-delivery-mechanism property of the extended operation handler configuration.

As an example, let’s demonstrate the process of authenticating as user jdoe with a one-time password delivered via email. We can start the process using the deliver-one-time password command-line tool as follows:

$ bin/deliver-one-time-password --hostname ds.example.com \
     --port 636 \
     --useSSL \
     --trustStorePath config/truststore \
     --userName jdoe \
     --promptForBindPassword \
     --deliveryMechanism Email
Enter the static password for the user:

Successfully delivered a one-time password via mechanism 'Email' to 'jdoe@example.com'

Now, we can check our email, and there should be a message with the one-time password. Once we have it, we can use a tool like ldapsearch to authenticate with that one-time password using the UNBOUNDID-DELIVERED-OTP SASL mechanism, like:

$ bin/ldapsearch --hostname ds.example.com \
     --port 636 \
     --useSSL \
     --trustStorePath config/truststore \
     --saslOption mech=UNBOUNDID-DELIVERED-OTP \
     --saslOption authID=u:jdoe \
     --saslOption otp={one-time-password} \
     --baseDN "" \
     --scope base \
     "(objectClass=*)"
dn:
objectClass: top
objectClass: ds-root-dse
startupUUID: 9d48d347-cd9e-428a-bedc-e6027b30b8ac
startTime: 20190107014535Z

# Result Code:  0 (success)
# Number of Entries Returned:  1

Combining Certificates and Passwords

When establishing a TLS-based connection, the server will always present its certificate to the client, and the client will decide whether it wants to trust that certificate and continue establishing the secure connection. Further, the server may optionally ask the client to provide its own certificate, and then the client may then optionally provide one. If the server requests a client certificate, and if the client provides one, then the determine whether it wants to trust that client certificate and continue the negotiation process.

If a client has provided its own certificate to the directory server and the server has accepted it, then the client can use a SASL EXTERNAL bind to request that the server use the information in the certificate to identify and authenticate the client. Most LDAP servers support this, and it can be a very strong form of single-factor authentication. However, the Ping Identity Directory Server also offers an UNBOUNDID-CERTIFICATE-PLUS-PASSWORD SASL mechanism that takes this even further by combining the client certificate with a static password.

Certificate-based authentication (regardless of whether you also include a static password) isn’t something that has really caught on because of the hassle and complexity of dealing with certificates. It’s honestly probably not a great option for most end users, although it may be an attractive option for more advanced users like server administrators. But one big benefit that the UNBOUNDID-CERTIFICATE-PLUS-PASSWORD mechanism has over the two-factor mechanisms that rely on one-time passwords is that it can be used in a completely non-interactive manner. That makes it suitable for use in authenticating one application to another.

As with the EXTERNAL mechanism, the Ping Identity Directory Server has support for the UNBOUNDID-CERTIFICATE-PLUS-PASSWORD mechanism enabled out of the box. Just about the only thing you’re likely to want to configure is the certificate-mapper property in its configuration, which is used to uniquely identify the account for the user that is trying to authenticate based on the contents of the certificate. The certificate mapper that is configured by default will only work if the certificate’s subject DN matches the DN of the corresponding user entry. Other certificate mappers can be used to identify the user in other ways, including searching based on attributes in the certificate subject or searching for the owner of a certificate based on the fingerprint of that certificate.

Due to an unfortunate oversight, command-line tools currently shipped with the server do not include support for the UNBOUNDID-CERTIFICATE-PLUS-PASSWORD SASL mechanism. That will be corrected in the next release, but if you want to test with it now, you can check out the UnboundID LDAP SDK for Java from its GitHub project and build it for yourself. That will allow you to test certificate+password authentication like so:

$ tools/ldapsearch --hostname ds.example.com \
     --port 636 \
     --useSSL \
     --keyStorePath client-keystore \
     --promptForKeyStorePassword \
     --trustStorePath config/truststore \
     --saslOption mech=UNBOUNDID-CERTIFICATE-PLUS-PASSWORD \
     --promptForBindPassword \
     --baseDN "" \
     --scope base \
     "(objectClass=*)"
Enter the key store password:

Enter the bind password:

dn:
objectClass: top
objectClass: ds-root-dse
startupUUID: 42a82498-93c6-4e62-9c6c-8fe6b33e1550
startTime: 20190107072612Z

# Result Code:  0 (success)
# Number of Entries Returned:  1