Commit 9a73f871 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Added support for validating certificates of BOSH clients. JM-1390

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@10511 b35dd754-fafc-0310-a699-88a17e54d16e
parent 6b109ac1
......@@ -16,8 +16,8 @@ import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.session.LocalSession;
import org.xmpp.packet.Packet;
import javax.net.ssl.SSLSession;
import java.net.UnknownHostException;
import java.security.cert.Certificate;
/**
* Represents a connection on the server.
......@@ -90,11 +90,11 @@ public interface Connection {
public String getHostName() throws UnknownHostException;
/**
* Returns the underlying {@link SSLSession} for the connection.
* Returns the underlying {@link javax.security.cert.X509Certificate} for the connection.
*
* @return <tt>null</tt> if no {@link SSLSession} is initialized yet.
* @return <tt>null</tt> if no {@link javax.security.cert.X509Certificate} is present for the connection.
*/
public SSLSession getSSLSession();
public Certificate[] getPeerCertificates();
/**
* Close this session including associated socket connection. The order of
......
......@@ -20,6 +20,7 @@ import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.component.InternalComponentManager;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.event.SessionEventDispatcher;
import org.jivesoftware.openfire.http.HttpConnection;
import org.jivesoftware.openfire.http.HttpSession;
import org.jivesoftware.openfire.multiplex.ConnectionMultiplexerManager;
import org.jivesoftware.openfire.server.OutgoingSessionPromise;
......@@ -315,14 +316,13 @@ public class SessionManager extends BasicModule implements ClusterEventListener
return session;
}
public HttpSession createClientHttpSession(long rid, InetAddress address, StreamID id)
throws UnauthorizedException
{
public HttpSession createClientHttpSession(long rid, InetAddress address, StreamID id, HttpConnection connection)
throws UnauthorizedException {
if (serverName == null) {
throw new UnauthorizedException("Server not initialized");
}
PacketDeliverer backupDeliverer = server.getPacketDeliverer();
HttpSession session = new HttpSession(backupDeliverer, serverName, address, id, rid);
HttpSession session = new HttpSession(backupDeliverer, serverName, address, id, rid, connection);
Connection conn = session.getConnection();
conn.init(session);
conn.registerCloseListener(clientSessionListener, session);
......
......@@ -12,11 +12,12 @@
package org.jivesoftware.openfire.http;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.util.JiveGlobals;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;
/**
......@@ -27,23 +28,73 @@ import java.io.IOException;
*/
public class FlashCrossDomainServlet extends HttpServlet {
public static String CROSS_DOMAIN_TEXT = "<?xml version=\"1.0\"?>" +
private static String CROSS_DOMAIN_TEXT = "<?xml version=\"1.0\"?>" +
"<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">" +
"<cross-domain-policy>" +
"<site-control permitted-cross-domain-policies=\"all\"/>" +
"<allow-access-from domain=\"*\" to-ports=\"";
public static String CROSS_DOMAIN_END_TEXT = "\" /></cross-domain-policy>";
private static String CROSS_DOMAIN_MIDDLE_TEXT = "\" secure=\"";
private static String CROSS_DOMAIN_END_TEXT = "\"/></cross-domain-policy>";
private static String CROSS_DOMAIN_SECURE_ENABLED = "httpbind.crossdomain.secure";
private static boolean CROSS_DOMAIN_SECURE_DEFAULT = true;
@Override
protected void doGet(HttpServletRequest httpServletRequest,
HttpServletResponse response) throws
ServletException, IOException {
StringBuilder builder = new StringBuilder();
builder.append(CROSS_DOMAIN_TEXT +
XMPPServer.getInstance().getConnectionManager().getClientListenerPort() +
CROSS_DOMAIN_END_TEXT);
builder.append(CROSS_DOMAIN_TEXT);
getPortList(builder);
builder.append(CROSS_DOMAIN_MIDDLE_TEXT);
getSecure(builder);
builder.append(CROSS_DOMAIN_END_TEXT);
builder.append("\n");
response.setContentType("text/xml");
response.getOutputStream().write(builder.toString().getBytes());
}
private StringBuilder getPortList(StringBuilder builder) {
boolean multiple = false;
if(XMPPServer.getInstance().getConnectionManager().getClientListenerPort() > 0) {
builder.append(XMPPServer.getInstance().getConnectionManager().getClientListenerPort());
multiple = true;
}
if(XMPPServer.getInstance().getConnectionManager().getClientSSLListenerPort() > 0) {
if(multiple) {
builder.append(",");
}
builder.append(XMPPServer.getInstance().getConnectionManager().getClientSSLListenerPort());
multiple = true;
}
if(HttpBindManager.getInstance().isHttpBindEnabled()) {
// ports for http-binding may not be strictly needed in here, but it doesn't hurt
if(HttpBindManager.getInstance().getHttpBindUnsecurePort() > 0) {
if(multiple) {
builder.append(",");
}
builder.append(HttpBindManager.getInstance().getHttpBindUnsecurePort());
multiple = true;
}
if(HttpBindManager.getInstance().getHttpBindSecurePort() > 0) {
if(multiple) {
builder.append(",");
}
builder.append(HttpBindManager.getInstance().getHttpBindSecurePort());
}
}
return builder;
}
private StringBuilder getSecure(StringBuilder builder) {
if (JiveGlobals.getBooleanProperty(CROSS_DOMAIN_SECURE_ENABLED,CROSS_DOMAIN_SECURE_DEFAULT)) {
builder.append("true");
} else {
builder.append("false");
}
return builder;
}
}
......@@ -25,6 +25,7 @@ import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.security.SslSelectChannelConnector;
import org.mortbay.jetty.servlet.ServletHandler;
import org.mortbay.jetty.webapp.WebAppContext;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.security.KeyStore;
......@@ -145,9 +146,20 @@ public final class HttpBindManager {
sslConnector.setTrustPassword(SSLConfig.getc2sTrustPassword());
sslConnector.setTruststoreType(SSLConfig.getStoreType());
sslConnector.setTruststore(SSLConfig.getc2sTruststoreLocation());
sslConnector.setNeedClientAuth(false);
sslConnector.setWantClientAuth(false);
// Set policy for checking client certificates
String certPol = JiveGlobals.getProperty("xmpp.client.cert.policy", "disabled");
if(certPol.equals("needed")) {
sslConnector.setNeedClientAuth(true);
sslConnector.setWantClientAuth(true);
} else if(certPol.equals("wanted")) {
sslConnector.setNeedClientAuth(false);
sslConnector.setWantClientAuth(true);
} else {
sslConnector.setNeedClientAuth(false);
sslConnector.setWantClientAuth(false);
}
sslConnector.setKeyPassword(SSLConfig.getKeyPassword());
sslConnector.setKeystoreType(SSLConfig.getStoreType());
sslConnector.setKeystore(SSLConfig.getKeystoreLocation());
......@@ -474,7 +486,7 @@ public final class HttpBindManager {
@Override
protected SSLContext createSSLContext() throws Exception {
return SSLConfig.getSSLContext();
return SSLConfig.getc2sSSLContext();
}
}
......
......@@ -11,29 +11,30 @@
package org.jivesoftware.openfire.http;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlPullParserException;
import org.jivesoftware.util.Log;
import org.jivesoftware.openfire.net.MXParser;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.dom4j.io.XMPPPacketReader;
import org.apache.commons.lang.StringEscapeUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.XMPPPacketReader;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.net.MXParser;
import org.jivesoftware.util.Log;
import org.mortbay.util.ajax.ContinuationSupport;
import org.apache.commons.lang.StringEscapeUtils;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import javax.servlet.ServletConfig;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.net.InetAddress;
import java.net.URLDecoder;
import java.security.cert.X509Certificate;
/**
* Servlet which handles requests to the HTTP binding service. It determines if there is currently
......@@ -261,7 +262,9 @@ public class HttpBindServlet extends HttpServlet {
}
try {
HttpConnection connection = new HttpConnection(rid, request.isSecure());
X509Certificate[] certificates =
(X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
HttpConnection connection = new HttpConnection(rid, request.isSecure(), certificates);
InetAddress address = InetAddress.getByName(request.getRemoteAddr());
connection.setSession(sessionManager.createSession(address, rootNode, connection));
respond(response, connection, request.getMethod());
......
......@@ -14,6 +14,8 @@ package org.jivesoftware.openfire.http;
import org.mortbay.util.ajax.Continuation;
import java.security.cert.X509Certificate;
/**
* Represents one HTTP connection with a client using the HTTP Binding service. The client will wait
* on {@link #getResponse()} until the server forwards a message to it or the wait time on the
......@@ -29,6 +31,7 @@ public class HttpConnection {
private boolean isClosed;
private boolean isSecure = false;
private boolean isDelivered;
private X509Certificate[] sslCertificates;
private static final String CONNECTION_CLOSED = "connection closed";
......@@ -37,11 +40,13 @@ public class HttpConnection {
*
* @param requestId the ID which uniquely identifies this request.
* @param isSecure true if this connection is using HTTPS
* @param sslCertificates list of certificates presented by the client.
*/
public HttpConnection(long requestId, boolean isSecure) {
public HttpConnection(long requestId, boolean isSecure, X509Certificate[] sslCertificates) {
this.requestId = requestId;
this.isSecure = isSecure;
this.isDelivered = false;
this.sslCertificates = sslCertificates;
}
/**
......@@ -165,6 +170,15 @@ public class HttpConnection {
public HttpSession getSession() {
return session;
}
/**
* Returns the peer certificates for this connection.
*
* @return the peer certificates for this connection or null.
*/
public X509Certificate[] getPeerCertificates() {
return sslCertificates;
}
void setContinuation(Continuation continuation) {
this.continuation = continuation;
......
......@@ -38,6 +38,8 @@ import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
......@@ -88,6 +90,7 @@ public class HttpSession extends LocalClientSession {
private int maxRequests;
private PacketDeliverer backupDeliverer;
private Double version = Double.NaN;
private X509Certificate[] sslCertificates;
private final Queue<Collection<Element>> packetsToSend = new LinkedList<Collection<Element>>();
// Semaphore which protects the packets to send, so, there can only be one consumer at a time.
......@@ -101,12 +104,13 @@ public class HttpSession extends LocalClientSession {
};
public HttpSession(PacketDeliverer backupDeliverer, String serverName, InetAddress address,
StreamID streamID, long rid) {
StreamID streamID, long rid, HttpConnection connection) {
super(serverName, null, streamID);
conn = new HttpVirtualConnection(address);
this.lastActivity = System.currentTimeMillis();
this.lastRequestID = rid;
this.backupDeliverer = backupDeliverer;
this.sslCertificates = connection.getPeerCertificates();
}
/**
......@@ -470,6 +474,15 @@ public class HttpSession extends LocalClientSession {
}
}
}
/**
* Return the X509Certificates associated with this session.
*
* @return the X509Certificate associated with this session.
*/
public X509Certificate[] getPeerCertificates() {
return sslCertificates;
}
/**
* Creates a new connection on this session. If a response is currently available for this
......@@ -491,7 +504,7 @@ public class HttpSession extends LocalClientSession {
boolean isSecure)
throws HttpConnectionClosedException, HttpBindException
{
HttpConnection connection = new HttpConnection(rid, isSecure);
HttpConnection connection = new HttpConnection(rid, isSecure, sslCertificates);
if (rid <= lastRequestID) {
Delivered deliverable = retrieveDeliverable(rid);
if (deliverable == null) {
......@@ -540,6 +553,8 @@ public class HttpSession extends LocalClientSession {
"connections on this session must be secured.", BoshBindingError.badRequest);
}
sslCertificates = connection.getPeerCertificates();
connection.setSession(this);
// We aren't supposed to hold connections open or we already have some packets waiting
// to be sent to the client.
......@@ -763,6 +778,10 @@ public class HttpSession extends LocalClientSession {
public void deliverRawText(String text) {
((HttpSession) session).deliver(text);
}
public Certificate[] getPeerCertificates() {
return ((HttpSession) session).getPeerCertificates();
}
}
private class Deliverable implements Comparable<Deliverable> {
......
......@@ -122,7 +122,7 @@ public class HttpSessionManager {
int hold = getIntAttribute(rootNode.attributeValue("hold"), 1);
double version = getDoubleAttribute(rootNode.attributeValue("ver"), 1.5);
HttpSession session = createSession(connection.getRequestId(), address);
HttpSession session = createSession(connection.getRequestId(), address, connection);
session.setWait(Math.min(wait, getMaxWait()));
session.setHold(hold);
session.setSecure(connection.isSecure());
......@@ -233,11 +233,11 @@ public class HttpSessionManager {
return connection;
}
private HttpSession createSession(long rid, InetAddress address) throws UnauthorizedException {
private HttpSession createSession(long rid, InetAddress address, HttpConnection connection) throws UnauthorizedException {
// Create a ClientSession for this user.
StreamID streamID = SessionManager.getInstance().nextStreamID();
// Send to the server that a new client session has been created
HttpSession session = sessionManager.createClientHttpSession(rid, address, streamID);
HttpSession session = sessionManager.createClientHttpSession(rid, address, streamID, connection);
// Register that the new session is associated with the specified stream ID
sessionMap.put(streamID.getID(), session);
session.addSessionCloseListener(sessionListener);
......
......@@ -18,10 +18,10 @@ import org.dom4j.Namespace;
import org.dom4j.QName;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.lockout.LockOutManager;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.AuthorizationManager;
import org.jivesoftware.openfire.lockout.LockOutManager;
import org.jivesoftware.openfire.session.*;
import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.JiveGlobals;
......@@ -29,16 +29,15 @@ import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.xmpp.packet.JID;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.*;
import java.net.UnknownHostException;
/**
* SASLAuthentication is responsible for returning the available SASL mechanisms to use and for
......@@ -458,19 +457,16 @@ public class SASLAuthentication {
}
// Check that hostname matches the one provided in a certificate
SocketConnection connection = (SocketConnection) session.getConnection();
try {
for (Certificate certificate : connection.getSSLSession().getPeerCertificates()) {
for (String identity : CertificateManager.getPeerIdentities((X509Certificate) certificate)) {
if (identity.equals(hostname) || identity.equals("*." + hostname)) {
authenticationSuccessful(session, hostname, null);
return Status.authenticated;
}
for (Certificate certificate : connection.getPeerCertificates()) {
for (String identity : CertificateManager.getPeerIdentities((X509Certificate) certificate)) {
if (identity.equals(hostname) || identity.equals("*." + hostname)) {
authenticationSuccessful(session, hostname, null);
return Status.authenticated;
}
}
}
catch (SSLPeerUnverifiedException e) {
Log.warn("Error retrieving client certificates of: " + session, e);
}
}
else if (session instanceof LocalClientSession) {
// Client EXTERNALL login
......@@ -481,18 +477,14 @@ public class SASLAuthentication {
String principal = "";
ArrayList<String> principals = new ArrayList<String>();
Connection connection = session.getConnection();
if (connection.getSSLSession() == null) {
Log.debug("SASLAuthentication: EXTERNAL authentication requested, but no SSL/TLS connection found.");
if (connection.getPeerCertificates().length < 1) {
Log.debug("SASLAuthentication: EXTERNAL authentication requested, but no certificates found.");
authenticationFailed(session);
return Status.failed;
}
try {
for (Certificate certificate : connection.getSSLSession().getPeerCertificates()) {
principals.addAll(CertificateManager.getPeerIdentities((X509Certificate)certificate));
}
}
catch (SSLPeerUnverifiedException e) {
Log.warn("Error retrieving client certificates of: " + session, e);
for (Certificate certificate : connection.getPeerCertificates()) {
principals.addAll(CertificateManager.getPeerIdentities((X509Certificate)certificate));
}
if(principals.size() == 1) {
......
......@@ -27,7 +27,7 @@ import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.xmpp.packet.Packet;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLPeerUnverifiedException;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
......@@ -35,6 +35,7 @@ import java.io.Writer;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.channels.Channels;
import java.security.cert.Certificate;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
......@@ -394,11 +395,16 @@ public class SocketConnection implements Connection {
this.flashClient = flashClient;
}
public SSLSession getSSLSession() {
public Certificate[] getPeerCertificates() {
if (tlsStreamHandler != null) {
return tlsStreamHandler.getSSLSession();
try {
return tlsStreamHandler.getSSLSession().getPeerCertificates();
} catch (SSLPeerUnverifiedException e ) {
Log.warn("Error retrieving client certificates of: " + tlsStreamHandler.getSSLSession(), e);
//pretend tlsStreamHandler is null
}
}
return null;
return new Certificate[0];
}
public PacketDeliverer getPacketDeliverer() {
......@@ -659,4 +665,4 @@ public class SocketConnection implements Connection {
public void setSocketReader(SocketReader socketReader) {
this.socketReader = socketReader;
}
}
\ No newline at end of file
}
......@@ -20,7 +20,7 @@ import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import javax.net.ssl.SSLSession;
import java.security.cert.Certificate;
import java.util.HashMap;
import java.util.Map;
......@@ -58,9 +58,9 @@ public abstract class VirtualConnection implements Connection {
return 0;
}
public SSLSession getSSLSession() {
public Certificate[] getPeerCertificates() {
// Ignore
return null;
return new Certificate[0];
}
public boolean isClosed() {
......
......@@ -21,11 +21,7 @@ import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.ConnectionCloseListener;
import org.jivesoftware.openfire.PacketDeliverer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.openfire.net.SSLJiveKeyManagerFactory;
import org.jivesoftware.openfire.net.SSLJiveTrustManagerFactory;
import org.jivesoftware.openfire.net.ServerTrustManager;
import org.jivesoftware.openfire.net.ClientTrustManager;
import org.jivesoftware.openfire.net.*;
import org.jivesoftware.openfire.session.LocalSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.JiveGlobals;
......@@ -33,15 +29,13 @@ import org.jivesoftware.util.Log;
import org.jivesoftware.util.XMLWriter;
import org.xmpp.packet.Packet;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.*;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.security.KeyStore;
import java.security.cert.Certificate;
/**
* Implementation of {@link Connection} inteface specific for NIO connections when using
......@@ -137,8 +131,16 @@ public class NIOConnection implements Connection {
return ((InetSocketAddress) ioSession.getRemoteAddress()).getAddress().getHostName();
}
public SSLSession getSSLSession() {
return (SSLSession) ioSession.getAttribute(SSLFilter.SSL_SESSION);
public Certificate[] getPeerCertificates() {
try {
SSLSession sslSession = (SSLSession) ioSession.getAttribute(SSLFilter.SSL_SESSION);
if (sslSession != null) {
return sslSession.getPeerCertificates();
}
} catch (SSLPeerUnverifiedException e) {
Log.warn("Error retrieving client certificates of: " + session, e);
}
return new Certificate[0];
}
public PacketDeliverer getPacketDeliverer() {
......
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