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