001/*
002 * Copyright 2017-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2017-2018 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.util.Arrays;
027import java.util.List;
028
029import com.unboundid.ldap.sdk.LDAPException;
030import com.unboundid.ldap.sdk.Modification;
031import com.unboundid.ldap.sdk.ReadOnlyEntry;
032import com.unboundid.ldap.sdk.ResultCode;
033import com.unboundid.util.ThreadSafety;
034import com.unboundid.util.ThreadSafetyLevel;
035import com.unboundid.util.Validator;
036
037import static com.unboundid.ldap.listener.ListenerMessages.*;
038
039
040
041/**
042 * This class provides an implementation of an in-memory directory server
043 * password encoder that uses a message digest to encode passwords.  No salt
044 * will be used when generating the digest, so the same clear-text password will
045 * always result in the same encoded representation.
046 */
047@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
048public final class UnsaltedMessageDigestInMemoryPasswordEncoder
049       extends InMemoryPasswordEncoder
050{
051  // The length of the generated message digest, in bytes.
052  private final int digestLengthBytes;
053
054  // The message digest instance tha will be used to actually perform the
055  // encoding.
056  private final MessageDigest messageDigest;
057
058
059
060
061  /**
062   * Creates a new instance of this in-memory directory server password encoder
063   * with the provided information.
064   *
065   * @param  prefix           The string that will appear at the beginning of
066   *                          encoded passwords.  It must not be {@code null} or
067   *                          empty.
068   * @param  outputFormatter  The output formatter that will be used to format
069   *                          the encoded representation of clear-text
070   *                          passwords.  It may be {@code null} if no
071   *                          special formatting should be applied to the raw
072   *                          bytes.
073   * @param  messageDigest    The message digest that will be used to actually
074   *                          perform the encoding.  It must not be
075   *                          {@code null}, it must have a fixed length, and it
076   *                          must properly report that length via the
077   *                          {@code MessageDigest.getDigestLength} method..
078   */
079  public UnsaltedMessageDigestInMemoryPasswordEncoder(final String prefix,
080              final PasswordEncoderOutputFormatter outputFormatter,
081              final MessageDigest messageDigest)
082  {
083    super(prefix, outputFormatter);
084
085    Validator.ensureNotNull(messageDigest);
086    this.messageDigest = messageDigest;
087
088    digestLengthBytes = messageDigest.getDigestLength();
089    Validator.ensureTrue((digestLengthBytes > 0),
090         "The message digest use a fixed digest length, and that " +
091              "length must be greater than zero.");
092  }
093
094
095
096  /**
097   * Retrieves the digest algorithm that will be used when encoding passwords.
098   *
099   * @return  The message digest
100   */
101  public String getDigestAlgorithm()
102  {
103    return messageDigest.getAlgorithm();
104  }
105
106
107
108  /**
109   * Retrieves the digest length, in bytes.
110   *
111   * @return  The digest length, in bytes.
112   */
113  public int getDigestLengthBytes()
114  {
115    return digestLengthBytes;
116  }
117
118
119
120  /**
121   * {@inheritDoc}
122   */
123  @Override()
124  protected byte[] encodePassword(final byte[] clearPassword,
125                                  final ReadOnlyEntry userEntry,
126                                  final List<Modification> modifications)
127            throws LDAPException
128  {
129    return messageDigest.digest(clearPassword);
130  }
131
132
133
134  /**
135   * {@inheritDoc}
136   */
137  @Override()
138  protected void ensurePreEncodedPasswordAppearsValid(
139                      final byte[] unPrefixedUnFormattedEncodedPasswordBytes,
140                      final ReadOnlyEntry userEntry,
141                      final List<Modification> modifications)
142            throws LDAPException
143  {
144    // Make sure that the length of the array containing the encoded password
145    // matches the digest length.
146    if (unPrefixedUnFormattedEncodedPasswordBytes.length != digestLengthBytes)
147    {
148      throw new LDAPException(ResultCode.PARAM_ERROR,
149           ERR_UNSALTED_DIGEST_PW_ENCODER_PRE_ENCODED_LENGTH_MISMATCH.get(
150                messageDigest.getAlgorithm(),
151                unPrefixedUnFormattedEncodedPasswordBytes.length,
152                digestLengthBytes));
153    }
154  }
155
156
157
158  /**
159   * {@inheritDoc}
160   */
161  @Override()
162  protected boolean passwordMatches(final byte[] clearPasswordBytes,
163                         final byte[] unPrefixedUnFormattedEncodedPasswordBytes,
164                         final ReadOnlyEntry userEntry)
165            throws LDAPException
166  {
167    final byte[] expectedEncodedPassword =
168         messageDigest.digest(clearPasswordBytes);
169    return Arrays.equals(unPrefixedUnFormattedEncodedPasswordBytes,
170         expectedEncodedPassword);
171  }
172
173
174
175  /**
176   * {@inheritDoc}
177   */
178  @Override()
179  protected byte[] extractClearPassword(
180                 final byte[] unPrefixedUnFormattedEncodedPasswordBytes,
181                 final ReadOnlyEntry userEntry)
182            throws LDAPException
183  {
184    throw new LDAPException(ResultCode.NOT_SUPPORTED,
185         ERR_UNSALTED_DIGEST_PW_ENCODER_NOT_REVERSIBLE.get());
186  }
187
188
189
190  /**
191   * {@inheritDoc}
192   */
193  @Override()
194  public void toString(final StringBuilder buffer)
195  {
196    buffer.append("SaltedMessageDigestInMemoryPasswordEncoder(prefix='");
197    buffer.append(getPrefix());
198    buffer.append("', outputFormatter=");
199
200    final PasswordEncoderOutputFormatter outputFormatter =
201         getOutputFormatter();
202    if (outputFormatter == null)
203    {
204      buffer.append("null");
205    }
206    else
207    {
208      outputFormatter.toString(buffer);
209    }
210
211    buffer.append(", digestAlgorithm='");
212    buffer.append(messageDigest.getAlgorithm());
213    buffer.append("', digestLengthBytes=");
214    buffer.append(messageDigest.getDigestLength());
215    buffer.append(')');
216  }
217}