/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2015 ForgeRock AS */ package org.opends.server.util.cli; import static com.forgerock.opendj.cli.Utils.isDN; import static com.forgerock.opendj.cli.Utils.getAdministratorDN; import static com.forgerock.opendj.cli.Utils.getThrowableMsg; import static com.forgerock.opendj.cli.CliMessages.*; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.LinkedHashMap; import javax.net.ssl.KeyManager; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.opends.admin.ads.util.ApplicationKeyManager; import org.opends.admin.ads.util.ApplicationTrustManager; import org.opends.server.admin.client.cli.SecureConnectionCliArgs; import org.opends.server.tools.LDAPConnectionOptions; import org.opends.server.tools.SSLConnectionException; import org.opends.server.tools.SSLConnectionFactory; import org.opends.server.util.SelectableCertificateKeyManager; import com.forgerock.opendj.cli.ArgumentException; import com.forgerock.opendj.cli.ClientException; import com.forgerock.opendj.cli.CommandBuilder; import com.forgerock.opendj.cli.ConsoleApplication; import com.forgerock.opendj.cli.Menu; import com.forgerock.opendj.cli.MenuBuilder; import com.forgerock.opendj.cli.MenuResult; import com.forgerock.opendj.cli.ValidationCallback; /** * Supports interacting with a user through the command line to prompt for * information necessary to create an LDAP connection. * * Actually the LDAPConnectionConsoleInteraction is used by UninstallCliHelper, StatusCli, * LDAPManagementContextFactory and ReplicationCliMain. */ public class LDAPConnectionConsoleInteraction { private boolean useSSL; private boolean useStartTLS; private String hostName; private int portNumber; private String bindDN; private String providedBindDN; private String adminUID; private String providedAdminUID; private String bindPassword; private KeyManager keyManager; private ApplicationTrustManager trustManager; /** Boolean that tells if we ask for bind DN or admin UID in the same prompt. */ private boolean useAdminOrBindDn; /** * Boolean that tells if we must propose LDAP if it is available even if the * user provided certificate parameters. */ private boolean displayLdapIfSecureParameters; /** The SecureConnectionCliArgsList object. */ private SecureConnectionCliArgs secureArgsList; /** Indicate if we need to display the heading. */ private boolean isHeadingDisplayed; /** the Console application. */ private ConsoleApplication app; /** Indicate if the trust store in in memory. */ private boolean trustStoreInMemory; /** Indicate if the all certificates are accepted. */ private boolean trustAll; /** Indicate that the trust manager was created with the parameters provided. */ private boolean trustManagerInitialized; /** The trust store to use for the SSL or STARTTLS connection. */ private KeyStore truststore; private String keystorePath; private String keystorePassword; private String certifNickname; private String truststorePath; private String truststorePassword; /** The timeout to be used to connect. */ private int connectTimeout; private LocalizableMessage heading = INFO_LDAP_CONN_HEADING_CONNECTION_PARAMETERS.get(); /** A copy of the secureArgList for convenience. */ private SecureConnectionCliArgs copySecureArgsList; /** The command builder that we can return with the connection information. */ private CommandBuilder commandBuilder; /** * Enumeration description protocols for interactive CLI choices. */ private enum Protocols { LDAP(1, INFO_LDAP_CONN_PROMPT_SECURITY_LDAP.get()), SSL(2, INFO_LDAP_CONN_PROMPT_SECURITY_USE_SSL.get()), START_TLS(3, INFO_LDAP_CONN_PROMPT_SECURITY_USE_START_TLS.get()); private Integer choice; private LocalizableMessage msg; /** * Private constructor. * * @param i * the menu return value. * @param msg * the message message. */ private Protocols(int i, LocalizableMessage msg) { choice = i; this.msg = msg; } /** * Returns the choice number. * * @return the attribute name. */ public Integer getChoice() { return choice; } /** * Return the menu message. * * @return the menu message. */ public LocalizableMessage getMenuMessage() { return msg; } } /** * Enumeration description protocols for interactive CLI choices. */ private enum TrustMethod { TRUSTALL(1, INFO_LDAP_CONN_PROMPT_SECURITY_USE_TRUST_ALL.get()), TRUSTSTORE(2, INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE.get()), DISPLAY_CERTIFICATE(3, INFO_LDAP_CONN_PROMPT_SECURITY_MANUAL_CHECK.get()); private Integer choice; private LocalizableMessage msg; /** * Private constructor. * * @param i * the menu return value. * @param msg * the message message. */ private TrustMethod(int i, LocalizableMessage msg) { choice = Integer.valueOf(i); this.msg = msg; } /** * Returns the choice number. * * @return the attribute name. */ public Integer getChoice() { return choice; } /** * Return the menu message. * * @return the menu message. */ public LocalizableMessage getMenuMessage() { return msg; } } /** * Enumeration description server certificate trust option. */ private enum TrustOption { UNTRUSTED(1, INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_NO.get()), SESSION(2, INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_SESSION.get()), PERMAMENT(3, INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_ALWAYS.get()), CERTIFICATE_DETAILS(4, INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_DETAILS .get()); private Integer choice; private LocalizableMessage msg; /** * Private constructor. * * @param i * the menu return value. * @param msg * the message message. */ private TrustOption(int i, LocalizableMessage msg) { choice = Integer.valueOf(i); this.msg = msg; } /** * Returns the choice number. * * @return the attribute name. */ public Integer getChoice() { return choice; } /** * Return the menu message. * * @return the menu message. */ public LocalizableMessage getMenuMessage() { return msg; } } /** * Constructs a parameterized instance. * * @param app * console application * @param secureArgs * existing set of arguments that have already been parsed and * contain some potential command line specified LDAP arguments */ public LDAPConnectionConsoleInteraction(ConsoleApplication app, SecureConnectionCliArgs secureArgs) { this.app = app; this.secureArgsList = secureArgs; this.commandBuilder = new CommandBuilder(null, null); this.useSSL = secureArgs.useSSL(); this.useStartTLS = secureArgs.useStartTLS(); this.trustAll = secureArgs.trustAllArg.isPresent(); copySecureArgsList = new SecureConnectionCliArgs(secureArgs.alwaysSSL()); try { copySecureArgsList.createGlobalArguments(); } catch (Throwable t) { // This is a bug: we should always be able to create the global arguments // no need to localize this one. throw new RuntimeException("Unexpected error: " + t, t); } } /** * Interact with the user though the console to get information necessary to * establish an LDAP connection. * * @throws ArgumentException * if there is a problem with the arguments */ public void run() throws ArgumentException { run(true); } /** * Interact with the user though the console to get information necessary to * establish an LDAP connection. * * @param canUseStartTLS * whether we can propose to connect using Start TLS or not. * @throws ArgumentException * if there is a problem with the arguments */ public void run(boolean canUseStartTLS) throws ArgumentException { // Reset everything commandBuilder.clearArguments(); copySecureArgsList.createGlobalArguments(); boolean secureConnection = true; // Get the LDAP host. hostName = secureArgsList.hostNameArg.getValue(); final String tmpHostName = hostName; if (app.isInteractive() && !secureArgsList.hostNameArg.isPresent()) { checkHeadingDisplayed(); ValidationCallback callback = new ValidationCallback() { @Override public String validate(ConsoleApplication app, String input) throws ClientException { String ninput = input.trim(); if (ninput.length() == 0) { return tmpHostName; } else { try { InetAddress.getByName(ninput); return ninput; } catch (UnknownHostException e) { // Try again... app.println(); app.println(ERR_LDAP_CONN_BAD_HOST_NAME.get(ninput)); app.println(); return null; } } } }; try { app.println(); hostName = app.readValidatedInput(INFO_LDAP_CONN_PROMPT_HOST_NAME .get(hostName), callback); } catch (ClientException e) { throw cannotReadConnectionParameters(e); } } copySecureArgsList.hostNameArg.clearValues(); copySecureArgsList.hostNameArg.addValue(hostName); commandBuilder.addArgument(copySecureArgsList.hostNameArg); // Connection type useSSL = secureArgsList.useSSL(); useStartTLS = secureArgsList.useStartTLS(); boolean connectionTypeIsSet = secureArgsList.alwaysSSL() || secureArgsList.useSSLArg.isPresent() || secureArgsList.useStartTLSArg.isPresent() || (secureArgsList.useSSLArg.isValueSetByProperty() && secureArgsList.useStartTLSArg .isValueSetByProperty()); if (app.isInteractive() && !connectionTypeIsSet) { checkHeadingDisplayed(); MenuBuilder builder = new MenuBuilder(app); builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_USE_SECURE_CTX.get()); Protocols defaultProtocol; if (secureConnection) { defaultProtocol = Protocols.SSL; } else { defaultProtocol = Protocols.LDAP; } for (Protocols p : Protocols.values()) { if (secureConnection && p.equals(Protocols.LDAP) && !displayLdapIfSecureParameters) { continue; } if (!canUseStartTLS && p.equals(Protocols.START_TLS)) { continue; } int i = builder.addNumberedOption(p.getMenuMessage(), MenuResult.success(p .getChoice())); if (p.equals(defaultProtocol)) { builder.setDefault( INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE.get(i), MenuResult.success(p.getChoice())); } } Menu menu = builder.toMenu(); try { MenuResult result = menu.run(); if (result.isSuccess()) { if (result.getValue().equals(Protocols.SSL.getChoice())) { useSSL = true; } else if (result.getValue().equals(Protocols.START_TLS.getChoice())) { useStartTLS = true; } } else { // Should never happen. throw new RuntimeException(); } } catch (ClientException e) { throw new RuntimeException(e); } } if (useSSL) { commandBuilder.addArgument(copySecureArgsList.useSSLArg); } else if (useStartTLS) { commandBuilder.addArgument(copySecureArgsList.useStartTLSArg); } // Get the LDAP port. if (!useSSL) { portNumber = secureArgsList.portArg.getIntValue(); } else { if (secureArgsList.portArg.isPresent()) { portNumber = secureArgsList.portArg.getIntValue(); } else { portNumber = secureArgsList.getPortFromConfig(); } } final int tmpPortNumber = portNumber; if (app.isInteractive() && !secureArgsList.portArg.isPresent()) { checkHeadingDisplayed(); ValidationCallback callback = new ValidationCallback() { @Override public Integer validate(ConsoleApplication app, String input) throws ClientException { String ninput = input.trim(); if (ninput.length() == 0) { return tmpPortNumber; } else { try { int i = Integer.parseInt(ninput); if (i < 1 || i > 65535) { throw new NumberFormatException(); } return i; } catch (NumberFormatException e) { // Try again... app.println(); app.println(ERR_LDAP_CONN_BAD_PORT_NUMBER.get(ninput)); app.println(); return null; } } } }; try { app.println(); LocalizableMessage askPortNumber = null; if (secureArgsList.alwaysSSL()) { askPortNumber = INFO_ADMIN_CONN_PROMPT_PORT_NUMBER.get(portNumber); } else { askPortNumber = INFO_LDAP_CONN_PROMPT_PORT_NUMBER.get(portNumber); } portNumber = app.readValidatedInput(askPortNumber, callback); } catch (ClientException e) { throw cannotReadConnectionParameters(e); } } copySecureArgsList.portArg.clearValues(); copySecureArgsList.portArg.addValue(String.valueOf(portNumber)); commandBuilder.addArgument(copySecureArgsList.portArg); // Handle certificate if ((useSSL || useStartTLS) && trustManager == null) { initializeTrustManager(); } // Get the LDAP bind credentials. bindDN = secureArgsList.bindDnArg.getValue(); adminUID = secureArgsList.adminUidArg.getValue(); final boolean useAdmin = secureArgsList.useAdminUID(); if (useAdmin && secureArgsList.adminUidArg.isPresent()) { providedAdminUID = adminUID; } else { providedAdminUID = null; } if ((!useAdmin || useAdminOrBindDn) && secureArgsList.bindDnArg.isPresent()) { providedBindDN = bindDN; } else { providedBindDN = null; } boolean argIsPresent = providedAdminUID != null || providedBindDN != null; final String tmpBindDN = bindDN; final String tmpAdminUID = adminUID; if (keyManager == null) { if (app.isInteractive() && !argIsPresent) { checkHeadingDisplayed(); ValidationCallback callback = new ValidationCallback() { @Override public String validate(ConsoleApplication app, String input) throws ClientException { String ninput = input.trim(); if (ninput.length() == 0) { if (useAdmin) { return tmpAdminUID; } else { return tmpBindDN; } } else { return ninput; } } }; try { app.println(); if (useAdminOrBindDn) { String def = adminUID != null ? adminUID : bindDN; String v = app.readValidatedInput( INFO_LDAP_CONN_GLOBAL_ADMINISTRATOR_OR_BINDDN_PROMPT .get(def), callback); if (isDN(v)) { bindDN = v; providedBindDN = v; adminUID = null; providedAdminUID = null; } else { bindDN = null; providedBindDN = null; adminUID = v; providedAdminUID = v; } } else if (useAdmin) { adminUID = app.readValidatedInput(INFO_LDAP_CONN_PROMPT_ADMINISTRATOR_UID .get(adminUID), callback); providedAdminUID = adminUID; } else { bindDN = app.readValidatedInput(INFO_LDAP_CONN_PROMPT_BIND_DN .get(bindDN), callback); providedBindDN = bindDN; } } catch (ClientException e) { throw cannotReadConnectionParameters(e); } } if (useAdminOrBindDn) { boolean addAdmin = providedAdminUID != null; boolean addBindDN = providedBindDN != null; if (!addAdmin && !addBindDN) { addAdmin = getAdministratorUID() != null; addBindDN = getBindDN() != null; } if (addAdmin) { copySecureArgsList.adminUidArg.clearValues(); copySecureArgsList.adminUidArg.addValue(getAdministratorUID()); commandBuilder.addArgument(copySecureArgsList.adminUidArg); } else if (addBindDN) { copySecureArgsList.bindDnArg.clearValues(); copySecureArgsList.bindDnArg.addValue(getBindDN()); commandBuilder.addArgument(copySecureArgsList.bindDnArg); } } else if (useAdmin) { copySecureArgsList.adminUidArg.clearValues(); copySecureArgsList.adminUidArg.addValue(getAdministratorUID()); commandBuilder.addArgument(copySecureArgsList.adminUidArg); } else { copySecureArgsList.bindDnArg.clearValues(); copySecureArgsList.bindDnArg.addValue(getBindDN()); commandBuilder.addArgument(copySecureArgsList.bindDnArg); } } else { bindDN = null; adminUID = null; } boolean addedPasswordFileArgument = false; if (secureArgsList.bindPasswordArg.isPresent()) { bindPassword = secureArgsList.bindPasswordArg.getValue(); } if (keyManager == null) { if (secureArgsList.bindPasswordFileArg.isPresent()) { // Read from file if it exists. bindPassword = secureArgsList.bindPasswordFileArg.getValue(); if (bindPassword == null) { if (useAdmin) { throw new ArgumentException(ERR_ERROR_NO_ADMIN_PASSWORD.get(adminUID)); } else { throw new ArgumentException(ERR_ERROR_NO_ADMIN_PASSWORD.get(bindDN)); } } copySecureArgsList.bindPasswordFileArg.clearValues(); copySecureArgsList.bindPasswordFileArg.getNameToValueMap().putAll( secureArgsList.bindPasswordFileArg.getNameToValueMap()); commandBuilder.addArgument(copySecureArgsList.bindPasswordFileArg); addedPasswordFileArgument = true; } else if (bindPassword == null || "-".equals(bindPassword)) { // Read the password from the stdin. if (!app.isInteractive()) { throw new ArgumentException(ERR_ERROR_BIND_PASSWORD_NONINTERACTIVE.get()); } checkHeadingDisplayed(); try { app.println(); LocalizableMessage prompt; if (providedAdminUID != null) { prompt = INFO_LDAPAUTH_PASSWORD_PROMPT.get(providedAdminUID); } else if (providedBindDN != null) { prompt = INFO_LDAPAUTH_PASSWORD_PROMPT.get(providedBindDN); } else if (bindDN != null) { prompt = INFO_LDAPAUTH_PASSWORD_PROMPT.get(bindDN); } else { prompt = INFO_LDAPAUTH_PASSWORD_PROMPT.get(adminUID); } bindPassword = readPassword(prompt); } catch (Exception e) { throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS .get(e.getMessage()), e.getCause()); } } copySecureArgsList.bindPasswordArg.clearValues(); copySecureArgsList.bindPasswordArg.addValue(bindPassword); if (!addedPasswordFileArgument) { commandBuilder .addObfuscatedArgument(copySecureArgsList.bindPasswordArg); } } connectTimeout = secureArgsList.connectTimeoutArg.getIntValue(); } private ArgumentException cannotReadConnectionParameters(ClientException e) { return new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS .get(e.getMessage()), e.getCause()); } private String readPassword(LocalizableMessage prompt) throws ClientException { final char[] pwd = app.readPassword(prompt); if (pwd != null) { return String.valueOf(pwd); } return null; } /** * Get the trust manager. * * @return The trust manager based on CLI args on interactive prompt. * @throws ArgumentException * If an error occurs when getting args values. */ private ApplicationTrustManager getTrustManagerInternal() throws ArgumentException { // Remove these arguments since this method might be called several times. commandBuilder.removeArgument(copySecureArgsList.trustAllArg); commandBuilder.removeArgument(copySecureArgsList.trustStorePathArg); commandBuilder.removeArgument(copySecureArgsList.trustStorePasswordArg); commandBuilder.removeArgument(copySecureArgsList.trustStorePasswordFileArg); // If we have the trustALL flag, don't do anything // just return null if (secureArgsList.trustAllArg.isPresent()) { commandBuilder.addArgument(copySecureArgsList.trustAllArg); return null; } // Check if some trust manager info are set boolean weDontKnowTheTrustMethod = !secureArgsList.trustAllArg.isPresent() && !secureArgsList.trustStorePathArg.isPresent() && !secureArgsList.trustStorePasswordArg.isPresent() && !secureArgsList.trustStorePasswordFileArg.isPresent(); boolean askForTrustStore = false; trustAll = secureArgsList.trustAllArg.isPresent(); // Try to use the local instance trust store, to avoid certificate // validation when both the CLI and the server are in the same instance. if (weDontKnowTheTrustMethod && addLocalTrustStore()) { weDontKnowTheTrustMethod = false; } if (app.isInteractive() && weDontKnowTheTrustMethod) { checkHeadingDisplayed(); app.println(); MenuBuilder builder = new MenuBuilder(app); builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_METHOD.get()); TrustMethod defaultTrustMethod = TrustMethod.DISPLAY_CERTIFICATE; for (TrustMethod t : TrustMethod.values()) { int i = builder.addNumberedOption(t.getMenuMessage(), MenuResult.success(t .getChoice())); if (t.equals(defaultTrustMethod)) { builder.setDefault( INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE .get(Integer.valueOf(i)), MenuResult.success(t.getChoice())); } } Menu menu = builder.toMenu(); trustStoreInMemory = false; try { MenuResult result = menu.run(); if (result.isSuccess()) { if (result.getValue().equals(TrustMethod.TRUSTALL.getChoice())) { commandBuilder.addArgument(copySecureArgsList.trustAllArg); trustAll = true; // If we have the trustALL flag, don't do anything // just return null return null; } else if (result.getValue().equals(TrustMethod.TRUSTSTORE.getChoice())) { // We have to ask for trust store info askForTrustStore = true; } else if (result.getValue().equals( TrustMethod.DISPLAY_CERTIFICATE.getChoice())) { // The certificate will be displayed to the user askForTrustStore = false; trustStoreInMemory = true; // There is no direct equivalent for this option, so propose the // trust all option as command-line argument. commandBuilder.addArgument(copySecureArgsList.trustAllArg); } else { // Should never happen. throw new RuntimeException(); } } else { // Should never happen. throw new RuntimeException(); } } catch (ClientException e) { throw new RuntimeException(e); } } // If we do not trust all server certificates, we have to get info // about trust store. First get the trust store path. truststorePath = secureArgsList.trustStorePathArg.getValue(); if (app.isInteractive() && !secureArgsList.trustStorePathArg.isPresent() && askForTrustStore) { checkHeadingDisplayed(); ValidationCallback callback = new ValidationCallback() { @Override public String validate(ConsoleApplication app, String input) throws ClientException { String ninput = input.trim(); if (ninput.length() == 0) { app.println(); app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH.get()); app.println(); return null; } File f = new File(ninput); if (f.exists() && f.canRead() && !f.isDirectory()) { return ninput; } else { app.println(); app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH.get()); app.println(); return null; } } }; try { app.println(); truststorePath = app.readValidatedInput( INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE_PATH.get(), callback); } catch (ClientException e) { throw cannotReadConnectionParameters(e); } } if (truststorePath != null) { copySecureArgsList.trustStorePathArg.clearValues(); copySecureArgsList.trustStorePathArg.addValue(truststorePath); commandBuilder.addArgument(copySecureArgsList.trustStorePathArg); } // Then the truststore password. // As the most common case is to have no password for truststore, // we don't ask it in the interactive mode. if (secureArgsList.trustStorePasswordArg.isPresent()) { truststorePassword = secureArgsList.trustStorePasswordArg.getValue(); } if (secureArgsList.trustStorePasswordFileArg.isPresent()) { // Read from file if it exists. truststorePassword = secureArgsList.trustStorePasswordFileArg.getValue(); } if ("-".equals(truststorePassword)) { // Read the password from the stdin. if (!app.isInteractive()) { truststorePassword = null; } else { checkHeadingDisplayed(); try { app.println(); LocalizableMessage prompt = INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE_PASSWORD .get(truststorePath); truststorePassword = readPassword(prompt); } catch (Exception e) { throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS .get(e.getMessage()), e.getCause()); } } } // We've got all the information to get the truststore manager try { truststore = KeyStore.getInstance(KeyStore.getDefaultType()); if (truststorePath != null) { FileInputStream fos = new FileInputStream(truststorePath); if (truststorePassword != null) { truststore.load(fos, truststorePassword.toCharArray()); } else { truststore.load(fos, null); } fos.close(); } else { truststore.load(null, null); } if (secureArgsList.trustStorePasswordFileArg.isPresent() && truststorePath != null) { copySecureArgsList.trustStorePasswordFileArg.clearValues(); copySecureArgsList.trustStorePasswordFileArg.getNameToValueMap() .putAll( secureArgsList.trustStorePasswordFileArg.getNameToValueMap()); commandBuilder .addArgument(copySecureArgsList.trustStorePasswordFileArg); } else if (truststorePassword != null && truststorePath != null) { // Only add the trust store password if there is one AND if the user // specified a trust store path. copySecureArgsList.trustStorePasswordArg.clearValues(); copySecureArgsList.trustStorePasswordArg.addValue(truststorePassword); commandBuilder .addObfuscatedArgument(copySecureArgsList.trustStorePasswordArg); } return new ApplicationTrustManager(truststore); } catch (Exception e) { throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS .get(e.getMessage()), e.getCause()); } } /** * Get the key manager. * * @return The key manager based on CLI args on interactive prompt. * @throws ArgumentException * If an error occurs when getting args values. */ private KeyManager getKeyManagerInternal() throws ArgumentException { // Remove these arguments since this method might be called several times. commandBuilder.removeArgument(copySecureArgsList.certNicknameArg); commandBuilder.removeArgument(copySecureArgsList.keyStorePathArg); commandBuilder.removeArgument(copySecureArgsList.keyStorePasswordArg); commandBuilder.removeArgument(copySecureArgsList.keyStorePasswordFileArg); // Do we need client side authentication ? // If one of the client side authentication args is set, we assume // that we // need client side authentication. boolean weDontKnowIfWeNeedKeystore = !secureArgsList.keyStorePathArg.isPresent() && !secureArgsList.keyStorePasswordArg.isPresent() && !secureArgsList.keyStorePasswordFileArg.isPresent() && !secureArgsList.certNicknameArg.isPresent(); // We don't have specific key manager parameter. // We assume that no client side authentication is required // Client side authentication is not the common use case. As a // consequence, interactive mode doesn't add an extra question // which will be in most cases useless. if (weDontKnowIfWeNeedKeystore) { return null; } // Get info about keystore. First get the keystore path. keystorePath = secureArgsList.keyStorePathArg.getValue(); if (app.isInteractive() && !secureArgsList.keyStorePathArg.isPresent()) { checkHeadingDisplayed(); ValidationCallback callback = new ValidationCallback() { @Override public String validate(ConsoleApplication app, String input) throws ClientException { String ninput = input.trim(); if (ninput.length() == 0) { return ninput; } File f = new File(ninput); if (f.exists() && f.canRead() && !f.isDirectory()) { return ninput; } else { app.println(); app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH.get()); app.println(); return null; } } }; try { app.println(); keystorePath = app.readValidatedInput(INFO_LDAP_CONN_PROMPT_SECURITY_KEYSTORE_PATH .get(), callback); } catch (ClientException e) { throw cannotReadConnectionParameters(e); } } if (keystorePath != null) { copySecureArgsList.keyStorePathArg.clearValues(); copySecureArgsList.keyStorePathArg.addValue(keystorePath); commandBuilder.addArgument(copySecureArgsList.keyStorePathArg); } else { // KeystorePath is null. Either it's unspecified or there's a pb // We should throw an exception here, anyway since code below will // anyway throw new ArgumentException(ERR_ERROR_INCOMPATIBLE_PROPERTY_MOD .get("null keystorePath")); } // Then the keystore password. keystorePassword = secureArgsList.keyStorePasswordArg.getValue(); if (secureArgsList.keyStorePasswordFileArg.isPresent()) { // Read from file if it exists. keystorePassword = secureArgsList.keyStorePasswordFileArg.getValue(); if (keystorePassword == null) { throw new ArgumentException(ERR_ERROR_NO_ADMIN_PASSWORD.get(keystorePassword)); } } else if (keystorePassword == null || "-".equals(keystorePassword)) { // Read the password from the stdin. if (!app.isInteractive()) { throw new ArgumentException(ERR_ERROR_BIND_PASSWORD_NONINTERACTIVE.get()); } checkHeadingDisplayed(); try { app.println(); LocalizableMessage prompt = INFO_LDAP_CONN_PROMPT_SECURITY_KEYSTORE_PASSWORD.get(keystorePath); keystorePassword = readPassword(prompt); } catch (Exception e) { throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS .get(e.getMessage()), e.getCause()); } } // finally the certificate name, if needed. KeyStore keystore = null; Enumeration aliasesEnum = null; try { FileInputStream fos = new FileInputStream(keystorePath); keystore = KeyStore.getInstance(KeyStore.getDefaultType()); keystore.load(fos, keystorePassword.toCharArray()); fos.close(); aliasesEnum = keystore.aliases(); } catch (Exception e) { throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS .get(e.getMessage()), e.getCause()); } certifNickname = secureArgsList.certNicknameArg.getValue(); if (app.isInteractive() && !secureArgsList.certNicknameArg.isPresent() && aliasesEnum.hasMoreElements()) { checkHeadingDisplayed(); try { MenuBuilder builder = new MenuBuilder(app); builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_ALIASES .get()); int certificateNumber = 0; for (; aliasesEnum.hasMoreElements();) { String alias = aliasesEnum.nextElement(); if (keystore.isKeyEntry(alias)) { X509Certificate certif = (X509Certificate) keystore.getCertificate(alias); certificateNumber++; builder .addNumberedOption( INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_ALIAS.get(alias, certif.getSubjectDN().getName()), MenuResult .success(alias)); } } if (certificateNumber > 1) { app.println(); Menu menu = builder.toMenu(); MenuResult result = menu.run(); if (result.isSuccess()) { certifNickname = result.getValue(); } else { // Should never happen. throw new RuntimeException(); } } else { certifNickname = null; } } catch (KeyStoreException e) { throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS .get(e.getMessage()), e.getCause()); } catch (ClientException e) { throw cannotReadConnectionParameters(e); } } // We'we got all the information to get the keys manager ApplicationKeyManager akm = new ApplicationKeyManager(keystore, keystorePassword.toCharArray()); if (secureArgsList.keyStorePasswordFileArg.isPresent()) { copySecureArgsList.keyStorePasswordFileArg.clearValues(); copySecureArgsList.keyStorePasswordFileArg.getNameToValueMap().putAll( secureArgsList.keyStorePasswordFileArg.getNameToValueMap()); commandBuilder.addArgument(copySecureArgsList.keyStorePasswordFileArg); } else if (keystorePassword != null) { copySecureArgsList.keyStorePasswordArg.clearValues(); copySecureArgsList.keyStorePasswordArg.addValue(keystorePassword); commandBuilder .addObfuscatedArgument(copySecureArgsList.keyStorePasswordArg); } if (certifNickname != null) { copySecureArgsList.certNicknameArg.clearValues(); copySecureArgsList.certNicknameArg.addValue(certifNickname); } if (certifNickname != null) { return new SelectableCertificateKeyManager(akm, certifNickname); } return akm; } /** * Indicates whether or not a connection should use SSL based on this * interaction. * * @return boolean where true means use SSL */ public boolean useSSL() { return useSSL; } /** * Indicates whether or not a connection should use StartTLS based on this * interaction. * * @return boolean where true means use StartTLS */ public boolean useStartTLS() { return useStartTLS; } /** * Gets the host name that should be used for connections based on this * interaction. * * @return host name for connections */ public String getHostName() { return hostName; } /** * Gets the port number name that should be used for connections based on this * interaction. * * @return port number for connections */ public int getPortNumber() { return portNumber; } /** * Sets the port number name that should be used for connections based on this * interaction. * * @param portNumber * port number for connections */ public void setPortNumber(int portNumber) { this.portNumber = portNumber; } /** * Gets the bind DN name that should be used for connections based on this * interaction. * * @return bind DN for connections */ public String getBindDN() { String dn; if (useAdminOrBindDn) { if (providedBindDN != null) { dn = providedBindDN; } else if (providedAdminUID != null) { dn = getAdministratorDN(providedAdminUID); } else if (bindDN != null) { dn = bindDN; } else if (adminUID != null) { dn = getAdministratorDN(adminUID); } else { dn = null; } } else if (secureArgsList.useAdminUID()) { dn = getAdministratorDN(adminUID); } else { dn = bindDN; } return dn; } /** * Gets the administrator UID name that should be used for connections based * on this interaction. * * @return administrator UID for connections */ public String getAdministratorUID() { return adminUID; } /** * Gets the bind password that should be used for connections based on this * interaction. * * @return bind password for connections */ public String getBindPassword() { return bindPassword; } /** * Gets the trust manager that should be used for connections based on this * interaction. * * @return trust manager for connections */ public ApplicationTrustManager getTrustManager() { return trustManager; } /** * Gets the key store that should be used for connections based on this * interaction. * * @return key store for connections */ public KeyStore getKeyStore() { return truststore; } /** * Gets the key manager that should be used for connections based on this * interaction. * * @return key manager for connections */ public KeyManager getKeyManager() { return keyManager; } /** * Indicate if the trust store is in memory. * * @return true if the trust store is in memory. */ public boolean isTrustStoreInMemory() { return trustStoreInMemory; } /** * Indicate if all certificates must be accepted. * * @return true all certificates must be accepted. */ public boolean isTrustAll() { return trustAll; } /** * Returns the timeout to be used to connect with the server. * * @return the timeout to be used to connect with the server. */ public int getConnectTimeout() { return connectTimeout; } /** * Indicate if the certificate chain can be trusted. * * @param chain * The certificate chain to validate * @return true if the server certificate is trusted. */ public boolean checkServerCertificate(X509Certificate[] chain) { return checkServerCertificate(chain, null, null); } /** * Indicate if the certificate chain can be trusted. * * @param chain * The certificate chain to validate * @param authType * the authentication type. * @param host * the host we tried to connect and that presented the certificate. * @return true if the server certificate is trusted. */ public boolean checkServerCertificate(X509Certificate[] chain, String authType, String host) { if (trustManager == null) { try { initializeTrustManager(); } catch (ArgumentException ae) { // Should not occur throw new RuntimeException(ae); } } app.println(); app.println(INFO_LDAP_CONN_PROMPT_SECURITY_SERVER_CERTIFICATE.get()); app.println(); for (int i = 0; i < chain.length; i++) { // Certificate DN app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_USER_DN .get(chain[i].getSubjectDN())); // certificate validity app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_VALIDITY.get( chain[i].getNotBefore(), chain[i].getNotAfter())); // certificate Issuer app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_ISSUER .get(chain[i].getIssuerDN())); if (i + 1 < chain.length) { app.println(); app.println(); } } MenuBuilder builder = new MenuBuilder(app); builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION.get()); TrustOption defaultTrustMethod = TrustOption.SESSION; for (TrustOption t : TrustOption.values()) { int i = builder.addNumberedOption(t.getMenuMessage(), MenuResult.success(t .getChoice())); if (t.equals(defaultTrustMethod)) { builder.setDefault( INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE.get(Integer .valueOf(i)), MenuResult.success(t.getChoice())); } } app.println(); app.println(); Menu menu = builder.toMenu(); while (true) { try { MenuResult result = menu.run(); if (result.isSuccess()) { if (result.getValue().equals(TrustOption.UNTRUSTED.getChoice())) { return false; } if (result.getValue().equals( TrustOption.CERTIFICATE_DETAILS.getChoice())) { for (X509Certificate cert : chain) { app.println(); app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE.get(cert)); } continue; } // We should add it in the memory truststore for (X509Certificate cert : chain) { String alias = cert.getSubjectDN().getName(); try { truststore.setCertificateEntry(alias, cert); } catch (KeyStoreException e1) { // What else should we do? return false; } } // Update the trust manager if (trustManager == null) { trustManager = new ApplicationTrustManager(truststore); } if (authType != null && host != null) { // Update the trust manager with the new certificate trustManager.acceptCertificate(chain, authType, host); } else { // Do a full reset of the contents of the keystore. trustManager = new ApplicationTrustManager(truststore); } if (result.getValue().equals(TrustOption.PERMAMENT.getChoice())) { ValidationCallback callback = new ValidationCallback() { @Override public String validate(ConsoleApplication app, String input) throws ClientException { String ninput = input.trim(); if (ninput.length() == 0) { app.println(); app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH .get()); app.println(); return null; } File f = new File(ninput); if (!f.isDirectory()) { return ninput; } else { app.println(); app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH .get()); app.println(); return null; } } }; String truststorePath; try { app.println(); truststorePath = app.readValidatedInput( INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE_PATH.get(), callback); } catch (ClientException e) { return true; } // Read the password from the stdin. String truststorePassword; try { app.println(); LocalizableMessage prompt = INFO_LDAP_CONN_PROMPT_SECURITY_KEYSTORE_PASSWORD .get(truststorePath); truststorePassword = readPassword(prompt); } catch (Exception e) { return true; } try { KeyStore ts = KeyStore.getInstance("JKS"); FileInputStream fis; try { fis = new FileInputStream(truststorePath); } catch (FileNotFoundException e) { fis = null; } ts.load(fis, truststorePassword.toCharArray()); if (fis != null) { fis.close(); } for (X509Certificate cert : chain) { String alias = cert.getSubjectDN().getName(); ts.setCertificateEntry(alias, cert); } FileOutputStream fos = new FileOutputStream(truststorePath); try { ts.store(fos, truststorePassword.toCharArray()); } finally { fos.close(); } } catch (Exception e) { return true; } } return true; } else { // Should never happen. throw new RuntimeException(); } } catch (ClientException cliE) { throw new RuntimeException(cliE); } } } /** * Populates a set of LDAP options with state from this interaction. * * @param options * existing set of options; may be null in which case this method * will create a new set of LDAPConnectionOptions to be * returned * @return used during this interaction * @throws SSLConnectionException * if this interaction has specified the use of SSL and there is a * problem initializing the SSL connection factory */ public LDAPConnectionOptions populateLDAPOptions(LDAPConnectionOptions options) throws SSLConnectionException { if (options == null) { options = new LDAPConnectionOptions(); } if (this.useSSL) { options.setUseSSL(true); SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory(); sslConnectionFactory.init(getTrustManager() == null, keystorePath, keystorePassword, certifNickname, truststorePath, truststorePassword); options.setSSLConnectionFactory(sslConnectionFactory); } else { options.setUseSSL(false); } options.setStartTLS(this.useStartTLS); return options; } /** * Prompts the user to accept the certificate. * * @param t * the throwable that was generated because the certificate was not * trusted. * @param usedTrustManager * the trustManager used when trying to establish the connection. * @param usedUrl * the LDAP URL used to connect to the server. * @param logger * the Logger used to log messages. * @return {@code true} if the user accepted the certificate and * {@code false} otherwise. */ public boolean promptForCertificateConfirmation(Throwable t, ApplicationTrustManager usedTrustManager, String usedUrl, LocalizedLogger logger) { ApplicationTrustManager.Cause cause; if (usedTrustManager != null) { cause = usedTrustManager.getLastRefusedCause(); } else { cause = null; } if (logger != null) { logger.debug(LocalizableMessage.raw("Certificate exception cause: " + cause)); } if (cause != null) { String h; int p; try { URI uri = new URI(usedUrl); h = uri.getHost(); p = uri.getPort(); } catch (Throwable t1) { printLogger(logger, "Error parsing ldap url of ldap url. " + t1); h = INFO_NOT_AVAILABLE_LABEL.get().toString(); p = -1; } String authType = usedTrustManager.getLastRefusedAuthType(); LocalizableMessage msg; if (authType.equals(ApplicationTrustManager.Cause.NOT_TRUSTED)) { msg = INFO_CERTIFICATE_NOT_TRUSTED_TEXT_CLI.get(h, p); } else { msg = INFO_CERTIFICATE_NAME_MISMATCH_TEXT_CLI.get(h, p, h, h, p); } app.println(msg); X509Certificate[] chain = usedTrustManager.getLastRefusedChain(); if (chain == null) { printLogger(logger, "Null chain for this certificate exception."); return false; } if (authType == null) { printLogger(logger, "Null auth type for this certificate exception."); } if (h == null) { printLogger(logger, "Null host name for this certificate exception."); } return checkServerCertificate(chain, authType, h); } else { app.println(getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), t)); } return false; } private void printLogger(final LocalizedLogger logger, final String msg) { if (logger != null) { logger.warn(LocalizableMessage.raw(msg)); } } /** * Sets the heading that is displayed in interactive mode. * * @param heading * the heading that is displayed in interactive mode. */ public void setHeadingMessage(LocalizableMessage heading) { this.heading = heading; } /** * Returns the command builder with the equivalent arguments on the * non-interactive mode. * * @return the command builder with the equivalent arguments on the * non-interactive mode. */ public CommandBuilder getCommandBuilder() { return commandBuilder; } /** * Displays the heading if it was not displayed before. */ private void checkHeadingDisplayed() { if (!isHeadingDisplayed) { app.println(); app.println(); app.println(heading); isHeadingDisplayed = true; } } /** * Tells whether during interaction we can ask for both the DN or the admin * UID. * * @return {@code true} if during interaction we can ask for both the DN * and the admin UID and {@code false} otherwise. */ public boolean isUseAdminOrBindDn() { return useAdminOrBindDn; } /** * Tells whether we can ask during interaction for both the DN and the admin * UID or not. * * @param useAdminOrBindDn * whether we can ask for both the DN and the admin UID during * interaction or not. */ public void setUseAdminOrBindDn(boolean useAdminOrBindDn) { this.useAdminOrBindDn = useAdminOrBindDn; } /** * Tells whether we propose LDAP as protocol even if the user provided * security parameters. This is required in command-lines that access multiple * servers (like dsreplication). * * @param displayLdapIfSecureParameters * whether propose LDAP as protocol even if the user provided * security parameters or not. */ public void setDisplayLdapIfSecureParameters( boolean displayLdapIfSecureParameters) { this.displayLdapIfSecureParameters = displayLdapIfSecureParameters; } /** * Resets the heading displayed flag, so that next time we call run the * heading is displayed. */ public void resetHeadingDisplayed() { isHeadingDisplayed = false; } /** * Resets the trust manager, so that next time we call the run() method the * trust manager takes into account the local trust store. */ public void resetTrustManager() { trustManager = null; } /** * Forces the initialization of the trust manager with the arguments provided * by the user. * * @throws ArgumentException * if there is an error with the arguments provided by the user. */ public void initializeTrustManagerIfRequired() throws ArgumentException { if (!trustManagerInitialized) { initializeTrustManager(); } } /** * Initializes the global arguments in the parser with the provided values. * This is useful when we want to call LDAPConnectionConsoleInteraction.run() * with some default values. * * @param hostName * the host name. * @param port * the port to connect to the server. * @param adminUid * the administrator UID. * @param bindDn * the bind DN to bind to the server. * @param bindPwd * the password to bind. * @param pwdFile * the Map containing the file and the password to bind. */ public void initializeGlobalArguments(String hostName, int port, String adminUid, String bindDn, String bindPwd, LinkedHashMap pwdFile) { resetConnectionArguments(); if (hostName != null) { secureArgsList.hostNameArg.addValue(hostName); secureArgsList.hostNameArg.setPresent(true); } // resetConnectionArguments does not clear the values for the port secureArgsList.portArg.clearValues(); if (port != -1) { secureArgsList.portArg.addValue(String.valueOf(port)); secureArgsList.portArg.setPresent(true); } else { // This is done to be able to call IntegerArgument.getIntValue() secureArgsList.portArg.addValue(secureArgsList.portArg.getDefaultValue()); } secureArgsList.useSSLArg.setPresent(useSSL); secureArgsList.useStartTLSArg.setPresent(useStartTLS); if (adminUid != null) { secureArgsList.adminUidArg.addValue(adminUid); secureArgsList.adminUidArg.setPresent(true); } if (bindDn != null) { secureArgsList.bindDnArg.addValue(bindDn); secureArgsList.bindDnArg.setPresent(true); } if (pwdFile != null) { secureArgsList.bindPasswordFileArg.getNameToValueMap().putAll(pwdFile); for (String value : pwdFile.keySet()) { secureArgsList.bindPasswordFileArg.addValue(value); } secureArgsList.bindPasswordFileArg.setPresent(true); } else if (bindPwd != null) { secureArgsList.bindPasswordArg.addValue(bindPwd); secureArgsList.bindPasswordArg.setPresent(true); } } /** * Resets the connection parameters for the LDAPConsoleInteraction object. The * reset does not apply to the certificate parameters. This is called in order * the LDAPConnectionConsoleInteraction object to ask for all this connection * parameters next time we call LDAPConnectionConsoleInteraction.run(). */ public void resetConnectionArguments() { secureArgsList.hostNameArg.clearValues(); secureArgsList.hostNameArg.setPresent(false); secureArgsList.portArg.clearValues(); secureArgsList.portArg.setPresent(false); // This is done to be able to call IntegerArgument.getIntValue() secureArgsList.portArg.addValue(secureArgsList.portArg.getDefaultValue()); secureArgsList.bindDnArg.clearValues(); secureArgsList.bindDnArg.setPresent(false); secureArgsList.bindPasswordArg.clearValues(); secureArgsList.bindPasswordArg.setPresent(false); secureArgsList.bindPasswordFileArg.clearValues(); secureArgsList.bindPasswordFileArg.getNameToValueMap().clear(); secureArgsList.bindPasswordFileArg.setPresent(false); secureArgsList.adminUidArg.clearValues(); secureArgsList.adminUidArg.setPresent(false); } private void initializeTrustManager() throws ArgumentException { // Get trust store info trustManager = getTrustManagerInternal(); // Check if we need client side authentication keyManager = getKeyManagerInternal(); trustManagerInitialized = true; } /** * Returns the explicitly provided Admin UID from the user (interactively or * through the argument). * * @return the explicitly provided Admin UID from the user (interactively or * through the argument). */ public String getProvidedAdminUID() { return providedAdminUID; } /** * Returns the explicitly provided bind DN from the user (interactively or * through the argument). * * @return the explicitly provided bind DN from the user (interactively or * through the argument). */ public String getProvidedBindDN() { return providedBindDN; } /** * Add the TrustStore of the administration connector of the local instance. * * @return true if the local trust store has been added. */ private boolean addLocalTrustStore() { try { // If remote host, return if (!InetAddress.getLocalHost().getHostName().equals(hostName) || secureArgsList.getAdminPortFromConfig() != portNumber) { return false; } // check if we are in a local instance. Already checked the host, // now check the port if (secureArgsList.getAdminPortFromConfig() != portNumber) { return false; } String truststoreFileAbsolute = secureArgsList.getTruststoreFileFromConfig(); if (truststoreFileAbsolute != null) { secureArgsList.trustStorePathArg.addValue(truststoreFileAbsolute); return true; } return false; } catch (Exception ex) { // do nothing return false; } } }