001/* 002 * Copyright 2017-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2017-2019 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.listener; 022 023 024 025import java.security.MessageDigest; 026import java.security.SecureRandom; 027import java.util.Arrays; 028import java.util.List; 029 030import com.unboundid.ldap.sdk.LDAPException; 031import com.unboundid.ldap.sdk.Modification; 032import com.unboundid.ldap.sdk.ReadOnlyEntry; 033import com.unboundid.ldap.sdk.ResultCode; 034import com.unboundid.util.ThreadSafety; 035import com.unboundid.util.ThreadSafetyLevel; 036import com.unboundid.util.Validator; 037 038import static com.unboundid.ldap.listener.ListenerMessages.*; 039 040 041 042/** 043 * This class provides an implementation of an in-memory directory server 044 * password encoder that uses a message digest to encode passwords. Encoded 045 * passwords will also include some number of randomly generated bytes, called a 046 * salt, to ensure that encoding the same password multiple times will yield 047 * multiple different encoded representations. 048 */ 049@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 050public final class SaltedMessageDigestInMemoryPasswordEncoder 051 extends InMemoryPasswordEncoder 052{ 053 // Indicates whether the salt should go after or before the clear-text 054 // password when generating the message digest. 055 private final boolean saltAfterClearPassword; 056 057 // Indicates whether the salt should go after or before the digest bytes 058 // when generating the final encoded representation. 059 private final boolean saltAfterMessageDigest; 060 061 // The length of the generated message digest, in bytes. 062 private final int digestLengthBytes; 063 064 // The number of salt bytes to generate. 065 private final int numSaltBytes; 066 067 // The message digest instance tha will be used to actually perform the 068 // encoding. 069 private final MessageDigest messageDigest; 070 071 // The secure random number generator used for generating salts. 072 private final SecureRandom random; 073 074 075 076 /** 077 * Creates a new instance of this in-memory directory server password encoder 078 * with the provided information. 079 * 080 * @param prefix The string that will appear at the 081 * beginning of encoded passwords. It must 082 * not be {@code null} or empty. 083 * @param outputFormatter The output formatter that will be used to 084 * format the encoded representation of 085 * clear-text passwords. It may be 086 * {@code null} if no special formatting 087 * should be applied to the raw bytes. 088 * @param messageDigest The message digest that will be used to 089 * actually perform the encoding. It must not 090 * be {@code null}. 091 * @param numSaltBytes The number of salt bytes to generate when 092 * encoding passwords. It must be greater 093 * than zero. 094 * @param saltAfterClearPassword Indicates whether the salt should be placed 095 * after or before the clear-text password 096 * when computing the message digest. If this 097 * is {@code true}, then the digest will be 098 * computed from the concatenation of the 099 * clear-text password and the salt, in that 100 * order. If this is {@code false}, then the 101 * digest will be computed from the 102 * concatenation of the salt and the 103 * clear-text password. 104 * @param saltAfterMessageDigest Indicates whether the salt should be placed 105 * after or before the computed digest when 106 * creating the encoded representation. If 107 * this is {@code true}, then the encoded 108 * password will consist of the concatenation 109 * of the computed message digest and the 110 * salt, in that order. If this is 111 * {@code false}, then the encoded password 112 * will consist of the concatenation of the 113 * salt and the message digest. 114 */ 115 public SaltedMessageDigestInMemoryPasswordEncoder(final String prefix, 116 final PasswordEncoderOutputFormatter outputFormatter, 117 final MessageDigest messageDigest, final int numSaltBytes, 118 final boolean saltAfterClearPassword, 119 final boolean saltAfterMessageDigest) 120 { 121 super(prefix, outputFormatter); 122 123 Validator.ensureNotNull(messageDigest); 124 this.messageDigest = messageDigest; 125 126 digestLengthBytes = messageDigest.getDigestLength(); 127 Validator.ensureTrue((digestLengthBytes > 0), 128 "The message digest use a fixed digest length, and that " + 129 "length must be greater than zero."); 130 131 this.numSaltBytes = numSaltBytes; 132 Validator.ensureTrue((numSaltBytes > 0), 133 "numSaltBytes must be greater than zero."); 134 135 this.saltAfterClearPassword = saltAfterClearPassword; 136 this.saltAfterMessageDigest = saltAfterMessageDigest; 137 138 random = new SecureRandom(); 139 } 140 141 142 143 /** 144 * Retrieves the digest algorithm that will be used when encoding passwords. 145 * 146 * @return The message digest 147 */ 148 public String getDigestAlgorithm() 149 { 150 return messageDigest.getAlgorithm(); 151 } 152 153 154 155 /** 156 * Retrieves the digest length, in bytes. 157 * 158 * @return The digest length, in bytes. 159 */ 160 public int getDigestLengthBytes() 161 { 162 return digestLengthBytes; 163 } 164 165 166 167 /** 168 * Retrieves the number of bytes of salt that will be generated when encoding 169 * a password. Note that this is used only when encoding new clear-text 170 * passwords. When comparing a clear-text password against an existing 171 * encoded representation, the number of salt bytes from the existing encoded 172 * password will be used. 173 * 174 * @return The number of bytes of salt that will be generated when encoding a 175 * password. 176 */ 177 public int getNumSaltBytes() 178 { 179 return numSaltBytes; 180 } 181 182 183 184 /** 185 * Indicates whether the salt should be appended or prepended to the 186 * clear-text password when computing the message digest. 187 * 188 * @return {@code true} if the salt should be appended to the clear-text 189 * password when computing the message digest, or {@code false} if 190 * the salt should be prepended to the clear-text password. 191 */ 192 public boolean isSaltAfterClearPassword() 193 { 194 return saltAfterClearPassword; 195 } 196 197 198 199 /** 200 * Indicates whether the salt should be appended or prepended to the digest 201 * when generating the encoded representation for the password. 202 * 203 * @return {@code true} if the salt should be appended to the digest when 204 * generating the encoded representation for the password, or 205 * {@code false} if the salt should be prepended to the digest. 206 */ 207 public boolean isSaltAfterMessageDigest() 208 { 209 return saltAfterMessageDigest; 210 } 211 212 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override() 218 protected byte[] encodePassword(final byte[] clearPassword, 219 final ReadOnlyEntry userEntry, 220 final List<Modification> modifications) 221 throws LDAPException 222 { 223 final byte[] salt = new byte[numSaltBytes]; 224 random.nextBytes(salt); 225 226 final byte[] saltedPassword; 227 if (saltAfterClearPassword) 228 { 229 saltedPassword = concatenate(clearPassword, salt); 230 } 231 else 232 { 233 saltedPassword = concatenate(salt, clearPassword); 234 } 235 236 final byte[] digest = messageDigest.digest(saltedPassword); 237 238 if (saltAfterMessageDigest) 239 { 240 return concatenate(digest, salt); 241 } 242 else 243 { 244 return concatenate(salt, digest); 245 } 246 } 247 248 249 250 /** 251 * Creates a new byte array that is a concatenation of the provided byte 252 * arrays. 253 * 254 * @param b1 The byte array to appear first in the concatenation. 255 * @param b2 The byte array to appear second in the concatenation. 256 * 257 * @return A byte array containing the concatenation. 258 */ 259 private static byte[] concatenate(final byte[] b1, final byte[] b2) 260 { 261 final byte[] combined = new byte[b1.length + b2.length]; 262 System.arraycopy(b1, 0, combined, 0, b1.length); 263 System.arraycopy(b2, 0, combined, b1.length, b2.length); 264 return combined; 265 } 266 267 268 269 /** 270 * {@inheritDoc} 271 */ 272 @Override() 273 protected void ensurePreEncodedPasswordAppearsValid( 274 final byte[] unPrefixedUnFormattedEncodedPasswordBytes, 275 final ReadOnlyEntry userEntry, 276 final List<Modification> modifications) 277 throws LDAPException 278 { 279 // Make sure that the encoded password is longer than the digest length 280 // so that there is room for some amount of salt. 281 if (unPrefixedUnFormattedEncodedPasswordBytes.length <= digestLengthBytes) 282 { 283 throw new LDAPException(ResultCode.PARAM_ERROR, 284 ERR_SALTED_DIGEST_PW_ENCODER_PRE_ENCODED_LENGTH_MISMATCH.get( 285 messageDigest.getAlgorithm(), 286 unPrefixedUnFormattedEncodedPasswordBytes.length, 287 (digestLengthBytes + 1))); 288 } 289 } 290 291 292 293 /** 294 * {@inheritDoc} 295 */ 296 @Override() 297 protected boolean passwordMatches(final byte[] clearPasswordBytes, 298 final byte[] unPrefixedUnFormattedEncodedPasswordBytes, 299 final ReadOnlyEntry userEntry) 300 throws LDAPException 301 { 302 // Subtract the digest length from the encoded password to get the number 303 // of salt bytes. If the number of salt bytes is less than or equal to 304 // zero, then the password will not match. 305 final int numComputedSaltBytes = 306 unPrefixedUnFormattedEncodedPasswordBytes.length - digestLengthBytes; 307 if (numComputedSaltBytes <= 0) 308 { 309 return false; 310 } 311 312 313 // Separate the salt and the digest. 314 final byte[] salt = new byte[numComputedSaltBytes]; 315 final byte[] digest = new byte[digestLengthBytes]; 316 if (saltAfterMessageDigest) 317 { 318 System.arraycopy(unPrefixedUnFormattedEncodedPasswordBytes, 0, digest, 0, 319 digestLengthBytes); 320 System.arraycopy(unPrefixedUnFormattedEncodedPasswordBytes, 321 digestLengthBytes, salt, 0, salt.length); 322 } 323 else 324 { 325 System.arraycopy(unPrefixedUnFormattedEncodedPasswordBytes, 0, salt, 0, 326 salt.length); 327 System.arraycopy(unPrefixedUnFormattedEncodedPasswordBytes, salt.length, 328 digest, 0, digestLengthBytes); 329 } 330 331 332 // Now that we have the salt, combine it with the clear-text password in the 333 // proper order. 334 // Combine the clear-text password and the salt in the proper order. 335 final byte[] saltedPassword; 336 if (saltAfterClearPassword) 337 { 338 saltedPassword = concatenate(clearPasswordBytes, salt); 339 } 340 else 341 { 342 saltedPassword = concatenate(salt, clearPasswordBytes); 343 } 344 345 346 // Compute a digest of the salted password and see whether it matches the 347 // digest we extracted earlier. If so, then the clear-text password 348 // matches. If not, then it doesn't. 349 final byte[] computedDigest = messageDigest.digest(saltedPassword); 350 return Arrays.equals(computedDigest, digest); 351 } 352 353 354 355 /** 356 * {@inheritDoc} 357 */ 358 @Override() 359 protected byte[] extractClearPassword( 360 final byte[] unPrefixedUnFormattedEncodedPasswordBytes, 361 final ReadOnlyEntry userEntry) 362 throws LDAPException 363 { 364 throw new LDAPException(ResultCode.NOT_SUPPORTED, 365 ERR_SALTED_DIGEST_PW_ENCODER_NOT_REVERSIBLE.get()); 366 } 367 368 369 370 /** 371 * {@inheritDoc} 372 */ 373 @Override() 374 public void toString(final StringBuilder buffer) 375 { 376 buffer.append("SaltedMessageDigestInMemoryPasswordEncoder(prefix='"); 377 buffer.append(getPrefix()); 378 buffer.append("', outputFormatter="); 379 380 final PasswordEncoderOutputFormatter outputFormatter = 381 getOutputFormatter(); 382 if (outputFormatter == null) 383 { 384 buffer.append("null"); 385 } 386 else 387 { 388 outputFormatter.toString(buffer); 389 } 390 391 buffer.append(", digestAlgorithm='"); 392 buffer.append(messageDigest.getAlgorithm()); 393 buffer.append("', digestLengthBytes="); 394 buffer.append(messageDigest.getDigestLength()); 395 buffer.append(", numSaltBytes="); 396 buffer.append(numSaltBytes); 397 buffer.append(", saltAfterClearPassword="); 398 buffer.append(saltAfterClearPassword); 399 buffer.append(", saltAfterMessageDigest="); 400 buffer.append(saltAfterMessageDigest); 401 buffer.append(')'); 402 } 403}