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.util.List; 026 027import com.unboundid.asn1.ASN1OctetString; 028import com.unboundid.ldap.sdk.LDAPException; 029import com.unboundid.ldap.sdk.Modification; 030import com.unboundid.ldap.sdk.ReadOnlyEntry; 031import com.unboundid.ldap.sdk.ResultCode; 032import com.unboundid.util.Extensible; 033import com.unboundid.util.StaticUtils; 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 defines an API that may be used to interact with clear-text 044 * passwords provided to the in-memory directory server. It can be used to 045 * ensure that clear-text passwords are encoded when storing them in the server, 046 * and to determine whether a provided clear-text password matches an encoded 047 * value. 048 */ 049@Extensible() 050@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE) 051public abstract class InMemoryPasswordEncoder 052{ 053 // The bytes that comprise the prefix. 054 private final byte[] prefixBytes; 055 056 // The output formatter that will be used to format the encoded representation 057 // of clear-text passwords. 058 private final PasswordEncoderOutputFormatter outputFormatter; 059 060 // The string that will appear at the beginning of encoded passwords. 061 private final String prefix; 062 063 064 065 /** 066 * Creates a new instance of this in-memory directory server password encoder 067 * with the provided information. 068 * 069 * @param prefix The string that will appear at the beginning of 070 * encoded passwords. It must not be {@code null} or 071 * empty. 072 * @param outputFormatter The output formatter that will be used to format 073 * the encoded representation of clear-text 074 * passwords. It may be {@code null} if no 075 * special formatting should be applied to the raw 076 * bytes. 077 */ 078 protected InMemoryPasswordEncoder(final String prefix, 079 final PasswordEncoderOutputFormatter outputFormatter) 080 { 081 Validator.ensureNotNullOrEmpty(prefix, 082 "The password encoder prefix must not be null or empty."); 083 084 this.prefix = prefix; 085 this.outputFormatter = outputFormatter; 086 087 prefixBytes = StaticUtils.getBytes(prefix); 088 } 089 090 091 092 /** 093 * Retrieves the string that will appear at the beginning of encoded 094 * passwords. 095 * 096 * @return The string that will appear at the beginning of encoded passwords. 097 */ 098 public final String getPrefix() 099 { 100 return prefix; 101 } 102 103 104 105 /** 106 * Retrieves the output formatter that will be used when generating the 107 * encoded representation of a password. 108 * 109 * @return The output formatter that will be used when generating the encoded 110 * representation of a password, or {@code nulL} if no output 111 * formatting will be applied. 112 */ 113 public final PasswordEncoderOutputFormatter getOutputFormatter() 114 { 115 return outputFormatter; 116 } 117 118 119 120 /** 121 * Encodes the provided clear-text password for storage in the in-memory 122 * directory server. The encoded password that is returned will include the 123 * prefix, and any appropriate output formatting will have been applied. 124 * <BR><BR> 125 * This method will be invoked when adding data into the server, including 126 * through LDAP add operations or LDIF imports, and when modifying existing 127 * entries through LDAP modify operations. 128 * 129 * @param clearPassword The clear-text password to be encoded. It must not 130 * be {@code null} or empty, and it must not be 131 * pre-encoded. 132 * @param userEntry The entry in which the encoded password will appear. 133 * It must not be {@code null}. If the entry is in the 134 * process of being modified, then this will be a 135 * representation of the entry as it appeared before 136 * any changes have been applied. 137 * @param modifications A set of modifications to be applied to the user 138 * entry. It must not be [@code null}. It will be an 139 * empty list for entries created via LDAP add and LDIF 140 * import operations. It will be a non-empty list for 141 * LDAP modifications. 142 * 143 * @return The encoded representation of the provided clear-text password. 144 * It will include the prefix, and any appropriate output formatting 145 * will have been applied. 146 * 147 * @throws LDAPException If a problem is encountered while trying to encode 148 * the provided clear-text password. 149 */ 150 public final ASN1OctetString encodePassword( 151 final ASN1OctetString clearPassword, 152 final ReadOnlyEntry userEntry, 153 final List<Modification> modifications) 154 throws LDAPException 155 { 156 if (clearPassword.getValueLength() == 0) 157 { 158 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 159 ERR_PW_ENCODER_ENCODE_PASSWORD_EMPTY.get()); 160 } 161 162 final byte[] clearPasswordBytes = clearPassword.getValue(); 163 final byte[] encodedPasswordBytes = 164 encodePassword(clearPasswordBytes, userEntry, modifications); 165 166 final byte[] formattedEncodedPasswordBytes; 167 if (outputFormatter == null) 168 { 169 formattedEncodedPasswordBytes = encodedPasswordBytes; 170 } 171 else 172 { 173 formattedEncodedPasswordBytes = 174 outputFormatter.format(encodedPasswordBytes); 175 } 176 177 final byte[] formattedPasswordBytesWithPrefix = 178 new byte[formattedEncodedPasswordBytes.length + prefixBytes.length]; 179 System.arraycopy(prefixBytes, 0, formattedPasswordBytesWithPrefix, 0, 180 prefixBytes.length); 181 System.arraycopy(formattedEncodedPasswordBytes, 0, 182 formattedPasswordBytesWithPrefix, prefixBytes.length, 183 formattedEncodedPasswordBytes.length); 184 185 return new ASN1OctetString(formattedPasswordBytesWithPrefix); 186 } 187 188 189 190 /** 191 * Encodes the provided clear-text password for storage in the in-memory 192 * directory server. The encoded password that is returned must not include 193 * the prefix, and no output formatting should have been applied. 194 * <BR><BR> 195 * This method will be invoked when adding data into the server, including 196 * through LDAP add operations or LDIF imports, and when modifying existing 197 * entries through LDAP modify operations. 198 * 199 * @param clearPassword The bytes that comprise the clear-text password to 200 * be encoded. It must not be {@code null} or empty. 201 * @param userEntry The entry in which the encoded password will appear. 202 * It must not be {@code null}. If the entry is in the 203 * process of being modified, then this will be a 204 * representation of the entry as it appeared before 205 * any changes have been applied. 206 * @param modifications A set of modifications to be applied to the user 207 * entry. It must not be [@code null}. It will be an 208 * empty list for entries created via LDAP add and LDIF 209 * import operations. It will be a non-empty list for 210 * LDAP modifications. 211 * 212 * @return The bytes that comprise encoded representation of the provided 213 * clear-text password, without the prefix, and without any output 214 * formatting applied. 215 * 216 * @throws LDAPException If a problem is encountered while trying to encode 217 * the provided clear-text password. 218 */ 219 protected abstract byte[] encodePassword(byte[] clearPassword, 220 ReadOnlyEntry userEntry, 221 List<Modification> modifications) 222 throws LDAPException; 223 224 225 226 /** 227 * Verifies that the provided pre-encoded password (including the prefix, and 228 * with any appropriate output formatting applied) is compatible with the 229 * validation performed by this password encoder. 230 * <BR><BR> 231 * This method will be invoked when adding data into the server, including 232 * through LDAP add operations or LDIF imports, and when modifying existing 233 * entries through LDAP modify operations. Any password included in any of 234 * these entries that starts with a prefix registered with the in-memory 235 * directory server will be validated with the encoder that corresponds to 236 * that password's prefix. 237 * 238 * @param prefixedFormattedEncodedPassword 239 * The pre-encoded password to validate. It must not be 240 * {@code null}, and it should include the prefix and any 241 * applicable output formatting. 242 * @param userEntry 243 * The entry in which the password will appear. It must not be 244 * {@code null}. If the entry is in the process of being 245 * modified, then this will be a representation of the entry 246 * as it appeared before any changes have been applied. 247 * @param modifications 248 * A set of modifications to be applied to the user entry. It 249 * must not be [@code null}. It will be an empty list for 250 * entries created via LDAP add and LDIF import operations. It 251 * will be a non-empty list for LDAP modifications. 252 * 253 * @throws LDAPException If the provided encoded password is not compatible 254 * with the validation performed by this password 255 * encoder, or if a problem is encountered while 256 * making the determination. 257 */ 258 public final void ensurePreEncodedPasswordAppearsValid( 259 final ASN1OctetString prefixedFormattedEncodedPassword, 260 final ReadOnlyEntry userEntry, 261 final List<Modification> modifications) 262 throws LDAPException 263 { 264 // Strip the prefix off the encoded password. 265 final byte[] prefixedFormattedEncodedPasswordBytes = 266 prefixedFormattedEncodedPassword.getValue(); 267 if (! passwordStartsWithPrefix(prefixedFormattedEncodedPasswordBytes)) 268 { 269 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 270 ERR_PW_ENCODER_VALIDATE_ENCODED_PW_MISSING_PREFIX.get( 271 getClass().getName(), prefix)); 272 } 273 274 final byte[] unPrefixedFormattedEncodedPasswordBytes = 275 new byte[prefixedFormattedEncodedPasswordBytes.length - 276 prefixBytes.length]; 277 System.arraycopy(prefixedFormattedEncodedPasswordBytes, prefixBytes.length, 278 unPrefixedFormattedEncodedPasswordBytes, 0, 279 unPrefixedFormattedEncodedPasswordBytes.length); 280 281 282 // If an output formatter is configured, then revert the output formatting. 283 final byte[] unPrefixedUnFormattedEncodedPasswordBytes; 284 if (outputFormatter == null) 285 { 286 unPrefixedUnFormattedEncodedPasswordBytes = 287 unPrefixedFormattedEncodedPasswordBytes; 288 } 289 else 290 { 291 unPrefixedUnFormattedEncodedPasswordBytes = 292 outputFormatter.unFormat(unPrefixedFormattedEncodedPasswordBytes); 293 } 294 295 296 // Validate the un-prefixed, un-formatted password. 297 ensurePreEncodedPasswordAppearsValid( 298 unPrefixedUnFormattedEncodedPasswordBytes, userEntry, modifications); 299 } 300 301 302 303 /** 304 * Verifies that the provided pre-encoded password (with the prefix removed 305 * and any output formatting reverted) is compatible with the validation 306 * performed by this password encoder. 307 * <BR><BR> 308 * Note that this method should return {@code true} if the provided 309 * {@code unPrefixedUnFormattedEncodedPasswordBytes} value could be used in 310 * conjunction with the {@link #passwordMatches} method, even if it does not 311 * exactly match the format of the output that would have been generated by 312 * the {@link #encodePassword} method. For example, if this password encoder 313 * uses a salt, then it may be desirable to accept passwords encoded with a 314 * salt that has a different length than the {@code encodePassword} method 315 * would use when encoding a clear-test password. This may allow the 316 * in-memory directory server to support pre-encoded passwords generated from 317 * other types of directory servers that may use different settings when 318 * encoding passwords, but still generates encoded passwords that are 319 * compatible with this password encoder. 320 * 321 * @param unPrefixedUnFormattedEncodedPasswordBytes 322 * The bytes that comprise the pre-encoded password to validate, 323 * with the prefix stripped off and the output formatting 324 * reverted. 325 * @param userEntry 326 * The entry in which the password will appear. It must not be 327 * {@code null}. If the entry is in the process of being 328 * modified, then this will be a representation of the entry 329 * as it appeared before any changes have been applied. 330 * @param modifications 331 * A set of modifications to be applied to the user entry. It 332 * must not be [@code null}. It will be an empty list for 333 * entries created via LDAP add and LDIF import operations. It 334 * will be a non-empty list for LDAP modifications. 335 * 336 * @throws LDAPException If the provided encoded password is not compatible 337 * with the validation performed by this password 338 * encoder, or if a problem is encountered while 339 * making the determination. 340 */ 341 protected abstract void ensurePreEncodedPasswordAppearsValid( 342 byte[] unPrefixedUnFormattedEncodedPasswordBytes, 343 ReadOnlyEntry userEntry, 344 List<Modification> modifications) 345 throws LDAPException; 346 347 348 349 /** 350 * Indicates whether the provided clear-text password could have been used to 351 * generate the given encoded password. This method will be invoked when 352 * verifying a provided clear-text password during bind processing, or when 353 * removing an existing password in a modify operation. 354 * 355 * @param clearPassword 356 * The clear-text password to be compared against the encoded 357 * password. It must not be {@code null} or empty. 358 * @param prefixedFormattedEncodedPassword 359 * The encoded password to compare against the clear-text 360 * password. It must not be {@code null}, it must include the 361 * prefix, and any appropriate output formatting must have been 362 * applied. 363 * @param userEntry 364 * The entry in which the encoded password appears. It must not 365 * be {@code null}. 366 * 367 * @return {@code true} if the provided clear-text password could be used to 368 * generate the given encoded password, or {@code false} if not. 369 * 370 * @throws LDAPException If a problem is encountered while making the 371 * determination. 372 */ 373 public final boolean clearPasswordMatchesEncodedPassword( 374 final ASN1OctetString clearPassword, 375 final ASN1OctetString prefixedFormattedEncodedPassword, 376 final ReadOnlyEntry userEntry) 377 throws LDAPException 378 { 379 // Make sure that the provided clear-text password is not null or empty. 380 final byte[] clearPasswordBytes = clearPassword.getValue(); 381 if (clearPasswordBytes.length == 0) 382 { 383 return false; 384 } 385 386 387 // If the password doesn't start with the right prefix, then it's not 388 // considered a match. If it does start with the right prefix, then strip 389 // it off. 390 final byte[] prefixedFormattedEncodedPasswordBytes = 391 prefixedFormattedEncodedPassword.getValue(); 392 if (! passwordStartsWithPrefix(prefixedFormattedEncodedPasswordBytes)) 393 { 394 return false; 395 } 396 397 final byte[] unPrefixedFormattedEncodedPasswordBytes = 398 new byte[prefixedFormattedEncodedPasswordBytes.length - 399 prefixBytes.length]; 400 System.arraycopy(prefixedFormattedEncodedPasswordBytes, prefixBytes.length, 401 unPrefixedFormattedEncodedPasswordBytes, 0, 402 unPrefixedFormattedEncodedPasswordBytes.length); 403 404 405 // If an output formatter is configured, then revert the output formatting. 406 final byte[] unPrefixedUnFormattedEncodedPasswordBytes; 407 if (outputFormatter == null) 408 { 409 unPrefixedUnFormattedEncodedPasswordBytes = 410 unPrefixedFormattedEncodedPasswordBytes; 411 } 412 else 413 { 414 unPrefixedUnFormattedEncodedPasswordBytes = 415 outputFormatter.unFormat(unPrefixedFormattedEncodedPasswordBytes); 416 } 417 418 419 // Make sure that the resulting un-prefixed, un-formatted password is not 420 // empty. 421 if (unPrefixedUnFormattedEncodedPasswordBytes.length == 0) 422 { 423 return false; 424 } 425 426 427 // Determine whether the provided clear-text password could have been used 428 // to generate the encoded representation. 429 return passwordMatches(clearPasswordBytes, 430 unPrefixedUnFormattedEncodedPasswordBytes, userEntry); 431 } 432 433 434 435 /** 436 * Indicates whether the provided clear-text password could have been used to 437 * generate the given encoded password. This method will be invoked when 438 * verifying a provided clear-text password during bind processing, or when 439 * removing an existing password in a modify operation. 440 * 441 * @param clearPasswordBytes 442 * The bytes that comprise the clear-text password to be 443 * compared against the encoded password. It must not be 444 * {@code null} or empty. 445 * @param unPrefixedUnFormattedEncodedPasswordBytes 446 * The bytes that comprise the encoded password, with the prefix 447 * stripped off and the output formatting reverted. 448 * @param userEntry 449 * The entry in which the encoded password appears. It must not 450 * be {@code null}. 451 * 452 * @return {@code true} if the provided clear-text password could have been 453 * used to generate the given encoded password, or {@code false} if 454 * not. 455 * 456 * @throws LDAPException If a problem is encountered while attempting to 457 * make the determination. 458 */ 459 protected abstract boolean passwordMatches( 460 byte[] clearPasswordBytes, 461 byte[] unPrefixedUnFormattedEncodedPasswordBytes, 462 ReadOnlyEntry userEntry) 463 throws LDAPException; 464 465 466 467 /** 468 * Attempts to extract the clear-text password used to generate the provided 469 * encoded representation, if possible. Many password encoder implementations 470 * may use one-way encoding mechanisms, so it will often not be possible to 471 * obtain the original clear-text password from its encoded representation. 472 * 473 * @param prefixedFormattedEncodedPassword 474 * The encoded password from which to extract the clear-text 475 * password. It must not be {@code null}, it must include the 476 * prefix, and any appropriate output formatting must have been 477 * applied. 478 * @param userEntry 479 * The entry in which the encoded password appears. It must not 480 * be {@code null}. 481 * 482 * @return The clear-text password used to generate the provided encoded 483 * representation. 484 * 485 * @throws LDAPException If this password encoder is not reversible, or if a 486 * problem occurs while trying to extract the 487 * clear-text representation from the provided encoded 488 * password. 489 */ 490 public final ASN1OctetString extractClearPasswordFromEncodedPassword( 491 final ASN1OctetString prefixedFormattedEncodedPassword, 492 final ReadOnlyEntry userEntry) 493 throws LDAPException 494 { 495 // Strip the prefix off the encoded password. 496 final byte[] prefixedFormattedEncodedPasswordBytes = 497 prefixedFormattedEncodedPassword.getValue(); 498 if (! passwordStartsWithPrefix(prefixedFormattedEncodedPasswordBytes)) 499 { 500 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 501 ERR_PW_ENCODER_PW_MATCHES_ENCODED_PW_MISSING_PREFIX.get( 502 getClass().getName(), prefix)); 503 } 504 505 final byte[] unPrefixedFormattedEncodedPasswordBytes = 506 new byte[prefixedFormattedEncodedPasswordBytes.length - 507 prefixBytes.length]; 508 System.arraycopy(prefixedFormattedEncodedPasswordBytes, prefixBytes.length, 509 unPrefixedFormattedEncodedPasswordBytes, 0, 510 unPrefixedFormattedEncodedPasswordBytes.length); 511 512 513 // If an output formatter is configured, then revert the output formatting. 514 final byte[] unPrefixedUnFormattedEncodedPasswordBytes; 515 if (outputFormatter == null) 516 { 517 unPrefixedUnFormattedEncodedPasswordBytes = 518 unPrefixedFormattedEncodedPasswordBytes; 519 } 520 else 521 { 522 unPrefixedUnFormattedEncodedPasswordBytes = 523 outputFormatter.unFormat(unPrefixedFormattedEncodedPasswordBytes); 524 } 525 526 527 // Try to extract the clear-text password. 528 final byte[] clearPasswordBytes = extractClearPassword( 529 unPrefixedUnFormattedEncodedPasswordBytes, userEntry); 530 return new ASN1OctetString(clearPasswordBytes); 531 } 532 533 534 535 /** 536 * Attempts to extract the clear-text password used to generate the provided 537 * encoded representation, if possible. Many password encoder implementations 538 * may use one-way encoding mechanisms, so it will often not be possible to 539 * obtain the original clear-text password from its encoded representation. 540 * 541 * @param unPrefixedUnFormattedEncodedPasswordBytes 542 * The bytes that comprise the encoded password, with the prefix 543 * stripped off and the output formatting reverted. 544 * @param userEntry 545 * The entry in which the encoded password appears. It must not 546 * be {@code null}. 547 * 548 * @return The clear-text password used to generate the provided encoded 549 * representation. 550 * 551 * @throws LDAPException If this password encoder is not reversible, or if a 552 * problem occurs while trying to extract the 553 * clear-text representation from the provided encoded 554 * password. 555 */ 556 protected abstract byte[] extractClearPassword( 557 byte[] unPrefixedUnFormattedEncodedPasswordBytes, 558 ReadOnlyEntry userEntry) 559 throws LDAPException; 560 561 562 563 /** 564 * Indicates whether the provided password starts with the encoded password 565 * prefix. 566 * 567 * @param password The password for which to make the determination. 568 * 569 * @return {@code true} if the provided password starts with the encoded 570 * password prefix, or {@code false} if not. 571 */ 572 public final boolean passwordStartsWithPrefix(final ASN1OctetString password) 573 { 574 return passwordStartsWithPrefix(password.getValue()); 575 } 576 577 578 579 /** 580 * Indicates whether the provided byte array starts with the encoded password 581 * prefix. 582 * 583 * @param b The byte array for which to make the determination. 584 * 585 * @return {@code true} if the provided byte array starts with the encoded 586 * password prefix, or {@code false} if not. 587 */ 588 private boolean passwordStartsWithPrefix(final byte[] b) 589 { 590 if (b.length < prefixBytes.length) 591 { 592 return false; 593 } 594 595 for (int i=0; i < prefixBytes.length; i++) 596 { 597 if (b[i] != prefixBytes[i]) 598 { 599 return false; 600 } 601 } 602 603 return true; 604 } 605 606 607 608 /** 609 * Retrieves a string representation of this password encoder. 610 * 611 * @return A string representation of this password encoder. 612 */ 613 @Override() 614 public final String toString() 615 { 616 final StringBuilder buffer = new StringBuilder(); 617 toString(buffer); 618 return buffer.toString(); 619 } 620 621 622 623 /** 624 * Appends a string representation of this password encoder to the provided 625 * buffer. 626 * 627 * @param buffer The buffer to which the information should be appended. 628 */ 629 public abstract void toString(StringBuilder buffer); 630}