OSDN Git Service

Implement the DB table to store the recovery service's public key
authorBo Zhu <bozhu@google.com>
Thu, 21 Dec 2017 22:36:11 +0000 (14:36 -0800)
committerBo Zhu <bozhu@google.com>
Fri, 22 Dec 2017 19:35:19 +0000 (19:35 +0000)
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

services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java

index 0f1aeec..7fba57c 100644 (file)
@@ -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);
     }
 
     /**
index 56804e0..8d1aed2 100644 (file)
@@ -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;
 
@@ -296,6 +306,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.
      */
     public void close() {
index 82e5650..e575687 100644 (file)
@@ -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";
+    }
 }
index 46846e1..76106d9 100644 (file)
@@ -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);
     }
 }
index aaeaf7c..e172c49 100644 (file)
@@ -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();
+    }
 }