001/*
002 * Copyright 2017-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2017-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.listener;
037
038
039
040import java.security.MessageDigest;
041import java.util.Arrays;
042import java.util.List;
043
044import com.unboundid.ldap.sdk.LDAPException;
045import com.unboundid.ldap.sdk.Modification;
046import com.unboundid.ldap.sdk.ReadOnlyEntry;
047import com.unboundid.ldap.sdk.ResultCode;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050import com.unboundid.util.Validator;
051
052import static com.unboundid.ldap.listener.ListenerMessages.*;
053
054
055
056/**
057 * This class provides an implementation of an in-memory directory server
058 * password encoder that uses a message digest to encode passwords.  No salt
059 * will be used when generating the digest, so the same clear-text password will
060 * always result in the same encoded representation.
061 */
062@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
063public final class UnsaltedMessageDigestInMemoryPasswordEncoder
064       extends InMemoryPasswordEncoder
065{
066  // The length of the generated message digest, in bytes.
067  private final int digestLengthBytes;
068
069  // The message digest instance tha will be used to actually perform the
070  // encoding.
071  private final MessageDigest messageDigest;
072
073
074
075  /**
076   * Creates a new instance of this in-memory directory server password encoder
077   * with the provided information.
078   *
079   * @param  prefix           The string that will appear at the beginning of
080   *                          encoded passwords.  It must not be {@code null} or
081   *                          empty.
082   * @param  outputFormatter  The output formatter that will be used to format
083   *                          the encoded representation of clear-text
084   *                          passwords.  It may be {@code null} if no
085   *                          special formatting should be applied to the raw
086   *                          bytes.
087   * @param  messageDigest    The message digest that will be used to actually
088   *                          perform the encoding.  It must not be
089   *                          {@code null}, it must have a fixed length, and it
090   *                          must properly report that length via the
091   *                          {@code MessageDigest.getDigestLength} method..
092   */
093  public UnsaltedMessageDigestInMemoryPasswordEncoder(final String prefix,
094              final PasswordEncoderOutputFormatter outputFormatter,
095              final MessageDigest messageDigest)
096  {
097    super(prefix, outputFormatter);
098
099    Validator.ensureNotNull(messageDigest);
100    this.messageDigest = messageDigest;
101
102    digestLengthBytes = messageDigest.getDigestLength();
103    Validator.ensureTrue((digestLengthBytes > 0),
104         "The message digest use a fixed digest length, and that " +
105              "length must be greater than zero.");
106  }
107
108
109
110  /**
111   * Retrieves the digest algorithm that will be used when encoding passwords.
112   *
113   * @return  The message digest
114   */
115  public String getDigestAlgorithm()
116  {
117    return messageDigest.getAlgorithm();
118  }
119
120
121
122  /**
123   * Retrieves the digest length, in bytes.
124   *
125   * @return  The digest length, in bytes.
126   */
127  public int getDigestLengthBytes()
128  {
129    return digestLengthBytes;
130  }
131
132
133
134  /**
135   * {@inheritDoc}
136   */
137  @Override()
138  protected byte[] encodePassword(final byte[] clearPassword,
139                                  final ReadOnlyEntry userEntry,
140                                  final List<Modification> modifications)
141            throws LDAPException
142  {
143    return messageDigest.digest(clearPassword);
144  }
145
146
147
148  /**
149   * {@inheritDoc}
150   */
151  @Override()
152  protected void ensurePreEncodedPasswordAppearsValid(
153                      final byte[] unPrefixedUnFormattedEncodedPasswordBytes,
154                      final ReadOnlyEntry userEntry,
155                      final List<Modification> modifications)
156            throws LDAPException
157  {
158    // Make sure that the length of the array containing the encoded password
159    // matches the digest length.
160    if (unPrefixedUnFormattedEncodedPasswordBytes.length != digestLengthBytes)
161    {
162      throw new LDAPException(ResultCode.PARAM_ERROR,
163           ERR_UNSALTED_DIGEST_PW_ENCODER_PRE_ENCODED_LENGTH_MISMATCH.get(
164                messageDigest.getAlgorithm(),
165                unPrefixedUnFormattedEncodedPasswordBytes.length,
166                digestLengthBytes));
167    }
168  }
169
170
171
172  /**
173   * {@inheritDoc}
174   */
175  @Override()
176  protected boolean passwordMatches(final byte[] clearPasswordBytes,
177                         final byte[] unPrefixedUnFormattedEncodedPasswordBytes,
178                         final ReadOnlyEntry userEntry)
179            throws LDAPException
180  {
181    final byte[] expectedEncodedPassword =
182         messageDigest.digest(clearPasswordBytes);
183    return Arrays.equals(unPrefixedUnFormattedEncodedPasswordBytes,
184         expectedEncodedPassword);
185  }
186
187
188
189  /**
190   * {@inheritDoc}
191   */
192  @Override()
193  protected byte[] extractClearPassword(
194                 final byte[] unPrefixedUnFormattedEncodedPasswordBytes,
195                 final ReadOnlyEntry userEntry)
196            throws LDAPException
197  {
198    throw new LDAPException(ResultCode.NOT_SUPPORTED,
199         ERR_UNSALTED_DIGEST_PW_ENCODER_NOT_REVERSIBLE.get());
200  }
201
202
203
204  /**
205   * {@inheritDoc}
206   */
207  @Override()
208  public void toString(final StringBuilder buffer)
209  {
210    buffer.append("SaltedMessageDigestInMemoryPasswordEncoder(prefix='");
211    buffer.append(getPrefix());
212    buffer.append("', outputFormatter=");
213
214    final PasswordEncoderOutputFormatter outputFormatter =
215         getOutputFormatter();
216    if (outputFormatter == null)
217    {
218      buffer.append("null");
219    }
220    else
221    {
222      outputFormatter.toString(buffer);
223    }
224
225    buffer.append(", digestAlgorithm='");
226    buffer.append(messageDigest.getAlgorithm());
227    buffer.append("', digestLengthBytes=");
228    buffer.append(messageDigest.getDigestLength());
229    buffer.append(')');
230  }
231}