diff --git a/src/com/fsck/k9/helper/DomainNameChecker.java b/src/com/fsck/k9/helper/DomainNameChecker.java deleted file mode 100644 index 6731de648..000000000 --- a/src/com/fsck/k9/helper/DomainNameChecker.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.fsck.k9.helper; - -import android.net.http.SslCertificate; -import android.util.Log; -import com.fsck.k9.K9; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.cert.X509Certificate; -import java.security.cert.CertificateParsingException; -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -/** - * Implements basic domain-name validation as specified by RFC2818. - */ -public class DomainNameChecker { - private static Pattern QUICK_IP_PATTERN; - static { - try { - QUICK_IP_PATTERN = Pattern.compile("^[a-f0-9\\.:]+$"); - } catch (PatternSyntaxException e) { - } - } - - private static final int ALT_DNS_NAME = 2; - private static final int ALT_IPA_NAME = 7; - - /** - * Checks the site certificate against the domain name of the site being - * visited - * - * @param certificate - * The certificate to check - * @param thisDomain - * The domain name of the site being visited - * @return True iff if there is a domain match as specified by RFC2818 - */ - public static boolean match(X509Certificate certificate, String thisDomain) { - if ((certificate == null) || (thisDomain == null) - || thisDomain.isEmpty()) { - return false; - } - - thisDomain = thisDomain.toLowerCase(Locale.US); - if (!isIpAddress(thisDomain)) { - return matchDns(certificate, thisDomain); - } else { - return matchIpAddress(certificate, thisDomain); - } - } - - /** - * @return True iff the domain name is specified as an IP address - */ - private static boolean isIpAddress(String domain) { - if ((domain == null) || domain.isEmpty()) { - return false; - } - - boolean rval; - try { - // do a quick-dirty IP match first to avoid DNS lookup - rval = QUICK_IP_PATTERN.matcher(domain).matches(); - if (rval) { - rval = domain.equals(InetAddress.getByName(domain) - .getHostAddress()); - } - } catch (UnknownHostException e) { - String errorMessage = e.getMessage(); - if (errorMessage == null) { - errorMessage = "unknown host exception"; - } - - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "DomainNameChecker.isIpAddress(): " - + errorMessage); - } - - rval = false; - } - - return rval; - } - - /** - * Checks the site certificate against the IP domain name of the site being - * visited - * - * @param certificate - * The certificate to check - * @param thisDomain - * The DNS domain name of the site being visited - * @return True iff if there is a domain match as specified by RFC2818 - */ - private static boolean matchIpAddress(X509Certificate certificate, String thisDomain) { - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "DomainNameChecker.matchIpAddress(): this domain: " + thisDomain); - } - - try { - Collection> subjectAltNames = certificate.getSubjectAlternativeNames(); - if (subjectAltNames != null) { - for (List altNameEntry : subjectAltNames) { - if ((altNameEntry != null) && (2 <= altNameEntry.size())) { - Integer altNameType = (Integer)(altNameEntry.get(0)); - if (altNameType != null && altNameType.intValue() == ALT_IPA_NAME) { - String altName = (String)(altNameEntry.get(1)); - if (altName != null) { - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "alternative IP: " + altName); - } - if (thisDomain.equalsIgnoreCase(altName)) { - return true; - } - } - } - } - } - } - } catch (CertificateParsingException e) { - } - - return false; - } - - /** - * Checks the site certificate against the DNS domain name of the site being - * visited - * - * @param certificate - * The certificate to check - * @param thisDomain - * The DNS domain name of the site being visited - * @return True iff if there is a domain match as specified by RFC2818 - */ - private static boolean matchDns(X509Certificate certificate, String thisDomain) { - boolean hasDns = false; - try { - Collection> subjectAltNames = certificate.getSubjectAlternativeNames(); - if (subjectAltNames != null) { - for (List altNameEntry : subjectAltNames) { - if ((altNameEntry != null) && (2 <= altNameEntry.size())) { - Integer altNameType = (Integer)(altNameEntry.get(0)); - if (altNameType != null && altNameType.intValue() == ALT_DNS_NAME) { - hasDns = true; - String altName = (String)(altNameEntry.get(1)); - if (altName != null && matchDns(thisDomain, altName)) { - return true; - } - } - } - } - } - } catch (CertificateParsingException e) { - // one way we can get here is if an alternative name starts with - // '*' character, which is contrary to one interpretation of the - // spec (a valid DNS name must start with a letter); there is no - // good way around this, and in order to be compatible we proceed - // to check the common name (ie, ignore alternative names) - if (K9.DEBUG) { - String errorMessage = e.getMessage(); - if (errorMessage == null) { - errorMessage = "failed to parse certificate"; - } - - Log.v(K9.LOG_TAG, "DomainNameChecker.matchDns(): " - + errorMessage); - } - } - - if (!hasDns) { - SslCertificate sslCertificate = new SslCertificate(certificate); - return matchDns(thisDomain, sslCertificate.getIssuedTo().getCName()); - } - - return false; - } - - /** - * @param thisDomain - * The domain name of the site being visited - * @param thatDomain - * The domain name from the certificate - * @return True iff thisDomain matches thatDomain as specified by RFC2818 - */ - private static boolean matchDns(String thisDomain, String thatDomain) { - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "DomainNameChecker.matchDns():" - + " this domain: " + thisDomain + " that domain: " - + thatDomain); - } - - if ((thisDomain == null) || thisDomain.isEmpty() - || (thatDomain == null) || thatDomain.isEmpty()) { - return false; - } - - thatDomain = thatDomain.toLowerCase(Locale.US); - - // (a) domain name strings are equal, ignoring case: X matches X - boolean rval = thisDomain.equals(thatDomain); - if (!rval) { - String[] thisDomainTokens = thisDomain.split("\\."); - String[] thatDomainTokens = thatDomain.split("\\."); - - int thisDomainTokensNum = thisDomainTokens.length; - int thatDomainTokensNum = thatDomainTokens.length; - - // (b) OR thatHost is a '.'-suffix of thisHost: Z.Y.X matches X - if (thisDomainTokensNum >= thatDomainTokensNum) { - for (int i = thatDomainTokensNum - 1; i >= 0; --i) { - rval = thisDomainTokens[i].equals(thatDomainTokens[i]); - if (!rval) { - // (c) OR we have a special *-match: - // Z.Y.X matches *.Y.X but does not match *.X - rval = ((i == 0) && (thisDomainTokensNum == thatDomainTokensNum)); - if (rval) { - rval = thatDomainTokens[0].equals("*"); - if (!rval) { - // (d) OR we have a *-component match: - // f*.com matches foo.com but not bar.com - rval = domainTokenMatch(thisDomainTokens[0], - thatDomainTokens[0]); - } - } - - break; - } - } - } - } - - return rval; - } - - /** - * @param thisDomainToken - * The domain token from the current domain name - * @param thatDomainToken - * The domain token from the certificate - * @return True iff thisDomainToken matches thatDomainToken, using the - * wildcard match as specified by RFC2818-3.1. For example, f*.com - * must match foo.com but not bar.com - */ - private static boolean domainTokenMatch(String thisDomainToken, String thatDomainToken) { - if ((thisDomainToken != null) && (thatDomainToken != null)) { - int starIndex = thatDomainToken.indexOf('*'); - if (starIndex >= 0) { - if (thatDomainToken.length() - 1 <= thisDomainToken.length()) { - String prefix = thatDomainToken.substring(0, starIndex); - String suffix = thatDomainToken.substring(starIndex + 1); - - return thisDomainToken.startsWith(prefix) - && thisDomainToken.endsWith(suffix); - } - } - } - - return false; - } -} diff --git a/src/com/fsck/k9/net/ssl/TrustManagerFactory.java b/src/com/fsck/k9/net/ssl/TrustManagerFactory.java index 27b2c70bb..4a2c7206a 100644 --- a/src/com/fsck/k9/net/ssl/TrustManagerFactory.java +++ b/src/com/fsck/k9/net/ssl/TrustManagerFactory.java @@ -3,12 +3,15 @@ package com.fsck.k9.net.ssl; import android.util.Log; -import com.fsck.k9.helper.DomainNameChecker; import com.fsck.k9.mail.CertificateChainException; import com.fsck.k9.security.LocalKeyStore; +import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; + +import org.apache.http.conn.ssl.StrictHostnameVerifier; + import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -58,31 +61,25 @@ public final class TrustManagerFactory { public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { String message = null; - boolean foundInGlobalKeyStore = false; + X509Certificate certificate = chain[0]; + try { defaultTrustManager.checkServerTrusted(chain, authType); - foundInGlobalKeyStore = true; + new StrictHostnameVerifier().verify(mHost, certificate); + return; } catch (CertificateException e) { + // cert. chain can't be validated + message = e.getMessage(); + } catch (SSLException e) { + // host name doesn't match certificate message = e.getMessage(); } - X509Certificate certificate = chain[0]; - // Check the local key store if we couldn't verify the certificate using the global // key store or if the host name doesn't match the certificate name - if (foundInGlobalKeyStore - && DomainNameChecker.match(certificate, mHost) - || keyStore.isValidCertificate(certificate, mHost, mPort)) { - return; + if (!keyStore.isValidCertificate(certificate, mHost, mPort)) { + throw new CertificateChainException(message, chain); } - - if (message == null) { - message = (foundInGlobalKeyStore) ? - "Certificate domain name does not match " + mHost : - "Couldn't find certificate in local key store"; - } - - throw new CertificateChainException(message, chain); } public X509Certificate[] getAcceptedIssuers() {