Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2b8cabc8 authored by Thiébaud Weksteen's avatar Thiébaud Weksteen
Browse files

Define new NetworkSecurityConfig for localhost

Some default settings for network security config such as
cleartextTrafficPermitted make no sense for localhost connections.

Define a default localhost NetworkSecurityConfig which is only applied
if the application does not already define a NetworkSecurityConfig for
localhost.

An hostname is considered to be localhost if either:
  - The hostname is "localhost" or "ip6-localhost"; or
  - InetAddress.isLoopbackAddress() returns true

Because of the size of the address space for what is considered
localhost, instead of adding entries to the existing Set returned by
getPerDomainConfigs(), a new method is added to ConfigSource:
getLocalhostConfig().

Bug: 398997783
Test: atest CtsNetSecConfigLocalhostCleartextTestCases
Test: atest CtsNetSecConfigLocalhostCleartextWithNscTestCases
Test: atest CtsNetSecConfigLocalhostTlsTestCases
Flag: com.android.org.conscrypt.net.flags.network_security_config_localhost
Change-Id: I608ff46ea5c976976a1b466f39be69cedea3a00c
parent 3f5a77d3
Loading
Loading
Loading
Loading
+33 −23
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ public final class ApplicationConfig {

    private Set<Pair<Domain, NetworkSecurityConfig>> mConfigs;
    private NetworkSecurityConfig mDefaultConfig;
    private NetworkSecurityConfig mLocalhostConfig;
    private X509TrustManager mTrustManager;

    private ConfigSource mConfigSource;
@@ -62,8 +63,10 @@ public final class ApplicationConfig {

    /**
     * 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.
     * The most specific matching domain rule will be used. If no match exists
     * and the hostname is considered to be localhost (according to {@link
     * Domain#isLocalhost()}), the localhost configuration will be returned.
     * Otherwise, 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
@@ -74,7 +77,8 @@ public final class ApplicationConfig {
     */
    public NetworkSecurityConfig getConfigForHostname(String hostname) {
        ensureInitialized();
        if (hostname == null || hostname.isEmpty() || mConfigs == null) {
        if (hostname == null || hostname.isEmpty()
                || (mConfigs == null && mLocalhostConfig == null)) {
            return mDefaultConfig;
        }
        if (hostname.charAt(0) ==  '.') {
@@ -89,7 +93,7 @@ public final class ApplicationConfig {
        }
        // Find the Domain -> NetworkSecurityConfig entry with the most specific matching
        // Domain entry for hostname.
        // TODO: Use a smarter data structure for the lookup.
        if (mConfigs != null) {
            Pair<Domain, NetworkSecurityConfig> bestMatch = null;
            for (Pair<Domain, NetworkSecurityConfig> entry : mConfigs) {
                Domain domain = entry.first;
@@ -102,7 +106,8 @@ public final class ApplicationConfig {
                // sub-domain of the Domain.
                if (domain.subdomainsIncluded
                        && hostname.endsWith(domain.hostname)
                    && hostname.charAt(hostname.length() - domain.hostname.length() - 1) == '.') {
                        && hostname.charAt(
                                hostname.length() - domain.hostname.length() - 1) == '.') {
                    if (bestMatch == null) {
                        bestMatch = entry;
                    } else if (domain.hostname.length() > bestMatch.first.hostname.length()) {
@@ -113,6 +118,10 @@ public final class ApplicationConfig {
            if (bestMatch != null) {
                return bestMatch.second;
            }
        }
        if (mLocalhostConfig != null && Domain.isLocalhost(hostname)) {
            return mLocalhostConfig;
        }
        // If no match was found use the default configuration.
        return mDefaultConfig;
    }
@@ -195,6 +204,7 @@ public final class ApplicationConfig {
            }
            mConfigs = mConfigSource.getPerDomainConfigs();
            mDefaultConfig = mConfigSource.getDefaultConfig();
            mLocalhostConfig = mConfigSource.getLocalhostConfig();
            mConfigSource = null;
            mTrustManager = new RootTrustManager(this);
            mInitialized = true;
+13 −0
Original line number Diff line number Diff line
@@ -16,11 +16,24 @@

package android.security.net.config;

import android.annotation.Nullable;
import android.util.Pair;

import java.util.Set;

/** @hide */
public interface ConfigSource {

    /** Returns the set of configurations that are associated with a Domain. */
    @Nullable
    Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs();

    /** Returns the default NetworkSecurityConfig */
    NetworkSecurityConfig getDefaultConfig();

    /** Returns the NetworkSecurityConfig associated with localhost.
     *  See {@link Domain#isLocalhost()} for the exact definition.
     */
    @Nullable
    NetworkSecurityConfig getLocalhostConfig();
}
+33 −0
Original line number Diff line number Diff line
@@ -17,11 +17,17 @@
package android.security.net.config;

import android.annotation.Nullable;
import android.net.InetAddresses;

import java.net.InetAddress;
import java.util.Locale;
import java.util.Set;


/** @hide */
public final class Domain {
    private static final Set<String> LOCALHOSTS = Set.of("localhost", "ip6-localhost");

    /**
     * Lower case hostname for this domain rule.
     */
@@ -57,4 +63,31 @@ public final class Domain {
        return otherDomain.subdomainsIncluded == this.subdomainsIncluded &&
                otherDomain.hostname.equals(this.hostname);
    }

    public boolean isLocalhost() {
        return isLocalhost(this.hostname);
    }

    /**
     * Whether the hostname is considered to be localhost or not.
     */
    public static boolean isLocalhost(String hostname) {
        if (LOCALHOSTS.contains(hostname)) {
            return true;
        }
        // RFC 2732: To use a literal IPv6 address in a URL, the literal
        // address should be enclosed in "[" and "]" characters.
        if (hostname.charAt(0) == '[' && hostname.charAt(hostname.length() - 1) == ']') {
            hostname = hostname.substring(1, hostname.length() - 1);
        }
        // parseNumericAddress raises an exception if the address is not valid.
        // We could use isNumericAddress beforehand to avoid the exception, but
        // this would imply parsing the address twice. Simply ignore
        // IllegalArgumentException.
        try {
            InetAddress addr = InetAddresses.parseNumericAddress(hostname);
            return addr.isLoopbackAddress();
        } catch (IllegalArgumentException e) { }
        return false;
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -45,5 +45,10 @@ class KeyStoreConfigSource implements ConfigSource {
    public NetworkSecurityConfig getDefaultConfig() {
        return mConfig;
    }

    @Override
    public NetworkSecurityConfig getLocalhostConfig() {
        return null;
    }
}
+18 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.security.net.config;

import static com.android.org.conscrypt.net.flags.Flags.networkSecurityConfigLocalhost;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.util.Log;
@@ -50,6 +52,11 @@ public class ManifestConfigSource implements ConfigSource {
        return getConfigSource().getDefaultConfig();
    }

    @Override
    public NetworkSecurityConfig getLocalhostConfig() {
        return getConfigSource().getLocalhostConfig();
    }

    private ConfigSource getConfigSource() {
        synchronized (mLock) {
            if (mConfigSource != null) {
@@ -86,11 +93,17 @@ public class ManifestConfigSource implements ConfigSource {
    private static final class DefaultConfigSource implements ConfigSource {

        private final NetworkSecurityConfig mDefaultConfig;
        private final NetworkSecurityConfig mLocalhostConfig;

        DefaultConfigSource(boolean usesCleartextTraffic, ApplicationInfo info) {
            mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(info)
                    .setCleartextTrafficPermitted(usesCleartextTraffic)
                    .build();
            if (networkSecurityConfigLocalhost()) {
                mLocalhostConfig = NetworkSecurityConfig.getLocalhostBuilder().build();
            } else {
                mLocalhostConfig = null;
            }
        }

        @Override
@@ -98,6 +111,11 @@ public class ManifestConfigSource implements ConfigSource {
            return mDefaultConfig;
        }

        @Override
        public NetworkSecurityConfig getLocalhostConfig() {
            return mLocalhostConfig;
        }

        @Override
        public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
            return null;
Loading