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

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

Implement API to store and retrieve recovery status.

Currently recovery agents can set/get statuses only for  their own keys.

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: I5cb70ce139ca29c066d46d0bd4d2967bd3c30843
parent 3b17c63f
Loading
Loading
Loading
Loading
+24 −5
Original line number Diff line number Diff line
@@ -159,18 +159,37 @@ public class RecoverableKeyStoreManager {
        throw new UnsupportedOperationException();
    }

    /**
     * Updates recovery status for the application given its {@code packageName}.
     *
     * @param packageName which recoverable key statuses will be returned
     * @param aliases - KeyStore aliases or {@code null} for all aliases of the app
     * @param status - new status
     */
    public void setRecoveryStatus(
            @NonNull String packageName, @Nullable String[] aliases, int status, int userId)
            throws RemoteException {
        checkRecoverKeyStorePermission();
        throw new UnsupportedOperationException();
        int uid = Binder.getCallingUid();
        if (packageName != null) {
            // TODO: get uid for package name, when many apps are supported.
        }
        if (aliases == null) {
            // Get all keys for the app.
            Map<String, Integer> allKeys = mDatabase.getStatusForAllKeys(uid);
            aliases = new String[allKeys.size()];
            allKeys.keySet().toArray(aliases);
        }
        for (String alias: aliases) {
            mDatabase.setRecoveryStatus(uid, alias, status);
        }
    }

    /**
     * Gets recovery status for keys {@code packageName}.
     * Gets recovery status for caller or other application {@code packageName}.
     * @param packageName which recoverable keys statuses will be returned.
     *
     * @param packageName which recoverable keys statuses will be returned
     * @return Map from KeyStore alias to recovery status
     * @return {@code Map} from KeyStore alias to recovery status.
     */
    public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId)
            throws RemoteException {
@@ -178,7 +197,7 @@ public class RecoverableKeyStoreManager {
        // If caller is a recovery agent it can check statuses for other packages, but
        // only for recoverable keys it manages.
        checkRecoverKeyStorePermission();
        throw new UnsupportedOperationException();
        return mDatabase.getStatusForAllKeys(Binder.getCallingUid());
    }

    /**
+34 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.locksettings.recoverablekeystore;

import android.util.Log;
import android.security.recoverablekeystore.RecoverableKeyStoreLoader;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -45,6 +46,7 @@ public class WrappedKey {
    private static final int GCM_TAG_LENGTH_BITS = 128;

    private final int mPlatformKeyGenerationId;
    private final int mRecoveryStatus;
    private final byte[] mNonce;
    private final byte[] mKeyMaterial;

@@ -94,22 +96,43 @@ public class WrappedKey {
        return new WrappedKey(
                /*nonce=*/ cipher.getIV(),
                /*keyMaterial=*/ encryptedKeyMaterial,
                /*platformKeyGenerationId=*/ wrappingKey.getGenerationId());
                /*platformKeyGenerationId=*/ wrappingKey.getGenerationId(),
                RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS);
    }

    /**
     * A new instance.
     * A new instance with default recovery status.
     *
     * @param nonce The nonce with which the key material was encrypted.
     * @param keyMaterial The encrypted bytes of the key material.
     * @param platformKeyGenerationId The generation ID of the key used to wrap this key.
     *
     * @see RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS
     * @hide
     */
    public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) {
        mNonce = nonce;
        mKeyMaterial = keyMaterial;
        mPlatformKeyGenerationId = platformKeyGenerationId;
        mRecoveryStatus = RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS;
    }

    /**
     * A new instance.
     *
     * @param nonce The nonce with which the key material was encrypted.
     * @param keyMaterial The encrypted bytes of the key material.
     * @param platformKeyGenerationId The generation ID of the key used to wrap this key.
     * @param recoveryStatus recovery status of the key.
     *
     * @hide
     */
    public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId,
            int recoveryStatus) {
        mNonce = nonce;
        mKeyMaterial = keyMaterial;
        mPlatformKeyGenerationId = platformKeyGenerationId;
        mRecoveryStatus = recoveryStatus;
    }

    /**
@@ -130,7 +153,6 @@ public class WrappedKey {
        return mKeyMaterial;
    }


    /**
     * Returns the generation ID of the platform key, with which this key was wrapped.
     *
@@ -140,6 +162,15 @@ public class WrappedKey {
        return mPlatformKeyGenerationId;
    }

    /**
     * Returns recovery status of the key.
     *
     * @hide
     */
    public int getRecoveryStatus() {
        return mRecoveryStatus;
    }

    /**
     * Unwraps the {@code wrappedKeys} with the {@code platformKey}.
     *
+74 −4
Original line number Diff line number Diff line
@@ -16,11 +16,13 @@

package com.android.server.locksettings.recoverablekeystore.storage;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
import android.util.Log;

import com.android.server.locksettings.recoverablekeystore.WrappedKey;
@@ -80,6 +82,7 @@ public class RecoverableKeyStoreDb {
        values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial());
        values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, LAST_SYNCED_AT_UNSYNCED);
        values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId());
        values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, wrappedKey.getRecoveryStatus());
        return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
    }

@@ -94,7 +97,8 @@ public class RecoverableKeyStoreDb {
                KeysEntry._ID,
                KeysEntry.COLUMN_NAME_NONCE,
                KeysEntry.COLUMN_NAME_WRAPPED_KEY,
                KeysEntry.COLUMN_NAME_GENERATION_ID};
                KeysEntry.COLUMN_NAME_GENERATION_ID,
                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
        String selection =
                KeysEntry.COLUMN_NAME_UID + " = ? AND "
                + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
@@ -128,10 +132,72 @@ public class RecoverableKeyStoreDb {
                    cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
            int generationId = cursor.getInt(
                    cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID));
            return new WrappedKey(nonce, keyMaterial, generationId);
            int recoveryStatus = cursor.getInt(
                    cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
            return new WrappedKey(nonce, keyMaterial, generationId, recoveryStatus);
        }
    }

    /**
     * Returns all statuses for keys {@code uid} and {@code platformKeyGenerationId}.
     *
     * @param uid of the application
     *
     * @return Map from Aliases to status.
     *
     * @hide
     */
    public @NonNull Map<String, Integer> getStatusForAllKeys(int uid) {
        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
        String[] projection = {
                KeysEntry._ID,
                KeysEntry.COLUMN_NAME_ALIAS,
                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
        String selection =
                KeysEntry.COLUMN_NAME_UID + " = ?";
        String[] selectionArguments = {Integer.toString(uid)};

        try (
            Cursor cursor = db.query(
                KeysEntry.TABLE_NAME,
                projection,
                selection,
                selectionArguments,
                /*groupBy=*/ null,
                /*having=*/ null,
                /*orderBy=*/ null)
        ) {
            HashMap<String, Integer> statuses = new HashMap<>();
            while (cursor.moveToNext()) {
                String alias = cursor.getString(
                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
                int recoveryStatus = cursor.getInt(
                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
                statuses.put(alias, recoveryStatus);
            }
            return statuses;
        }
    }

    /**
     * Updates status for given key.
     * @param uid of the application
     * @param alias of the key
     * @param status - new status
     * @return number of updated entries.
     * @hide
     **/
    public int setRecoveryStatus(int uid, String alias, int status) {
        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, status);
        String selection =
                KeysEntry.COLUMN_NAME_UID + " = ? AND "
                + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
        return db.update(KeysEntry.TABLE_NAME, values, selection,
            new String[] {String.valueOf(uid), alias});
    }

    /**
     * Returns all keys for the given {@code userId} and {@code platformKeyGenerationId}.
     *
@@ -148,7 +214,8 @@ public class RecoverableKeyStoreDb {
                KeysEntry._ID,
                KeysEntry.COLUMN_NAME_NONCE,
                KeysEntry.COLUMN_NAME_WRAPPED_KEY,
                KeysEntry.COLUMN_NAME_ALIAS};
                KeysEntry.COLUMN_NAME_ALIAS,
                KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
        String selection =
                KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
                + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?";
@@ -173,7 +240,10 @@ public class RecoverableKeyStoreDb {
                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
                String alias = cursor.getString(
                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
                keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId));
                int recoveryStatus = cursor.getInt(
                        cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
                keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId,
                        recoveryStatus));
            }
            return keys;
        }
+5 −0
Original line number Diff line number Diff line
@@ -62,6 +62,11 @@ class RecoverableKeyStoreDbContract {
         * Timestamp of when this key was last synced with remote storage, or -1 if never synced.
         */
        static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at";

        /**
         * Status of the key sync {@code RecoverableKeyStoreLoader#setRecoveryStatus}
         */
        static final String COLUMN_NAME_RECOVERY_STATUS = "recovery_status";
    }

    /**
+1 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
                    + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB,"
                    + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER,"
                    + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER,"
                    + KeysEntry.COLUMN_NAME_RECOVERY_STATUS + " INTEGER,"
                    + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + ","
                    + KeysEntry.COLUMN_NAME_ALIAS + "))";

Loading