001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.util.cli; 018 019import static com.forgerock.opendj.cli.Utils.portValidationCallback; 020import static com.forgerock.opendj.cli.Utils.isDN; 021import static com.forgerock.opendj.cli.Utils.getAdministratorDN; 022import static com.forgerock.opendj.cli.Utils.getThrowableMsg; 023import static org.opends.messages.ToolMessages.*; 024 025import java.io.File; 026import java.io.FileInputStream; 027import java.io.FileNotFoundException; 028import java.io.FileOutputStream; 029import java.net.InetAddress; 030import java.net.URI; 031import java.net.URISyntaxException; 032import java.net.UnknownHostException; 033import java.security.KeyStore; 034import java.security.KeyStoreException; 035import java.security.cert.X509Certificate; 036import java.util.Enumeration; 037import java.util.LinkedHashMap; 038import java.util.Map; 039 040import javax.net.ssl.KeyManager; 041 042import com.forgerock.opendj.cli.Argument; 043import com.forgerock.opendj.cli.FileBasedArgument; 044import org.forgerock.i18n.LocalizableMessage; 045import org.forgerock.i18n.slf4j.LocalizedLogger; 046import org.opends.admin.ads.util.ApplicationKeyManager; 047import org.opends.admin.ads.util.ApplicationTrustManager; 048import org.opends.server.admin.client.cli.SecureConnectionCliArgs; 049import org.opends.server.tools.LDAPConnectionOptions; 050import org.opends.server.tools.SSLConnectionException; 051import org.opends.server.tools.SSLConnectionFactory; 052import org.opends.server.util.CollectionUtils; 053import org.opends.server.util.SelectableCertificateKeyManager; 054 055import com.forgerock.opendj.cli.ArgumentException; 056import com.forgerock.opendj.cli.ClientException; 057import com.forgerock.opendj.cli.CommandBuilder; 058import com.forgerock.opendj.cli.ConsoleApplication; 059import com.forgerock.opendj.cli.Menu; 060import com.forgerock.opendj.cli.MenuBuilder; 061import com.forgerock.opendj.cli.MenuResult; 062import com.forgerock.opendj.cli.ValidationCallback; 063 064/** 065 * Supports interacting with a user through the command line to prompt for 066 * information necessary to create an LDAP connection. 067 * 068 * Actually the LDAPConnectionConsoleInteraction is used by UninstallCliHelper, StatusCli, 069 * LDAPManagementContextFactory and ReplicationCliMain. 070 */ 071public class LDAPConnectionConsoleInteraction 072{ 073 074 private static final Protocol DEFAULT_PROMPT_PROTOCOL = Protocol.SSL; 075 private static final TrustMethod DEFAULT_PROMPT_TRUST_METHOD = TrustMethod.DISPLAY_CERTIFICATE; 076 private static final TrustOption DEFAULT_PROMPT_TRUST_OPTION = TrustOption.SESSION; 077 078 private static final boolean ALLOW_EMPTY_PATH = true; 079 private static final boolean FILE_MUST_EXISTS = true; 080 private boolean allowAnonymousIfNonInteractive; 081 082 /** 083 * Information from the latest console interaction. 084 * TODO: should it extend MonoServerReplicationUserData or a subclass? 085 */ 086 private static class State 087 { 088 private boolean useSSL; 089 private boolean useStartTLS; 090 private String hostName; 091 private String bindDN; 092 private String providedBindDN; 093 private String adminUID; 094 private String providedAdminUID; 095 private String bindPassword; 096 /** The timeout to be used to connect. */ 097 private int connectTimeout; 098 /** Indicate if we need to display the heading. */ 099 private boolean isHeadingDisplayed; 100 101 private ApplicationTrustManager trustManager; 102 /** Indicate if the trust store in in memory. */ 103 private boolean trustStoreInMemory; 104 /** Indicate if the all certificates are accepted. */ 105 private boolean trustAll; 106 /** Indicate that the trust manager was created with the parameters provided. */ 107 private boolean trustManagerInitialized; 108 /** The trust store to use for the SSL or STARTTLS connection. */ 109 private KeyStore truststore; 110 private String truststorePath; 111 private String truststorePassword; 112 113 private KeyManager keyManager; 114 private String keyStorePath; 115 private String keystorePassword; 116 private String certifNickname; 117 118 private State(SecureConnectionCliArgs secureArgs) 119 { 120 setSsl(secureArgs); 121 trustAll = secureArgs.getTrustAllArg().isPresent(); 122 } 123 124 protected LocalizableMessage getPrompt() 125 { 126 if (providedAdminUID != null) 127 { 128 return INFO_LDAPAUTH_PASSWORD_PROMPT.get(providedAdminUID); 129 } 130 else if (providedBindDN != null) 131 { 132 return INFO_LDAPAUTH_PASSWORD_PROMPT.get(providedBindDN); 133 } 134 else if (bindDN != null) 135 { 136 return INFO_LDAPAUTH_PASSWORD_PROMPT.get(bindDN); 137 } 138 139 return INFO_LDAPAUTH_PASSWORD_PROMPT.get(adminUID); 140 } 141 142 protected String getAdminOrBindDN() 143 { 144 if (providedBindDN != null) 145 { 146 return providedBindDN; 147 } 148 else if (providedAdminUID != null) 149 { 150 return getAdministratorDN(providedAdminUID); 151 } 152 else if (bindDN != null) 153 { 154 return bindDN; 155 } 156 else if (adminUID != null) 157 { 158 return getAdministratorDN(adminUID); 159 } 160 161 return null; 162 } 163 164 private void setSsl(final SecureConnectionCliArgs secureArgs) 165 { 166 this.useSSL = secureArgs.alwaysSSL() || secureArgs.getUseSSLArg().isPresent(); 167 this.useStartTLS = secureArgs.getUseStartTLSArg().isPresent(); 168 } 169 } 170 171 /** The console application. */ 172 private ConsoleApplication app; 173 174 private State state; 175 176 /** The SecureConnectionCliArgsList object. */ 177 private final SecureConnectionCliArgs secureArgsList; 178 179 /** The command builder that we can return with the connection information. */ 180 private CommandBuilder commandBuilder; 181 182 /** A copy of the secureArgList for convenience. */ 183 private SecureConnectionCliArgs copySecureArgsList; 184 185 /** 186 * Boolean that tells if we must propose LDAP if it is available even if the 187 * user provided certificate parameters. 188 */ 189 private boolean displayLdapIfSecureParameters; 190 191 private int portNumber; 192 193 private LocalizableMessage heading = INFO_LDAP_CONN_HEADING_CONNECTION_PARAMETERS.get(); 194 195 /** Boolean that tells if we ask for bind DN or admin UID in the same prompt. */ 196 private boolean useAdminOrBindDn; 197 198 /** Enumeration description protocols for interactive CLI choices. */ 199 private enum Protocol 200 { 201 LDAP(INFO_LDAP_CONN_PROMPT_SECURITY_LDAP.get()), 202 SSL(INFO_LDAP_CONN_PROMPT_SECURITY_USE_SSL.get()), 203 START_TLS(INFO_LDAP_CONN_PROMPT_SECURITY_USE_START_TLS.get()); 204 205 private final LocalizableMessage message; 206 207 Protocol(final LocalizableMessage message) 208 { 209 this.message = message; 210 } 211 212 private int getChoice() 213 { 214 return ordinal() + 1; 215 } 216 } 217 218 /** Enumeration description protocols for interactive CLI choices. */ 219 private enum TrustMethod 220 { 221 TRUSTALL(INFO_LDAP_CONN_PROMPT_SECURITY_USE_TRUST_ALL.get()), 222 TRUSTSTORE(INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE.get()), 223 DISPLAY_CERTIFICATE(INFO_LDAP_CONN_PROMPT_SECURITY_MANUAL_CHECK.get()); 224 225 private LocalizableMessage message; 226 227 TrustMethod(final LocalizableMessage message) 228 { 229 this.message = message; 230 } 231 232 private int getChoice() 233 { 234 return ordinal() + 1; 235 } 236 237 private static TrustMethod getTrustMethodForIndex(final int value) 238 { 239 for (final TrustMethod trustMethod : TrustMethod.values()) 240 { 241 if (trustMethod.getChoice() == value) 242 { 243 return trustMethod; 244 } 245 } 246 return null; 247 } 248 } 249 250 /** Enumeration description server certificate trust option. */ 251 private enum TrustOption 252 { 253 UNTRUSTED(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_NO.get()), 254 SESSION(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_SESSION.get()), 255 PERMAMENT(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_ALWAYS.get()), 256 CERTIFICATE_DETAILS(INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_DETAILS.get()); 257 258 private LocalizableMessage message; 259 260 TrustOption(final LocalizableMessage message) 261 { 262 this.message = message; 263 } 264 265 private int getChoice() 266 { 267 return ordinal() + 1; 268 } 269 270 private static TrustOption getTrustOptionForIndex(final int value) 271 { 272 for (final TrustOption trustOption : TrustOption.values()) 273 { 274 if (trustOption.getChoice() == value) 275 { 276 return trustOption; 277 } 278 } 279 return null; 280 } 281 } 282 283 /** 284 * Constructs a new console interaction. 285 * 286 * @param app 287 * console application 288 * @param secureArgs 289 * existing set of arguments that have already been parsed and 290 * contain some potential command line specified LDAP arguments 291 */ 292 public LDAPConnectionConsoleInteraction(ConsoleApplication app, SecureConnectionCliArgs secureArgs) 293 { 294 this(app, secureArgs, false); 295 } 296 297 /** 298 * Constructs a new console interaction. 299 * 300 * @param app 301 * console application 302 * @param secureArgs 303 * existing set of arguments that have already been parsed and 304 * contain some potential command line specified LDAP arguments 305 * @param allowAnonymousIfNonInteractive 306 * If this console interaction should allow anonymous user in non interactive mode. 307 * If console application is interactive, the user will always be prompted for credentials. 308 */ 309 public LDAPConnectionConsoleInteraction( 310 ConsoleApplication app, SecureConnectionCliArgs secureArgs, final boolean allowAnonymousIfNonInteractive) 311 { 312 this.app = app; 313 this.secureArgsList = secureArgs; 314 this.commandBuilder = new CommandBuilder(); 315 this.allowAnonymousIfNonInteractive = allowAnonymousIfNonInteractive; 316 state = new State(secureArgs); 317 copySecureArgsList = new SecureConnectionCliArgs(secureArgs.alwaysSSL()); 318 try 319 { 320 copySecureArgsList.createGlobalArguments(); 321 } 322 catch (Throwable t) 323 { 324 // This is a bug: we should always be able to create the global arguments 325 // no need to localize this one. 326 throw new RuntimeException("Unexpected error: " + t, t); 327 } 328 } 329 330 /** 331 * Interact with the user though the console to get information necessary to 332 * establish an LDAP connection. 333 * 334 * @throws ArgumentException 335 * if there is a problem with the arguments 336 */ 337 public void run() throws ArgumentException 338 { 339 run(true); 340 } 341 342 /** 343 * Interact with the user though the console to get information necessary to 344 * establish an LDAP connection. 345 * 346 * @param canUseStartTLS 347 * whether we can propose to connect using Start TLS or not. 348 * @throws ArgumentException 349 * if there is a problem with the arguments 350 */ 351 public void run(boolean canUseStartTLS) throws ArgumentException 352 { 353 resetBeforeRun(); 354 resolveHostName(); 355 resolveConnectionType(canUseStartTLS); 356 resolvePortNumber(); 357 resolveTrustAndKeyManagers(); 358 resolveCredentialLogin(); 359 resolveCredentialPassword(); 360 resolveConnectTimeout(); 361 } 362 363 private void resetBeforeRun() throws ArgumentException 364 { 365 commandBuilder.clearArguments(); 366 copySecureArgsList.createGlobalArguments(); 367 state.providedAdminUID = null; 368 state.providedBindDN = null; 369 } 370 371 private void resolveHostName() throws ArgumentException 372 { 373 state.hostName = secureArgsList.getHostNameArg().getValue(); 374 promptForHostNameIfRequired(); 375 addArgToCommandBuilder(copySecureArgsList.getHostNameArg(), state.hostName); 376 } 377 378 private void resolveConnectionType(boolean canUseStartTLS) 379 { 380 state.setSsl(secureArgsList); 381 promptForConnectionTypeIfRequired(canUseStartTLS); 382 addConnectionTypeToCommandBuilder(); 383 } 384 385 private void resolvePortNumber() throws ArgumentException 386 { 387 portNumber = (state.useSSL && !secureArgsList.getPortArg().isPresent()) 388 ? secureArgsList.getPortFromConfig() 389 : secureArgsList.getPortArg().getIntValue(); 390 promptForPortNumberIfRequired(); 391 addArgToCommandBuilder(copySecureArgsList.getPortArg(), String.valueOf(portNumber)); 392 } 393 394 private void resolveTrustAndKeyManagers() throws ArgumentException { 395 if ((state.useSSL || state.useStartTLS) && state.trustManager == null) 396 { 397 initializeTrustAndKeyManagers(); 398 } 399 } 400 401 private void resolveCredentialLogin() throws ArgumentException 402 { 403 setAdminUidAndBindDnFromArgs(); 404 if (useKeyManager()) 405 { 406 return; 407 } 408 promptForCredentialLoginIfRequired(secureArgsList.getBindDnArg().getValue(), 409 secureArgsList.getAdminUidArg().getValue()); 410 final boolean onlyBindDnProvided = state.providedAdminUID != null || state.providedBindDN == null; 411 if ((useAdminOrBindDn && onlyBindDnProvided) 412 || (!useAdminOrBindDn && isAdminUidArgVisible())) 413 { 414 addArgToCommandBuilder(copySecureArgsList.getAdminUidArg(), getAdministratorUID()); 415 } 416 else 417 { 418 addArgToCommandBuilder(copySecureArgsList.getBindDnArg(), getBindDN()); 419 } 420 } 421 422 private void setAdminUidAndBindDnFromArgs() 423 { 424 final Argument adminUid = secureArgsList.getAdminUidArg(); 425 final Argument bindDn = secureArgsList.getBindDnArg(); 426 427 state.providedAdminUID = (isAdminUidArgVisible() && adminUid.isPresent()) ? adminUid.getValue() : null; 428 state.providedBindDN = ((useAdminOrBindDn || !isAdminUidArgVisible()) && bindDn.isPresent()) ? bindDn.getValue() 429 : null; 430 state.adminUID = !useKeyManager() ? adminUid.getValue() : null; 431 state.bindDN = !useKeyManager() ? bindDn.getValue() : null; 432 } 433 434 private void resolveCredentialPassword() throws ArgumentException 435 { 436 if (secureArgsList.getBindPasswordArg().isPresent()) 437 { 438 state.bindPassword = secureArgsList.getBindPasswordArg().getValue(); 439 } 440 441 if (useKeyManager()) 442 { 443 return; 444 } 445 446 setBindPasswordFileFromArgs(); 447 final boolean addedPasswordFileArgument = secureArgsList.getBindPasswordFileArg().isPresent(); 448 if (!addedPasswordFileArgument && (state.bindPassword == null || "-".equals(state.bindPassword))) 449 { 450 promptForBindPasswordIfRequired(); 451 } 452 453 final Argument bindPassword = copySecureArgsList.getBindPasswordArg(); 454 bindPassword.clearValues(); 455 bindPassword.addValue(state.bindPassword); 456 if (!addedPasswordFileArgument) 457 { 458 commandBuilder.addObfuscatedArgument(bindPassword); 459 } 460 } 461 462 private void setBindPasswordFileFromArgs() throws ArgumentException 463 { 464 final FileBasedArgument bindPasswordFile = secureArgsList.getBindPasswordFileArg(); 465 if (bindPasswordFile.isPresent()) 466 { 467 // Read from file if it exists. 468 state.bindPassword = bindPasswordFile.getValue(); 469 if (state.bindPassword == null) 470 { 471 throw new ArgumentException( 472 ERR_ERROR_NO_ADMIN_PASSWORD.get(isAdminUidArgVisible() ? state.adminUID : state.bindDN)); 473 } 474 addArgToCommandBuilder(copySecureArgsList.getBindPasswordFileArg(), bindPasswordFile.getNameToValueMap()); 475 } 476 } 477 478 private void resolveConnectTimeout() throws ArgumentException 479 { 480 state.connectTimeout = secureArgsList.getConnectTimeoutArg().getIntValue(); 481 } 482 483 private void promptForHostNameIfRequired() throws ArgumentException 484 { 485 if (!app.isInteractive() || secureArgsList.getHostNameArg().isPresent()) 486 { 487 return; 488 } 489 checkHeadingDisplayed(); 490 ValidationCallback<String> callback = new ValidationCallback<String>() 491 { 492 @Override 493 public String validate(ConsoleApplication app, String rawInput) throws ClientException 494 { 495 final String input = rawInput.trim(); 496 if (input.length() == 0) 497 { 498 return state.hostName; 499 } 500 501 try 502 { 503 // Ensure that the prompted host is known 504 InetAddress.getByName(input); 505 return input; 506 } 507 catch (UnknownHostException e) 508 { 509 // Try again... 510 app.println(); 511 app.println(ERR_LDAP_CONN_BAD_HOST_NAME.get(input)); 512 app.println(); 513 return null; 514 } 515 } 516 }; 517 518 try 519 { 520 app.println(); 521 state.hostName = app.readValidatedInput(INFO_LDAP_CONN_PROMPT_HOST_NAME.get(state.hostName), callback); 522 } 523 catch (ClientException e) 524 { 525 throw cannotReadConnectionParameters(e); 526 } 527 } 528 529 private void promptForConnectionTypeIfRequired(final boolean canUseStartTLS) 530 { 531 final boolean valuesSetByProperty = secureArgsList.getUseSSLArg().isValueSetByProperty() 532 && secureArgsList.getUseStartTLSArg().isValueSetByProperty(); 533 if (!app.isInteractive() || state.useSSL || state.useStartTLS || valuesSetByProperty) 534 { 535 return; 536 } 537 checkHeadingDisplayed(); 538 final MenuBuilder<Integer> builder = new MenuBuilder<>(app); 539 builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_USE_SECURE_CTX.get()); 540 541 for (Protocol p : Protocol.values()) 542 { 543 if ((!displayLdapIfSecureParameters && Protocol.LDAP.equals(p)) 544 || (!canUseStartTLS && Protocol.START_TLS.equals(p))) 545 { 546 continue; 547 } 548 549 final MenuResult<Integer> menuResult = MenuResult.success(p.getChoice()); 550 final int i = builder.addNumberedOption(p.message, menuResult); 551 if (DEFAULT_PROMPT_PROTOCOL.equals(p)) 552 { 553 builder.setDefault(INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE.get(i), menuResult); 554 } 555 } 556 557 Menu<Integer> menu = builder.toMenu(); 558 try 559 { 560 final MenuResult<Integer> result = menu.run(); 561 throwIfMenuResultNotSucceeded(result); 562 final int userChoice = result.getValue(); 563 if (Protocol.SSL.getChoice() == userChoice) 564 { 565 state.useSSL = true; 566 } 567 else if (Protocol.START_TLS.getChoice() == userChoice) 568 { 569 state.useStartTLS = true; 570 } 571 } 572 catch (ClientException e) 573 { 574 throw new RuntimeException(e); 575 } 576 } 577 578 private void promptForPortNumberIfRequired() throws ArgumentException 579 { 580 if (!app.isInteractive() || secureArgsList.getPortArg().isPresent()) 581 { 582 return; 583 } 584 checkHeadingDisplayed(); 585 try 586 { 587 app.println(); 588 final LocalizableMessage askPortNumberMsg = secureArgsList.alwaysSSL() ? 589 INFO_ADMIN_CONN_PROMPT_PORT_NUMBER.get(portNumber) : 590 INFO_LDAP_CONN_PROMPT_PORT_NUMBER.get(portNumber); 591 portNumber = app.readValidatedInput(askPortNumberMsg, portValidationCallback(portNumber)); 592 } 593 catch (ClientException e) 594 { 595 throw cannotReadConnectionParameters(e); 596 } 597 } 598 599 private void promptForCredentialLoginIfRequired(final String defaultBindDN, final String defaultAdminUID) 600 throws ArgumentException 601 { 602 if (!app.isInteractive() || state.providedAdminUID != null || state.providedBindDN != null) 603 { 604 return; 605 } 606 checkHeadingDisplayed(); 607 ValidationCallback<String> callback = new ValidationCallback<String>() 608 { 609 @Override public String validate(ConsoleApplication app, String rawInput) throws ClientException 610 { 611 final String input = rawInput.trim(); 612 if (input.isEmpty()) 613 { 614 return isAdminUidArgVisible() ? defaultAdminUID : defaultBindDN; 615 } 616 617 return input; 618 } 619 }; 620 621 try 622 { 623 app.println(); 624 if (useAdminOrBindDn) 625 { 626 String def = state.adminUID != null ? state.adminUID : state.bindDN; 627 String v = app.readValidatedInput(INFO_LDAP_CONN_GLOBAL_ADMINISTRATOR_OR_BINDDN_PROMPT.get(def), callback); 628 if (isDN(v)) 629 { 630 state.bindDN = v; 631 state.providedBindDN = v; 632 state.adminUID = null; 633 state.providedAdminUID = null; 634 } 635 else 636 { 637 state.bindDN = null; 638 state.providedBindDN = null; 639 state.adminUID = v; 640 state.providedAdminUID = v; 641 } 642 } 643 else if (isAdminUidArgVisible()) 644 { 645 state.adminUID = app.readValidatedInput(INFO_LDAP_CONN_PROMPT_ADMINISTRATOR_UID.get(state.adminUID), callback); 646 state.providedAdminUID = state.adminUID; 647 } 648 else 649 { 650 state.bindDN = app.readValidatedInput(INFO_LDAP_CONN_PROMPT_BIND_DN.get(state.bindDN), callback); 651 state.providedBindDN = state.bindDN; 652 } 653 } 654 catch (ClientException e) 655 { 656 throw cannotReadConnectionParameters(e); 657 } 658 } 659 660 private void promptForBindPasswordIfRequired() throws ArgumentException 661 { 662 if (!app.isInteractive()) 663 { 664 if (allowAnonymousIfNonInteractive) 665 { 666 return; 667 } 668 throw new ArgumentException(ERR_ERROR_BIND_PASSWORD_NONINTERACTIVE.get()); 669 } 670 checkHeadingDisplayed(); 671 try 672 { 673 state.bindPassword = readPassword(state.getPrompt()); 674 } 675 catch (Exception e) 676 { 677 throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause()); 678 } 679 } 680 681 /** 682 * Get the trust manager. 683 * 684 * @return The trust manager based on CLI args on interactive prompt. 685 * @throws ArgumentException 686 * If an error occurs when getting args values. 687 */ 688 private ApplicationTrustManager getTrustManagerInternal() throws ArgumentException 689 { 690 // Remove these arguments since this method might be called several times. 691 commandBuilder.removeArguments(copySecureArgsList.getTrustAllArg(), 692 copySecureArgsList.getTrustStorePathArg(), 693 copySecureArgsList.getTrustStorePasswordArg(), 694 copySecureArgsList.getTrustStorePasswordFileArg()); 695 696 final TrustMethod trustMethod = resolveTrustMethod(); 697 if (TrustMethod.TRUSTALL == trustMethod) 698 { 699 return null; 700 } 701 702 final boolean promptForTrustStore = TrustMethod.TRUSTSTORE == trustMethod; 703 resolveTrustStorePath(promptForTrustStore); 704 setTrustStorePassword(); 705 setTrustStorePasswordFromFile(); 706 if ("-".equals(state.truststorePassword)) 707 { 708 // Read the password from the stdin. 709 promptForTrustStorePasswordIfRequired(); 710 } 711 712 return resolveTrustStore(); 713 } 714 715 /** As the most common case is to have no password for trust store, we do not ask it in the interactive mode.*/ 716 private void setTrustStorePassword() 717 { 718 if (secureArgsList.getTrustStorePasswordArg().isPresent()) 719 { 720 state.truststorePassword = secureArgsList.getTrustStorePasswordArg().getValue(); 721 } 722 } 723 724 private void setTrustStorePasswordFromFile() 725 { 726 if (secureArgsList.getTrustStorePasswordFileArg().isPresent()) 727 { 728 state.truststorePassword = secureArgsList.getTrustStorePasswordFileArg().getValue(); 729 } 730 } 731 732 /** Return the trust method chosen by user or {@code null} if the information is not available. */ 733 private TrustMethod resolveTrustMethod() 734 { 735 state.trustAll = secureArgsList.getTrustAllArg().isPresent(); 736 // Check if some trust manager info are set 737 boolean needPromptForTrustMethod = !state.trustAll 738 && !secureArgsList.getTrustStorePathArg().isPresent() 739 && !secureArgsList.getTrustStorePasswordArg().isPresent() 740 && !secureArgsList.getTrustStorePasswordFileArg().isPresent(); 741 742 TrustMethod trustMethod = state.trustAll ? TrustMethod.TRUSTALL : null; 743 // Try to use the local instance trust store, to avoid certificate 744 // validation when both the CLI and the server are in the same instance. 745 if (needPromptForTrustMethod && !useLocalTrustStoreIfPossible()) 746 { 747 trustMethod = promptForTrustMethodIfRequired(); 748 } 749 750 if (trustMethod != TrustMethod.TRUSTSTORE) 751 { 752 // There is no direct equivalent for the display certificate option, 753 // so propose trust all option as command-line argument. 754 commandBuilder.addArgument(copySecureArgsList.getTrustAllArg()); 755 } 756 757 return trustMethod; 758 } 759 760 private void resolveTrustStorePath(final boolean promptForTrustStore) throws ArgumentException 761 { 762 state.truststorePath = secureArgsList.getTrustStorePathArg().getValue(); 763 if (promptForTrustStore) 764 { 765 promptForTrustStorePathIfRequired(); 766 } 767 addArgToCommandBuilder(copySecureArgsList.getTrustStorePathArg(), state.truststorePath); 768 } 769 770 private ApplicationTrustManager resolveTrustStore() throws ArgumentException 771 { 772 try 773 { 774 state.truststore = KeyStore.getInstance(KeyStore.getDefaultType()); 775 if (state.truststorePath != null) 776 { 777 try (FileInputStream fos = new FileInputStream(state.truststorePath)) 778 { 779 state.truststore.load(fos, state.truststorePassword != null ? state.truststorePassword.toCharArray() : null); 780 } 781 } 782 else 783 { 784 state.truststore.load(null, null); 785 } 786 787 if (secureArgsList.getTrustStorePasswordFileArg().isPresent() && state.truststorePath != null) 788 { 789 addArgToCommandBuilder(copySecureArgsList.getTrustStorePasswordFileArg(), 790 secureArgsList.getTrustStorePasswordFileArg().getNameToValueMap()); 791 } 792 else if (state.truststorePassword != null && state.truststorePath != null) 793 { 794 addObfuscatedArgToCommandBuilder(copySecureArgsList.getTrustStorePasswordArg(), state.truststorePassword); 795 } 796 797 return new ApplicationTrustManager(state.truststore); 798 } 799 catch (Exception e) 800 { 801 throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause()); 802 } 803 } 804 805 private TrustMethod promptForTrustMethodIfRequired() 806 { 807 if (!app.isInteractive()) 808 { 809 return null; 810 } 811 812 checkHeadingDisplayed(); 813 app.println(); 814 MenuBuilder<Integer> builder = new MenuBuilder<>(app); 815 builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_METHOD.get()); 816 817 for (TrustMethod t : TrustMethod.values()) 818 { 819 int i = builder.addNumberedOption(t.message, MenuResult.success(t.getChoice())); 820 if (DEFAULT_PROMPT_TRUST_METHOD.equals(t)) 821 { 822 builder.setDefault( 823 INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE.get(i), MenuResult.success(t.getChoice())); 824 } 825 } 826 827 Menu<Integer> menu = builder.toMenu(); 828 state.trustStoreInMemory = false; 829 try 830 { 831 final MenuResult<Integer> result = menu.run(); 832 throwIfMenuResultNotSucceeded(result); 833 final int userChoice = result.getValue(); 834 if (TrustMethod.TRUSTALL.getChoice() == userChoice) 835 { 836 state.trustAll = true; 837 } 838 else if (TrustMethod.DISPLAY_CERTIFICATE.getChoice() == userChoice) 839 { 840 state.trustStoreInMemory = true; 841 } 842 return TrustMethod.getTrustMethodForIndex(userChoice); 843 } 844 catch (ClientException e) 845 { 846 throw new RuntimeException(e); 847 } 848 } 849 850 private void promptForTrustStorePathIfRequired() throws ArgumentException 851 { 852 if (!app.isInteractive() || secureArgsList.getTrustStorePathArg().isPresent()) 853 { 854 return; 855 } 856 857 checkHeadingDisplayed(); 858 try 859 { 860 app.println(); 861 state.truststorePath = app.readValidatedInput( 862 INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE_PATH.get(), 863 filePathValidationCallback(!ALLOW_EMPTY_PATH, FILE_MUST_EXISTS)); 864 } 865 catch (ClientException e) 866 { 867 throw cannotReadConnectionParameters(e); 868 } 869 } 870 871 private void promptForTrustStorePasswordIfRequired() throws ArgumentException 872 { 873 if (!app.isInteractive()) 874 { 875 return; 876 } 877 878 checkHeadingDisplayed(); 879 try 880 { 881 state.truststorePassword = readPassword( 882 INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE_PASSWORD.get(state.truststorePath)); 883 } 884 catch (Exception e) 885 { 886 throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause()); 887 } 888 } 889 890 /** 891 * Get the key manager. 892 * 893 * @return The key manager based on CLI args on interactive prompt. 894 * @throws ArgumentException 895 * If an error occurs when getting args values. 896 */ 897 private KeyManager getKeyManagerInternal() throws ArgumentException 898 { 899 // Remove these arguments since this method might be called several times. 900 commandBuilder.removeArguments(copySecureArgsList.getCertNicknameArg(), 901 copySecureArgsList.getKeyStorePathArg(), 902 copySecureArgsList.getKeyStorePasswordArg(), 903 copySecureArgsList.getKeyStorePasswordFileArg()); 904 905 if (!secureArgsList.getKeyStorePathArg().isPresent() 906 && !secureArgsList.getKeyStorePasswordArg().isPresent() 907 && !secureArgsList.getKeyStorePasswordFileArg().isPresent() 908 && !secureArgsList.getCertNicknameArg().isPresent()) 909 { 910 // If no one of these parameters above are set, we assume that we do not need client side authentication. 911 // Client side authentication is not the common use case so interactive mode doesn't add an extra question. 912 return null; 913 } 914 915 resolveKeyStorePath(); 916 resolveKeyStorePassword(); 917 918 final KeyStore keystore = createKeyStore(); 919 resolveCertificateNickname(keystore); 920 921 final ApplicationKeyManager keyManager = new ApplicationKeyManager(keystore, state.keystorePassword.toCharArray()); 922 addKeyStorePasswordArgToCommandBuilder(); 923 if (state.certifNickname != null) 924 { 925 addArgToCommandBuilder(copySecureArgsList.getCertNicknameArg(), state.certifNickname); 926 return SelectableCertificateKeyManager.wrap( 927 new KeyManager[] { keyManager }, CollectionUtils.newTreeSet(state.certifNickname))[0]; 928 } 929 930 return keyManager; 931 } 932 933 private void resolveKeyStorePath() throws ArgumentException 934 { 935 state.keyStorePath = secureArgsList.getKeyStorePathArg().getValue(); 936 promptForKeyStorePathIfRequired(); 937 938 if (state.keyStorePath == null) 939 { 940 throw new ArgumentException(ERR_ERROR_INCOMPATIBLE_PROPERTY_MOD.get("null keystorePath")); 941 } 942 addArgToCommandBuilder(copySecureArgsList.getKeyStorePathArg(), state.keyStorePath); 943 } 944 945 private void resolveKeyStorePassword() throws ArgumentException 946 { 947 state.keystorePassword = secureArgsList.getKeyStorePasswordArg().getValue(); 948 949 if (secureArgsList.getKeyStorePasswordFileArg().isPresent()) 950 { 951 state.keystorePassword = secureArgsList.getKeyStorePasswordFileArg().getValue(); 952 if (state.keystorePassword == null) 953 { 954 throw new ArgumentException(ERR_INSTALLDS_NO_KEYSTORE_PASSWORD.get( 955 secureArgsList.getKeyStorePathArg().getLongIdentifier(), 956 secureArgsList.getKeyStorePasswordFileArg().getLongIdentifier())); 957 } 958 } 959 else if (state.keystorePassword == null || "-".equals(state.keystorePassword)) 960 { 961 promptForKeyStorePasswordIfRequired(); 962 } 963 } 964 965 private KeyStore createKeyStore() throws ArgumentException 966 { 967 try (FileInputStream fos = new FileInputStream(state.keyStorePath)) 968 { 969 final KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); 970 keystore.load(fos, state.keystorePassword.toCharArray()); 971 return keystore; 972 } 973 catch (Exception e) 974 { 975 throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause()); 976 } 977 } 978 979 private void resolveCertificateNickname(final KeyStore keystore) throws ArgumentException 980 { 981 state.certifNickname = secureArgsList.getCertNicknameArg().getValue(); 982 try 983 { 984 promptForCertificateNicknameIfRequired(keystore, keystore.aliases()); 985 } 986 catch (final KeyStoreException e) 987 { 988 throw new ArgumentException(ERR_RESOLVE_KEYSTORE_ALIASES.get(e.getMessage()), e); 989 } 990 } 991 992 private void promptForKeyStorePathIfRequired() throws ArgumentException 993 { 994 if (!app.isInteractive() || secureArgsList.getKeyStorePathArg().isPresent()) 995 { 996 return; 997 } 998 checkHeadingDisplayed(); 999 try 1000 { 1001 app.println(); 1002 state.keyStorePath = app.readValidatedInput(INFO_LDAP_CONN_PROMPT_SECURITY_KEYSTORE_PATH.get(), 1003 filePathValidationCallback(ALLOW_EMPTY_PATH, FILE_MUST_EXISTS)); 1004 } 1005 catch (ClientException e) 1006 { 1007 throw cannotReadConnectionParameters(e); 1008 } 1009 } 1010 1011 private void promptForKeyStorePasswordIfRequired() throws ArgumentException 1012 { 1013 if (!app.isInteractive()) 1014 { 1015 throw new ArgumentException(ERR_ERROR_BIND_PASSWORD_NONINTERACTIVE.get()); 1016 } 1017 checkHeadingDisplayed(); 1018 try 1019 { 1020 state.keystorePassword = readPassword(INFO_LDAP_CONN_PROMPT_SECURITY_KEYSTORE_PASSWORD.get(state.keyStorePath)); 1021 } 1022 catch (Exception e) 1023 { 1024 throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause()); 1025 } 1026 } 1027 1028 private void promptForCertificateNicknameIfRequired(KeyStore keystore, Enumeration<String> aliasesEnum) 1029 throws ArgumentException 1030 { 1031 if (!app.isInteractive() || secureArgsList.getCertNicknameArg().isPresent() || !aliasesEnum.hasMoreElements()) 1032 { 1033 return; 1034 } 1035 state.certifNickname = null; 1036 checkHeadingDisplayed(); 1037 try 1038 { 1039 MenuBuilder<String> builder = new MenuBuilder<>(app); 1040 builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_ALIASES.get()); 1041 int certificateNumber = 0; 1042 while (aliasesEnum.hasMoreElements()) 1043 { 1044 final String alias = aliasesEnum.nextElement(); 1045 if (keystore.isKeyEntry(alias)) 1046 { 1047 certificateNumber++; 1048 X509Certificate certif = (X509Certificate) keystore.getCertificate(alias); 1049 builder.addNumberedOption(INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_ALIAS.get( 1050 alias, certif.getSubjectDN().getName()), 1051 MenuResult.success(alias)); 1052 } 1053 } 1054 1055 if (certificateNumber > 1) 1056 { 1057 app.println(); 1058 Menu<String> menu = builder.toMenu(); 1059 final MenuResult<String> result = menu.run(); 1060 throwIfMenuResultNotSucceeded(result); 1061 state.certifNickname = result.getValue(); 1062 } 1063 } 1064 catch (KeyStoreException e) 1065 { 1066 throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause()); 1067 } 1068 catch (ClientException e) 1069 { 1070 throw cannotReadConnectionParameters(e); 1071 } 1072 } 1073 1074 private void addKeyStorePasswordArgToCommandBuilder() 1075 { 1076 if (secureArgsList.getKeyStorePasswordFileArg().isPresent()) 1077 { 1078 addArgToCommandBuilder(copySecureArgsList.getKeyStorePasswordFileArg(), 1079 secureArgsList.getKeyStorePasswordFileArg().getNameToValueMap()); 1080 } 1081 else if (state.keystorePassword != null) 1082 { 1083 addObfuscatedArgToCommandBuilder(copySecureArgsList.getKeyStorePasswordArg(), state.keystorePassword); 1084 } 1085 } 1086 1087 private void addConnectionTypeToCommandBuilder() 1088 { 1089 if (state.useSSL) 1090 { 1091 commandBuilder.addArgument(copySecureArgsList.getUseSSLArg()); 1092 } 1093 else if (state.useStartTLS) 1094 { 1095 commandBuilder.addArgument(copySecureArgsList.getUseStartTLSArg()); 1096 } 1097 } 1098 1099 private void addArgToCommandBuilder(final Argument arg, final String value) 1100 { 1101 addArgToCommandBuilder(arg, value, false); 1102 } 1103 1104 private void addObfuscatedArgToCommandBuilder(final Argument arg, final String value) 1105 { 1106 addArgToCommandBuilder(arg, value, true); 1107 } 1108 1109 private void addArgToCommandBuilder(final Argument arg, final String value, final boolean obfuscated) 1110 { 1111 if (value != null) 1112 { 1113 arg.clearValues(); 1114 arg.addValue(value); 1115 commandBuilder.addArgument(arg); 1116 } 1117 } 1118 1119 private void addArgToCommandBuilder(final FileBasedArgument arg, final Map<String, String> nameToValueMap) 1120 { 1121 arg.clearValues(); 1122 arg.getNameToValueMap().putAll(nameToValueMap); 1123 commandBuilder.addArgument(arg); 1124 } 1125 1126 private ArgumentException cannotReadConnectionParameters(ClientException e) 1127 { 1128 return new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause()); 1129 } 1130 1131 private String readPassword(LocalizableMessage prompt) throws ClientException 1132 { 1133 app.println(); 1134 final char[] pwd = app.readPassword(prompt); 1135 if (pwd != null) 1136 { 1137 return String.valueOf(pwd); 1138 } 1139 return null; 1140 } 1141 1142 private ValidationCallback<String> filePathValidationCallback( 1143 final boolean allowEmptyPath, final boolean checkExistenceAndReadability) 1144 { 1145 return new ValidationCallback<String>() 1146 { 1147 @Override 1148 public String validate(final ConsoleApplication app, final String filePathUserInput) throws ClientException 1149 { 1150 final String filePath = filePathUserInput.trim(); 1151 final File f = new File(filePath); 1152 1153 if ((!allowEmptyPath && filePath.isEmpty()) 1154 || f.isDirectory() 1155 || (checkExistenceAndReadability && !(f.exists() && f.canRead()))) 1156 { 1157 app.println(); 1158 app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH.get()); 1159 app.println(); 1160 return null; 1161 } 1162 1163 return filePath; 1164 } 1165 }; 1166 } 1167 1168 /** Returns {@code true} if client script uses the adminUID argument. */ 1169 private boolean isAdminUidArgVisible() 1170 { 1171 return !secureArgsList.getAdminUidArg().isHidden(); 1172 } 1173 1174 private boolean useKeyManager() 1175 { 1176 return state.keyManager != null; 1177 } 1178 1179 /** 1180 * Indicates whether a connection should use SSL based on this interaction. 1181 * 1182 * @return boolean where true means use SSL 1183 */ 1184 public boolean useSSL() 1185 { 1186 return state.useSSL; 1187 } 1188 1189 /** 1190 * Indicates whether a connection should use StartTLS based on this interaction. 1191 * 1192 * @return boolean where true means use StartTLS 1193 */ 1194 public boolean useStartTLS() 1195 { 1196 return state.useStartTLS; 1197 } 1198 1199 /** 1200 * Gets the host name that should be used for connections based on this 1201 * interaction. 1202 * 1203 * @return host name for connections 1204 */ 1205 public String getHostName() 1206 { 1207 return state.hostName; 1208 } 1209 1210 /** 1211 * Gets the port number name that should be used for connections based on this 1212 * interaction. 1213 * 1214 * @return port number for connections 1215 */ 1216 public int getPortNumber() 1217 { 1218 return portNumber; 1219 } 1220 1221 /** 1222 * Sets the port number name that should be used for connections based on this 1223 * interaction. 1224 * 1225 * @param portNumber 1226 * port number for connections 1227 */ 1228 public void setPortNumber(int portNumber) 1229 { 1230 this.portNumber = portNumber; 1231 } 1232 1233 /** 1234 * Gets the bind DN name that should be used for connections based on this 1235 * interaction. 1236 * 1237 * @return bind DN for connections 1238 */ 1239 public String getBindDN() 1240 { 1241 if (useAdminOrBindDn) 1242 { 1243 return state.getAdminOrBindDN(); 1244 } 1245 else if (isAdminUidArgVisible()) 1246 { 1247 return getAdministratorDN(state.adminUID); 1248 } 1249 else 1250 { 1251 return state.bindDN; 1252 } 1253 } 1254 1255 /** 1256 * Gets the administrator UID name that should be used for connections based 1257 * on this interaction. 1258 * 1259 * @return administrator UID for connections 1260 */ 1261 public String getAdministratorUID() 1262 { 1263 return state.adminUID; 1264 } 1265 1266 /** 1267 * Gets the bind password that should be used for connections based on this 1268 * interaction. 1269 * 1270 * @return bind password for connections 1271 */ 1272 public String getBindPassword() 1273 { 1274 return state.bindPassword; 1275 } 1276 1277 /** 1278 * Gets the trust manager that should be used for connections based on this 1279 * interaction. 1280 * 1281 * @return trust manager for connections 1282 */ 1283 public ApplicationTrustManager getTrustManager() 1284 { 1285 return state.trustManager; 1286 } 1287 1288 /** 1289 * Gets the key store that should be used for connections based on this 1290 * interaction. 1291 * 1292 * @return key store for connections 1293 */ 1294 public KeyStore getKeyStore() 1295 { 1296 return state.truststore; 1297 } 1298 1299 /** 1300 * Gets the key manager that should be used for connections based on this 1301 * interaction. 1302 * 1303 * @return key manager for connections 1304 */ 1305 public KeyManager getKeyManager() 1306 { 1307 return state.keyManager; 1308 } 1309 1310 /** 1311 * Indicate if the trust store is in memory. 1312 * 1313 * @return true if the trust store is in memory. 1314 */ 1315 public boolean isTrustStoreInMemory() 1316 { 1317 return state.trustStoreInMemory; 1318 } 1319 1320 /** 1321 * Indicate if all certificates must be accepted. 1322 * 1323 * @return true all certificates must be accepted. 1324 */ 1325 public boolean isTrustAll() 1326 { 1327 return state.trustAll; 1328 } 1329 1330 /** 1331 * Returns the timeout to be used to connect with the server. 1332 * 1333 * @return the timeout to be used to connect with the server. 1334 */ 1335 public int getConnectTimeout() 1336 { 1337 return state.connectTimeout; 1338 } 1339 1340 /** 1341 * Indicate if the certificate chain can be trusted. 1342 * 1343 * @param chain 1344 * The certificate chain to validate 1345 * @param authType 1346 * the authentication type. 1347 * @param host 1348 * the host we tried to connect and that presented the certificate. 1349 * @return true if the server certificate is trusted. 1350 */ 1351 public boolean checkServerCertificate(final X509Certificate[] chain, final String authType, final String host) 1352 { 1353 if (state.trustManager == null) 1354 { 1355 try 1356 { 1357 initializeTrustAndKeyManagers(); 1358 } 1359 catch (ArgumentException ae) 1360 { 1361 // Should not append because this.run() should has been called at this stage. 1362 throw new RuntimeException(ae); 1363 } 1364 } 1365 printCertificateChain(chain); 1366 MenuBuilder<Integer> builder = new MenuBuilder<>(app); 1367 builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION.get()); 1368 1369 for (TrustOption t : TrustOption.values()) 1370 { 1371 final MenuResult<Integer> result = MenuResult.success(t.getChoice()); 1372 int i = builder.addNumberedOption(t.message, result); 1373 if (DEFAULT_PROMPT_TRUST_OPTION.equals(t)) 1374 { 1375 builder.setDefault(INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE.get(i), result); 1376 } 1377 } 1378 1379 app.println(); 1380 app.println(); 1381 1382 final Menu<Integer> menu = builder.toMenu(); 1383 try 1384 { 1385 boolean promptAgain; 1386 int userChoice; 1387 do 1388 { 1389 promptAgain = false; 1390 final MenuResult<Integer> result = menu.run(); 1391 throwIfMenuResultNotSucceeded(result); 1392 userChoice = result.getValue(); 1393 if (TrustOption.CERTIFICATE_DETAILS.getChoice() == userChoice) 1394 { 1395 promptAgain = true; 1396 printCertificateDetails(chain); 1397 } 1398 } 1399 while (promptAgain); 1400 1401 return trustCertificate(TrustOption.getTrustOptionForIndex(userChoice), chain, authType, host); 1402 } 1403 catch (ClientException e) 1404 { 1405 throw new RuntimeException(e); 1406 } 1407 } 1408 1409 private void printCertificateChain(X509Certificate[] chain) 1410 { 1411 app.println(); 1412 app.println(INFO_LDAP_CONN_PROMPT_SECURITY_SERVER_CERTIFICATE.get()); 1413 app.println(); 1414 boolean printSeparatorLines = false; 1415 for (final X509Certificate cert : chain) 1416 { 1417 if (!printSeparatorLines) 1418 { 1419 app.println(); 1420 app.println(); 1421 printSeparatorLines = true; 1422 } 1423 1424 // Certificate DN 1425 app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_USER_DN.get(cert.getSubjectDN())); 1426 // certificate validity 1427 app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_VALIDITY.get( 1428 cert.getNotBefore(), cert.getNotAfter())); 1429 // certificate Issuer 1430 app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_ISSUER.get(cert.getIssuerDN())); 1431 } 1432 } 1433 1434 private void printCertificateDetails(X509Certificate[] chain) 1435 { 1436 for (X509Certificate cert : chain) 1437 { 1438 app.println(); 1439 app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE.get(cert)); 1440 } 1441 } 1442 1443 private boolean trustCertificate(final TrustOption trustOption, final X509Certificate[] chain, 1444 final String authType, final String host) throws ClientException 1445 { 1446 try 1447 { 1448 switch (trustOption) 1449 { 1450 case SESSION: 1451 updateTrustManager(chain, authType, host); 1452 return true; 1453 1454 case PERMAMENT: 1455 updateTrustManager(chain, authType, host); 1456 try 1457 { 1458 trustCertificatePermanently(chain); 1459 } 1460 catch (Exception e) 1461 { 1462 app.println(ERR_TRUSTING_CERTIFICATE_PERMANENTLY.get(e.getMessage())); 1463 } 1464 return true; 1465 1466 case UNTRUSTED: 1467 default: 1468 return false; 1469 } 1470 } 1471 catch (KeyStoreException e) 1472 { 1473 app.println(ERR_TRUSTING_CERTIFICATE.get(e.getMessage())); 1474 return false; 1475 } 1476 } 1477 1478 private void updateTrustManager(X509Certificate[] chain, String authType, String host) throws KeyStoreException 1479 { 1480 // User choice if to add the certificate to the trust store for the current session or permanently. 1481 for (final X509Certificate cert : chain) 1482 { 1483 state.truststore.setCertificateEntry(cert.getSubjectDN().getName(), cert); 1484 } 1485 1486 // Update the trust manager 1487 if (state.trustManager == null) 1488 { 1489 state.trustManager = new ApplicationTrustManager(state.truststore); 1490 } 1491 1492 if (authType != null && host != null) 1493 { 1494 // Update the trust manager with the new certificate 1495 state.trustManager.acceptCertificate(chain, authType, host); 1496 } 1497 else 1498 { 1499 // Do a full reset of the contents of the keystore. 1500 state.trustManager = new ApplicationTrustManager(state.truststore); 1501 } 1502 } 1503 1504 private void trustCertificatePermanently(final X509Certificate[] chain) throws Exception 1505 { 1506 app.println(); 1507 final String trustStorePath = app.readValidatedInput( 1508 INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE_PATH.get(), 1509 filePathValidationCallback(!ALLOW_EMPTY_PATH, !FILE_MUST_EXISTS)); 1510 1511 // Read the password from the stdin. 1512 final String trustStorePasswordStr = readPassword( 1513 INFO_LDAP_CONN_PROMPT_SECURITY_KEYSTORE_PASSWORD.get(trustStorePath)); 1514 final KeyStore keyStore = KeyStore.getInstance("JKS"); 1515 final char[] trustStorePassword = trustStorePasswordStr.toCharArray(); 1516 loadKeyStoreFromFile(keyStore, trustStorePath, trustStorePassword); 1517 1518 for (final X509Certificate cert : chain) 1519 { 1520 keyStore.setCertificateEntry(cert.getSubjectDN().getName(), cert); 1521 } 1522 1523 try (final FileOutputStream trustStoreOutputFile = new FileOutputStream(trustStorePath)) 1524 { 1525 keyStore.store(trustStoreOutputFile, trustStorePassword); 1526 } 1527 } 1528 1529 private void loadKeyStoreFromFile( 1530 final KeyStore keyStore, final String trustStorePath, final char[] trustStorePassword) throws Exception 1531 { 1532 try (FileInputStream inputStream = new FileInputStream(trustStorePath)) 1533 { 1534 keyStore.load(inputStream, trustStorePassword); 1535 } 1536 catch (FileNotFoundException ignored) 1537 { 1538 // create empty keystore 1539 keyStore.load(null, trustStorePassword); 1540 } 1541 } 1542 1543 /** 1544 * Populates a set of LDAP options with state from this interaction. 1545 * 1546 * @param options 1547 * existing set of options; may be null in which case this method 1548 * will create a new set of <code>LDAPConnectionOptions</code> to be 1549 * returned 1550 * @return used during this interaction 1551 * @throws SSLConnectionException 1552 * if this interaction has specified the use of SSL and there is a 1553 * problem initializing the SSL connection factory 1554 */ 1555 public LDAPConnectionOptions populateLDAPOptions(LDAPConnectionOptions options) throws SSLConnectionException 1556 { 1557 if (options == null) 1558 { 1559 options = new LDAPConnectionOptions(); 1560 } 1561 options.setUseSSL(state.useSSL); 1562 options.setStartTLS(state.useStartTLS); 1563 if (state.useSSL) 1564 { 1565 SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory(); 1566 sslConnectionFactory.init(getTrustManager() == null, state.keyStorePath, 1567 state.keystorePassword, state.certifNickname, state.truststorePath, state.truststorePassword); 1568 options.setSSLConnectionFactory(sslConnectionFactory); 1569 } 1570 1571 return options; 1572 } 1573 1574 /** 1575 * Prompts the user to accept the certificate. 1576 * 1577 * @param errorRaised 1578 * the error raised because the certificate was not trusted. 1579 * @param usedTrustManager 1580 * the trustManager used when trying to establish the connection. 1581 * @param usedUrl 1582 * the LDAP URL used to connect to the server. 1583 * @param logger 1584 * the Logger used to log messages. 1585 * @return {@code true} if the user accepted the certificate and 1586 * {@code false} otherwise. 1587 */ 1588 public boolean promptForCertificateConfirmation(Throwable errorRaised, 1589 ApplicationTrustManager usedTrustManager, String usedUrl, LocalizedLogger logger) 1590 { 1591 final ApplicationTrustManager.Cause cause = usedTrustManager != null ? usedTrustManager.getLastRefusedCause() 1592 : null; 1593 logger.debug(INFO_CERTIFICATE_EXCEPTION_CAUSE.get(cause)); 1594 1595 if (cause == null) 1596 { 1597 app.println(getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), errorRaised)); 1598 return false; 1599 } 1600 1601 String host; 1602 int port; 1603 try 1604 { 1605 URI uri = new URI(usedUrl); 1606 host = uri.getHost(); 1607 port = uri.getPort(); 1608 } 1609 catch (URISyntaxException e) 1610 { 1611 logger.warn(ERROR_CERTIFICATE_PARSING_URL.get(usedUrl, e)); 1612 host = INFO_NOT_AVAILABLE_LABEL.get().toString(); 1613 port = -1; 1614 } 1615 1616 final String authType = usedTrustManager.getLastRefusedAuthType(); 1617 if (authType == null) 1618 { 1619 logger.warn(ERROR_CERTIFICATE_NULL_AUTH_TYPE.get()); 1620 } 1621 else 1622 { 1623 app.println(ApplicationTrustManager.Cause.NOT_TRUSTED.equals(authType) 1624 ? INFO_CERTIFICATE_NOT_TRUSTED_TEXT_CLI.get(host, port) 1625 : INFO_CERTIFICATE_NAME_MISMATCH_TEXT_CLI.get(host, port, host, host, port)); 1626 } 1627 1628 final X509Certificate[] chain = usedTrustManager.getLastRefusedChain(); 1629 if (chain == null) 1630 { 1631 logger.warn(ERROR_CERTIFICATE_NULL_CHAIN.get()); 1632 return false; 1633 } 1634 if (host == null) 1635 { 1636 logger.warn(ERROR_CERTIFICATE_NULL_HOST_NAME.get()); 1637 } 1638 1639 return checkServerCertificate(chain, authType, host); 1640 } 1641 1642 /** 1643 * Sets the heading that is displayed in interactive mode. 1644 * 1645 * @param heading 1646 * the heading that is displayed in interactive mode. 1647 */ 1648 public void setHeadingMessage(LocalizableMessage heading) 1649 { 1650 this.heading = heading; 1651 } 1652 1653 /** 1654 * Returns the command builder with the equivalent arguments on the 1655 * non-interactive mode. 1656 * 1657 * @return the command builder with the equivalent arguments on the 1658 * non-interactive mode. 1659 */ 1660 public CommandBuilder getCommandBuilder() 1661 { 1662 return commandBuilder; 1663 } 1664 1665 /** 1666 * Displays the heading if it was not displayed before. 1667 */ 1668 private void checkHeadingDisplayed() 1669 { 1670 if (!state.isHeadingDisplayed) 1671 { 1672 app.println(); 1673 app.println(); 1674 app.println(heading); 1675 state.isHeadingDisplayed = true; 1676 } 1677 } 1678 1679 /** 1680 * Tells whether we can ask during interaction for both the DN and the admin 1681 * UID or not. 1682 * Default value is {@code false}. 1683 * 1684 * @param useAdminOrBindDn 1685 * whether we can ask for both the DN and the admin UID during 1686 * interaction or not. 1687 */ 1688 public void setUseAdminOrBindDn(boolean useAdminOrBindDn) 1689 { 1690 this.useAdminOrBindDn = useAdminOrBindDn; 1691 } 1692 1693 /** 1694 * Tells whether we propose LDAP as protocol even if the user provided 1695 * security parameters. This is required in command-lines that access multiple 1696 * servers (like dsreplication). 1697 * 1698 * @param displayLdapIfSecureParameters 1699 * whether propose LDAP as protocol even if the user provided 1700 * security parameters or not. 1701 */ 1702 public void setDisplayLdapIfSecureParameters(boolean displayLdapIfSecureParameters) 1703 { 1704 this.displayLdapIfSecureParameters = displayLdapIfSecureParameters; 1705 } 1706 1707 /** 1708 * Resets the heading displayed flag, so that next time we call run the 1709 * heading is displayed. 1710 */ 1711 public void resetHeadingDisplayed() 1712 { 1713 state.isHeadingDisplayed = false; 1714 } 1715 1716 /** 1717 * Forces the initialization of the trust manager with the arguments provided 1718 * by the user. 1719 * 1720 * @throws ArgumentException 1721 * if there is an error with the arguments provided by the user. 1722 */ 1723 public void initializeTrustManagerIfRequired() throws ArgumentException 1724 { 1725 if (!state.trustManagerInitialized) 1726 { 1727 initializeTrustAndKeyManagers(); 1728 } 1729 } 1730 1731 /** 1732 * Initializes the global arguments in the parser with the provided values. 1733 * This is useful when we want to call LDAPConnectionConsoleInteraction.run() 1734 * with some default values. 1735 * 1736 * @param hostName 1737 * the host name. 1738 * @param port 1739 * the port to connect to the server. 1740 * @param adminUid 1741 * the administrator UID. 1742 * @param bindDn 1743 * the bind DN to bind to the server. 1744 * @param bindPwd 1745 * the password to bind. 1746 * @param pwdFile 1747 * the Map containing the file and the password to bind. 1748 */ 1749 public void initializeGlobalArguments(String hostName, int port, 1750 String adminUid, String bindDn, String bindPwd, 1751 LinkedHashMap<String, String> pwdFile) 1752 { 1753 resetConnectionArguments(); 1754 if (hostName != null) 1755 { 1756 secureArgsList.getHostNameArg().addValue(hostName); 1757 secureArgsList.getHostNameArg().setPresent(true); 1758 } 1759 // resetConnectionArguments does not clear the values for the port 1760 secureArgsList.getPortArg().clearValues(); 1761 if (port != -1) 1762 { 1763 secureArgsList.getPortArg().addValue(String.valueOf(port)); 1764 secureArgsList.getPortArg().setPresent(true); 1765 } 1766 else 1767 { 1768 // This is done to be able to call IntegerArgument.getIntValue() 1769 secureArgsList.getPortArg().addValue(secureArgsList.getPortArg().getDefaultValue()); 1770 } 1771 secureArgsList.getUseSSLArg().setPresent(state.useSSL); 1772 secureArgsList.getUseStartTLSArg().setPresent(state.useStartTLS); 1773 if (adminUid != null) 1774 { 1775 secureArgsList.getAdminUidArg().addValue(adminUid); 1776 secureArgsList.getAdminUidArg().setPresent(true); 1777 } 1778 if (bindDn != null) 1779 { 1780 secureArgsList.getBindDnArg().addValue(bindDn); 1781 secureArgsList.getBindDnArg().setPresent(true); 1782 } 1783 if (pwdFile != null) 1784 { 1785 secureArgsList.getBindPasswordFileArg().getNameToValueMap().putAll(pwdFile); 1786 for (String value : pwdFile.keySet()) 1787 { 1788 secureArgsList.getBindPasswordFileArg().addValue(value); 1789 } 1790 secureArgsList.getBindPasswordFileArg().setPresent(true); 1791 } 1792 else if (bindPwd != null) 1793 { 1794 secureArgsList.getBindPasswordArg().addValue(bindPwd); 1795 secureArgsList.getBindPasswordArg().setPresent(true); 1796 } 1797 state = new State(secureArgsList); 1798 } 1799 1800 /** 1801 * Resets the connection parameters for the LDAPConsoleInteraction object. The 1802 * reset does not apply to the certificate parameters. This is called in order 1803 * the LDAPConnectionConsoleInteraction object to ask for all this connection 1804 * parameters next time we call LDAPConnectionConsoleInteraction.run(). 1805 */ 1806 public void resetConnectionArguments() 1807 { 1808 secureArgsList.getHostNameArg().clearValues(); 1809 secureArgsList.getHostNameArg().setPresent(false); 1810 secureArgsList.getPortArg().clearValues(); 1811 secureArgsList.getPortArg().setPresent(false); 1812 // This is done to be able to call IntegerArgument.getIntValue() 1813 secureArgsList.getPortArg().addValue(secureArgsList.getPortArg().getDefaultValue()); 1814 secureArgsList.getBindDnArg().clearValues(); 1815 secureArgsList.getBindDnArg().setPresent(false); 1816 secureArgsList.getBindPasswordArg().clearValues(); 1817 secureArgsList.getBindPasswordArg().setPresent(false); 1818 secureArgsList.getBindPasswordFileArg().clearValues(); 1819 secureArgsList.getBindPasswordFileArg().getNameToValueMap().clear(); 1820 secureArgsList.getBindPasswordFileArg().setPresent(false); 1821 state.bindPassword = null; 1822 secureArgsList.getAdminUidArg().clearValues(); 1823 secureArgsList.getAdminUidArg().setPresent(false); 1824 } 1825 1826 private void initializeTrustAndKeyManagers() throws ArgumentException 1827 { 1828 // Get trust store info 1829 state.trustManager = getTrustManagerInternal(); 1830 // Check if we need client side authentication 1831 state.keyManager = getKeyManagerInternal(); 1832 state.trustManagerInitialized = true; 1833 } 1834 1835 /** 1836 * Returns the explicitly provided Admin UID from the user (interactively or 1837 * through the argument). 1838 * 1839 * @return the explicitly provided Admin UID from the user (interactively or 1840 * through the argument). 1841 */ 1842 public String getProvidedAdminUID() 1843 { 1844 return state.providedAdminUID; 1845 } 1846 1847 /** 1848 * Returns the explicitly provided bind DN from the user (interactively or 1849 * through the argument). 1850 * 1851 * @return the explicitly provided bind DN from the user (interactively or 1852 * through the argument). 1853 */ 1854 public String getProvidedBindDN() 1855 { 1856 return state.providedBindDN; 1857 } 1858 1859 /** 1860 * Add the TrustStore of the administration connector of the local instance. 1861 * 1862 * @return true if the local trust store has been added. 1863 */ 1864 private boolean useLocalTrustStoreIfPossible() 1865 { 1866 try 1867 { 1868 if (InetAddress.getLocalHost().getHostName().equals(state.hostName) 1869 && secureArgsList.getAdminPortFromConfig() == portNumber) 1870 { 1871 final String trustStoreFileAbsolute = secureArgsList.getTruststoreFileFromConfig(); 1872 if (trustStoreFileAbsolute != null) 1873 { 1874 secureArgsList.getTrustStorePathArg().addValue(trustStoreFileAbsolute); 1875 return true; 1876 } 1877 } 1878 } 1879 catch (Exception ex) 1880 { 1881 // do nothing 1882 } 1883 return false; 1884 } 1885 1886 private void throwIfMenuResultNotSucceeded(final MenuResult<?> result) 1887 { 1888 if (!result.isSuccess()) 1889 { 1890 throw new RuntimeException("Expected successful menu result, but got " + result); 1891 } 1892 } 1893}