• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
verify(String host, SSLSession session)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 
verify(String host, X509Certificate certificate)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      */
verifyIpAddress(String ipAddress, X509Certificate certificate)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      */
verifyHostName(String hostName, X509Certificate certificate)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 
getSubjectAltNames(X509Certificate certificate, int type)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      */
verifyHostName(String hostName, String cn)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