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