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