001/*
002 * Copyright 2020-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2020-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds;
037
038
039
040import java.io.Serializable;
041import java.security.GeneralSecurityException;
042import java.text.ParseException;
043import java.util.Arrays;
044import java.util.concurrent.atomic.AtomicReference;
045import javax.crypto.SecretKey;
046import javax.crypto.SecretKeyFactory;
047import javax.crypto.spec.PBEKeySpec;
048import javax.crypto.spec.SecretKeySpec;
049import javax.security.auth.Destroyable;
050
051import com.unboundid.util.CryptoHelper;
052import com.unboundid.util.Debug;
053import com.unboundid.util.NotMutable;
054import com.unboundid.util.NotNull;
055import com.unboundid.util.StaticUtils;
056import com.unboundid.util.ThreadLocalSecureRandom;
057import com.unboundid.util.ThreadSafety;
058import com.unboundid.util.ThreadSafetyLevel;
059import com.unboundid.util.Validator;
060
061import static com.unboundid.ldap.sdk.unboundidds.AES256EncodedPassword.*;
062
063
064
065/**
066 * This class provides a data structure that may be used to hold a reusable
067 * secret key for use in conjunction with {@link AES256EncodedPassword}
068 * objects.  Reusing a secret key avoids the (potentially significant) cost of
069 * generating it for each encryption and decryption operation.
070 * <BR>
071 * <BLOCKQUOTE>
072 *   <B>NOTE:</B>  This class, and other classes within the
073 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
074 *   supported for use against Ping Identity, UnboundID, and
075 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
076 *   for proprietary functionality or for external specifications that are not
077 *   considered stable or mature enough to be guaranteed to work in an
078 *   interoperable way with other types of LDAP servers.
079 * </BLOCKQUOTE>
080 */
081@NotMutable
082@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
083public final class AES256EncodedPasswordSecretKey
084       implements Serializable
085{
086  /**
087   * The serial version UID for this serializable class.
088   */
089  private static final long serialVersionUID = -5993762526459847323L;
090
091
092
093  // A references to the secret key that was generated.
094  @NotNull private final AtomicReference<SecretKey> secretKeyRef;
095
096  // The bytes that comprise the raw encryption settings definition ID whose
097  // passphrase was used to generate the secret key.
098  @NotNull private final byte[] encryptionSettingsDefinitionID;
099
100  // The salt used in the course of generating the secret key.
101  @NotNull private final byte[] keyFactorySalt;
102
103
104
105  /**
106   * Creates a new ASE256 secret key object from the provided information.
107   *
108   * @param  encryptionSettingsDefinitionID
109   *              The bytes that comprise the raw encryption settings definition
110   *              ID whose passphrase was used to generate the secret key.  It
111   *              must not be {@code null} or empty, and its length must be less
112   *              than or equal to 255 bytes.
113   * @param  keyFactorySalt
114   *              The salt used to generate the encryption key from the
115   *              encryption settings definition passphrase.  It must not be
116   *              {@code null} and it must have a length of exactly 16 bytes.
117   * @param  secretKey
118   *              The secret key that was generated from the salt and the
119   *              encryption settings definition passphrase.
120   */
121  private AES256EncodedPasswordSecretKey(
122              @NotNull final byte[] encryptionSettingsDefinitionID,
123              @NotNull final byte[] keyFactorySalt,
124              @NotNull final SecretKey secretKey)
125  {
126    this.encryptionSettingsDefinitionID = encryptionSettingsDefinitionID;
127    this.keyFactorySalt = keyFactorySalt;
128
129    secretKeyRef = new AtomicReference<>(secretKey);
130  }
131
132
133
134  /**
135   * Generates an AES256 secret key from the provided information.
136   *
137   * @param  encryptionSettingsDefinitionID
138   *              A string with the hexadecimal representation of the
139   *              encryption settings definition whose passphrase was used to
140   *              generate the encoded password.  It must not be
141   *              {@code null} or empty, and it must represent a valid
142   *              hexadecimal string whose length is an even number less than
143   *              or equal to 510 bytes.
144   * @param  encryptionSettingsDefinitionPassphrase
145   *              The passphrase associated with the specified encryption
146   *              settings definition.  It must not be {@code null} or empty.
147   *
148   * @return  The AES256 secret key that was generated.
149   *
150   * @throws  GeneralSecurityException  If a problem occurs while trying to
151   *                                    generate the secret key.
152   *
153   * @throws  ParseException  If the provided encryption settings ID cannot be
154   *                          parsed as a hexadecimal string.
155   */
156  @NotNull()
157  public static AES256EncodedPasswordSecretKey generate(
158              @NotNull final String encryptionSettingsDefinitionID,
159              @NotNull final String encryptionSettingsDefinitionPassphrase)
160         throws GeneralSecurityException, ParseException
161  {
162    final char[] passphraseChars =
163         encryptionSettingsDefinitionPassphrase.toCharArray();
164    try
165    {
166      return generate(
167           StaticUtils.fromHex(encryptionSettingsDefinitionID),
168           passphraseChars);
169    }
170    finally
171    {
172      Arrays.fill(passphraseChars, '\u0000');
173    }
174  }
175
176
177
178  /**
179   * Generates an AES256 secret key from the provided information.
180   *
181   * @param  encryptionSettingsDefinitionID
182   *              The bytes that comprise the raw encryption settings definition
183   *              ID whose passphrase was used to generate the encoded password.
184   *              It must not be {@code null} or empty, and its length must be
185   *              less than or equal to 255 bytes.
186   * @param  encryptionSettingsDefinitionPassphrase
187   *              The passphrase associated with the specified encryption
188   *              settings definition.  It must not be {@code null} or empty.
189   *
190   * @return  The AES256 secret key that was generated.
191   *
192   * @throws  GeneralSecurityException  If a problem occurs while trying to
193   *                                    generate the secret key.
194   */
195  @NotNull()
196  public static AES256EncodedPasswordSecretKey generate(
197              @NotNull final byte[] encryptionSettingsDefinitionID,
198              @NotNull final char[] encryptionSettingsDefinitionPassphrase)
199         throws GeneralSecurityException
200  {
201    final byte[] keyFactorySalt =
202         new byte[ENCODING_VERSION_0_KEY_FACTORY_SALT_LENGTH_BYTES];
203    ThreadLocalSecureRandom.get().nextBytes(keyFactorySalt);
204
205    return generate(encryptionSettingsDefinitionID,
206         encryptionSettingsDefinitionPassphrase, keyFactorySalt);
207  }
208
209
210
211  /**
212   * Generates an AES256 secret key from the provided information.
213   *
214   * @param  encryptionSettingsDefinitionID
215   *              The bytes that comprise the raw encryption settings definition
216   *              ID whose passphrase was used to generate the encoded password.
217   *              It must not be {@code null} or empty, and its length must be
218   *              less than or equal to 255 bytes.
219   * @param  encryptionSettingsDefinitionPassphrase
220   *              The passphrase associated with the specified encryption
221   *              settings definition.  It must not be {@code null} or empty.
222   * @param  keyFactorySalt
223   *              The salt used to generate the encryption key from the
224   *              encryption settings definition passphrase.  It must not be
225   *              {@code null} and it must have a length of exactly 16 bytes.
226   *
227   * @return  The AES256 secret key that was generated.
228   *
229   * @throws  GeneralSecurityException  If a problem occurs while trying to
230   *                                    generate the secret key.
231   */
232  @NotNull()
233  public static AES256EncodedPasswordSecretKey generate(
234              @NotNull final byte[] encryptionSettingsDefinitionID,
235              @NotNull final char[] encryptionSettingsDefinitionPassphrase,
236              @NotNull final byte[] keyFactorySalt)
237         throws GeneralSecurityException
238  {
239    Validator.ensureNotNullOrEmpty(encryptionSettingsDefinitionID,
240         "AES256EncodedPasswordSecretKey.encryptionSettingsDefinitionID must " +
241              "not be null or empty.");
242    Validator.ensureTrue((encryptionSettingsDefinitionID.length <= 255),
243         "AES256EncodedPasswordSecretKey.encryptionSettingsDefinitionID must " +
244              "have a length that is between 1 and 255 bytes, inclusive.");
245
246    Validator.ensureNotNullOrEmpty(encryptionSettingsDefinitionPassphrase,
247         "AES256EncodedPasswordSecretKey." +
248              "encryptionSettingsDefinitionPassphrase must not be null or " +
249              "empty.");
250    Validator.ensureNotNull(keyFactorySalt,
251         "AES256EncodedPasswordSecretKey.keyFactorySalt must not be null.");
252    Validator.ensureTrue((keyFactorySalt.length == 16),
253         "AES256EncodedPasswordSecretKey.keyFactorySalt must have a length " +
254              "of exactly 16 bytes.");
255
256    final PBEKeySpec pbeKeySpec = new PBEKeySpec(
257         encryptionSettingsDefinitionPassphrase, keyFactorySalt,
258         ENCODING_VERSION_0_KEY_FACTORY_ITERATION_COUNT,
259         ENCODING_VERSION_0_GENERATED_KEY_LENGTH_BITS);
260
261    final SecretKeyFactory secretKeyFactory = CryptoHelper.getSecretKeyFactory(
262         ENCODING_VERSION_0_KEY_FACTORY_ALGORITHM);
263
264    final SecretKey secretKey = new SecretKeySpec(
265         secretKeyFactory.generateSecret(pbeKeySpec).getEncoded(),
266         ENCODING_VERSION_0_CIPHER_ALGORITHM);
267
268    return new AES256EncodedPasswordSecretKey(encryptionSettingsDefinitionID,
269         keyFactorySalt, secretKey);
270  }
271
272
273
274  /**
275   * Retrieves the bytes that comprise the raw identifier for the encryption
276   * settings definition whose passphrase was used to generate the secret key.
277   *
278   * @return  A bytes that comprise the raw identifier for the encryption
279   *          settings definition whose passphrase was used to generate the
280   *          secret key.
281   */
282  @NotNull()
283  public byte[] getEncryptionSettingsDefinitionID()
284  {
285    return encryptionSettingsDefinitionID;
286  }
287
288
289
290  /**
291   * Retrieves the salt used to generate the secret key from the encryption
292   * settings definition passphrase.
293   *
294   * @return  The salt used to generate the secret key from the encryption
295   *          settings definition passphrase.
296   */
297  @NotNull()
298  public byte[] getKeyFactorySalt()
299  {
300    return keyFactorySalt;
301  }
302
303
304
305  /**
306   * Retrieves the secret key that was generated.  This method must not be
307   * called after the {@link #destroy} method has been called.
308   *
309   * @return  The secret key that was generated.
310   */
311  @NotNull()
312  public SecretKey getSecretKey()
313  {
314    final SecretKey secretKey = secretKeyRef.get();
315    if (secretKey == null)
316    {
317      Validator.violation("An AES256EncodedPasswordSecretKey instance must " +
318           "not be used after it has been destroyed.");
319    }
320
321    return secretKey;
322  }
323
324
325
326  /**
327   * Destroys this secret key.  The key must not be used after it has been
328   * destroyed.
329   */
330  public void destroy()
331  {
332    final SecretKey secretKey = secretKeyRef.getAndSet(null);
333    if ((secretKey != null) && (secretKey instanceof Destroyable))
334    {
335      try
336      {
337        final Destroyable destroyableSecretKey = (Destroyable) secretKey;
338        destroyableSecretKey.destroy();
339      }
340      catch (final Exception e)
341      {
342        Debug.debugException(e);
343      }
344    }
345  }
346
347
348
349  /**
350   * Retrieves a string representation of this AES256 encoded password secret
351   * key.
352   *
353   * @return  A string representation of this AES256 encoded password secret
354   *          key.
355   */
356  @NotNull()
357  @Override()
358  public String toString()
359  {
360    final StringBuilder buffer = new StringBuilder();
361    toString(buffer);
362    return buffer.toString();
363  }
364
365
366
367  /**
368   * Appends a string representation of this AES256 encoded password secret key
369   * to the provided buffer.
370   *
371   * @param  buffer  The buffer to which the information should be appended.
372   */
373  public void toString(@NotNull final StringBuilder buffer)
374  {
375    buffer.append("AES256EncodedPasswordSecretKey(" +
376         "encryptionSettingsDefinitionIDHex='");
377    StaticUtils.toHex(encryptionSettingsDefinitionID, buffer);
378    buffer.append("', keyFactorySaltBytesHex='");
379    StaticUtils.toHex(keyFactorySalt, buffer);
380    buffer.append("')");
381  }
382}