001/*
002 * Copyright 2017-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2017-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.listener;
022
023
024
025import java.security.MessageDigest;
026import java.security.SecureRandom;
027import java.util.Arrays;
028import java.util.List;
029
030import com.unboundid.ldap.sdk.LDAPException;
031import com.unboundid.ldap.sdk.Modification;
032import com.unboundid.ldap.sdk.ReadOnlyEntry;
033import com.unboundid.ldap.sdk.ResultCode;
034import com.unboundid.util.ThreadSafety;
035import com.unboundid.util.ThreadSafetyLevel;
036import com.unboundid.util.Validator;
037
038import static com.unboundid.ldap.listener.ListenerMessages.*;
039
040
041
042/**
043 * This class provides an implementation of an in-memory directory server
044 * password encoder that uses a message digest to encode passwords.  Encoded
045 * passwords will also include some number of randomly generated bytes, called a
046 * salt, to ensure that encoding the same password multiple times will yield
047 * multiple different encoded representations.
048 */
049@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
050public final class SaltedMessageDigestInMemoryPasswordEncoder
051       extends InMemoryPasswordEncoder
052{
053  // Indicates whether the salt should go after or before the clear-text
054  // password when generating the message digest.
055  private final boolean saltAfterClearPassword;
056
057  // Indicates whether the salt should go after or before the digest bytes
058  // when generating the final encoded representation.
059  private final boolean saltAfterMessageDigest;
060
061  // The length of the generated message digest, in bytes.
062  private final int digestLengthBytes;
063
064  // The number of salt bytes to generate.
065  private final int numSaltBytes;
066
067  // The message digest instance tha will be used to actually perform the
068  // encoding.
069  private final MessageDigest messageDigest;
070
071  // The secure random number generator used for generating salts.
072  private final SecureRandom random;
073
074
075
076  /**
077   * Creates a new instance of this in-memory directory server password encoder
078   * with the provided information.
079   *
080   * @param  prefix                  The string that will appear at the
081   *                                 beginning of encoded passwords.  It must
082   *                                 not be {@code null} or empty.
083   * @param  outputFormatter         The output formatter that will be used to
084   *                                 format the encoded representation of
085   *                                 clear-text passwords.  It may be
086   *                                 {@code null} if no special formatting
087   *                                 should be applied to the raw bytes.
088   * @param  messageDigest           The message digest that will be used to
089   *                                 actually perform the encoding.  It must not
090   *                                 be {@code null}.
091   * @param  numSaltBytes            The number of salt bytes to generate when
092   *                                 encoding passwords.  It must be greater
093   *                                 than zero.
094   * @param  saltAfterClearPassword  Indicates whether the salt should be placed
095   *                                 after or before the clear-text password
096   *                                 when computing the message digest.  If this
097   *                                 is {@code true}, then the digest will be
098   *                                 computed from the concatenation of the
099   *                                 clear-text password and the salt, in that
100   *                                 order.  If this is {@code false}, then the
101   *                                 digest will be computed from the
102   *                                 concatenation of the salt and the
103   *                                 clear-text password.
104   * @param  saltAfterMessageDigest  Indicates whether the salt should be placed
105   *                                 after or before the computed digest when
106   *                                 creating the encoded representation.  If
107   *                                 this is {@code true}, then the encoded
108   *                                 password will consist of the concatenation
109   *                                 of the computed message digest and the
110   *                                 salt, in that order.  If this is
111   *                                 {@code false}, then the encoded password
112   *                                 will consist of the concatenation of the
113   *                                 salt and the message digest.
114   */
115  public SaltedMessageDigestInMemoryPasswordEncoder(final String prefix,
116              final PasswordEncoderOutputFormatter outputFormatter,
117              final MessageDigest messageDigest, final int numSaltBytes,
118              final boolean saltAfterClearPassword,
119              final boolean saltAfterMessageDigest)
120  {
121    super(prefix, outputFormatter);
122
123    Validator.ensureNotNull(messageDigest);
124    this.messageDigest = messageDigest;
125
126    digestLengthBytes = messageDigest.getDigestLength();
127    Validator.ensureTrue((digestLengthBytes > 0),
128         "The message digest use a fixed digest length, and that " +
129              "length must be greater than zero.");
130
131    this.numSaltBytes = numSaltBytes;
132    Validator.ensureTrue((numSaltBytes > 0),
133         "numSaltBytes must be greater than zero.");
134
135    this.saltAfterClearPassword = saltAfterClearPassword;
136    this.saltAfterMessageDigest = saltAfterMessageDigest;
137
138    random = new SecureRandom();
139  }
140
141
142
143  /**
144   * Retrieves the digest algorithm that will be used when encoding passwords.
145   *
146   * @return  The message digest
147   */
148  public String getDigestAlgorithm()
149  {
150    return messageDigest.getAlgorithm();
151  }
152
153
154
155  /**
156   * Retrieves the digest length, in bytes.
157   *
158   * @return  The digest length, in bytes.
159   */
160  public int getDigestLengthBytes()
161  {
162    return digestLengthBytes;
163  }
164
165
166
167  /**
168   * Retrieves the number of bytes of salt that will be generated when encoding
169   * a password.  Note that this is used only when encoding new clear-text
170   * passwords.  When comparing a clear-text password against an existing
171   * encoded representation, the number of salt bytes from the existing encoded
172   * password will be used.
173   *
174   * @return  The number of bytes of salt that will be generated when encoding a
175   *          password.
176   */
177  public int getNumSaltBytes()
178  {
179    return numSaltBytes;
180  }
181
182
183
184  /**
185   * Indicates whether the salt should be appended or prepended to the
186   * clear-text password when computing the message digest.
187   *
188   * @return  {@code true} if the salt should be appended to the clear-text
189   *          password when computing the message digest, or {@code false} if
190   *          the salt should be prepended to the clear-text password.
191   */
192  public boolean isSaltAfterClearPassword()
193  {
194    return saltAfterClearPassword;
195  }
196
197
198
199  /**
200   * Indicates whether the salt should be appended or prepended to the digest
201   * when generating the encoded representation for the password.
202   *
203   * @return  {@code true} if the salt should be appended to the digest when
204   *          generating the encoded representation for the password, or
205   *          {@code false} if the salt should be prepended to the digest.
206   */
207  public boolean isSaltAfterMessageDigest()
208  {
209    return saltAfterMessageDigest;
210  }
211
212
213
214  /**
215   * {@inheritDoc}
216   */
217  @Override()
218  protected byte[] encodePassword(final byte[] clearPassword,
219                                  final ReadOnlyEntry userEntry,
220                                  final List<Modification> modifications)
221            throws LDAPException
222  {
223    final byte[] salt = new byte[numSaltBytes];
224    random.nextBytes(salt);
225
226    final byte[] saltedPassword;
227    if (saltAfterClearPassword)
228    {
229      saltedPassword = concatenate(clearPassword, salt);
230    }
231    else
232    {
233      saltedPassword = concatenate(salt, clearPassword);
234    }
235
236    final byte[] digest = messageDigest.digest(saltedPassword);
237
238    if (saltAfterMessageDigest)
239    {
240      return concatenate(digest, salt);
241    }
242    else
243    {
244      return concatenate(salt, digest);
245    }
246  }
247
248
249
250  /**
251   * Creates a new byte array that is a concatenation of the provided byte
252   * arrays.
253   *
254   * @param  b1  The byte array to appear first in the concatenation.
255   * @param  b2  The byte array to appear second in the concatenation.
256   *
257   * @return  A byte array containing the concatenation.
258   */
259  private static byte[] concatenate(final byte[] b1, final byte[] b2)
260  {
261    final byte[] combined = new byte[b1.length + b2.length];
262    System.arraycopy(b1, 0, combined, 0, b1.length);
263    System.arraycopy(b2, 0, combined, b1.length, b2.length);
264    return combined;
265  }
266
267
268
269  /**
270   * {@inheritDoc}
271   */
272  @Override()
273  protected void ensurePreEncodedPasswordAppearsValid(
274                      final byte[] unPrefixedUnFormattedEncodedPasswordBytes,
275                      final ReadOnlyEntry userEntry,
276                      final List<Modification> modifications)
277            throws LDAPException
278  {
279    // Make sure that the encoded password is longer than the digest length
280    // so that there is room for some amount of salt.
281    if (unPrefixedUnFormattedEncodedPasswordBytes.length <= digestLengthBytes)
282    {
283      throw new LDAPException(ResultCode.PARAM_ERROR,
284           ERR_SALTED_DIGEST_PW_ENCODER_PRE_ENCODED_LENGTH_MISMATCH.get(
285                messageDigest.getAlgorithm(),
286                unPrefixedUnFormattedEncodedPasswordBytes.length,
287                (digestLengthBytes + 1)));
288    }
289  }
290
291
292
293  /**
294   * {@inheritDoc}
295   */
296  @Override()
297  protected boolean passwordMatches(final byte[] clearPasswordBytes,
298                         final byte[] unPrefixedUnFormattedEncodedPasswordBytes,
299                         final ReadOnlyEntry userEntry)
300            throws LDAPException
301  {
302    // Subtract the digest length from the encoded password to get the number
303    // of salt bytes.  If the number of salt bytes is less than or equal to
304    // zero, then the password will not match.
305    final int numComputedSaltBytes =
306         unPrefixedUnFormattedEncodedPasswordBytes.length - digestLengthBytes;
307    if (numComputedSaltBytes <= 0)
308    {
309      return false;
310    }
311
312
313    // Separate the salt and the digest.
314    final byte[] salt = new byte[numComputedSaltBytes];
315    final byte[] digest = new byte[digestLengthBytes];
316    if (saltAfterMessageDigest)
317    {
318      System.arraycopy(unPrefixedUnFormattedEncodedPasswordBytes, 0, digest, 0,
319           digestLengthBytes);
320      System.arraycopy(unPrefixedUnFormattedEncodedPasswordBytes,
321           digestLengthBytes, salt, 0, salt.length);
322    }
323    else
324    {
325      System.arraycopy(unPrefixedUnFormattedEncodedPasswordBytes, 0, salt, 0,
326           salt.length);
327      System.arraycopy(unPrefixedUnFormattedEncodedPasswordBytes, salt.length,
328           digest, 0, digestLengthBytes);
329    }
330
331
332    // Now that we have the salt, combine it with the clear-text password in the
333    // proper order.
334    // Combine the clear-text password and the salt in the proper order.
335    final byte[] saltedPassword;
336    if (saltAfterClearPassword)
337    {
338      saltedPassword = concatenate(clearPasswordBytes, salt);
339    }
340    else
341    {
342      saltedPassword = concatenate(salt, clearPasswordBytes);
343    }
344
345
346    // Compute a digest of the salted password and see whether it matches the
347    // digest we extracted earlier.  If so, then the clear-text password
348    // matches.  If not, then it doesn't.
349    final byte[] computedDigest = messageDigest.digest(saltedPassword);
350    return Arrays.equals(computedDigest, digest);
351  }
352
353
354
355  /**
356   * {@inheritDoc}
357   */
358  @Override()
359  protected byte[] extractClearPassword(
360                 final byte[] unPrefixedUnFormattedEncodedPasswordBytes,
361                 final ReadOnlyEntry userEntry)
362            throws LDAPException
363  {
364    throw new LDAPException(ResultCode.NOT_SUPPORTED,
365         ERR_SALTED_DIGEST_PW_ENCODER_NOT_REVERSIBLE.get());
366  }
367
368
369
370  /**
371   * {@inheritDoc}
372   */
373  @Override()
374  public void toString(final StringBuilder buffer)
375  {
376    buffer.append("SaltedMessageDigestInMemoryPasswordEncoder(prefix='");
377    buffer.append(getPrefix());
378    buffer.append("', outputFormatter=");
379
380    final PasswordEncoderOutputFormatter outputFormatter =
381         getOutputFormatter();
382    if (outputFormatter == null)
383    {
384      buffer.append("null");
385    }
386    else
387    {
388      outputFormatter.toString(buffer);
389    }
390
391    buffer.append(", digestAlgorithm='");
392    buffer.append(messageDigest.getAlgorithm());
393    buffer.append("', digestLengthBytes=");
394    buffer.append(messageDigest.getDigestLength());
395    buffer.append(", numSaltBytes=");
396    buffer.append(numSaltBytes);
397    buffer.append(", saltAfterClearPassword=");
398    buffer.append(saltAfterClearPassword);
399    buffer.append(", saltAfterMessageDigest=");
400    buffer.append(saltAfterMessageDigest);
401    buffer.append(')');
402  }
403}