Commit 611b54ea authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Added support for TLS negotiation as a client. JM-395

git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@2858 b35dd754-fafc-0310-a699-88a17e54d16e
parent 60a3afc3
...@@ -26,7 +26,6 @@ import java.io.Writer; ...@@ -26,7 +26,6 @@ import java.io.Writer;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
/** /**
...@@ -43,7 +42,7 @@ public class SocketConnection implements Connection { ...@@ -43,7 +42,7 @@ public class SocketConnection implements Connection {
*/ */
public static final String CHARSET = "UTF-8"; public static final String CHARSET = "UTF-8";
private Map listeners = new HashMap(); private Map<ConnectionCloseListener, Object> listeners = new HashMap<ConnectionCloseListener, Object>();
private Socket socket; private Socket socket;
...@@ -96,12 +95,13 @@ public class SocketConnection implements Connection { ...@@ -96,12 +95,13 @@ public class SocketConnection implements Connection {
/** /**
* Secures the plain connection by negotiating TLS with the client. * Secures the plain connection by negotiating TLS with the client.
* *
* @param clientMode boolean indicating if this entity is a client or a server.
* @throws IOException if an error occured while securing the connection. * @throws IOException if an error occured while securing the connection.
*/ */
public void startTLS() throws IOException { public void startTLS(boolean clientMode) throws IOException {
if (!secure) { if (!secure) {
secure = true; secure = true;
tlsStreamHandler = new TLSStreamHandler(socket); tlsStreamHandler = new TLSStreamHandler(socket, clientMode);
writer = new BufferedWriter(new OutputStreamWriter(tlsStreamHandler.getOutputStream(), CHARSET)); writer = new BufferedWriter(new OutputStreamWriter(tlsStreamHandler.getOutputStream(), CHARSET));
xmlSerializer = new XMLSocketWriter(writer, socket); xmlSerializer = new XMLSocketWriter(writer, socket);
} }
...@@ -336,9 +336,7 @@ public class SocketConnection implements Connection { ...@@ -336,9 +336,7 @@ public class SocketConnection implements Connection {
*/ */
private void notifyCloseListeners() { private void notifyCloseListeners() {
synchronized (listeners) { synchronized (listeners) {
Iterator itr = listeners.keySet().iterator(); for (ConnectionCloseListener listener : listeners.keySet()) {
while (itr.hasNext()) {
ConnectionCloseListener listener = (ConnectionCloseListener)itr.next();
listener.onConnectionClose(listeners.get(listener)); listener.onConnectionClose(listeners.get(listener));
} }
} }
......
...@@ -77,11 +77,12 @@ public class TLSStreamHandler { ...@@ -77,11 +77,12 @@ public class TLSStreamHandler {
/** /**
* Creates a new TLSStreamHandler and secures the plain socket connection. * Creates a new TLSStreamHandler and secures the plain socket connection.
* *
* @param clientMode boolean indicating if this entity is a client or a server.
* @param socket the plain socket connection to secure * @param socket the plain socket connection to secure
* @throws IOException * @throws IOException
*/ */
public TLSStreamHandler(Socket socket) throws IOException { public TLSStreamHandler(Socket socket, boolean clientMode) throws IOException {
wrapper = new TLSWrapper(); wrapper = new TLSWrapper(clientMode);
tlsEngine = wrapper.getTlsEngine(); tlsEngine = wrapper.getTlsEngine();
reader = new TLSStreamReader(wrapper, socket); reader = new TLSStreamReader(wrapper, socket);
writer = new TLSStreamWriter(wrapper, socket); writer = new TLSStreamWriter(wrapper, socket);
...@@ -101,8 +102,12 @@ public class TLSStreamHandler { ...@@ -101,8 +102,12 @@ public class TLSStreamHandler {
appBB = ByteBuffer.allocate(appBBSize); appBB = ByteBuffer.allocate(appBBSize);
//socket.setSoTimeout(0); if (clientMode) {
//socket.setKeepAlive(true); socket.setSoTimeout(0);
socket.setKeepAlive(true);
initialHSStatus = HandshakeStatus.NEED_WRAP;
tlsEngine.beginHandshake();
}
while (!initialHSComplete) { while (!initialHSComplete) {
initialHSComplete = doHandshake(null); initialHSComplete = doHandshake(null);
......
...@@ -11,23 +11,18 @@ ...@@ -11,23 +11,18 @@
package org.jivesoftware.messenger.net; package org.jivesoftware.messenger.net;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import javax.net.ssl.*;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import org.jivesoftware.util.Log;
/** /**
* Creates and initializes the SSLContext instance to use to secure the plain connection. This * Creates and initializes the SSLContext instance to use to secure the plain connection. This
...@@ -38,243 +33,263 @@ import org.jivesoftware.util.Log; ...@@ -38,243 +33,263 @@ import org.jivesoftware.util.Log;
*/ */
public class TLSWrapper { public class TLSWrapper {
/* /*
* Enables logging of the SSLEngine operations. * Enables logging of the SSLEngine operations.
*/ */
private boolean logging = false; private boolean logging = false;
/* /*
* Enables the JSSE system debugging system property: * Enables the JSSE system debugging system property:
* *
* -Djavax.net.debug=all * -Djavax.net.debug=all
* *
* This gives a lot of low-level information about operations underway, including specific * This gives a lot of low-level information about operations underway, including specific
* handshake messages, and might be best examined after gaining some familiarity with this * handshake messages, and might be best examined after gaining some familiarity with this
* application. * application.
*/ */
private static boolean debug = false; private static boolean debug = false;
private static final String PROTOCOL = "TLS"; private static final String PROTOCOL = "TLS";
private SSLEngine tlsEngine; private SSLEngine tlsEngine;
private SSLEngineResult tlsEngineResult; private SSLEngineResult tlsEngineResult;
private int netBuffSize; private int netBuffSize;
private int appBuffSize; private int appBuffSize;
public TLSWrapper() { public TLSWrapper(boolean clientMode) {
if (debug) { if (debug) {
System.setProperty("javax.net.debug", "all"); System.setProperty("javax.net.debug", "all");
} }
// Create/initialize the SSLContext with key material // Create/initialize the SSLContext with key material
try { try {
// First initialize the key and trust material. // First initialize the key and trust material.
KeyStore ksKeys = SSLConfig.getKeyStore(); KeyStore ksKeys = SSLConfig.getKeyStore();
String keypass = SSLConfig.getKeyPassword(); String keypass = SSLConfig.getKeyPassword();
KeyStore ksTrust = SSLConfig.getTrustStore(); KeyStore ksTrust = SSLConfig.getTrustStore();
String trustpass = SSLConfig.getTrustPassword(); String trustpass = SSLConfig.getTrustPassword();
// KeyManager's decide which key material to use. // KeyManager's decide which key material to use.
KeyManager[] km = SSLJiveKeyManagerFactory.getKeyManagers(ksKeys, keypass); KeyManager[] km = SSLJiveKeyManagerFactory.getKeyManagers(ksKeys, keypass);
// TrustManager's decide whether to allow connections. // TrustManager's decide whether to allow connections.
TrustManager[] tm = SSLJiveTrustManagerFactory.getTrustManagers(ksTrust, trustpass); TrustManager[] tm = SSLJiveTrustManagerFactory.getTrustManagers(ksTrust, trustpass);
boolean verify = JiveGlobals.getBooleanProperty("xmpp.server.certificate.verify", true);
SSLContext tlsContext = SSLContext.getInstance(PROTOCOL); if (clientMode && !verify) {
// Trust any certificate presented by the server. Disabling certificate validation
tlsContext.init(km, tm, null); // is not recommended for production environments.
tm = new TrustManager[]{new X509TrustManager() {
/* public void checkClientTrusted(X509Certificate[] x509Certificates,
* Configure the tlsEngine to act as a server in the SSL/TLS handshake. We're a server, String string) {
* so no need to use host/port variant. }
*
* The first call for a server is a NEED_UNWRAP. public void checkServerTrusted(X509Certificate[] x509Certificates,
*/ String string) {
tlsEngine = tlsContext.createSSLEngine(); }
tlsEngine.setUseClientMode(false);
SSLSession session = tlsEngine.getSession(); public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
netBuffSize = session.getPacketBufferSize(); }
appBuffSize = session.getApplicationBufferSize(); }
};
} catch (KeyManagementException e) { }
Log.error("TLSHandler startup problem.\n" + " SSLContext initialisation failed.", e);
} catch (NoSuchAlgorithmException e) {
Log.error("TLSHandler startup problem.\n" + " The " + PROTOCOL + " does not exist", e); SSLContext tlsContext = SSLContext.getInstance(PROTOCOL);
} catch (IOException e) {
Log.error("TLSHandler startup problem.\n" tlsContext.init(km, tm, null);
+ " the KeyStore or TrustStore does not exist", e);
} /*
} * Configure the tlsEngine to act as a server in the SSL/TLS handshake. We're a server,
* so no need to use host/port variant.
public int getNetBuffSize() { *
return netBuffSize; * The first call for a server is a NEED_UNWRAP.
} */
tlsEngine = tlsContext.createSSLEngine();
public int getAppBuffSize() { tlsEngine.setUseClientMode(clientMode);
return appBuffSize; SSLSession session = tlsEngine.getSession();
}
netBuffSize = session.getPacketBufferSize();
/** appBuffSize = session.getApplicationBufferSize();
* Returns whether unwrap(ByteBuffer, ByteBuffer) will accept any more inbound data messages and
* whether wrap(ByteBuffer, ByteBuffer) will produce any more outbound data messages. } catch (KeyManagementException e) {
* Log.error("TLSHandler startup problem.\n" + " SSLContext initialisation failed.", e);
* @return true if the TLSHandler will not consume anymore network data and will not produce any } catch (NoSuchAlgorithmException e) {
* anymore network data. Log.error("TLSHandler startup problem.\n" + " The " + PROTOCOL + " does not exist", e);
*/ } catch (IOException e) {
public boolean isEngineClosed() { Log.error("TLSHandler startup problem.\n"
return (tlsEngine.isOutboundDone() && tlsEngine.isInboundDone()); + " the KeyStore or TrustStore does not exist", e);
} }
}
public void enableLogging(boolean logging) {
this.logging = logging; public int getNetBuffSize() {
} return netBuffSize;
}
/**
* Attempts to decode SSL/TLS network data into a subsequence of plaintext application data public int getAppBuffSize() {
* buffers. Depending on the state of the TLSWrapper, this method may consume network data return appBuffSize;
* without producing any application data (for example, it may consume handshake data.) }
*
* If this TLSWrapper has not yet started its initial handshake, this method will automatically /**
* start the handshake. * Returns whether unwrap(ByteBuffer, ByteBuffer) will accept any more inbound data messages and
* * whether wrap(ByteBuffer, ByteBuffer) will produce any more outbound data messages.
* @param net a ByteBuffer containing inbound network data *
* @param app a ByteBuffer to hold inbound application data * @return true if the TLSHandler will not consume anymore network data and will not produce any
* @return a ByteBuffer containing inbound application data * anymore network data.
* @throws SSLException A problem was encountered while processing the data that caused the */
* TLSHandler to abort. public boolean isEngineClosed() {
*/ return (tlsEngine.isOutboundDone() && tlsEngine.isInboundDone());
public ByteBuffer unwrap(ByteBuffer net, ByteBuffer app) throws SSLException { }
ByteBuffer out = app;
out = resizeApplicationBuffer(out);// guarantees enough room for unwrap public void enableLogging(boolean logging) {
tlsEngineResult = tlsEngine.unwrap(net, out); this.logging = logging;
log("server unwrap: ", tlsEngineResult); }
if (tlsEngineResult.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
// If the result indicates that we have outstanding tasks to do, go /**
// ahead and run them in this thread. * Attempts to decode SSL/TLS network data into a subsequence of plaintext application data
doTasks(); * buffers. Depending on the state of the TLSWrapper, this method may consume network data
} * without producing any application data (for example, it may consume handshake data.)
return out; *
} * If this TLSWrapper has not yet started its initial handshake, this method will automatically
* start the handshake.
/** *
* Attempts to encode a buffer of plaintext application data into TLS network data. Depending on * @param net a ByteBuffer containing inbound network data
* the state of the TLSWrapper, this method may produce network data without consuming any * @param app a ByteBuffer to hold inbound application data
* application data (for example, it may generate handshake data). * @return a ByteBuffer containing inbound application data
* * @throws SSLException A problem was encountered while processing the data that caused the
* If this TLSWrapper has not yet started its initial handshake, this method will automatically * TLSHandler to abort.
* start the handshake. */
* public ByteBuffer unwrap(ByteBuffer net, ByteBuffer app) throws SSLException {
* @param app a ByteBuffer containing outbound application data ByteBuffer out = app;
* @param net a ByteBuffer to hold outbound network data out = resizeApplicationBuffer(out);// guarantees enough room for unwrap
* @throws SSLException A problem was encountered while processing the data that caused the tlsEngineResult = tlsEngine.unwrap(net, out);
* TLSWrapper to abort. log("server unwrap: ", tlsEngineResult);
*/ if (tlsEngineResult.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
public void wrap(ByteBuffer app, ByteBuffer net) throws SSLException { // If the result indicates that we have outstanding tasks to do, go
tlsEngineResult = tlsEngine.wrap(app, net); // ahead and run them in this thread.
log("server wrap: ", tlsEngineResult); doTasks();
if (tlsEngineResult.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { }
// If the result indicates that we have outstanding tasks to do, go return out;
// ahead and run them in this thread. }
doTasks();
} /**
} * Attempts to encode a buffer of plaintext application data into TLS network data. Depending on
* the state of the TLSWrapper, this method may produce network data without consuming any
/** * application data (for example, it may generate handshake data).
* Signals that no more outbound application data will be sent on this TLSHandler. *
* * If this TLSWrapper has not yet started its initial handshake, this method will automatically
* @throws SSLException * start the handshake.
*/ *
public void close() throws SSLException { * @param app a ByteBuffer containing outbound application data
// Indicate that application is done with engine * @param net a ByteBuffer to hold outbound network data
tlsEngine.closeOutbound(); * @throws SSLException A problem was encountered while processing the data that caused the
} * TLSWrapper to abort.
*/
/** public void wrap(ByteBuffer app, ByteBuffer net) throws SSLException {
* Returns the current status for this TLSHandler. tlsEngineResult = tlsEngine.wrap(app, net);
* log("server wrap: ", tlsEngineResult);
* @return the current TLSStatus if (tlsEngineResult.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
*/ // If the result indicates that we have outstanding tasks to do, go
public TLSStatus getStatus() { // ahead and run them in this thread.
TLSStatus status = null; doTasks();
if (tlsEngineResult != null && tlsEngineResult.getStatus() == Status.BUFFER_UNDERFLOW) { }
status = TLSStatus.UNDERFLOW; }
} else {
if (tlsEngineResult != null && tlsEngineResult.getStatus() == Status.CLOSED) { /**
status = TLSStatus.CLOSED; * Signals that no more outbound application data will be sent on this TLSHandler.
} else { *
switch (tlsEngine.getHandshakeStatus()) { * @throws SSLException
case NEED_WRAP: */
status = TLSStatus.NEED_WRITE; public void close() throws SSLException {
break; // Indicate that application is done with engine
case NEED_UNWRAP: tlsEngine.closeOutbound();
status = TLSStatus.NEED_READ; }
break;
default: /**
status = TLSStatus.OK; * Returns the current status for this TLSHandler.
break; *
} * @return the current TLSStatus
} */
} public TLSStatus getStatus() {
return status; TLSStatus status = null;
} if (tlsEngineResult != null && tlsEngineResult.getStatus() == Status.BUFFER_UNDERFLOW) {
status = TLSStatus.UNDERFLOW;
private ByteBuffer resizeApplicationBuffer(ByteBuffer app) { } else {
if (tlsEngineResult != null && tlsEngineResult.getStatus() == Status.CLOSED) {
status = TLSStatus.CLOSED;
} else {
switch (tlsEngine.getHandshakeStatus()) {
case NEED_WRAP:
status = TLSStatus.NEED_WRITE;
break;
case NEED_UNWRAP:
status = TLSStatus.NEED_READ;
break;
default:
status = TLSStatus.OK;
break;
}
}
}
return status;
}
private ByteBuffer resizeApplicationBuffer(ByteBuffer app) {
// TODO Creating new buffers and copying over old one may not scale and may even be a // TODO Creating new buffers and copying over old one may not scale and may even be a
// security risk. Consider using views. Thanks to Noah for the tip. // security risk. Consider using views. Thanks to Noah for the tip.
if (app.remaining() < appBuffSize) { if (app.remaining() < appBuffSize) {
ByteBuffer bb = ByteBuffer.allocate(app.capacity() + appBuffSize); ByteBuffer bb = ByteBuffer.allocate(app.capacity() + appBuffSize);
app.flip(); app.flip();
bb.put(app); bb.put(app);
return bb; return bb;
} else { } else {
return app; return app;
} }
} }
/* /*
* Do all the outstanding handshake tasks in the current Thread. * Do all the outstanding handshake tasks in the current Thread.
*/ */
private SSLEngineResult.HandshakeStatus doTasks() { private SSLEngineResult.HandshakeStatus doTasks() {
Runnable runnable; Runnable runnable;
/* /*
* We could run this in a separate thread, but do in the current for now. * We could run this in a separate thread, but do in the current for now.
*/ */
while ((runnable = tlsEngine.getDelegatedTask()) != null) { while ((runnable = tlsEngine.getDelegatedTask()) != null) {
runnable.run(); runnable.run();
} }
return tlsEngine.getHandshakeStatus(); return tlsEngine.getHandshakeStatus();
} }
/* /*
* Logging code * Logging code
*/ */
private boolean resultOnce = true; private boolean resultOnce = true;
private void log(String str, SSLEngineResult result) { private void log(String str, SSLEngineResult result) {
if (!logging) { if (!logging) {
return; return;
} }
if (resultOnce) { if (resultOnce) {
resultOnce = false; resultOnce = false;
Log.info("The format of the SSLEngineResult is: \n" Log.info("The format of the SSLEngineResult is: \n"
+ "\t\"getStatus() / getHandshakeStatus()\" +\n" + "\t\"getStatus() / getHandshakeStatus()\" +\n"
+ "\t\"bytesConsumed() / bytesProduced()\"\n"); + "\t\"bytesConsumed() / bytesProduced()\"\n");
} }
HandshakeStatus hsStatus = result.getHandshakeStatus(); HandshakeStatus hsStatus = result.getHandshakeStatus();
Log.info(str + result.getStatus() + "/" + hsStatus + ", " + result.bytesConsumed() + "/" Log.info(str + result.getStatus() + "/" + hsStatus + ", " + result.bytesConsumed() + "/"
+ result.bytesProduced() + " bytes"); + result.bytesProduced() + " bytes");
if (hsStatus == HandshakeStatus.FINISHED) { if (hsStatus == HandshakeStatus.FINISHED) {
Log.info("\t...ready for application data"); Log.info("\t...ready for application data");
} }
} }
protected SSLEngine getTlsEngine() { protected SSLEngine getTlsEngine() {
return tlsEngine; return tlsEngine;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment