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