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 // RFC 2818 advises using the most specific name for matching. 84 String cn = new DistinguishedNameParser(principal).findMostSpecific("cn"); 85 if (cn != null) { 86 return verifyHostName(hostName, cn); 87 } 88 } 89 90 return false; 91 } 92 93 private List<String> getSubjectAltNames(X509Certificate certificate, int type) { 94 List<String> result = new ArrayList<String>(); 95 try { 96 Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames(); 97 if (subjectAltNames == null) { 98 return Collections.emptyList(); 99 } 100 for (Object subjectAltName : subjectAltNames) { 101 List<?> entry = (List<?>) subjectAltName; 102 if (entry == null || entry.size() < 2) { 103 continue; 104 } 105 Integer altNameType = (Integer) entry.get(0); 106 if (altNameType == null) { 107 continue; 108 } 109 if (altNameType == type) { 110 String altName = (String) entry.get(1); 111 if (altName != null) { 112 result.add(altName); 113 } 114 } 115 } 116 return result; 117 } catch (CertificateParsingException e) { 118 return Collections.emptyList(); 119 } 120 } 121 122 /** 123 * Returns true if {@code hostName} matches the name or pattern {@code cn}. 124 * 125 * @param hostName lowercase host name. 126 * @param cn certificate host name. May include wildcards like 127 * {@code *.android.com}. 128 */ 129 public boolean verifyHostName(String hostName, String cn) { 130 if (hostName == null || hostName.isEmpty() || cn == null || cn.isEmpty()) { 131 return false; 132 } 133 134 cn = cn.toLowerCase(Locale.US); 135 136 if (!cn.contains("*")) { 137 return hostName.equals(cn); 138 } 139 140 if (cn.startsWith("*.") && hostName.regionMatches(0, cn, 2, cn.length() - 2)) { 141 return true; // "*.foo.com" matches "foo.com" 142 } 143 144 int asterisk = cn.indexOf('*'); 145 int dot = cn.indexOf('.'); 146 if (asterisk > dot) { 147 return false; // malformed; wildcard must be in the first part of the cn 148 } 149 150 if (!hostName.regionMatches(0, cn, 0, asterisk)) { 151 return false; // prefix before '*' doesn't match 152 } 153 154 int suffixLength = cn.length() - (asterisk + 1); 155 int suffixStart = hostName.length() - suffixLength; 156 if (hostName.indexOf('.', asterisk) < suffixStart) { 157 // TODO: remove workaround for *.clients.google.com http://b/5426333 158 if (!hostName.endsWith(".clients.google.com")) { 159 return false; // wildcard '*' can't match a '.' 160 } 161 } 162 163 if (!hostName.regionMatches(suffixStart, cn, asterisk + 1, suffixLength)) { 164 return false; // suffix after '*' doesn't match 165 } 166 167 return true; 168 } 169 } 170