1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/conn/ssl/AbstractVerifier.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,366 @@ 1.4 +/* 1.5 + * ==================================================================== 1.6 + * Licensed to the Apache Software Foundation (ASF) under one 1.7 + * or more contributor license agreements. See the NOTICE file 1.8 + * distributed with this work for additional information 1.9 + * regarding copyright ownership. The ASF licenses this file 1.10 + * to you under the Apache License, Version 2.0 (the 1.11 + * "License"); you may not use this file except in compliance 1.12 + * with the License. You may obtain a copy of the License at 1.13 + * 1.14 + * http://www.apache.org/licenses/LICENSE-2.0 1.15 + * 1.16 + * Unless required by applicable law or agreed to in writing, 1.17 + * software distributed under the License is distributed on an 1.18 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 1.19 + * KIND, either express or implied. See the License for the 1.20 + * specific language governing permissions and limitations 1.21 + * under the License. 1.22 + * ==================================================================== 1.23 + * 1.24 + * This software consists of voluntary contributions made by many 1.25 + * individuals on behalf of the Apache Software Foundation. For more 1.26 + * information on the Apache Software Foundation, please see 1.27 + * <http://www.apache.org/>. 1.28 + * 1.29 + */ 1.30 + 1.31 +package ch.boye.httpclientandroidlib.conn.ssl; 1.32 + 1.33 +import ch.boye.httpclientandroidlib.annotation.Immutable; 1.34 + 1.35 +import ch.boye.httpclientandroidlib.conn.util.InetAddressUtils; 1.36 + 1.37 +import java.io.IOException; 1.38 +import java.io.InputStream; 1.39 +import java.security.cert.Certificate; 1.40 +import java.security.cert.CertificateParsingException; 1.41 +import java.security.cert.X509Certificate; 1.42 +import java.util.Arrays; 1.43 +import java.util.Collection; 1.44 +import java.util.Iterator; 1.45 +import java.util.LinkedList; 1.46 +import java.util.List; 1.47 +import java.util.Locale; 1.48 +import java.util.StringTokenizer; 1.49 +import java.util.logging.Logger; 1.50 +import java.util.logging.Level; 1.51 + 1.52 +import javax.net.ssl.SSLException; 1.53 +import javax.net.ssl.SSLSession; 1.54 +import javax.net.ssl.SSLSocket; 1.55 + 1.56 +/** 1.57 + * Abstract base class for all standard {@link X509HostnameVerifier} 1.58 + * implementations. 1.59 + * 1.60 + * @since 4.0 1.61 + */ 1.62 +@Immutable 1.63 +public abstract class AbstractVerifier implements X509HostnameVerifier { 1.64 + 1.65 + /** 1.66 + * This contains a list of 2nd-level domains that aren't allowed to 1.67 + * have wildcards when combined with country-codes. 1.68 + * For example: [*.co.uk]. 1.69 + * <p/> 1.70 + * The [*.co.uk] problem is an interesting one. Should we just hope 1.71 + * that CA's would never foolishly allow such a certificate to happen? 1.72 + * Looks like we're the only implementation guarding against this. 1.73 + * Firefox, Curl, Sun Java 1.4, 5, 6 don't bother with this check. 1.74 + */ 1.75 + private final static String[] BAD_COUNTRY_2LDS = 1.76 + { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info", 1.77 + "lg", "ne", "net", "or", "org" }; 1.78 + 1.79 + static { 1.80 + // Just in case developer forgot to manually sort the array. :-) 1.81 + Arrays.sort(BAD_COUNTRY_2LDS); 1.82 + } 1.83 + 1.84 + public AbstractVerifier() { 1.85 + super(); 1.86 + } 1.87 + 1.88 + public final void verify(String host, SSLSocket ssl) 1.89 + throws IOException { 1.90 + if(host == null) { 1.91 + throw new NullPointerException("host to verify is null"); 1.92 + } 1.93 + 1.94 + SSLSession session = ssl.getSession(); 1.95 + if(session == null) { 1.96 + // In our experience this only happens under IBM 1.4.x when 1.97 + // spurious (unrelated) certificates show up in the server' 1.98 + // chain. Hopefully this will unearth the real problem: 1.99 + InputStream in = ssl.getInputStream(); 1.100 + in.available(); 1.101 + /* 1.102 + If you're looking at the 2 lines of code above because 1.103 + you're running into a problem, you probably have two 1.104 + options: 1.105 + 1.106 + #1. Clean up the certificate chain that your server 1.107 + is presenting (e.g. edit "/etc/apache2/server.crt" 1.108 + or wherever it is your server's certificate chain 1.109 + is defined). 1.110 + 1.111 + OR 1.112 + 1.113 + #2. Upgrade to an IBM 1.5.x or greater JVM, or switch 1.114 + to a non-IBM JVM. 1.115 + */ 1.116 + 1.117 + // If ssl.getInputStream().available() didn't cause an 1.118 + // exception, maybe at least now the session is available? 1.119 + session = ssl.getSession(); 1.120 + if(session == null) { 1.121 + // If it's still null, probably a startHandshake() will 1.122 + // unearth the real problem. 1.123 + ssl.startHandshake(); 1.124 + 1.125 + // Okay, if we still haven't managed to cause an exception, 1.126 + // might as well go for the NPE. Or maybe we're okay now? 1.127 + session = ssl.getSession(); 1.128 + } 1.129 + } 1.130 + 1.131 + Certificate[] certs = session.getPeerCertificates(); 1.132 + X509Certificate x509 = (X509Certificate) certs[0]; 1.133 + verify(host, x509); 1.134 + } 1.135 + 1.136 + public final boolean verify(String host, SSLSession session) { 1.137 + try { 1.138 + Certificate[] certs = session.getPeerCertificates(); 1.139 + X509Certificate x509 = (X509Certificate) certs[0]; 1.140 + verify(host, x509); 1.141 + return true; 1.142 + } 1.143 + catch(SSLException e) { 1.144 + return false; 1.145 + } 1.146 + } 1.147 + 1.148 + public final void verify(String host, X509Certificate cert) 1.149 + throws SSLException { 1.150 + String[] cns = getCNs(cert); 1.151 + String[] subjectAlts = getSubjectAlts(cert, host); 1.152 + verify(host, cns, subjectAlts); 1.153 + } 1.154 + 1.155 + public final void verify(final String host, final String[] cns, 1.156 + final String[] subjectAlts, 1.157 + final boolean strictWithSubDomains) 1.158 + throws SSLException { 1.159 + 1.160 + // Build the list of names we're going to check. Our DEFAULT and 1.161 + // STRICT implementations of the HostnameVerifier only use the 1.162 + // first CN provided. All other CNs are ignored. 1.163 + // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way). 1.164 + LinkedList<String> names = new LinkedList<String>(); 1.165 + if(cns != null && cns.length > 0 && cns[0] != null) { 1.166 + names.add(cns[0]); 1.167 + } 1.168 + if(subjectAlts != null) { 1.169 + for (String subjectAlt : subjectAlts) { 1.170 + if (subjectAlt != null) { 1.171 + names.add(subjectAlt); 1.172 + } 1.173 + } 1.174 + } 1.175 + 1.176 + if(names.isEmpty()) { 1.177 + String msg = "Certificate for <" + host + "> doesn't contain CN or DNS subjectAlt"; 1.178 + throw new SSLException(msg); 1.179 + } 1.180 + 1.181 + // StringBuilder for building the error message. 1.182 + StringBuilder buf = new StringBuilder(); 1.183 + 1.184 + // We're can be case-insensitive when comparing the host we used to 1.185 + // establish the socket to the hostname in the certificate. 1.186 + String hostName = host.trim().toLowerCase(Locale.ENGLISH); 1.187 + boolean match = false; 1.188 + for(Iterator<String> it = names.iterator(); it.hasNext();) { 1.189 + // Don't trim the CN, though! 1.190 + String cn = it.next(); 1.191 + cn = cn.toLowerCase(Locale.ENGLISH); 1.192 + // Store CN in StringBuilder in case we need to report an error. 1.193 + buf.append(" <"); 1.194 + buf.append(cn); 1.195 + buf.append('>'); 1.196 + if(it.hasNext()) { 1.197 + buf.append(" OR"); 1.198 + } 1.199 + 1.200 + // The CN better have at least two dots if it wants wildcard 1.201 + // action. It also can't be [*.co.uk] or [*.co.jp] or 1.202 + // [*.org.uk], etc... 1.203 + String parts[] = cn.split("\\."); 1.204 + boolean doWildcard = parts.length >= 3 && 1.205 + parts[0].endsWith("*") && 1.206 + acceptableCountryWildcard(cn) && 1.207 + !isIPAddress(host); 1.208 + 1.209 + if(doWildcard) { 1.210 + if (parts[0].length() > 1) { // e.g. server* 1.211 + String prefix = parts[0].substring(0, parts.length-2); // e.g. server 1.212 + String suffix = cn.substring(parts[0].length()); // skip wildcard part from cn 1.213 + String hostSuffix = hostName.substring(prefix.length()); // skip wildcard part from host 1.214 + match = hostName.startsWith(prefix) && hostSuffix.endsWith(suffix); 1.215 + } else { 1.216 + match = hostName.endsWith(cn.substring(1)); 1.217 + } 1.218 + if(match && strictWithSubDomains) { 1.219 + // If we're in strict mode, then [*.foo.com] is not 1.220 + // allowed to match [a.b.foo.com] 1.221 + match = countDots(hostName) == countDots(cn); 1.222 + } 1.223 + } else { 1.224 + match = hostName.equals(cn); 1.225 + } 1.226 + if(match) { 1.227 + break; 1.228 + } 1.229 + } 1.230 + if(!match) { 1.231 + throw new SSLException("hostname in certificate didn't match: <" + host + "> !=" + buf); 1.232 + } 1.233 + } 1.234 + 1.235 + public static boolean acceptableCountryWildcard(String cn) { 1.236 + String parts[] = cn.split("\\."); 1.237 + if (parts.length != 3 || parts[2].length() != 2) { 1.238 + return true; // it's not an attempt to wildcard a 2TLD within a country code 1.239 + } 1.240 + return Arrays.binarySearch(BAD_COUNTRY_2LDS, parts[1]) < 0; 1.241 + } 1.242 + 1.243 + public static String[] getCNs(X509Certificate cert) { 1.244 + LinkedList<String> cnList = new LinkedList<String>(); 1.245 + /* 1.246 + Sebastian Hauer's original StrictSSLProtocolSocketFactory used 1.247 + getName() and had the following comment: 1.248 + 1.249 + Parses a X.500 distinguished name for the value of the 1.250 + "Common Name" field. This is done a bit sloppy right 1.251 + now and should probably be done a bit more according to 1.252 + <code>RFC 2253</code>. 1.253 + 1.254 + I've noticed that toString() seems to do a better job than 1.255 + getName() on these X500Principal objects, so I'm hoping that 1.256 + addresses Sebastian's concern. 1.257 + 1.258 + For example, getName() gives me this: 1.259 + 1.2.840.113549.1.9.1=#16166a756c6975736461766965734063756362632e636f6d 1.260 + 1.261 + whereas toString() gives me this: 1.262 + EMAILADDRESS=juliusdavies@cucbc.com 1.263 + 1.264 + Looks like toString() even works with non-ascii domain names! 1.265 + I tested it with "花子.co.jp" and it worked fine. 1.266 + */ 1.267 + String subjectPrincipal = cert.getSubjectX500Principal().toString(); 1.268 + StringTokenizer st = new StringTokenizer(subjectPrincipal, ","); 1.269 + while(st.hasMoreTokens()) { 1.270 + String tok = st.nextToken(); 1.271 + int x = tok.indexOf("CN="); 1.272 + if(x >= 0) { 1.273 + cnList.add(tok.substring(x + 3)); 1.274 + } 1.275 + } 1.276 + if(!cnList.isEmpty()) { 1.277 + String[] cns = new String[cnList.size()]; 1.278 + cnList.toArray(cns); 1.279 + return cns; 1.280 + } else { 1.281 + return null; 1.282 + } 1.283 + } 1.284 + 1.285 + /** 1.286 + * Extracts the array of SubjectAlt DNS or IP names from an X509Certificate. 1.287 + * Returns null if there aren't any. 1.288 + * 1.289 + * @param cert X509Certificate 1.290 + * @param hostname 1.291 + * @return Array of SubjectALT DNS or IP names stored in the certificate. 1.292 + */ 1.293 + private static String[] getSubjectAlts( 1.294 + final X509Certificate cert, final String hostname) { 1.295 + int subjectType; 1.296 + if (isIPAddress(hostname)) { 1.297 + subjectType = 7; 1.298 + } else { 1.299 + subjectType = 2; 1.300 + } 1.301 + 1.302 + LinkedList<String> subjectAltList = new LinkedList<String>(); 1.303 + Collection<List<?>> c = null; 1.304 + try { 1.305 + c = cert.getSubjectAlternativeNames(); 1.306 + } 1.307 + catch(CertificateParsingException cpe) { 1.308 + Logger.getLogger(AbstractVerifier.class.getName()) 1.309 + .log(Level.FINE, "Error parsing certificate.", cpe); 1.310 + } 1.311 + if(c != null) { 1.312 + for (List<?> aC : c) { 1.313 + List<?> list = aC; 1.314 + int type = ((Integer) list.get(0)).intValue(); 1.315 + if (type == subjectType) { 1.316 + String s = (String) list.get(1); 1.317 + subjectAltList.add(s); 1.318 + } 1.319 + } 1.320 + } 1.321 + if(!subjectAltList.isEmpty()) { 1.322 + String[] subjectAlts = new String[subjectAltList.size()]; 1.323 + subjectAltList.toArray(subjectAlts); 1.324 + return subjectAlts; 1.325 + } else { 1.326 + return null; 1.327 + } 1.328 + } 1.329 + 1.330 + /** 1.331 + * Extracts the array of SubjectAlt DNS names from an X509Certificate. 1.332 + * Returns null if there aren't any. 1.333 + * <p/> 1.334 + * Note: Java doesn't appear able to extract international characters 1.335 + * from the SubjectAlts. It can only extract international characters 1.336 + * from the CN field. 1.337 + * <p/> 1.338 + * (Or maybe the version of OpenSSL I'm using to test isn't storing the 1.339 + * international characters correctly in the SubjectAlts?). 1.340 + * 1.341 + * @param cert X509Certificate 1.342 + * @return Array of SubjectALT DNS names stored in the certificate. 1.343 + */ 1.344 + public static String[] getDNSSubjectAlts(X509Certificate cert) { 1.345 + return getSubjectAlts(cert, null); 1.346 + } 1.347 + 1.348 + /** 1.349 + * Counts the number of dots "." in a string. 1.350 + * @param s string to count dots from 1.351 + * @return number of dots 1.352 + */ 1.353 + public static int countDots(final String s) { 1.354 + int count = 0; 1.355 + for(int i = 0; i < s.length(); i++) { 1.356 + if(s.charAt(i) == '.') { 1.357 + count++; 1.358 + } 1.359 + } 1.360 + return count; 1.361 + } 1.362 + 1.363 + private static boolean isIPAddress(final String hostname) { 1.364 + return hostname != null && 1.365 + (InetAddressUtils.isIPv4Address(hostname) || 1.366 + InetAddressUtils.isIPv6Address(hostname)); 1.367 + } 1.368 + 1.369 +}