NetworkSecurityTrustManager.java revision 725fefb38a4cb0ab89de439f8131d6c46ccd8b17
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.security.net.config;
18
19import com.android.org.conscrypt.TrustManagerImpl;
20
21import android.util.ArrayMap;
22import java.io.IOException;
23import java.security.cert.CertificateException;
24import java.security.cert.X509Certificate;
25import java.security.GeneralSecurityException;
26import java.security.KeyStore;
27import java.security.MessageDigest;
28import java.util.List;
29import java.util.Map;
30import java.util.Set;
31
32import javax.net.ssl.X509TrustManager;
33
34/**
35 * {@link X509TrustManager} that implements the trust anchor and pinning for a
36 * given {@link NetworkSecurityConfig}.
37 * @hide
38 */
39public class NetworkSecurityTrustManager implements X509TrustManager {
40    // TODO: Replace this with a general X509TrustManager and use duck-typing.
41    private final TrustManagerImpl mDelegate;
42    private final NetworkSecurityConfig mNetworkSecurityConfig;
43
44    public NetworkSecurityTrustManager(NetworkSecurityConfig config) {
45        if (config == null) {
46            throw new NullPointerException("config must not be null");
47        }
48        mNetworkSecurityConfig = config;
49        try {
50            TrustedCertificateStoreAdapter certStore = new TrustedCertificateStoreAdapter(config);
51            // Provide an empty KeyStore since TrustManagerImpl doesn't support null KeyStores.
52            // TrustManagerImpl will use certStore to lookup certificates.
53            KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
54            store.load(null);
55            mDelegate = new TrustManagerImpl(store, null, certStore);
56        } catch (GeneralSecurityException | IOException e) {
57            throw new RuntimeException(e);
58        }
59    }
60
61    @Override
62    public void checkClientTrusted(X509Certificate[] chain, String authType)
63            throws CertificateException {
64        mDelegate.checkClientTrusted(chain, authType);
65    }
66
67    @Override
68    public void checkServerTrusted(X509Certificate[] certs, String authType)
69            throws CertificateException {
70        checkServerTrusted(certs, authType, null);
71    }
72
73    /**
74     * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
75     * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
76     * modify without modifying those callers.
77     */
78    public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
79            String host) throws CertificateException {
80        List<X509Certificate> trustedChain = mDelegate.checkServerTrusted(certs, authType, host);
81        checkPins(trustedChain);
82        return trustedChain;
83    }
84
85    /**
86     * Check if the provided certificate is a user added certificate authority.
87     * This is required by android.net.http.X509TrustManagerExtensions.
88     */
89    public boolean isUserAddedCertificate(X509Certificate cert) {
90        // TODO: Figure out the right way to handle this, and if it is still even used.
91        return false;
92    }
93
94    private void checkPins(List<X509Certificate> chain) throws CertificateException {
95        PinSet pinSet = mNetworkSecurityConfig.getPins();
96        if (pinSet.pins.isEmpty()
97                || System.currentTimeMillis() > pinSet.expirationTime
98                || !isPinningEnforced(chain)) {
99            return;
100        }
101        Set<String> pinAlgorithms = pinSet.getPinAlgorithms();
102        Map<String, MessageDigest> digestMap = new ArrayMap<String, MessageDigest>(
103                pinAlgorithms.size());
104        for (int i = chain.size() - 1; i >= 0 ; i--) {
105            X509Certificate cert = chain.get(i);
106            byte[] encodedSPKI = cert.getPublicKey().getEncoded();
107            for (String algorithm : pinAlgorithms) {
108                MessageDigest md = digestMap.get(algorithm);
109                if (md == null) {
110                    try {
111                        md = MessageDigest.getInstance(algorithm);
112                    } catch (GeneralSecurityException e) {
113                        throw new RuntimeException(e);
114                    }
115                    digestMap.put(algorithm, md);
116                }
117                if (pinSet.pins.contains(new Pin(algorithm, md.digest(encodedSPKI)))) {
118                    return;
119                }
120            }
121        }
122
123        // TODO: Throw a subclass of CertificateException which indicates a pinning failure.
124        throw new CertificateException("Pin verification failed");
125    }
126
127    private boolean isPinningEnforced(List<X509Certificate> chain) throws CertificateException {
128        if (chain.isEmpty()) {
129            return false;
130        }
131        X509Certificate anchorCert = chain.get(chain.size() - 1);
132        TrustAnchor chainAnchor =
133                mNetworkSecurityConfig.findTrustAnchorBySubjectAndPublicKey(anchorCert);
134        if (chainAnchor == null) {
135            throw new CertificateException("Trusted chain does not end in a TrustAnchor");
136        }
137        return !chainAnchor.overridesPins;
138    }
139
140    @Override
141    public X509Certificate[] getAcceptedIssuers() {
142        return mDelegate.getAcceptedIssuers();
143    }
144}
145