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