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