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}