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

Commit 5b81fa66 authored by Bo Zhu's avatar Bo Zhu
Browse files

Implement the DB table to store the recovery service's public key

Change-Id: Ic80469dd0a199aa45d353ee07d712310047fd428
Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
parent 550e8ec8
Loading
Loading
Loading
Loading
+17 −3
Original line number Diff line number Diff line
@@ -38,10 +38,12 @@ import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessi
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -112,12 +114,24 @@ public class RecoverableKeyStoreManager {
        }
    }

    public int initRecoveryService(
    public void initRecoveryService(
            @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, int userId)
            throws RemoteException {
        checkRecoverKeyStorePermission();
        // TODO open /system/etc/security/... cert file
        throw new UnsupportedOperationException();
        // TODO: open /system/etc/security/... cert file, and check the signature on the public keys
        PublicKey publicKey;
        try {
            KeyFactory kf = KeyFactory.getInstance("EC");
            // TODO: Randomly choose a key from the list -- right now we just use the whole input
            X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(signedPublicKeyList);
            publicKey = kf.generatePublic(pkSpec);
        } catch (NoSuchAlgorithmException e) {
            // Should never happen
            throw new RuntimeException(e);
        } catch (InvalidKeySpecException e) {
            throw new RemoteException("Invalid public key for the recovery service");
        }
        mDatabase.setRecoveryServicePublicKey(userId, Binder.getCallingUid(), publicKey);
    }

    /**
+92 −0
Original line number Diff line number Diff line
@@ -27,9 +27,19 @@ import android.util.Log;

import com.android.server.locksettings.recoverablekeystore.WrappedKey;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServicePublicKeyEntry;
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.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

@@ -295,6 +305,88 @@ public class RecoverableKeyStoreDb {
        }
    }

    /**
     * Inserts or updates the public key of the recovery service into the database.
     *
     * @param userId The uid of the profile the application is running under.
     * @param uid The uid of the application to whom the key belongs.
     * @param publicKey The public key of the recovery service.
     * @return The primary key of the inserted row, or -1 if failed.
     *
     * @hide
     */
    public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) {
        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID, userId);
        values.put(RecoveryServicePublicKeyEntry.COLUMN_NAME_UID, uid);
        values.put(RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded());
        return db.replace(RecoveryServicePublicKeyEntry.TABLE_NAME, /*nullColumnHack=*/ null,
                values);
    }

    /**
     * Returns the public key of the recovery service.
     *
     * @param userId The uid of the profile the application is running under.
     * @param uid The uid of the application who initializes the local recovery components.
     *
     * @hide
     */
    public PublicKey getRecoveryServicePublicKey(int userId, int uid) {
        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();

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

        try (
                Cursor cursor = db.query(
                        RecoveryServicePublicKeyEntry.TABLE_NAME,
                        projection,
                        selection,
                        selectionArguments,
                        /*groupBy=*/ null,
                        /*having=*/ null,
                        /*orderBy=*/ null)
        ) {
            int count = cursor.getCount();
            if (count == 0) {
                return null;
            }
            if (count > 1) {
                Log.wtf(TAG,
                        String.format(Locale.US,
                                "%d PublicKey entries found for userId=%d uid=%d. "
                                        + "Should only ever be 0 or 1.", count, userId, uid));
                return null;
            }
            cursor.moveToFirst();
            byte[] keyBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(
                    RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY));
            X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(keyBytes);
            try {
                return KeyFactory.getInstance("EC").generatePublic(pkSpec);
            } catch (NoSuchAlgorithmException e) {
                // Should never happen
                throw new RuntimeException(e);
            } catch (InvalidKeySpecException e) {
                Log.wtf(TAG,
                        String.format(Locale.US,
                                "Recovery service public key entry cannot be decoded for "
                                        + "userId=%d uid=%d.",
                                userId, uid));
                return null;
            }
        }
    }

    /**
     * Closes all open connections to the database.
     */
+22 −0
Original line number Diff line number Diff line
@@ -86,4 +86,26 @@ class RecoverableKeyStoreDbContract {
         */
        static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id";
    }

    /**
     * Table holding public keys of the recovery service.
     */
    static class RecoveryServicePublicKeyEntry implements BaseColumns {
        static final String TABLE_NAME = "recovery_service_public_keys";

        /**
         * The user id of the profile the application is running under.
         */
        static final String COLUMN_NAME_USER_ID = "user_id";

        /**
         * The uid of the application that initializes the local recovery components.
         */
        static final String COLUMN_NAME_UID = "uid";

        /**
         * The public key of the recovery service.
         */
        static final String COLUMN_NAME_PUBLIC_KEY = "public_key";
    }
}
+16 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServicePublicKeyEntry;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;

/**
@@ -34,12 +35,25 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
                    + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
                    + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";

    private static final String SQL_CREATE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY =
            "CREATE TABLE " + RecoveryServicePublicKeyEntry.TABLE_NAME + " ("
                    + RecoveryServicePublicKeyEntry._ID + " INTEGER PRIMARY KEY,"
                    + RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID + " INTEGER,"
                    + RecoveryServicePublicKeyEntry.COLUMN_NAME_UID + " INTEGER,"
                    + RecoveryServicePublicKeyEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
                    + "UNIQUE("
                    + RecoveryServicePublicKeyEntry.COLUMN_NAME_USER_ID  + ","
                    + RecoveryServicePublicKeyEntry.COLUMN_NAME_UID + "))";

    private static final String SQL_DELETE_KEYS_ENTRY =
            "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;

    private static final String SQL_DELETE_USER_METADATA_ENTRY =
            "DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME;

    private static final String SQL_DELETE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY =
            "DROP TABLE IF EXISTS " + RecoveryServicePublicKeyEntry.TABLE_NAME;

    RecoverableKeyStoreDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
@@ -48,12 +62,14 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_KEYS_ENTRY);
        db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
        db.execSQL(SQL_CREATE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL(SQL_DELETE_KEYS_ENTRY);
        db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
        db.execSQL(SQL_DELETE_RECOVERY_SERVICE_PUBLIC_KEY_ENTRY);
        onCreate(db);
    }
}
+67 −0
Original line number Diff line number Diff line
@@ -36,6 +36,9 @@ import com.android.server.locksettings.recoverablekeystore.WrappedKey;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.spec.ECGenParameterSpec;
import java.util.Map;

@SmallTest
@@ -290,7 +293,71 @@ public class RecoverableKeyStoreDbTest {
        assertThat(statuses).hasSize(0);
    }

    @Test
    public void setRecoveryServicePublicKey_replaceOldKey() throws Exception {
        int userId = 12;
        int uid = 10009;
        PublicKey pubkey1 = genRandomPublicKey();
        PublicKey pubkey2 = genRandomPublicKey();
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
                pubkey2);
    }

    @Test
    public void setRecoveryServicePublicKey_allowsTwoUsersToHaveTheSameUidAndKey()
            throws Exception {
        int userId1 = 12;
        int userId2 = 23;
        int uid = 10009;
        PublicKey pubkey = genRandomPublicKey();
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId1, uid, pubkey);
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId2, uid, pubkey);
        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId1, uid)).isEqualTo(
                pubkey);
        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId2, uid)).isEqualTo(
                pubkey);
    }

    @Test
    public void setRecoveryServicePublicKey_allowsAUserToHaveTwoUids() throws Exception {
        int userId = 12;
        int uid1 = 10009;
        int uid2 = 20009;
        PublicKey pubkey = genRandomPublicKey();
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid1, pubkey);
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid2, pubkey);
        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid1)).isEqualTo(
                pubkey);
        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid2)).isEqualTo(
                pubkey);
    }

    @Test
    public void getRecoveryServicePublicKey_returnsNullIfNoKey() throws Exception {
        int userId = 12;
        int uid = 10009;
        assertNull(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid));
    }

    @Test
    public void getRecoveryServicePublicKey_returnsInsertedKey() throws Exception {
        int userId = 12;
        int uid = 10009;
        PublicKey pubkey = genRandomPublicKey();
        mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
        assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
                pubkey);
    }

    private static byte[] getUtf8Bytes(String s) {
        return s.getBytes(StandardCharsets.UTF_8);
    }

    private static PublicKey genRandomPublicKey() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
        keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"));
        return keyPairGenerator.generateKeyPair().getPublic();
    }
}