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

Commit 17f831f0 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents dc40b908 5b81fa66
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();
    }
}