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

Commit 6fea6611 authored by Chad Brubaker's avatar Chad Brubaker Committed by Gerrit Code Review
Browse files

Merge "Expose findTrustAnchorBySubjectAndPublicKey"

parents 73c06b1d d3af9620
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -22,4 +22,5 @@ import java.security.cert.X509Certificate;
/** @hide */
public interface CertificateSource {
    Set<X509Certificate> getCertificates();
    X509Certificate findBySubjectAndPublicKey(X509Certificate cert);
}
+13 −0
Original line number Diff line number Diff line
@@ -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>();
@@ -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);
    }
}
+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);
        }
    }
}
+23 −2
Original line number Diff line number Diff line
@@ -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}.
@@ -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) {
@@ -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();
    }
}
+30 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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() {
@@ -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);
                    }
                }
            }
@@ -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