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