1adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project/*
2adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  Licensed to the Apache Software Foundation (ASF) under one or more
3adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  contributor license agreements.  See the NOTICE file distributed with
4adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  this work for additional information regarding copyright ownership.
5adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  The ASF licenses this file to You under the Apache License, Version 2.0
6adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  (the "License"); you may not use this file except in compliance with
7adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  the License.  You may obtain a copy of the License at
8adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *
9adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *     http://www.apache.org/licenses/LICENSE-2.0
10adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *
11adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  Unless required by applicable law or agreed to in writing, software
12adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  distributed under the License is distributed on an "AS IS" BASIS,
13adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  See the License for the specific language governing permissions and
15adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  limitations under the License.
16adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */
17adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
18adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectpackage javax.net.ssl;
19adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
206767bdbe6bb1d4542c97868d8df1f71d2414fc62Jesse Wilsonimport java.net.InetAddress;
21adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.security.cert.Certificate;
22adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.security.cert.CertificateParsingException;
23adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.security.cert.X509Certificate;
246767bdbe6bb1d4542c97868d8df1f71d2414fc62Jesse Wilsonimport java.util.ArrayList;
25adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.util.Collection;
266767bdbe6bb1d4542c97868d8df1f71d2414fc62Jesse Wilsonimport java.util.Collections;
27adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.util.List;
28adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.util.Locale;
2957d73e33dc039f6fff06db52106a358192868060Jesse Wilsonimport javax.security.auth.x500.X500Principal;
30adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
31adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project/**
3257d73e33dc039f6fff06db52106a358192868060Jesse Wilson * A HostnameVerifier consistent with <a
3357d73e33dc039f6fff06db52106a358192868060Jesse Wilson * href="http://www.ietf.org/rfc/rfc2818.txt">RFC 2818</a>.
346767bdbe6bb1d4542c97868d8df1f71d2414fc62Jesse Wilson *
3557d73e33dc039f6fff06db52106a358192868060Jesse Wilson * @hide accessible via HttpsURLConnection.getDefaultHostnameVerifier()
36adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */
3757d73e33dc039f6fff06db52106a358192868060Jesse Wilsonpublic final class DefaultHostnameVerifier implements HostnameVerifier {
3857d73e33dc039f6fff06db52106a358192868060Jesse Wilson    private static final int ALT_DNS_NAME = 2;
3957d73e33dc039f6fff06db52106a358192868060Jesse Wilson    private static final int ALT_IPA_NAME = 7;
40adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
41adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public final boolean verify(String host, SSLSession session) {
42adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        try {
4357d73e33dc039f6fff06db52106a358192868060Jesse Wilson            Certificate[] certificates = session.getPeerCertificates();
4457d73e33dc039f6fff06db52106a358192868060Jesse Wilson            return verify(host, (X509Certificate) certificates[0]);
45735f93475d1d54ad7b2d1c2376adc0fcf4d4c7a1Brian Carlstrom        } catch (SSLException e) {
46adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            return false;
47adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
4857d73e33dc039f6fff06db52106a358192868060Jesse Wilson    }
49adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
5057d73e33dc039f6fff06db52106a358192868060Jesse Wilson    public boolean verify(String host, X509Certificate certificate) {
5157d73e33dc039f6fff06db52106a358192868060Jesse Wilson        return InetAddress.isNumeric(host)
5257d73e33dc039f6fff06db52106a358192868060Jesse Wilson                ? verifyIpAddress(host, certificate)
5357d73e33dc039f6fff06db52106a358192868060Jesse Wilson                : verifyHostName(host, certificate);
5457d73e33dc039f6fff06db52106a358192868060Jesse Wilson    }
55adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
5657d73e33dc039f6fff06db52106a358192868060Jesse Wilson    /**
5757d73e33dc039f6fff06db52106a358192868060Jesse Wilson     * Returns true if {@code certificate} matches {@code ipAddress}.
5857d73e33dc039f6fff06db52106a358192868060Jesse Wilson     */
5957d73e33dc039f6fff06db52106a358192868060Jesse Wilson    private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) {
6057d73e33dc039f6fff06db52106a358192868060Jesse Wilson        for (String altName : getSubjectAltNames(certificate, ALT_IPA_NAME)) {
6157d73e33dc039f6fff06db52106a358192868060Jesse Wilson            if (ipAddress.equalsIgnoreCase(altName)) {
6257d73e33dc039f6fff06db52106a358192868060Jesse Wilson                return true;
6357d73e33dc039f6fff06db52106a358192868060Jesse Wilson            }
646767bdbe6bb1d4542c97868d8df1f71d2414fc62Jesse Wilson        }
6557d73e33dc039f6fff06db52106a358192868060Jesse Wilson        return false;
6657d73e33dc039f6fff06db52106a358192868060Jesse Wilson    }
67adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
6857d73e33dc039f6fff06db52106a358192868060Jesse Wilson    /**
6957d73e33dc039f6fff06db52106a358192868060Jesse Wilson     * Returns true if {@code certificate} matches {@code hostName}.
7057d73e33dc039f6fff06db52106a358192868060Jesse Wilson     */
7157d73e33dc039f6fff06db52106a358192868060Jesse Wilson    private boolean verifyHostName(String hostName, X509Certificate certificate) {
7257d73e33dc039f6fff06db52106a358192868060Jesse Wilson        hostName = hostName.toLowerCase(Locale.US);
7357d73e33dc039f6fff06db52106a358192868060Jesse Wilson        boolean hasDns = false;
7457d73e33dc039f6fff06db52106a358192868060Jesse Wilson        for (String altName : getSubjectAltNames(certificate, ALT_DNS_NAME)) {
7557d73e33dc039f6fff06db52106a358192868060Jesse Wilson            hasDns = true;
7657d73e33dc039f6fff06db52106a358192868060Jesse Wilson            if (verifyHostName(hostName, altName)) {
776767bdbe6bb1d4542c97868d8df1f71d2414fc62Jesse Wilson                return true;
78adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
79adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
806767bdbe6bb1d4542c97868d8df1f71d2414fc62Jesse Wilson
8157d73e33dc039f6fff06db52106a358192868060Jesse Wilson        if (!hasDns) {
8257d73e33dc039f6fff06db52106a358192868060Jesse Wilson            X500Principal principal = certificate.getSubjectX500Principal();
831331404bf45cb2f220ee9aa2c0c108ce59453a74Brian Carlstrom            // RFC 2818 advises using the most specific name for matching.
841331404bf45cb2f220ee9aa2c0c108ce59453a74Brian Carlstrom            String cn = new DistinguishedNameParser(principal).findMostSpecific("cn");
8557d73e33dc039f6fff06db52106a358192868060Jesse Wilson            if (cn != null) {
8657d73e33dc039f6fff06db52106a358192868060Jesse Wilson                return verifyHostName(hostName, cn);
8757d73e33dc039f6fff06db52106a358192868060Jesse Wilson            }
8857d73e33dc039f6fff06db52106a358192868060Jesse Wilson        }
8957d73e33dc039f6fff06db52106a358192868060Jesse Wilson
906767bdbe6bb1d4542c97868d8df1f71d2414fc62Jesse Wilson        return false;
91adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
92adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
9357d73e33dc039f6fff06db52106a358192868060Jesse Wilson    private List<String> getSubjectAltNames(X509Certificate certificate, int type) {
9457d73e33dc039f6fff06db52106a358192868060Jesse Wilson        List<String> result = new ArrayList<String>();
9557d73e33dc039f6fff06db52106a358192868060Jesse Wilson        try {
9657d73e33dc039f6fff06db52106a358192868060Jesse Wilson            Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames();
9757d73e33dc039f6fff06db52106a358192868060Jesse Wilson            if (subjectAltNames == null) {
9857d73e33dc039f6fff06db52106a358192868060Jesse Wilson                return Collections.emptyList();
9957d73e33dc039f6fff06db52106a358192868060Jesse Wilson            }
10057d73e33dc039f6fff06db52106a358192868060Jesse Wilson            for (Object subjectAltName : subjectAltNames) {
10157d73e33dc039f6fff06db52106a358192868060Jesse Wilson                List<?> entry = (List<?>) subjectAltName;
10257d73e33dc039f6fff06db52106a358192868060Jesse Wilson                if (entry == null || entry.size() < 2) {
10357d73e33dc039f6fff06db52106a358192868060Jesse Wilson                    continue;
10457d73e33dc039f6fff06db52106a358192868060Jesse Wilson                }
10557d73e33dc039f6fff06db52106a358192868060Jesse Wilson                Integer altNameType = (Integer) entry.get(0);
10657d73e33dc039f6fff06db52106a358192868060Jesse Wilson                if (altNameType == null) {
10757d73e33dc039f6fff06db52106a358192868060Jesse Wilson                    continue;
10857d73e33dc039f6fff06db52106a358192868060Jesse Wilson                }
10957d73e33dc039f6fff06db52106a358192868060Jesse Wilson                if (altNameType == type) {
11057d73e33dc039f6fff06db52106a358192868060Jesse Wilson                    String altName = (String) entry.get(1);
11157d73e33dc039f6fff06db52106a358192868060Jesse Wilson                    if (altName != null) {
11257d73e33dc039f6fff06db52106a358192868060Jesse Wilson                        result.add(altName);
11357d73e33dc039f6fff06db52106a358192868060Jesse Wilson                    }
11457d73e33dc039f6fff06db52106a358192868060Jesse Wilson                }
11557d73e33dc039f6fff06db52106a358192868060Jesse Wilson            }
11657d73e33dc039f6fff06db52106a358192868060Jesse Wilson            return result;
11757d73e33dc039f6fff06db52106a358192868060Jesse Wilson        } catch (CertificateParsingException e) {
11857d73e33dc039f6fff06db52106a358192868060Jesse Wilson            return Collections.emptyList();
11957d73e33dc039f6fff06db52106a358192868060Jesse Wilson        }
12057d73e33dc039f6fff06db52106a358192868060Jesse Wilson    }
12157d73e33dc039f6fff06db52106a358192868060Jesse Wilson
1220917c4a9d5d0115950450cdd0bb46e43a48da5dbElliott Hughes    /**
12357d73e33dc039f6fff06db52106a358192868060Jesse Wilson     * Returns true if {@code hostName} matches the name or pattern {@code cn}.
1240917c4a9d5d0115950450cdd0bb46e43a48da5dbElliott Hughes     *
12557d73e33dc039f6fff06db52106a358192868060Jesse Wilson     * @param hostName lowercase host name.
12657d73e33dc039f6fff06db52106a358192868060Jesse Wilson     * @param cn certificate host name. May include wildcards like
12757d73e33dc039f6fff06db52106a358192868060Jesse Wilson     *     {@code *.android.com}.
1280917c4a9d5d0115950450cdd0bb46e43a48da5dbElliott Hughes     */
12957d73e33dc039f6fff06db52106a358192868060Jesse Wilson    public boolean verifyHostName(String hostName, String cn) {
13057d73e33dc039f6fff06db52106a358192868060Jesse Wilson        if (hostName == null || hostName.isEmpty() || cn == null || cn.isEmpty()) {
1310917c4a9d5d0115950450cdd0bb46e43a48da5dbElliott Hughes            return false;
1320917c4a9d5d0115950450cdd0bb46e43a48da5dbElliott Hughes        }
1330917c4a9d5d0115950450cdd0bb46e43a48da5dbElliott Hughes
13457d73e33dc039f6fff06db52106a358192868060Jesse Wilson        cn = cn.toLowerCase(Locale.US);
135d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson
136d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        if (!cn.contains("*")) {
137d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson            return hostName.equals(cn);
1380917c4a9d5d0115950450cdd0bb46e43a48da5dbElliott Hughes        }
1390917c4a9d5d0115950450cdd0bb46e43a48da5dbElliott Hughes
140d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        if (cn.startsWith("*.") && hostName.regionMatches(0, cn, 2, cn.length() - 2)) {
141d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson            return true; // "*.foo.com" matches "foo.com"
142adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
143adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
144d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        int asterisk = cn.indexOf('*');
145d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        int dot = cn.indexOf('.');
146d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        if (asterisk > dot) {
147d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson            return false; // malformed; wildcard must be in the first part of the cn
148adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
14957d73e33dc039f6fff06db52106a358192868060Jesse Wilson
150d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        if (!hostName.regionMatches(0, cn, 0, asterisk)) {
151d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson            return false; // prefix before '*' doesn't match
152d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        }
153adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
154d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        int suffixLength = cn.length() - (asterisk + 1);
155d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        int suffixStart = hostName.length() - suffixLength;
156d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        if (hostName.indexOf('.', asterisk) < suffixStart) {
157265a2b554dacfa36f8adc5617ec6f05c8e1710f7Brian Carlstrom            // TODO: remove workaround for *.clients.google.com http://b/5426333
158265a2b554dacfa36f8adc5617ec6f05c8e1710f7Brian Carlstrom            if (!hostName.endsWith(".clients.google.com")) {
1598576f309825e23add080f2a50345ec1884939c39Jesse Wilson                return false; // wildcard '*' can't match a '.'
1608576f309825e23add080f2a50345ec1884939c39Jesse Wilson            }
161adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
162adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
163d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        if (!hostName.regionMatches(suffixStart, cn, asterisk + 1, suffixLength)) {
164d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson            return false; // suffix after '*' doesn't match
165d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        }
166d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson
167d2afaee242a56adede5510cd1c6455a5e7dbabe9Jesse Wilson        return true;
168adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
169adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project}
170