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-2016 ForgeRock AS.
016 */
017package org.opends.server.extensions;
018
019import java.security.MessageDigest;
020import java.util.Arrays;
021
022import org.forgerock.i18n.LocalizableMessage;
023import org.forgerock.opendj.server.config.server.SHA1PasswordStorageSchemeCfg;
024import org.opends.server.api.PasswordStorageScheme;
025import org.forgerock.opendj.config.server.ConfigException;
026import org.opends.server.core.DirectoryServer;
027import org.forgerock.i18n.slf4j.LocalizedLogger;
028import org.opends.server.types.*;
029import org.forgerock.opendj.ldap.ResultCode;
030import org.forgerock.opendj.ldap.ByteString;
031import org.forgerock.opendj.ldap.ByteSequence;
032import org.opends.server.util.Base64;
033
034import static org.opends.messages.ExtensionMessages.*;
035import static org.opends.server.extensions.ExtensionsConstants.*;
036import static org.opends.server.util.StaticUtils.*;
037
038/**
039 * This class defines a Directory Server password storage scheme based on the
040 * SHA-1 algorithm defined in FIPS 180-1.  This is a one-way digest algorithm
041 * so there is no way to retrieve the original clear-text version of the
042 * password from the hashed value (although this means that it is not suitable
043 * for things that need the clear-text password like DIGEST-MD5).  This
044 * implementation does not perform any salting, which means that it is more
045 * vulnerable to dictionary attacks than salted variants.
046 */
047public class SHA1PasswordStorageScheme
048       extends PasswordStorageScheme<SHA1PasswordStorageSchemeCfg>
049{
050  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
051
052  /** The fully-qualified name of this class. */
053  private static final String CLASS_NAME =
054       "org.opends.server.extensions.SHA1PasswordStorageScheme";
055
056  /** The message digest that will actually be used to generate the SHA-1 hashes. */
057  private MessageDigest messageDigest;
058
059  /** The lock used to provide threadsafe access to the message digest. */
060  private Object digestLock;
061
062  /**
063   * Creates a new instance of this password storage scheme.  Note that no
064   * initialization should be performed here, as all initialization should be
065   * done in the <CODE>initializePasswordStorageScheme</CODE> method.
066   */
067  public SHA1PasswordStorageScheme()
068  {
069    super();
070  }
071
072  @Override
073  public void initializePasswordStorageScheme(
074                   SHA1PasswordStorageSchemeCfg configuration)
075         throws ConfigException, InitializationException
076  {
077    try
078    {
079      messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_SHA_1);
080    }
081    catch (Exception e)
082    {
083      logger.traceException(e);
084
085      LocalizableMessage message = ERR_PWSCHEME_CANNOT_INITIALIZE_MESSAGE_DIGEST.get(
086          MESSAGE_DIGEST_ALGORITHM_SHA_1, e);
087      throw new InitializationException(message, e);
088    }
089
090    digestLock = new Object();
091  }
092
093  @Override
094  public String getStorageSchemeName()
095  {
096    return STORAGE_SCHEME_NAME_SHA_1;
097  }
098
099  @Override
100  public ByteString encodePassword(ByteSequence plaintext)
101         throws DirectoryException
102  {
103    byte[] digestBytes;
104    byte[] plaintextBytes = null;
105
106    synchronized (digestLock)
107    {
108      try
109      {
110        // TODO: Can we avoid this copy?
111        plaintextBytes = plaintext.toByteArray();
112        digestBytes = messageDigest.digest(plaintextBytes);
113      }
114      catch (Exception e)
115      {
116        logger.traceException(e);
117
118        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
119            CLASS_NAME, getExceptionMessage(e));
120        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
121                                     message, e);
122      }
123      finally
124      {
125        if (plaintextBytes != null)
126        {
127          Arrays.fill(plaintextBytes, (byte) 0);
128        }
129      }
130    }
131
132    return ByteString.valueOfUtf8(Base64.encode(digestBytes));
133  }
134
135  @Override
136  public ByteString encodePasswordWithScheme(ByteSequence plaintext)
137         throws DirectoryException
138  {
139    StringBuilder buffer = new StringBuilder();
140    buffer.append('{');
141    buffer.append(STORAGE_SCHEME_NAME_SHA_1);
142    buffer.append('}');
143
144    // TODO: Can we avoid this copy?
145    byte[] plaintextBytes = null;
146    byte[] digestBytes;
147
148    synchronized (digestLock)
149    {
150      try
151      {
152        plaintextBytes = plaintext.toByteArray();
153        digestBytes = messageDigest.digest(plaintextBytes);
154      }
155      catch (Exception e)
156      {
157        logger.traceException(e);
158
159        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
160            CLASS_NAME, getExceptionMessage(e));
161        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
162                                     message, e);
163      }
164      finally
165      {
166        if (plaintextBytes != null)
167        {
168          Arrays.fill(plaintextBytes, (byte) 0);
169        }
170      }
171    }
172
173    buffer.append(Base64.encode(digestBytes));
174
175    return ByteString.valueOfUtf8(buffer);
176  }
177
178  @Override
179  public boolean passwordMatches(ByteSequence plaintextPassword,
180                                 ByteSequence storedPassword)
181  {
182    // TODO: Can we avoid this copy?
183    byte[] plaintextPasswordBytes = null;
184    ByteString userPWDigestBytes;
185
186    synchronized (digestLock)
187    {
188      try
189      {
190        plaintextPasswordBytes = plaintextPassword.toByteArray();
191        userPWDigestBytes =
192            ByteString.wrap(messageDigest.digest(plaintextPasswordBytes));
193      }
194      catch (Exception e)
195      {
196        logger.traceException(e);
197
198        return false;
199      }
200      finally
201      {
202        if (plaintextPasswordBytes != null)
203        {
204          Arrays.fill(plaintextPasswordBytes, (byte) 0);
205        }
206      }
207    }
208
209    ByteString storedPWDigestBytes;
210    try
211    {
212      storedPWDigestBytes =
213          ByteString.wrap(Base64.decode(storedPassword.toString()));
214    }
215    catch (Exception e)
216    {
217      logger.traceException(e);
218      logger.error(ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD, storedPassword, e);
219      return false;
220    }
221
222    return userPWDigestBytes.equals(storedPWDigestBytes);
223  }
224
225  @Override
226  public boolean supportsAuthPasswordSyntax()
227  {
228    // This storage scheme does not support the authentication password syntax.
229    return false;
230  }
231
232  @Override
233  public ByteString encodeAuthPassword(ByteSequence plaintext)
234         throws DirectoryException
235  {
236    LocalizableMessage message =
237        ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
238    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
239  }
240
241  @Override
242  public boolean authPasswordMatches(ByteSequence plaintextPassword,
243                                     String authInfo, String authValue)
244  {
245    // This storage scheme does not support the authentication password syntax.
246    return false;
247  }
248
249  @Override
250  public boolean isReversible()
251  {
252    return false;
253  }
254
255  @Override
256  public ByteString getPlaintextValue(ByteSequence storedPassword)
257         throws DirectoryException
258  {
259    LocalizableMessage message =
260        ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_SHA_1);
261    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
262  }
263
264  @Override
265  public ByteString getAuthPasswordPlaintextValue(String authInfo,
266                                                  String authValue)
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  @Override
275  public boolean isStorageSchemeSecure()
276  {
277    // SHA-1 should be considered secure.
278    return true;
279  }
280}