Added support for key and trust stores when making SSL/TLS connections

that require the client authenticate itself.  This supports PKCS#11 devices.


git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@9538 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
Jay Kline 2007-11-30 19:40:31 +00:00 committed by jay
parent dbac260051
commit d3ee0e2f3e
2 changed files with 109 additions and 25 deletions

View File

@ -46,7 +46,7 @@ public class ConnectionConfiguration implements Cloneable {
private String truststorePassword; private String truststorePassword;
private String keystorePath; private String keystorePath;
private String keystoreType; private String keystoreType;
private String keystorePassword; private String pkcsConfig;
private boolean verifyChainEnabled = false; private boolean verifyChainEnabled = false;
private boolean verifyRootCAEnabled = false; private boolean verifyRootCAEnabled = false;
private boolean selfSignedCertificateEnabled = false; private boolean selfSignedCertificateEnabled = false;
@ -133,7 +133,7 @@ public class ConnectionConfiguration implements Cloneable {
truststorePassword = "changeit"; truststorePassword = "changeit";
keystorePath = System.getProperty("javax.net.ssl.keyStore"); keystorePath = System.getProperty("javax.net.ssl.keyStore");
keystoreType = "jks"; keystoreType = "jks";
keystorePassword = "changeit"; pkcsConfig = "pkcs11.config";
} }
/** /**
@ -286,24 +286,25 @@ public class ConnectionConfiguration implements Cloneable {
this.keystoreType = keystoreType; this.keystoreType = keystoreType;
} }
/** /**
* Returns the password to use to access the keystore file. It is assumed that all * Returns the PKCS11 configuration file location, needed when the
* certificates share the same password in the keystore. * Keystore type is PKCS11.
* *
* @return the password to use to access the keystore file. * @return the path to the PKCS11 configuration file
*/ */
public String getKeystorePassword() { public String getPKCSConfig() {
return keystorePassword; return pkcsConfig;
} }
/** /**
* Sets the password to use to access the keystore file. It is assumed that all * Sets the PKCS11 configuration file location, needed when the
* certificates share the same password in the trust store. * Keystore type is PKCS11
* *
* @param keystorePassword the password to use to access the keystore file. * @param pkcsConfig the path to the PKCS11 configuration file
*/ */
public void setKeystorePassword(String keystorePassword) { public void setPKCSConfig(String pkcsConfig) {
this.keystorePassword = keystorePassword; this.pkcsConfig = pkcsConfig;
} }
/** /**

View File

@ -30,6 +30,8 @@ import org.jivesoftware.smack.util.StringUtils;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import java.io.*; import java.io.*;
@ -43,6 +45,8 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.PasswordCallback;
/** /**
* Creates a connection to a XMPP server. A simple use of this API might * Creates a connection to a XMPP server. A simple use of this API might
@ -101,6 +105,9 @@ public class XMPPConnection {
// connection ID, which is a value sent by the server once a connection is made. // connection ID, which is a value sent by the server once a connection is made.
private static AtomicInteger connectionCounter = new AtomicInteger(0); private static AtomicInteger connectionCounter = new AtomicInteger(0);
// CallbackHandler to handle prompting for theh keystore password.
private CallbackHandler callbackHandler = null;
static { static {
// Use try block since we may not have permission to get a system // Use try block since we may not have permission to get a system
// property (for example, when an applet). // property (for example, when an applet).
@ -172,6 +179,7 @@ public class XMPPConnection {
private ConnectionConfiguration configuration; private ConnectionConfiguration configuration;
private ChatManager chatManager; private ChatManager chatManager;
/** /**
* Creates a new connection to the specified XMPP server. A DNS SRV lookup will be * Creates a new connection to the specified XMPP server. A DNS SRV lookup will be
* performed to determine the IP address and port corresponding to the * performed to determine the IP address and port corresponding to the
@ -185,7 +193,30 @@ public class XMPPConnection {
* {@link #XMPPConnection(ConnectionConfiguration)} constructor.<p> * {@link #XMPPConnection(ConnectionConfiguration)} constructor.<p>
* <p/> * <p/>
* Note that XMPPConnection constructors do not establish a connection to the server * Note that XMPPConnection constructors do not establish a connection to the server
* and you must call {@link #connect()}. * and you must call {@link #connect()}.<p>
* <p/>
* The CallbackHandler will only be used if the connection requires the client provide
* an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback
* to prompt for a password to unlock the keystore containing the SSL certificate.
*
* @param serviceName the name of the XMPP server to connect to; e.g. <tt>example.com</tt>.
* @param callbackHandler the CallbackHandler used to prompt for the password to the keystore.
*/
public XMPPConnection(String serviceName, CallbackHandler callbackHandler) {
// Create the configuration for this new connection
ConnectionConfiguration config = new ConnectionConfiguration(serviceName);
config.setCompressionEnabled(false);
config.setSASLAuthenticationEnabled(true);
config.setDebuggerEnabled(DEBUG_ENABLED);
this.configuration = config;
this.callbackHandler = callbackHandler;
}
/**
* Creates a new XMPP conection in the same way {@link #XMPPConnection(String,CallbackHandler)} does, but
* with no callback handler for password prompting of the keystore. This will work
* in most cases, provided the client is not required to provide a certificate to
* the server.
* *
* @param serviceName the name of the XMPP server to connect to; e.g. <tt>example.com</tt>. * @param serviceName the name of the XMPP server to connect to; e.g. <tt>example.com</tt>.
*/ */
@ -196,6 +227,21 @@ public class XMPPConnection {
config.setSASLAuthenticationEnabled(true); config.setSASLAuthenticationEnabled(true);
config.setDebuggerEnabled(DEBUG_ENABLED); config.setDebuggerEnabled(DEBUG_ENABLED);
this.configuration = config; this.configuration = config;
this.callbackHandler = null;
}
/**
* Creates a new XMPP conection in the same way {@link #XMPPConnection(ConnectionConfiguration,CallbackHandler)} does, but
* with no callback handler for password prompting of the keystore. This will work
* in most cases, provided the client is not required to provide a certificate to
* the server.
*
*
* @param config the connection configuration.
*/
public XMPPConnection(ConnectionConfiguration config) {
this.configuration = config;
this.callbackHandler = null;
} }
/** /**
@ -206,14 +252,22 @@ public class XMPPConnection {
* {@link #XMPPConnection(String)} constructor is a better approach.<p> * {@link #XMPPConnection(String)} constructor is a better approach.<p>
* <p/> * <p/>
* Note that XMPPConnection constructors do not establish a connection to the server * Note that XMPPConnection constructors do not establish a connection to the server
* and you must call {@link #connect()}. * and you must call {@link #connect()}.<p>
* <p/>
*
* The CallbackHandler will only be used if the connection requires the client provide
* an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback
* to prompt for a password to unlock the keystore containing the SSL certificate.
* *
* @param config the connection configuration. * @param config the connection configuration.
* @param callbackHandler the CallbackHandler used to prompt for the password to the keystore.
*/ */
public XMPPConnection(ConnectionConfiguration config) { public XMPPConnection(ConnectionConfiguration config, CallbackHandler callbackHandler) {
this.configuration = config; this.configuration = config;
this.callbackHandler = callbackHandler;
} }
/** /**
* Returns the connection ID for this connection, which is the value set by the server * Returns the connection ID for this connection, which is the value set by the server
* when opening a XMPP stream. If the server does not set a connection ID, this value * when opening a XMPP stream. If the server does not set a connection ID, this value
@ -1249,17 +1303,46 @@ public class XMPPConnection {
*/ */
void proceedTLSReceived() throws Exception { void proceedTLSReceived() throws Exception {
SSLContext context = SSLContext.getInstance("TLS"); SSLContext context = SSLContext.getInstance("TLS");
KeyStore ks = KeyStore.getInstance(configuration.getKeystoreType()); KeyStore ks;
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
KeyManager[] kms = null; KeyManager[] kms = null;
try {
ks.load(new FileInputStream(configuration.getKeystorePath()), configuration.getKeystorePassword().toCharArray()); if(callbackHandler == null) {
kmf.init(ks,configuration.getKeystorePassword().toCharArray()); ks = null;
kms = kmf.getKeyManagers(); } else {
} catch (FileNotFoundException fourohfour) { PasswordCallback pcb;
kms = null; System.out.println("Keystore type: "+configuration.getKeystoreType());
} catch (NullPointerException npe) { if(configuration.getKeystoreType().equals("PKCS11")) {
kms = null; Provider p = new sun.security.pkcs11.SunPKCS11(configuration.getPKCSConfig());
Security.addProvider(p);
ks = KeyStore.getInstance("PKCS11",p);
pcb = new PasswordCallback("PKCS11 Password: ",false);
callbackHandler.handle(new Callback[]{pcb});
ks.load(null,pcb.getPassword());
}
else if(configuration.getKeystoreType().equals("Apple")) {
ks = KeyStore.getInstance("KeychainStore","Apple");
ks.load(null,null);
//pcb = new PasswordCallback("Apple Keychain",false);
//pcb.setPassword(null);
}
else {
ks = KeyStore.getInstance(configuration.getKeystoreType());
pcb = new PasswordCallback("Keystore Password: ",false);
callbackHandler.handle(new Callback[]{pcb});
ks.load(new FileInputStream(configuration.getKeystorePath()), pcb.getPassword());
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
try {
if(pcb == null) {
kmf.init(ks,null);
} else {
kmf.init(ks,pcb.getPassword());
}
kms = kmf.getKeyManagers();
} catch (NullPointerException npe) {
kms = null;
}
pcb.clearPassword();
} }
// Verify certificate presented by the server // Verify certificate presented by the server