001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2015 ForgeRock AS.
016 */
017package org.opends.server.extensions;
018
019
020
021import java.security.MessageDigest;
022import java.util.Arrays;
023
024import org.forgerock.i18n.LocalizableMessage;
025import org.opends.server.admin.std.server.MD5PasswordStorageSchemeCfg;
026import org.opends.server.api.PasswordStorageScheme;
027import org.forgerock.opendj.config.server.ConfigException;
028import org.opends.server.core.DirectoryServer;
029import org.forgerock.i18n.slf4j.LocalizedLogger;
030import org.opends.server.types.*;
031import org.forgerock.opendj.ldap.ResultCode;
032import org.forgerock.opendj.ldap.ByteString;
033import org.forgerock.opendj.ldap.ByteSequence;
034import org.opends.server.util.Base64;
035
036import static org.opends.messages.ExtensionMessages.*;
037import static org.opends.server.extensions.ExtensionsConstants.*;
038import static org.opends.server.util.StaticUtils.*;
039
040
041
042/**
043 * This class defines a Directory Server password storage scheme based on the
044 * MD5 algorithm defined in RFC 1321.  This is a one-way digest algorithm
045 * so there is no way to retrieve the original clear-text version of the
046 * password from the hashed value (although this means that it is not suitable
047 * for things that need the clear-text password like DIGEST-MD5).  This
048 * implementation does not perform any salting, which means that it is more
049 * vulnerable to dictionary attacks than salted variants.
050 */
051public class MD5PasswordStorageScheme
052       extends PasswordStorageScheme<MD5PasswordStorageSchemeCfg>
053{
054  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
055
056  /**
057   * The fully-qualified name of this class.
058   */
059  private static final String CLASS_NAME =
060       "org.opends.server.extensions.MD5PasswordStorageScheme";
061
062
063
064  /** The message digest that will actually be used to generate the MD5 hashes. */
065  private MessageDigest messageDigest;
066
067  /** The lock used to provide threadsafe access to the message digest. */
068  private Object digestLock;
069
070
071
072  /**
073   * Creates a new instance of this password storage scheme.  Note that no
074   * initialization should be performed here, as all initialization should be
075   * done in the <CODE>initializePasswordStorageScheme</CODE> method.
076   */
077  public MD5PasswordStorageScheme()
078  {
079    super();
080
081  }
082
083
084
085  /** {@inheritDoc} */
086  @Override
087  public void initializePasswordStorageScheme(
088                   MD5PasswordStorageSchemeCfg configuration)
089         throws ConfigException, InitializationException
090  {
091    try
092    {
093      messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_MD5);
094    }
095    catch (Exception e)
096    {
097      logger.traceException(e);
098
099      LocalizableMessage message = ERR_PWSCHEME_CANNOT_INITIALIZE_MESSAGE_DIGEST.get(
100          MESSAGE_DIGEST_ALGORITHM_MD5, e);
101      throw new InitializationException(message, e);
102    }
103
104    digestLock = new Object();
105  }
106
107
108
109  /** {@inheritDoc} */
110  @Override
111  public String getStorageSchemeName()
112  {
113    return STORAGE_SCHEME_NAME_MD5;
114  }
115
116
117
118  /** {@inheritDoc} */
119  @Override
120  public ByteString encodePassword(ByteSequence plaintext)
121         throws DirectoryException
122  {
123    byte[] digestBytes;
124    byte[] plaintextBytes = null;
125
126    synchronized (digestLock)
127    {
128      try
129      {
130        // TODO: Can we avoid this copy?
131        plaintextBytes = plaintext.toByteArray();
132        digestBytes = messageDigest.digest(plaintextBytes);
133      }
134      catch (Exception e)
135      {
136        logger.traceException(e);
137
138        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
139            CLASS_NAME, getExceptionMessage(e));
140        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
141                                     message, e);
142      }
143      finally
144      {
145        if (plaintextBytes != null)
146        {
147          Arrays.fill(plaintextBytes, (byte) 0);
148        }
149      }
150    }
151
152    return ByteString.valueOfUtf8(Base64.encode(digestBytes));
153  }
154
155
156
157  /** {@inheritDoc} */
158  @Override
159  public ByteString encodePasswordWithScheme(ByteSequence plaintext)
160         throws DirectoryException
161  {
162    StringBuilder buffer = new StringBuilder();
163    buffer.append('{');
164    buffer.append(STORAGE_SCHEME_NAME_MD5);
165    buffer.append('}');
166
167    byte[] plaintextBytes = null;
168    byte[] digestBytes;
169
170    synchronized (digestLock)
171    {
172      try
173      {
174        // TODO: Can we avoid this copy?
175        plaintextBytes = plaintext.toByteArray();
176        digestBytes = messageDigest.digest(plaintextBytes);
177      }
178      catch (Exception e)
179      {
180        logger.traceException(e);
181
182        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
183            CLASS_NAME, getExceptionMessage(e));
184        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
185                                     message, e);
186      }
187      finally
188      {
189        if (plaintextBytes != null)
190        {
191          Arrays.fill(plaintextBytes, (byte) 0);
192        }
193      }
194    }
195
196    buffer.append(Base64.encode(digestBytes));
197
198
199    return ByteString.valueOfUtf8(buffer);
200  }
201
202
203
204  /** {@inheritDoc} */
205  @Override
206  public boolean passwordMatches(ByteSequence plaintextPassword,
207                                 ByteSequence storedPassword)
208  {
209    byte[] plaintextPasswordBytes = null;
210    ByteString userPWDigestBytes;
211
212    synchronized (digestLock)
213    {
214      try
215      {
216        // TODO: Can we avoid this copy?
217        plaintextPasswordBytes = plaintextPassword.toByteArray();
218        userPWDigestBytes =
219            ByteString.wrap(messageDigest.digest(plaintextPasswordBytes));
220      }
221      catch (Exception e)
222      {
223        logger.traceException(e);
224
225        return false;
226      }
227      finally
228      {
229        if (plaintextPasswordBytes != null)
230        {
231          Arrays.fill(plaintextPasswordBytes, (byte) 0);
232        }
233      }
234    }
235
236    ByteString storedPWDigestBytes;
237    try
238    {
239      storedPWDigestBytes =
240          ByteString.wrap(Base64.decode(storedPassword.toString()));
241    }
242    catch (Exception e)
243    {
244      logger.traceException(e);
245      logger.error(ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD, storedPassword, e);
246      return false;
247    }
248
249    return userPWDigestBytes.equals(storedPWDigestBytes);
250  }
251
252
253
254  /** {@inheritDoc} */
255  @Override
256  public boolean supportsAuthPasswordSyntax()
257  {
258    // This storage scheme does not support the authentication password syntax.
259    return false;
260  }
261
262
263
264  /** {@inheritDoc} */
265  @Override
266  public ByteString encodeAuthPassword(ByteSequence plaintext)
267         throws DirectoryException
268  {
269    LocalizableMessage message =
270        ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
271    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
272  }
273
274
275
276  /** {@inheritDoc} */
277  @Override
278  public boolean authPasswordMatches(ByteSequence plaintextPassword,
279                                     String authInfo, String authValue)
280  {
281    // This storage scheme does not support the authentication password syntax.
282    return false;
283  }
284
285
286
287  /** {@inheritDoc} */
288  @Override
289  public boolean isReversible()
290  {
291    return false;
292  }
293
294
295
296  /** {@inheritDoc} */
297  @Override
298  public ByteString getPlaintextValue(ByteSequence storedPassword)
299         throws DirectoryException
300  {
301    LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_MD5);
302    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
303  }
304
305
306
307  /** {@inheritDoc} */
308  @Override
309  public ByteString getAuthPasswordPlaintextValue(String authInfo,
310                                                  String authValue)
311         throws DirectoryException
312  {
313    LocalizableMessage message =
314        ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
315    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
316  }
317
318
319
320  /** {@inheritDoc} */
321  @Override
322  public boolean isStorageSchemeSecure()
323  {
324    // MD5 may be considered reasonably secure for this purpose.
325    return true;
326  }
327}
328