Home | History | Annotate | Download | only in config
      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;
     20 import java.util.HashSet;
     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 {
     34     private static ApplicationConfig sInstance;
     35     private static Object sLock = new Object();
     36 
     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();
     55         return mConfigs != null && !mConfigs.isEmpty();
     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();
     72         if (hostname == null || hostname.isEmpty() || mConfigs == null) {
     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 
    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 
    150     public void handleTrustStorageUpdate() {
    151         synchronized(mLock) {
    152             // If the config is uninitialized then there is no work to be done to handle an update,
    153             // avoid needlessly parsing configs.
    154             if (!mInitialized) {
    155                 return;
    156             }
    157             mDefaultConfig.handleTrustStorageUpdate();
    158             if (mConfigs != null) {
    159                 Set<NetworkSecurityConfig> updatedConfigs =
    160                         new HashSet<NetworkSecurityConfig>(mConfigs.size());
    161                 for (Pair<Domain, NetworkSecurityConfig> entry : mConfigs) {
    162                     if (updatedConfigs.add(entry.second)) {
    163                         entry.second.handleTrustStorageUpdate();
    164                     }
    165                 }
    166             }
    167         }
    168     }
    169 
    170     private void ensureInitialized() {
    171         synchronized(mLock) {
    172             if (mInitialized) {
    173                 return;
    174             }
    175             mConfigs = mConfigSource.getPerDomainConfigs();
    176             mDefaultConfig = mConfigSource.getDefaultConfig();
    177             mConfigSource = null;
    178             mTrustManager = new RootTrustManager(this);
    179             mInitialized = true;
    180         }
    181     }
    182 
    183     public static void setDefaultInstance(ApplicationConfig config) {
    184         synchronized (sLock) {
    185             sInstance = config;
    186         }
    187     }
    188 
    189     public static ApplicationConfig getDefaultInstance() {
    190         synchronized (sLock) {
    191             return sInstance;
    192         }
    193     }
    194 }
    195