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