/*
 * 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.

getConfigForHostname(String) provides a means to obtain network security configuration to be used for communicating with a specific hostname.

@hide
/** * 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
/** * @hide */
public boolean hasPerDomainConfigs() { ensureInitialized(); return mConfigs != null && !mConfigs.isEmpty(); }
Get the 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. NetworkSecurityConfig objects returned by this method can be safely cached for hostname. Subsequent calls with the same hostname will always return the same NetworkSecurityConfig.
Returns:NetworkSecurityConfig to be used to determine the network security configuration for connections to hostname.
/** * 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 X509TrustManager that implements the checking of trust anchors and certificate pinning based on this configuration.
/** * 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 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 isCleartextTrafficPermitted(String).
/** * 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 true if cleartext traffic is permitted for this application when connecting to hostname.
/** * 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; } } }