NetworkSecurityTrustManager.java revision 0f5d386a769cafa3f4faf93f17acb22503b6fd3e
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.net.Socket;
24import java.security.cert.CertificateException;
25import java.security.cert.X509Certificate;
26import java.security.GeneralSecurityException;
27import java.security.KeyStore;
28import java.security.MessageDigest;
29import java.util.List;
30import java.util.Map;
31import java.util.Set;
32
33import javax.net.ssl.SSLEngine;
34import javax.net.ssl.X509ExtendedTrustManager;
35
36/**
37 * {@link X509ExtendedTrustManager} that implements the trust anchor and pinning for a
38 * given {@link NetworkSecurityConfig}.
39 * @hide
40 */
41public class NetworkSecurityTrustManager extends X509ExtendedTrustManager {
42    // TODO: Replace this with a general X509TrustManager and use duck-typing.
43    private final TrustManagerImpl mDelegate;
44    private final NetworkSecurityConfig mNetworkSecurityConfig;
45    private final Object mIssuersLock = new Object();
46
47    private X509Certificate[] mIssuers;
48
49    public NetworkSecurityTrustManager(NetworkSecurityConfig config) {
50        if (config == null) {
51            throw new NullPointerException("config must not be null");
52        }
53        mNetworkSecurityConfig = config;
54        try {
55            TrustedCertificateStoreAdapter certStore = new TrustedCertificateStoreAdapter(config);
56            // Provide an empty KeyStore since TrustManagerImpl doesn't support null KeyStores.
57            // TrustManagerImpl will use certStore to lookup certificates.
58            KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
59            store.load(null);
60            mDelegate = new TrustManagerImpl(store, null, certStore);
61        } catch (GeneralSecurityException | IOException e) {
62            throw new RuntimeException(e);
63        }
64    }
65
66    @Override
67    public void checkClientTrusted(X509Certificate[] chain, String authType)
68            throws CertificateException {
69        mDelegate.checkClientTrusted(chain, authType);
70    }
71
72    @Override
73    public void checkClientTrusted(X509Certificate[] certs, String authType, Socket socket)
74            throws CertificateException {
75        mDelegate.checkClientTrusted(certs, authType, socket);
76    }
77
78    @Override
79    public void checkClientTrusted(X509Certificate[] certs, String authType, SSLEngine engine)
80            throws CertificateException {
81        mDelegate.checkClientTrusted(certs, authType, engine);
82    }
83
84    @Override
85    public void checkServerTrusted(X509Certificate[] certs, String authType)
86            throws CertificateException {
87        checkServerTrusted(certs, authType, (String) null);
88    }
89
90    @Override
91    public void checkServerTrusted(X509Certificate[] certs, String authType, Socket socket)
92            throws CertificateException {
93        List<X509Certificate> trustedChain =
94                mDelegate.getTrustedChainForServer(certs, authType, socket);
95        checkPins(trustedChain);
96    }
97
98    @Override
99    public void checkServerTrusted(X509Certificate[] certs, String authType, SSLEngine engine)
100            throws CertificateException {
101        List<X509Certificate> trustedChain =
102                mDelegate.getTrustedChainForServer(certs, authType, engine);
103        checkPins(trustedChain);
104    }
105
106    /**
107     * Hostname aware version of {@link #checkServerTrusted(X509Certificate[], String)}.
108     * This interface is used by conscrypt and android.net.http.X509TrustManagerExtensions do not
109     * modify without modifying those callers.
110     */
111    public List<X509Certificate> checkServerTrusted(X509Certificate[] certs, String authType,
112            String host) throws CertificateException {
113        List<X509Certificate> trustedChain = mDelegate.checkServerTrusted(certs, authType, host);
114        checkPins(trustedChain);
115        return trustedChain;
116    }
117
118    /**
119     * Check if the provided certificate is a user added certificate authority.
120     * This is required by android.net.http.X509TrustManagerExtensions.
121     */
122    public boolean isUserAddedCertificate(X509Certificate cert) {
123        // TODO: Figure out the right way to handle this, and if it is still even used.
124        return false;
125    }
126
127    private void checkPins(List<X509Certificate> chain) throws CertificateException {
128        PinSet pinSet = mNetworkSecurityConfig.getPins();
129        if (pinSet.pins.isEmpty()
130                || System.currentTimeMillis() > pinSet.expirationTime
131                || !isPinningEnforced(chain)) {
132            return;
133        }
134        Set<String> pinAlgorithms = pinSet.getPinAlgorithms();
135        Map<String, MessageDigest> digestMap = new ArrayMap<String, MessageDigest>(
136                pinAlgorithms.size());
137        for (int i = chain.size() - 1; i >= 0 ; i--) {
138            X509Certificate cert = chain.get(i);
139            byte[] encodedSPKI = cert.getPublicKey().getEncoded();
140            for (String algorithm : pinAlgorithms) {
141                MessageDigest md = digestMap.get(algorithm);
142                if (md == null) {
143                    try {
144                        md = MessageDigest.getInstance(algorithm);
145                    } catch (GeneralSecurityException e) {
146                        throw new RuntimeException(e);
147                    }
148                    digestMap.put(algorithm, md);
149                }
150                if (pinSet.pins.contains(new Pin(algorithm, md.digest(encodedSPKI)))) {
151                    return;
152                }
153            }
154        }
155
156        // TODO: Throw a subclass of CertificateException which indicates a pinning failure.
157        throw new CertificateException("Pin verification failed");
158    }
159
160    private boolean isPinningEnforced(List<X509Certificate> chain) throws CertificateException {
161        if (chain.isEmpty()) {
162            return false;
163        }
164        X509Certificate anchorCert = chain.get(chain.size() - 1);
165        TrustAnchor chainAnchor =
166                mNetworkSecurityConfig.findTrustAnchorBySubjectAndPublicKey(anchorCert);
167        if (chainAnchor == null) {
168            throw new CertificateException("Trusted chain does not end in a TrustAnchor");
169        }
170        return !chainAnchor.overridesPins;
171    }
172
173    @Override
174    public X509Certificate[] getAcceptedIssuers() {
175        // TrustManagerImpl only looks at the provided KeyStore and not the TrustedCertificateStore
176        // for getAcceptedIssuers, so implement it here instead of delegating.
177        synchronized (mIssuersLock) {
178            if (mIssuers == null) {
179                Set<TrustAnchor> anchors = mNetworkSecurityConfig.getTrustAnchors();
180                X509Certificate[] issuers = new X509Certificate[anchors.size()];
181                int i = 0;
182                for (TrustAnchor anchor : anchors) {
183                    issuers[i++] = anchor.certificate;
184                }
185                mIssuers = issuers;
186            }
187            return mIssuers.clone();
188        }
189    }
190
191    public void handleTrustStorageUpdate() {
192        synchronized (mIssuersLock) {
193            mIssuers = null;
194            mDelegate.handleTrustStorageUpdate();
195        }
196    }
197}
198