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             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