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