001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * docs/licenses/cddl.txt 011 * or http://www.opensource.org/licenses/cddl1.php. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * docs/licenses/cddl.txt. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Portions Copyright 2010-2024 Ping Identity Corporation 026 */ 027package com.unboundid.directory.sdk.ds.api; 028 029 030 031import java.util.Collections; 032import java.util.List; 033import java.util.Map; 034 035import com.unboundid.asn1.ASN1OctetString; 036import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider; 037import com.unboundid.directory.sdk.common.internal.Reconfigurable; 038import com.unboundid.directory.sdk.common.internal.UnboundIDExtension; 039import com.unboundid.directory.sdk.ds.config.PasswordStorageSchemeConfig; 040import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension; 041import com.unboundid.directory.sdk.ds.types.DirectoryServerContext; 042import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension; 043import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension; 044import com.unboundid.ldap.sdk.DN; 045import com.unboundid.ldap.sdk.LDAPException; 046import com.unboundid.ldap.sdk.ResultCode; 047import com.unboundid.util.ByteString; 048import com.unboundid.util.Extensible; 049import com.unboundid.util.StaticUtils; 050import com.unboundid.util.ThreadSafety; 051import com.unboundid.util.ThreadSafetyLevel; 052import com.unboundid.util.args.ArgumentException; 053import com.unboundid.util.args.ArgumentParser; 054 055 056 057/** 058 * NOTE: The {@link EnhancedPasswordStorageScheme} class provides both a 059 * simpler and more functional API for interacting with passwords. It may be 060 * desirable for new storage scheme implementations to implement that API rather 061 * than this older version. 062 * <BR><BR> 063 * This class defines an API that must be implemented by extensions which may be 064 * used to encode passwords for storage in the server. Ideally, encoded 065 * passwords should be stored in a secure manner so that anyone with access to 066 * the encoded password will not be able to determine the clear-text password 067 * with which it is associated (e.g., using a one-way message digest, or 068 * using reversible encryption with a securely-obtained key). Passwords are not 069 * required to be stored in a reversible form that allows the server to 070 * determine the clear-text password used to generate an encoded representation 071 * as long as it is possible to determine whether a given clear-text password 072 * may be used to generate a provided encoded representation. 073 * <BR><BR> 074 * Encoded passwords may taken one of two forms. The first is the "user 075 * password" syntax, in which the encoded password is represented by the name of 076 * the storage scheme in curly braces followed by the transformed password 077 * (e.g., "{scheme}encoded"). This format isn't based on any defined standard, 078 * but is commonly used by a number of directory server implementations. The 079 * second format is the authentication password syntax as described in RFC 3112, 080 * in which the encoded representation is broken into scheme, authInfo, and 081 * authValue segments separated by dollar signs (e.g., 082 * "scheme$authInfo$authValue"). All password storage schemes are required to 083 * support the "user password" syntax and may optionally also support the 084 * authentication password syntax. 085 * <BR> 086 * <H2>Configuring Password Storage Schemes</H2> 087 * In order to configure a password storage scheme created using this API, use 088 * a command like: 089 * <PRE> 090 * dsconfig create-password-storage-scheme \ 091 * --scheme-name "<I>{scheme-name}</I>" \ 092 * --type third-party \ 093 * --set enabled:true \ 094 * --set "extension-class:<I>{class-name}</I>" \ 095 * --set "extension-argument:<I>{name=value}</I>" 096 * </PRE> 097 * where "<I>{scheme-name}</I>" is the name to use for the password storage 098 * scheme instance, "<I>{class-name}</I>" is the fully-qualified name of the 099 * Java class that extends 100 * {@code com.unboundid.directory.sdk.ds.api.PasswordStorageScheme}, and 101 * "<I>{name=value}</I>" represents name-value pairs for any arguments to 102 * provide to the password storage scheme. If multiple arguments should be 103 * provided to the password storage scheme, then the 104 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be 105 * provided multiple times. 106 * 107 * @see EnhancedPasswordStorageScheme 108 */ 109@Extensible() 110@DirectoryServerExtension() 111@DirectoryProxyServerExtension(appliesToLocalContent=true, 112 appliesToRemoteContent=false) 113@SynchronizationServerExtension(appliesToLocalContent=true, 114 appliesToSynchronizedContent=false) 115@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE) 116public abstract class PasswordStorageScheme 117 implements UnboundIDExtension, 118 Reconfigurable<PasswordStorageSchemeConfig>, 119 ExampleUsageProvider 120{ 121 /** 122 * Creates a new instance of this password storage scheme. All password 123 * storage scheme implementations must include a default constructor, but any 124 * initialization should generally be done in the 125 * {@code initializePasswordStorageScheme} method. 126 */ 127 public PasswordStorageScheme() 128 { 129 // No implementation is required. 130 } 131 132 133 134 /** 135 * {@inheritDoc} 136 */ 137 public abstract String getExtensionName(); 138 139 140 141 /** 142 * {@inheritDoc} 143 */ 144 public abstract String[] getExtensionDescription(); 145 146 147 148 /** 149 * {@inheritDoc} 150 */ 151 public void defineConfigArguments(final ArgumentParser parser) 152 throws ArgumentException 153 { 154 // No arguments will be allowed by default. 155 } 156 157 158 159 /** 160 * Initializes this password storage scheme. 161 * 162 * @param serverContext A handle to the server context for the server in 163 * which this extension is running. 164 * @param config The general configuration for this password storage 165 * scheme. 166 * @param parser The argument parser which has been initialized from 167 * the configuration for this password storage scheme. 168 * 169 * @throws LDAPException If a problem occurs while initializing this 170 * password storage scheme. 171 */ 172 public void initializePasswordStorageScheme( 173 final DirectoryServerContext serverContext, 174 final PasswordStorageSchemeConfig config, 175 final ArgumentParser parser) 176 throws LDAPException 177 { 178 // No initialization will be performed by default. 179 } 180 181 182 183 /** 184 * {@inheritDoc} 185 */ 186 public boolean isConfigurationAcceptable( 187 final PasswordStorageSchemeConfig config, 188 final ArgumentParser parser, 189 final List<String> unacceptableReasons) 190 { 191 // No extended validation will be performed by default. 192 return true; 193 } 194 195 196 197 /** 198 * {@inheritDoc} 199 */ 200 public ResultCode applyConfiguration(final PasswordStorageSchemeConfig config, 201 final ArgumentParser parser, 202 final List<String> adminActionsRequired, 203 final List<String> messages) 204 { 205 // By default, no configuration changes will be applied. If there are any 206 // arguments, then add an admin action message indicating that the extension 207 // needs to be restarted for any changes to take effect. 208 if (! parser.getNamedArguments().isEmpty()) 209 { 210 adminActionsRequired.add( 211 "No configuration change has actually been applied. The new " + 212 "configuration will not take effect until this password " + 213 "storage scheme is disabled and re-enabled or until the " + 214 "server is restarted."); 215 } 216 217 return ResultCode.SUCCESS; 218 } 219 220 221 222 /** 223 * Performs any cleanup which may be necessary when this password storage 224 * scheme is to be taken out of service. 225 */ 226 public void finalizePasswordStorageScheme() 227 { 228 // No implementation is required. 229 } 230 231 232 233 /** 234 * Retrieves the name for this password storage scheme. This will be the 235 * identifier which appears in curly braces at the beginning of the encoded 236 * password. The name should not include curly braces. 237 * 238 * @return The name for this password storage scheme. 239 */ 240 public abstract String getStorageSchemeName(); 241 242 243 244 /** 245 * Indicates whether this password storage scheme encodes passwords in a form 246 * that allows the original plaintext value to be obtained from the encoded 247 * representation. 248 * 249 * @return {@code true} if the original plaintext password may be obtained 250 * from the encoded password, or {@code false} if not. 251 */ 252 public abstract boolean isReversible(); 253 254 255 256 /** 257 * Indicates whether this password storage scheme encodes passwords in a form 258 * that may be considered secure. A storage scheme should only be considered 259 * secure if it is not possible to trivially determine a clear-text value 260 * which may be used to generate a given encoded representation. 261 * 262 * @return {@code true} if this password storage scheme may be considered 263 * secure, or {@code false} if not. 264 */ 265 public abstract boolean isSecure(); 266 267 268 269 /** 270 * Encodes the provided plaintext password. The encoded password should not 271 * include the scheme name in curly braces. 272 * 273 * @param plaintext The plaintext password to be encoded. It must not be 274 * {@code null}. Note that there is no guarantee that 275 * password validators have yet been invoked for this 276 * password, so this password storage scheme implementation 277 * should not make any assumptions about the format of the 278 * plaintext password or whether it will actually be 279 * allowed for use in the entry. 280 * 281 * @return The encoded representation of the provided password. 282 * 283 * @throws LDAPException If a problem occurs while attempting to encode the 284 * password. 285 */ 286 public abstract ByteString encodePassword(final ByteString plaintext) 287 throws LDAPException; 288 289 290 291 /** 292 * Encodes the provided plaintext password, prefixing the encoded 293 * representation with the name of the storage scheme in curly braces. 294 * 295 * @param plaintext The plaintext password to be encoded. It must not be 296 * {@code null}. Note that there is no guarantee that 297 * password validators have yet been invoked for this 298 * password, so this password storage scheme implementation 299 * should not make any assumptions about the format of the 300 * plaintext password or whether it will actually be 301 * allowed for use in the entry. 302 * 303 * @return The encoded representation of the provided password, prefixed with 304 * the storage scheme name. 305 * 306 * @throws LDAPException If a problem occurs while attempting to encode the 307 * password. 308 */ 309 public ByteString encodePasswordWithScheme(final ByteString plaintext) 310 throws LDAPException 311 { 312 final byte[] schemeBytes = StaticUtils.getBytes(getStorageSchemeName()); 313 final byte[] encodedPW = encodePassword(plaintext).getValue(); 314 315 final byte[] b = new byte[schemeBytes.length + encodedPW.length + 2]; 316 317 int pos = 0; 318 b[pos++] = '{'; 319 320 System.arraycopy(schemeBytes, 0, b, pos, schemeBytes.length); 321 pos += schemeBytes.length; 322 323 b[pos++] = '}'; 324 System.arraycopy(encodedPW, 0, b, pos, encodedPW.length); 325 326 return new ASN1OctetString(b); 327 } 328 329 330 331 /** 332 * Encodes the provided plaintext password, prefixing the encoded 333 * representation with the name of the storage scheme in curly braces. If an 334 * entry DN is provided, then a deterministic encoding should be used (e.g., 335 * a salt generated from the provided DN) so that the same encoding will 336 * always be used for same plaintext in the same entry. This will primarily 337 * be used when importing an LDIF file containing plaintext passwords so that 338 * the same LDIF file can be imported into multiple servers and the same 339 * encoded passwords will be generated in each server. 340 * 341 * @param plaintext The plaintext password to be encoded. It must not be 342 * {@code null}. Note that there is no guarantee that 343 * password validators have yet been invoked for this 344 * password, so this password storage scheme implementation 345 * should not make any assumptions about the format of the 346 * plaintext password or whether it will actually be 347 * allowed for use in the entry. 348 * @param entryDN The DN of the entry in which the encoded password will 349 * appear. This may be {@code null} if it is not known. 350 * 351 * @return The encoded representation of the provided password, prefixed with 352 * the storage scheme name. 353 * 354 * @throws LDAPException If a problem occurs while attempting to encode the 355 * password. 356 */ 357 public ByteString encodePasswordWithScheme(final ByteString plaintext, 358 final DN entryDN) 359 throws LDAPException 360 { 361 return encodePasswordWithScheme(plaintext); 362 } 363 364 365 366 /** 367 * Indicates whether the provided plaintext password could have been used to 368 * generate the given encoded password. 369 * 370 * @param plaintext The plaintext password for which to make the 371 * determination. 372 * @param encoded The encoded password for which to make the 373 * determination. It will not include the scheme name. 374 * 375 * @return {@code true} if the provided clear-text password could have been 376 * used to generate the encoded password, or {@code false} if not. 377 */ 378 public abstract boolean passwordMatches(final ByteString plaintext, 379 final ByteString encoded); 380 381 382 383 /** 384 * Indicates whether the provided password is encoded with the current 385 * settings for this password storage scheme. 386 * 387 * @param encodedPassword The encoded password for which to make the 388 * determination. This will not be {@code null}, and 389 * it will not include the scheme name in curly 390 * braces. 391 * @param explanations A list that may be updated with messages that may 392 * help explain the result that this method returns. 393 * This may include messages that explain why the 394 * provided password is not encoded with the current 395 * settings or why the determination could not be 396 * made. This will not be {@code null}, and it will 397 * be updatable. 398 * 399 * @return {@code true} if the provided password is encoded wit the current 400 * settings for this password storage scheme, {@code false} if it is 401 * not encoded with the current settings for this password storage 402 * scheme, or {@code null} if the determination cannot be made (e.g., 403 * because this method is not implemented for this password storage 404 * scheme, or because an error occurred while attempting to make 405 * the determination). 406 */ 407 public Boolean isPasswordEncodedWithCurrentSettings( 408 final ByteString encodedPassword, 409 final List<String> explanations) 410 { 411 explanations.add("The " + getStorageSchemeName() + " password storage " + 412 "scheme does not support determining whether a stored password is " + 413 "encoded with the current settings"); 414 return null; 415 } 416 417 418 419 /** 420 * Attempts to determine the plaintext password used to generate the provided 421 * encoded password. This method should only be called if the 422 * {@link #isReversible} method returns {@code true}. 423 * 424 * @param encoded The encoded password for which to obtain the original 425 * plaintext password. It must not be {@code null} and will 426 * not be prefixed with the scheme name. 427 * 428 * @return The plaintext password obtained from the given encoded password. 429 * 430 * @throws LDAPException If this password storage scheme is not reversible, 431 * or if the provided value could not be decoded to 432 * its plaintext representation. 433 */ 434 public abstract ByteString getPlaintextValue(final ByteString encoded) 435 throws LDAPException; 436 437 438 439 /** 440 * Indicates whether this password storage scheme provides the ability to 441 * encode passwords in the authentication password syntax as described in RFC 442 * 3112. 443 * 444 * @return {@code true} if this password storage scheme supports the 445 * authentication password syntax, or {@code false} if not. 446 */ 447 public boolean supportsAuthPasswordSyntax() 448 { 449 return false; 450 } 451 452 453 454 /** 455 * Retrieves the name that should be used to identify this password storage 456 * scheme when encoding passwords using the authentication password syntax as 457 * described in RFC 3112. This should only be used if the 458 * {@link #supportsAuthPasswordSyntax} method returns {@code true}. 459 * 460 * @return The name that should be used to identify this password storage 461 * scheme when encoding passwords using the authentication password 462 * syntax. 463 */ 464 public String getAuthPasswordSchemeName() 465 { 466 return getStorageSchemeName(); 467 } 468 469 470 471 /** 472 * Encodes the provided plaintext password using the authentication password 473 * syntax as defined in RFC 3112. This should only be used if the 474 * {@link #supportsAuthPasswordSyntax} method returns {@code true}. 475 * 476 * @param plaintext The plaintext password to be encoded. It must not be 477 * {@code null}. Note that there is no guarantee that 478 * password validators have yet been invoked for this 479 * password, so this password storage scheme implementation 480 * should not make any assumptions about the format of the 481 * plaintext password or whether it will actually be 482 * allowed for use in the entry. 483 * 484 * @return The encoded representation of the provided password. 485 * 486 * @throws LDAPException If a problem occurs while encoding the provided 487 * password, or if this password storage scheme does 488 * not support the authentication password syntax. 489 */ 490 public ByteString encodeAuthPassword(final ByteString plaintext) 491 throws LDAPException 492 { 493 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 494 "This password storage scheme does not support the use of the " + 495 "authentication password syntax."); 496 } 497 498 499 500 /** 501 * Indicates whether the provided plaintext password may be used to generate 502 * an encoded password with the given authInfo and authValue elements when 503 * using the authentication password syntax as defined in RFC 3112. This 504 * should only be used if the {@link #supportsAuthPasswordSyntax} method 505 * returns {@code true}. 506 * 507 * @param plaintext The plaintext password for which to make the 508 * determination. 509 * @param authInfo The authInfo portion of the encoded password for which 510 * to make the determination. 511 * @param authValue The authValue portion of the encoded password for which 512 * to make the determination. 513 * 514 * @return {@code true} if the provided plaintext password could be used to 515 * generate an encoded password with the given authInfo and authValue 516 * portions, or {@code false} if not. 517 */ 518 public boolean authPasswordMatches(final ByteString plaintext, 519 final String authInfo, 520 final String authValue) 521 { 522 return false; 523 } 524 525 526 527 /** 528 * Indicates whether the provided password is encoded with the current 529 * settings for this password storage scheme using the authentication password 530 * syntax. 531 * 532 * @param authInfo The authInfo component of the password encoded in the 533 * authentication password syntax. 534 * @param authValue The authValue component of the password encoded in 535 * the authentication password syntax. 536 * @param explanations A list that may be updated with messages that may 537 * help explain the result that this method returns. 538 * This may include messages that explain why the 539 * provided password is not encoded with the current 540 * settings or why the determination could not be made. 541 * This will not be {@code null}, and it will be 542 * updatable. 543 * 544 * @return {@code true} if the provided password is encoded wit the current 545 * settings for this password storage scheme, {@code false} if it is 546 * not encoded with the current settings for this password storage 547 * scheme, or {@code null} if the determination cannot be made (e.g., 548 * because this method is not implemented for this password storage 549 * scheme, or because an error occurred while attempting to make 550 * the determination). 551 */ 552 public Boolean isAuthPasswordEncodedWithCurrentSettings( 553 final String authInfo, final String authValue, 554 final List<String> explanations) 555 { 556 explanations.add("The " + getAuthPasswordSchemeName() + " password " + 557 "storage scheme does not support determining whether a stored " + 558 "password is encoded with the current settings"); 559 return null; 560 } 561 562 563 564 /** 565 * Obtains the plaintext password that was used to generate an encoded 566 * password with the given authInfo and authValue elements when using the 567 * authentication password syntax as described in RFC 3112. This should only 568 * be used if both the {@link #supportsAuthPasswordSyntax} and 569 * {@link #isReversible} methods return {@code true}. 570 * 571 * @param authInfo The authInfo portion of the encoded password for which 572 * to retrieve the corresponding plaintext value. 573 * @param authValue The authValue portion of the encoded password for which 574 * to retrieve the corresponding plaintext value. 575 * 576 * @return The plaintext password that was used to generate the encoded 577 * password. 578 * 579 * @throws LDAPException If this password storage scheme is not reversible, 580 * if this password storage scheme does not support 581 * the authentication password syntax, or if some 582 * other problem is encountered while attempting to 583 * determine the plaintext password. 584 */ 585 public ByteString getAuthPasswordPlaintextValue(final String authInfo, 586 final String authValue) 587 throws LDAPException 588 { 589 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 590 "This password storage scheme does not support the use of the " + 591 "authentication password syntax."); 592 } 593 594 595 596 /** 597 * {@inheritDoc} 598 */ 599 public Map<List<String>,String> getExamplesArgumentSets() 600 { 601 return Collections.emptyMap(); 602 } 603}