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

Commit 6bc1e396 authored by Chad Brubaker's avatar Chad Brubaker
Browse files

Add initial network security config implementation

Initial implementation of a unified application wide static
network security configuration.

This currently encompases:
* Trust decisions such as what trust anchors to use as well as static
  certificate pinning.
* Policy on what to do with cleartext traffic.

In order to prevent issues due to interplay of various components in an
application and their potentially different security requirements
configuration can be specified at a per-domain granularity in addition
to application wide defaults.

This change contains the internal data structures and trust management
code, hooking these up in application startup will come in a future
commit.

Change-Id: I53ce5ba510a4221d58839e61713262a8f4c6699c
parent 5d562d4d
Loading
Loading
Loading
Loading
+132 −0
Original line number Original line Diff line number Diff line
/*
 * 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.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 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.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;
    }

    private void ensureInitialized() {
        synchronized(mLock) {
            if (mInitialized) {
                return;
            }
            mConfigs = mConfigSource.getPerDomainConfigs();
            mDefaultConfig = mConfigSource.getDefaultConfig();
            mConfigSource = null;
            mTrustManager = new RootTrustManager(this);
            mInitialized = true;
        }
    }
}
+25 −0
Original line number Original line Diff line number Diff line
/*
 * 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 java.util.Set;
import java.security.cert.X509Certificate;

/** @hide */
public interface CertificateSource {
    Set<X509Certificate> getCertificates();
}
+41 −0
Original line number Original line Diff line number Diff line
/*
 * 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.ArraySet;
import java.util.Set;
import java.security.cert.X509Certificate;

/** @hide */
public final class CertificatesEntryRef {
    private final CertificateSource mSource;
    private final boolean mOverridesPins;

    public CertificatesEntryRef(CertificateSource source, boolean overridesPins) {
        mSource = source;
        mOverridesPins = overridesPins;
    }

    public Set<TrustAnchor> getTrustAnchors() {
        // TODO: cache this [but handle mutable sources]
        Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
        for (X509Certificate cert : mSource.getCertificates()) {
            anchors.add(new TrustAnchor(cert, mOverridesPins));
        }
        return anchors;
    }
}
+26 −0
Original line number Original line Diff line number Diff line
/*
 * 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.Set;

/** @hide */
public interface ConfigSource {
    Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs();
    NetworkSecurityConfig getDefaultConfig();
}
+57 −0
Original line number Original line Diff line number Diff line
/*
 * 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 java.util.Locale;
/** @hide */
public final class Domain {
    /**
     * Lower case hostname for this domain rule.
     */
    public final String hostname;

    /**
     * Whether this domain includes subdomains.
     */
    public final boolean subdomainsIncluded;

    public Domain(String hostname, boolean subdomainsIncluded) {
        if (hostname == null) {
            throw new NullPointerException("Hostname must not be null");
        }
        this.hostname = hostname.toLowerCase(Locale.US);
        this.subdomainsIncluded = subdomainsIncluded;
    }

    @Override
    public int hashCode() {
        return hostname.hashCode() ^ (subdomainsIncluded ? 1231 : 1237);
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        }
        if (!(other instanceof Domain)) {
            return false;
        }
        Domain otherDomain = (Domain) other;
        return otherDomain.subdomainsIncluded == this.subdomainsIncluded &&
                otherDomain.hostname.equals(this.hostname);
    }
}
Loading