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