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

Commit 83a13307 authored by Dmitry Dementyev's avatar Dmitry Dementyev Committed by android-build-merger
Browse files

Merge "Add support for testing mode root certificate." into pi-dev

am: 2d3e4b74

Change-Id: I24ac397b82ec0ce0913f4aa9c8bad9481ed5a970
parents 889325dc 2d3e4b74
Loading
Loading
Loading
Loading
+67 −3
Original line number Diff line number Diff line
@@ -37,6 +37,40 @@ public final class TrustedRootCertificates {

    public static final String GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS =
            "GoogleCloudKeyVaultServiceV1";
    /**
     * Certificate used for client-side end-to-end encryption tests.
     * When recovery controller is initialized with the certificate, recovery snapshots will only
     * contain application keys started with {@link INSECURE_KEY_ALIAS}.
     * Recovery snapshot will only be created if device is unlocked with password started with
     * {@link #INSECURE_PASSWORD_PREFIX}.
     *
     * @hide
     */
    public static final String TEST_ONLY_INSECURE_CERTIFICATE_ALIAS =
            "TEST_ONLY_INSECURE_CERTIFICATE_ALIAS";

    /**
     * TODO: Add insecure certificate to TestApi.
     * @hide
     */
    public static @NonNull X509Certificate getTestOnlyInsecureCertificate() {
        return parseBase64Certificate(TEST_ONLY_INSECURE_CERTIFICATE_BASE64);
    }
    /**
     * Keys, which alias starts with the prefix are not protected if
     * recovery agent uses {@link #TEST_ONLY_INSECURE_CERTIFICATE_ALIAS} root certificate.
     * @hide
     */
    public static final String INSECURE_KEY_ALIAS_PREFIX =
            "INSECURE_KEY_ALIAS_KEY_MATERIAL_IS_NOT_PROTECTED_";
    /**
     * Prefix for insecure passwords with length 14.
     * Passwords started with the prefix are not protected if recovery agent uses
     * {@link #TEST_ONLY_INSECURE_CERTIFICATE_ALIAS} root certificate.
     * @hide
     */
    public static final String INSECURE_PASSWORD_PREFIX =
            "INSECURE_PSWD_";

    private static final String GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64 = ""
            + "MIIFJjCCAw6gAwIBAgIJAIobXsJlzhNdMA0GCSqGSIb3DQEBDQUAMCAxHjAcBgNV"
@@ -68,13 +102,43 @@ public final class TrustedRootCertificates {
            + "/oM58v0orUWINtIc2hBlka36PhATYQiLf+AiWKnwhCaaHExoYKfQlMtXBodNvOK8"
            + "xqx69x05q/qbHKEcTHrsss630vxrp1niXvA=";

    private static final String TEST_ONLY_INSECURE_CERTIFICATE_BASE64 = ""
            + "MIIFMDCCAxigAwIBAgIJAIZ9/G8KQie9MA0GCSqGSIb3DQEBDQUAMCUxIzAhBgNV"
            + "BAMMGlRlc3QgT25seSBVbnNlY3VyZSBSb290IENBMB4XDTE4MDMyODAwMzIyM1oX"
            + "DTM4MDMyMzAwMzIyM1owJTEjMCEGA1UEAwwaVGVzdCBPbmx5IFVuc2VjdXJlIFJv"
            + "b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGxFNzAEyzSPmw"
            + "E5gfuBXdXq++bl9Ep62V7Xn1UiejvmS+pRHT39pf/M7sl4Zr9ezanJTrFvf9+B85"
            + "VGehdsD32TgfEjThcqaoQCI6pKkHYsUo7FZ5n+G3eE8oabWRZJMVo3QDjnnFYp7z"
            + "20vnpjDofI2oQyxHcb/1yep+ca1+4lIvbUp/ybhNFqhRXAMcDXo7pyH38eUQ1JdK"
            + "Q/QlBbShpFEqx1Y6KilKfTDf7Wenqr67LkaEim//yLZjlHzn/BpuRTrpo+XmJZx1"
            + "P9CX9LGOXTtmsaCcYgD4yijOvV8aEsIJaf1kCIO558oH0oQc+0JG5aXeLN7BDlyZ"
            + "vH0RdSx5nQLS9kj2I6nthOw/q00/L+S6A0m5jyNZOAl1SY78p+wO0d9eHbqQzJwf"
            + "EsSq3qGAqlgQyyjp6oxHBqT9hZtN4rxw+iq0K1S4kmTLNF1FvmIB1BE+lNvvoGdY"
            + "5G0b6Pe4R5JFn9LV3C3PEmSYnae7iG0IQlKmRADIuvfJ7apWAVanJPJAAWh2Akfp"
            + "8Uxr02cHoY6o7vsEhJJOeMkipaBHThESm/XeFVubQzNfZ9gjQnB9ZX2v+lyj+WYZ"
            + "SAz3RuXx6TlLrmWccMpQDR1ibcgyyjLUtX3kwZl2OxmJXitjuD7xlxvAXYob15N+"
            + "K4xKHgxUDrbt2zU/tY0vgepAUg/xbwIDAQABo2MwYTAdBgNVHQ4EFgQUwyeNpYgs"
            + "XXYvh9z0/lFrja7sV+swHwYDVR0jBBgwFoAUwyeNpYgsXXYvh9z0/lFrja7sV+sw"
            + "DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQENBQAD"
            + "ggIBAGuOsvMN5SD3RIQnMJtBpcHNrxun+QFjPZFlYCLfIPrUkHpn5O1iIIq8tVLd"
            + "2V+12VKnToUEANsYBD3MP8XjP+6GZ7ZQ2rwLGvUABKSX4YXvmjEEXZUZp0y3tIV4"
            + "kUDlbACzguPneZDp5Qo7YWH4orgqzHkn0sD/ikO5XrAqmzc245ewJlrf+V11mjcu"
            + "ELfDrEejpPhi7Hk/ZNR0ftP737Hs/dNoCLCIaVNgYzBZhgo4kd220TeJu2ttW0XZ"
            + "ldyShtpcOmyWKBgVseixR6L/3sspPHyAPXkSuRo0Eh1xvzDKCg9ttb0qoacTlXMF"
            + "GkBpNzmVq67NWFGGa9UElift1mv6RfktPCAGZ+Ai8xUiKAUB0Eookpt/8gX9Senq"
            + "yP/jMxkxXmHWxUu8+KnLvj6WLrfftuuD7u3cfc7j5kkrheDz3O4h4477GnqL5wdo"
            + "9DuEsNc4FxJVz8Iy8RS6cJuW4pihYpM1Tyn7uopLnImpYzEY+R5aQqqr+q/A1diq"
            + "ogbEKPH6oUiqJUwq3nD70gPBUKJmIzS4vLwLouqUHEm1k/MgHV/BkEU0uVHszPFa"
            + "XUMMCHb0iT9P8LuZ7Ajer3SR/0TRVApCrk/6OV68e+6k/OFpM5kcZnNMD5ANyBri"
            + "Tsz3NrDwSw4i4+Dsfh6A9dB/cEghw4skLaBxnQLQIgVeqCzK";

    /**
     * The X509 certificate of the trusted root CA cert for the recoverable key store service.
     *
     * TODO: Change it to the production certificate root CA before the final launch.
     */
    private static final X509Certificate GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_CERTIFICATE =
            parseGoogleCloudKeyVaultServiceV1Certificate();
            parseBase64Certificate(GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64);

    private static final int NUMBER_OF_ROOT_CERTIFICATES = 1;

@@ -107,9 +171,9 @@ public final class TrustedRootCertificates {
        return certificates;
    }

    private static X509Certificate parseGoogleCloudKeyVaultServiceV1Certificate() {
    private static X509Certificate parseBase64Certificate(String base64Certificate) {
        try {
            return decodeBase64Cert(GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_BASE64);
            return decodeBase64Cert(base64Certificate);
        } catch (CertificateException e) {
            // Should not happen
            throw new RuntimeException(e);
+24 −20
Original line number Diff line number Diff line
@@ -79,6 +79,7 @@ public class KeySyncTask implements Runnable {
    private final PlatformKeyManager mPlatformKeyManager;
    private final RecoverySnapshotStorage mRecoverySnapshotStorage;
    private final RecoverySnapshotListenersStorage mSnapshotListenersStorage;
    private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;

    public static KeySyncTask newInstance(
            Context context,
@@ -98,7 +99,8 @@ public class KeySyncTask implements Runnable {
                credentialType,
                credential,
                credentialUpdated,
                PlatformKeyManager.getInstance(context, recoverableKeyStoreDb));
                PlatformKeyManager.getInstance(context, recoverableKeyStoreDb),
                new TestOnlyInsecureCertificateHelper());
    }

    /**
@@ -110,6 +112,7 @@ public class KeySyncTask implements Runnable {
     * @param credential The credential, encoded as a {@link String}.
     * @param credentialUpdated signals weather credentials were updated.
     * @param platformKeyManager platform key manager
     * @param TestOnlyInsecureCertificateHelper utility class used for end-to-end tests
     */
    @VisibleForTesting
    KeySyncTask(
@@ -120,7 +123,8 @@ public class KeySyncTask implements Runnable {
            int credentialType,
            String credential,
            boolean credentialUpdated,
            PlatformKeyManager platformKeyManager) {
            PlatformKeyManager platformKeyManager,
            TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) {
        mSnapshotListenersStorage = recoverySnapshotListenersStorage;
        mRecoverableKeyStoreDb = recoverableKeyStoreDb;
        mUserId = userId;
@@ -129,6 +133,7 @@ public class KeySyncTask implements Runnable {
        mCredentialUpdated = credentialUpdated;
        mPlatformKeyManager = platformKeyManager;
        mRecoverySnapshotStorage = snapshotStorage;
        mTestOnlyInsecureCertificateHelper = TestOnlyInsecureCertificateHelper;
    }

    @Override
@@ -189,8 +194,9 @@ public class KeySyncTask implements Runnable {
        PublicKey publicKey;
        String rootCertAlias =
                mRecoverableKeyStoreDb.getActiveRootOfTrust(mUserId, recoveryAgentUid);
        rootCertAlias = mTestOnlyInsecureCertificateHelper
                .getDefaultCertificateAliasIfEmpty(rootCertAlias);

        rootCertAlias = replaceEmptyValueWithSecureDefault(rootCertAlias);
        CertPath certPath = mRecoverableKeyStoreDb.getRecoveryServiceCertPath(mUserId,
                recoveryAgentUid, rootCertAlias);
        if (certPath != null) {
@@ -212,12 +218,18 @@ public class KeySyncTask implements Runnable {
            return;
        }

        // The only place in this class which uses credential value
        if (!TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS.equals(
                rootCertAlias)) {
            // TODO: allow only whitelisted LSKF usage
            Log.w(TAG, "Untrusted root certificate is used by recovery agent "
        if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(rootCertAlias)) {
            Log.w(TAG, "Insecure root certificate is used by recovery agent "
                    + recoveryAgentUid);
            if (mTestOnlyInsecureCertificateHelper.doesCredentailSupportInsecureMode(
                    mCredentialType, mCredential)) {
                Log.w(TAG, "Whitelisted credential is used to generate snapshot by "
                        + "recovery agent "+ recoveryAgentUid);
            } else {
                Log.w(TAG, "Non whitelisted credential is used to generate recovery snapshot by "
                        + recoveryAgentUid + " - ignore attempt.");
                return; // User secret will not be used.
            }
        }

        byte[] salt = generateSalt();
@@ -239,8 +251,10 @@ public class KeySyncTask implements Runnable {
            return;
        }

        // TODO: filter raw keys based on the root of trust.
        // It is the only place in the class where raw key material is used.
        // Only include insecure key material for test
        if (mTestOnlyInsecureCertificateHelper.isTestOnlyCertificate(rootCertAlias)) {
            rawKeys = mTestOnlyInsecureCertificateHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
        }
        SecretKey recoveryKey;
        try {
            recoveryKey = generateRecoveryKey();
@@ -467,14 +481,4 @@ public class KeySyncTask implements Runnable {
        }
        return keyEntries;
    }

    private @NonNull String replaceEmptyValueWithSecureDefault(
            @Nullable String rootCertificateAlias) {
        if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
            Log.e(TAG, "rootCertificateAlias is null or empty");
            // Use the default Google Key Vault Service CA certificate if the alias is not provided
            rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
        }
        return rootCertificateAlias;
    }
}
+18 −30
Original line number Diff line number Diff line
@@ -38,7 +38,6 @@ import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.RecoveryCertPath;
import android.security.keystore.recovery.RecoveryController;
import android.security.keystore.recovery.TrustedRootCertificates;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.KeyStore;
import android.util.ArrayMap;
@@ -100,6 +99,7 @@ public class RecoverableKeyStoreManager {
    private final RecoverySnapshotStorage mSnapshotStorage;
    private final PlatformKeyManager mPlatformKeyManager;
    private final ApplicationKeyStorage mApplicationKeyStorage;
    private final TestOnlyInsecureCertificateHelper mTestCertHelper;

    /**
     * Returns a new or existing instance.
@@ -130,7 +130,8 @@ public class RecoverableKeyStoreManager {
                    RecoverySnapshotStorage.newInstance(),
                    new RecoverySnapshotListenersStorage(),
                    platformKeyManager,
                    applicationKeyStorage);
                    applicationKeyStorage,
                    new TestOnlyInsecureCertificateHelper());
        }
        return mInstance;
    }
@@ -144,7 +145,8 @@ public class RecoverableKeyStoreManager {
            RecoverySnapshotStorage snapshotStorage,
            RecoverySnapshotListenersStorage listenersStorage,
            PlatformKeyManager platformKeyManager,
            ApplicationKeyStorage applicationKeyStorage) {
            ApplicationKeyStorage applicationKeyStorage,
            TestOnlyInsecureCertificateHelper TestOnlyInsecureCertificateHelper) {
        mContext = context;
        mDatabase = recoverableKeyStoreDb;
        mRecoverySessionStorage = recoverySessionStorage;
@@ -153,6 +155,7 @@ public class RecoverableKeyStoreManager {
        mSnapshotStorage = snapshotStorage;
        mPlatformKeyManager = platformKeyManager;
        mApplicationKeyStorage = applicationKeyStorage;
        mTestCertHelper = TestOnlyInsecureCertificateHelper;

        try {
            mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
@@ -171,7 +174,8 @@ public class RecoverableKeyStoreManager {
        checkRecoverKeyStorePermission();
        int userId = UserHandle.getCallingUserId();
        int uid = Binder.getCallingUid();
        rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
        rootCertificateAlias
                = mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);

        // Always set active alias to the argument of the last call to initRecoveryService method,
        // even if cert file is incorrect.
@@ -216,7 +220,8 @@ public class RecoverableKeyStoreManager {

        // Randomly choose and validate an endpoint certificate from the list
        CertPath certPath;
        X509Certificate rootCert = getRootCertificate(rootCertificateAlias);
        X509Certificate rootCert =
                mTestCertHelper.getRootCertificate(rootCertificateAlias);
        try {
            Log.d(TAG, "Getting and validating a random endpoint certificate");
            certPath = certXml.getRandomEndpointCert(rootCert);
@@ -265,7 +270,8 @@ public class RecoverableKeyStoreManager {
            @NonNull byte[] recoveryServiceSigFile)
            throws RemoteException {
        checkRecoverKeyStorePermission();
        rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
        rootCertificateAlias =
                mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
        Preconditions.checkNotNull(recoveryServiceCertFile, "recoveryServiceCertFile is null");
        Preconditions.checkNotNull(recoveryServiceSigFile, "recoveryServiceSigFile is null");

@@ -279,7 +285,8 @@ public class RecoverableKeyStoreManager {
                    ERROR_BAD_CERTIFICATE_FORMAT, "Failed to parse the sig file.");
        }

        X509Certificate rootCert = getRootCertificate(rootCertificateAlias);
        X509Certificate rootCert =
                mTestCertHelper.getRootCertificate(rootCertificateAlias);
        try {
            sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile);
        } catch (CertValidationException e) {
@@ -519,7 +526,8 @@ public class RecoverableKeyStoreManager {
            @NonNull List<KeyChainProtectionParams> secrets)
            throws RemoteException {
        checkRecoverKeyStorePermission();
        rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
        rootCertificateAlias =
                mTestCertHelper.getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
        Preconditions.checkNotNull(sessionId, "invalid session");
        Preconditions.checkNotNull(verifierCertPath, "verifierCertPath is null");
        Preconditions.checkNotNull(vaultParams, "vaultParams is null");
@@ -534,7 +542,8 @@ public class RecoverableKeyStoreManager {
        }

        try {
            CertUtils.validateCertPath(getRootCertificate(rootCertificateAlias), certPath);
            CertUtils.validateCertPath(
                    mTestCertHelper.getRootCertificate(rootCertificateAlias), certPath);
        } catch (CertValidationException e) {
            Log.e(TAG, "Failed to validate the given cert path", e);
            throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage());
@@ -960,27 +969,6 @@ public class RecoverableKeyStoreManager {
        }
    }

    private X509Certificate getRootCertificate(String rootCertificateAlias) throws RemoteException {
        rootCertificateAlias = replaceEmptyValueWithSecureDefault(rootCertificateAlias);
        X509Certificate rootCertificate =
                TrustedRootCertificates.getRootCertificate(rootCertificateAlias);
        if (rootCertificate == null) {
            throw new ServiceSpecificException(
                    ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid");
        }
        return rootCertificate;
    }

    private @NonNull String replaceEmptyValueWithSecureDefault(
            @Nullable String rootCertificateAlias) {
        if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
            Log.e(TAG, "rootCertificateAlias is null or empty");
            // Use the default Google Key Vault Service CA certificate if the alias is not provided
            rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
        }
        return rootCertificateAlias;
    }

    private void checkRecoverKeyStorePermission() {
        mContext.enforceCallingOrSelfPermission(
                Manifest.permission.RECOVER_KEYSTORE,
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 com.android.server.locksettings.recoverablekeystore;

import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;

import com.android.internal.widget.LockPatternUtils;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.security.keystore.recovery.TrustedRootCertificates;
import android.util.Log;

import java.util.HashMap;
import java.security.cert.X509Certificate;
import java.util.Map;
import javax.crypto.SecretKey;

/**
 * The class provides helper methods to support end-to-end test with insecure certificate.
 */
public class TestOnlyInsecureCertificateHelper {
    private static final String TAG = "TestCertHelper";

    /**
     * Constructor for the helper class.
     */
    public TestOnlyInsecureCertificateHelper() {
    }

    /**
     * Returns a root certificate installed in the system for given alias.
     * Returns default secure certificate if alias is empty or null.
     * Can return insecure certificate for its alias.
     */
    public @NonNull X509Certificate
            getRootCertificate(String rootCertificateAlias) throws RemoteException {
        rootCertificateAlias = getDefaultCertificateAliasIfEmpty(rootCertificateAlias);
        if (isTestOnlyCertificate(rootCertificateAlias)) {
            return TrustedRootCertificates.getTestOnlyInsecureCertificate();
        }

        X509Certificate rootCertificate =
                TrustedRootCertificates.getRootCertificate(rootCertificateAlias);
        if (rootCertificate == null) {
            throw new ServiceSpecificException(
                    ERROR_INVALID_CERTIFICATE, "The provided root certificate alias is invalid");
        }
        return rootCertificate;
    }

    public @NonNull String getDefaultCertificateAliasIfEmpty(
            @Nullable String rootCertificateAlias) {
        if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) {
            Log.e(TAG, "rootCertificateAlias is null or empty - use secure default value");
            // Use the default Google Key Vault Service CA certificate if the alias is not provided
            rootCertificateAlias = TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS;
        }
        return rootCertificateAlias;
    }

    public boolean isTestOnlyCertificate(String rootCertificateAlias) {
        return TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS
                .equals(rootCertificateAlias);
    }

    public boolean doesCredentailSupportInsecureMode(int credentialType, String credential) {
        return (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD)
            && (credential != null)
            && credential.startsWith(TrustedRootCertificates.INSECURE_PASSWORD_PREFIX);
    }

    public Map<String, SecretKey> keepOnlyWhitelistedInsecureKeys(Map<String, SecretKey> rawKeys) {
        if (rawKeys == null) {
            return null;
        }
        Map<String, SecretKey> filteredKeys = new HashMap<>();
        for (Map.Entry<String, SecretKey> entry : rawKeys.entrySet()) {
            String alias = entry.getKey();
            if (alias != null
                    && alias.startsWith(TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX)) {
                filteredKeys.put(entry.getKey(), entry.getValue());
                Log.d(TAG, "adding key with insecure alias " + alias + " to the recovery snapshot");
            }
        }
        return filteredKeys;
    }
}
+112 −6

File changed.

Preview size limit exceeded, changes collapsed.

Loading