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