/*
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Dave Locke - initial API and implementation and/or initial documentation
*/
package org.eclipse.paho.client.mqttv3.internal.security;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.eclipse.paho.client.mqttv3.internal.comms.MqttDirectException;
import org.eclipse.paho.client.mqttv3.internal.comms.MqttSSLInitException;
import org.eclipse.paho.client.mqttv3.internal.logging.Logger;
/**
* An SSLSocketFactoryFactory provides a socket factory and a server socket
* factory that then can be used to create SSL client sockets or SSL server
* sockets.
* <p>
* The SSLSocketFactoryFactory is configured using IBM SSL properties, i.e.
* properties of the format "com.ibm.ssl.propertyName", e.g.
* "com.ibm.ssl.keyStore". The class supports multiple configurations, each
* configuration is identified using a name or configuration ID. The
* configuration ID with "null" is used as a default configuration. When a
* socket factory is being created for a given configuration, properties of that
* configuration are first picked. If a property is not defined there, then that
* property is looked up in the default configuration. Finally, if a property
* element is still not found, then the corresponding system property is
* inspected, i.e. javax.net.ssl.keyStore. If the system property is not set
* either, then the system's default value is used (if available) or an
* exception is thrown.
* <p>
* The SSLSocketFacotryFactory can be reconfigured at any time. A
* reconfiguration does not affect existing socket factories.
* <p>
* All properties share the same key space; i.e. the configuration ID is not
* part of the property keys.
* <p>
* The methods should be called in the following order:
* <ol>
* <li><b>isSupportedOnJVM()</b>: to check whether this class is supported on
* the runtime platform. Not all runtimes support SSL/TLS.</li>
* <li><b>SSLSocketFactoryFactory()</b>: the constructor. Clients
* (in the same JVM) may share an SSLSocketFactoryFactory, or have one each.</li>
* <li><b>initialize(properties, configID)</b>: to initialize this object with
* the required SSL properties for a configuration. This may be called multiple
* times, once for each required configuration (e.g. once per Listener, once per
* Pipe, once per Client). It may be called again to change the required SSL
* properties for a particular configuration</li>
* <li><b>getEnabledCipherSuites(configID)</b>: to later set the enabled
* cipher suites on the socket [see below].</li>
* </ol>
* <ul>
* <li><i>For an MQTT server:</i></li>
* <ol>
* <li><b>getKeyStore(configID)</b>: Optionally, to check that if there is no
* keystore, then that all the enabled cipher suits are anonymous.</li>
* <li><b>createServerSocketFactory(configID)</b>: to create an
* SSLServerSocketFactory.</li>
* <li><b>getClientAuthentication(configID)</b>: to later set on the
* SSLServerSocket (itself created from the SSLServerSocketFactory) whether
* client authentication is needed.</li>
* </ol>
* <li><i>For an MQTT client:</i></li>
* <ol>
* <li><b>createSocketFactory(configID)</b>: to create an SSLSocketFactory.</li>
* </ol>
* </ul>
*/
public class SSLSocketFactoryFactory {
private final static String CLASS_NAME = "org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory";
/**
* Property keys specific to the client).
*/
public final static String SSLPROTOCOL="com.ibm.ssl.protocol";
public final static String JSSEPROVIDER="com.ibm.ssl.contextProvider";
public final static String KEYSTORE="com.ibm.ssl.keyStore";
public final static String KEYSTOREPWD="com.ibm.ssl.keyStorePassword";
public final static String KEYSTORETYPE="com.ibm.ssl.keyStoreType";
public final static String KEYSTOREPROVIDER="com.ibm.ssl.keyStoreProvider";
public final static String KEYSTOREMGR="com.ibm.ssl.keyManager";
public final static String TRUSTSTORE="com.ibm.ssl.trustStore";
public final static String TRUSTSTOREPWD="com.ibm.ssl.trustStorePassword";
public final static String TRUSTSTORETYPE="com.ibm.ssl.trustStoreType";
public final static String TRUSTSTOREPROVIDER="com.ibm.ssl.trustStoreProvider";
public final static String TRUSTSTOREMGR="com.ibm.ssl.trustManager";
public final static String CIPHERSUITES="com.ibm.ssl.enabledCipherSuites";
public final static String CLIENTAUTH="com.ibm.ssl.clientAuthentication";
/**
* Property keys used for java system properties
*/
public final static String SYSKEYSTORE="javax.net.ssl.keyStore";
public final static String SYSKEYSTORETYPE="javax.net.ssl.keyStoreType";
public final static String SYSKEYSTOREPWD="javax.net.ssl.keyStorePassword";
public final static String SYSTRUSTSTORE="javax.net.ssl.trustStore";
public final static String SYSTRUSTSTORETYPE="javax.net.ssl.trustStoreType";
public final static String SYSTRUSTSTOREPWD="javax.net.ssl.trustStorePassword";
public final static String SYSKEYMGRALGO="ssl.KeyManagerFactory.algorithm";
public final static String SYSTRUSTMGRALGO="ssl.TrustManagerFactory.algorithm";
public final static String DEFAULT_PROTOCOL = "TLS"; // "SSL_TLS" is not supported by DesktopEE
private final static String propertyKeys[] = { SSLPROTOCOL, JSSEPROVIDER,
KEYSTORE, KEYSTOREPWD, KEYSTORETYPE, KEYSTOREPROVIDER, KEYSTOREMGR,
TRUSTSTORE, TRUSTSTOREPWD, TRUSTSTORETYPE, TRUSTSTOREPROVIDER,
TRUSTSTOREMGR, CIPHERSUITES, CLIENTAUTH};
private Hashtable configs; // a hashtable that maps configIDs to properties.
private Properties defaultProperties;
private static final byte[] key = { (byte) 0x9d, (byte) 0xa7, (byte) 0xd9,
(byte) 0x80, (byte) 0x05, (byte) 0xb8, (byte) 0x89, (byte) 0x9c };
private static final String xorTag = "{xor}";
private Logger logger = null;
/**
* Not all of the JVM/Platforms support all of its
* security features. This method determines if is supported.
*
* @return whether dependent classes can be instantiated on the current
* JVM/platform.
*
* @throws Error
* if any unexpected error encountered whilst checking. Note
* this should not be a ClassNotFoundException, which should
* cause the method to return false.
*/
public static boolean isSupportedOnJVM() throws LinkageError, ExceptionInInitializerError {
String requiredClassname = "javax.net.ssl.SSLServerSocketFactory";
try {
Class.forName(requiredClassname);
} catch (ClassNotFoundException e) {
return false;
}
return true;
}
/**
* Create new instance of class.
* Constructor used by clients.
*/
public SSLSocketFactoryFactory() {
configs = new Hashtable();
}
/**
* Create new instance of class.
* Constructor used by the broker.
*/
public SSLSocketFactoryFactory(Logger logger) {
this();
this.logger = logger;
}
/**
* Checks whether a key belongs to the supported IBM SSL property keys.
*
* @param key
* @return whether a key belongs to the supported IBM SSL property keys.
*/
private boolean keyValid(String key) {
int i = 0;
while (i < propertyKeys.length) {
if (propertyKeys[i].equals(key)) {
break;
}
++i;
}
return i < propertyKeys.length;
}
/**
* Checks whether the property keys belong to the supported IBM SSL property
* key set.
*
* @param properties
* @throws IllegalArgumentException
* if any of the properties is not a valid IBM SSL property key.
*/
private void checkPropertyKeys(Properties properties)
throws IllegalArgumentException {
Set keys = properties.keySet();
Iterator i = keys.iterator();
while (i.hasNext()) {
String k = (String) i.next();
if (!keyValid(k)) {
throw new IllegalArgumentException(k + " is not a valid IBM SSL property key.");
}
}
}
/**
* Convert byte array to char array, where each char is constructed from two
* bytes.
*
* @param b
* byte array
* @return char array
*/
public static char[] toChar(byte[] b) {
if(b==null) return null;
char[] c= new char[b.length/2];
int i=0; int j=0;
while(i<b.length) {
c[j++] = (char) ((b[i++] & 0xFF) + ((b[i++] & 0xFF)<<8));
}
return c;
}
/**
* Convert char array to byte array, where each char is split into two
* bytes.
*
* @param c
* char array
* @return byte array
*/
public static byte[] toByte(char[] c) {
if(c==null) return null;
byte[] b=new byte[c.length*2];
int i=0; int j=0;
while(j<c.length) {
b[i++] = (byte) (c[j] & 0xFF);
b[i++] = (byte) ((c[j++] >> 8)& 0xFF);
}
return b;
}
/**
* Obfuscates the password using a simple and not very secure XOR mechanism.
* This should not be used for cryptographical purpose, it's a simple
* scrambler to obfuscate clear-text passwords.
*
* @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#deObfuscate
*
* @param password
* The password to be encrypted, as a char[] array.
* @return An obfuscated password as a String.
*/
public static String obfuscate(char[] password) {
if (password == null)
return null;
byte[] bytes = toByte(password);
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) ((bytes[i] ^ key[i % key.length]) & 0x00ff);
}
String encryptedValue = xorTag
+ new String(SimpleBase64Encoder.encode(bytes));
return encryptedValue;
}
/**
* The inverse operation of obfuscate: returns a cleartext password that was
* previously obfuscated using the XOR scrambler.
*
* @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#obfuscate
*
* @param ePassword
* An obfuscated password.
* @return An array of char, containing the clear text password.
*/
public static char[] deObfuscate(String ePassword) {
if (ePassword == null)
return null;
byte[] bytes = null;
try {
bytes = SimpleBase64Encoder.decode(ePassword.substring(xorTag
.length()));
} catch (Exception e) {
return null;
}
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) ((bytes[i] ^ key[i % key.length]) & 0x00ff);
}
return toChar(bytes);
}
/**
* Converts an array of ciphers into a single String.
*
* @param ciphers
* The array of cipher names.
* @return A string containing the name of the ciphers, separated by comma.
*/
public static String packCipherSuites(String[] ciphers) {
String cipherSet=null;
if (ciphers != null) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < ciphers.length; i++) {
buf.append(ciphers[i]);
if (i < ciphers.length - 1) {
buf.append(',');
}
}
cipherSet = buf.toString();
}
return cipherSet;
}
/**
* Inverse operation of packCipherSuites: converts a string of cipher names
* into an array of cipher names
*
* @param ciphers
* A list of ciphers, separated by comma.
* @return An array of string, each string containing a single cipher name.
*/
public static String[] unpackCipherSuites(String ciphers) {
// can't use split as split is not available on all java platforms.
if(ciphers==null) return null;
Vector c=new Vector();
int i=ciphers.indexOf(',');
int j=0;
// handle all commas.
while(i>-1) {
// add stuff before and up to (but not including) the comma.
c.add(ciphers.substring(j, i));
j=i+1; // skip the comma.
i=ciphers.indexOf(',',j);
}
// add last element after the comma or only element if no comma is present.
c.add(ciphers.substring(j));
String[] s = new String[c.size()];
c.toArray(s);
return s;
}
/**
* Obfuscate any key & trust store passwords within the given properties.
*
* @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#obfuscate
*
* @param p
* properties
*/
private void convertPassword(Properties p) {
String pw = p.getProperty(KEYSTOREPWD);
if (pw != null && !pw.startsWith(xorTag)) {
String epw = obfuscate(pw.toCharArray());
p.put(KEYSTOREPWD, epw);
}
pw = p.getProperty(TRUSTSTOREPWD);
if (pw != null && !pw.startsWith(xorTag)) {
String epw = obfuscate(pw.toCharArray());
p.put(TRUSTSTOREPWD, epw);
}
}
/**
* Returns the properties object for configuration configID or creates a new
* one if required.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return the properties object for configuration configID
*/
// private Properties getOrCreate(String configID) {
// Properties res = null;
// if (configID == null) {
// if (this.defaultProperties == null) {
// this.defaultProperties = new Properties();
// }
// res = this.defaultProperties;
// } else {
// res = (Properties) this.configs.get(configID);
// if (res == null) {
// res = new Properties();
// this.configs.put(configID, res);
// }
// }
// return res;
// }
/**
* Initializes the SSLSocketFactoryFactory with the provided properties for
* the provided configuration.
*
* This may be called multiple times, once for each required configuration
* (e.g. once per Listener, once per Pipe, once per Client). It may be
* called again to change the required SSL properties for a particular
* configuration
*
* @param props
* A properties object containing IBM SSL properties that are
* qualified by one or more configuration identifiers.
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @throws IllegalArgumentException
* if any of the properties is not a valid IBM SSL property key.
*/
public void initialize(Properties props, String configID)
throws IllegalArgumentException {
checkPropertyKeys(props);
// copy the properties.
Properties p = new Properties();
p.putAll(props);
convertPassword(p);
if (configID != null) {
this.configs.put(configID, p);
} else {
this.defaultProperties = p;
}
}
/**
* Merges the given IBM SSL properties into the existing configuration,
* overwriting existing properties. This method is used to selectively
* change properties for a given configuration. The method throws an
* IllegalArgumentException if any of the properties is not a valid IBM SSL
* property key.
*
* @param props
* A properties object containing IBM SSL properties
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @throws IllegalArgumentException
* if any of the properties is not a valid IBM SSL property key.
*/
public void merge(Properties props, String configID)
throws IllegalArgumentException {
checkPropertyKeys(props);
Properties p = this.defaultProperties;
if (configID == null) {
p = (Properties) this.configs.get(configID);
}
if (p == null) {
p = new Properties();
}
convertPassword(props);
p.putAll(props);
if (configID != null) {
this.configs.put(configID, p);
} else {
this.defaultProperties = p;
}
}
/**
* Remove the configuration of a given configuration identifier.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return true, if the configuation could be removed.
*/
public boolean remove(String configID) {
boolean res = false;
if (configID != null) {
res = this.configs.remove(configID) != null;
} else {
if(null != this.defaultProperties) {
res = true;
this.defaultProperties = null;
}
}
return res;
}
/**
* Returns the configuration of the SSLSocketFactoryFactory for a given
* configuration. Note that changes in the property are reflected in the
* SSLSocketFactoryFactory.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return A property object containing the current configuration of the
* SSLSocketFactoryFactory. Note that it could be null.
*/
public Properties getConfiguration(String configID) {
return (Properties) (configID == null ? this.defaultProperties
: this.configs.get(configID));
}
/**
* @return Returns the set of configuration IDs that exist in the SSLSocketFactoryFactory.
*/
// public String[] getConfigurationIDs() {
// Set s = this.configs.keySet();
// String[] configs = new String[s.size()];
// configs = (String[]) s.toArray(configs);
// return configs;
// }
/**
* If the value is not null, then put it in the properties object using the
* key. If the value is null, then remove the entry in the properties object
* with the key.
*
* @param p
* @param key
* @param value
*/
// private final void putOrRemove(Properties p, String key, String value) {
// if (value == null) {
// p.remove(key);
// } else {
// p.put(key, value);
// }
// }
/**
* Sets the SSL protocol variant. If protocol is NULL then an existing value
* will be removed.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param protocol
* One of SSL, SSLv3, TLS, TLSv1, SSL_TLS
*/
// public void setSSLProtocol(String configID, String protocol) {
// Properties p = getOrCreate(configID);
// putOrRemove(p, SSLPROTOCOL, protocol);
// }
/**
* Sets the JSSE context provider. If provider is null, then an existing
* value will be removed.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param provider
* The JSSE provider. For example "IBMJSSE2" or "SunJSSE".
*/
// public void setJSSEProvider(String configID, String provider) {
// Properties p = getOrCreate(configID);
// putOrRemove(p, JSSEPROVIDER, provider);
// }
/**
* Sets the filename of the keyStore object. A null value is ignored.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param keyStore
* A filename that points to a valid keystore.
*/
// public void setKeyStore(String configID, String keyStore) {
// if (keyStore == null)
// return;
// Properties p = getOrCreate(configID);
// putOrRemove(p, KEYSTORE, keyStore);
// }
/**
* Sets the password that is used for the keystore. The password must be
* provided in plain text, but it will be stored internally in a scrambled
* XOR format.
*
* @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#obfuscate
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param password
* The keystore password
*/
// public void setKeyStorePassword(String configID, char[] password) {
// if (password == null)
// return;
// Properties p = getOrCreate(configID);
// // convert password, using XOR-based scrambling.
// String ePasswd = obfuscate(password);
// for(int i=0;i<password.length;i++) {
// password[i]=' ';
// }
// putOrRemove(p, KEYSTOREPWD, ePasswd);
// }
/**
* Sets the keystore provider. The corresponding provider must be installed
* in the system. Example values: "IBMJCE" or "IBMJCEFIPS".
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param provider
* The name of a java cryptography extension
*/
// public void setKeyStoreProvider(String configID, String provider) {
// Properties p = getOrCreate(configID);
// putOrRemove(p, KEYSTOREPROVIDER, provider);
// }
/**
* Sets the keystore type. For example, PKCS12, JKS or JCEKS. The types that
* are supported depend on the keystore provider.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param type
* The keystore type
*/
// public void setKeyStoreType(String configID, String type) {
// Properties p = getOrCreate(configID);
// putOrRemove(p, KEYSTORETYPE, type);
// }
/**
* Sets a custom key manager and the algorithm that it uses. The keymanager
* is specified in the format "algorithm|provider", for example
* "IbmX509|IBMJSSE2". The provider might be empty, in which case the
* default provider is configured with the specified algorithm. The key
* manager must implement the javax.net.ssl.X509KeyManager interface.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param keymanager
* An algorithm, provider pair as secified above.
*/
// public void setCustomKeyManager(String configID, String keymanager) {
// Properties p = getOrCreate(configID);
// putOrRemove(p, CUSTOMKEYMGR, keymanager);
// }
/**
* Sets the filename of the truststore object.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param trustStore
* A filename that points to a valid truststore.
*/
// public void setTrustStore(String configID, String trustStore) {
// Properties p = getOrCreate(configID);
// putOrRemove(p, TRUSTSTORE, trustStore);
// }
/**
* Sets the password that is used for the truststore. The password must be
* provided in plain text, but it will be stored internally in a scrambled
* XOR format.
*
* @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#obfuscate
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param password
* The truststore password.
*/
// public void setTrustStorePassword(String configID, char[] password) {
// Properties p = getOrCreate(configID);
// // convert password, using XOR-based scrambling.
// String ePasswd = obfuscate(password);
// for(int i=0;i<password.length;i++) {
// password[i]=' ';
// }
// putOrRemove(p, TRUSTSTOREPWD, ePasswd);
// }
/**
* Sets the truststore provider. The corresponding provider must be
* installed in the system. Example values: "IBMJCE" or "IBMJCEFIPS".
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param provider
* The name of a java cryptography extension.
*/
// public void setTrustStoreProvider(String configID, String provider) {
// Properties p = getOrCreate(configID);
// putOrRemove(p, TRUSTSTOREPROVIDER, provider);
// }
/**
* Sets the truststore type. For example, PKCS12, JKS or JCEKS. The types
* that are supported depend on the truststore provider.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param type
* The truststore type.
*/
// public void setTrustStoreType(String configID, String type) {
// Properties p = getOrCreate(configID);
// putOrRemove(p, TRUSTSTORETYPE, type);
// }
/**
* Sets a custom trust managers and the algorithm that it uses. The
* trustmanager is specified in the format "algorithm|provider", for example
* "IbmX509|IBMJSSE2". The provider might be empty, in which case the
* default provider is configured with the specified algorithm. The trust
* manager must implement the javax.net.ssl.X509TrustManager interface.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param trustmanager
* An algorithm, provider pair as secified above.
*/
// public void setCustomTrustManager(String configID, String trustmanager) {
// Properties p = getOrCreate(configID);
// putOrRemove(p, CUSTOMTRUSTMGR, trustmanager);
// }
/**
* Sets the list of enabled ciphers. For a list of acceptable values, see
* the documentation of the underlying JSSE.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param ciphers
* An array of cipher suite names such as
* SSL_RSA_WITH_AES_128_CBC_SHA.
*/
// public void setEnabledCipherSuites(String configID, String[] ciphers) {
// if (ciphers == null)
// return;
// Properties p = getOrCreate(configID);
// String cipherSet = packCipherSuites(ciphers);
// putOrRemove(p, CIPHERSUITES, cipherSet);
// }
/**
* Specifies whether the client is required to provide a valid certificate
* to the client during SSL negotiation.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param clientAuth
* true, if clients are required to authenticate, false
* otherwise.
*/
// public void setClientAuthentication(String configID, boolean clientAuth) {
// Properties p = getOrCreate(configID);
// p.put(CLIENTAUTH, Boolean.toString(clientAuth));
// }
/**
* Returns the property of a given key or null if it doesn't exist. It first
* scans the indicated configuration, then the default configuration, then
* the system properties.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param ibmKey
* @param sysProperty
* The key for the System property.
* @return the property of a given key or null if it doesn't exist.
*/
private String getProperty(String configID, String ibmKey, String sysProperty) {
String res = null;
res = getPropertyFromConfig(configID, ibmKey);
if ( res != null ) {
return res;
}
// scan system property, if it exists.
if (sysProperty != null) {
res = System.getProperty(sysProperty);
}
return res;
}
/**
* Returns the property of a given key or null if it doesn't exist. It first
* scans the indicated configuration, then the default configuration
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @param ibmKey
* @return the property of a given key or null if it doesn't exist. It first
* scans the indicated configuration, then the default configuration
*/
private String getPropertyFromConfig(String configID, String ibmKey) {
String res = null;
Properties p =null;
if(configID!=null) {;
p = (Properties) configs.get(configID);
}
if (p != null) {
res = p.getProperty(ibmKey);
if (res != null)
return res;
}
// not found in config. try default properties.
p = (Properties) this.defaultProperties;
if (p != null) {
res = p.getProperty(ibmKey);
if (res != null)
return res;
}
return res;
}
/**
* Gets the SSL protocol variant of the indicated configuration or the
* default configuration.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The SSL protocol variant.
*/
public String getSSLProtocol(String configID) {
return getProperty(configID, SSLPROTOCOL, null);
}
/**
* Gets the JSSE provider of the indicated configuration
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The JSSE provider.
*/
public String getJSSEProvider(String configID) {
return getProperty(configID, JSSEPROVIDER, null);
}
/**
* Get the XPD Keystore if running on the XPD platform (otherwise null).
*
* @return the XPD Keystore if running on the XPD platform (otherwise null).
* @throws MqttDirectException
*/
private KeyStore getXPDKeystore() throws MqttDirectException {
KeyStore keyStore = null;
try {
Class secPlatClass = Class.forName("com.ibm.rcp.security.auth.SecurePlatform");
Method m = secPlatClass.getMethod("getKeyStore", null);
Object secPlat = m.invoke(null,null); // getKeyStore is static
m = secPlatClass.getMethod("isLoggedIn", null);
Boolean b = (Boolean) m.invoke(secPlat, null);
if (b.booleanValue()) {
// login to secure platform was done.
m = secPlatClass.getMethod("getKeyStore", null);
keyStore = (KeyStore) m.invoke(secPlat, null);
}
} catch (ClassNotFoundException e) {
/*
* DEVELOPER NOTE: This is not an error. This means that we are not
* running on XPD runtime and therefore we can not get XPD keystore.
* [Next step for the caller, is try to get the keystore from System
* properties (see getKeyStore() method).]
*/
} catch (IllegalAccessException e) {
Object[] inserts = { e.getLocalizedMessage() };
throw new MqttSSLInitException(3026, inserts, e);
} catch (SecurityException e) {
Object[] inserts = { e.getLocalizedMessage() };
throw new MqttSSLInitException(3026, inserts, e);
} catch (NoSuchMethodException e) {
Object[] inserts = { e.getLocalizedMessage() };
throw new MqttSSLInitException(3026, inserts, e);
} catch (IllegalArgumentException e) {
Object[] inserts = { e.getLocalizedMessage() };
throw new MqttSSLInitException(3026, inserts, e);
} catch (InvocationTargetException e) {
Object[] inserts = { e.getLocalizedMessage() };
throw new MqttSSLInitException(3026, inserts, e);
}
return keyStore;
}
/**
* Gets the name of the keystore file that is used.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The name of the file that contains the keystore.
*/
public String getKeyStore(String configID) throws MqttDirectException {
String ibmKey = KEYSTORE;
String sysProperty = SYSKEYSTORE;
String res = null;
res = getPropertyFromConfig(configID, ibmKey);
if ( res != null ) {
return res;
}
// check for the XPD keystore here
if ( ibmKey != null && ibmKey.equals(KEYSTORE) ) {
KeyStore keyStore = getXPDKeystore();
if (keyStore != null)
return res = "Lotus Expeditor";
}
// scan system property, if it exists.
if (sysProperty != null) {
res = System.getProperty(sysProperty);
}
return res;
}
/**
* Gets the plain-text password that is used for the keystore.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The password in plain text.
*/
public char[] getKeyStorePassword(String configID) {
String pw = getProperty(configID, KEYSTOREPWD, SYSKEYSTOREPWD);
char[] r=null;
if (pw!=null) {
if (pw.startsWith(xorTag)) {
r = deObfuscate(pw);
} else {
r = pw.toCharArray();
}
}
return r;
}
/**
* Gets the type of keystore.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The keystore type.
*/
public String getKeyStoreType(String configID) {
return getProperty(configID, KEYSTORETYPE, SYSKEYSTORETYPE);
}
/**
* Gets the keystore provider.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The name of the keystore provider.
*/
public String getKeyStoreProvider(String configID) {
return getProperty(configID, KEYSTOREPROVIDER, null);
}
/**
* Gets the key manager algorithm that is used.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The key manager algorithm.
*/
public String getKeyManager(String configID) {
return getProperty(configID, KEYSTOREMGR, SYSKEYMGRALGO);
}
/**
* Gets the name of the truststore file that is used.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The name of the file that contains the truststore.
*/
public String getTrustStore(String configID) {
return getProperty(configID, TRUSTSTORE, SYSTRUSTSTORE);
}
/**
* Gets the plain-text password that is used for the truststore.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The password in plain text.
*/
public char[] getTrustStorePassword(String configID) {
String pw = getProperty(configID, TRUSTSTOREPWD, SYSTRUSTSTOREPWD);
char[] r=null;
if (pw!=null) {
if(pw.startsWith(xorTag)) {
r = deObfuscate(pw);
} else {
r = pw.toCharArray();
}
}
return r;
}
/**
* Gets the type of truststore.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The truststore type.
*/
public String getTrustStoreType(String configID) {
return getProperty(configID, TRUSTSTORETYPE, null);
}
/**
* Gets the truststore provider.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The name of the truststore provider.
*/
public String getTrustStoreProvider(String configID) {
return getProperty(configID, TRUSTSTOREPROVIDER, null);
}
/**
* Gets the trust manager algorithm that is used.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return The trust manager algorithm.
*/
public String getTrustManager(String configID) {
return getProperty(configID, TRUSTSTOREMGR, SYSTRUSTMGRALGO);
}
/**
* Returns an array with the enabled ciphers.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return an array with the enabled ciphers
*/
public String[] getEnabledCipherSuites(String configID) {
String ciphers = getProperty(configID, CIPHERSUITES, null);
String[] res = unpackCipherSuites(ciphers);
return res;
}
/**
* Returns whether client authentication is required.
*
* @param configID
* The configuration identifier for selecting a configuration or
* null for the default configuration.
* @return true, if clients are required to authenticate, false otherwise.
*/
public boolean getClientAuthentication(String configID) {
String auth = getProperty(configID, CLIENTAUTH, null);
boolean res = false;
if (auth != null) {
res = Boolean.valueOf(auth).booleanValue();
}
return res;
}
/**
* Initializes key- and truststore. Returns an SSL context factory. If no
* SSLProtocol is already set, uses DEFAULT_PROTOCOL
*
* @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#DEFAULT_PROTOCOL
*
* @param configID
* The configuration ID
* @return An SSL context factory.
* @throws MqttDirectException
*/
private SSLContext getSSLContext(String configID)
throws MqttDirectException {
final String METHOD_NAME = "getSSLContext";
SSLContext ctx = null;
String protocol = getSSLProtocol(configID);
if (protocol == null) {
protocol = DEFAULT_PROTOCOL;
}
if (logger != null) {
// 12000 "SSL initialization: configID = {0}, protocol = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12000", new Object[] {configID!=null ? configID : "null (broker defaults)",
protocol});
}
String provider = getJSSEProvider(configID);
try {
if (provider == null) {
ctx = SSLContext.getInstance(protocol);
} else {
ctx = SSLContext.getInstance(protocol, provider);
}
if (logger != null) {
// 12001 "SSL initialization: configID = {0}, provider = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12001", new Object[] {configID!=null ? configID : "null (broker defaults)",
ctx.getProvider().getName()});
}
String keyStoreName = getProperty(configID, KEYSTORE, null);
KeyStore keyStore=null;
KeyManagerFactory keyMgrFact=null;
KeyManager[] keyMgr=null;
if(keyStoreName==null) {
// try to instantiate XPD keyStore.
keyStore=getXPDKeystore();
if (logger != null) {
if (keyStore == null) {
// 12002 "SSL initialization: configID = {0}, XPD keystore not available"
logger.fine(CLASS_NAME, METHOD_NAME, "12002", new Object[]{configID!=null ? configID : "null (broker defaults)"});
} else {
// 12003 "SSL initialization: configID = {0}, XPD keystore available"
logger.fine(CLASS_NAME, METHOD_NAME, "12003", new Object[]{configID!=null ? configID : "null (broker defaults)"});
}
}
}
if(keyStore==null) {
if(keyStoreName==null) {
/*
* No keystore in config, XPD keystore not available. Try to
* get config from system properties.
*/
keyStoreName = getProperty(configID, KEYSTORE, SYSKEYSTORE);
}
if (logger != null) {
// 12004 "SSL initialization: configID = {0}, keystore = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12004", new Object[]{configID!=null ? configID : "null (broker defaults)",
keyStoreName!=null ? keyStoreName : "null"});
}
char[] keyStorePwd=getKeyStorePassword(configID);
if (logger != null) {
// 12005 "SSL initialization: configID = {0}, keystore password = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12005", new Object[]{configID!=null ? configID : "null (broker defaults)",
keyStorePwd!=null ? obfuscate(keyStorePwd) : "null"});
}
String keyStoreType=getKeyStoreType(configID);
if(keyStoreType==null) {
keyStoreType = KeyStore.getDefaultType();
}
if (logger != null) {
// 12006 "SSL initialization: configID = {0}, keystore type = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12006", new Object[]{configID!=null ? configID : "null (broker defaults)",
keyStoreType!=null ? keyStoreType : "null"});
}
String keyMgrAlgo = KeyManagerFactory.getDefaultAlgorithm();
String keyMgrProvider = getKeyStoreProvider(configID);
String keyManager = getKeyManager(configID);
if (keyManager != null) {
keyMgrAlgo = keyManager;
}
if(keyStoreName!=null && keyStoreType!=null && keyStorePwd!=null && keyMgrAlgo!=null) {
try {
keyStore=KeyStore.getInstance(keyStoreType);
keyStore.load(new FileInputStream(keyStoreName), keyStorePwd);
if(keyMgrProvider!=null) {
keyMgrFact = KeyManagerFactory.getInstance(keyMgrAlgo, keyMgrProvider);
} else {
keyMgrFact = KeyManagerFactory.getInstance(keyMgrAlgo);
}
if (logger != null) {
// 12010 "SSL initialization: configID = {0}, keystore manager algorithm = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12010", new Object[]{configID!=null ? configID : "null (broker defaults)",
keyMgrAlgo!=null ? keyMgrAlgo : "null"});
// 12009 "SSL initialization: configID = {0}, keystore manager provider = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12009", new Object[]{configID!=null ? configID : "null (broker defaults)",
keyMgrFact.getProvider().getName()});
}
keyMgrFact.init(keyStore, keyStorePwd);
keyMgr=keyMgrFact.getKeyManagers();
} catch (KeyStoreException e) {
throw new MqttSSLInitException(3016, null, e);
} catch (CertificateException e) {
throw new MqttSSLInitException(3019, null, e);
} catch (FileNotFoundException e) {
throw new MqttSSLInitException(3020, null, e);
} catch (IOException e) {
throw new MqttSSLInitException(3021, null, e);
} catch (UnrecoverableKeyException e) {
throw new MqttSSLInitException(3022, null, e);
}
}
}
// keystore loaded, keymanagers instantiated if possible
// now the same for the truststore.
String trustStoreName = getTrustStore(configID);
if (logger != null) {
// 12011 "SSL initialization: configID = {0}, truststore = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12011", new Object[]{configID!=null ? configID : "null (broker defaults)",
trustStoreName!=null ? trustStoreName : "null"});
}
KeyStore trustStore=null;
TrustManagerFactory trustMgrFact=null;
TrustManager[] trustMgr=null;
char[] trustStorePwd=getTrustStorePassword(configID);
if (logger != null) {
// 12012 "SSL initialization: configID = {0}, truststore password = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12012", new Object[]{configID!=null ? configID : "null (broker defaults)",
trustStorePwd!=null ? obfuscate(trustStorePwd) : "null"});
}
String trustStoreType=getTrustStoreType(configID);
if(trustStoreType==null) {
trustStoreType = KeyStore.getDefaultType();
}
if (logger != null) {
// 12013 "SSL initialization: configID = {0}, truststore type = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12013", new Object[]{configID!=null ? configID : "null (broker defaults)",
trustStoreType!=null ? trustStoreType : "null"});
}
String trustMgrAlgo = TrustManagerFactory.getDefaultAlgorithm();
String trustMgrProvider = getTrustStoreProvider(configID);
String trustManager = getTrustManager(configID);
if (trustManager != null) {
trustMgrAlgo = trustManager;
}
if(trustStoreName!=null && trustStoreType!=null && trustStorePwd!=null && trustMgrAlgo!=null) {
try {
trustStore=KeyStore.getInstance(trustStoreType);
trustStore.load(new FileInputStream(trustStoreName), trustStorePwd);
if(trustMgrProvider!=null) {
trustMgrFact = TrustManagerFactory.getInstance(trustMgrAlgo, trustMgrProvider);
} else {
trustMgrFact = TrustManagerFactory.getInstance(trustMgrAlgo);
}
if (logger != null) {
// 12017 "SSL initialization: configID = {0}, truststore manager algorithm = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12017", new Object[]{configID!=null ? configID : "null (broker defaults)",
trustMgrAlgo!=null ? trustMgrAlgo : "null"});
// 12016 "SSL initialization: configID = {0}, truststore manager provider = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12016", new Object[]{configID!=null ? configID : "null (broker defaults)",
trustMgrFact.getProvider().getName()});
}
trustMgrFact.init(trustStore);
trustMgr=trustMgrFact.getTrustManagers();
} catch (KeyStoreException e) {
throw new MqttSSLInitException(3027, null, e);
} catch (CertificateException e) {
throw new MqttSSLInitException(3028, null, e);
} catch (FileNotFoundException e) {
throw new MqttSSLInitException(3029, null, e);
} catch (IOException e) {
throw new MqttSSLInitException(3030, null, e);
}
}
// done.
ctx.init(keyMgr, trustMgr, null);
} catch (NoSuchAlgorithmException e) {
throw new MqttSSLInitException(3018, null, e);
} catch (NoSuchProviderException e) {
throw new MqttSSLInitException(3023, null, e);
} catch (KeyManagementException e) {
throw new MqttSSLInitException(3024, null, e);
}
return ctx;
}
/**
* Returns an SSL server socket factory for the given configuration. If no
* SSLProtocol is already set, uses DEFAULT_PROTOCOL. Throws
* IllegalArgumentException if the server socket factory could not be
* created due to underlying configuration problems.
*
* @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#DEFAULT_PROTOCOL
*
* @param configID
* The configuration identifier for selecting a configuration.
* @return An SSLServerSocketFactory
* @throws MqttDirectException
*/
public SSLServerSocketFactory createServerSocketFactory(String configID)
throws MqttDirectException {
final String METHOD_NAME = "createServerSocketFactory";
SSLContext ctx = getSSLContext(configID);
if (logger != null) {
// 12018 "SSL initialization: configID = {0}, application-enabled cipher suites = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12018", new Object[]{configID!=null ? configID : "null (broker defaults)",
getEnabledCipherSuites(configID)!=null ? getProperty(configID, CIPHERSUITES, null) : "null (using platform-enabled cipher suites)"});
// 12019 "SSL initialization: configID = {0}, client authentication = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12019", new Object[]{configID!=null ? configID : "null (broker defaults)",
new Boolean (getClientAuthentication(configID)).toString()});
}
return ctx.getServerSocketFactory();
}
/**
* Returns an SSL socket factory for the given configuration. If no
* SSLProtocol is already set, uses DEFAULT_PROTOCOL. Throws
* IllegalArgumentException if the socket factory could not be created due
* to underlying configuration problems.
*
* @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#DEFAULT_PROTOCOL
* @param configID
* The configuration identifier for selecting a configuration.
* @return An SSLSocketFactory
* @throws MqttDirectException
*/
public SSLSocketFactory createSocketFactory(String configID)
throws MqttDirectException {
final String METHOD_NAME = "createSocketFactory";
SSLContext ctx = getSSLContext(configID);
if (logger != null) {
// 12020 "SSL initialization: configID = {0}, application-enabled cipher suites = {1}"
logger.fine(CLASS_NAME, METHOD_NAME, "12020", new Object[]{configID!=null ? configID : "null (broker defaults)",
getEnabledCipherSuites(configID)!=null ? getProperty(configID, CIPHERSUITES, null) : "null (using platform-enabled cipher suites)"});
}
return ctx.getSocketFactory();
}
}