1ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra/*
2ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra * Copyright (C) 2012 The Android Open Source Project
3ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra *
4ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra * Licensed under the Apache License, Version 2.0 (the "License");
5ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra * you may not use this file except in compliance with the License.
6ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra * You may obtain a copy of the License at
7ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra *
8ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra *      http://www.apache.org/licenses/LICENSE-2.0
9ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra *
10ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra * Unless required by applicable law or agreed to in writing, software
11ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra * distributed under the License is distributed on an "AS IS" BASIS,
12ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra * See the License for the specific language governing permissions and
14ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra * limitations under the License.
15ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra */
16ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra
17ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condrapackage android.net.http;
18ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra
19ec95c407b43a03890d3b42b86571110ee90d283dChad Brubakerimport android.annotation.SystemApi;
2000a4e299694ccf5edc07894ca46e38602e9b4a38Chad Brubakerimport android.security.net.config.UserCertificateSource;
21ec95c407b43a03890d3b42b86571110ee90d283dChad Brubaker
2212e752225aa96888358294be0d725d499a1c9f03Kenny Rootimport com.android.org.conscrypt.TrustManagerImpl;
238071124375e336b98de45b44b9884c92cdfd9bd8Andy Stadler
24bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubakerimport java.lang.reflect.InvocationTargetException;
25bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubakerimport java.lang.reflect.Method;
26ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condraimport java.security.cert.CertificateException;
27ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condraimport java.security.cert.X509Certificate;
28ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condraimport java.util.List;
29ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra
30ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condraimport javax.net.ssl.X509TrustManager;
31ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra
32ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra/**
33ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra * X509TrustManager wrapper exposing Android-added features.
34da776c872e8880b48dfac4a9e9a2d90e398e4608Kenny Root * <p>
35da776c872e8880b48dfac4a9e9a2d90e398e4608Kenny Root * The checkServerTrusted method allows callers to perform additional
36da776c872e8880b48dfac4a9e9a2d90e398e4608Kenny Root * verification of certificate chains after they have been successfully verified
37da776c872e8880b48dfac4a9e9a2d90e398e4608Kenny Root * by the platform.
38da776c872e8880b48dfac4a9e9a2d90e398e4608Kenny Root * </p>
39ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra */
40ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condrapublic class X509TrustManagerExtensions {
41ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra
42bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker    private final TrustManagerImpl mDelegate;
43bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker    // Methods to use when mDelegate is not a TrustManagerImpl and duck typing is being used.
44bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker    private final X509TrustManager mTrustManager;
45bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker    private final Method mCheckServerTrusted;
465116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker    private final Method mIsSameTrustConfiguration;
47ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra
48ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra    /**
49ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     * Constructs a new X509TrustManagerExtensions wrapper.
50ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     *
51ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     * @param tm A {@link X509TrustManager} as returned by TrustManagerFactory.getInstance();
52ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     * @throws IllegalArgumentException If tm is an unsupported TrustManager type.
53ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     */
54ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra    public X509TrustManagerExtensions(X509TrustManager tm) throws IllegalArgumentException {
55cb4c5819758a7e2951bd4818383f7c5e10a5f016Geremy Condra        if (tm instanceof TrustManagerImpl) {
56ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra            mDelegate = (TrustManagerImpl) tm;
57bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker            mTrustManager = null;
58bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker            mCheckServerTrusted = null;
595116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker            mIsSameTrustConfiguration = null;
60bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker            return;
61bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        }
62bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        // Use duck typing if possible.
63bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        mDelegate = null;
64bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        mTrustManager = tm;
65bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        // Check that the hostname aware checkServerTrusted is present.
66bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        try {
67bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker            mCheckServerTrusted = tm.getClass().getMethod("checkServerTrusted",
68bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                    X509Certificate[].class,
69bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                    String.class,
70bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                    String.class);
71bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        } catch (NoSuchMethodException e) {
72bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker            throw new IllegalArgumentException("Required method"
73bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                    + " checkServerTrusted(X509Certificate[], String, String, String) missing");
74bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        }
755116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        // Get the option isSameTrustConfiguration method.
765116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        Method isSameTrustConfiguration = null;
775116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        try {
785116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker            isSameTrustConfiguration = tm.getClass().getMethod("isSameTrustConfiguration",
795116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker                    String.class,
805116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker                    String.class);
815116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        } catch (ReflectiveOperationException ignored) {
825116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        }
835116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        mIsSameTrustConfiguration = isSameTrustConfiguration;
84ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra    }
85ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra
86ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra    /**
87ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     * Verifies the given certificate chain.
88ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     *
89ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     * <p>See {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)} for a
90ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     * description of the chain and authType parameters. The final parameter, host, should be the
91ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     * hostname of the server.</p>
92ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     *
93ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     * @throws CertificateException if the chain does not verify correctly.
94ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     * @return the properly ordered chain used for verification as a list of X509Certificates.
95ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra     */
96ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra    public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
97ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra                                                    String host) throws CertificateException {
98bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        if (mDelegate != null) {
99bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker            return mDelegate.checkServerTrusted(chain, authType, host);
100bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        } else {
101bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker            try {
102bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                return (List<X509Certificate>) mCheckServerTrusted.invoke(mTrustManager, chain,
103bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                        authType, host);
104bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker            } catch (IllegalAccessException e) {
105bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                throw new CertificateException("Failed to call checkServerTrusted", e);
106bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker            } catch (InvocationTargetException e) {
107bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                if (e.getCause() instanceof CertificateException) {
108bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                    throw (CertificateException) e.getCause();
109bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                }
110bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                if (e.getCause() instanceof RuntimeException) {
111bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                    throw (RuntimeException) e.getCause();
112bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                }
113bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker                throw new CertificateException("checkServerTrusted failed", e.getCause());
114bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker            }
115bfcd67f71eedcefd269e63e72219ac6a3435626eChad Brubaker        }
116ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra    }
117d96371521299627c8be59e0d9d5b42a61d0720beWilliam Luh
118d96371521299627c8be59e0d9d5b42a61d0720beWilliam Luh    /**
119d96371521299627c8be59e0d9d5b42a61d0720beWilliam Luh     * Checks whether a CA certificate is added by an user.
120d96371521299627c8be59e0d9d5b42a61d0720beWilliam Luh     *
12100a4e299694ccf5edc07894ca46e38602e9b4a38Chad Brubaker     * <p>Since {@link X509TrustManager#checkServerTrusted} may allow its parameter {@code chain} to
122d96371521299627c8be59e0d9d5b42a61d0720beWilliam Luh     * chain up to user-added CA certificates, this method can be used to perform additional
123d96371521299627c8be59e0d9d5b42a61d0720beWilliam Luh     * policies for user-added CA certificates.
124d96371521299627c8be59e0d9d5b42a61d0720beWilliam Luh     *
12500a4e299694ccf5edc07894ca46e38602e9b4a38Chad Brubaker     * @return {@code true} to indicate that the certificate authority exists in the user added
12600a4e299694ccf5edc07894ca46e38602e9b4a38Chad Brubaker     * certificate store, {@code false} otherwise.
127d96371521299627c8be59e0d9d5b42a61d0720beWilliam Luh     */
128d96371521299627c8be59e0d9d5b42a61d0720beWilliam Luh    public boolean isUserAddedCertificate(X509Certificate cert) {
12900a4e299694ccf5edc07894ca46e38602e9b4a38Chad Brubaker        return UserCertificateSource.getInstance().findBySubjectAndPublicKey(cert) != null;
130d96371521299627c8be59e0d9d5b42a61d0720beWilliam Luh    }
131ec95c407b43a03890d3b42b86571110ee90d283dChad Brubaker
132ec95c407b43a03890d3b42b86571110ee90d283dChad Brubaker    /**
133ec95c407b43a03890d3b42b86571110ee90d283dChad Brubaker     * Returns {@code true} if the TrustManager uses the same trust configuration for the provided
134ec95c407b43a03890d3b42b86571110ee90d283dChad Brubaker     * hostnames.
135ec95c407b43a03890d3b42b86571110ee90d283dChad Brubaker     */
136ec95c407b43a03890d3b42b86571110ee90d283dChad Brubaker    @SystemApi
137ec95c407b43a03890d3b42b86571110ee90d283dChad Brubaker    public boolean isSameTrustConfiguration(String hostname1, String hostname2) {
1385116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        if (mIsSameTrustConfiguration == null) {
1395116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker            return true;
1405116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        }
1415116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        try {
1425116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker            return (Boolean) mIsSameTrustConfiguration.invoke(mTrustManager, hostname1, hostname2);
1435116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        } catch (IllegalAccessException e) {
1445116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker            throw new RuntimeException("Failed to call isSameTrustConfiguration", e);
1455116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        } catch (InvocationTargetException e) {
1465116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker            if (e.getCause() instanceof RuntimeException) {
1475116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker                throw (RuntimeException) e.getCause();
1485116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker            } else {
1495116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker                throw new RuntimeException("isSameTrustConfiguration failed", e.getCause());
1505116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker            }
1515116ac0d222e3c85f04c446c75d7211280f56ffaChad Brubaker        }
152ec95c407b43a03890d3b42b86571110ee90d283dChad Brubaker    }
153ed41a4e2d81fceafbddb1e9a4ca327535bb739efGeremy Condra}
154