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