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

Commit 3e58039f authored by Bo Zhu's avatar Bo Zhu Committed by Android (Google) Code Review
Browse files

Merge "Check the given CertPath against the root of trust during recovery"

parents 9371be3b 7ce4ea52
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;