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