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

Commit 7ce4ea52 authored by Bo Zhu's avatar Bo Zhu
Browse files

Check the given CertPath against the root of trust during recovery

Bug: 73826459
Test: adb shell am instrument -w -e package \
com.android.server.locksettings.recoverablekeystore \
com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner

Change-Id: I28893d815c57260c4d0f0d55d252bff5d34d4832
parent 03b91d77
Loading
Loading
Loading
Loading
+8 −5
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
@@ -446,12 +447,14 @@ public class RecoverableKeyStoreManager {
                    "Failed decode the certificate path");
        }

        // TODO: Validate the cert path according to the root of trust

        if (certPath.getCertificates().isEmpty()) {
            throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT,
                    "The given CertPath is empty");
        try {
            CertUtils.validateCertPath(TrustedRootCert.TRUSTED_ROOT_CERT, certPath);
        } catch (CertValidationException e) {
            Log.e(TAG, "Failed to validate the given cert path", e);
            // TODO: Change this to ERROR_INVALID_CERTIFICATE once ag/3666620 is submitted
            throw new ServiceSpecificException(ERROR_BAD_CERTIFICATE_FORMAT, e.getMessage());
        }

        byte[] verifierPublicKey = certPath.getCertificates().get(0).getPublicKey().getEncoded();
        if (verifierPublicKey == null) {
            Log.e(TAG, "Failed to encode verifierPublicKey");
+37 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import java.security.cert.CertPathBuilderException;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
@@ -292,6 +293,42 @@ public final class CertUtils {
        return certPath;
    }

    /**
     * Validates a given {@code CertPath} against the trusted root certificate.
     *
     * @param trustedRoot the trusted root certificate
     * @param certPath the certificate path to be validated
     * @throws CertValidationException if the given certificate path is invalid, e.g., is expired,
     *                                 or does not have a valid signature
     */
    public static void validateCertPath(X509Certificate trustedRoot, CertPath certPath)
            throws CertValidationException {
        validateCertPath(/*validationDate=*/ null, trustedRoot, certPath);
    }

    /**
     * Validates a given {@code CertPath} against a given {@code validationDate}. If the given
     * validation date is null, the current date will be used.
     */
    @VisibleForTesting
    static void validateCertPath(@Nullable Date validationDate, X509Certificate trustedRoot,
            CertPath certPath) throws CertValidationException {
        if (certPath.getCertificates().isEmpty()) {
            throw new CertValidationException("The given certificate path is empty");
        }
        if (!(certPath.getCertificates().get(0) instanceof X509Certificate)) {
            throw new CertValidationException(
                    "The given certificate path does not contain X509 certificates");
        }

        List<X509Certificate> certificates = (List<X509Certificate>) certPath.getCertificates();
        X509Certificate leafCert = certificates.get(0);
        List<X509Certificate> intermediateCerts =
                certificates.subList(/*fromIndex=*/ 1, certificates.size());

        validateCert(validationDate, trustedRoot, intermediateCerts, leafCert);
    }

    @VisibleForTesting
    static CertPath buildCertPath(PKIXParameters pkixParams) throws CertValidationException {
        CertPathBuilder certPathBuilder;
+26 −2
Original line number Diff line number Diff line
@@ -277,7 +277,7 @@ public class RecoverableKeyStoreManagerTest {
    }

    @Test
    public void initRecoveryService_succeeds() throws Exception {
    public void initRecoveryService_succeedsWithCertFile() throws Exception {
        int uid = Binder.getCallingUid();
        int userId = UserHandle.getCallingUserId();
        long certSerial = 1000L;
@@ -566,7 +566,31 @@ public class RecoverableKeyStoreManagerTest {
                                    TEST_SECRET)));
            fail("should have thrown");
        } catch (ServiceSpecificException e) {
            assertThat(e.getMessage()).contains("CertPath is empty");
            assertThat(e.getMessage()).contains("empty");
        }
    }

    @Test
    public void startRecoverySessionWithCertPath_throwsIfInvalidCertPath() throws Exception {
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        CertPath shortCertPath = certFactory.generateCertPath(
                TestData.CERT_PATH_1.getCertificates()
                        .subList(0, TestData.CERT_PATH_1.getCertificates().size() - 1));
        try {
            mRecoverableKeyStoreManager.startRecoverySessionWithCertPath(
                    TEST_SESSION_ID,
                    RecoveryCertPath.createRecoveryCertPath(shortCertPath),
                    TEST_VAULT_PARAMS,
                    TEST_VAULT_CHALLENGE,
                    ImmutableList.of(
                            new KeyChainProtectionParams(
                                    TYPE_LOCKSCREEN,
                                    UI_FORMAT_PASSWORD,
                                    KeyDerivationParams.createSha256Params(TEST_SALT),
                                    TEST_SECRET)));
            fail("should have thrown");
        } catch (ServiceSpecificException e) {
            // expected
        }
    }

+38 −0
Original line number Diff line number Diff line
@@ -30,8 +30,10 @@ import java.io.InputStream;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.cert.CertPath;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
@@ -316,6 +318,42 @@ public final class CertUtilsTest {
                                        leafCert)));
    }

    @Test
    public void validateCertPath_succeeds() throws Exception {
        X509Certificate rootCert = TestData.ROOT_CA_TRUSTED;
        List<X509Certificate> intermediateCerts =
                Arrays.asList(TestData.INTERMEDIATE_CA_1, TestData.INTERMEDIATE_CA_2);
        X509Certificate leafCert = TestData.LEAF_CERT_2;
        CertPath certPath =
                CertUtils.buildCertPath(
                        CertUtils.buildPkixParams(
                                TestData.DATE_ALL_CERTS_VALID, rootCert, intermediateCerts,
                                leafCert));
        CertUtils.validateCertPath(
                TestData.DATE_ALL_CERTS_VALID, TestData.ROOT_CA_TRUSTED, certPath);
    }

    @Test
    public void validateCertPath_throwsIfEmptyCertPath() throws Exception {
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        CertPath emptyCertPath = certFactory.generateCertPath(new ArrayList<X509Certificate>());
        CertValidationException expected =
                expectThrows(
                        CertValidationException.class,
                        () -> CertUtils.validateCertPath(TestData.DATE_ALL_CERTS_VALID,
                                TestData.ROOT_CA_TRUSTED, emptyCertPath));
        assertThat(expected.getMessage()).contains("empty");
    }

    @Test
    public void validateCertPath_throwsIfNotValidated() throws Exception {
        assertThrows(
                CertValidationException.class,
                () -> CertUtils.validateCertPath(TestData.DATE_ALL_CERTS_VALID,
                        TestData.ROOT_CA_DIFFERENT_COMMON_NAME,
                        com.android.server.locksettings.recoverablekeystore.TestData.CERT_PATH_1));
    }

    @Test
    public void validateCert_succeeds() throws Exception {
        X509Certificate rootCert = TestData.ROOT_CA_TRUSTED;