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 2008 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.server.extensions;
018
019import org.forgerock.i18n.LocalizableMessage;
020import org.forgerock.opendj.server.config.server.AESPasswordStorageSchemeCfg;
021import org.opends.server.api.PasswordStorageScheme;
022import org.forgerock.opendj.config.server.ConfigException;
023import org.opends.server.core.DirectoryServer;
024import org.forgerock.i18n.slf4j.LocalizedLogger;
025import org.opends.server.types.*;
026import org.forgerock.opendj.ldap.ResultCode;
027import org.forgerock.opendj.ldap.ByteString;
028import org.forgerock.opendj.ldap.ByteSequence;
029import org.opends.server.util.Base64;
030
031import java.util.Arrays;
032
033import static org.opends.messages.ExtensionMessages.*;
034import static org.opends.server.extensions.ExtensionsConstants.*;
035import static org.opends.server.util.StaticUtils.*;
036
037/**
038 * This class defines a Directory Server password storage scheme that will
039 * encode values using the AES reversible encryption algorithm.  This
040 * implementation supports only the user password syntax and not the auth
041 * password syntax.
042 */
043public class AESPasswordStorageScheme
044       extends PasswordStorageScheme<AESPasswordStorageSchemeCfg>
045{
046  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
047
048  /**
049   * The reference to the Directory Server crypto manager that we will use to
050   * handle the encryption/decryption.
051   */
052  private CryptoManager cryptoManager;
053
054  /**
055   * Creates a new instance of this password storage scheme.  Note that no
056   * initialization should be performed here, as all initialization should be
057   * done in the {@code initializePasswordStorageScheme} method.
058   */
059  public AESPasswordStorageScheme()
060  {
061    super();
062  }
063
064  @Override
065  public void initializePasswordStorageScheme(
066                   AESPasswordStorageSchemeCfg configuration)
067         throws ConfigException, InitializationException
068  {
069    cryptoManager = DirectoryServer.getCryptoManager();
070  }
071
072  @Override
073  public String getStorageSchemeName()
074  {
075    return STORAGE_SCHEME_NAME_AES;
076  }
077
078  @Override
079  public ByteString encodePassword(ByteSequence plaintext)
080         throws DirectoryException
081  {
082    byte[] plaintextBytes = null;
083    try
084    {
085      // TODO: Can we avoid this copy?
086      plaintextBytes = plaintext.toByteArray();
087      byte[] encodedBytes = cryptoManager.encrypt(CIPHER_TRANSFORMATION_AES,
088                                                  KEY_SIZE_AES,
089                                                  plaintextBytes);
090      return ByteString.valueOfUtf8(Base64.encode(encodedBytes));
091    }
092    catch (Exception e)
093    {
094      logger.traceException(e);
095
096      LocalizableMessage m = ERR_PWSCHEME_CANNOT_ENCRYPT.get(STORAGE_SCHEME_NAME_AES,
097                                                  getExceptionMessage(e));
098      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
099                                   m, e);
100    }
101    finally
102    {
103      if (plaintextBytes != null)
104      {
105        Arrays.fill(plaintextBytes, (byte) 0);
106      }
107    }
108  }
109
110  @Override
111  public ByteString encodePasswordWithScheme(ByteSequence plaintext)
112         throws DirectoryException
113  {
114    StringBuilder buffer = new StringBuilder();
115    buffer.append('{');
116    buffer.append(STORAGE_SCHEME_NAME_AES);
117    buffer.append('}');
118    byte[] plaintextBytes = null;
119    try
120    {
121      // TODO: Can we avoid this copy?
122      plaintextBytes = plaintext.toByteArray();
123      byte[] encodedBytes = cryptoManager.encrypt(CIPHER_TRANSFORMATION_AES,
124                                                  KEY_SIZE_AES,
125                                                  plaintextBytes);
126      buffer.append(Base64.encode(encodedBytes));
127    }
128    catch (Exception e)
129    {
130      logger.traceException(e);
131
132      LocalizableMessage m = ERR_PWSCHEME_CANNOT_ENCRYPT.get(STORAGE_SCHEME_NAME_AES,
133                                                  getExceptionMessage(e));
134      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
135                                   m, e);
136    }
137    finally
138    {
139      if (plaintextBytes != null)
140      {
141        Arrays.fill(plaintextBytes, (byte) 0);
142      }
143    }
144
145    return ByteString.valueOfUtf8(buffer);
146  }
147
148  @Override
149  public boolean passwordMatches(ByteSequence plaintextPassword,
150                                 ByteSequence storedPassword)
151  {
152    try
153    {
154      ByteString decryptedPassword =
155          ByteString.wrap(cryptoManager.decrypt(
156               Base64.decode(storedPassword.toString())));
157      return plaintextPassword.equals(decryptedPassword);
158    }
159    catch (Exception e)
160    {
161      logger.traceException(e);
162
163      return false;
164    }
165  }
166
167  @Override
168  public boolean isReversible()
169  {
170    return true;
171  }
172
173  @Override
174  public ByteString getPlaintextValue(ByteSequence storedPassword)
175         throws DirectoryException
176  {
177    try
178    {
179      byte[] decryptedPassword =
180           cryptoManager.decrypt(Base64.decode(storedPassword.toString()));
181      return ByteString.wrap(decryptedPassword);
182    }
183    catch (Exception e)
184    {
185      logger.traceException(e);
186
187      LocalizableMessage m = ERR_PWSCHEME_CANNOT_DECRYPT.get(STORAGE_SCHEME_NAME_AES,
188                                                  getExceptionMessage(e));
189      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
190                                   m, e);
191    }
192  }
193
194  @Override
195  public boolean supportsAuthPasswordSyntax()
196  {
197    // This storage scheme does not support the authentication password syntax.
198    return false;
199  }
200
201  @Override
202  public ByteString encodeAuthPassword(ByteSequence plaintext)
203         throws DirectoryException
204  {
205    LocalizableMessage message =
206        ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
207    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
208  }
209
210  @Override
211  public boolean authPasswordMatches(ByteSequence plaintextPassword,
212                                     String authInfo, String authValue)
213  {
214    // This storage scheme does not support the authentication password syntax.
215    return false;
216  }
217
218  @Override
219  public ByteString getAuthPasswordPlaintextValue(String authInfo,
220                                                  String authValue)
221         throws DirectoryException
222  {
223    LocalizableMessage message =
224        ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
225    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
226  }
227
228  @Override
229  public boolean isStorageSchemeSecure()
230  {
231    // This password storage scheme should be considered secure.
232    return true;
233  }
234}