1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package javax.net.ssl; 19 20 import java.net.InetAddress; 21 import java.security.cert.Certificate; 22 import java.security.cert.CertificateParsingException; 23 import java.security.cert.X509Certificate; 24 import java.util.ArrayList; 25 import java.util.Collection; 26 import java.util.Collections; 27 import java.util.List; 28 import java.util.Locale; 29 import javax.security.auth.x500.X500Principal; 30 31 /** 32 * A HostnameVerifier consistent with <a 33 * href="http://www.ietf.org/rfc/rfc2818.txt">RFC 2818</a>. 34 * 35 * @hide accessible via HttpsURLConnection.getDefaultHostnameVerifier() 36 */ 37 public final class DefaultHostnameVerifier implements HostnameVerifier { 38 private static final int ALT_DNS_NAME = 2; 39 private static final int ALT_IPA_NAME = 7; 40 41 public final boolean verify(String host, SSLSession session) { 42 try { 43 Certificate[] certificates = session.getPeerCertificates(); 44 return verify(host, (X509Certificate) certificates[0]); 45 } catch (SSLException e) { 46 return false; 47 } 48 } 49 50 public boolean verify(String host, X509Certificate certificate) { 51 return InetAddress.isNumeric(host) 52 ? verifyIpAddress(host, certificate) 53 : verifyHostName(host, certificate); 54 } 55 56 /** 57 * Returns true if {@code certificate} matches {@code ipAddress}. 58 */ 59 private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) { 60 for (String altName : getSubjectAltNames(certificate, ALT_IPA_NAME)) { 61 if (ipAddress.equalsIgnoreCase(altName)) { 62 return true; 63 } 64 } 65 return false; 66 } 67 68 /** 69 * Returns true if {@code certificate} matches {@code hostName}. 70 */ 71 private boolean verifyHostName(String hostName, X509Certificate certificate) { 72 hostName = hostName.toLowerCase(Locale.US); 73 boolean hasDns = false; 74 for (String altName : getSubjectAltNames(certificate, ALT_DNS_NAME)) { 75 hasDns = true; 76 if (verifyHostName(hostName, altName)) { 77 return true; 78 } 79 } 80 81 if (!hasDns) { 82 X500Principal principal = certificate.getSubjectX500Principal(); 83 String cn = new DistinguishedNameParser(principal).find("cn"); 84 if (cn != null) { 85 return verifyHostName(hostName, cn); 86 } 87 } 88 89 return false; 90 } 91 92 private List<String> getSubjectAltNames(X509Certificate certificate, int type) { 93 List<String> result = new ArrayList<String>(); 94 try { 95 Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames(); 96 if (subjectAltNames == null) { 97 return Collections.emptyList(); 98 } 99 for (Object subjectAltName : subjectAltNames) { 100 List<?> entry = (List<?>) subjectAltName; 101 if (entry == null || entry.size() < 2) { 102 continue; 103 } 104 Integer altNameType = (Integer) entry.get(0); 105 if (altNameType == null) { 106 continue; 107 } 108 if (altNameType == type) { 109 String altName = (String) entry.get(1); 110 if (altName != null) { 111 result.add(altName); 112 } 113 } 114 } 115 return result; 116 } catch (CertificateParsingException e) { 117 return Collections.emptyList(); 118 } 119 } 120 121 /** 122 * Returns true if {@code hostName} matches the name or pattern {@code cn}. 123 * 124 * @param hostName lowercase host name. 125 * @param cn certificate host name. May include wildcards like 126 * {@code *.android.com}. 127 */ 128 public boolean verifyHostName(String hostName, String cn) { 129 if (hostName == null || hostName.isEmpty() || cn == null || cn.isEmpty()) { 130 return false; 131 } 132 133 cn = cn.toLowerCase(Locale.US); 134 135 if (!cn.contains("*")) { 136 return hostName.equals(cn); 137 } 138 139 if (cn.startsWith("*.") && hostName.regionMatches(0, cn, 2, cn.length() - 2)) { 140 return true; // "*.foo.com" matches "foo.com" 141 } 142 143 int asterisk = cn.indexOf('*'); 144 int dot = cn.indexOf('.'); 145 if (asterisk > dot) { 146 return false; // malformed; wildcard must be in the first part of the cn 147 } 148 149 if (!hostName.regionMatches(0, cn, 0, asterisk)) { 150 return false; // prefix before '*' doesn't match 151 } 152 153 int suffixLength = cn.length() - (asterisk + 1); 154 int suffixStart = hostName.length() - suffixLength; 155 if (hostName.indexOf('.', asterisk) < suffixStart) { 156 // TODO: remove workaround for *.clients.google.com http://b/5426333 157 if (!hostName.endsWith(".clients.google.com")) { 158 return false; // wildcard '*' can't match a '.' 159 } 160 } 161 162 if (!hostName.regionMatches(suffixStart, cn, asterisk + 1, suffixLength)) { 163 return false; // suffix after '*' doesn't match 164 } 165 166 return true; 167 } 168 } 169