Commit 937cdc6a authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Added TLS and SASL support. JM-387 JM-7

git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@2776 b35dd754-fafc-0310-a699-88a17e54d16e
parent 180f5579
...@@ -11,28 +11,28 @@ ...@@ -11,28 +11,28 @@
package org.jivesoftware.messenger; package org.jivesoftware.messenger;
import org.dom4j.io.XPPPacketReader;
import org.jivesoftware.messenger.auth.AuthToken; import org.jivesoftware.messenger.auth.AuthToken;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.net.SocketConnection;
import org.jivesoftware.messenger.user.User; import org.jivesoftware.messenger.user.User;
import org.jivesoftware.messenger.user.UserManager; import org.jivesoftware.messenger.user.UserManager;
import org.jivesoftware.messenger.user.UserNotFoundException; import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.messenger.net.SocketConnection; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.JiveGlobals; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.Packet; import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence; import org.xmpp.packet.Presence;
import org.xmpp.packet.StreamError; import org.xmpp.packet.StreamError;
import org.dom4j.io.XPPPacketReader;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParser;
import java.io.Writer;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.io.Writer;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
/** /**
...@@ -44,13 +44,6 @@ public class ClientSession extends Session { ...@@ -44,13 +44,6 @@ public class ClientSession extends Session {
private static final String ETHERX_NAMESPACE = "http://etherx.jabber.org/streams"; private static final String ETHERX_NAMESPACE = "http://etherx.jabber.org/streams";
private static final String FLASH_NAMESPACE = "http://www.jabber.com/streams/flash"; private static final String FLASH_NAMESPACE = "http://www.jabber.com/streams/flash";
private static final String TLS_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
/**
* Version of the XMPP spec supported as MAJOR_VERSION.MINOR_VERSION (e.g. 1.0).
*/
private static final int MAJOR_VERSION = 0;
private static final int MINOR_VERSION = 0;
/** /**
* Keep the list of IP address that are allowed to connect to the server. If the list is * Keep the list of IP address that are allowed to connect to the server. If the list is
...@@ -242,6 +235,7 @@ public class ClientSession extends Session { ...@@ -242,6 +235,7 @@ public class ClientSession extends Session {
sb = new StringBuilder(); sb = new StringBuilder();
sb.append("<stream:features>"); sb.append("<stream:features>");
sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">"); sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
// TODO Consider that STARTTLS may be optional (add TLS options to the AC - disabled, optional, required)
// sb.append("<required/>"); // sb.append("<required/>");
sb.append("</starttls></stream:features>"); sb.append("</starttls></stream:features>");
...@@ -252,24 +246,6 @@ public class ClientSession extends Session { ...@@ -252,24 +246,6 @@ public class ClientSession extends Session {
} }
writer.flush(); writer.flush();
boolean done = false;
while (!done) {
if (xpp.next() == XmlPullParser.START_TAG) {
done = true;
if (xpp.getName().equals("starttls") &&
xpp.getNamespace(xpp.getPrefix()).equals(TLS_NAMESPACE))
{
writer.write("<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
if (isFlashClient) {
writer.write('\0');
}
writer.flush();
// TODO: setup SSLEngine and negotiate TLS.
}
}
}
return session; return session;
} }
...@@ -335,6 +311,17 @@ public class ClientSession extends Session { ...@@ -335,6 +311,17 @@ public class ClientSession extends Session {
return getAddress().getNode(); return getAddress().getNode();
} }
/**
* Sets the new Authorization Token for this session. The session is not yet considered fully
* authenticated (i.e. active) since a resource has not been binded at this point. This
* message will be sent after SASL authentication was successful but yet resource binding
* is required.
*
* @param auth the authentication token obtained from SASL authentication.
*/
public void setAuthToken(AuthToken auth) {
authToken = auth;
}
/** /**
* Initialize the session with a valid authentication token and * Initialize the session with a valid authentication token and
......
...@@ -14,6 +14,7 @@ package org.jivesoftware.messenger.net; ...@@ -14,6 +14,7 @@ package org.jivesoftware.messenger.net;
import org.dom4j.Element; import org.dom4j.Element;
import org.jivesoftware.messenger.ClientSession; import org.jivesoftware.messenger.ClientSession;
import org.jivesoftware.messenger.PacketRouter; import org.jivesoftware.messenger.PacketRouter;
import org.jivesoftware.messenger.XMPPServer;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.IQ; import org.xmpp.packet.IQ;
...@@ -76,4 +77,29 @@ public class ClientSocketReader extends SocketReader { ...@@ -76,4 +77,29 @@ public class ClientSocketReader extends SocketReader {
return false; return false;
} }
String getNamespace() {
return "jabber:client";
}
protected String getAvailableStreamFeatures() {
StringBuilder sb = new StringBuilder();
// TODO Create and use #hasSASLAuthentication
if (((ClientSession)session).getAuthToken() == null) {
// Advertise that the server supports Non-SASL Authentication
if (XMPPServer.getInstance().getIQAuthHandler().isAllowAnonymous()) {
sb.append("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
}
// Advertise that the server supports In-Band Registration
if (XMPPServer.getInstance().getIQRegisterHandler().isInbandRegEnabled()) {
sb.append("<register xmlns=\"http://jabber.org/features/iq-register\"/>");
}
}
else {
// If the session has been authenticated then offer resource binding
// and session establishment
sb.append("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
sb.append("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>");
}
return sb.toString();
}
} }
/** /**
* $RCSfile$ * $RCSfile: ComponentSocketReader.java,v $
* $Revision$ * $Revision$
* $Date$ * $Date$
* *
...@@ -53,4 +53,13 @@ public class ComponentSocketReader extends SocketReader { ...@@ -53,4 +53,13 @@ public class ComponentSocketReader extends SocketReader {
} }
return false; return false;
} }
String getNamespace() {
return "jabber:component:accept";
}
String getAvailableStreamFeatures() {
// Nothing special to add
return null;
}
} }
/** /**
* $RCSfile$ * $RCSfile: ServerSocketReader.java,v $
* $Revision$ * $Revision$
* $Date$ * $Date$
* *
...@@ -210,4 +210,13 @@ public class ServerSocketReader extends SocketReader { ...@@ -210,4 +210,13 @@ public class ServerSocketReader extends SocketReader {
} }
return false; return false;
} }
String getNamespace() {
return "jabber:server";
}
String getAvailableStreamFeatures() {
// Nothing special to add
return null;
}
} }
...@@ -58,6 +58,7 @@ public class SocketConnection implements Connection { ...@@ -58,6 +58,7 @@ public class SocketConnection implements Connection {
private int majorVersion = 1; private int majorVersion = 1;
private int minorVersion = 0; private int minorVersion = 0;
private String language = null; private String language = null;
private TLSStreamHandler tlsStreamHandler;
/** /**
* Create a new session using the supplied socket. * Create a new session using the supplied socket.
...@@ -81,6 +82,31 @@ public class SocketConnection implements Connection { ...@@ -81,6 +82,31 @@ public class SocketConnection implements Connection {
xmlSerializer = new XMLSocketWriter(writer, socket); xmlSerializer = new XMLSocketWriter(writer, socket);
} }
/**
* Returns the stream handler responsible for securing the plain connection and providing
* the corresponding input and output streams.
*
* @return the stream handler responsible for securing the plain connection and providing
* the corresponding input and output streams.
*/
public TLSStreamHandler getTLSStreamHandler() {
return tlsStreamHandler;
}
/**
* Secures the plain connection by negotiating TLS with the client.
*
* @throws IOException if an error occured while securing the connection.
*/
public void startTLS() throws IOException {
if (!secure) {
secure = true;
tlsStreamHandler = new TLSStreamHandler(socket);
writer = new BufferedWriter(new OutputStreamWriter(tlsStreamHandler.getOutputStream(), CHARSET));
xmlSerializer = new XMLSocketWriter(writer, socket);
}
}
public boolean validate() { public boolean validate() {
if (isClosed()) { if (isClosed()) {
return false; return false;
......
...@@ -11,25 +11,26 @@ ...@@ -11,25 +11,26 @@
package org.jivesoftware.messenger.net; package org.jivesoftware.messenger.net;
import org.xmlpull.v1.XmlPullParserFactory; import org.dom4j.Element;
import org.xmlpull.v1.XmlPullParserException; import org.dom4j.io.XPPPacketReader;
import org.xmlpull.v1.XmlPullParser; import org.jivesoftware.messenger.PacketRouter;
import org.jivesoftware.messenger.*; import org.jivesoftware.messenger.Session;
import org.jivesoftware.messenger.auth.UnauthorizedException; import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.interceptor.InterceptorManager; import org.jivesoftware.messenger.interceptor.InterceptorManager;
import org.jivesoftware.messenger.interceptor.PacketRejectedException; import org.jivesoftware.messenger.interceptor.PacketRejectedException;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.dom4j.io.XPPPacketReader; import org.jivesoftware.util.Log;
import org.dom4j.Element; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmpp.packet.*; import org.xmpp.packet.*;
import java.net.Socket;
import java.net.SocketException;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer; import java.io.Writer;
import java.net.Socket;
import java.net.SocketException;
/** /**
* A SocketReader creates the appropriate {@link Session} based on the defined namespace in the * A SocketReader creates the appropriate {@link Session} based on the defined namespace in the
...@@ -229,6 +230,33 @@ public abstract class SocketReader implements Runnable { ...@@ -229,6 +230,33 @@ public abstract class SocketReader implements Runnable {
} }
processIQ(packet); processIQ(packet);
} }
else if ("starttls".equals(tag)) {
// Client requested to secure the connection using TLS
connection.deliverRawText("<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
// Negotiate TLS
if (negotiateTLS()) {
tlsNegotiated();
}
else {
open = false;
session = null;
}
continue;
}
else if ("auth".equals(tag)) {
// User is trying to authenticate using SASL
SASLAuthentication saslAuth = new SASLAuthentication(session, reader);
if (saslAuth.doHandshake(doc)) {
// SASL authentication was successful so open a new stream and offer
// resource binding and session establishment (to client sessions only)
saslSuccessful();
}
else {
open = false;
session = null;
}
continue;
}
else { else {
if (!processUnknowPacket(doc)) { if (!processUnknowPacket(doc)) {
Log.warn(LocaleUtils.getLocalizedString("admin.error.packet.tag") + Log.warn(LocaleUtils.getLocalizedString("admin.error.packet.tag") +
...@@ -397,7 +425,9 @@ public abstract class SocketReader implements Runnable { ...@@ -397,7 +425,9 @@ public abstract class SocketReader implements Runnable {
eventType = xpp.next(); eventType = xpp.next();
} }
// Create the correct session based on the sent namespace // Create the correct session based on the sent namespace. At this point the server
// may offer the client to secure the connection. If the client decides to secure
// the connection then a <starttls> stanza should be received
if (!createSession(xpp.getNamespace(null))) { if (!createSession(xpp.getNamespace(null))) {
// No session was created because of an invalid namespace prefix so answer a stream // No session was created because of an invalid namespace prefix so answer a stream
// error and close the underlying connection // error and close the underlying connection
...@@ -416,6 +446,123 @@ public abstract class SocketReader implements Runnable { ...@@ -416,6 +446,123 @@ public abstract class SocketReader implements Runnable {
} }
} }
/**
* Tries to secure the connection using TLS. If the connection is secured then reset
* the parser to use the new secured reader. But if the connection failed to be secured
* then send a <failure> stanza and close the connection.
*
* @return true if the connection was secured.
* @throws IOException if an I/O error occures while parsing the input stream.
* @throws XmlPullParserException if an error occures while parsing.
*/
private boolean negotiateTLS() throws IOException, XmlPullParserException {
// Negotiate TLS.
try {
connection.startTLS();
}
catch (IOException e) {
connection.deliverRawText("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
connection.close();
return false;
}
XmlPullParser xpp = reader.getXPPParser();
// Reset the parser to use the new reader
xpp.setInput(new InputStreamReader(connection.getTLSStreamHandler().getInputStream(), CHARSET));
// Skip new stream element
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next();
}
return true;
}
/**
* TLS negotiation was successful so open a new stream and offer the new stream features.
* The new stream features will include available SASL mechanisms and specific features
* depending on the session type such as auth for Non-SASL authentication and register
* for in-band registration.
*/
private void tlsNegotiated() {
// Offer stream features including SASL Mechanisms
StringBuilder sb = new StringBuilder();
sb.append(geStreamHeader());
sb.append("<stream:features>");
// Include available SASL Mechanisms
sb.append(SASLAuthentication.getSASLMechanisms(session));
// Include specific features such as auth and register for client sessions
String specificFeatures = getAvailableStreamFeatures();
if (specificFeatures != null) {
sb.append(specificFeatures);
}
sb.append("</stream:features>");
connection.deliverRawText(sb.toString());
}
/**
* After SASL authentication was successful we should open a new stream and offer
* new stream features such as resource binding and session establishment. Notice that
* resource binding and session establishment should only be offered to clients (i.e. not
* to servers or external components)
*/
private void saslSuccessful() throws XmlPullParserException, IOException {
StringBuilder sb = new StringBuilder();
sb.append(geStreamHeader());
sb.append("<stream:features>");
// Include specific features such as resource binding and session establishment
// for client sessions
String specificFeatures = getAvailableStreamFeatures();
if (specificFeatures != null) {
sb.append(specificFeatures);
}
sb.append("</stream:features>");
connection.deliverRawText(sb.toString());
// Skip the opening stream sent by the client
XmlPullParser xpp = reader.getXPPParser();
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next();
}
}
/**
* Returns a text with the available stream features. Each subclass may return different
* values depending whether the session has been authenticated or not.
*
* @return a text with the available stream features or <tt>null</tt> to add nothing.
*/
abstract String getAvailableStreamFeatures();
/**
* Returns the stream namespace. (E.g. jabber:client, jabber:server, etc.).
*
* @return the stream namespace.
*/
abstract String getNamespace();
private String geStreamHeader() {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version='1.0' encoding='");
sb.append(CHARSET);
sb.append("'?>");
if (connection.isFlashClient()) {
sb.append("<flash:stream xmlns:flash=\"http://www.jabber.com/streams/flash\" ");
} else {
sb.append("<stream:stream ");
}
sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"");
sb.append(getNamespace());
sb.append("\" from=\"");
sb.append(session.getServerName());
sb.append("\" id=\"");
sb.append(session.getStreamID().toString());
sb.append("\" xml:lang=\"");
sb.append(connection.getLanguage());
sb.append("\" version=\"");
sb.append(Session.MAJOR_VERSION).append(".").append(Session.MINOR_VERSION);
sb.append("\">");
return sb.toString();
}
/** /**
* Notification message indicating that the SocketReader is shutting down. The thread will * Notification message indicating that the SocketReader is shutting down. The thread will
* stop reading and processing new requests. Subclasses may want to redefine this message * stop reading and processing new requests. Subclasses may want to redefine this message
......
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