blob: ed570c3b660ca75b7430f952e2d153ab3e502868 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.locksettings.recoverablekeystore.storage;
import android.annotation.Nullable;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
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.UserMetadataEntry;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* Database of recoverable key information.
*
* @hide
*/
public class RecoverableKeyStoreDb {
private static final String TAG = "RecoverableKeyStoreDb";
private static final int IDLE_TIMEOUT_SECONDS = 30;
private static final int LAST_SYNCED_AT_UNSYNCED = -1;
private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper;
/**
* A new instance, storing the database in the user directory of {@code context}.
*
* @hide
*/
public static RecoverableKeyStoreDb newInstance(Context context) {
RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context);
helper.setWriteAheadLoggingEnabled(true);
helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS);
return new RecoverableKeyStoreDb(helper);
}
private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) {
this.mKeyStoreDbHelper = keyStoreDbHelper;
}
/**
* Inserts a key into the database.
*
* @param userId The uid of the profile the application is running under.
* @param uid Uid of the application to whom the key belongs.
* @param alias The alias of the key in the AndroidKeyStore.
* @param wrappedKey The wrapped key.
* @return The primary key of the inserted row, or -1 if failed.
*
* @hide
*/
public long insertKey(int userId, int uid, String alias, WrappedKey wrappedKey) {
SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KeysEntry.COLUMN_NAME_USER_ID, userId);
values.put(KeysEntry.COLUMN_NAME_UID, uid);
values.put(KeysEntry.COLUMN_NAME_ALIAS, alias);
values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce());
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());
return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
}
/**
* Gets the key with {@code alias} for the app with {@code uid}.
*
* @hide
*/
@Nullable public WrappedKey getKey(int uid, String alias) {
SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
String[] projection = {
KeysEntry._ID,
KeysEntry.COLUMN_NAME_NONCE,
KeysEntry.COLUMN_NAME_WRAPPED_KEY,
KeysEntry.COLUMN_NAME_GENERATION_ID};
String selection =
KeysEntry.COLUMN_NAME_UID + " = ? AND "
+ KeysEntry.COLUMN_NAME_ALIAS + " = ?";
String[] selectionArguments = { Integer.toString(uid), alias };
try (
Cursor cursor = db.query(
KeysEntry.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 WrappedKey entries found for uid=%d alias='%s'. "
+ "Should only ever be 0 or 1.", count, uid, alias));
return null;
}
cursor.moveToFirst();
byte[] nonce = cursor.getBlob(
cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
byte[] keyMaterial = cursor.getBlob(
cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
int generationId = cursor.getInt(
cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID));
return new WrappedKey(nonce, keyMaterial, generationId);
}
}
/**
* Returns all keys for the given {@code userId} and {@code platformKeyGenerationId}.
*
* @param userId User id of the profile to which all the keys are associated.
* @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys.
* (i.e., this should be the most recent generation ID, as older platform keys are not
* usable.)
*
* @hide
*/
public Map<String, WrappedKey> getAllKeys(int userId, int platformKeyGenerationId) {
SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
String[] projection = {
KeysEntry._ID,
KeysEntry.COLUMN_NAME_NONCE,
KeysEntry.COLUMN_NAME_WRAPPED_KEY,
KeysEntry.COLUMN_NAME_ALIAS};
String selection =
KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?";
String[] selectionArguments = {
Integer.toString(userId), Integer.toString(platformKeyGenerationId) };
try (
Cursor cursor = db.query(
KeysEntry.TABLE_NAME,
projection,
selection,
selectionArguments,
/*groupBy=*/ null,
/*having=*/ null,
/*orderBy=*/ null)
) {
HashMap<String, WrappedKey> keys = new HashMap<>();
while (cursor.moveToNext()) {
byte[] nonce = cursor.getBlob(
cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
byte[] keyMaterial = cursor.getBlob(
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));
}
return keys;
}
}
/**
* Sets the {@code generationId} of the platform key for the account owned by {@code userId}.
*
* @return The primary key ID of the relation.
*/
public long setPlatformKeyGenerationId(int userId, int generationId) {
SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId);
return db.replace(
UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
}
/**
* Returns the generation ID associated with the platform key of the user with {@code userId}.
*/
public int getPlatformKeyGenerationId(int userId) {
SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
String[] projection = {
UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID};
String selection =
UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
String[] selectionArguments = {
Integer.toString(userId)};
try (
Cursor cursor = db.query(
UserMetadataEntry.TABLE_NAME,
projection,
selection,
selectionArguments,
/*groupBy=*/ null,
/*having=*/ null,
/*orderBy=*/ null)
) {
if (cursor.getCount() == 0) {
return -1;
}
cursor.moveToFirst();
return cursor.getInt(
cursor.getColumnIndexOrThrow(
UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID));
}
}
/**
* Closes all open connections to the database.
*/
public void close() {
mKeyStoreDbHelper.close();
}
// TODO: Add method for updating the 'last synced' time.
}