001/*
002 * Copyright 2018-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2018-2024 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) 2018-2024 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.sdk.unboundidds.tools;
037
038
039
040import java.io.BufferedInputStream;
041import java.io.BufferedReader;
042import java.io.ByteArrayInputStream;
043import java.io.File;
044import java.io.FileInputStream;
045import java.io.FileReader;
046import java.io.IOException;
047import java.io.InputStream;
048import java.io.PrintStream;
049import java.lang.reflect.Method;
050import java.security.GeneralSecurityException;
051import java.security.InvalidKeyException;
052import java.util.ArrayList;
053import java.util.Arrays;
054import java.util.Collection;
055import java.util.Collections;
056import java.util.Iterator;
057import java.util.List;
058import java.util.logging.Level;
059import java.util.zip.GZIPInputStream;
060
061import com.unboundid.ldap.sdk.LDAPException;
062import com.unboundid.ldap.sdk.ResultCode;
063import com.unboundid.util.AggregateInputStream;
064import com.unboundid.util.ByteStringBuffer;
065import com.unboundid.util.Debug;
066import com.unboundid.util.NotNull;
067import com.unboundid.util.Nullable;
068import com.unboundid.util.ObjectPair;
069import com.unboundid.util.PassphraseEncryptedInputStream;
070import com.unboundid.util.PassphraseEncryptedOutputStream;
071import com.unboundid.util.PassphraseEncryptedStreamHeader;
072import com.unboundid.util.PasswordReader;
073import com.unboundid.util.StaticUtils;
074import com.unboundid.util.ThreadSafety;
075import com.unboundid.util.ThreadSafetyLevel;
076import com.unboundid.util.Validator;
077
078import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*;
079
080
081
082/**
083 * This class provides a number of utility methods primarily intended for use
084 * with command-line tools.
085 * <BR>
086 * <BLOCKQUOTE>
087 *   <B>NOTE:</B>  This class, and other classes within the
088 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
089 *   supported for use against Ping Identity, UnboundID, and
090 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
091 *   for proprietary functionality or for external specifications that are not
092 *   considered stable or mature enough to be guaranteed to work in an
093 *   interoperable way with other types of LDAP servers.
094 * </BLOCKQUOTE>
095 */
096@ThreadSafety(level= ThreadSafetyLevel.NOT_THREADSAFE)
097public final class ToolUtils
098{
099  /**
100   * The column at which long lines should be wrapped.
101   */
102  private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;
103
104
105
106  /**
107   * A handle to a method that can be used to get the passphrase for an
108   * encryption settings definition ID if the server code is available.  We have
109   * to call this via reflection because the server code may not be available.
110   */
111  @Nullable private static final Method
112       GET_PASSPHRASE_FOR_ENCRYPTION_SETTINGS_ID_METHOD;
113  static
114  {
115    Method m = null;
116
117    try
118    {
119      final Class<?> serverStaticUtilsClass = Class.forName(
120           "com.unboundid.directory.server.util.StaticUtils");
121      m = serverStaticUtilsClass.getMethod(
122           "getPassphraseForEncryptionSettingsID", String.class,
123           PrintStream.class, PrintStream.class);
124    }
125    catch (final Exception e)
126    {
127      // This is fine.  It probably just means that the server code isn't
128      // available.
129      Debug.debugException(Level.FINEST, e);
130    }
131
132    GET_PASSPHRASE_FOR_ENCRYPTION_SETTINGS_ID_METHOD = m;
133  }
134
135
136
137  /**
138   * Prevent this utility class from being instantiated.
139   */
140  private ToolUtils()
141  {
142    // No implementation is required.
143  }
144
145
146
147  /**
148   * Reads an encryption passphrase from the specified file.  The file must
149   * contain exactly one line, which must not be empty, and must be comprised
150   * entirely of the encryption passphrase.
151   *
152   * @param  f  The file from which the passphrase should be read.  It must not
153   *            be {@code null}.
154   *
155   * @return  The encryption passphrase read from the specified file.
156   *
157   * @throws  LDAPException  If a problem occurs while attempting to read the
158   *                         encryption passphrase.
159   */
160  @NotNull()
161  public static String readEncryptionPassphraseFromFile(@NotNull final File f)
162         throws LDAPException
163  {
164    Validator.ensureTrue((f != null),
165         "ToolUtils.readEncryptionPassphraseFromFile.f must not be null.");
166
167    if (! f.exists())
168    {
169      throw new LDAPException(ResultCode.PARAM_ERROR,
170           ERR_TOOL_UTILS_ENCRYPTION_PW_FILE_MISSING.get(f.getAbsolutePath()));
171    }
172
173    if (! f.isFile())
174    {
175      throw new LDAPException(ResultCode.PARAM_ERROR,
176           ERR_TOOL_UTILS_ENCRYPTION_PW_FILE_NOT_FILE.get(f.getAbsolutePath()));
177    }
178
179    try (FileReader fileReader = new FileReader(f);
180         BufferedReader bufferedReader = new BufferedReader(fileReader))
181    {
182      final String encryptionPassphrase = bufferedReader.readLine();
183      if (encryptionPassphrase == null)
184      {
185        throw new LDAPException(ResultCode.PARAM_ERROR,
186             ERR_TOOL_UTILS_ENCRYPTION_PW_FILE_EMPTY.get(f.getAbsolutePath()));
187      }
188      else if (bufferedReader.readLine() != null)
189      {
190        throw new LDAPException(ResultCode.PARAM_ERROR,
191             ERR_TOOL_UTILS_ENCRYPTION_PW_FILE_MULTIPLE_LINES.get(
192                  f.getAbsolutePath()));
193      }
194      else if (encryptionPassphrase.isEmpty())
195      {
196        throw new LDAPException(ResultCode.PARAM_ERROR,
197             ERR_TOOL_UTILS_ENCRYPTION_PW_FILE_EMPTY.get(f.getAbsolutePath()));
198      }
199
200      return encryptionPassphrase;
201    }
202    catch (final LDAPException e)
203    {
204      Debug.debugException(e);
205      throw e;
206    }
207    catch (final Exception e)
208    {
209      Debug.debugException(e);
210      throw new LDAPException(ResultCode.LOCAL_ERROR,
211           ERR_TOOL_UTILS_ENCRYPTION_PW_FILE_READ_ERROR.get(
212                f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)));
213    }
214  }
215
216
217
218  /**
219   * Interactively prompts the user for an encryption passphrase.
220   *
221   * @param  allowEmpty  Indicates whether the encryption passphrase is allowed
222   *                     to be empty.  If this is {@code false}, then the user
223   *                     will be re-prompted for the passphrase if the value
224   *                     they enter is empty.
225   * @param  confirm     Indicates whether the user will asked to confirm the
226   *                     passphrase.  If this is {@code true}, then the user
227   *                     will have to enter the same passphrase twice.  If this
228   *                     is {@code false}, then the user will only be prompted
229   *                     once.
230   * @param  out         The {@code PrintStream} that will be used for standard
231   *                     output.  It must not be {@code null}.
232   * @param  err         The {@code PrintStream} that will be used for standard
233   *                     error.  It must not be {@code null}.
234   *
235   * @return  The encryption passphrase provided by the user.
236   *
237   * @throws  LDAPException  If a problem is encountered while trying to obtain
238   *                         the passphrase from the user.
239   */
240  @NotNull()
241  public static String promptForEncryptionPassphrase(final boolean allowEmpty,
242              final boolean confirm,
243              @NotNull final PrintStream out,
244              @NotNull final PrintStream err)
245          throws LDAPException
246  {
247    return promptForEncryptionPassphrase(allowEmpty, confirm,
248         INFO_TOOL_UTILS_ENCRYPTION_PW_PROMPT.get(),
249         INFO_TOOL_UTILS_ENCRYPTION_PW_CONFIRM.get(), out, err);
250  }
251
252
253
254  /**
255   * Interactively prompts the user for an encryption passphrase.
256   *
257   * @param  allowEmpty     Indicates whether the encryption passphrase is
258   *                        allowed to be empty.  If this is {@code false}, then
259   *                        the user will be re-prompted for the passphrase if
260   *                        the value they enter is empty.
261   * @param  confirm        Indicates whether the user will asked to confirm the
262   *                        passphrase.  If this is {@code true}, then the user
263   *                        will have to enter the same passphrase twice.  If
264   *                        this is {@code false}, then the user will only be
265   *                        prompted once.
266   * @param  initialPrompt  The initial prompt that will be presented to the
267   *                        user.  It must not be {@code null} or empty.
268   * @param  confirmPrompt  The prompt that will be presented to the user when
269   *                        asked to confirm the passphrase.  It may be
270   *                        {@code null} only if {@code confirm} is
271   *                        {@code false}.
272   * @param  out            The {@code PrintStream} that will be used for
273   *                        standard output.  It must not be {@code null}.
274   * @param  err            The {@code PrintStream} that will be used for
275   *                        standard error.  It must not be {@code null}.
276   *
277   * @return  The encryption passphrase provided by the user.
278   *
279   * @throws  LDAPException  If a problem is encountered while trying to obtain
280   *                         the passphrase from the user.
281   */
282  @NotNull()
283  public static String promptForEncryptionPassphrase(final boolean allowEmpty,
284                            final boolean confirm,
285                            @NotNull final CharSequence initialPrompt,
286                            @Nullable final CharSequence confirmPrompt,
287                            @NotNull final PrintStream out,
288                            @NotNull final PrintStream err)
289          throws LDAPException
290  {
291    Validator.ensureTrue(
292         ((initialPrompt != null) && (initialPrompt.length() > 0)),
293         "TestUtils.promptForEncryptionPassphrase.initialPrompt must not be " +
294              "null or empty.");
295    Validator.ensureTrue(
296         ((! confirm) ||
297              ((confirmPrompt != null) && (confirmPrompt.length() > 0))),
298         "TestUtils.promptForEncryptionPassphrase.confirmPrompt must not be " +
299              "null or empty when confirm is true.");
300    Validator.ensureTrue((out != null),
301         "ToolUtils.promptForEncryptionPassphrase.out must not be null");
302    Validator.ensureTrue((err != null),
303         "ToolUtils.promptForEncryptionPassphrase.err must not be null");
304
305    while (true)
306    {
307      char[] passphraseChars = null;
308      char[] confirmChars = null;
309
310      try
311      {
312        wrapPrompt(initialPrompt, true, out);
313
314        passphraseChars = PasswordReader.readPasswordChars();
315        if ((passphraseChars == null) || (passphraseChars.length == 0))
316        {
317          if (allowEmpty)
318          {
319            passphraseChars = StaticUtils.NO_CHARS;
320          }
321          else
322          {
323            wrap(ERR_TOOL_UTILS_ENCRYPTION_PW_EMPTY.get(), err);
324            err.println();
325            continue;
326          }
327        }
328
329        if (confirm)
330        {
331          wrapPrompt(confirmPrompt, true, out);
332
333          confirmChars = PasswordReader.readPasswordChars();
334          if ((confirmChars == null) ||
335               (! Arrays.equals(passphraseChars, confirmChars)))
336          {
337            wrap(ERR_TOOL_UTILS_ENCRYPTION_PW_MISMATCH.get(), err);
338            err.println();
339            continue;
340          }
341        }
342
343        return new String(passphraseChars);
344      }
345      finally
346      {
347        if (passphraseChars != null)
348        {
349          Arrays.fill(passphraseChars, '\u0000');
350        }
351
352        if (confirmChars != null)
353        {
354          Arrays.fill(confirmChars, '\u0000');
355        }
356      }
357    }
358  }
359
360
361
362  /**
363   * Writes a wrapped version of the provided message to the given stream.
364   *
365   * @param  message  The message to be written.  If it is {@code null} or
366   *                  empty, then an empty line will be printed.
367   * @param  out      The {@code PrintStream} that should be used to write the
368   *                  provided message.
369   */
370  public static void wrap(@NotNull final CharSequence message,
371                          @NotNull final PrintStream out)
372  {
373    Validator.ensureTrue((out != null), "ToolUtils.wrap.out must not be null.");
374
375    if ((message == null) || (message.length() == 0))
376    {
377      out.println();
378      return;
379    }
380
381    for (final String line :
382         StaticUtils.wrapLine(message.toString(), WRAP_COLUMN))
383    {
384      out.println(line);
385    }
386  }
387
388
389
390  /**
391   * Wraps the provided prompt such that every line except the last will be
392   * followed by a newline, but the last line will not be followed by a newline.
393   *
394   * @param  prompt               The prompt to be wrapped.  It must not be
395   *                              {@code null} or empty.
396   * @param  ensureTrailingSpace  Indicates whether to ensure that there is a
397   *                              trailing space after the end of the prompt.
398   * @param  out                  The {@code PrintStream} to which the prompt
399   *                              should be written.  It must not be
400   *                              {@code null}.
401   */
402  public static void wrapPrompt(@NotNull final CharSequence prompt,
403                                final boolean ensureTrailingSpace,
404                                @NotNull final PrintStream out)
405  {
406    Validator.ensureTrue(((prompt != null) && (prompt.length() > 0)),
407         "ToolUtils.wrapPrompt.prompt must not be null or empty.");
408    Validator.ensureTrue((out != null),
409         "ToolUtils.wrapPrompt.out must not be null.");
410
411    String promptString = prompt.toString();
412    if (ensureTrailingSpace && (! promptString.endsWith(" ")))
413    {
414      promptString += ' ';
415    }
416
417    final List<String> lines = StaticUtils.wrapLine(promptString, WRAP_COLUMN);
418    final Iterator<String> iterator = lines.iterator();
419    while (iterator.hasNext())
420    {
421      final String line = iterator.next();
422      if (iterator.hasNext())
423      {
424        out.println(line);
425      }
426      else
427      {
428        out.print(line);
429      }
430    }
431  }
432
433
434
435  /**
436   * Retrieves an input stream that can be used to read data from the specified
437   * list of files.  It will handle the possibility that any or all of the LDIF
438   * files are encrypted and/or compressed.
439   *
440   * @param  ldifFiles             The list of LDIF files from which the data
441   *                               is to be read.  It must not be {@code null}
442   *                               or empty.
443   * @param  encryptionPassphrase  The passphrase that should be used to access
444   *                               encrypted LDIF files.  It may be {@code null}
445   *                               if the user should be interactively prompted
446   *                               for the passphrase if any of the files is
447   *                               encrypted.
448   * @param  out                   The print stream to use for standard output.
449   *                               It must not be {@code null}.
450   * @param  err                   The print stream to use for standard error.
451   *                               It must not be {@code null}.
452   *
453   * @return  An {@code ObjectPair} whose first element is an input stream that
454   *          can be used to read data from the specified list of files, and
455   *          whose second element is a possibly-{@code null} passphrase that
456   *          is used to encrypt the input data.
457   *
458   * @throws  IOException  If a problem is encountered while attempting to get
459   *                       the input stream for reading the data.
460   */
461  @NotNull()
462  public static ObjectPair<InputStream,String> getInputStreamForLDIFFiles(
463                     @NotNull final List<File> ldifFiles,
464                     @Nullable final String encryptionPassphrase,
465                     @NotNull final PrintStream out,
466                     @NotNull final PrintStream err)
467         throws IOException
468  {
469    Validator.ensureTrue(((ldifFiles != null) && (! ldifFiles.isEmpty())),
470         "ToolUtils.getInputStreamForLDIFFiles.ldifFiles must not be null or " +
471              "empty.");
472    Validator.ensureTrue((out != null),
473         "ToolUtils.getInputStreamForLDIFFiles.out must not be null");
474    Validator.ensureTrue((err != null),
475         "ToolUtils.getInputStreamForLDIFFiles.err must not be null");
476
477
478    boolean createdSuccessfully = false;
479    final ArrayList<InputStream> inputStreams =
480         new ArrayList<>(ldifFiles.size() * 2);
481
482    try
483    {
484      byte[] twoEOLs = null;
485      String passphrase = encryptionPassphrase;
486      for (final File f : ldifFiles)
487      {
488        if (! inputStreams.isEmpty())
489        {
490          if (twoEOLs == null)
491          {
492            final ByteStringBuffer buffer = new ByteStringBuffer(4);
493            buffer.append(StaticUtils.EOL_BYTES);
494            buffer.append(StaticUtils.EOL_BYTES);
495            twoEOLs = buffer.toByteArray();
496          }
497
498          inputStreams.add(new ByteArrayInputStream(twoEOLs));
499        }
500
501        InputStream inputStream = new FileInputStream(f);
502        try
503        {
504          final ObjectPair<InputStream,String> p =
505               getPossiblyPassphraseEncryptedInputStream(
506                    inputStream, passphrase, (encryptionPassphrase == null),
507                    INFO_TOOL_UTILS_ENCRYPTED_LDIF_FILE_PW_PROMPT.get(
508                         f.getPath()),
509                    ERR_TOOL_UTILS_ENCRYPTED_LDIF_FILE_WRONG_PW.get(), out,
510                    err);
511          inputStream = p.getFirst();
512          if ((p.getSecond() != null) && (passphrase == null))
513          {
514            passphrase = p.getSecond();
515          }
516        }
517        catch (final GeneralSecurityException e)
518        {
519          Debug.debugException(e);
520          inputStream.close();
521          throw new IOException(
522               ERR_TOOL_UTILS_ENCRYPTED_LDIF_FILE_CANNOT_DECRYPT.get(
523                    f.getPath(), StaticUtils.getExceptionMessage(e)),
524               e);
525        }
526
527        inputStream = getPossiblyGZIPCompressedInputStream(inputStream);
528        inputStreams.add(inputStream);
529      }
530
531      createdSuccessfully = true;
532      if (inputStreams.size() == 1)
533      {
534        return new ObjectPair<>(inputStreams.get(0), passphrase);
535      }
536      else
537      {
538        return new ObjectPair<InputStream,String>(
539             new AggregateInputStream(inputStreams), passphrase);
540      }
541    }
542    finally
543    {
544      if (! createdSuccessfully)
545      {
546        for (final InputStream inputStream : inputStreams)
547        {
548          try
549          {
550            inputStream.close();
551          }
552          catch (final IOException e)
553          {
554            Debug.debugException(e);
555          }
556        }
557      }
558    }
559  }
560
561
562
563  /**
564   * Retrieves an {@code InputStream} that can be used to read data from the
565   * provided input stream that may have potentially been GZIP-compressed.  If
566   * the provided input stream does not appear to contain GZIP-compressed data,
567   * then the returned stream will permit reading the data from the provided
568   * stream without any alteration.
569   * <BR><BR>
570   * The determination will be made by looking to see if the first two bytes
571   * read from the provided input stream are 0x1F and 0x8B, respectively (which
572   * is the GZIP magic header).  To avoid false positives, this method should
573   * only be used if it is known that if the input stream does not contain
574   * compressed data, then it will not start with that two-byte sequence.  This
575   * method should always be safe to use if the data to be read is text.  If the
576   * data may be binary and that binary data may happen to start with 0x1F 0x8B,
577   * then this method should not be used.
578   * <BR><BR>
579   * The input stream's {@code mark} and {@code reset} methods will be used to
580   * permit peeking at the data at the head of the input stream.  If the
581   * provided stream does not support the use of those methods, then it will be
582   * wrapped in a {@code BufferedInputStream}, which does support them.
583   *
584   * @param  inputStream  The input stream from which the data is to be read.
585   *
586   * @return  A {@code GZIPInputStream} that wraps the provided input stream if
587   *          the stream appears to contain GZIP-compressed data, or the
588   *          provided input stream (potentially wrapped in a
589   *          {@code BufferedInputStream}) if the provided stream does not
590   *          appear to contain GZIP-compressed data.
591   *
592   * @throws  IOException  If a problem is encountered while attempting to
593   *                       determine whether the stream contains GZIP-compressed
594   *                       data.
595   */
596  @NotNull()
597  public static InputStream getPossiblyGZIPCompressedInputStream(
598                                 @NotNull final InputStream inputStream)
599         throws IOException
600  {
601    Validator.ensureTrue((inputStream != null),
602         "StaticUtils.getPossiblyGZIPCompressedInputStream.inputStream must " +
603              "not be null.");
604
605
606    // Mark the input stream so that we can peek at data from the beginning of
607    // the stream.
608    final InputStream markableInputStream;
609    if (inputStream.markSupported())
610    {
611      markableInputStream = inputStream;
612    }
613    else
614    {
615      markableInputStream = new BufferedInputStream(inputStream);
616    }
617
618    markableInputStream.mark(2);
619
620
621    // Check to see if the file starts with the GZIP magic header.  Whether it
622    // does or not, reset the stream so that we can read it from the beginning.
623    final boolean isCompressed;
624    try
625    {
626      isCompressed = ((markableInputStream.read() == 0x1F) &&
627           (markableInputStream.read() == 0x8B));
628    }
629    finally
630    {
631      markableInputStream.reset();
632    }
633
634
635    // If the stream starts with the GZIP magic header, then assume it's
636    // GZIP-compressed.  Otherwise, assume it's not.
637    if (isCompressed)
638    {
639      return new GZIPInputStream(markableInputStream);
640    }
641    else
642    {
643      return markableInputStream;
644    }
645  }
646
647
648
649  /**
650   * Retrieves an {@code InputStream} that can be used to read data from the
651   * provided input stream that may have potentially been encrypted with a
652   * {@link PassphraseEncryptedOutputStream} using a key from a Ping Identity
653   * or Nokia/Alcatel-Lucent 8661 Directory Server's encryption settings
654   * database.  This method will throw an exception rather than interactively
655   * prompting for a passphrase if the content is encrypted but the encryption
656   * key is not readily available.
657   * <BR><BR>
658   * The determination will be made by looking to see if the input stream starts
659   * with a valid {@link PassphraseEncryptedStreamHeader}.  Because of the
660   * complex nature of that header, it is highly unlikely that the input stream
661   * will just happen to start with a valid header if the stream does not
662   * actually contain encrypted data.
663   * <BR><BR>
664   * The input stream's {@code mark} and {@code reset} methods will be used to
665   * permit peeking at the data at the head of the input stream.  If the
666   * provided stream does not support the use of those methods, then it will be
667   * wrapped in a {@code BufferedInputStream}, which does support them.
668   *
669   * @param  inputStream  The input stream from which the data is to be read.
670   *                      It must not be {@code null}.
671   *
672   * @return  The resulting input stream that may be used to read from the
673   *          given input stream.  If the data was encrypted, then the returned
674   *          stream will be a {@link PassphraseEncryptedInputStream} that may
675   *          be used to read decrypted data from it.  If the data was not
676   *          encrypted, then the original stream (possibly wrapped by a
677   *          {@code BufferedInputStream} will be returned.
678   *
679   * @throws  IOException  If a problem is encountered while attempting to
680   *                       determine whether the stream contains
681   *                       passphrase-encrypted data.
682   *
683   * @throws  GeneralSecurityException  If a problem is encountered while
684   *                                    attempting to prepare to decrypt data
685   *                                    read from the input stream, or if the
686   *                                    stream is encrypted with a key that is
687   *                                    not readily available.
688   */
689  @NotNull()
690  public static InputStream getPossiblyPassphraseEncryptedInputStream(
691              @NotNull final InputStream inputStream)
692         throws IOException, GeneralSecurityException
693  {
694    final ObjectPair<InputStream,char[]> pair =
695         getPossiblyPassphraseEncryptedInputStream(inputStream,
696              Collections.<char[]>emptyList(), false, false, "", "", System.out,
697              System.err);
698    return pair.getFirst();
699  }
700
701
702
703  /**
704   * Retrieves an {@code InputStream} that can be used to read data from the
705   * provided input stream that may have potentially been encrypted with a
706   * {@link PassphraseEncryptedOutputStream}.  If the provided input stream does
707   * not appear to contain passphrase-encrypted data, then the returned stream
708   * will permit reading the data from the provided stream without any
709   * alteration.
710   * <BR><BR>
711   * The determination will be made by looking to see if the input stream starts
712   * with a valid {@link PassphraseEncryptedStreamHeader}.  Because of the
713   * complex nature of that header, it is highly unlikely that the input stream
714   * will just happen to start with a valid header if the stream does not
715   * actually contain encrypted data.
716   * <BR><BR>
717   * The input stream's {@code mark} and {@code reset} methods will be used to
718   * permit peeking at the data at the head of the input stream.  If the
719   * provided stream does not support the use of those methods, then it will be
720   * wrapped in a {@code BufferedInputStream}, which does support them.
721   *
722   * @param  inputStream                  The input stream from which the data
723   *                                      is to be read.  It must not be
724   *                                      {@code null}.
725   * @param  potentialPassphrase          A potential passphrase that may have
726   *                                      been used to encrypt the data.  It
727   *                                      may be {@code null} if the passphrase
728   *                                      should only be obtained via
729   *                                      interactive prompting, or if the
730   *                                      data was encrypted with a server-side
731   *                                      encryption settings definition.  If
732   *                                      the passphrase is not {@code null} but
733   *                                      is incorrect, then the user may be
734   *                                      interactively prompted for the correct
735   *                                      passphrase.
736   * @param  promptOnIncorrectPassphrase  Indicates whether the user should be
737   *                                      interactively prompted for the correct
738   *                                      passphrase if the provided passphrase
739   *                                      is non-{@code null} and is also
740   *                                      incorrect.
741   * @param  passphrasePrompt             The prompt that will be presented to
742   *                                      the user if the input stream does
743   *                                      contain encrypted data and the
744   *                                      passphrase needs to be interactively
745   *                                      requested from the user.  It must not
746   *                                      be {@code null} or empty.
747   * @param  incorrectPassphraseError     The error message that will be
748   *                                      presented to the user if the entered
749   *                                      passphrase is not correct.  It must
750   *                                      not be {@code null} or empty.
751   * @param  standardOutput               The {@code PrintStream} to use to
752   *                                      write to standard output while
753   *                                      interactively prompting for the
754   *                                      passphrase.  It must not be
755   *                                      {@code null}.
756   * @param  standardError                The {@code PrintStream} to use to
757   *                                      write to standard error while
758   *                                      interactively prompting for the
759   *                                      passphrase.  It must not be
760   *                                      {@code null}.
761   *
762   * @return  An {@code ObjectPair} that combines the resulting input stream
763   *          with the associated encryption passphrase.  If the provided input
764   *          stream is encrypted, then the returned input stream element will
765   *          be a {@code PassphraseEncryptedInputStream} and the returned
766   *          passphrase element will be non-{@code null}.  If the provided
767   *          input stream is not encrypted, then the returned input stream
768   *          element will be the provided input stream (potentially wrapped in
769   *          a {@code BufferedInputStream}), and the returned passphrase
770   *          element will be {@code null}.
771   *
772   * @throws  IOException  If a problem is encountered while attempting to
773   *                       determine whether the stream contains
774   *                       passphrase-encrypted data.
775   *
776   * @throws  InvalidKeyException  If the provided passphrase is incorrect and
777   *                               the user should not be interactively prompted
778   *                               for the correct passphrase.
779   *
780   * @throws  GeneralSecurityException  If a problem is encountered while
781   *                                    attempting to prepare to decrypt data
782   *                                    read from the input stream.
783   */
784  @NotNull()
785  public static ObjectPair<InputStream,String>
786                     getPossiblyPassphraseEncryptedInputStream(
787                          @NotNull final InputStream inputStream,
788                          @Nullable final String potentialPassphrase,
789                          final boolean promptOnIncorrectPassphrase,
790                          @NotNull final CharSequence passphrasePrompt,
791                          @NotNull final CharSequence incorrectPassphraseError,
792                          @NotNull final PrintStream standardOutput,
793                          @NotNull final PrintStream standardError)
794         throws IOException, InvalidKeyException, GeneralSecurityException
795  {
796    final Collection<char[]> potentialPassphrases;
797    if (potentialPassphrase == null)
798    {
799      potentialPassphrases = Collections.emptySet();
800    }
801    else
802    {
803      potentialPassphrases =
804           Collections.singleton(potentialPassphrase.toCharArray());
805    }
806
807    final ObjectPair<InputStream, char[]> p =
808         getPossiblyPassphraseEncryptedInputStream(inputStream,
809              potentialPassphrases, promptOnIncorrectPassphrase,
810              passphrasePrompt, incorrectPassphraseError, standardOutput,
811              standardError);
812
813    if (p.getSecond() == null)
814    {
815      return new ObjectPair<>(p.getFirst(), null);
816    }
817    else
818    {
819      return new ObjectPair<>(p.getFirst(), new String(p.getSecond()));
820    }
821  }
822
823
824
825  /**
826   * Retrieves an {@code InputStream} that can be used to read data from the
827   * provided input stream that may have potentially been encrypted with a
828   * {@link PassphraseEncryptedOutputStream}.  If the provided input stream does
829   * not appear to contain passphrase-encrypted data, then the returned stream
830   * will permit reading the data from the provided stream without any
831   * alteration.
832   * <BR><BR>
833   * The determination will be made by looking to see if the input stream starts
834   * with a valid {@link PassphraseEncryptedStreamHeader}.  Because of the
835   * complex nature of that header, it is highly unlikely that the input stream
836   * will just happen to start with a valid header if the stream does not
837   * actually contain encrypted data.
838   * <BR><BR>
839   * The input stream's {@code mark} and {@code reset} methods will be used to
840   * permit peeking at the data at the head of the input stream.  If the
841   * provided stream does not support the use of those methods, then it will be
842   * wrapped in a {@code BufferedInputStream}, which does support them.
843   *
844   * @param  inputStream                  The input stream from which the data
845   *                                      is to be read.  It must not be
846   *                                      {@code null}.
847   * @param  potentialPassphrase          A potential passphrase that may have
848   *                                      been used to encrypt the data.  It
849   *                                      may be {@code null} if the passphrase
850   *                                      should only be obtained via
851   *                                      interactive prompting, or if the
852   *                                      data was encrypted with a server-side
853   *                                      encryption settings definition.  If
854   *                                      the passphrase is not {@code null} but
855   *                                      is incorrect, then the user may be
856   *                                      interactively prompted for the correct
857   *                                      passphrase.
858   * @param  promptOnIncorrectPassphrase  Indicates whether the user should be
859   *                                      interactively prompted for the correct
860   *                                      passphrase if the provided passphrase
861   *                                      is non-{@code null} and is also
862   *                                      incorrect.
863   * @param  passphrasePrompt             The prompt that will be presented to
864   *                                      the user if the input stream does
865   *                                      contain encrypted data and the
866   *                                      passphrase needs to be interactively
867   *                                      requested from the user.  It must not
868   *                                      be {@code null} or empty.
869   * @param  incorrectPassphraseError     The error message that will be
870   *                                      presented to the user if the entered
871   *                                      passphrase is not correct.  It must
872   *                                      not be {@code null} or empty.
873   * @param  standardOutput               The {@code PrintStream} to use to
874   *                                      write to standard output while
875   *                                      interactively prompting for the
876   *                                      passphrase.  It must not be
877   *                                      {@code null}.
878   * @param  standardError                The {@code PrintStream} to use to
879   *                                      write to standard error while
880   *                                      interactively prompting for the
881   *                                      passphrase.  It must not be
882   *                                      {@code null}.
883   *
884   * @return  An {@code ObjectPair} that combines the resulting input stream
885   *          with the associated encryption passphrase.  If the provided input
886   *          stream is encrypted, then the returned input stream element will
887   *          be a {@code PassphraseEncryptedInputStream} and the returned
888   *          passphrase element will be non-{@code null}.  If the provided
889   *          input stream is not encrypted, then the returned input stream
890   *          element will be the provided input stream (potentially wrapped in
891   *          a {@code BufferedInputStream}), and the returned passphrase
892   *          element will be {@code null}.
893   *
894   * @throws  IOException  If a problem is encountered while attempting to
895   *                       determine whether the stream contains
896   *                       passphrase-encrypted data.
897   *
898   * @throws  InvalidKeyException  If the provided passphrase is incorrect and
899   *                               the user should not be interactively prompted
900   *                               for the correct passphrase.
901   *
902   * @throws  GeneralSecurityException  If a problem is encountered while
903   *                                    attempting to prepare to decrypt data
904   *                                    read from the input stream.
905   */
906  @NotNull()
907  public static ObjectPair<InputStream,char[]>
908                     getPossiblyPassphraseEncryptedInputStream(
909                          @NotNull final InputStream inputStream,
910                          @Nullable final char[] potentialPassphrase,
911                          final boolean promptOnIncorrectPassphrase,
912                          @NotNull final CharSequence passphrasePrompt,
913                          @NotNull final CharSequence incorrectPassphraseError,
914                          @NotNull final PrintStream standardOutput,
915                          @NotNull final PrintStream standardError)
916         throws IOException, InvalidKeyException, GeneralSecurityException
917  {
918    final Collection<char[]> potentialPassphrases;
919    if (potentialPassphrase == null)
920    {
921      potentialPassphrases = Collections.emptySet();
922    }
923    else
924    {
925      potentialPassphrases =
926           Collections.singleton(potentialPassphrase);
927    }
928
929    final ObjectPair<InputStream, char[]> p =
930         getPossiblyPassphraseEncryptedInputStream(inputStream,
931              potentialPassphrases, promptOnIncorrectPassphrase,
932              passphrasePrompt, incorrectPassphraseError, standardOutput,
933              standardError);
934
935    if (p.getSecond() == null)
936    {
937      return new ObjectPair<>(p.getFirst(), null);
938    }
939    else
940    {
941      return new ObjectPair<>(p.getFirst(), p.getSecond());
942    }
943  }
944
945
946
947  /**
948   * Retrieves an {@code InputStream} that can be used to read data from the
949   * provided input stream that may have potentially been encrypted with a
950   * {@link PassphraseEncryptedOutputStream}.  If the provided input stream does
951   * not appear to contain passphrase-encrypted data, then the returned stream
952   * will permit reading the data from the provided stream without any
953   * alteration.
954   * <BR><BR>
955   * The determination will be made by looking to see if the input stream starts
956   * with a valid {@link PassphraseEncryptedStreamHeader}.  Because of the
957   * complex nature of that header, it is highly unlikely that the input stream
958   * will just happen to start with a valid header if the stream does not
959   * actually contain encrypted data.
960   * <BR><BR>
961   * The input stream's {@code mark} and {@code reset} methods will be used to
962   * permit peeking at the data at the head of the input stream.  If the
963   * provided stream does not support the use of those methods, then it will be
964   * wrapped in a {@code BufferedInputStream}, which does support them.
965   *
966   * @param  inputStream
967   *              The input stream from which the data is to be read.  It must
968   *              not be {@code null}.
969   * @param  potentialPassphrases
970   *              A collection of potential passphrases that may have been used
971   *              to encrypt the data.  It may be {@code null} or empty if the
972   *              passphrase should only be obtained via interactive prompting,
973   *              or if the data was encrypted with a server-side encryption
974   *              settings definition.  If none of the provided passphrases are
975   *              correct, then the user may still be interactively prompted
976   *              for the correct passphrase based on the value of the
977   *              {@code promptOnIncorrectPassphrase} argument.
978   * @param  promptOnIncorrectPassphrase
979   *              Indicates whether the user should be interactively prompted
980   *              for the correct passphrase if the set of potential passphrases
981   *              is non-{@code null} and non-empty, but none of the passphrases
982   *              were correct.
983   * @param  passphrasePrompt
984   *              The prompt that will be presented to the user if the input
985   *              stream does contain encrypted data and the passphrase needs to
986   *              be interactively requested from the user.  It must not be
987   *              {@code null} or empty.
988   * @param  incorrectPassphraseError
989   *              The error message that will be presented to the user if the
990   *              entered passphrase is not correct.  It must not be
991   *              {@code null} or empty.
992   * @param  standardOutput
993   *              The {@code PrintStream} to use to write to standard output
994   *              while interactively prompting for the passphrase.  It must not
995   *              be {@code null}.
996   * @param  standardError
997   *              The {@code PrintStream} to use to write to standard error
998   *              while interactively prompting for the passphrase.  It must not
999   *              be {@code null}.
1000   *
1001   * @return  An {@code ObjectPair} that combines the resulting input stream
1002   *          with the associated encryption passphrase.  If the provided input
1003   *          stream is encrypted, then the returned input stream element will
1004   *          be a {@code PassphraseEncryptedInputStream} and the returned
1005   *          passphrase element will be non-{@code null}.  If the provided
1006   *          input stream is not encrypted, then the returned input stream
1007   *          element will be the provided input stream (potentially wrapped in
1008   *          a {@code BufferedInputStream}), and the returned passphrase
1009   *          element will be {@code null}.
1010   *
1011   * @throws  IOException  If a problem is encountered while attempting to
1012   *                       determine whether the stream contains
1013   *                       passphrase-encrypted data.
1014   *
1015   * @throws  InvalidKeyException  If the provided passphrase is incorrect and
1016   *                               the user should not be interactively prompted
1017   *                               for the correct passphrase.
1018   *
1019   * @throws  GeneralSecurityException  If a problem is encountered while
1020   *                                    attempting to prepare to decrypt data
1021   *                                    read from the input stream.
1022   */
1023  @NotNull()
1024  public static ObjectPair<InputStream,char[]>
1025              getPossiblyPassphraseEncryptedInputStream(
1026                   @NotNull final InputStream inputStream,
1027                   @Nullable final Collection<char[]> potentialPassphrases,
1028                   final boolean promptOnIncorrectPassphrase,
1029                   @NotNull final CharSequence passphrasePrompt,
1030                   @NotNull final CharSequence incorrectPassphraseError,
1031                   @NotNull final PrintStream standardOutput,
1032                   @NotNull final PrintStream standardError)
1033         throws IOException, InvalidKeyException, GeneralSecurityException
1034  {
1035    return getPossiblyPassphraseEncryptedInputStream(inputStream,
1036         potentialPassphrases, promptOnIncorrectPassphrase, true,
1037         passphrasePrompt, incorrectPassphraseError, standardOutput,
1038         standardError);
1039  }
1040
1041
1042
1043  /**
1044   * Retrieves an {@code InputStream} that can be used to read data from the
1045   * provided input stream that may have potentially been encrypted with a
1046   * {@link PassphraseEncryptedOutputStream}.  If the provided input stream does
1047   * not appear to contain passphrase-encrypted data, then the returned stream
1048   * will permit reading the data from the provided stream without any
1049   * alteration.
1050   * <BR><BR>
1051   * The determination will be made by looking to see if the input stream starts
1052   * with a valid {@link PassphraseEncryptedStreamHeader}.  Because of the
1053   * complex nature of that header, it is highly unlikely that the input stream
1054   * will just happen to start with a valid header if the stream does not
1055   * actually contain encrypted data.
1056   * <BR><BR>
1057   * The input stream's {@code mark} and {@code reset} methods will be used to
1058   * permit peeking at the data at the head of the input stream.  If the
1059   * provided stream does not support the use of those methods, then it will be
1060   * wrapped in a {@code BufferedInputStream}, which does support them.
1061   *
1062   * @param  inputStream
1063   *              The input stream from which the data is to be read.  It must
1064   *              not be {@code null}.
1065   * @param  potentialPassphrases
1066   *              A collection of potential passphrases that may have been used
1067   *              to encrypt the data.  It may be {@code null} or empty if the
1068   *              passphrase should only be obtained via interactive prompting,
1069   *              or if the data was encrypted with a server-side encryption
1070   *              settings definition.  If none of the provided passphrases are
1071   *              correct, then the user may still be interactively prompted
1072   *              for the correct passphrase based on the value of the
1073   *              {@code promptOnIncorrectPassphrase} argument.
1074   * @param  promptOnIncorrectPassphrase
1075   *              Indicates whether the user should be interactively prompted
1076   *              for the correct passphrase if the set of potential passphrases
1077   *              is non-{@code null} and non-empty, but none of the passphrases
1078   *              were correct.
1079   * @param  promptOnUnavailablePassphrase
1080   *              Indicates whether the user should be interactively prompted
1081   *              for the correct passphrase if the input stream is encrypted,
1082   *              the key cannot be automatically obtained from a server-side
1083   *              encryption settings definition, and no potential passphrases
1084   *              were provided.  If this is {@code false}, then an exception
1085   *              will be thrown if the data is encrypted with a key that is
1086   *              not available.
1087   * @param  passphrasePrompt
1088   *              The prompt that will be presented to the user if the input
1089   *              stream does contain encrypted data and the passphrase needs to
1090   *              be interactively requested from the user.  It must not be
1091   *              {@code null} or empty.
1092   * @param  incorrectPassphraseError
1093   *              The error message that will be presented to the user if the
1094   *              entered passphrase is not correct.  It must not be
1095   *              {@code null} or empty.
1096   * @param  standardOutput
1097   *              The {@code PrintStream} to use to write to standard output
1098   *              while interactively prompting for the passphrase.  It must not
1099   *              be {@code null}.
1100   * @param  standardError
1101   *              The {@code PrintStream} to use to write to standard error
1102   *              while interactively prompting for the passphrase.  It must not
1103   *              be {@code null}.
1104   *
1105   * @return  An {@code ObjectPair} that combines the resulting input stream
1106   *          with the associated encryption passphrase.  If the provided input
1107   *          stream is encrypted, then the returned input stream element will
1108   *          be a {@code PassphraseEncryptedInputStream} and the returned
1109   *          passphrase element will be non-{@code null}.  If the provided
1110   *          input stream is not encrypted, then the returned input stream
1111   *          element will be the provided input stream (potentially wrapped in
1112   *          a {@code BufferedInputStream}), and the returned passphrase
1113   *          element will be {@code null}.
1114   *
1115   * @throws  IOException  If a problem is encountered while attempting to
1116   *                       determine whether the stream contains
1117   *                       passphrase-encrypted data.
1118   *
1119   * @throws  InvalidKeyException  If the provided passphrase is incorrect and
1120   *                               the user should not be interactively prompted
1121   *                               for the correct passphrase.
1122   *
1123   * @throws  GeneralSecurityException  If a problem is encountered while
1124   *                                    attempting to prepare to decrypt data
1125   *                                    read from the input stream.
1126   */
1127  @NotNull()
1128  private static ObjectPair<InputStream,char[]>
1129               getPossiblyPassphraseEncryptedInputStream(
1130                    @NotNull final InputStream inputStream,
1131                    @Nullable final Collection<char[]> potentialPassphrases,
1132                    final boolean promptOnIncorrectPassphrase,
1133                    final boolean promptOnUnavailablePassphrase,
1134                    @Nullable final CharSequence passphrasePrompt,
1135                    @Nullable final CharSequence incorrectPassphraseError,
1136                    @Nullable final PrintStream standardOutput,
1137                    @Nullable final PrintStream standardError)
1138         throws IOException, InvalidKeyException, GeneralSecurityException
1139  {
1140    Validator.ensureTrue((inputStream != null),
1141         "StaticUtils.getPossiblyPassphraseEncryptedInputStream.inputStream " +
1142              "must not be null.");
1143    Validator.ensureTrue(
1144         (((passphrasePrompt != null) && (passphrasePrompt.length() > 0)) ||
1145              (! promptOnUnavailablePassphrase)),
1146         "StaticUtils.getPossiblyPassphraseEncryptedInputStream." +
1147              "passphrasePrompt must not be null or empty.");
1148    Validator.ensureTrue(
1149         (((incorrectPassphraseError != null) &&
1150              (incorrectPassphraseError.length() > 0)) ||
1151              (! promptOnUnavailablePassphrase)),
1152         "StaticUtils.getPossiblyPassphraseEncryptedInputStream." +
1153              "incorrectPassphraseError must not be null or empty.");
1154    Validator.ensureTrue((standardOutput!= null),
1155         "StaticUtils.getPossiblyPassphraseEncryptedInputStream." +
1156              "standardOutput must not be null.");
1157    Validator.ensureTrue((standardError!= null),
1158         "StaticUtils.getPossiblyPassphraseEncryptedInputStream." +
1159              "standardError must not be null.");
1160
1161
1162    // Mark the input stream so that we can peek at data from the beginning of
1163    // the stream.
1164    final InputStream markableInputStream;
1165    if (inputStream.markSupported())
1166    {
1167      markableInputStream = inputStream;
1168    }
1169    else
1170    {
1171      markableInputStream = new BufferedInputStream(inputStream);
1172    }
1173
1174    markableInputStream.mark(1024);
1175
1176
1177    // Try to read a passphrase-encrypted stream header from the beginning of
1178    // the stream.  Just decode the header, but don't attempt to make it usable
1179    // for encryption or decryption.
1180    final PassphraseEncryptedStreamHeader streamHeaderShell;
1181    try
1182    {
1183      streamHeaderShell = PassphraseEncryptedStreamHeader.readFrom(
1184           markableInputStream, null);
1185    }
1186    catch (final LDAPException e)
1187    {
1188      // This is fine.  It just means that the stream doesn't contain encrypted
1189      // data.  In that case, reset the stream and return it so that the
1190      // unencrypted data can be read.
1191      Debug.debugException(Level.FINEST, e);
1192      markableInputStream.reset();
1193      return new ObjectPair<>(markableInputStream, null);
1194    }
1195
1196
1197    // If the header includes a key identifier, and if the server code is
1198    // available, then see if we can get a passphrase for the corresponding
1199    // encryption settings definition ID.
1200    if ((streamHeaderShell.getKeyIdentifier() != null) &&
1201         (GET_PASSPHRASE_FOR_ENCRYPTION_SETTINGS_ID_METHOD != null))
1202    {
1203      try
1204      {
1205        final Object passphraseObject =
1206             GET_PASSPHRASE_FOR_ENCRYPTION_SETTINGS_ID_METHOD.invoke(null,
1207                  streamHeaderShell.getKeyIdentifier(), standardOutput,
1208                  standardError);
1209        if ((passphraseObject != null) && (passphraseObject instanceof String))
1210        {
1211          final char[] passphraseChars =
1212               ((String) passphraseObject).toCharArray();
1213          final PassphraseEncryptedStreamHeader validStreamHeader =
1214               PassphraseEncryptedStreamHeader.decode(
1215                    streamHeaderShell.getEncodedHeader(),
1216                    passphraseChars);
1217          return new ObjectPair<InputStream,char[]>(
1218               new PassphraseEncryptedInputStream(markableInputStream,
1219                    validStreamHeader),
1220               passphraseChars);
1221        }
1222      }
1223      catch (final Exception e)
1224      {
1225        // This means that either an error occurred while trying to get the
1226        // passphrase, or the passphrase we got was incorrect.  That's fine.
1227        // We'll just continue on to prompt for the passphrase.
1228        Debug.debugException(e);
1229      }
1230    }
1231
1232
1233    // If any potential passphrases were provided, then see if any of them is
1234    // correct.
1235    final boolean potentialPassphrasesProvided =
1236         ((potentialPassphrases != null) && (! potentialPassphrases.isEmpty()));
1237    if (potentialPassphrasesProvided)
1238    {
1239      final Iterator<char[]> passphraseIterator =
1240           potentialPassphrases.iterator();
1241      while (passphraseIterator.hasNext())
1242      {
1243        try
1244        {
1245          final char[] passphraseChars = passphraseIterator.next();
1246          final PassphraseEncryptedStreamHeader validStreamHeader =
1247               PassphraseEncryptedStreamHeader.decode(
1248                    streamHeaderShell.getEncodedHeader(),
1249                    passphraseChars);
1250          return new ObjectPair<InputStream,char[]>(
1251               new PassphraseEncryptedInputStream(markableInputStream,
1252                    validStreamHeader),
1253               passphraseChars);
1254        }
1255        catch (final InvalidKeyException e)
1256        {
1257          // The provided passphrase is not correct.  That's fine.  We'll just
1258          // prompt for the correct one.
1259          Debug.debugException(e);
1260          if ((! promptOnIncorrectPassphrase) &&
1261               (! passphraseIterator.hasNext()))
1262          {
1263            throw e;
1264          }
1265        }
1266        catch (final GeneralSecurityException e)
1267        {
1268          Debug.debugException(e);
1269          if (! passphraseIterator.hasNext())
1270          {
1271            throw e;
1272          }
1273        }
1274        catch (final LDAPException e)
1275        {
1276          // This should never happen, since we were previously able to decode
1277          // the header.  Just treat it like a GeneralSecurityException.
1278          Debug.debugException(e);
1279          if (! passphraseIterator.hasNext())
1280          {
1281            throw new GeneralSecurityException(e.getMessage(), e);
1282          }
1283        }
1284      }
1285    }
1286
1287
1288    // The correct passphrase wasn't provided, and it isn't available from an
1289    // encryption settings definition, so we need to prompt for it.  See if
1290    // that's allowed.  Note that if any potential passphrases were provided,
1291    // then we should continue on to prompt, since we've already handled the
1292    // case in which we shouldn't prompt on an incorrect passphrase.
1293    if ((! potentialPassphrasesProvided) && (! promptOnUnavailablePassphrase))
1294    {
1295      throw new GeneralSecurityException(
1296           ERR_TOOL_UTILS_UNAVAILABLE_PASSPHRASE.get());
1297    }
1298
1299    while (true)
1300    {
1301      // Read the passphrase from the user.
1302      final String promptedPassphrase;
1303      try
1304      {
1305        promptedPassphrase =
1306             promptForEncryptionPassphrase(false, false, passphrasePrompt, null,
1307                  standardOutput, standardError);
1308      }
1309      catch (final LDAPException e)
1310      {
1311        Debug.debugException(e);
1312        throw new IOException(e.getMessage(), e);
1313      }
1314
1315
1316      // Check to see if the passphrase was correct.  If so, then use it.
1317      // Otherwise, show an error and prompt again.
1318      try
1319      {
1320        final char[] passphraseChars = promptedPassphrase.toCharArray();
1321        final PassphraseEncryptedStreamHeader validStreamHeader =
1322             PassphraseEncryptedStreamHeader.decode(
1323                  streamHeaderShell.getEncodedHeader(), passphraseChars);
1324        return new ObjectPair<InputStream,char[]>(
1325             new PassphraseEncryptedInputStream(markableInputStream,
1326                  validStreamHeader),
1327             passphraseChars);
1328      }
1329      catch (final InvalidKeyException e)
1330      {
1331        Debug.debugException(e);
1332
1333        // The passphrase was incorrect.  Display a wrapped error message and
1334        // re-prompt.
1335        wrap(incorrectPassphraseError, standardError);
1336        standardError.println();
1337      }
1338      catch (final GeneralSecurityException e)
1339      {
1340        Debug.debugException(e);
1341        throw e;
1342      }
1343      catch (final LDAPException e)
1344      {
1345        // This should never happen, since we were previously able to decode the
1346        // header.  Just treat it like a GeneralSecurityException.
1347        Debug.debugException(e);
1348        throw new GeneralSecurityException(e.getMessage(), e);
1349      }
1350    }
1351  }
1352}