Loading core/java/android/security/net/config/CertificateSource.java +1 −0 Original line number Diff line number Diff line Loading @@ -22,4 +22,5 @@ import java.security.cert.X509Certificate; /** @hide */ public interface CertificateSource { Set<X509Certificate> getCertificates(); X509Certificate findBySubjectAndPublicKey(X509Certificate cert); } core/java/android/security/net/config/CertificatesEntryRef.java +13 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,10 @@ public final class CertificatesEntryRef { mOverridesPins = overridesPins; } boolean overridesPins() { return mOverridesPins; } public Set<TrustAnchor> getTrustAnchors() { // TODO: cache this [but handle mutable sources] Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>(); Loading @@ -38,4 +42,13 @@ public final class CertificatesEntryRef { } return anchors; } public TrustAnchor findBySubjectAndPublicKey(X509Certificate cert) { X509Certificate foundCert = mSource.findBySubjectAndPublicKey(cert); if (foundCert == null) { return null; } return new TrustAnchor(foundCert, mOverridesPins); } } core/java/android/security/net/config/DirectoryCertificateSource.java 0 → 100644 +138 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.security.net.config; import android.os.Environment; import android.os.UserHandle; import android.util.ArraySet; import android.util.Pair; 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; import com.android.org.conscrypt.NativeCrypto; import javax.security.auth.x500.X500Principal; /** * {@link CertificateSource} based on a directory where certificates are stored as individual files * named after a hash of their SubjectName for more efficient lookups. * @hide */ abstract class DirectoryCertificateSource implements CertificateSource { private final File mDir; private final Object mLock = new Object(); private final CertificateFactory mCertFactory; private Set<X509Certificate> mCertificates; protected DirectoryCertificateSource(File caDir) { mDir = caDir; try { mCertFactory = CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); } } protected abstract boolean isCertMarkedAsRemoved(String caFile); @Override public Set<X509Certificate> getCertificates() { // TODO: loading all of these is wasteful, we should instead use a keystore style API. synchronized (mLock) { if (mCertificates != null) { return mCertificates; } Set<X509Certificate> certs = new ArraySet<X509Certificate>(); if (mDir.isDirectory()) { for (String caFile : mDir.list()) { if (isCertMarkedAsRemoved(caFile)) { continue; } X509Certificate cert = readCertificate(caFile); if (cert != null) { certs.add(cert); } } } mCertificates = certs; return mCertificates; } } @Override public X509Certificate findBySubjectAndPublicKey(final X509Certificate cert) { return findCert(cert.getSubjectX500Principal(), new CertSelector() { @Override public boolean match(X509Certificate ca) { return ca.getPublicKey().equals(cert.getPublicKey()); } }); } private static interface CertSelector { boolean match(X509Certificate cert); } private X509Certificate findCert(X500Principal subj, CertSelector selector) { String hash = getHash(subj); for (int index = 0; index >= 0; index++) { String fileName = hash + "." + index; if (!new File(mDir, fileName).exists()) { break; } if (isCertMarkedAsRemoved(fileName)) { continue; } X509Certificate cert = readCertificate(fileName); if (!subj.equals(cert.getSubjectX500Principal())) { continue; } if (selector.match(cert)) { return cert; } } return null; } private String getHash(X500Principal name) { int hash = NativeCrypto.X509_NAME_hash_old(name); return IntegralToString.intToHexString(hash, false, 8); } private X509Certificate readCertificate(String file) { InputStream is = null; try { is = new BufferedInputStream(new FileInputStream(new File(mDir, file))); return (X509Certificate) mCertFactory.generateCertificate(is); } catch (CertificateException | IOException e) { return null; } finally { IoUtils.closeQuietly(is); } } } core/java/android/security/net/config/KeyStoreCertificateSource.java +23 −2 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.Set; import com.android.org.conscrypt.TrustedCertificateIndex; /** * {@link CertificateSource} which provides certificates from trusted certificate entries of a * {@link KeyStore}. Loading @@ -31,6 +33,7 @@ import java.util.Set; class KeyStoreCertificateSource implements CertificateSource { private final Object mLock = new Object(); private final KeyStore mKeyStore; private TrustedCertificateIndex mIndex; private Set<X509Certificate> mCertificates; public KeyStoreCertificateSource(KeyStore ks) { Loading @@ -39,24 +42,42 @@ class KeyStoreCertificateSource implements CertificateSource { @Override public Set<X509Certificate> getCertificates() { ensureInitialized(); return mCertificates; } private void ensureInitialized() { synchronized (mLock) { if (mCertificates != null) { return mCertificates; return; } try { TrustedCertificateIndex localIndex = new TrustedCertificateIndex(); Set<X509Certificate> certificates = new ArraySet<>(mKeyStore.size()); for (Enumeration<String> en = mKeyStore.aliases(); en.hasMoreElements();) { String alias = en.nextElement(); X509Certificate cert = (X509Certificate) mKeyStore.getCertificate(alias); if (cert != null) { certificates.add(cert); localIndex.index(cert); } } mIndex = localIndex; mCertificates = certificates; return mCertificates; } catch (KeyStoreException e) { throw new RuntimeException("Failed to load certificates from KeyStore", e); } } } @Override public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) { ensureInitialized(); java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert); if (anchor == null) { return null; } return anchor.getTrustedCert(); } } core/java/android/security/net/config/NetworkSecurityConfig.java +30 −4 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; Loading Loading @@ -53,6 +54,19 @@ public final class NetworkSecurityConfig { mHstsEnforced = hstsEnforced; mPins = pins; mCertificatesEntryRefs = certificatesEntryRefs; // Sort the certificates entry refs so that all entries that override pins come before // non-override pin entries. This allows us to handle the case where a certificate is in // multiple entry refs by returning the certificate from the first entry ref. Collections.sort(mCertificatesEntryRefs, new Comparator<CertificatesEntryRef>() { @Override public int compare(CertificatesEntryRef lhs, CertificatesEntryRef rhs) { if (lhs.overridesPins()) { return rhs.overridesPins() ? 0 : -1; } else { return rhs.overridesPins() ? 1 : 0; } } }); } public Set<TrustAnchor> getTrustAnchors() { Loading @@ -63,14 +77,15 @@ public final class NetworkSecurityConfig { // Merge trust anchors based on the X509Certificate. // If we see the same certificate in two TrustAnchors, one with overridesPins and one // without, the one with overridesPins wins. // Because mCertificatesEntryRefs is sorted with all overridesPins anchors coming first // this can be simplified to just using the first occurrence of a certificate. Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>(); for (CertificatesEntryRef ref : mCertificatesEntryRefs) { Set<TrustAnchor> anchors = ref.getTrustAnchors(); for (TrustAnchor anchor : anchors) { if (anchor.overridesPins) { anchorMap.put(anchor.certificate, anchor); } else if (!anchorMap.containsKey(anchor.certificate)) { anchorMap.put(anchor.certificate, anchor); X509Certificate cert = anchor.certificate; if (!anchorMap.containsKey(cert)) { anchorMap.put(cert, anchor); } } } Loading Loading @@ -108,6 +123,17 @@ public final class NetworkSecurityConfig { } } /** @hide */ public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) { for (CertificatesEntryRef ref : mCertificatesEntryRefs) { TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert); if (anchor != null) { return anchor; } } return null; } /** * Return a {@link Builder} for the default {@code NetworkSecurityConfig}. * Loading Loading
core/java/android/security/net/config/CertificateSource.java +1 −0 Original line number Diff line number Diff line Loading @@ -22,4 +22,5 @@ import java.security.cert.X509Certificate; /** @hide */ public interface CertificateSource { Set<X509Certificate> getCertificates(); X509Certificate findBySubjectAndPublicKey(X509Certificate cert); }
core/java/android/security/net/config/CertificatesEntryRef.java +13 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,10 @@ public final class CertificatesEntryRef { mOverridesPins = overridesPins; } boolean overridesPins() { return mOverridesPins; } public Set<TrustAnchor> getTrustAnchors() { // TODO: cache this [but handle mutable sources] Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>(); Loading @@ -38,4 +42,13 @@ public final class CertificatesEntryRef { } return anchors; } public TrustAnchor findBySubjectAndPublicKey(X509Certificate cert) { X509Certificate foundCert = mSource.findBySubjectAndPublicKey(cert); if (foundCert == null) { return null; } return new TrustAnchor(foundCert, mOverridesPins); } }
core/java/android/security/net/config/DirectoryCertificateSource.java 0 → 100644 +138 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.security.net.config; import android.os.Environment; import android.os.UserHandle; import android.util.ArraySet; import android.util.Pair; 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; import com.android.org.conscrypt.NativeCrypto; import javax.security.auth.x500.X500Principal; /** * {@link CertificateSource} based on a directory where certificates are stored as individual files * named after a hash of their SubjectName for more efficient lookups. * @hide */ abstract class DirectoryCertificateSource implements CertificateSource { private final File mDir; private final Object mLock = new Object(); private final CertificateFactory mCertFactory; private Set<X509Certificate> mCertificates; protected DirectoryCertificateSource(File caDir) { mDir = caDir; try { mCertFactory = CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); } } protected abstract boolean isCertMarkedAsRemoved(String caFile); @Override public Set<X509Certificate> getCertificates() { // TODO: loading all of these is wasteful, we should instead use a keystore style API. synchronized (mLock) { if (mCertificates != null) { return mCertificates; } Set<X509Certificate> certs = new ArraySet<X509Certificate>(); if (mDir.isDirectory()) { for (String caFile : mDir.list()) { if (isCertMarkedAsRemoved(caFile)) { continue; } X509Certificate cert = readCertificate(caFile); if (cert != null) { certs.add(cert); } } } mCertificates = certs; return mCertificates; } } @Override public X509Certificate findBySubjectAndPublicKey(final X509Certificate cert) { return findCert(cert.getSubjectX500Principal(), new CertSelector() { @Override public boolean match(X509Certificate ca) { return ca.getPublicKey().equals(cert.getPublicKey()); } }); } private static interface CertSelector { boolean match(X509Certificate cert); } private X509Certificate findCert(X500Principal subj, CertSelector selector) { String hash = getHash(subj); for (int index = 0; index >= 0; index++) { String fileName = hash + "." + index; if (!new File(mDir, fileName).exists()) { break; } if (isCertMarkedAsRemoved(fileName)) { continue; } X509Certificate cert = readCertificate(fileName); if (!subj.equals(cert.getSubjectX500Principal())) { continue; } if (selector.match(cert)) { return cert; } } return null; } private String getHash(X500Principal name) { int hash = NativeCrypto.X509_NAME_hash_old(name); return IntegralToString.intToHexString(hash, false, 8); } private X509Certificate readCertificate(String file) { InputStream is = null; try { is = new BufferedInputStream(new FileInputStream(new File(mDir, file))); return (X509Certificate) mCertFactory.generateCertificate(is); } catch (CertificateException | IOException e) { return null; } finally { IoUtils.closeQuietly(is); } } }
core/java/android/security/net/config/KeyStoreCertificateSource.java +23 −2 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.Set; import com.android.org.conscrypt.TrustedCertificateIndex; /** * {@link CertificateSource} which provides certificates from trusted certificate entries of a * {@link KeyStore}. Loading @@ -31,6 +33,7 @@ import java.util.Set; class KeyStoreCertificateSource implements CertificateSource { private final Object mLock = new Object(); private final KeyStore mKeyStore; private TrustedCertificateIndex mIndex; private Set<X509Certificate> mCertificates; public KeyStoreCertificateSource(KeyStore ks) { Loading @@ -39,24 +42,42 @@ class KeyStoreCertificateSource implements CertificateSource { @Override public Set<X509Certificate> getCertificates() { ensureInitialized(); return mCertificates; } private void ensureInitialized() { synchronized (mLock) { if (mCertificates != null) { return mCertificates; return; } try { TrustedCertificateIndex localIndex = new TrustedCertificateIndex(); Set<X509Certificate> certificates = new ArraySet<>(mKeyStore.size()); for (Enumeration<String> en = mKeyStore.aliases(); en.hasMoreElements();) { String alias = en.nextElement(); X509Certificate cert = (X509Certificate) mKeyStore.getCertificate(alias); if (cert != null) { certificates.add(cert); localIndex.index(cert); } } mIndex = localIndex; mCertificates = certificates; return mCertificates; } catch (KeyStoreException e) { throw new RuntimeException("Failed to load certificates from KeyStore", e); } } } @Override public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) { ensureInitialized(); java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert); if (anchor == null) { return null; } return anchor.getTrustedCert(); } }
core/java/android/security/net/config/NetworkSecurityConfig.java +30 −4 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; Loading Loading @@ -53,6 +54,19 @@ public final class NetworkSecurityConfig { mHstsEnforced = hstsEnforced; mPins = pins; mCertificatesEntryRefs = certificatesEntryRefs; // Sort the certificates entry refs so that all entries that override pins come before // non-override pin entries. This allows us to handle the case where a certificate is in // multiple entry refs by returning the certificate from the first entry ref. Collections.sort(mCertificatesEntryRefs, new Comparator<CertificatesEntryRef>() { @Override public int compare(CertificatesEntryRef lhs, CertificatesEntryRef rhs) { if (lhs.overridesPins()) { return rhs.overridesPins() ? 0 : -1; } else { return rhs.overridesPins() ? 1 : 0; } } }); } public Set<TrustAnchor> getTrustAnchors() { Loading @@ -63,14 +77,15 @@ public final class NetworkSecurityConfig { // Merge trust anchors based on the X509Certificate. // If we see the same certificate in two TrustAnchors, one with overridesPins and one // without, the one with overridesPins wins. // Because mCertificatesEntryRefs is sorted with all overridesPins anchors coming first // this can be simplified to just using the first occurrence of a certificate. Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>(); for (CertificatesEntryRef ref : mCertificatesEntryRefs) { Set<TrustAnchor> anchors = ref.getTrustAnchors(); for (TrustAnchor anchor : anchors) { if (anchor.overridesPins) { anchorMap.put(anchor.certificate, anchor); } else if (!anchorMap.containsKey(anchor.certificate)) { anchorMap.put(anchor.certificate, anchor); X509Certificate cert = anchor.certificate; if (!anchorMap.containsKey(cert)) { anchorMap.put(cert, anchor); } } } Loading Loading @@ -108,6 +123,17 @@ public final class NetworkSecurityConfig { } } /** @hide */ public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) { for (CertificatesEntryRef ref : mCertificatesEntryRefs) { TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert); if (anchor != null) { return anchor; } } return null; } /** * Return a {@link Builder} for the default {@code NetworkSecurityConfig}. * Loading