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   * Attempts to determine the plaintext password used to generate the provided
385   * encoded password.  This method should only be called if the
386   * {@link #isReversible} method returns {@code true}.
387   *
388   * @param  encoded  The encoded password for which to obtain the original
389   *                  plaintext password.  It must not be {@code null} and will
390   *                  not be prefixed with the scheme name.
391   *
392   * @return  The plaintext password obtained from the given encoded password.
393   *
394   * @throws  LDAPException  If this password storage scheme is not reversible,
395   *                         or if the provided value could not be decoded to
396   *                         its plaintext representation.
397   */
398  public abstract ByteString getPlaintextValue(final ByteString encoded)
399         throws LDAPException;
400
401
402
403  /**
404   * Indicates whether this password storage scheme provides the ability to
405   * encode passwords in the authentication password syntax as described in RFC
406   * 3112.
407   *
408   * @return  {@code true} if this password storage scheme supports the
409   *          authentication password syntax, or {@code false} if not.
410   */
411  public boolean supportsAuthPasswordSyntax()
412  {
413    return false;
414  }
415
416
417
418  /**
419   * Retrieves the name that should be used to identify this password storage
420   * scheme when encoding passwords using the authentication password syntax as
421   * described in RFC 3112.  This should only be used if the
422   * {@link #supportsAuthPasswordSyntax} method returns {@code true}.
423   *
424   * @return  The name that should be used to identify this password storage
425   *          scheme when encoding passwords using the authentication password
426   *          syntax.
427   */
428  public String getAuthPasswordSchemeName()
429  {
430    return getStorageSchemeName();
431  }
432
433
434
435  /**
436   * Encodes the provided plaintext password using the authentication password
437   * syntax as defined in RFC 3112.  This should only be used if the
438   * {@link #supportsAuthPasswordSyntax} method returns {@code true}.
439   *
440   * @param  plaintext  The plaintext password to be encoded.  It must not be
441   *                    {@code null}.  Note that there is no guarantee that
442   *                    password validators have yet been invoked for this
443   *                    password, so this password storage scheme implementation
444   *                    should not make any assumptions about the format of the
445   *                    plaintext password or whether it will actually be
446   *                    allowed for use in the entry.
447   *
448   * @return  The encoded representation of the provided password.
449   *
450   * @throws  LDAPException  If a problem occurs while encoding the provided
451   *                         password, or if this password storage scheme does
452   *                         not support the authentication password syntax.
453   */
454  public ByteString encodeAuthPassword(final ByteString plaintext)
455         throws LDAPException
456  {
457    throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
458         "This password storage scheme does not support the use of the " +
459         "authentication password syntax.");
460  }
461
462
463
464  /**
465   * Indicates whether the provided plaintext password may be used to generate
466   * an encoded password with the given authInfo and authValue elements when
467   * using the authentication password syntax as defined in RFC 3112.  This
468   * should only be used if the {@link #supportsAuthPasswordSyntax} method
469   * returns {@code true}.
470   *
471   * @param  plaintext  The plaintext password for which to make the
472   *                    determination.
473   * @param  authInfo   The authInfo portion of the encoded password for which
474   *                    to make the determination.
475   * @param  authValue  The authValue portion of the encoded password for which
476   *                    to make the determination.
477   *
478   * @return  {@code true} if the provided plaintext password could be used to
479   *          generate an encoded password with the given authInfo and authValue
480   *          portions, or {@code false} if not.
481   */
482  public boolean authPasswordMatches(final ByteString plaintext,
483                                     final String authInfo,
484                                     final String authValue)
485  {
486    return false;
487  }
488
489
490
491  /**
492   * Obtains the plaintext password that was used to generate an encoded
493   * password with the given authInfo and authValue elements when using the
494   * authentication password syntax as described in RFC 3112.  This should only
495   * be used if both the {@link #supportsAuthPasswordSyntax} and
496   * {@link #isReversible} methods return {@code true}.
497   *
498   * @param  authInfo   The authInfo portion of the encoded password for which
499   *                    to retrieve the corresponding plaintext value.
500   * @param  authValue  The authValue portion of the encoded password for which
501   *                    to retrieve the corresponding plaintext value.
502   *
503   * @return  The plaintext password that was used to generate the encoded
504   *          password.
505   *
506   * @throws  LDAPException  If this password storage scheme is not reversible,
507   *                         if this password storage scheme does not support
508   *                         the authentication password syntax, or if some
509   *                         other problem is encountered while attempting to
510   *                         determine the plaintext password.
511   */
512  public ByteString getAuthPasswordPlaintextValue(final String authInfo,
513                                                  final String authValue)
514         throws LDAPException
515  {
516    throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
517         "This password storage scheme does not support the use of the " +
518         "authentication password syntax.");
519  }
520
521
522
523  /**
524   * {@inheritDoc}
525   */
526  public Map<List<String>,String> getExamplesArgumentSets()
527  {
528    return Collections.emptyMap();
529  }
530}