Home | History | Annotate | Download | only in ssl
      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