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

{@link #getConfigForHostname(String)} provides a means to obtain network security * configuration to be used for communicating with a specific hostname.

* * @hide */ public final class ApplicationConfig { private static ApplicationConfig sInstance; private static Object sLock = new Object(); private Set> 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 bestMatch = null; for (Pair 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 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() { ensureInitialized(); mDefaultConfig.handleTrustStorageUpdate(); if (mConfigs != null) { Set updatedConfigs = new HashSet(mConfigs.size()); for (Pair 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; } } }