/** * VMware Continuent Tungsten Replicator * Copyright (C) 2015 VMware, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Initial developer(s): Ludovic Launer * Contributors: Robert Hodges */ package com.continuent.tungsten.common.security; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Random; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import com.continuent.tungsten.common.config.TungstenProperties; import com.continuent.tungsten.common.config.cluster.ClusterConfiguration; import com.continuent.tungsten.common.config.cluster.ConfigurationException; import com.continuent.tungsten.common.jmx.ServerRuntimeException; import com.continuent.tungsten.common.security.SecurityConf.KEYSTORE_TYPE; import com.continuent.tungsten.common.sockets.SSLSocketFactoryGenerator; import com.continuent.tungsten.common.utils.CLUtils; /** * Helper class for security related topics * * @author <a href="mailto:ludovic.launer@continuent.com">Ludovic Launer</a> * @version 1.0 */ public class SecurityHelper { private static final Logger logger = Logger.getLogger(SecurityHelper.class); /* * Defines the type of application requesting Security information. This * allows module specific configuration of security. */ // TUC-1872 public static enum TUNGSTEN_APPLICATION_NAME { CONNECTOR, REPLICATOR, REST_API, ANY; } /** * Save passwords from a TungstenProperties into a file * * @param authenticationInfo containing password file location */ public static void saveCredentialsFromAuthenticationInfo( AuthenticationInfo authenticationInfo) throws ServerRuntimeException { String passwordFileLocation = authenticationInfo .getPasswordFileLocation(); try { String username = authenticationInfo.getUsername(); String password = authenticationInfo.getPassword(); PropertiesConfiguration props = new PropertiesConfiguration( passwordFileLocation); // Use Apache commons-configuration: // preserves comments in .properties // ! props.setProperty(username, password); props.save(); } catch (org.apache.commons.configuration.ConfigurationException ce) { logger.error("Error while saving properties for file:" + authenticationInfo.getPasswordFileLocation(), ce); throw new ServerRuntimeException( "Error while saving Credentials: " + ce.getMessage()); } } /** * Delete a user and password from a file * * @param authenticationInfo containing password file location */ public static void deleteUserFromAuthenticationInfo( AuthenticationInfo authenticationInfo) throws ServerRuntimeException { String username = authenticationInfo.getUsername(); String passwordFileLocation = authenticationInfo .getPasswordFileLocation(); try { PropertiesConfiguration props = new PropertiesConfiguration( passwordFileLocation); // --- Check that the user exists --- String usernameInFile = props.getString(username); if (usernameInFile == null) { throw new ServerRuntimeException(MessageFormat .format("Username does not exist: {0}", username)); } props.clearProperty(username); props.save(); } catch (org.apache.commons.configuration.ConfigurationException ce) { logger.error("Error while saving properties for file:" + authenticationInfo.getPasswordFileLocation(), ce); throw new ServerRuntimeException( "Error while saving Credentials: " + ce.getMessage()); } } /** * Loads passwords from a TungstenProperties from a .properties file * * @return TungstenProperties containing logins as key and passwords as * values */ public static TungstenProperties loadPasswordsFromAuthenticationInfo( AuthenticationInfo authenticationInfo) throws ServerRuntimeException { try { String passwordFileLocation = authenticationInfo .getPasswordFileLocation(); TungstenProperties newProps = new TungstenProperties(); newProps.load(new FileInputStream(passwordFileLocation), false); newProps.trim(); logger.debug(MessageFormat.format("Passwords loaded from: {0}", passwordFileLocation)); return newProps; } catch (FileNotFoundException e) { throw new ServerRuntimeException("Unable to find properties file: " + authenticationInfo.getPasswordFileLocation(), e); } catch (IOException e) { throw new ServerRuntimeException("Unable to read properties file: " + authenticationInfo.getPasswordFileLocation(), e); } } /** * Loads Authentication and Encryption parameters from default location for * service.properties file * * @return AuthenticationInfo loaded from file * @throws ConfigurationException */ public static AuthenticationInfo loadAuthenticationInformation() throws ConfigurationException { return loadAuthenticationInformation((String) null); } public static AuthenticationInfo loadAuthenticationInformation( TUNGSTEN_APPLICATION_NAME tungstenApplicationName) throws ConfigurationException { return loadAuthenticationInformation(null, true, tungstenApplicationName); } /** * Loads Authentication and Encryption parameters from service.properties * file * * @param propertiesFileLocation Location of the security.properties file. * If set to null, will try to locate default file. * @return AuthenticationInfo * @throws ConfigurationException * @throws ReplicatorException */ public static AuthenticationInfo loadAuthenticationInformation( String propertiesFileLocation) throws ConfigurationException { return loadAuthenticationInformation(propertiesFileLocation, true, TUNGSTEN_APPLICATION_NAME.ANY); } public static AuthenticationInfo loadAuthenticationInformation( String propertiesFileLocation, boolean doConsistencyChecks, TUNGSTEN_APPLICATION_NAME tungstenApplicationName) throws ConfigurationException { // Load properties and perform substitution TungstenProperties securityProperties = null; try { securityProperties = loadSecurityPropertiesFromFile( propertiesFileLocation); } catch (ConfigurationException ce) { if (doConsistencyChecks) throw ce; } AuthenticationInfo authInfo = new AuthenticationInfo( propertiesFileLocation); // Authorisation and/or encryption if (securityProperties != null) { securityProperties.trim(); // Remove white spaces // --- Get common values --- List<String> enabledProtocols = securityProperties.getStringList( SecurityConf.SECURITY_ENABLED_TRANSPORT_PROTOCOL); List<String> enabledCipherSuites = securityProperties .getStringList(SecurityConf.SECURITY_ENABLED_CIPHER_SUITES); Integer minWaitOnFailedLogin = securityProperties.getInt( SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_MIN, SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_MIN_DEFAULT, false); Integer maxWaitOnFailedLogin = securityProperties.getInt( SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_MAX, SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_MAX_DEFAULT, false); Integer incrementStepWaitOnFailedLogin = securityProperties.getInt( SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_INCREMENT_STEP, SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_INCREMENT_STEP_DEFAULT, false); // Always use custom Tungsten Authentication Realm // CONT-1348 boolean useTungstenAuthenticationRealm = true; // Define application specific settings // Use default values by default String security_use_encryption = SecurityConf.SECURITY_JMX_USE_ENCRYPTION; String security_use_encryption_default = SecurityConf.SECURITY_JMX_USE_ENCRYPTION; String security_use_authentication = SecurityConf.SECURITY_JMX_USE_AUTHENTICATION; String security_use_authentication_default = SecurityConf.SECURITY_USE_AUTHENTICATION_DEFAULT; String security_keystore_location = SecurityConf.SECURITY_KEYSTORE_LOCATION; String security_keystore_password = SecurityConf.SECURITY_KEYSTORE_PASSWORD; String security_truststore_location = SecurityConf.SECURITY_TRUSTSTORE_LOCATION; String security_truststore_password = SecurityConf.SECURITY_TRUSTSTORE_PASSWORD; String security_authentication_use_encrypted_password = SecurityConf.SECURITY_JMX_USE_TUNGSTEN_AUTHENTICATION_REALM_ENCRYPTED_PASSWORD; String security_authentication_use_encrypted_password_default = SecurityConf.SECURITY_USE_TUNGSTEN_AUTHENTICATION_REALM_ENCRYPTED_PASSWORD_DEFAULT; // Use application specific settings if needed switch (tungstenApplicationName) { case CONNECTOR : security_keystore_location = SecurityConf.CONNECTOR_SECURITY_KEYSTORE_LOCATION; security_keystore_password = SecurityConf.CONNECTOR_SECURITY_KEYSTORE_PASSWORD; security_truststore_location = SecurityConf.CONNECTOR_SECURITY_TRUSTSTORE_LOCATION; security_truststore_password = SecurityConf.CONNECTOR_SECURITY_TRUSTSTORE_PASSWORD; security_use_encryption = SecurityConf.CONNECTOR_USE_SSL; security_use_encryption_default = SecurityConf.CONNECTOR_USE_SSL_DEFAULT; break; case REST_API : security_use_encryption = SecurityConf.HTTP_REST_API_SSL_USESSL; security_use_encryption_default = SecurityConf.HTTP_REST_API_SSL_USESSL_DEFAULT; security_use_authentication = SecurityConf.HTTP_REST_API_AUTHENTICATION; security_use_authentication_default = SecurityConf.HTTP_REST_API_AUTHENTICATION_DEFAULT; security_authentication_use_encrypted_password = SecurityConf.HTTP_REST_API_USE_TUNGSTEN_AUTHENTICATION_REALM_ENCRYPTED_PASSWORD; security_authentication_use_encrypted_password_default = SecurityConf.HTTP_REST_API_USE_TUNGSTEN_AUTHENTICATION_REALM_ENCRYPTED_PASSWORD_DEFAULT; security_keystore_location = SecurityConf.HTTP_REST_API_KEYSTORE_LOCATION; security_keystore_password = SecurityConf.HTTP_REST_API_KEYSTORE_PASSWORD; security_truststore_location = SecurityConf.HTTP_REST_API_TRUSTSTORE_LOCATION; security_truststore_password = SecurityConf.HTTP_REST_API_TRUSTSTORE_PASSWORD; break; default : // Keep default values break; } // --- Retrieve properties --- boolean connectorUseSSL = securityProperties .getBoolean(SecurityConf.CONNECTOR_USE_SSL, "false", false); boolean useEncryption = securityProperties.getBoolean( security_use_encryption, security_use_encryption_default, false); boolean useAuthentication = securityProperties.getBoolean( security_use_authentication, security_use_authentication_default, false); boolean authenticationByCertificateNeeded = securityProperties .getBoolean( SecurityConf.HTTP_REST_API_AUTHENTICATION_USE_CERTIFICATE, SecurityConf.HTTP_REST_API_AUTHENTICATION_USE_CERTIFICATE_DEFAULT, false); boolean useEncryptedPassword = securityProperties.getBoolean( security_authentication_use_encrypted_password, security_authentication_use_encrypted_password_default, false); String parentFileLocation = securityProperties.getString( SecurityConf.SECURITY_PROPERTIES_PARENT_FILE_LOCATION); String passwordFileLocation = securityProperties .getString(SecurityConf.SECURITY_PASSWORD_FILE_LOCATION); String accessFileLocation = securityProperties .getString(SecurityConf.SECURITY_ACCESS_FILE_LOCATION); String keystoreLocation = securityProperties .getString(security_keystore_location); keystoreLocation = (keystoreLocation != null && StringUtils.isNotBlank(keystoreLocation)) ? keystoreLocation : null; String keystorePassword = securityProperties .getString(security_keystore_password); String truststoreLocation = securityProperties .getString(security_truststore_location); truststoreLocation = (truststoreLocation != null && StringUtils.isNotBlank(truststoreLocation)) ? truststoreLocation : null; String truststorePassword = securityProperties .getString(security_truststore_password); String clientKeystoreLocation = securityProperties.getString( SecurityConf.HTTP_REST_API_CLIENT_KEYSTORE_LOCATION); clientKeystoreLocation = (clientKeystoreLocation != null && StringUtils.isNotBlank(clientKeystoreLocation)) ? clientKeystoreLocation : null; String clientKeystorePassword = securityProperties.getString( SecurityConf.HTTP_REST_API_CLIENT_KEYSTORE_PASSWORD); clientKeystorePassword = (clientKeystorePassword != null && StringUtils.isNotBlank(clientKeystorePassword)) ? clientKeystorePassword : null; String userName = securityProperties .getString(SecurityConf.SECURITY_JMX_USERNAME, null, false); // Aliases for keystore String connector_alias_client_to_connector = securityProperties .getString( SecurityConf.KEYSTORE_ALIAS_CONNECTOR_CLIENT_TO_CONNECTOR, SecurityConf.KEYSTORE_ALIAS_CONNECTOR_CLIENT_TO_CONNECTOR_DEFAULT, false); String connector_alias_connector_to_db = securityProperties .getString( SecurityConf.KEYSTORE_ALIAS_CONNECTOR_CONNECTOR_TO_DB, SecurityConf.KEYSTORE_ALIAS_CONNECTOR_CONNECTOR_TO_DB_DEFAULT, false); String replicator_alias_master_to_slave = securityProperties .getString( SecurityConf.KEYSTORE_ALIAS_REPLICATOR_MASTER_TO_SLAVE, SecurityConf.KEYSTORE_ALIAS_REPLICATOR_MASTER_TO_SLAVE_DEFAULT, false); // --- Populate return object --- authInfo.setTungstenApplicationName(tungstenApplicationName); authInfo.setConnectorUseSSL(connectorUseSSL); authInfo.setParentPropertiesFileLocation(parentFileLocation); authInfo.setAuthenticationNeeded(useAuthentication); authInfo.setAuthenticationByCertificateNeeded( authenticationByCertificateNeeded); authInfo.setMinWaitOnFailedLogin(minWaitOnFailedLogin); authInfo.setMaxWaitOnFailedLogin(maxWaitOnFailedLogin); authInfo.setIncrementStepWaitOnFailedLogin( incrementStepWaitOnFailedLogin); authInfo.setUseTungstenAuthenticationRealm( useTungstenAuthenticationRealm); authInfo.setUseEncryptedPasswords(useEncryptedPassword); authInfo.setEncryptionNeeded(useEncryption); authInfo.setPasswordFileLocation(passwordFileLocation); authInfo.setAccessFileLocation(accessFileLocation); authInfo.setKeystoreLocation(keystoreLocation); authInfo.setKeystorePassword(keystorePassword); authInfo.setTruststoreLocation(truststoreLocation); authInfo.setTruststorePassword(truststorePassword); authInfo.setEnabledProtocols(enabledProtocols); authInfo.setEnabledCipherSuites(enabledCipherSuites); authInfo.setClientKeystoreLocation(clientKeystoreLocation); authInfo.setClientKeystorePassword(clientKeystorePassword); authInfo.setUsername(userName); authInfo.setParentProperties(securityProperties); // aliases if (connector_alias_client_to_connector != null) authInfo.getMapKeystoreAliasesForTungstenApplication().put( SecurityConf.KEYSTORE_ALIAS_CONNECTOR_CLIENT_TO_CONNECTOR, connector_alias_client_to_connector); if (connector_alias_connector_to_db != null) authInfo.getMapKeystoreAliasesForTungstenApplication().put( SecurityConf.KEYSTORE_ALIAS_CONNECTOR_CONNECTOR_TO_DB, connector_alias_connector_to_db); if (replicator_alias_master_to_slave != null) authInfo.getMapKeystoreAliasesForTungstenApplication().put( SecurityConf.KEYSTORE_ALIAS_REPLICATOR_MASTER_TO_SLAVE, replicator_alias_master_to_slave); // --- Check information is correct --- // Checks authentication and encryption parameters // file exists, aliases exists... if (doConsistencyChecks) authInfo.checkAndCleanAuthenticationInfo( tungstenApplicationName); // --- Set critical properties as System Properties --- SecurityHelper.setSecurityProperties(authInfo, false); } return authInfo; } /** * Set system properties required for SSL and password management. Since * these settings are critical to correct operation we optionally log them. * Configured cipher suites and protocols may only match partially with the * ciphers and protocols supported by the SSL implementation being used. In * system properties we only store ciphers and protocols which are both * configured and supported. * * @param authInfo Populated authentication information * @param verbose If true, log information * @throws ConfigurationException * @throws GeneralSecurityException * @throws IOException */ private static void setSecurityProperties(AuthenticationInfo authInfo, boolean verbose) throws ConfigurationException { if (verbose) { CLUtils.println("Setting security properties!"); } // Keystore and Truststore setSystemProperty("javax.net.ssl.keyStore", authInfo.getKeystoreLocation(), verbose); setSystemProperty("javax.net.ssl.keyStorePassword", authInfo.getKeystorePassword(), verbose); setSystemProperty("javax.net.ssl.trustStore", authInfo.getTruststoreLocation(), verbose); setSystemProperty("javax.net.ssl.trustStorePassword", authInfo.getTruststorePassword(), verbose); if (authInfo.isEncryptionNeeded()) { SSLSocketFactory sf; try { sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); } catch (Exception e) { throw new ConfigurationException("Failed to create a socket " + "factory for examining cipher suites supported by " + "underlying SSL implementation. " + e.getMessage()); } /** * Find out which protocols and cipher suites are both specified in * cluster configuration and supported by the current, underlying * SSL implementation. Set common protocols and cipher suites to * corresponding System properties which will be the only access * point to encryption information hereafter. */ setProtocolsToSystemProperties(authInfo, sf, verbose); setCipherSuitesToSystemProperties(authInfo, sf, verbose); // There must be at least one protocol and cipher suite if // encryption is used if (SecurityHelper.getProtocols() == null) { throw new ConfigurationException("Unable to find suitable " + "protocols for encrypted messaging."); } if (SecurityHelper.getCiphers() == null) { throw new ConfigurationException("Unable to find suitable " + "cipher suites for encrypted messaging."); } } } /** * Set system properties required for client SSL support and authentication. * The keyStore holds the client's private key to be used for authentication * The trustStore holds the certificates of the trusted servers * * @param authInfo * @param verbose */ public static void setClientSecurityProperties(AuthenticationInfo authInfo, boolean debug) { if (debug) { CLUtils.println("Clearing and Setting Client security properties"); } System.clearProperty("javax.net.ssl.keyStore"); System.clearProperty("javax.net.ssl.trustStore"); setSystemProperty("javax.net.ssl.keyStore", authInfo.getClientKeystoreLocation(), debug); setSystemProperty("javax.net.ssl.keyStorePassword", authInfo.getClientKeystorePassword(), debug); setSystemProperty("javax.net.ssl.trustStore", authInfo.getTruststoreLocation(), debug); setSystemProperty("javax.net.ssl.trustStorePassword", authInfo.getTruststorePassword(), debug); } /** * Sets a system property with a log message. Java -Dxxx system property * * @param name the name of the system property to set * @param value value of the system property * @param verbose log the property being set if true. */ private static void setSystemProperty(String name, String value, boolean verbose) { if (verbose) { CLUtils.println("Setting system property: name=" + name + " value=" + value); } if (value != null) System.setProperty(name, value); } /** * Loads Security related properties from a file. File location = * {clusterhome}/conf/security.properties * * @param propertiesFileLocation location of the security.properties file. * If set to null will look for the default file. * @return TungstenProperties containing security parameters * @throws ConfigurationException */ private static TungstenProperties loadSecurityPropertiesFromFile( String propertiesFileLocation) throws ConfigurationException { TungstenProperties securityProps = null; FileInputStream securityConfigurationFileInputStream = null; // --- Get Configuration file --- if (propertiesFileLocation == null && ClusterConfiguration.getClusterHome() == null) { throw new ConfigurationException( "No cluster.home found from which to configure cluster resources."); } File securityPropertiesFile; if (propertiesFileLocation == null) // Get from default location { File clusterConfDirectory = ClusterConfiguration .getDir(ClusterConfiguration.getGlobalConfigDirName( ClusterConfiguration.getClusterHome())); securityPropertiesFile = new File(clusterConfDirectory.getPath(), SecurityConf.SECURITY_PROPERTIES_FILE_NAME); } else // Get from supplied location { securityPropertiesFile = new File(propertiesFileLocation); } // --- Get properties --- try { securityProps = new TungstenProperties(); securityConfigurationFileInputStream = new FileInputStream( securityPropertiesFile); securityProps.load(securityConfigurationFileInputStream, true); closeSecurityConfigurationFileInputStream( securityConfigurationFileInputStream); } catch (FileNotFoundException e) { String msg = MessageFormat.format( "Cannot find configuration file: {0}", securityPropertiesFile.getPath()); logger.debug(msg, e); throw new ConfigurationException(msg); } catch (IOException e) { String msg = MessageFormat.format( "Cannot load configuration file: {0}.\n Reason: {1}", securityPropertiesFile.getPath(), e.getMessage()); logger.debug(msg, e); throw new ConfigurationException(msg); } finally { closeSecurityConfigurationFileInputStream( securityConfigurationFileInputStream); } if (logger.isDebugEnabled()) { logger.debug(MessageFormat.format(": {0}", securityPropertiesFile.getPath())); } // Update propertiesFileLocation with the location actualy used securityProps.put(SecurityConf.SECURITY_PROPERTIES_PARENT_FILE_LOCATION, securityPropertiesFile.getAbsolutePath()); return securityProps; } /** * Close the security.properties input stream once it's been used. Best * effort * * @param fis */ private static void closeSecurityConfigurationFileInputStream( FileInputStream fis) { // TUC-2065 Close input stream once it's used if (fis != null) { try { fis.close(); } catch (Exception ignoreMe) { } } } /** * Read client's SSL protocols * * @return first value on the list or null if property doesn't have value. */ public static String getProtocol() { if (System.getProperty( SecurityConf.SYSTEM_PROP_CLIENT_SSLPROTOCOLS) != null) return System .getProperty(SecurityConf.SYSTEM_PROP_CLIENT_SSLPROTOCOLS) .split(",")[0]; else return null; } public static String[] getProtocols() { if (System.getProperty( SecurityConf.SYSTEM_PROP_CLIENT_SSLPROTOCOLS) != null) return System .getProperty(SecurityConf.SYSTEM_PROP_CLIENT_SSLPROTOCOLS) .split(","); else return null; } /** * Read client's SSL ciphers * * @return first value on the list or null if property doesn't have value. */ public static String getCipher() { if (System.getProperty( SecurityConf.SYSTEM_PROP_CLIENT_SSLCIPHERS) != null) return System .getProperty(SecurityConf.SYSTEM_PROP_CLIENT_SSLCIPHERS) .split(",")[0]; else return null; } /** Get client encryption ciphers. */ public static String[] getCiphers() { if (System.getProperty( SecurityConf.SYSTEM_PROP_CLIENT_SSLCIPHERS) != null) return System .getProperty(SecurityConf.SYSTEM_PROP_CLIENT_SSLCIPHERS) .split(","); else return null; } /** Return the ciphers available on this JVM. */ public static String[] getJvmSupportedCiphers() { String[] supportedCiphers = ((SSLSocketFactory) SSLSocketFactory .getDefault()).getSupportedCipherSuites(); return supportedCiphers; } /** * Process a list of candidate ciphers for use on the JVM and return those * candidates that are supported and hence usable. */ public static String[] getJvmEnabledCiphers(String[] candidateCiphers) { String[] jvmEnabledCiphers = getMatchingStrings( getJvmSupportedCiphers(), candidateCiphers); return jvmEnabledCiphers; } /** * Get the system keystore location * * @return The keyStore location */ public static String getKeyStoreLocation() { return System.getProperty("javax.net.ssl.keyStore"); } /** * Get the system truststore location * * @return */ public static String getTrustStoreLocation() { return System.getProperty("javax.net.ssl.trustStore"); } /** * Get alias in a keystore or truststore * * @param keystoreLocation * @param keystorePassword * @return * @throws KeyStoreException * @throws NoSuchAlgorithmException * @throws CertificateException * @throws IOException */ public static List<String> getAliasesforKeystore(String keystoreLocation, String keystorePassword) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { Enumeration<String> enumAliases = null; FileInputStream inputstreamStore; inputstreamStore = new FileInputStream(keystoreLocation); KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); keystore.load(inputstreamStore, keystorePassword.toCharArray()); // List the aliases enumAliases = keystore.aliases(); List<String> listAliases = Collections.list(enumAliases); // while (enumAliases.hasMoreElements()) // { // String alias = enumAliases.nextElement(); // // // Does alias refer to a private key? // // boolean b = keystore.isKeyEntry(alias); // // // Does alias refer to a trusted certificate? // // b = keystore.isCertificateEntry(alias); // } return listAliases; } /** * Check that the keystore / trustore can be accessed and has non empty list * of Aliases * * @param storeLocation * @param storePassword * @param shouldNotBeEmpty true if the store list of aliases should not be * empty * @throws ConfigurationException */ public static void checkAccessAndAliasesForKeystore(String storeLocation, String storePassword, boolean shouldNotBeEmpty) throws ConfigurationException { final String errorMessage = MessageFormat.format( "Could not access or retrieve aliases from {0}", storeLocation); try { List<String> aliasesInKeystore = SecurityHelper .getAliasesforKeystore(storeLocation, storePassword); if (aliasesInKeystore.isEmpty() && shouldNotBeEmpty) { throw new ConfigurationException(MessageFormat.format( "Keystore / Truststore does not contain any aliases: {0}", storeLocation)); } } catch (KeyStoreException e) { throw new ConfigurationException( MessageFormat.format(errorMessage, e)); } catch (NoSuchAlgorithmException e) { throw new ConfigurationException( MessageFormat.format(errorMessage, e)); } catch (CertificateException e) { throw new ConfigurationException( MessageFormat.format(errorMessage, e)); } catch (IOException e) { throw new ConfigurationException( MessageFormat.format(errorMessage, e)); } } /** * Stores the certificate in the host certificate truststore with the * associated alias. If the certificate is already added, this operation * does nothing. Any other certificate associated with the alias is * replaced. * * @param certificateAlias Alias of the certificate in the truststore * @param certificateLocation Where to get the certificate from */ public static void addCertificateToTruststore(String certificateAlias, String certificateLocation) { } /** * Compare two String arrays and store matching Strings to result array. * * @param str1 * @param str2 * @return String array consisting of all common Strings in str1 and str2. */ public static String[] getMatchingStrings(String[] str1, String[] str2) { // Put first string in table and then iterate across it using the // second. HashMap<String, String> map = new HashMap<String, String>(); for (String s : str1) { map.put(s, "FOUND"); } ArrayList<String> resultList = new ArrayList<String>(); for (String s : str2) { if (map.get(s) != null) resultList.add(s); } return resultList.toArray(new String[0]); } public static void setCiphersAndProtocolsToSSLSocket(SSLSocket sslSocket, String[] enabledCiphers, String[] enabledProtocols) throws ConfigurationException { if (enabledCiphers != null && enabledCiphers.length > 0) { if (SecurityHelper.getMatchingStrings( sslSocket.getSupportedCipherSuites(), enabledCiphers).length == 0) { throw new ConfigurationException( "SSLSocket doesn't support any " + "of the enabled (configured) cipher suites."); } // Enable ciphers which are both supported by socket service and // configured by user sslSocket.setEnabledCipherSuites(SecurityHelper.getMatchingStrings( sslSocket.getSupportedCipherSuites(), enabledCiphers)); } if (enabledProtocols != null && enabledProtocols.length > 0) { if (SecurityHelper.getMatchingStrings( sslSocket.getSupportedProtocols(), enabledProtocols).length == 0) { throw new ConfigurationException( "SSLSocket doesn't support any " + "of the enabled (configured) protocols."); } // Enable protocols which are both supported by socket and // configured by user. sslSocket.setEnabledProtocols(SecurityHelper.getMatchingStrings( sslSocket.getSupportedProtocols(), enabledProtocols)); } } public static void setCiphersAndProtocolsToSSLSocket2(SSLSocket sslSocket, String[] enabledCiphers, String[] enabledProtocols) throws ConfigurationException { // Check that ciphers and protocols lists aren't empty if (enabledCiphers == null || enabledCiphers.length == 0) { throw new ConfigurationException( "No ciphers are enabled in security properties."); } if (enabledProtocols == null || enabledProtocols.length == 0) { throw new ConfigurationException( "No protocols are enabled in security properties."); } // Set cipher suites and protocols if (SecurityHelper.getMatchingStrings( sslSocket.getSupportedCipherSuites(), enabledCiphers).length == 0) { throw new ConfigurationException("SSLSocket doesn't support any " + "of the enabled (configured) cipher suites."); } // Enable ciphers which are both supported by socket service and // configured by user sslSocket.setEnabledCipherSuites(SecurityHelper.getMatchingStrings( sslSocket.getSupportedCipherSuites(), enabledCiphers)); if (SecurityHelper.getMatchingStrings(sslSocket.getSupportedProtocols(), enabledProtocols).length == 0) { throw new ConfigurationException("SSLSocket doesn't support any " + "of the enabled (configured) protocols."); } // Enable protocols which are both supported by socket and // configured by user. sslSocket.setEnabledProtocols(SecurityHelper.getMatchingStrings( sslSocket.getSupportedProtocols(), enabledProtocols)); } /** * Find out which protocols are both configured and supported. Set common * ones to System properties from where (and only there) they are accessed. * * @param sf * @param verbose */ private static void setProtocolsToSystemProperties( AuthenticationInfo authInfo, SSLSocketFactory sf, boolean verbose) { String[] supportedProtocolsArray; String[] configuredProtocolsArray; String[] possibleProtocolsArray; SSLSocket ssl; try { ssl = ((SSLSocket) sf.createSocket()); } catch (IOException e) { logger.error("Failed to create SSLSocket. " + e.getMessage()); throw new RuntimeException( "Unable to find out" + "the protocols supported by current " + "underlying SSL implementation."); } supportedProtocolsArray = ssl.getSupportedProtocols(); if (supportedProtocolsArray.length == 0) { throw new RuntimeException("Reading supported protocols from " + "SSLSocket returned empty set. Encryption is not " + "possible."); } if (!authInfo.getEnabledProtocols().isEmpty()) { // There are protocols specified in the configuration configuredProtocolsArray = authInfo.getEnabledProtocols() .toArray(new String[0]); // Find common protocols from configured and from supported lists possibleProtocolsArray = SecurityHelper.getMatchingStrings( supportedProtocolsArray, configuredProtocolsArray); if (possibleProtocolsArray.length == 0) { // We don't have any protocols in common. This is not good! String message = "Configured and supported protocol lists " + "don't have anything in common. Encryption is not " + "possible."; StringBuffer sb = new StringBuffer(message).append("\n"); sb.append(String.format( "SSL implementation supports these " + "cipher suites: %s\n", StringUtils.join(supportedProtocolsArray, ","))); sb.append(String.format( "These were the configured protocols : %s\n", StringUtils.join(configuredProtocolsArray, ","))); logger.error(sb.toString()); throw new RuntimeException(message); } } else { StringBuffer sb = new StringBuffer("Unable to find protocol(s) " + "from user-provided configuration. Using all protocols" + " supported by SSL implementation.\n\tSupported protocols : "); sb.append(StringUtils.join(supportedProtocolsArray, ", ")); logger.warn(sb.toString()); possibleProtocolsArray = supportedProtocolsArray; } // Set System properties for protocols setSystemProperty(SecurityConf.SYSTEM_PROP_CLIENT_SSLPROTOCOLS, StringUtils.join(possibleProtocolsArray, ","), verbose); setSystemProperty("https.protocols", StringUtils.join(possibleProtocolsArray, "\n"), verbose); } /** * Find out which cipher suites are both configured and supported. Set * selected ones to System properties from where (and only there) they are * accessed. * * @param authInfo * @param sf * @param verbose */ private static void setCipherSuitesToSystemProperties( AuthenticationInfo authInfo, SSLSocketFactory sf, boolean verbose) { String[] supportedCipherSuitesArray = sf.getDefaultCipherSuites(); String[] configuredCipherSuitesArray; String[] possibleCipherSuitesArray; if (!authInfo.getEnabledCipherSuites().isEmpty()) { // There are cipher suites specified in the configuration supportedCipherSuitesArray = sf.getSupportedCipherSuites(); configuredCipherSuitesArray = authInfo.getEnabledCipherSuites() .toArray(new String[0]); possibleCipherSuitesArray = SecurityHelper.getMatchingStrings( supportedCipherSuitesArray, configuredCipherSuitesArray); if (possibleCipherSuitesArray.length == 0) { // We don't have any cipher suites in common. This is not good! String message = "Unable to find approved ciphers in the supported cipher suites on this JVM"; StringBuffer sb = new StringBuffer(message).append("\n"); sb.append(String.format( "SSL implementation supported cipher suites: %s\n", StringUtils.join(supportedCipherSuitesArray))); sb.append(String.format( "Approved cipher suites from security.properties: %s\n", StringUtils.join(configuredCipherSuitesArray))); logger.error(sb.toString()); throw new RuntimeException(message); } } else { /** * Cipher suites aren't specified in configuration. Read default * cipher suites and use them. */ possibleCipherSuitesArray = sf.getDefaultCipherSuites(); } // Set System properties for cipher suites setSystemProperty(SecurityConf.SYSTEM_PROP_CLIENT_SSLCIPHERS, StringUtils.join(possibleCipherSuitesArray, ","), verbose); } /** * Print whether JMX connections are encrypted and if so, used cipher suites * and protocols * * @param authInfo * @return */ public static String printSecuritySummary(AuthenticationInfo authInfo) { StringBuilder sb = new StringBuilder(); sb.append("Security summary :\n"); if (authInfo.isEncryptionNeeded()) { sb.append("JMX connections are encrypted\n"); sb.append("Enabled cipher suites : "); for (String s : SecurityHelper.getCiphers()) sb.append("\n\t" + s + " "); sb.append("\n"); sb.append("Enabled protocols : "); for (String s : SecurityHelper.getProtocols()) sb.append("\n\t" + s + " "); sb.append("\n"); } else { sb.append("JMX connections are not encrypted\n"); } if (authInfo.isAuthenticationNeeded()) { sb.append("Password authentication is used\n"); } else { sb.append("No password authentication\n"); } return sb.toString(); } /** * Check that KeyStore and the keys inside have a common password. * * @param keystoreLocation * @param keystoreType "jks", jceks", or "pkcs" * @param password * @throws ConfigurationException * @throws GeneralSecurityException * @throws IOException */ public static void checkKeyStorePasswords(String keystoreLocation, KEYSTORE_TYPE keystoreType, String password, String aliasToFind, String keyPassword) throws ConfigurationException, GeneralSecurityException, IOException { String ksLocation; // Check that varibale holding key store location gets non-empty value if (keystoreLocation != null && !keystoreLocation.isEmpty()) { ksLocation = keystoreLocation; } else if (SecurityHelper.getKeyStoreLocation() != null && !SecurityHelper.getKeyStoreLocation().isEmpty()) { ksLocation = SecurityHelper.getKeyStoreLocation(); } else { throw new ConfigurationException("KeyStore location is not given."); } // Check that key store type is not null if (keystoreType == null) { throw new ConfigurationException( "Invalid KeyStore type : " + keystoreType); } FileInputStream fis = new FileInputStream(ksLocation); String alg = KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory kmFact = KeyManagerFactory.getInstance(alg); char[] charPassword = password.toCharArray(); KeyStore ks = KeyStore.getInstance(keystoreType.name()); ks.load(fis, charPassword); fis.close(); // --- Enumerate keys and try to retrieve then with given password List<String> listKeysWithWrongPassword = new ArrayList<String>(); boolean aliasToFindIsFound = false; Enumeration<String> enumeration = ks.aliases(); while (enumeration.hasMoreElements()) { String alias = (String) enumeration.nextElement(); if (aliasToFind != null && alias.equalsIgnoreCase(aliasToFind)) aliasToFindIsFound = true; try { logger.debug(MessageFormat.format("Trying alias:{0}", alias)); Key key = ks.getKey(alias, keyPassword.toCharArray()); } catch (Exception e) { if (aliasToFind != null && alias.equalsIgnoreCase(aliasToFind)) listKeysWithWrongPassword.add(alias); else if (aliasToFind == null) listKeysWithWrongPassword.add(alias); } } // Throw exception if aliasToFind was not inside keystore if (aliasToFind != null && !aliasToFindIsFound) throw new ConfigurationException(MessageFormat.format( "Keystore does not contain alias={0}", aliasToFind)); // Throw exception if wrong password found if (!listKeysWithWrongPassword.isEmpty()) { String strListKeysWithWrongPassword = StringUtils .join(listKeysWithWrongPassword, ","); throw new UnrecoverableKeyException(MessageFormat.format( "Incorrect password for following keys:{0}", strListKeysWithWrongPassword)); } } /** * Does a random sleep on a failed login attempt. Sleep duration depends on * parameters defined in security.properties * * @param authenticationInfo * @throws ConfigurationException */ public static void doRandomSleepOnFailedLoginAttempt( AuthenticationInfo authenticationInfo) { try { // Load security properties if (authenticationInfo == null) { authenticationInfo = SecurityHelper .loadAuthenticationInformation(); } // Generate a random number between // security.randomWaitOnFailedLogin.min and // security.randomWaitOnFailedLogin.max int min = authenticationInfo.getMinWaitOnFailedLogin(); int max = authenticationInfo.getMaxWaitOnFailedLogin(); int increment = authenticationInfo .getIncrementStepWaitOnFailedLogin(); int randomNum = SecurityHelper.getRandomInt(min, max, increment); // Sleep a random number of seconds = between min and max logger.info(MessageFormat.format( "Invalid credentials. Sleeping (ms): {0,number,#}", randomNum)); if (randomNum > 0) Thread.sleep(randomNum); } catch (InterruptedException e) { logger.error(MessageFormat.format("Could not sleep !: {0}", e)); } catch (ConfigurationException e) { logger.error(MessageFormat.format( "Could not get security information. Will use default values: {0}", e)); } } /** * Returns a psuedo-random number between min and max, inclusive. The * difference between min and max can be at most * <code>Integer.MAX_VALUE - 1</code>. * * @param min Minimim value * @param max Maximim value. Must be greater than min. * @return Integer between min and max, inclusive. * @see java.util.Random#nextInt(int) */ public static int getRandomInt(int min, int max, int incrementStep) { // Checks and validation if (min < 0) min = 0; if (max < 0) max = 0; if (max < min) { // Invert max and min logger.warn(MessageFormat.format( "{0} and {1} are inverted. You must specify values so that min <= max", SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_MIN, SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_MAX)); int tmpMin = max; min = max; max = tmpMin; } if (incrementStep <= 0) incrementStep = 1; if (incrementStep >= max - min && (max - min) > 0) { logger.warn(MessageFormat.format( "{0} is not coherent. {0} >= {1}-{2}", SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_INCREMENT_STEP, SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_MIN, SecurityConf.SECURITY_RANDOM_WAIT_ON_FAILED_LOGIN_MAX)); incrementStep = 1; } if (max - min == 0) return max; // Usually this can be a field rather than a method variable Random rand = new Random(); // nextInt is normally exclusive of the top value, // so add 1 to make it inclusive // int randomNum = rand.nextInt((max - min) + 1) + min; Integer range = max - min; int maxMultiplier = Double.valueOf(Math.floor(range / incrementStep)) .intValue(); int randomMultiplier = rand.nextInt(maxMultiplier); int randomNumberWithIncrement = min + (randomMultiplier * incrementStep); return randomNumberWithIncrement; } public static void testSetSecurityProperties(AuthenticationInfo authInfo, boolean verbose) throws ConfigurationException { setSecurityProperties(authInfo, verbose); } }