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