Commit b66f4bcf authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

The SubjectAltName certificate extension is now used to get server names. JM-564

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@3431 b35dd754-fafc-0310-a699-88a17e54d16e
parent db7d09c8
...@@ -14,9 +14,9 @@ package org.jivesoftware.wildfire.net; ...@@ -14,9 +14,9 @@ package org.jivesoftware.wildfire.net;
import org.dom4j.DocumentException; import org.dom4j.DocumentException;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.io.XMPPPacketReader; import org.dom4j.io.XMPPPacketReader;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.wildfire.ClientSession; import org.jivesoftware.wildfire.ClientSession;
import org.jivesoftware.wildfire.Session; import org.jivesoftware.wildfire.Session;
import org.jivesoftware.wildfire.XMPPServer; import org.jivesoftware.wildfire.XMPPServer;
...@@ -285,9 +285,8 @@ public class SASLAuthentication { ...@@ -285,9 +285,8 @@ public class SASLAuthentication {
hostname = StringUtils.decodeBase64(hostname); hostname = StringUtils.decodeBase64(hostname);
// Check that hostname matches the one provided in a certificate // Check that hostname matches the one provided in a certificate
for (Certificate certificate : connection.getSSLSession().getPeerCertificates()) { for (Certificate certificate : connection.getSSLSession().getPeerCertificates()) {
if (hostname if (TLSStreamHandler.getPeerIdentities((X509Certificate) certificate)
.equals(TLSStreamHandler.getPeerIdentity((X509Certificate) certificate))) .contains(hostname)) {
{
authenticationSuccessful(hostname); authenticationSuccessful(hostname);
return true; return true;
} }
......
...@@ -16,6 +16,7 @@ import java.security.cert.CertificateException; ...@@ -16,6 +16,7 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Date; import java.util.Date;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List;
/** /**
* ServerTrustManager is a Trust Manager that is only used for s2s connections. This TrustManager * ServerTrustManager is a Trust Manager that is only used for s2s connections. This TrustManager
...@@ -79,7 +80,7 @@ class ServerTrustManager implements X509TrustManager { ...@@ -79,7 +80,7 @@ class ServerTrustManager implements X509TrustManager {
if (verify) { if (verify) {
int nSize = x509Certificates.length; int nSize = x509Certificates.length;
String peerIdentity = TLSStreamHandler.getPeerIdentity(x509Certificates[0]); List<String> peerIdentities = TLSStreamHandler.getPeerIdentities(x509Certificates[0]);
if (JiveGlobals.getBooleanProperty("xmpp.server.certificate.verify.chain", true)) { if (JiveGlobals.getBooleanProperty("xmpp.server.certificate.verify.chain", true)) {
// Working down the chain, for every certificate in the chain, // Working down the chain, for every certificate in the chain,
...@@ -99,12 +100,12 @@ class ServerTrustManager implements X509TrustManager { ...@@ -99,12 +100,12 @@ class ServerTrustManager implements X509TrustManager {
} }
catch (GeneralSecurityException generalsecurityexception) { catch (GeneralSecurityException generalsecurityexception) {
throw new CertificateException( throw new CertificateException(
"signature verification failed of " + peerIdentity); "signature verification failed of " + peerIdentities);
} }
} }
else { else {
throw new CertificateException( throw new CertificateException(
"subject/issuer verification failed of " + peerIdentity); "subject/issuer verification failed of " + peerIdentities);
} }
} }
principalLast = principalSubject; principalLast = principalSubject;
...@@ -121,7 +122,7 @@ class ServerTrustManager implements X509TrustManager { ...@@ -121,7 +122,7 @@ class ServerTrustManager implements X509TrustManager {
.getBooleanProperty("xmpp.server.certificate.accept-selfsigned", false)) .getBooleanProperty("xmpp.server.certificate.accept-selfsigned", false))
{ {
Log.warn("Accepting self-signed certificate of remote server: " + Log.warn("Accepting self-signed certificate of remote server: " +
peerIdentity); peerIdentities);
trusted = true; trusted = true;
} }
} }
...@@ -129,23 +130,23 @@ class ServerTrustManager implements X509TrustManager { ...@@ -129,23 +130,23 @@ class ServerTrustManager implements X509TrustManager {
Log.error(e); Log.error(e);
} }
if (!trusted) { if (!trusted) {
throw new CertificateException("root certificate not trusted of " + peerIdentity); throw new CertificateException("root certificate not trusted of " + peerIdentities);
} }
} }
// Verify that the first certificate in the chain corresponds to // Verify that the first certificate in the chain corresponds to
// the server we desire to authenticate. // the server we desire to authenticate.
// Check if the certificate uses a wildcard indicating that subdomains are valid // Check if the certificate uses a wildcard indicating that subdomains are valid
if (peerIdentity.startsWith("*.")) { if (peerIdentities.size() == 1 && peerIdentities.get(0).startsWith("*.")) {
// Remove the wildcard // Remove the wildcard
peerIdentity = peerIdentity.replace("*.", ""); String peerIdentity = peerIdentities.get(0).replace("*.", "");
// Check if the requested subdomain matches the certified domain // Check if the requested subdomain matches the certified domain
if (!server.endsWith(peerIdentity)) { if (!server.endsWith(peerIdentity)) {
throw new CertificateException("target verification failed of " + peerIdentity); throw new CertificateException("target verification failed of " + peerIdentities);
} }
} }
else if (!server.equals(peerIdentity)) { else if (!peerIdentities.contains(server)) {
throw new CertificateException("target verification failed of " + peerIdentity); throw new CertificateException("target verification failed of " + peerIdentities);
} }
if (JiveGlobals.getBooleanProperty("xmpp.server.certificate.verify.validity", true)) { if (JiveGlobals.getBooleanProperty("xmpp.server.certificate.verify.validity", true)) {
...@@ -157,7 +158,7 @@ class ServerTrustManager implements X509TrustManager { ...@@ -157,7 +158,7 @@ class ServerTrustManager implements X509TrustManager {
x509Certificates[i].checkValidity(date); x509Certificates[i].checkValidity(date);
} }
catch (GeneralSecurityException generalsecurityexception) { catch (GeneralSecurityException generalsecurityexception) {
throw new CertificateException("invalid date of " + peerIdentity); throw new CertificateException("invalid date of " + peerIdentities);
} }
} }
} }
......
...@@ -11,6 +11,9 @@ ...@@ -11,6 +11,9 @@
package org.jivesoftware.wildfire.net; package org.jivesoftware.wildfire.net;
import org.bouncycastle.asn1.*;
import org.jivesoftware.util.Log;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLEngineResult.HandshakeStatus;
...@@ -18,14 +21,16 @@ import javax.net.ssl.SSLSession; ...@@ -18,14 +21,16 @@ import javax.net.ssl.SSLSession;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket; import java.net.Socket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
import java.nio.channels.WritableByteChannel; import java.nio.channels.WritableByteChannel;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.Principal; import java.util.*;
/** /**
* TLSStreamHandler is responsible for securing plain connections by negotiating TLS. By creating * TLSStreamHandler is responsible for securing plain connections by negotiating TLS. By creating
...@@ -77,21 +82,75 @@ public class TLSStreamHandler { ...@@ -77,21 +82,75 @@ public class TLSStreamHandler {
private static ByteBuffer hsBB = ByteBuffer.allocate(0); private static ByteBuffer hsBB = ByteBuffer.allocate(0);
/** /**
* Returns the identity of the remote server as defined in the specified certificate. The * Returns the identities 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 * identities are 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 * the subjectAltName extensions 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 * identities defined in the extension are going to be returned. Otherwise, the value stored in
* the subjectDN is returned. * the subjectDN is returned.
* *
* @param x509Certificate the certificate the holds the identity of the remote server. * @param x509Certificate the certificate the holds the identities of the remote server.
* @return the identity of the remote server as defined in the specified certificate. * @return the identities of the remote server as defined in the specified certificate.
*/
public static List<String> getPeerIdentities(X509Certificate x509Certificate) {
// Look the identity in the subjectAltName extension if available
List<String> names = getSubjectAlternativeNames(x509Certificate);
if (names.isEmpty()) {
String name = x509Certificate.getSubjectDN().getName();
name = name.replace("CN=", "");
// Create an array with the unique identity
names = new ArrayList<String>();
names.add(name);
}
return names;
}
/**
* Returns the JID representation of an XMPP entity contained as a SubjectAltName extension
* in the certificate. If none was found then return <tt>null</tt>.
*
* @param certificate the certificate presented by the remote entity.
* @return the JID representation of an XMPP entity contained as a SubjectAltName extension
* in the certificate. If none was found then return <tt>null</tt>.
*/ */
public static String getPeerIdentity(X509Certificate x509Certificate) { private static List<String> getSubjectAlternativeNames(X509Certificate certificate) {
Principal principalSubject = x509Certificate.getSubjectDN(); List<String> identities = new ArrayList<String>();
// TODO Look the identity in the subjectAltName extension if available try {
String name = principalSubject.getName(); Collection altNames = certificate.getSubjectAlternativeNames();
name = name.replace("CN=", ""); // Check that the certificate includes the SubjectAltName extension
return name; if (altNames == null) {
return Collections.emptyList();
}
// Use the type OtherName to search for the certified server name
for (Iterator lists=altNames.iterator(); lists.hasNext();) {
List item = (List) lists.next();
Integer type = (Integer) item.get(0);
if (type.intValue() == 0) {
// Type OtherName found so return the associated value
try {
// Value is encoded using ASN.1 so decode it to get the server's identity
ASN1InputStream decoder = new ASN1InputStream((byte[]) item.toArray()[1]);
DEREncodable encoded = decoder.readObject();
encoded = ((DERSequence) encoded).getObjectAt(1);
encoded = ((DERTaggedObject) encoded).getObject();
encoded = ((DERTaggedObject) encoded).getObject();
String identity = ((DERUTF8String) encoded).getString();
// Add the decoded server name to the list of identities
identities.add(identity);
}
catch (UnsupportedEncodingException e) {}
catch (IOException e) {}
}
// Other types are not good for XMPP so ignore them
if (Log.isDebugEnabled()) {
Log.debug("SubjectAltName of invalid type found: " + certificate);
}
}
}
catch (CertificateParsingException e) {
Log.error("Error parsing SubjectAltName in certificate: " + certificate, e);
}
return identities;
} }
/** /**
......
...@@ -8,22 +8,25 @@ ...@@ -8,22 +8,25 @@
- a copy of which is included in this distribution. - a copy of which is included in this distribution.
--%> --%>
<%@ page import="org.jivesoftware.util.*, <%@ page import="org.jivesoftware.util.JiveGlobals,
java.util.*, org.jivesoftware.util.ParamUtils,
org.jivesoftware.wildfire.net.SSLConfig, org.jivesoftware.wildfire.ClientSession,
java.security.KeyStore, org.jivesoftware.wildfire.Connection,
java.security.cert.CertificateFactory, org.jivesoftware.wildfire.ConnectionManager,
java.security.cert.Certificate, org.jivesoftware.wildfire.XMPPServer,
java.io.ByteArrayInputStream" org.jivesoftware.wildfire.net.SSLConfig"
errorPage="error.jsp" errorPage="error.jsp"
%> %>
<%@ page import="org.jivesoftware.wildfire.ClientSession"%>
<%@ page import="org.jivesoftware.wildfire.net.SocketConnection"%>
<%@ page import="org.jivesoftware.wildfire.XMPPServer"%>
<%@ page import="org.jivesoftware.wildfire.ConnectionManager"%>
<%@ page import="org.jivesoftware.wildfire.Connection"%>
<%@ page import="java.security.cert.X509Certificate"%>
<%@ page import="org.jivesoftware.wildfire.net.TLSStreamHandler"%> <%@ page import="org.jivesoftware.wildfire.net.TLSStreamHandler"%>
<%@ page import="java.io.ByteArrayInputStream"%>
<%@ page import="java.security.KeyStore"%>
<%@ page import="java.security.cert.Certificate"%>
<%@ page import="java.security.cert.CertificateFactory"%>
<%@ page import="java.security.cert.X509Certificate"%>
<%@ page import="java.util.Date"%>
<%@ page import="java.util.Enumeration"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Map"%>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %> <%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %> <%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
...@@ -562,11 +565,18 @@ ...@@ -562,11 +565,18 @@
i++; i++;
String a = (String)aliases.nextElement(); String a = (String)aliases.nextElement();
X509Certificate c = (X509Certificate) keyStore.getCertificate(a); X509Certificate c = (X509Certificate) keyStore.getCertificate(a);
StringBuffer identities = new StringBuffer();
for (String identity : TLSStreamHandler.getPeerIdentities(c)) {
identities.append(identity).append(", ");
}
if (identities.length() > 0) {
identities.setLength(identities.length() - 2);
}
%> %>
<tr valign="top"> <tr valign="top">
<td id="rs<%=i%>" width="1" rowspan="1"><%= (i) %>.</td> <td id="rs<%=i%>" width="1" rowspan="1"><%= (i) %>.</td>
<td> <td>
<%= TLSStreamHandler.getPeerIdentity(c) %> (<%= a %>) <%= identities.toString() %> (<%= a %>)
</td> </td>
<td> <td>
<% boolean expired = c.getNotAfter().before(new Date()); <% boolean expired = c.getNotAfter().before(new Date());
......
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