xmpp-compliance-tester

XMPP Compliance Tester, forked from github.com/iNPUTmice/ComplianceTester
git clone https://git.in0rdr.ch/xmpp-compliance-tester.git
Log | Files | Refs | Pull requests |Archive | README | LICENSE

commit 7a349c97da253ad481768e9712fb38c5270f408f
parent c7109d424d96e4083344d4eb65a26afc5399315e
Author: Daniel Gultsch <daniel@gultsch.de>
Date:   Tue, 24 Jan 2017 14:55:39 +0100

set sni hostname for for xep-0368 + actual cert verification

Diffstat:
Mpom.xml | 15+++++++++++++++
Msrc/main/java/eu/siacs/compliance/tests/XmppOverTls.java | 18++++++++++++++++--
Asrc/main/java/eu/siacs/utils/XmppDomainVerifier.java | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 162 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml @@ -27,6 +27,11 @@ <artifactId>minidns-hla</artifactId> <version>0.2.0</version> </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk15on</artifactId> + <version>1.56</version> + </dependency> </dependencies> <build> @@ -50,6 +55,16 @@ <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> </transformers> + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> </configuration> </execution> </executions> diff --git a/src/main/java/eu/siacs/compliance/tests/XmppOverTls.java b/src/main/java/eu/siacs/compliance/tests/XmppOverTls.java @@ -4,11 +4,15 @@ import de.measite.minidns.hla.ResolverApi; import de.measite.minidns.hla.ResolverResult; import de.measite.minidns.record.SRV; import eu.siacs.compliance.Result; +import eu.siacs.utils.XmppDomainVerifier; import rocks.xmpp.core.session.XmppClient; +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import java.io.IOException; +import java.util.Arrays; public class XmppOverTls extends AbstractTest { @@ -18,22 +22,32 @@ public class XmppOverTls extends AbstractTest { @Override public Result run() { - String domain = client.getDomain().getDomain(); + final String domain = client.getDomain().getDomain(); + final SSLParameters parameters = new SSLParameters(); + parameters.setServerNames(Arrays.asList(new SNIHostName(domain))); try { final SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); ResolverResult<SRV> results = ResolverApi.INSTANCE.resolve("_xmpps-client._tcp." + domain, SRV.class); for(SRV record : results.getAnswers()) { try { SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(record.name.toString(),record.port); + socket.setSSLParameters(parameters); socket.setSoTimeout(1000); socket.startHandshake(); + final Result result; + if (XmppDomainVerifier.getInstance().verify(domain,socket.getSession())) { + result = Result.PASSED; + } else { + result = Result.FAILED; + } socket.close(); - return Result.PASSED; + return result; } catch (IOException e) { //ignored } } } catch (Exception e) { + e.printStackTrace(); return Result.FAILED; } return Result.FAILED; diff --git a/src/main/java/eu/siacs/utils/XmppDomainVerifier.java b/src/main/java/eu/siacs/utils/XmppDomainVerifier.java @@ -0,0 +1,131 @@ +package eu.siacs.utils; + +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.DERIA5String; +import org.bouncycastle.asn1.DERTaggedObject; +import org.bouncycastle.asn1.DERUTF8String; +import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; + +import java.io.IOException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +public class XmppDomainVerifier implements HostnameVerifier { + + private static XmppDomainVerifier instance = new XmppDomainVerifier(); + + private final static String SRVName = "1.3.6.1.5.5.7.8.7"; + private final static String xmppAddr = "1.3.6.1.5.5.7.8.5"; + + @Override + public boolean verify(String domain, SSLSession sslSession) { + try { + Certificate[] chain = sslSession.getPeerCertificates(); + if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) { + return false; + } + X509Certificate certificate = (X509Certificate) chain[0]; + Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames(); + List<String> xmppAddrs = new ArrayList<>(); + List<String> srvNames = new ArrayList<>(); + List<String> domains = new ArrayList<>(); + if (alternativeNames != null) { + for (List<?> san : alternativeNames) { + Integer type = (Integer) san.get(0); + if (type == 0) { + OtherName otherName = parseOtherName((byte[]) san.get(1)); + if (otherName != null) { + switch (otherName.oid) { + case SRVName: + srvNames.add(otherName.value); + break; + case xmppAddr: + xmppAddrs.add(otherName.value); + break; + } + } + } else if (type == 2) { + Object value = san.get(1); + if (value instanceof String) { + domains.add((String) value); + } + } + } + } + if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) { + X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); + RDN[] rdns = x500name.getRDNs(BCStyle.CN); + for (int i = 0; i < rdns.length; ++i) { + domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue())); + } + } + return xmppAddrs.contains(domain) || srvNames.contains("_xmpp-client." + domain) || matchDomain(domain, domains); + } catch (Exception e) { + return false; + } + } + + private static OtherName parseOtherName(byte[] otherName) { + try { + ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName); + if (asn1Primitive instanceof DERTaggedObject) { + ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject(); + if (inner instanceof DLSequence) { + DLSequence sequence = (DLSequence) inner; + if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) { + String oid = sequence.getObjectAt(0).toString(); + ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject(); + if (value instanceof DERUTF8String) { + return new OtherName(oid, ((DERUTF8String) value).getString()); + } else if (value instanceof DERIA5String) { + return new OtherName(oid, ((DERIA5String) value).getString()); + } + } + } + } + return null; + } catch (IOException e) { + return null; + } + } + + private static boolean matchDomain(String needle, List<String> haystack) { + for (String entry : haystack) { + if (entry.startsWith("*.")) { + int i = needle.indexOf('.'); + if (i != -1 && needle.substring(i).equals(entry.substring(1))) { + return true; + } + } else { + if (entry.equals(needle)) { + return true; + } + } + } + return false; + } + + public static XmppDomainVerifier getInstance() { + return instance; + } + + private static class OtherName { + private final String oid; + private final String value; + private OtherName(String oid, String value) { + this.oid = oid; + this.value = value; + } + } +}