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 2015-2016 ForgeRock AS.
015 */
016package org.opends.server.extensions;
017
018
019import org.forgerock.i18n.LocalizableMessage;
020import org.forgerock.i18n.slf4j.LocalizedLogger;
021import org.forgerock.opendj.config.server.ConfigChangeResult;
022import org.forgerock.opendj.config.server.ConfigException;
023import org.forgerock.opendj.ldap.ByteSequence;
024import org.forgerock.opendj.ldap.ByteString;
025import org.forgerock.opendj.ldap.ResultCode;
026import org.opends.server.admin.server.ConfigurationChangeListener;
027import org.opends.server.admin.std.server.BcryptPasswordStorageSchemeCfg;
028import org.opends.server.api.PasswordStorageScheme;
029import org.opends.server.types.DirectoryException;
030import org.opends.server.types.InitializationException;
031
032import java.util.List;
033
034import static org.opends.messages.ExtensionMessages.*;
035import static org.opends.server.extensions.ExtensionsConstants.*;
036
037
038/**
039 * This class defines a Directory Server password storage scheme that will
040 * encode values using the Blowfish reversible encryption algorithm.  This
041 * implementation supports only the user password syntax and not the auth
042 * password syntax.
043 */
044public class BcryptPasswordStorageScheme
045       extends PasswordStorageScheme<BcryptPasswordStorageSchemeCfg>
046    implements ConfigurationChangeListener<BcryptPasswordStorageSchemeCfg>
047{
048  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
049  private static final String CLASS_NAME = BcryptPasswordStorageScheme.class.getName();;
050  /** The current configuration for this storage scheme. */
051  private volatile BcryptPasswordStorageSchemeCfg config;
052
053  /**
054   * Creates a new instance of this password storage scheme.  Note that no
055   * initialization should be performed here, as all initialization should be
056   * done in the {@link #initializePasswordStorageScheme(BcryptPasswordStorageSchemeCfg)} method.
057   */
058  public BcryptPasswordStorageScheme()
059  {
060  }
061
062
063  @Override
064  public void initializePasswordStorageScheme(BcryptPasswordStorageSchemeCfg configuration)
065         throws ConfigException, InitializationException
066  {
067    this.config = configuration;
068    config.addBcryptChangeListener(this);
069  }
070
071
072  @Override
073  public String getStorageSchemeName()
074  {
075    return STORAGE_SCHEME_NAME_BCRYPT;
076  }
077
078
079  @Override
080  public boolean isConfigurationChangeAcceptable(BcryptPasswordStorageSchemeCfg configuration,
081                                                 List<LocalizableMessage> unacceptableReasons)
082  {
083    return true;
084  }
085
086
087  @Override
088  public ConfigChangeResult applyConfigurationChange(BcryptPasswordStorageSchemeCfg configuration)
089  {
090    this.config = configuration;
091    return new ConfigChangeResult();
092  }
093
094
095  @Override
096  public ByteString encodePassword(ByteSequence plaintext)
097         throws DirectoryException
098  {
099    String salt = BCrypt.gensalt(config.getBcryptCost());
100    String hashed_password = BCrypt.hashpw(plaintext.toByteArray(), salt);
101    return ByteString.valueOfUtf8(hashed_password);
102  }
103
104
105  @Override
106  public ByteString encodePasswordWithScheme(ByteSequence plaintext)
107         throws DirectoryException
108  {
109    return ByteString.valueOfUtf8('{' + getStorageSchemeName() + '}' +  encodePassword(plaintext));
110  }
111
112
113  @Override
114  public boolean passwordMatches(ByteSequence plaintextPassword,
115                                 ByteSequence storedPassword)
116  {
117    try
118    {
119      return BCrypt.checkpw(plaintextPassword.toString(), storedPassword.toString());
120    }
121    catch (IllegalArgumentException e)
122    {
123      logger.traceException(e);
124      logger.error(ERR_PWSCHEME_INVALID_STORED_PASSWORD, e);
125      return false;
126    }
127  }
128
129
130  @Override
131  public boolean isReversible()
132  {
133    return false;
134  }
135
136
137  @Override
138  public ByteString getPlaintextValue(ByteSequence storedPassword)
139         throws DirectoryException
140  {
141    LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(getStorageSchemeName());
142    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
143  }
144
145
146  @Override
147  public boolean supportsAuthPasswordSyntax()
148  {
149    return false;
150  }
151
152
153  @Override
154  public ByteString encodeAuthPassword(ByteSequence plaintext)
155         throws DirectoryException
156  {
157    LocalizableMessage message =
158        ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
159    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
160  }
161
162
163  @Override
164  public boolean authPasswordMatches(ByteSequence plaintextPassword,
165                                     String authInfo, String authValue)
166  {
167    return false;
168  }
169
170
171  @Override
172  public ByteString getAuthPasswordPlaintextValue(String authInfo,
173                                                  String authValue)
174         throws DirectoryException
175  {
176    LocalizableMessage message =
177        ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
178    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
179  }
180
181
182  @Override
183  public boolean isStorageSchemeSecure()
184  {
185    return true;
186  }
187}
188