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.SHA1PasswordStorageSchemeCfg;
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 * SHA-1 algorithm defined in FIPS 180-1.  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 SHA1PasswordStorageScheme
052       extends PasswordStorageScheme<SHA1PasswordStorageSchemeCfg>
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.SHA1PasswordStorageScheme";
061
062
063
064  /** The message digest that will actually be used to generate the SHA-1 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 SHA1PasswordStorageScheme()
078  {
079    super();
080  }
081
082
083
084  /** {@inheritDoc} */
085  @Override
086  public void initializePasswordStorageScheme(
087                   SHA1PasswordStorageSchemeCfg configuration)
088         throws ConfigException, InitializationException
089  {
090    try
091    {
092      messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
093    }
094    catch (Exception e)
095    {
096      logger.traceException(e);
097
098      LocalizableMessage message = ERR_PWSCHEME_CANNOT_INITIALIZE_MESSAGE_DIGEST.get(
099          MESSAGE_DIGEST_ALGORITHM_SHA_1, e);
100      throw new InitializationException(message, e);
101    }
102
103    digestLock = new Object();
104  }
105
106
107
108  /** {@inheritDoc} */
109  @Override
110  public String getStorageSchemeName()
111  {
112    return STORAGE_SCHEME_NAME_SHA_1;
113  }
114
115
116
117  /** {@inheritDoc} */
118  @Override
119  public ByteString encodePassword(ByteSequence plaintext)
120         throws DirectoryException
121  {
122    byte[] digestBytes;
123    byte[] plaintextBytes = null;
124
125    synchronized (digestLock)
126    {
127      try
128      {
129        // TODO: Can we avoid this copy?
130        plaintextBytes = plaintext.toByteArray();
131        digestBytes = messageDigest.digest(plaintextBytes);
132      }
133      catch (Exception e)
134      {
135        logger.traceException(e);
136
137        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
138            CLASS_NAME, getExceptionMessage(e));
139        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
140                                     message, e);
141      }
142      finally
143      {
144        if (plaintextBytes != null)
145        {
146          Arrays.fill(plaintextBytes, (byte) 0);
147        }
148      }
149    }
150
151    return ByteString.valueOfUtf8(Base64.encode(digestBytes));
152  }
153
154
155
156  /** {@inheritDoc} */
157  @Override
158  public ByteString encodePasswordWithScheme(ByteSequence plaintext)
159         throws DirectoryException
160  {
161    StringBuilder buffer = new StringBuilder();
162    buffer.append('{');
163    buffer.append(STORAGE_SCHEME_NAME_SHA_1);
164    buffer.append('}');
165
166    // TODO: Can we avoid this copy?
167    byte[] plaintextBytes = null;
168    byte[] digestBytes;
169
170    synchronized (digestLock)
171    {
172      try
173      {
174        plaintextBytes = plaintext.toByteArray();
175        digestBytes = messageDigest.digest(plaintextBytes);
176      }
177      catch (Exception e)
178      {
179        logger.traceException(e);
180
181        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
182            CLASS_NAME, getExceptionMessage(e));
183        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
184                                     message, e);
185      }
186      finally
187      {
188        if (plaintextBytes != null)
189        {
190          Arrays.fill(plaintextBytes, (byte) 0);
191        }
192      }
193    }
194
195    buffer.append(Base64.encode(digestBytes));
196
197    return ByteString.valueOfUtf8(buffer);
198  }
199
200
201
202  /** {@inheritDoc} */
203  @Override
204  public boolean passwordMatches(ByteSequence plaintextPassword,
205                                 ByteSequence storedPassword)
206  {
207    // TODO: Can we avoid this copy?
208    byte[] plaintextPasswordBytes = null;
209    ByteString userPWDigestBytes;
210
211    synchronized (digestLock)
212    {
213      try
214      {
215        plaintextPasswordBytes = plaintextPassword.toByteArray();
216        userPWDigestBytes =
217            ByteString.wrap(messageDigest.digest(plaintextPasswordBytes));
218      }
219      catch (Exception e)
220      {
221        logger.traceException(e);
222
223        return false;
224      }
225      finally
226      {
227        if (plaintextPasswordBytes != null)
228        {
229          Arrays.fill(plaintextPasswordBytes, (byte) 0);
230        }
231      }
232    }
233
234    ByteString storedPWDigestBytes;
235    try
236    {
237      storedPWDigestBytes =
238          ByteString.wrap(Base64.decode(storedPassword.toString()));
239    }
240    catch (Exception e)
241    {
242      logger.traceException(e);
243      logger.error(ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD, storedPassword, e);
244      return false;
245    }
246
247    return userPWDigestBytes.equals(storedPWDigestBytes);
248  }
249
250
251
252  /** {@inheritDoc} */
253  @Override
254  public boolean supportsAuthPasswordSyntax()
255  {
256    // This storage scheme does not support the authentication password syntax.
257    return false;
258  }
259
260
261
262  /** {@inheritDoc} */
263  @Override
264  public ByteString encodeAuthPassword(ByteSequence plaintext)
265         throws DirectoryException
266  {
267    LocalizableMessage message =
268        ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
269    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
270  }
271
272
273
274  /** {@inheritDoc} */
275  @Override
276  public boolean authPasswordMatches(ByteSequence plaintextPassword,
277                                     String authInfo, String authValue)
278  {
279    // This storage scheme does not support the authentication password syntax.
280    return false;
281  }
282
283
284
285  /** {@inheritDoc} */
286  @Override
287  public boolean isReversible()
288  {
289    return false;
290  }
291
292
293
294  /** {@inheritDoc} */
295  @Override
296  public ByteString getPlaintextValue(ByteSequence storedPassword)
297         throws DirectoryException
298  {
299    LocalizableMessage message =
300        ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_SHA_1);
301    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
302  }
303
304
305
306  /** {@inheritDoc} */
307  @Override
308  public ByteString getAuthPasswordPlaintextValue(String authInfo,
309                                                  String authValue)
310         throws DirectoryException
311  {
312    LocalizableMessage message =
313        ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
314    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
315  }
316
317
318
319  /** {@inheritDoc} */
320  @Override
321  public boolean isStorageSchemeSecure()
322  {
323    // SHA-1 should be considered secure.
324    return true;
325  }
326}
327