Merge "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