Merge "Add initial network security config implementation" am: 8c35820720 am: 0bafbbfcb4

am: b4b53b0741

* commit 'b4b53b0741b6ff75842d6630d5d1010c4efa766c':
  Add initial network security config implementation
This commit is contained in:
Chad Brubaker
2015-11-05 19:05:47 +00:00
committed by android-build-merger
19 changed files with 1323 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
/*
* 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;
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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();
}

View File

@@ -0,0 +1,41 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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();
}

View File

@@ -0,0 +1,57 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.List;
import java.util.Set;
import javax.net.ssl.X509TrustManager;
/**
* @hide
*/
public final class NetworkSecurityConfig {
private final boolean mCleartextTrafficPermitted;
private final boolean mHstsEnforced;
private final PinSet mPins;
private final List<CertificatesEntryRef> mCertificatesEntryRefs;
private Set<TrustAnchor> mAnchors;
private final Object mAnchorsLock = new Object();
private X509TrustManager mTrustManager;
private final Object mTrustManagerLock = new Object();
public NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced,
PinSet pins, List<CertificatesEntryRef> certificatesEntryRefs) {
mCleartextTrafficPermitted = cleartextTrafficPermitted;
mHstsEnforced = hstsEnforced;
mPins = pins;
mCertificatesEntryRefs = certificatesEntryRefs;
}
public Set<TrustAnchor> getTrustAnchors() {
synchronized (mAnchorsLock) {
if (mAnchors != null) {
return mAnchors;
}
Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
anchors.addAll(ref.getTrustAnchors());
}
mAnchors = anchors;
return anchors;
}
}
public boolean isCleartextTrafficPermitted() {
return mCleartextTrafficPermitted;
}
public boolean isHstsEnforced() {
return mHstsEnforced;
}
public PinSet getPins() {
return mPins;
}
public X509TrustManager getTrustManager() {
synchronized(mTrustManagerLock) {
if (mTrustManager == null) {
mTrustManager = new NetworkSecurityTrustManager(this);
}
return mTrustManager;
}
}
void onTrustStoreChange() {
synchronized (mAnchorsLock) {
mAnchors = null;
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* 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 com.android.org.conscrypt.TrustManagerImpl;
import android.util.ArrayMap;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.net.ssl.X509TrustManager;
/**
* {@link X509TrustManager} that implements the trust anchor and pinning for a
* given {@link NetworkSecurityConfig}.
* @hide
*/
public class NetworkSecurityTrustManager implements X509TrustManager {
// TODO: Replace this with a general X509TrustManager and use duck-typing.
private final TrustManagerImpl mDelegate;
private final NetworkSecurityConfig mNetworkSecurityConfig;
public NetworkSecurityTrustManager(NetworkSecurityConfig config) {
if (config == null) {
throw new NullPointerException("config must not be null");
}
mNetworkSecurityConfig = config;
// TODO: Create our own better KeyStoreImpl
try {
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
store.load(null);
int certNum = 0;
for (TrustAnchor anchor : mNetworkSecurityConfig.getTrustAnchors()) {
store.setEntry(String.valueOf(certNum++),
new KeyStore.TrustedCertificateEntry(anchor.certificate),
null);
}
mDelegate = new TrustManagerImpl(store);
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
throw new CertificateException("Client authentication not supported");
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
List<X509Certificate> trustedChain =
mDelegate.checkServerTrusted(certs, authType, (String) null);
checkPins(trustedChain);
}
private void checkPins(List<X509Certificate> chain) throws CertificateException {
PinSet pinSet = mNetworkSecurityConfig.getPins();
if (pinSet.pins.isEmpty()
|| System.currentTimeMillis() > pinSet.expirationTime
|| !isPinningEnforced(chain)) {
return;
}
Set<String> pinAlgorithms = pinSet.getPinAlgorithms();
Map<String, MessageDigest> digestMap = new ArrayMap<String, MessageDigest>(
pinAlgorithms.size());
for (int i = chain.size() - 1; i >= 0 ; i--) {
X509Certificate cert = chain.get(i);
byte[] encodedSPKI = cert.getPublicKey().getEncoded();
for (String algorithm : pinAlgorithms) {
MessageDigest md = digestMap.get(algorithm);
if (md == null) {
try {
md = MessageDigest.getInstance(algorithm);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
digestMap.put(algorithm, md);
}
if (pinSet.pins.contains(new Pin(algorithm, md.digest(encodedSPKI)))) {
return;
}
}
}
// TODO: Throw a subclass of CertificateException which indicates a pinning failure.
throw new CertificateException("Pin verification failed");
}
private boolean isPinningEnforced(List<X509Certificate> chain) throws CertificateException {
if (chain.isEmpty()) {
return false;
}
X509Certificate anchorCert = chain.get(chain.size() - 1);
TrustAnchor chainAnchor = null;
// TODO: faster lookup
for (TrustAnchor anchor : mNetworkSecurityConfig.getTrustAnchors()) {
if (anchor.certificate.equals(anchorCert)) {
chainAnchor = anchor;
break;
}
}
if (chainAnchor == null) {
throw new CertificateException("Trusted chain does not end in a TrustAnchor");
}
return !chainAnchor.overridesPins;
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.Arrays;
/** @hide */
public final class Pin {
public final String digestAlgorithm;
public final byte[] digest;
private final int mHashCode;
public Pin(String digestAlgorithm, byte[] digest) {
this.digestAlgorithm = digestAlgorithm;
this.digest = digest;
mHashCode = Arrays.hashCode(digest) ^ digestAlgorithm.hashCode();
}
@Override
public int hashCode() {
return mHashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Pin)) {
return false;
}
Pin other = (Pin) obj;
if (other.hashCode() != mHashCode) {
return false;
}
if (!Arrays.equals(digest, other.digest)) {
return false;
}
if (!digestAlgorithm.equals(other.digestAlgorithm)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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;
/** @hide */
public final class PinSet {
public final long expirationTime;
public final Set<Pin> pins;
public PinSet(Set<Pin> pins, long expirationTime) {
if (pins == null) {
throw new NullPointerException("pins must not be null");
}
this.pins = pins;
this.expirationTime = expirationTime;
}
Set<String> getPinAlgorithms() {
// TODO: Cache this.
Set<String> algorithms = new ArraySet<String>();
for (Pin pin : pins) {
algorithms.add(pin.digestAlgorithm);
}
return algorithms;
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.content.Context;
import android.util.ArraySet;
import libcore.io.IoUtils;
import java.io.InputStream;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Set;
/**
* {@link CertificateSource} based on certificates contained in an application resource file.
* @hide
*/
public class ResourceCertificateSource implements CertificateSource {
private Set<X509Certificate> mCertificates;
private final int mResourceId;
private Context mContext;
private final Object mLock = new Object();
public ResourceCertificateSource(int resourceId, Context context) {
mResourceId = resourceId;
mContext = context.getApplicationContext();
}
@Override
public Set<X509Certificate> getCertificates() {
synchronized (mLock) {
if (mCertificates != null) {
return mCertificates;
}
Set<X509Certificate> certificates = new ArraySet<X509Certificate>();
Collection<? extends Certificate> certs;
InputStream in = null;
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
in = mContext.getResources().openRawResource(mResourceId);
certs = factory.generateCertificates(in);
} catch (CertificateException e) {
throw new RuntimeException("Failed to load trust anchors from id " + mResourceId,
e);
} finally {
IoUtils.closeQuietly(in);
}
for (Certificate cert : certs) {
certificates.add((X509Certificate) cert);
}
mCertificates = certificates;
mContext = null;
return mCertificates;
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/**
* {@link X509TrustManager} based on an {@link ApplicationConfig}.
*
* <p>This {@code X509TrustManager} delegates to the specific trust manager for the hostname
* being used for the connection (See {@link ApplicationConfig#getConfigForHostname(String)} and
* {@link NetworkSecurityTrustManager}).</p>
*
* Note that if the {@code ApplicationConfig} has per-domain configurations the hostname aware
* {@link #checkServerTrusted(X509Certificate[], String String)} must be used instead of the normal
* non-aware call.
* @hide */
public class RootTrustManager implements X509TrustManager {
private final ApplicationConfig mConfig;
private static final X509Certificate[] EMPTY_ISSUERS = new X509Certificate[0];
public RootTrustManager(ApplicationConfig config) {
if (config == null) {
throw new NullPointerException("config must not be null");
}
mConfig = config;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
throw new CertificateException("Client authentication not supported");
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
if (mConfig.hasPerDomainConfigs()) {
throw new CertificateException(
"Domain specific configurations require that hostname aware"
+ " checkServerTrusted(X509Certificate[], String, String) is used");
}
NetworkSecurityConfig config = mConfig.getConfigForHostname("");
config.getTrustManager().checkServerTrusted(certs, authType);
}
public void checkServerTrusted(X509Certificate[] certs, String authType, String hostname)
throws CertificateException {
NetworkSecurityConfig config = mConfig.getConfigForHostname(hostname);
config.getTrustManager().checkServerTrusted(certs, authType);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return EMPTY_ISSUERS;
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.os.Environment;
import android.os.UserHandle;
import android.util.ArraySet;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Set;
import libcore.io.IoUtils;
/**
* {@link CertificateSource} based on the system trusted CA store.
* @hide
*/
public class SystemCertificateSource implements CertificateSource {
private static Set<X509Certificate> sSystemCerts = null;
private static final Object sLock = new Object();
public SystemCertificateSource() {
}
@Override
public Set<X509Certificate> getCertificates() {
// TODO: loading all of these is wasteful, we should instead use a keystore style API.
synchronized (sLock) {
if (sSystemCerts != null) {
return sSystemCerts;
}
CertificateFactory certFactory;
try {
certFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
}
final String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
final File systemCaDir = new File(ANDROID_ROOT + "/etc/security/cacerts");
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
final File userRemovedCaDir = new File(configDir, "cacerts-removed");
// Sanity check
if (!systemCaDir.isDirectory()) {
throw new AssertionError(systemCaDir + " is not a directory");
}
Set<X509Certificate> systemCerts = new ArraySet<X509Certificate>();
for (String caFile : systemCaDir.list()) {
// Skip any CAs in the user's deleted directory.
if (new File(userRemovedCaDir, caFile).exists()) {
continue;
}
InputStream is = null;
try {
is = new BufferedInputStream(
new FileInputStream(new File(systemCaDir, caFile)));
systemCerts.add((X509Certificate) certFactory.generateCertificate(is));
} catch (CertificateException | IOException e) {
// Don't rethrow to be consistent with conscrypt's cert loading code.
continue;
} finally {
IoUtils.closeQuietly(is);
}
}
sSystemCerts = systemCerts;
return sSystemCerts;
}
}
public void onCertificateStorageChange() {
synchronized (sLock) {
sSystemCerts = null;
}
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.security.cert.X509Certificate;
/** @hide */
public final class TrustAnchor {
public final X509Certificate certificate;
public final boolean overridesPins;
public TrustAnchor(X509Certificate certificate, boolean overridesPins) {
if (certificate == null) {
throw new NullPointerException("certificate");
}
this.certificate = certificate;
this.overridesPins = overridesPins;
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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.os.Environment;
import android.os.UserHandle;
import android.util.ArraySet;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Set;
import libcore.io.IoUtils;
/**
* {@link CertificateSource} based on the user-installed trusted CA store.
* @hide
*/
public class UserCertificateSource implements CertificateSource {
private static Set<X509Certificate> sUserCerts = null;
private static final Object sLock = new Object();
public UserCertificateSource() {
}
@Override
public Set<X509Certificate> getCertificates() {
// TODO: loading all of these is wasteful, we should instead use a keystore style API.
synchronized (sLock) {
if (sUserCerts != null) {
return sUserCerts;
}
CertificateFactory certFactory;
try {
certFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
}
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
final File userCaDir = new File(configDir, "cacerts-added");
if (!userCaDir.isDirectory()) {
throw new AssertionError(userCaDir + " is not a directory");
}
Set<X509Certificate> userCerts = new ArraySet<X509Certificate>();
for (String caFile : userCaDir.list()) {
InputStream is = null;
try {
is = new BufferedInputStream(
new FileInputStream(new File(userCaDir, caFile)));
userCerts.add((X509Certificate) certFactory.generateCertificate(is));
} catch (CertificateException | IOException e) {
// Don't rethrow to be consistent with conscrypt's cert loading code.
continue;
} finally {
IoUtils.closeQuietly(is);
}
}
sUserCerts = userCerts;
return sUserCerts;
}
}
public void onCertificateStorageChange() {
synchronized (sLock) {
sUserCerts = null;
}
}
}