blob: fadea5682cdb80dbed6fcee5088de2e2f5833059 [file] [log] [blame]
Chad Brubaker6bc1e392015-10-23 15:33:56 -07001/*
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 android.util.Pair;
Chad Brubakerbf9a82a2016-03-25 10:12:19 -070020import java.util.HashSet;
Chad Brubaker6bc1e392015-10-23 15:33:56 -070021import java.util.Locale;
22import java.util.Set;
23import 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 */
33public final class ApplicationConfig {
Chad Brubaker5a1078f2015-11-10 12:26:18 -080034 private static ApplicationConfig sInstance;
35 private static Object sLock = new Object();
36
Chad Brubaker6bc1e392015-10-23 15:33:56 -070037 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 Brubaker35de8d62015-11-06 12:50:31 -080055 return mConfigs != null && !mConfigs.isEmpty();
Chad Brubaker6bc1e392015-10-23 15:33:56 -070056 }
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 Brubaker96131572015-12-10 18:12:59 -080072 if (hostname == null || hostname.isEmpty() || mConfigs == null) {
Chad Brubaker6bc1e392015-10-23 15:33:56 -070073 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 Brubakerc136cb02015-12-09 12:58:37 -0800124 /**
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 Brubakerbf9a82a2016-03-25 10:12:19 -0700150 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 Brubaker6bc1e392015-10-23 15:33:56 -0700164 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 Brubaker5a1078f2015-11-10 12:26:18 -0800176
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 Brubaker6bc1e392015-10-23 15:33:56 -0700188}