1package eu.siacs.conversations.crypto;
2
3import android.util.Log;
4
5import org.bouncycastle.asn1.ASN1Primitive;
6import org.bouncycastle.asn1.DERIA5String;
7import org.bouncycastle.asn1.DERTaggedObject;
8import org.bouncycastle.asn1.DERUTF8String;
9import org.bouncycastle.asn1.DLSequence;
10import org.bouncycastle.asn1.x500.RDN;
11import org.bouncycastle.asn1.x500.X500Name;
12import org.bouncycastle.asn1.x500.style.BCStyle;
13import org.bouncycastle.asn1.x500.style.IETFUtils;
14import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
15
16import java.io.IOException;
17import java.security.cert.Certificate;
18import java.security.cert.X509Certificate;
19import java.util.ArrayList;
20import java.util.Collection;
21import java.util.List;
22
23import javax.net.ssl.HostnameVerifier;
24import javax.net.ssl.SSLSession;
25
26public class XmppDomainVerifier implements HostnameVerifier {
27
28 private final String LOGTAG = "XmppDomainVerifier";
29
30 private final String SRVName = "1.3.6.1.5.5.7.8.7";
31 private final String xmppAddr = "1.3.6.1.5.5.7.8.5";
32
33 @Override
34 public boolean verify(String domain, SSLSession sslSession) {
35 try {
36 Certificate[] chain = sslSession.getPeerCertificates();
37 if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) {
38 return false;
39 }
40 X509Certificate certificate = (X509Certificate) chain[0];
41 Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames();
42 List<String> xmppAddrs = new ArrayList<>();
43 List<String> srvNames = new ArrayList<>();
44 List<String> domains = new ArrayList<>();
45 if (alternativeNames != null) {
46 for(List<?> san : alternativeNames) {
47 Integer type = (Integer) san.get(0);
48 if (type == 0) {
49 try {
50 ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray((byte[]) san.get(1));
51 if (asn1Primitive instanceof DERTaggedObject) {
52 ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject();
53 if (inner instanceof DLSequence) {
54 DLSequence sequence = (DLSequence) inner;
55 if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) {
56 String oid = sequence.getObjectAt(0).toString();
57 ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject();
58 switch (oid) {
59 case xmppAddr:
60 if (value instanceof DERUTF8String) {
61 xmppAddrs.add(((DERUTF8String) value).getString());
62 } else if (value instanceof DERIA5String) {
63 xmppAddrs.add(((DERIA5String) value).getString());
64 }
65 break;
66 case SRVName:
67 if (value instanceof DERUTF8String) {
68 srvNames.add(((DERUTF8String) value).getString());
69 } else if (value instanceof DERIA5String) {
70 srvNames.add(((DERIA5String) value).getString());
71 }
72 break;
73 default:
74 Log.d(LOGTAG,"value was of type:"+value.getClass().getName()+ " oid was:"+oid);
75 }
76 }
77 }
78 }
79 } catch (IOException e) {
80 //ignored
81 }
82 } else if (type == 2) {
83 Object value = san.get(1);
84 if (value instanceof String) {
85 domains.add((String) value);
86 }
87 }
88 }
89 }
90 if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) {
91 X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject();
92 RDN[] rdns = x500name.getRDNs(BCStyle.CN);
93 for(int i = 0; i < rdns.length; ++i) {
94 domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue()));
95 }
96 }
97 Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains:" + domains);
98 return xmppAddrs.contains(domain) || srvNames.contains("_xmpp-client."+domain) || matchDomain(domain, domains);
99 } catch (Exception e) {
100 return false;
101 }
102 }
103
104 private boolean matchDomain(String needle, List<String> haystack) {
105 for(String entry : haystack) {
106 if (entry.startsWith("*.")) {
107 int i = needle.indexOf('.');
108 Log.d(LOGTAG,"comparing "+needle.substring(i)+ " and "+entry.substring(1));
109 if (i != -1 && needle.substring(i).equals(entry.substring(1))) {
110 Log.d(LOGTAG,"domain "+needle+" matched "+entry);
111 return true;
112 }
113 } else {
114 if (entry.equals(needle)) {
115 Log.d(LOGTAG,"domain "+needle+" matched "+entry);
116 return true;
117 }
118 }
119 }
120 return false;
121 }
122}