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}