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 17 package android.security.net.config; 18 19 import android.util.Pair; 20 import java.util.HashSet; 21 import java.util.Locale; 22 import java.util.Set; 23 import javax.net.ssl.X509TrustManager; 24 25 /** 26 * An application's network security configuration. 27 * 28 * <p>{@link #getConfigForHostname(String)} provides a means to obtain network security 29 * configuration to be used for communicating with a specific hostname.</p> 30 * 31 * @hide 32 */ 33 public final class ApplicationConfig { 34 private static ApplicationConfig sInstance; 35 private static Object sLock = new Object(); 36 37 private Set<Pair<Domain, NetworkSecurityConfig>> mConfigs; 38 private NetworkSecurityConfig mDefaultConfig; 39 private X509TrustManager mTrustManager; 40 41 private ConfigSource mConfigSource; 42 private boolean mInitialized; 43 private final Object mLock = new Object(); 44 ApplicationConfig(ConfigSource configSource)45 public ApplicationConfig(ConfigSource configSource) { 46 mConfigSource = configSource; 47 mInitialized = false; 48 } 49 50 /** 51 * @hide 52 */ hasPerDomainConfigs()53 public boolean hasPerDomainConfigs() { 54 ensureInitialized(); 55 return mConfigs != null && !mConfigs.isEmpty(); 56 } 57 58 /** 59 * Get the {@link NetworkSecurityConfig} corresponding to the provided hostname. 60 * When matching the most specific matching domain rule will be used, if no match exists 61 * then the default configuration will be returned. 62 * 63 * {@code NetworkSecurityConfig} objects returned by this method can be safely cached for 64 * {@code hostname}. Subsequent calls with the same hostname will always return the same 65 * {@code NetworkSecurityConfig}. 66 * 67 * @return {@link NetworkSecurityConfig} to be used to determine 68 * the network security configuration for connections to {@code hostname}. 69 */ getConfigForHostname(String hostname)70 public NetworkSecurityConfig getConfigForHostname(String hostname) { 71 ensureInitialized(); 72 if (hostname == null || hostname.isEmpty() || mConfigs == null) { 73 return mDefaultConfig; 74 } 75 if (hostname.charAt(0) == '.') { 76 throw new IllegalArgumentException("hostname must not begin with a ."); 77 } 78 // Domains are case insensitive. 79 hostname = hostname.toLowerCase(Locale.US); 80 // Normalize hostname by removing trailing . if present, all Domain hostnames are 81 // absolute. 82 if (hostname.charAt(hostname.length() - 1) == '.') { 83 hostname = hostname.substring(0, hostname.length() - 1); 84 } 85 // Find the Domain -> NetworkSecurityConfig entry with the most specific matching 86 // Domain entry for hostname. 87 // TODO: Use a smarter data structure for the lookup. 88 Pair<Domain, NetworkSecurityConfig> bestMatch = null; 89 for (Pair<Domain, NetworkSecurityConfig> entry : mConfigs) { 90 Domain domain = entry.first; 91 NetworkSecurityConfig config = entry.second; 92 // Check for an exact match. 93 if (domain.hostname.equals(hostname)) { 94 return config; 95 } 96 // Otherwise check if the Domain includes sub-domains and that the hostname is a 97 // sub-domain of the Domain. 98 if (domain.subdomainsIncluded 99 && hostname.endsWith(domain.hostname) 100 && hostname.charAt(hostname.length() - domain.hostname.length() - 1) == '.') { 101 if (bestMatch == null) { 102 bestMatch = entry; 103 } else if (domain.hostname.length() > bestMatch.first.hostname.length()) { 104 bestMatch = entry; 105 } 106 } 107 } 108 if (bestMatch != null) { 109 return bestMatch.second; 110 } 111 // If no match was found use the default configuration. 112 return mDefaultConfig; 113 } 114 115 /** 116 * Returns the {@link X509TrustManager} that implements the checking of trust anchors and 117 * certificate pinning based on this configuration. 118 */ getTrustManager()119 public X509TrustManager getTrustManager() { 120 ensureInitialized(); 121 return mTrustManager; 122 } 123 124 /** 125 * Returns {@code true} if cleartext traffic is permitted for this application, which is the 126 * case only if all configurations permit cleartext traffic. For finer-grained policy use 127 * {@link #isCleartextTrafficPermitted(String)}. 128 */ isCleartextTrafficPermitted()129 public boolean isCleartextTrafficPermitted() { 130 ensureInitialized(); 131 if (mConfigs != null) { 132 for (Pair<Domain, NetworkSecurityConfig> entry : mConfigs) { 133 if (!entry.second.isCleartextTrafficPermitted()) { 134 return false; 135 } 136 } 137 } 138 139 return mDefaultConfig.isCleartextTrafficPermitted(); 140 } 141 142 /** 143 * Returns {@code true} if cleartext traffic is permitted for this application when connecting 144 * to {@code hostname}. 145 */ isCleartextTrafficPermitted(String hostname)146 public boolean isCleartextTrafficPermitted(String hostname) { 147 return getConfigForHostname(hostname).isCleartextTrafficPermitted(); 148 } 149 handleTrustStorageUpdate()150 public void handleTrustStorageUpdate() { 151 synchronized(mLock) { 152 // If the config is uninitialized then there is no work to be done to handle an update, 153 // avoid needlessly parsing configs. 154 if (!mInitialized) { 155 return; 156 } 157 mDefaultConfig.handleTrustStorageUpdate(); 158 if (mConfigs != null) { 159 Set<NetworkSecurityConfig> updatedConfigs = 160 new HashSet<NetworkSecurityConfig>(mConfigs.size()); 161 for (Pair<Domain, NetworkSecurityConfig> entry : mConfigs) { 162 if (updatedConfigs.add(entry.second)) { 163 entry.second.handleTrustStorageUpdate(); 164 } 165 } 166 } 167 } 168 } 169 ensureInitialized()170 private void ensureInitialized() { 171 synchronized(mLock) { 172 if (mInitialized) { 173 return; 174 } 175 mConfigs = mConfigSource.getPerDomainConfigs(); 176 mDefaultConfig = mConfigSource.getDefaultConfig(); 177 mConfigSource = null; 178 mTrustManager = new RootTrustManager(this); 179 mInitialized = true; 180 } 181 } 182 setDefaultInstance(ApplicationConfig config)183 public static void setDefaultInstance(ApplicationConfig config) { 184 synchronized (sLock) { 185 sInstance = config; 186 } 187 } 188 getDefaultInstance()189 public static ApplicationConfig getDefaultInstance() { 190 synchronized (sLock) { 191 return sInstance; 192 } 193 } 194 } 195