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 or not a connection should use SSL based on this 1181 * interaction. 1182 * 1183 * @return boolean where true means use SSL 1184 */ 1185 public boolean useSSL() 1186 { 1187 return state.useSSL; 1188 } 1189 1190 /** 1191 * Indicates whether or not a connection should use StartTLS based on this 1192 * interaction. 1193 * 1194 * @return boolean where true means use StartTLS 1195 */ 1196 public boolean useStartTLS() 1197 { 1198 return state.useStartTLS; 1199 } 1200 1201 /** 1202 * Gets the host name that should be used for connections based on this 1203 * interaction. 1204 * 1205 * @return host name for connections 1206 */ 1207 public String getHostName() 1208 { 1209 return state.hostName; 1210 } 1211 1212 /** 1213 * Gets the port number name that should be used for connections based on this 1214 * interaction. 1215 * 1216 * @return port number for connections 1217 */ 1218 public int getPortNumber() 1219 { 1220 return portNumber; 1221 } 1222 1223 /** 1224 * Sets the port number name that should be used for connections based on this 1225 * interaction. 1226 * 1227 * @param portNumber 1228 * port number for connections 1229 */ 1230 public void setPortNumber(int portNumber) 1231 { 1232 this.portNumber = portNumber; 1233 } 1234 1235 /** 1236 * Gets the bind DN name that should be used for connections based on this 1237 * interaction. 1238 * 1239 * @return bind DN for connections 1240 */ 1241 public String getBindDN() 1242 { 1243 if (useAdminOrBindDn) 1244 { 1245 return state.getAdminOrBindDN(); 1246 } 1247 else 1248 { 1249 return state.bindDN; 1250 } 1251 } 1252 1253 /** 1254 * Gets the administrator UID name that should be used for connections based 1255 * on this interaction. 1256 * 1257 * @return administrator UID for connections 1258 */ 1259 public String getAdministratorUID() 1260 { 1261 return state.adminUID; 1262 } 1263 1264 /** 1265 * Gets the bind password that should be used for connections based on this 1266 * interaction. 1267 * 1268 * @return bind password for connections 1269 */ 1270 public String getBindPassword() 1271 { 1272 return state.bindPassword; 1273 } 1274 1275 /** 1276 * Gets the trust manager that should be used for connections based on this 1277 * interaction. 1278 * 1279 * @return trust manager for connections 1280 */ 1281 public ApplicationTrustManager getTrustManager() 1282 { 1283 return state.trustManager; 1284 } 1285 1286 /** 1287 * Gets the key store that should be used for connections based on this 1288 * interaction. 1289 * 1290 * @return key store for connections 1291 */ 1292 public KeyStore getKeyStore() 1293 { 1294 return state.truststore; 1295 } 1296 1297 /** 1298 * Gets the key manager that should be used for connections based on this 1299 * interaction. 1300 * 1301 * @return key manager for connections 1302 */ 1303 public KeyManager getKeyManager() 1304 { 1305 return state.keyManager; 1306 } 1307 1308 /** 1309 * Indicate if the trust store is in memory. 1310 * 1311 * @return true if the trust store is in memory. 1312 */ 1313 public boolean isTrustStoreInMemory() 1314 { 1315 return state.trustStoreInMemory; 1316 } 1317 1318 /** 1319 * Indicate if all certificates must be accepted. 1320 * 1321 * @return true all certificates must be accepted. 1322 */ 1323 public boolean isTrustAll() 1324 { 1325 return state.trustAll; 1326 } 1327 1328 /** 1329 * Returns the timeout to be used to connect with the server. 1330 * 1331 * @return the timeout to be used to connect with the server. 1332 */ 1333 public int getConnectTimeout() 1334 { 1335 return state.connectTimeout; 1336 } 1337 1338 /** 1339 * Indicate if the certificate chain can be trusted. 1340 * 1341 * @param chain 1342 * The certificate chain to validate 1343 * @param authType 1344 * the authentication type. 1345 * @param host 1346 * the host we tried to connect and that presented the certificate. 1347 * @return true if the server certificate is trusted. 1348 */ 1349 public boolean checkServerCertificate(final X509Certificate[] chain, final String authType, final String host) 1350 { 1351 if (state.trustManager == null) 1352 { 1353 try 1354 { 1355 initializeTrustAndKeyManagers(); 1356 } 1357 catch (ArgumentException ae) 1358 { 1359 // Should not append because this.run() should has been called at this stage. 1360 throw new RuntimeException(ae); 1361 } 1362 } 1363 printCertificateChain(chain); 1364 MenuBuilder<Integer> builder = new MenuBuilder<>(app); 1365 builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION.get()); 1366 1367 for (TrustOption t : TrustOption.values()) 1368 { 1369 final MenuResult<Integer> result = MenuResult.success(t.getChoice()); 1370 int i = builder.addNumberedOption(t.message, result); 1371 if (DEFAULT_PROMPT_TRUST_OPTION.equals(t)) 1372 { 1373 builder.setDefault(INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE.get(i), result); 1374 } 1375 } 1376 1377 app.println(); 1378 app.println(); 1379 1380 final Menu<Integer> menu = builder.toMenu(); 1381 try 1382 { 1383 boolean promptAgain; 1384 int userChoice; 1385 do 1386 { 1387 promptAgain = false; 1388 final MenuResult<Integer> result = menu.run(); 1389 throwIfMenuResultNotSucceeded(result); 1390 userChoice = result.getValue(); 1391 if (TrustOption.CERTIFICATE_DETAILS.getChoice() == userChoice) 1392 { 1393 promptAgain = true; 1394 printCertificateDetails(chain); 1395 } 1396 } 1397 while (promptAgain); 1398 1399 return trustCertificate(TrustOption.getTrustOptionForIndex(userChoice), chain, authType, host); 1400 } 1401 catch (ClientException e) 1402 { 1403 throw new RuntimeException(e); 1404 } 1405 } 1406 1407 private void printCertificateChain(X509Certificate[] chain) 1408 { 1409 app.println(); 1410 app.println(INFO_LDAP_CONN_PROMPT_SECURITY_SERVER_CERTIFICATE.get()); 1411 app.println(); 1412 boolean printSeparatorLines = false; 1413 for (final X509Certificate cert : chain) 1414 { 1415 if (!printSeparatorLines) 1416 { 1417 app.println(); 1418 app.println(); 1419 printSeparatorLines = true; 1420 } 1421 1422 // Certificate DN 1423 app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_USER_DN.get(cert.getSubjectDN())); 1424 // certificate validity 1425 app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_VALIDITY.get( 1426 cert.getNotBefore(), cert.getNotAfter())); 1427 // certificate Issuer 1428 app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_ISSUER.get(cert.getIssuerDN())); 1429 } 1430 } 1431 1432 private void printCertificateDetails(X509Certificate[] chain) 1433 { 1434 for (X509Certificate cert : chain) 1435 { 1436 app.println(); 1437 app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE.get(cert)); 1438 } 1439 } 1440 1441 private boolean trustCertificate(final TrustOption trustOption, final X509Certificate[] chain, 1442 final String authType, final String host) throws ClientException 1443 { 1444 try 1445 { 1446 switch (trustOption) 1447 { 1448 case SESSION: 1449 updateTrustManager(chain, authType, host); 1450 return true; 1451 1452 case PERMAMENT: 1453 updateTrustManager(chain, authType, host); 1454 try 1455 { 1456 trustCertificatePermanently(chain); 1457 } 1458 catch (Exception e) 1459 { 1460 app.println(ERR_TRUSTING_CERTIFICATE_PERMANENTLY.get(e.getMessage())); 1461 } 1462 return true; 1463 1464 case UNTRUSTED: 1465 default: 1466 return false; 1467 } 1468 } 1469 catch (KeyStoreException e) 1470 { 1471 app.println(ERR_TRUSTING_CERTIFICATE.get(e.getMessage())); 1472 return false; 1473 } 1474 } 1475 1476 private void updateTrustManager(X509Certificate[] chain, String authType, String host) throws KeyStoreException 1477 { 1478 // User choice if to add the certificate to the trust store for the current session or permanently. 1479 for (final X509Certificate cert : chain) 1480 { 1481 state.truststore.setCertificateEntry(cert.getSubjectDN().getName(), cert); 1482 } 1483 1484 // Update the trust manager 1485 if (state.trustManager == null) 1486 { 1487 state.trustManager = new ApplicationTrustManager(state.truststore); 1488 } 1489 1490 if (authType != null && host != null) 1491 { 1492 // Update the trust manager with the new certificate 1493 state.trustManager.acceptCertificate(chain, authType, host); 1494 } 1495 else 1496 { 1497 // Do a full reset of the contents of the keystore. 1498 state.trustManager = new ApplicationTrustManager(state.truststore); 1499 } 1500 } 1501 1502 private void trustCertificatePermanently(final X509Certificate[] chain) throws Exception 1503 { 1504 app.println(); 1505 final String trustStorePath = app.readValidatedInput( 1506 INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE_PATH.get(), 1507 filePathValidationCallback(!ALLOW_EMPTY_PATH, !FILE_MUST_EXISTS)); 1508 1509 // Read the password from the stdin. 1510 final String trustStorePasswordStr = readPassword( 1511 INFO_LDAP_CONN_PROMPT_SECURITY_KEYSTORE_PASSWORD.get(trustStorePath)); 1512 final KeyStore keyStore = KeyStore.getInstance("JKS"); 1513 final char[] trustStorePassword = trustStorePasswordStr.toCharArray(); 1514 loadKeyStoreFromFile(keyStore, trustStorePath, trustStorePassword); 1515 1516 for (final X509Certificate cert : chain) 1517 { 1518 keyStore.setCertificateEntry(cert.getSubjectDN().getName(), cert); 1519 } 1520 1521 try (final FileOutputStream trustStoreOutputFile = new FileOutputStream(trustStorePath)) 1522 { 1523 keyStore.store(trustStoreOutputFile, trustStorePassword); 1524 } 1525 } 1526 1527 private void loadKeyStoreFromFile( 1528 final KeyStore keyStore, final String trustStorePath, final char[] trustStorePassword) throws Exception 1529 { 1530 try (FileInputStream inputStream = new FileInputStream(trustStorePath)) 1531 { 1532 keyStore.load(inputStream, trustStorePassword); 1533 } 1534 catch (FileNotFoundException ignored) 1535 { 1536 // create empty keystore 1537 keyStore.load(null, trustStorePassword); 1538 } 1539 } 1540 1541 /** 1542 * Populates a set of LDAP options with state from this interaction. 1543 * 1544 * @param options 1545 * existing set of options; may be null in which case this method 1546 * will create a new set of <code>LDAPConnectionOptions</code> to be 1547 * returned 1548 * @return used during this interaction 1549 * @throws SSLConnectionException 1550 * if this interaction has specified the use of SSL and there is a 1551 * problem initializing the SSL connection factory 1552 */ 1553 public LDAPConnectionOptions populateLDAPOptions(LDAPConnectionOptions options) throws SSLConnectionException 1554 { 1555 if (options == null) 1556 { 1557 options = new LDAPConnectionOptions(); 1558 } 1559 options.setUseSSL(state.useSSL); 1560 options.setStartTLS(state.useStartTLS); 1561 if (state.useSSL) 1562 { 1563 SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory(); 1564 sslConnectionFactory.init(getTrustManager() == null, state.keyStorePath, 1565 state.keystorePassword, state.certifNickname, state.truststorePath, state.truststorePassword); 1566 options.setSSLConnectionFactory(sslConnectionFactory); 1567 } 1568 1569 return options; 1570 } 1571 1572 /** 1573 * Prompts the user to accept the certificate. 1574 * 1575 * @param errorRaised 1576 * the error raised because the certificate was not trusted. 1577 * @param usedTrustManager 1578 * the trustManager used when trying to establish the connection. 1579 * @param usedUrl 1580 * the LDAP URL used to connect to the server. 1581 * @param logger 1582 * the Logger used to log messages. 1583 * @return {@code true} if the user accepted the certificate and 1584 * {@code false} otherwise. 1585 */ 1586 public boolean promptForCertificateConfirmation(Throwable errorRaised, 1587 ApplicationTrustManager usedTrustManager, String usedUrl, LocalizedLogger logger) 1588 { 1589 final ApplicationTrustManager.Cause cause = usedTrustManager != null ? usedTrustManager.getLastRefusedCause() 1590 : null; 1591 logger.debug(INFO_CERTIFICATE_EXCEPTION_CAUSE.get(cause)); 1592 1593 if (cause == null) 1594 { 1595 app.println(getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), errorRaised)); 1596 return false; 1597 } 1598 1599 String host; 1600 int port; 1601 try 1602 { 1603 URI uri = new URI(usedUrl); 1604 host = uri.getHost(); 1605 port = uri.getPort(); 1606 } 1607 catch (URISyntaxException e) 1608 { 1609 logger.warn(ERROR_CERTIFICATE_PARSING_URL.get(usedUrl, e)); 1610 host = INFO_NOT_AVAILABLE_LABEL.get().toString(); 1611 port = -1; 1612 } 1613 1614 final String authType = usedTrustManager.getLastRefusedAuthType(); 1615 if (authType == null) 1616 { 1617 logger.warn(ERROR_CERTIFICATE_NULL_AUTH_TYPE.get()); 1618 } 1619 else 1620 { 1621 app.println(ApplicationTrustManager.Cause.NOT_TRUSTED.equals(authType) 1622 ? INFO_CERTIFICATE_NOT_TRUSTED_TEXT_CLI.get(host, port) 1623 : INFO_CERTIFICATE_NAME_MISMATCH_TEXT_CLI.get(host, port, host, host, port)); 1624 } 1625 1626 final X509Certificate[] chain = usedTrustManager.getLastRefusedChain(); 1627 if (chain == null) 1628 { 1629 logger.warn(ERROR_CERTIFICATE_NULL_CHAIN.get()); 1630 return false; 1631 } 1632 if (host == null) 1633 { 1634 logger.warn(ERROR_CERTIFICATE_NULL_HOST_NAME.get()); 1635 } 1636 1637 return checkServerCertificate(chain, authType, host); 1638 } 1639 1640 /** 1641 * Sets the heading that is displayed in interactive mode. 1642 * 1643 * @param heading 1644 * the heading that is displayed in interactive mode. 1645 */ 1646 public void setHeadingMessage(LocalizableMessage heading) 1647 { 1648 this.heading = heading; 1649 } 1650 1651 /** 1652 * Returns the command builder with the equivalent arguments on the 1653 * non-interactive mode. 1654 * 1655 * @return the command builder with the equivalent arguments on the 1656 * non-interactive mode. 1657 */ 1658 public CommandBuilder getCommandBuilder() 1659 { 1660 return commandBuilder; 1661 } 1662 1663 /** 1664 * Displays the heading if it was not displayed before. 1665 */ 1666 private void checkHeadingDisplayed() 1667 { 1668 if (!state.isHeadingDisplayed) 1669 { 1670 app.println(); 1671 app.println(); 1672 app.println(heading); 1673 state.isHeadingDisplayed = true; 1674 } 1675 } 1676 1677 /** 1678 * Tells whether we can ask during interaction for both the DN and the admin 1679 * UID or not. 1680 * Default value is {@code false}. 1681 * 1682 * @param useAdminOrBindDn 1683 * whether we can ask for both the DN and the admin UID during 1684 * interaction or not. 1685 */ 1686 public void setUseAdminOrBindDn(boolean useAdminOrBindDn) 1687 { 1688 this.useAdminOrBindDn = useAdminOrBindDn; 1689 } 1690 1691 /** 1692 * Tells whether we propose LDAP as protocol even if the user provided 1693 * security parameters. This is required in command-lines that access multiple 1694 * servers (like dsreplication). 1695 * 1696 * @param displayLdapIfSecureParameters 1697 * whether propose LDAP as protocol even if the user provided 1698 * security parameters or not. 1699 */ 1700 public void setDisplayLdapIfSecureParameters(boolean displayLdapIfSecureParameters) 1701 { 1702 this.displayLdapIfSecureParameters = displayLdapIfSecureParameters; 1703 } 1704 1705 /** 1706 * Resets the heading displayed flag, so that next time we call run the 1707 * heading is displayed. 1708 */ 1709 public void resetHeadingDisplayed() 1710 { 1711 state.isHeadingDisplayed = false; 1712 } 1713 1714 /** 1715 * Forces the initialization of the trust manager with the arguments provided 1716 * by the user. 1717 * 1718 * @throws ArgumentException 1719 * if there is an error with the arguments provided by the user. 1720 */ 1721 public void initializeTrustManagerIfRequired() throws ArgumentException 1722 { 1723 if (!state.trustManagerInitialized) 1724 { 1725 initializeTrustAndKeyManagers(); 1726 } 1727 } 1728 1729 /** 1730 * Initializes the global arguments in the parser with the provided values. 1731 * This is useful when we want to call LDAPConnectionConsoleInteraction.run() 1732 * with some default values. 1733 * 1734 * @param hostName 1735 * the host name. 1736 * @param port 1737 * the port to connect to the server. 1738 * @param adminUid 1739 * the administrator UID. 1740 * @param bindDn 1741 * the bind DN to bind to the server. 1742 * @param bindPwd 1743 * the password to bind. 1744 * @param pwdFile 1745 * the Map containing the file and the password to bind. 1746 */ 1747 public void initializeGlobalArguments(String hostName, int port, 1748 String adminUid, String bindDn, String bindPwd, 1749 LinkedHashMap<String, String> pwdFile) 1750 { 1751 resetConnectionArguments(); 1752 if (hostName != null) 1753 { 1754 secureArgsList.getHostNameArg().addValue(hostName); 1755 secureArgsList.getHostNameArg().setPresent(true); 1756 } 1757 // resetConnectionArguments does not clear the values for the port 1758 secureArgsList.getPortArg().clearValues(); 1759 if (port != -1) 1760 { 1761 secureArgsList.getPortArg().addValue(String.valueOf(port)); 1762 secureArgsList.getPortArg().setPresent(true); 1763 } 1764 else 1765 { 1766 // This is done to be able to call IntegerArgument.getIntValue() 1767 secureArgsList.getPortArg().addValue(secureArgsList.getPortArg().getDefaultValue()); 1768 } 1769 secureArgsList.getUseSSLArg().setPresent(state.useSSL); 1770 secureArgsList.getUseStartTLSArg().setPresent(state.useStartTLS); 1771 if (adminUid != null) 1772 { 1773 secureArgsList.getAdminUidArg().addValue(adminUid); 1774 secureArgsList.getAdminUidArg().setPresent(true); 1775 } 1776 if (bindDn != null) 1777 { 1778 secureArgsList.getBindDnArg().addValue(bindDn); 1779 secureArgsList.getBindDnArg().setPresent(true); 1780 } 1781 if (pwdFile != null) 1782 { 1783 secureArgsList.getBindPasswordFileArg().getNameToValueMap().putAll(pwdFile); 1784 for (String value : pwdFile.keySet()) 1785 { 1786 secureArgsList.getBindPasswordFileArg().addValue(value); 1787 } 1788 secureArgsList.getBindPasswordFileArg().setPresent(true); 1789 } 1790 else if (bindPwd != null) 1791 { 1792 secureArgsList.getBindPasswordArg().addValue(bindPwd); 1793 secureArgsList.getBindPasswordArg().setPresent(true); 1794 } 1795 state = new State(secureArgsList); 1796 } 1797 1798 /** 1799 * Resets the connection parameters for the LDAPConsoleInteraction object. The 1800 * reset does not apply to the certificate parameters. This is called in order 1801 * the LDAPConnectionConsoleInteraction object to ask for all this connection 1802 * parameters next time we call LDAPConnectionConsoleInteraction.run(). 1803 */ 1804 public void resetConnectionArguments() 1805 { 1806 secureArgsList.getHostNameArg().clearValues(); 1807 secureArgsList.getHostNameArg().setPresent(false); 1808 secureArgsList.getPortArg().clearValues(); 1809 secureArgsList.getPortArg().setPresent(false); 1810 // This is done to be able to call IntegerArgument.getIntValue() 1811 secureArgsList.getPortArg().addValue(secureArgsList.getPortArg().getDefaultValue()); 1812 secureArgsList.getBindDnArg().clearValues(); 1813 secureArgsList.getBindDnArg().setPresent(false); 1814 secureArgsList.getBindPasswordArg().clearValues(); 1815 secureArgsList.getBindPasswordArg().setPresent(false); 1816 secureArgsList.getBindPasswordFileArg().clearValues(); 1817 secureArgsList.getBindPasswordFileArg().getNameToValueMap().clear(); 1818 secureArgsList.getBindPasswordFileArg().setPresent(false); 1819 state.bindPassword = null; 1820 secureArgsList.getAdminUidArg().clearValues(); 1821 secureArgsList.getAdminUidArg().setPresent(false); 1822 } 1823 1824 private void initializeTrustAndKeyManagers() throws ArgumentException 1825 { 1826 // Get trust store info 1827 state.trustManager = getTrustManagerInternal(); 1828 // Check if we need client side authentication 1829 state.keyManager = getKeyManagerInternal(); 1830 state.trustManagerInitialized = true; 1831 } 1832 1833 /** 1834 * Returns the explicitly provided Admin UID from the user (interactively or 1835 * through the argument). 1836 * 1837 * @return the explicitly provided Admin UID from the user (interactively or 1838 * through the argument). 1839 */ 1840 public String getProvidedAdminUID() 1841 { 1842 return state.providedAdminUID; 1843 } 1844 1845 /** 1846 * Returns the explicitly provided bind DN from the user (interactively or 1847 * through the argument). 1848 * 1849 * @return the explicitly provided bind DN from the user (interactively or 1850 * through the argument). 1851 */ 1852 public String getProvidedBindDN() 1853 { 1854 return state.providedBindDN; 1855 } 1856 1857 /** 1858 * Add the TrustStore of the administration connector of the local instance. 1859 * 1860 * @return true if the local trust store has been added. 1861 */ 1862 private boolean useLocalTrustStoreIfPossible() 1863 { 1864 try 1865 { 1866 if (InetAddress.getLocalHost().getHostName().equals(state.hostName) 1867 && secureArgsList.getAdminPortFromConfig() == portNumber) 1868 { 1869 final String trustStoreFileAbsolute = secureArgsList.getTruststoreFileFromConfig(); 1870 if (trustStoreFileAbsolute != null) 1871 { 1872 secureArgsList.getTrustStorePathArg().addValue(trustStoreFileAbsolute); 1873 return true; 1874 } 1875 } 1876 } 1877 catch (Exception ex) 1878 { 1879 // do nothing 1880 } 1881 return false; 1882 } 1883 1884 private void throwIfMenuResultNotSucceeded(final MenuResult<?> result) 1885 { 1886 if (!result.isSuccess()) 1887 { 1888 throw new RuntimeException("Expected successful menu result, but got " + result); 1889 } 1890 } 1891}