Chad Brubaker | 6bc1e39 | 2015-10-23 15:33:56 -0700 | [diff] [blame] | 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; |
Chad Brubaker | bf9a82a | 2016-03-25 10:12:19 -0700 | [diff] [blame] | 20 | import java.util.HashSet; |
Chad Brubaker | 6bc1e39 | 2015-10-23 15:33:56 -0700 | [diff] [blame] | 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 { |
Chad Brubaker | 5a1078f | 2015-11-10 12:26:18 -0800 | [diff] [blame] | 34 | private static ApplicationConfig sInstance; |
| 35 | private static Object sLock = new Object(); |
| 36 | |
Chad Brubaker | 6bc1e39 | 2015-10-23 15:33:56 -0700 | [diff] [blame] | 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 | |
| 45 | public ApplicationConfig(ConfigSource configSource) { |
| 46 | mConfigSource = configSource; |
| 47 | mInitialized = false; |
| 48 | } |
| 49 | |
| 50 | /** |
| 51 | * @hide |
| 52 | */ |
| 53 | public boolean hasPerDomainConfigs() { |
| 54 | ensureInitialized(); |
Chad Brubaker | 35de8d6 | 2015-11-06 12:50:31 -0800 | [diff] [blame] | 55 | return mConfigs != null && !mConfigs.isEmpty(); |
Chad Brubaker | 6bc1e39 | 2015-10-23 15:33:56 -0700 | [diff] [blame] | 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 | */ |
| 70 | public NetworkSecurityConfig getConfigForHostname(String hostname) { |
| 71 | ensureInitialized(); |
Chad Brubaker | 9613157 | 2015-12-10 18:12:59 -0800 | [diff] [blame] | 72 | if (hostname == null || hostname.isEmpty() || mConfigs == null) { |
Chad Brubaker | 6bc1e39 | 2015-10-23 15:33:56 -0700 | [diff] [blame] | 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 | */ |
| 119 | public X509TrustManager getTrustManager() { |
| 120 | ensureInitialized(); |
| 121 | return mTrustManager; |
| 122 | } |
| 123 | |
Chad Brubaker | c136cb0 | 2015-12-09 12:58:37 -0800 | [diff] [blame] | 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 | */ |
| 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 | */ |
| 146 | public boolean isCleartextTrafficPermitted(String hostname) { |
| 147 | return getConfigForHostname(hostname).isCleartextTrafficPermitted(); |
| 148 | } |
| 149 | |
Chad Brubaker | bf9a82a | 2016-03-25 10:12:19 -0700 | [diff] [blame] | 150 | public void handleTrustStorageUpdate() { |
| 151 | ensureInitialized(); |
| 152 | mDefaultConfig.handleTrustStorageUpdate(); |
| 153 | if (mConfigs != null) { |
| 154 | Set<NetworkSecurityConfig> updatedConfigs = |
| 155 | new HashSet<NetworkSecurityConfig>(mConfigs.size()); |
| 156 | for (Pair<Domain, NetworkSecurityConfig> entry : mConfigs) { |
| 157 | if (updatedConfigs.add(entry.second)) { |
| 158 | entry.second.handleTrustStorageUpdate(); |
| 159 | } |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | |
Chad Brubaker | 6bc1e39 | 2015-10-23 15:33:56 -0700 | [diff] [blame] | 164 | private void ensureInitialized() { |
| 165 | synchronized(mLock) { |
| 166 | if (mInitialized) { |
| 167 | return; |
| 168 | } |
| 169 | mConfigs = mConfigSource.getPerDomainConfigs(); |
| 170 | mDefaultConfig = mConfigSource.getDefaultConfig(); |
| 171 | mConfigSource = null; |
| 172 | mTrustManager = new RootTrustManager(this); |
| 173 | mInitialized = true; |
| 174 | } |
| 175 | } |
Chad Brubaker | 5a1078f | 2015-11-10 12:26:18 -0800 | [diff] [blame] | 176 | |
| 177 | public static void setDefaultInstance(ApplicationConfig config) { |
| 178 | synchronized (sLock) { |
| 179 | sInstance = config; |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | public static ApplicationConfig getDefaultInstance() { |
| 184 | synchronized (sLock) { |
| 185 | return sInstance; |
| 186 | } |
| 187 | } |
Chad Brubaker | 6bc1e39 | 2015-10-23 15:33:56 -0700 | [diff] [blame] | 188 | } |