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