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}