content: /*
   * 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.content.Context;
  import android.database.sqlite.SQLiteDatabase;
  import android.database.sqlite.SQLiteOpenHelper;
  import android.util.Log;
  
  import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
  import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
  import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RootOfTrustEntry;
  import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
  
  /**
   * Helper for creating the recoverable key database.
   */
  class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper {
      private static final String TAG = "RecoverableKeyStoreDbHp";
  
      static final int DATABASE_VERSION = 6; // Added user id serial number.
      private static final String DATABASE_NAME = "recoverablekeystore.db";
  
      private static final String SQL_CREATE_KEYS_ENTRY =
              "CREATE TABLE " + KeysEntry.TABLE_NAME + "( "
                      + KeysEntry._ID + " INTEGER PRIMARY KEY,"
                      + KeysEntry.COLUMN_NAME_USER_ID + " INTEGER,"
                      + KeysEntry.COLUMN_NAME_UID + " INTEGER,"
                      + KeysEntry.COLUMN_NAME_ALIAS + " TEXT,"
                      + KeysEntry.COLUMN_NAME_NONCE + " BLOB,"
                      + 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,"
                      + KeysEntry.COLUMN_NAME_KEY_METADATA + " BLOB,"
                      + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + ","
                      + KeysEntry.COLUMN_NAME_ALIAS + "))";
  
      private static final String SQL_CREATE_USER_METADATA_ENTRY =
              "CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( "
                      + UserMetadataEntry._ID + " INTEGER PRIMARY KEY,"
                      + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
                      + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER,"
                      + UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER + " INTEGER DEFAULT -1)";
  
      private static final String SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY =
              "CREATE TABLE " + RecoveryServiceMetadataEntry.TABLE_NAME + " ("
                      + RecoveryServiceMetadataEntry._ID + " INTEGER PRIMARY KEY,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " INTEGER,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION + " INTEGER,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT + " INTEGER,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST + " TEXT,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH + " BLOB,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL + " INTEGER,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID + " INTEGER,"
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS + " BLOB,"
                      + "UNIQUE("
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID  + ","
                      + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";
  
      private static final String SQL_CREATE_ROOT_OF_TRUST_ENTRY =
              "CREATE TABLE " + RootOfTrustEntry.TABLE_NAME + " ("
                      + RootOfTrustEntry._ID + " INTEGER PRIMARY KEY,"
                      + RootOfTrustEntry.COLUMN_NAME_USER_ID + " INTEGER,"
                      + RootOfTrustEntry.COLUMN_NAME_UID + " INTEGER,"
                      + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " TEXT,"
                      + RootOfTrustEntry.COLUMN_NAME_CERT_PATH + " BLOB,"
                      + RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL + " INTEGER,"
                      + "UNIQUE("
                      + RootOfTrustEntry.COLUMN_NAME_USER_ID  + ","
                      + RootOfTrustEntry.COLUMN_NAME_UID  + ","
                      + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + "))";
  
      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_METADATA_ENTRY =
              "DROP TABLE IF EXISTS " + RecoveryServiceMetadataEntry.TABLE_NAME;
  
      private static final String SQL_DELETE_ROOT_OF_TRUST_ENTRY =
              "DROP TABLE IF EXISTS " + RootOfTrustEntry.TABLE_NAME;
  
      RecoverableKeyStoreDbHelper(Context context) {
          super(context, DATABASE_NAME, null, DATABASE_VERSION);
      }
  
      @Override
      public void onCreate(SQLiteDatabase db) {
          db.execSQL(SQL_CREATE_KEYS_ENTRY);
          db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
          db.execSQL(SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY);
          db.execSQL(SQL_CREATE_ROOT_OF_TRUST_ENTRY);
      }
  
      @Override
      public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
          Log.e(TAG, "Recreating recoverablekeystore after unexpected version downgrade.");
          dropAllKnownTables(db); // Wipe database.
          onCreate(db);
      }
  
      @Override
      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
          if (oldVersion < 2) {
              dropAllKnownTables(db); // Wipe database.
              onCreate(db);
              return;
          }
  
          if (oldVersion < 3 && newVersion >= 3) {
              upgradeDbForVersion3(db);
              oldVersion = 3;
          }
  
          if (oldVersion < 4 && newVersion >= 4) {
              upgradeDbForVersion4(db);
              oldVersion = 4;
          }
  
          if (oldVersion < 5 && newVersion >= 5) {
              upgradeDbForVersion5(db);
              oldVersion = 5;
          }
  
          if (oldVersion < 6 && newVersion >= 6) {
              upgradeDbForVersion6(db);
              oldVersion = 6;
          }
  
          if (oldVersion != newVersion) {
              Log.e(TAG, "Failed to update recoverablekeystore database to the most recent version");
          }
      }
  
      private void dropAllKnownTables(SQLiteDatabase db) {
              db.execSQL(SQL_DELETE_KEYS_ENTRY);
              db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
              db.execSQL(SQL_DELETE_RECOVERY_SERVICE_METADATA_ENTRY);
              db.execSQL(SQL_DELETE_ROOT_OF_TRUST_ENTRY);
      }
  
      private void upgradeDbForVersion3(SQLiteDatabase db) {
          // Add the two columns for cert path and cert serial number
          addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
                  RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH, "BLOB", /*defaultStr=*/ null);
          addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
                  RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL, "INTEGER", /*defaultStr=*/
                  null);
      }
  
      private void upgradeDbForVersion4(SQLiteDatabase db) {
          Log.d(TAG, "Updating recoverable keystore database to version 4");
          // Add new table with two columns for cert path and cert serial number.
          db.execSQL(SQL_CREATE_ROOT_OF_TRUST_ENTRY);
          // adds column to store root of trust currently used by the recovery agent
          addColumnToTable(db, RecoveryServiceMetadataEntry.TABLE_NAME,
                  RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST, "TEXT",
                  /*defaultStr=*/ null);
      }
  
      private void upgradeDbForVersion5(SQLiteDatabase db) {
          Log.d(TAG, "Updating recoverable keystore database to version 5");
          // adds a column to store the metadata for application keys
          addColumnToTable(db, KeysEntry.TABLE_NAME,
                  KeysEntry.COLUMN_NAME_KEY_METADATA, "BLOB", /*defaultStr=*/ null);
      }
  
      private void upgradeDbForVersion6(SQLiteDatabase db) {
          Log.d(TAG, "Updating recoverable keystore database to version 6");
          // adds a column to store the user serial number
          addColumnToTable(db, UserMetadataEntry.TABLE_NAME,
                  UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER,
                  "INTEGER DEFAULT -1",
                   /*defaultStr=*/ null);
      }
  
      private static void addColumnToTable(
              SQLiteDatabase db, String tableName, String column, String columnType,
              String defaultStr) {
          Log.d(TAG, "Adding column " + column + " to " + tableName + ".");
  
          String alterStr = "ALTER TABLE " + tableName + " ADD COLUMN " + column + " " + columnType;
          if (defaultStr != null && !defaultStr.isEmpty()) {
              alterStr += " DEFAULT " + defaultStr;
          }
  
          db.execSQL(alterStr + ";");
      }
  }
  
  