Commit 5399d0b0 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

1) Added compression for s2s.

2) Connection is no longer being closed when compression fails.

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@3278 b35dd754-fafc-0310-a699-88a17e54d16e
parent 15412cb3
......@@ -325,7 +325,7 @@ public class MXParser extends org.xmlpull.mxp1.MXParser {
}
}
protected void resetInput() {
public void resetInput() {
Reader oldReader = reader;
String oldEncoding = inputEncoding;
reset();
......
......@@ -286,7 +286,7 @@ public class SASLAuthentication {
// Check that hostname matches the one provided in a certificate
for (Certificate certificate : connection.getSSLSession().getPeerCertificates()) {
if (hostname
.equals(ServerTrustManager.getPeerIdentity((X509Certificate) certificate)))
.equals(TLSStreamHandler.getPeerIdentity((X509Certificate) certificate)))
{
authenticationSuccessful(hostname);
return true;
......
......@@ -79,7 +79,7 @@ class ServerTrustManager implements X509TrustManager {
if (verify) {
int nSize = x509Certificates.length;
String peerIdentity = getPeerIdentity(x509Certificates[0]);
String peerIdentity = TLSStreamHandler.getPeerIdentity(x509Certificates[0]);
if (JiveGlobals.getBooleanProperty("xmpp.server.certificate.verify.chain", true)) {
// Working down the chain, for every certificate in the chain,
......@@ -164,24 +164,6 @@ class ServerTrustManager implements X509TrustManager {
}
}
/**
* Returns the identity of the remote server as defined in the specified certificate. The
* identity is defined in the subjectDN of the certificate and it can also be defined in
* the subjectAltName extension of type "xmpp". When the extension is being used then the
* identity defined in the extension in going to be returned. Otherwise, the value stored in
* the subjectDN is returned.
*
* @param x509Certificate the certificate the holds the identity of the remote server.
* @return the identity of the remote server as defined in the specified certificate.
*/
static String getPeerIdentity(X509Certificate x509Certificate) {
Principal principalSubject = x509Certificate.getSubjectDN();
// TODO Look the identity in the subjectAltName extension if available
String name = principalSubject.getName();
name = name.replace("CN=", "");
return name;
}
private boolean isChainTrusted(X509Certificate[] chain) {
boolean trusted = false;
try {
......
......@@ -285,10 +285,6 @@ public abstract class SocketReader implements Runnable {
// resource binding and session establishment (to client sessions only)
compressionSuccessful();
}
else {
open = false;
session = null;
}
}
else
{
......@@ -354,8 +350,6 @@ public abstract class SocketReader implements Runnable {
if (error != null) {
// Deliver stanza
connection.deliverRawText(error);
// Close the underlying connection
connection.close();
return false;
}
else {
......
......@@ -24,6 +24,8 @@ import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.WritableByteChannel;
import java.security.cert.X509Certificate;
import java.security.Principal;
/**
* TLSStreamHandler is responsible for securing plain connections by negotiating TLS. By creating
......@@ -33,46 +35,64 @@ import java.nio.channels.WritableByteChannel;
*/
public class TLSStreamHandler {
private TLSStreamWriter writer;
private TLSStreamWriter writer;
private TLSStreamReader reader;
private TLSStreamReader reader;
private TLSWrapper wrapper;
private TLSWrapper wrapper;
private ReadableByteChannel rbc;
private WritableByteChannel wbc;
private ReadableByteChannel rbc;
private WritableByteChannel wbc;
private SSLEngine tlsEngine;
private SSLEngine tlsEngine;
/*
* During the initial handshake, keep track of the next SSLEngine operation that needs to occur:
*
* NEED_WRAP/NEED_UNWRAP
*
* Once the initial handshake has completed, we can short circuit handshake checks with
* initialHSComplete.
*/
private HandshakeStatus initialHSStatus;
private boolean initialHSComplete;
/*
* During the initial handshake, keep track of the next SSLEngine operation that needs to occur:
*
* NEED_WRAP/NEED_UNWRAP
*
* Once the initial handshake has completed, we can short circuit handshake checks with
* initialHSComplete.
*/
private HandshakeStatus initialHSStatus;
private boolean initialHSComplete;
private int appBBSize;
private int netBBSize;
private int appBBSize;
private int netBBSize;
/*
* All I/O goes through these buffers. It might be nice to use a cache of ByteBuffers so we're
* not alloc/dealloc'ing ByteBuffer's for each new SSLEngine. Outbound application data is
* supplied to us by our callers.
*/
private ByteBuffer incomingNetBB;
private ByteBuffer outgoingNetBB;
/*
* All I/O goes through these buffers. It might be nice to use a cache of ByteBuffers so we're
* not alloc/dealloc'ing ByteBuffer's for each new SSLEngine. Outbound application data is
* supplied to us by our callers.
*/
private ByteBuffer incomingNetBB;
private ByteBuffer outgoingNetBB;
private ByteBuffer appBB;
private ByteBuffer appBB;
/*
* An empty ByteBuffer for use when one isn't available, say as a source buffer during initial
* handshake wraps or for close operations.
*/
private static ByteBuffer hsBB = ByteBuffer.allocate(0);
/*
* An empty ByteBuffer for use when one isn't available, say as a source buffer during initial
* handshake wraps or for close operations.
*/
private static ByteBuffer hsBB = ByteBuffer.allocate(0);
/**
* Returns the identity of the remote server as defined in the specified certificate. The
* identity is defined in the subjectDN of the certificate and it can also be defined in
* the subjectAltName extension of type "xmpp". When the extension is being used then the
* identity defined in the extension in going to be returned. Otherwise, the value stored in
* the subjectDN is returned.
*
* @param x509Certificate the certificate the holds the identity of the remote server.
* @return the identity of the remote server as defined in the specified certificate.
*/
public static String getPeerIdentity(X509Certificate x509Certificate) {
Principal principalSubject = x509Certificate.getSubjectDN();
// TODO Look the identity in the subjectAltName extension if available
String name = principalSubject.getName();
name = name.replace("CN=", "");
return name;
}
/**
* Creates a new TLSStreamHandler and secures the plain socket connection. When connecting
......@@ -93,23 +113,23 @@ public class TLSStreamHandler {
boolean needClientAuth) throws IOException {
wrapper = new TLSWrapper(clientMode, needClientAuth, remoteServer);
tlsEngine = wrapper.getTlsEngine();
reader = new TLSStreamReader(wrapper, socket);
writer = new TLSStreamWriter(wrapper, socket);
reader = new TLSStreamReader(wrapper, socket);
writer = new TLSStreamWriter(wrapper, socket);
rbc = Channels.newChannel(socket.getInputStream());
wbc = Channels.newChannel(socket.getOutputStream());
initialHSStatus = HandshakeStatus.NEED_UNWRAP;
initialHSComplete = false;
rbc = Channels.newChannel(socket.getInputStream());
wbc = Channels.newChannel(socket.getOutputStream());
initialHSStatus = HandshakeStatus.NEED_UNWRAP;
initialHSComplete = false;
netBBSize = tlsEngine.getSession().getPacketBufferSize();
appBBSize = tlsEngine.getSession().getApplicationBufferSize();
netBBSize = tlsEngine.getSession().getPacketBufferSize();
appBBSize = tlsEngine.getSession().getApplicationBufferSize();
incomingNetBB = ByteBuffer.allocate(netBBSize);
outgoingNetBB = ByteBuffer.allocate(netBBSize);
outgoingNetBB.position(0);
outgoingNetBB.limit(0);
incomingNetBB = ByteBuffer.allocate(netBBSize);
outgoingNetBB = ByteBuffer.allocate(netBBSize);
outgoingNetBB.position(0);
outgoingNetBB.limit(0);
appBB = ByteBuffer.allocate(appBBSize);
appBB = ByteBuffer.allocate(appBBSize);
if (clientMode) {
socket.setSoTimeout(0);
......@@ -122,175 +142,175 @@ public class TLSStreamHandler {
}
while (!initialHSComplete) {
initialHSComplete = doHandshake(null);
}
}
public InputStream getInputStream(){
return reader.getInputStream();
}
public OutputStream getOutputStream(){
return writer.getOutputStream();
}
private boolean doHandshake(SelectionKey sk) throws IOException {
SSLEngineResult result;
if (initialHSComplete) {
return initialHSComplete;
}
/*
* Flush out the outgoing buffer, if there's anything left in it.
*/
if (outgoingNetBB.hasRemaining()) {
if (!flush(outgoingNetBB)) {
return false;
}
// See if we need to switch from write to read mode.
switch (initialHSStatus) {
/*
* Is this the last buffer?
*/
case FINISHED:
initialHSComplete = true;
case NEED_UNWRAP:
if (sk != null) {
sk.interestOps(SelectionKey.OP_READ);
}
break;
}
return initialHSComplete;
}
switch (initialHSStatus) {
case NEED_UNWRAP:
if (rbc.read(incomingNetBB) == -1) {
tlsEngine.closeInbound();
return initialHSComplete;
}
needIO: while (initialHSStatus == HandshakeStatus.NEED_UNWRAP) {
/*
* Don't need to resize requestBB, since no app data should be generated here.
*/
incomingNetBB.flip();
result = tlsEngine.unwrap(incomingNetBB, appBB);
incomingNetBB.compact();
initialHSStatus = result.getHandshakeStatus();
switch (result.getStatus()) {
case OK:
switch (initialHSStatus) {
case NOT_HANDSHAKING:
throw new IOException("Not handshaking during initial handshake");
case NEED_TASK:
initialHSStatus = doTasks();
break;
case FINISHED:
initialHSComplete = true;
break needIO;
}
break;
case BUFFER_UNDERFLOW:
/*
* Need to go reread the Channel for more data.
*/
if (sk != null) {
sk.interestOps(SelectionKey.OP_READ);
}
break needIO;
default: // BUFFER_OVERFLOW/CLOSED:
throw new IOException("Received" + result.getStatus()
+ "during initial handshaking");
}
}
/*
* Just transitioned from read to write.
*/
if (initialHSStatus != HandshakeStatus.NEED_WRAP) {
break;
}
// Fall through and fill the write buffers.
case NEED_WRAP:
/*
* The flush above guarantees the out buffer to be empty
*/
outgoingNetBB.clear();
result = tlsEngine.wrap(hsBB, outgoingNetBB);
outgoingNetBB.flip();
initialHSStatus = result.getHandshakeStatus();
switch (result.getStatus()) {
case OK:
if (initialHSStatus == HandshakeStatus.NEED_TASK) {
initialHSStatus = doTasks();
}
if (sk != null) {
sk.interestOps(SelectionKey.OP_WRITE);
}
break;
default: // BUFFER_OVERFLOW/BUFFER_UNDERFLOW/CLOSED:
throw new IOException("Received" + result.getStatus()
+ "during initial handshaking");
}
break;
default: // NOT_HANDSHAKING/NEED_TASK/FINISHED
throw new RuntimeException("Invalid Handshaking State" + initialHSStatus);
} // switch
return initialHSComplete;
}
/*
* Writes ByteBuffer to the SocketChannel. Returns true when the ByteBuffer has no remaining
* data.
*/
private boolean flush(ByteBuffer bb) throws IOException {
wbc.write(bb);
return !bb.hasRemaining();
}
/*
* Do all the outstanding handshake tasks in the current Thread.
*/
private SSLEngineResult.HandshakeStatus doTasks() {
Runnable runnable;
/*
* We could run this in a separate thread, but do in the current for now.
*/
while ((runnable = tlsEngine.getDelegatedTask()) != null) {
runnable.run();
}
return tlsEngine.getHandshakeStatus();
}
initialHSComplete = doHandshake(null);
}
}
public InputStream getInputStream(){
return reader.getInputStream();
}
public OutputStream getOutputStream(){
return writer.getOutputStream();
}
private boolean doHandshake(SelectionKey sk) throws IOException {
SSLEngineResult result;
if (initialHSComplete) {
return initialHSComplete;
}
/*
* Flush out the outgoing buffer, if there's anything left in it.
*/
if (outgoingNetBB.hasRemaining()) {
if (!flush(outgoingNetBB)) {
return false;
}
// See if we need to switch from write to read mode.
switch (initialHSStatus) {
/*
* Is this the last buffer?
*/
case FINISHED:
initialHSComplete = true;
case NEED_UNWRAP:
if (sk != null) {
sk.interestOps(SelectionKey.OP_READ);
}
break;
}
return initialHSComplete;
}
switch (initialHSStatus) {
case NEED_UNWRAP:
if (rbc.read(incomingNetBB) == -1) {
tlsEngine.closeInbound();
return initialHSComplete;
}
needIO: while (initialHSStatus == HandshakeStatus.NEED_UNWRAP) {
/*
* Don't need to resize requestBB, since no app data should be generated here.
*/
incomingNetBB.flip();
result = tlsEngine.unwrap(incomingNetBB, appBB);
incomingNetBB.compact();
initialHSStatus = result.getHandshakeStatus();
switch (result.getStatus()) {
case OK:
switch (initialHSStatus) {
case NOT_HANDSHAKING:
throw new IOException("Not handshaking during initial handshake");
case NEED_TASK:
initialHSStatus = doTasks();
break;
case FINISHED:
initialHSComplete = true;
break needIO;
}
break;
case BUFFER_UNDERFLOW:
/*
* Need to go reread the Channel for more data.
*/
if (sk != null) {
sk.interestOps(SelectionKey.OP_READ);
}
break needIO;
default: // BUFFER_OVERFLOW/CLOSED:
throw new IOException("Received" + result.getStatus()
+ "during initial handshaking");
}
}
/*
* Just transitioned from read to write.
*/
if (initialHSStatus != HandshakeStatus.NEED_WRAP) {
break;
}
// Fall through and fill the write buffers.
case NEED_WRAP:
/*
* The flush above guarantees the out buffer to be empty
*/
outgoingNetBB.clear();
result = tlsEngine.wrap(hsBB, outgoingNetBB);
outgoingNetBB.flip();
initialHSStatus = result.getHandshakeStatus();
switch (result.getStatus()) {
case OK:
if (initialHSStatus == HandshakeStatus.NEED_TASK) {
initialHSStatus = doTasks();
}
if (sk != null) {
sk.interestOps(SelectionKey.OP_WRITE);
}
break;
default: // BUFFER_OVERFLOW/BUFFER_UNDERFLOW/CLOSED:
throw new IOException("Received" + result.getStatus()
+ "during initial handshaking");
}
break;
default: // NOT_HANDSHAKING/NEED_TASK/FINISHED
throw new RuntimeException("Invalid Handshaking State" + initialHSStatus);
} // switch
return initialHSComplete;
}
/*
* Writes ByteBuffer to the SocketChannel. Returns true when the ByteBuffer has no remaining
* data.
*/
private boolean flush(ByteBuffer bb) throws IOException {
wbc.write(bb);
return !bb.hasRemaining();
}
/*
* Do all the outstanding handshake tasks in the current Thread.
*/
private SSLEngineResult.HandshakeStatus doTasks() {
Runnable runnable;
/*
* We could run this in a separate thread, but do in the current for now.
*/
while ((runnable = tlsEngine.getDelegatedTask()) != null) {
runnable.run();
}
return tlsEngine.getHandshakeStatus();
}
/**
* Closes the channels that will end up closing the input and output streams of the connection.
......
......@@ -148,6 +148,17 @@ public class IncomingServerSession extends Session {
openingStream.append(" version=\"1.0\">");
connection.deliverRawText(openingStream.toString());
// Indicate the TLS policy to use for this connection
connection.setTlsPolicy(ServerDialback.isEnabled() ? Connection.TLSPolicy.optional :
Connection.TLSPolicy.required);
// Indicate the compression policy to use for this connection
String policyName = JiveGlobals.getProperty("xmpp.server.compression.policy",
Connection.CompressionPolicy.disabled.toString());
Connection.CompressionPolicy compressionPolicy =
Connection.CompressionPolicy.valueOf(policyName);
connection.setCompressionPolicy(compressionPolicy);
StringBuilder sb = new StringBuilder();
sb.append("<stream:features>");
sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
......@@ -292,6 +303,11 @@ public class IncomingServerSession extends Session {
}
public String getAvailableStreamFeatures() {
// Include Stream Compression Mechanism
if (conn.getCompressionPolicy() != Connection.CompressionPolicy.disabled &&
!conn.isCompressed()) {
return "<compression xmlns=\"http://jabber.org/features/compress\"><method>zlib</method></compression>";
}
// Nothing special to add
return null;
}
......
......@@ -18,6 +18,7 @@ import org.jivesoftware.wildfire.*;
import org.jivesoftware.wildfire.auth.UnauthorizedException;
import org.jivesoftware.wildfire.net.DNSUtil;
import org.jivesoftware.wildfire.net.SocketConnection;
import org.jivesoftware.wildfire.net.MXParser;
import org.jivesoftware.wildfire.spi.BasicStreamIDFactory;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
......@@ -38,6 +39,9 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.regex.Pattern;
import com.jcraft.jzlib.ZInputStream;
import com.jcraft.jzlib.JZlib;
/**
* Server-to-server communication is done using two TCP connections between the servers. One
* connection is used for sending packets while the other connection is used for receiving packets.
......@@ -298,7 +302,7 @@ public class OutgoingServerSession extends Session {
// Secure the connection with TLS and authenticate using SASL
OutgoingServerSession answer;
answer = secureAndAuthenticate(hostname, connection, reader, openingStream,
xpp, domain);
domain);
if (answer != null) {
// Everything went fine so return the secured and
// authenticated connection
......@@ -334,11 +338,12 @@ public class OutgoingServerSession extends Session {
private static OutgoingServerSession secureAndAuthenticate(String hostname,
SocketConnection connection, XMPPPacketReader reader, StringBuilder openingStream,
XmlPullParser xpp, String domain) throws Exception {
String domain) throws Exception {
Element features;
Log.debug("OS - Indicating we want TLS to " + hostname);
connection.deliverRawText("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
MXParser xpp = (MXParser) reader.getXPPParser();
// Wait for the <proceed> response
Element proceed = reader.parseDocument().getRootElement();
if (proceed != null && proceed.getName().equals("proceed")) {
......@@ -359,6 +364,66 @@ public class OutgoingServerSession extends Session {
// Get new stream features
features = reader.parseDocument().getRootElement();
if (features != null && features.element("mechanisms") != null) {
// Check if we can use stream compression
String policyName = JiveGlobals.getProperty("xmpp.server.compression.policy",
Connection.CompressionPolicy.disabled.toString());
Connection.CompressionPolicy compressionPolicy =
Connection.CompressionPolicy.valueOf(policyName);
if (Connection.CompressionPolicy.optional == compressionPolicy) {
// Verify if the remote server supports stream compression
Element compression = features.element("compression");
if (compression != null) {
boolean zlibSupported = false;
Iterator it = compression.elementIterator("method");
while (it.hasNext()) {
Element method = (Element) it.next();
if ("zlib".equals(method.getTextTrim())) {
zlibSupported = true;
}
}
if (zlibSupported) {
// Request Stream Compression
connection.deliverRawText("<compress xmlns='http://jabber.org/protocol/compress'><method>zlib</method></compress>");
// Check if we are good to start compression
Element answer = reader.parseDocument().getRootElement();
if ("compressed".equals(answer.getName())) {
// Server confirmed that we can use zlib compression
connection.startCompression();
Log.debug("OS - Stream compression was successful with " + hostname);
// Stream compression was successful so initiate a new stream
connection.deliverRawText(openingStream.toString());
// Reset the parser to use stream compression over TLS
ZInputStream in = new ZInputStream(
connection.getTLSStreamHandler().getInputStream());
in.setFlushMode(JZlib.Z_PARTIAL_FLUSH);
xpp.setInput(new InputStreamReader(in, CHARSET));
// Skip the opening stream sent by the server
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;)
{
eventType = xpp.next();
}
// Get new stream features
features = reader.parseDocument().getRootElement();
if (features == null || features.element("mechanisms") == null) {
Log.debug("OS - Error, EXTERNAL SASL was not offered by " + hostname);
return null;
}
}
else {
Log.debug("OS - Stream compression was rejected by " + hostname);
}
}
else {
Log.debug(
"OS - Stream compression found but zlib method is not supported by" +
hostname);
}
}
else {
Log.debug("OS - Stream compression not supoprted by " + hostname);
}
}
Iterator it = features.element("mechanisms").elementIterator();
while (it.hasNext()) {
Element mechanism = (Element) it.next();
......@@ -369,9 +434,8 @@ public class OutgoingServerSession extends Session {
// SASL was successful so initiate a new stream
connection.deliverRawText(openingStream.toString());
// Reset the parser to use the new secured reader
xpp.setInput(new InputStreamReader(
connection.getTLSStreamHandler().getInputStream(), CHARSET));
// Reset the parser
xpp.resetInput();
// Skip the opening stream sent by the server
for (int eventType = xpp.getEventType();
eventType != XmlPullParser.START_TAG;) {
......
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