Merge "Add initial network security config implementation"
am: 8c35820720
* commit '8c358207206c5e19541c65e3f9761d1adb54550b':
Add initial network security config implementation
This commit is contained in:
132
core/java/android/security/net/config/ApplicationConfig.java
Normal file
132
core/java/android/security/net/config/ApplicationConfig.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
core/java/android/security/net/config/CertificateSource.java
Normal file
25
core/java/android/security/net/config/CertificateSource.java
Normal 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();
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
core/java/android/security/net/config/ConfigSource.java
Normal file
26
core/java/android/security/net/config/ConfigSource.java
Normal 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();
|
||||||
|
}
|
||||||
57
core/java/android/security/net/config/Domain.java
Normal file
57
core/java/android/security/net/config/Domain.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
58
core/java/android/security/net/config/Pin.java
Normal file
58
core/java/android/security/net/config/Pin.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
core/java/android/security/net/config/PinSet.java
Normal file
43
core/java/android/security/net/config/PinSet.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
core/java/android/security/net/config/RootTrustManager.java
Normal file
74
core/java/android/security/net/config/RootTrustManager.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
core/java/android/security/net/config/TrustAnchor.java
Normal file
33
core/java/android/security/net/config/TrustAnchor.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
tests/NetworkSecurityConfigTest/Android.mk
Normal file
15
tests/NetworkSecurityConfigTest/Android.mk
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
# We only want this apk build for tests.
|
||||||
|
LOCAL_MODULE_TAGS := tests
|
||||||
|
LOCAL_CERTIFICATE := platform
|
||||||
|
|
||||||
|
LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt
|
||||||
|
|
||||||
|
# Include all test java files.
|
||||||
|
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||||
|
|
||||||
|
LOCAL_PACKAGE_NAME := NetworkSecurityConfigTests
|
||||||
|
|
||||||
|
include $(BUILD_PACKAGE)
|
||||||
29
tests/NetworkSecurityConfigTest/AndroidManifest.xml
Normal file
29
tests/NetworkSecurityConfigTest/AndroidManifest.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="android.security.tests"
|
||||||
|
android:sharedUserId="android.uid.system">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<uses-library android:name="android.test.runner" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<instrumentation android:name="android.test.InstrumentationTestRunner"
|
||||||
|
android:targetPackage="android.security.tests"
|
||||||
|
android:label="ANSC Tests">
|
||||||
|
</instrumentation>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.Activity;
|
||||||
|
import android.test.ActivityUnitTestCase;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
import android.util.Pair;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
|
||||||
|
public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
|
||||||
|
|
||||||
|
public NetworkSecurityConfigTests() {
|
||||||
|
super(Activity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHA-256 of the G2 intermediate CA for android.com (as of 10/2015).
|
||||||
|
private static final byte[] G2_SPKI_SHA256
|
||||||
|
= hexToBytes("ec722969cb64200ab6638f68ac538e40abab5b19a6485661042a1061c4612776");
|
||||||
|
|
||||||
|
private static byte[] hexToBytes(String s) {
|
||||||
|
int len = s.length();
|
||||||
|
byte[] data = new byte[len / 2];
|
||||||
|
for (int i = 0; i < len; i += 2) {
|
||||||
|
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(
|
||||||
|
s.charAt(i + 1), 16));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertConnectionFails(SSLContext context, String host, int port)
|
||||||
|
throws Exception {
|
||||||
|
try {
|
||||||
|
Socket s = context.getSocketFactory().createSocket(host, port);
|
||||||
|
s.getInputStream();
|
||||||
|
fail("Expected connection to " + host + ":" + port + " to fail.");
|
||||||
|
} catch (SSLHandshakeException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertConnectionSucceeds(SSLContext context, String host, int port)
|
||||||
|
throws Exception {
|
||||||
|
Socket s = context.getSocketFactory().createSocket(host, port);
|
||||||
|
s.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertUrlConnectionFails(SSLContext context, String host, int port)
|
||||||
|
throws Exception {
|
||||||
|
URL url = new URL("https://" + host + ":" + port);
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
connection.setSSLSocketFactory(context.getSocketFactory());
|
||||||
|
try {
|
||||||
|
connection.getInputStream();
|
||||||
|
fail("Connection to " + host + ":" + port + " expected to fail");
|
||||||
|
} catch (SSLHandshakeException expected) {
|
||||||
|
// ignored.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertUrlConnectionSucceeds(SSLContext context, String host, int port)
|
||||||
|
throws Exception {
|
||||||
|
URL url = new URL("https://" + host + ":" + port);
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
connection.setSSLSocketFactory(context.getSocketFactory());
|
||||||
|
connection.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSLContext getSSLContext(ConfigSource source) throws Exception {
|
||||||
|
ApplicationConfig config = new ApplicationConfig(source);
|
||||||
|
SSLContext context = SSLContext.getInstance("TLS");
|
||||||
|
context.init(null, new TrustManager[] {config.getTrustManager()}, null);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a NetworkSecurityConfig that has an empty TrustAnchor set. This should always cause a
|
||||||
|
* SSLHandshakeException when used for a connection.
|
||||||
|
*/
|
||||||
|
private NetworkSecurityConfig getEmptyConfig() {
|
||||||
|
return new NetworkSecurityConfig(true, false,
|
||||||
|
new PinSet(new ArraySet<Pin>(), -1),
|
||||||
|
new ArrayList<CertificatesEntryRef>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkSecurityConfig getSystemStoreConfig() {
|
||||||
|
ArrayList<CertificatesEntryRef> defaultSource = new ArrayList<CertificatesEntryRef>();
|
||||||
|
defaultSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
|
||||||
|
return new NetworkSecurityConfig(true, false, new PinSet(new ArraySet<Pin>(),
|
||||||
|
-1), defaultSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEmptyConfig() throws Exception {
|
||||||
|
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
|
||||||
|
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
|
||||||
|
ConfigSource testSource =
|
||||||
|
new TestConfigSource(domainMap, getEmptyConfig());
|
||||||
|
SSLContext context = getSSLContext(testSource);
|
||||||
|
assertConnectionFails(context, "android.com", 443);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEmptyPerNetworkSecurityConfig() throws Exception {
|
||||||
|
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
|
||||||
|
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
|
||||||
|
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
|
||||||
|
new Domain("android.com", true), getEmptyConfig()));
|
||||||
|
ArrayList<CertificatesEntryRef> defaultSource = new ArrayList<CertificatesEntryRef>();
|
||||||
|
defaultSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
|
||||||
|
NetworkSecurityConfig defaultConfig = new NetworkSecurityConfig(true, false,
|
||||||
|
new PinSet(new ArraySet<Pin>(), -1),
|
||||||
|
defaultSource);
|
||||||
|
SSLContext context = getSSLContext(new TestConfigSource(domainMap, defaultConfig));
|
||||||
|
assertConnectionFails(context, "android.com", 443);
|
||||||
|
assertConnectionSucceeds(context, "google.com", 443);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBadPin() throws Exception {
|
||||||
|
ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
|
||||||
|
systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
|
||||||
|
ArraySet<Pin> pins = new ArraySet<Pin>();
|
||||||
|
pins.add(new Pin("SHA-256", new byte[0]));
|
||||||
|
NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
|
||||||
|
new PinSet(pins, Long.MAX_VALUE),
|
||||||
|
systemSource);
|
||||||
|
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
|
||||||
|
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
|
||||||
|
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
|
||||||
|
new Domain("android.com", true), domain));
|
||||||
|
SSLContext context
|
||||||
|
= getSSLContext(new TestConfigSource(domainMap, getSystemStoreConfig()));
|
||||||
|
assertConnectionFails(context, "android.com", 443);
|
||||||
|
assertConnectionSucceeds(context, "google.com", 443);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGoodPin() throws Exception {
|
||||||
|
ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
|
||||||
|
systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
|
||||||
|
ArraySet<Pin> pins = new ArraySet<Pin>();
|
||||||
|
pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
|
||||||
|
NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
|
||||||
|
new PinSet(pins, Long.MAX_VALUE),
|
||||||
|
systemSource);
|
||||||
|
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
|
||||||
|
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
|
||||||
|
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
|
||||||
|
new Domain("android.com", true), domain));
|
||||||
|
SSLContext context
|
||||||
|
= getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
|
||||||
|
assertConnectionSucceeds(context, "android.com", 443);
|
||||||
|
assertConnectionSucceeds(context, "developer.android.com", 443);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testOverridePins() throws Exception {
|
||||||
|
// Use a bad pin + granting the system CA store the ability to override pins.
|
||||||
|
ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
|
||||||
|
systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), true));
|
||||||
|
ArraySet<Pin> pins = new ArraySet<Pin>();
|
||||||
|
pins.add(new Pin("SHA-256", new byte[0]));
|
||||||
|
NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
|
||||||
|
new PinSet(pins, Long.MAX_VALUE),
|
||||||
|
systemSource);
|
||||||
|
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
|
||||||
|
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
|
||||||
|
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
|
||||||
|
new Domain("android.com", true), domain));
|
||||||
|
SSLContext context
|
||||||
|
= getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
|
||||||
|
assertConnectionSucceeds(context, "android.com", 443);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testMostSpecificNetworkSecurityConfig() throws Exception {
|
||||||
|
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
|
||||||
|
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
|
||||||
|
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
|
||||||
|
new Domain("android.com", true), getEmptyConfig()));
|
||||||
|
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
|
||||||
|
new Domain("developer.android.com", false), getSystemStoreConfig()));
|
||||||
|
SSLContext context
|
||||||
|
= getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
|
||||||
|
assertConnectionFails(context, "android.com", 443);
|
||||||
|
assertConnectionSucceeds(context, "developer.android.com", 443);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSubdomainIncluded() throws Exception {
|
||||||
|
// First try connecting to a subdomain of a domain entry that includes subdomains.
|
||||||
|
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
|
||||||
|
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
|
||||||
|
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
|
||||||
|
new Domain("android.com", true), getSystemStoreConfig()));
|
||||||
|
SSLContext context
|
||||||
|
= getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
|
||||||
|
assertConnectionSucceeds(context, "developer.android.com", 443);
|
||||||
|
// Now try without including subdomains.
|
||||||
|
domainMap = new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
|
||||||
|
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
|
||||||
|
new Domain("android.com", false), getSystemStoreConfig()));
|
||||||
|
context = getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
|
||||||
|
assertConnectionFails(context, "developer.android.com", 443);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testWithUrlConnection() throws Exception {
|
||||||
|
ArrayList<CertificatesEntryRef> systemSource = new ArrayList<CertificatesEntryRef>();
|
||||||
|
systemSource.add(new CertificatesEntryRef(new SystemCertificateSource(), false));
|
||||||
|
ArraySet<Pin> pins = new ArraySet<Pin>();
|
||||||
|
pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
|
||||||
|
NetworkSecurityConfig domain = new NetworkSecurityConfig(true, false,
|
||||||
|
new PinSet(pins, Long.MAX_VALUE),
|
||||||
|
systemSource);
|
||||||
|
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
|
||||||
|
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
|
||||||
|
domainMap.add(new Pair<Domain, NetworkSecurityConfig>(
|
||||||
|
new Domain("android.com", true), domain));
|
||||||
|
SSLContext context
|
||||||
|
= getSSLContext(new TestConfigSource(domainMap, getEmptyConfig()));
|
||||||
|
assertUrlConnectionSucceeds(context, "android.com", 443);
|
||||||
|
assertUrlConnectionSucceeds(context, "developer.android.com", 443);
|
||||||
|
assertUrlConnectionFails(context, "google.com", 443);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.util.Set;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public class TestCertificateSource implements CertificateSource {
|
||||||
|
|
||||||
|
private final Set<X509Certificate> mCertificates;
|
||||||
|
public TestCertificateSource(Set<X509Certificate> certificates) {
|
||||||
|
mCertificates = certificates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<X509Certificate> getCertificates() {
|
||||||
|
return mCertificates;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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 class TestConfigSource implements ConfigSource {
|
||||||
|
private final Set<Pair<Domain, NetworkSecurityConfig>> mConfigs;
|
||||||
|
private final NetworkSecurityConfig mDefaultConfig;
|
||||||
|
public TestConfigSource(Set<Pair<Domain, NetworkSecurityConfig>> configs,
|
||||||
|
NetworkSecurityConfig defaultConfig) {
|
||||||
|
mConfigs = configs;
|
||||||
|
mDefaultConfig = defaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
|
||||||
|
return mConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkSecurityConfig getDefaultConfig() {
|
||||||
|
return mDefaultConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user