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 2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.extensions;
028    
029    
030    
031    import java.util.Arrays;
032    import java.util.Random;
033    
034    import org.opends.messages.Message;
035    import org.opends.server.admin.std.server.CryptPasswordStorageSchemeCfg;
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.types.ByteString;
040    import org.opends.server.types.ByteStringFactory;
041    import org.opends.server.types.DirectoryException;
042    import org.opends.server.types.InitializationException;
043    import org.opends.server.types.ResultCode;
044    import org.opends.server.util.Crypt;
045    
046    import static org.opends.messages.ExtensionMessages.*;
047    import static org.opends.server.extensions.ExtensionsConstants.*;
048    import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
049    
050    
051    
052    /**
053     * This class defines a Directory Server password storage scheme based on the
054     * UNIX Crypt algorithm.  This is a legacy one-way digest algorithm
055     * intended only for situations where passwords have not yet been
056     * updated to modern hashes such as SHA-1 and friends.  This
057     * implementation does perform weak salting, which means that it is more
058     * vulnerable to dictionary attacks than schemes with larger salts.
059     */
060    public class CryptPasswordStorageScheme
061           extends PasswordStorageScheme<CryptPasswordStorageSchemeCfg>
062    {
063      /**
064       * The fully-qualified name of this class for debugging purposes.
065       */
066      private static final String CLASS_NAME =
067           "org.opends.server.extensions.CryptPasswordStorageScheme";
068    
069      /**
070       * An array of values that can be used to create salt characters
071       * when encoding new crypt hashes.
072       * */
073      private static final byte[] SALT_CHARS =
074        ("./0123456789abcdefghijklmnopqrstuvwxyz"
075        +"ABCDEFGHIJKLMNOPQRSTUVWXYZ").getBytes();
076    
077      private final Random randomSaltIndex = new Random();
078      private final Object saltLock = new Object();
079      private final Crypt crypt = new Crypt();
080    
081    
082    
083      /**
084       * Creates a new instance of this password storage scheme.  Note that no
085       * initialization should be performed here, as all initialization should be
086       * done in the <CODE>initializePasswordStorageScheme</CODE> method.
087       */
088      public CryptPasswordStorageScheme()
089      {
090        super();
091      }
092    
093    
094      /**
095       * {@inheritDoc}
096       */
097      @Override()
098      public void initializePasswordStorageScheme(
099                       CryptPasswordStorageSchemeCfg configuration)
100             throws ConfigException, InitializationException {
101        // Nothing to configure
102      }
103    
104      /**
105       * {@inheritDoc}
106       */
107      @Override()
108      public String getStorageSchemeName()
109      {
110        return STORAGE_SCHEME_NAME_CRYPT;
111      }
112    
113    
114      /**
115       * {@inheritDoc}
116       */
117      @Override()
118      public ByteString encodePassword(ByteString plaintext)
119             throws DirectoryException
120      {
121    
122        byte[] digestBytes;
123    
124        try
125        {
126          digestBytes = crypt.crypt(plaintext.value(), randomSalt());
127        }
128        catch (Exception e)
129        {
130          Message message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
131              CLASS_NAME, stackTraceToSingleLineString(e));
132          throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
133                                       message, e);
134        }
135    
136        return ByteStringFactory.create(digestBytes);
137      }
138    
139    
140      /**
141       * Return a random 2-byte salt.
142       *
143       * @return a random 2-byte salt
144       */
145      private byte[] randomSalt() {
146        synchronized (saltLock)
147        {
148          byte[] salt = new byte[2];
149          int sb1 = randomSaltIndex.nextInt(SALT_CHARS.length);
150          int sb2 = randomSaltIndex.nextInt(SALT_CHARS.length);
151          salt[0] = SALT_CHARS[sb1];
152          salt[1] = SALT_CHARS[sb2];
153    
154          return salt;
155        }
156      }
157    
158    
159      /**
160       * {@inheritDoc}
161       */
162      @Override()
163      public ByteString encodePasswordWithScheme(ByteString plaintext)
164             throws DirectoryException
165      {
166        StringBuilder buffer =
167          new StringBuilder(STORAGE_SCHEME_NAME_CRYPT.length()+12);
168        buffer.append('{');
169        buffer.append(STORAGE_SCHEME_NAME_CRYPT);
170        buffer.append('}');
171    
172        buffer.append(encodePassword(plaintext));
173    
174        return ByteStringFactory.create(buffer.toString());
175      }
176    
177    
178    
179      /**
180       * {@inheritDoc}
181       */
182      @Override()
183      public boolean passwordMatches(ByteString plaintextPassword,
184                                     ByteString storedPassword)
185      {
186        byte[] storedPWDigestBytes = storedPassword.value();
187    
188        byte[] userPWDigestBytes;
189        try
190        {
191          // The salt is stored as the first two bytes of the storedPassword
192          // value, and crypt.crypt() only looks at the first two bytes, so
193          // we can pass it in directly.
194          byte[] salt = storedPWDigestBytes;
195    
196          userPWDigestBytes = crypt.crypt(plaintextPassword.value(), salt);
197        }
198        catch (Exception e)
199        {
200          return false;
201        }
202    
203        return Arrays.equals(userPWDigestBytes, storedPWDigestBytes);
204      }
205    
206    
207    
208      /**
209       * {@inheritDoc}
210       */
211      @Override()
212      public boolean supportsAuthPasswordSyntax()
213      {
214        // This storage scheme does not support the authentication password syntax.
215        return false;
216      }
217    
218    
219    
220      /**
221       * {@inheritDoc}
222       */
223      @Override()
224      public ByteString encodeAuthPassword(ByteString plaintext)
225             throws DirectoryException
226      {
227        Message message =
228            ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
229        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
230      }
231    
232    
233    
234      /**
235       * {@inheritDoc}
236       */
237      @Override()
238      public boolean authPasswordMatches(ByteString plaintextPassword,
239                                         String authInfo, String authValue)
240      {
241        // This storage scheme does not support the authentication password syntax.
242        return false;
243      }
244    
245    
246    
247      /**
248       * {@inheritDoc}
249       */
250      @Override()
251      public boolean isReversible()
252      {
253        return false;
254      }
255    
256    
257    
258      /**
259       * {@inheritDoc}
260       */
261      @Override()
262      public ByteString getPlaintextValue(ByteString storedPassword)
263             throws DirectoryException
264      {
265        Message message =
266            ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_CRYPT);
267        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
268      }
269    
270    
271    
272      /**
273       * {@inheritDoc}
274       */
275      @Override()
276      public ByteString getAuthPasswordPlaintextValue(String authInfo,
277                                                      String authValue)
278             throws DirectoryException
279      {
280        Message message =
281            ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
282        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
283      }
284    
285    
286    
287      /**
288       * {@inheritDoc}
289       */
290      @Override()
291      public boolean isStorageSchemeSecure()
292      {
293        // FIXME:
294        // Technically, this isn't quite in keeping with the original spirit of
295        // this method, since the point was to determine whether the scheme could
296        // be trivially reversed.  I'm not sure I would put crypt into that
297        // category, but it's certainly a lot more vulnerable to lookup tables
298        // than most other algorithms.  I'd say we can keep it this way for now,
299        // but it might be something to reconsider later.
300        //
301        // Currently, this method is unused.  However, the intended purpose is
302        // eventually for use in issue #321, where we could do things like prevent
303        // even authorized users from seeing the password value over an insecure
304        // connection if it isn't considered secure.
305    
306        return false;
307      }
308    }
309