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