From d3ee0e2f3e663733010eadf701819d44b6090c4c Mon Sep 17 00:00:00 2001 From: Jay Kline Date: Fri, 30 Nov 2007 19:40:31 +0000 Subject: [PATCH] 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 --- .../smack/ConnectionConfiguration.java | 25 ++-- .../jivesoftware/smack/XMPPConnection.java | 109 +++++++++++++++--- 2 files changed, 109 insertions(+), 25 deletions(-) diff --git a/source/org/jivesoftware/smack/ConnectionConfiguration.java b/source/org/jivesoftware/smack/ConnectionConfiguration.java index 3321e4edf..8a8f3891d 100644 --- a/source/org/jivesoftware/smack/ConnectionConfiguration.java +++ b/source/org/jivesoftware/smack/ConnectionConfiguration.java @@ -46,7 +46,7 @@ public class ConnectionConfiguration implements Cloneable { private String truststorePassword; private String keystorePath; private String keystoreType; - private String keystorePassword; + private String pkcsConfig; private boolean verifyChainEnabled = false; private boolean verifyRootCAEnabled = false; private boolean selfSignedCertificateEnabled = false; @@ -133,7 +133,7 @@ public class ConnectionConfiguration implements Cloneable { truststorePassword = "changeit"; keystorePath = System.getProperty("javax.net.ssl.keyStore"); keystoreType = "jks"; - keystorePassword = "changeit"; + pkcsConfig = "pkcs11.config"; } /** @@ -286,24 +286,25 @@ public class ConnectionConfiguration implements Cloneable { this.keystoreType = keystoreType; } + /** - * Returns the password to use to access the keystore file. It is assumed that all - * certificates share the same password in the keystore. + * Returns the PKCS11 configuration file location, needed when the + * 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() { - return keystorePassword; + public String getPKCSConfig() { + return pkcsConfig; } /** - * Sets the password to use to access the keystore file. It is assumed that all - * certificates share the same password in the trust store. + * Sets the PKCS11 configuration file location, needed when the + * 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) { - this.keystorePassword = keystorePassword; + public void setPKCSConfig(String pkcsConfig) { + this.pkcsConfig = pkcsConfig; } /** diff --git a/source/org/jivesoftware/smack/XMPPConnection.java b/source/org/jivesoftware/smack/XMPPConnection.java index 61bce66a5..33b132630 100644 --- a/source/org/jivesoftware/smack/XMPPConnection.java +++ b/source/org/jivesoftware/smack/XMPPConnection.java @@ -30,6 +30,8 @@ import org.jivesoftware.smack.util.StringUtils; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManager; import java.io.*; @@ -43,6 +45,8 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; 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 @@ -101,6 +105,9 @@ public class XMPPConnection { // connection ID, which is a value sent by the server once a connection is made. private static AtomicInteger connectionCounter = new AtomicInteger(0); + // CallbackHandler to handle prompting for theh keystore password. + private CallbackHandler callbackHandler = null; + static { // Use try block since we may not have permission to get a system // property (for example, when an applet). @@ -172,6 +179,7 @@ public class XMPPConnection { private ConnectionConfiguration configuration; private ChatManager chatManager; + /** * 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 @@ -185,7 +193,30 @@ public class XMPPConnection { * {@link #XMPPConnection(ConnectionConfiguration)} constructor.

*

* Note that XMPPConnection constructors do not establish a connection to the server - * and you must call {@link #connect()}. + * and you must call {@link #connect()}.

+ *

+ * 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. example.com. + * @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. example.com. */ @@ -196,6 +227,21 @@ public class XMPPConnection { config.setSASLAuthenticationEnabled(true); config.setDebuggerEnabled(DEBUG_ENABLED); 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.

*

* Note that XMPPConnection constructors do not establish a connection to the server - * and you must call {@link #connect()}. + * and you must call {@link #connect()}.

+ *

+ * + * 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 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.callbackHandler = callbackHandler; } + /** * 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 @@ -1249,17 +1303,46 @@ public class XMPPConnection { */ void proceedTLSReceived() throws Exception { SSLContext context = SSLContext.getInstance("TLS"); - KeyStore ks = KeyStore.getInstance(configuration.getKeystoreType()); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + KeyStore ks; KeyManager[] kms = null; - try { - ks.load(new FileInputStream(configuration.getKeystorePath()), configuration.getKeystorePassword().toCharArray()); - kmf.init(ks,configuration.getKeystorePassword().toCharArray()); - kms = kmf.getKeyManagers(); - } catch (FileNotFoundException fourohfour) { - kms = null; - } catch (NullPointerException npe) { - kms = null; + + if(callbackHandler == null) { + ks = null; + } else { + PasswordCallback pcb; + System.out.println("Keystore type: "+configuration.getKeystoreType()); + if(configuration.getKeystoreType().equals("PKCS11")) { + 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