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

Commit bdfdf53d authored by Dmitry Dementyev's avatar Dmitry Dementyev
Browse files

Implement RecoverableKeyStore API to set/get recovery secret types.

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

Change-Id: If29f22f24438a9d050fabebf970b9ae56b0df805
parent 6015c072
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -229,7 +229,8 @@ public class RecoverableKeyStoreManager {
            @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId)
            throws RemoteException {
        checkRecoverKeyStorePermission();
        throw new UnsupportedOperationException();
        mDatabase.setRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid(),
            secretTypes);
    }

    /**
@@ -240,7 +241,8 @@ public class RecoverableKeyStoreManager {
     */
    public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException {
        checkRecoverKeyStorePermission();
        throw new UnsupportedOperationException();
        return mDatabase.getRecoverySecretTypes(UserHandle.getCallingUserId(),
            Binder.getCallingUid());
    }

    /**
+96 −2
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
import android.text.TextUtils;
import android.util.Log;

import com.android.server.locksettings.recoverablekeystore.WrappedKey;
@@ -30,18 +31,18 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKe
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;



import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringJoiner;

/**
 * Database of recoverable key information.
@@ -425,6 +426,99 @@ public class RecoverableKeyStoreDb {
        }
    }

    /**
     * Updates the list of user secret types used for end-to-end encryption.
     * If no secret types are set, recovery snapshot will not be created.
     * See {@code KeyStoreRecoveryMetadata}
     *
     * @param userId The uid of the profile the application is running under.
     * @param uid The uid of the application.
     * @param secretTypes list of secret types
     * @return The primary key of the updated row, or -1 if failed.
     *
     * @hide
     */
    public long setRecoverySecretTypes(int userId, int uid, int[] secretTypes) {
        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        StringJoiner joiner = new StringJoiner(",");
        Arrays.stream(secretTypes).forEach(i -> joiner.add(Integer.toString(i)));
        String typesAsCsv = joiner.toString();
        values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, typesAsCsv);
        String selection =
                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
                + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
        ensureRecoveryServiceMetadataEntryExists(userId, uid);
        return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, selection,
            new String[] {String.valueOf(userId), String.valueOf(uid)});
    }

    /**
     * Returns the list of secret types used for end-to-end encryption.
     *
     * @param userId The uid of the profile the application is running under.
     * @param uid The uid of the application who initialized the local recovery components.
     * @return Secret types or empty array, if types were not set.
     *
     * @hide
     */
    public @NonNull int[] getRecoverySecretTypes(int userId, int uid) {
        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();

        String[] projection = {
                RecoveryServiceMetadataEntry._ID,
                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
                RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
                RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES};
        String selection =
                RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
                        + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
        String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};

        try (
                Cursor cursor = db.query(
                        RecoveryServiceMetadataEntry.TABLE_NAME,
                        projection,
                        selection,
                        selectionArguments,
                        /*groupBy=*/ null,
                        /*having=*/ null,
                        /*orderBy=*/ null)
        ) {
            int count = cursor.getCount();
            if (count == 0) {
                return new int[]{};
            }
            if (count > 1) {
                Log.wtf(TAG,
                        String.format(Locale.US,
                                "%d deviceId entries found for userId=%d uid=%d. "
                                        + "Should only ever be 0 or 1.", count, userId, uid));
                return new int[]{};
            }
            cursor.moveToFirst();
            int idx = cursor.getColumnIndexOrThrow(
                    RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES);
            if (cursor.isNull(idx)) {
                return new int[]{};
            }
            String csv = cursor.getString(idx);
            if (TextUtils.isEmpty(csv)) {
                return new int[]{};
            }
            String[] types = csv.split(",");
            int[] result =  new int[types.length];
            for (int i = 0; i < types.length; i++) {
                try {
                    result[i] = Integer.parseInt(types[i]);
                } catch (NumberFormatException e) {
                    Log.wtf(TAG, "String format error " + e);
                }
            }
            return result;
        }
    }

    /**
     * Updates the server parameters given by the application initializing the local recovery
     * components.
+5 −0
Original line number Diff line number Diff line
@@ -108,6 +108,11 @@ class RecoverableKeyStoreDbContract {
         */
        static final String COLUMN_NAME_PUBLIC_KEY = "public_key";

        /**
         * Secret types used for end-to-end encryption.
         */
        static final String COLUMN_NAME_SECRET_TYPES = "secret_types";

        /**
         * The server parameters of the recovery service.
         */
+1 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS + " INTEGER,"
                    + "UNIQUE("
                    + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID  + ","
+20 −0
Original line number Diff line number Diff line
@@ -361,6 +361,26 @@ public class RecoverableKeyStoreManagerTest {
        verify(mMockListenersStorage).setSnapshotListener(eq(uid), any(PendingIntent.class));
    }

    @Test
    public void setRecoverySecretTypes() throws Exception {
        int userId = UserHandle.getCallingUserId();
        int[] types1 = new int[]{11, 2000};
        int[] types2 = new int[]{1, 2, 3};
        int[] types3 = new int[]{};

        mRecoverableKeyStoreManager.setRecoverySecretTypes(types1, userId);
        assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo(
                types1);

        mRecoverableKeyStoreManager.setRecoverySecretTypes(types2, userId);
        assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo(
                types2);

        mRecoverableKeyStoreManager.setRecoverySecretTypes(types3, userId);
        assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes(userId)).isEqualTo(
                types3);
    }

    @Test
    public void setRecoveryStatus_forOneAlias() throws Exception {
        int userId = UserHandle.getCallingUserId();
Loading