Home | History | Annotate | Download | only in storage
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.locksettings.recoverablekeystore.storage;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.content.ContentValues;
     22 import android.content.Context;
     23 import android.database.Cursor;
     24 import android.database.sqlite.SQLiteDatabase;
     25 import android.security.keystore.recovery.RecoveryController;
     26 import android.text.TextUtils;
     27 import android.util.Log;
     28 
     29 import com.android.server.locksettings.recoverablekeystore.TestOnlyInsecureCertificateHelper;
     30 import com.android.server.locksettings.recoverablekeystore.WrappedKey;
     31 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
     32 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RecoveryServiceMetadataEntry;
     33 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.RootOfTrustEntry;
     34 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
     35 
     36 import java.io.ByteArrayInputStream;
     37 import java.security.KeyFactory;
     38 import java.security.NoSuchAlgorithmException;
     39 import java.security.PublicKey;
     40 import java.security.cert.CertPath;
     41 import java.security.cert.CertificateEncodingException;
     42 import java.security.cert.CertificateException;
     43 import java.security.cert.CertificateFactory;
     44 import java.security.spec.InvalidKeySpecException;
     45 import java.security.spec.X509EncodedKeySpec;
     46 import java.util.ArrayList;
     47 import java.util.Arrays;
     48 import java.util.HashMap;
     49 import java.util.List;
     50 import java.util.Locale;
     51 import java.util.Map;
     52 import java.util.StringJoiner;
     53 
     54 /**
     55  * Database of recoverable key information.
     56  *
     57  * @hide
     58  */
     59 public class RecoverableKeyStoreDb {
     60     private static final String TAG = "RecoverableKeyStoreDb";
     61     private static final int IDLE_TIMEOUT_SECONDS = 30;
     62     private static final int LAST_SYNCED_AT_UNSYNCED = -1;
     63     private static final String CERT_PATH_ENCODING = "PkiPath";
     64 
     65     private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper;
     66     private final TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
     67 
     68     /**
     69      * A new instance, storing the database in the user directory of {@code context}.
     70      *
     71      * @hide
     72      */
     73     public static RecoverableKeyStoreDb newInstance(Context context) {
     74         RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context);
     75         helper.setWriteAheadLoggingEnabled(true);
     76         helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS);
     77         return new RecoverableKeyStoreDb(helper);
     78     }
     79 
     80     private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) {
     81         this.mKeyStoreDbHelper = keyStoreDbHelper;
     82         this.mTestOnlyInsecureCertificateHelper = new TestOnlyInsecureCertificateHelper();
     83     }
     84 
     85     /**
     86      * Inserts a key into the database.
     87      *
     88      * @param userId The uid of the profile the application is running under.
     89      * @param uid Uid of the application to whom the key belongs.
     90      * @param alias The alias of the key in the AndroidKeyStore.
     91      * @param wrappedKey The wrapped key.
     92      * @return The primary key of the inserted row, or -1 if failed.
     93      *
     94      * @hide
     95      */
     96     public long insertKey(int userId, int uid, String alias, WrappedKey wrappedKey) {
     97         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
     98         ContentValues values = new ContentValues();
     99         values.put(KeysEntry.COLUMN_NAME_USER_ID, userId);
    100         values.put(KeysEntry.COLUMN_NAME_UID, uid);
    101         values.put(KeysEntry.COLUMN_NAME_ALIAS, alias);
    102         values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce());
    103         values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial());
    104         values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, LAST_SYNCED_AT_UNSYNCED);
    105         values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId());
    106         values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, wrappedKey.getRecoveryStatus());
    107         return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
    108     }
    109 
    110     /**
    111      * Gets the key with {@code alias} for the app with {@code uid}.
    112      *
    113      * @hide
    114      */
    115     @Nullable public WrappedKey getKey(int uid, String alias) {
    116         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
    117         String[] projection = {
    118                 KeysEntry._ID,
    119                 KeysEntry.COLUMN_NAME_NONCE,
    120                 KeysEntry.COLUMN_NAME_WRAPPED_KEY,
    121                 KeysEntry.COLUMN_NAME_GENERATION_ID,
    122                 KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
    123         String selection =
    124                 KeysEntry.COLUMN_NAME_UID + " = ? AND "
    125                 + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
    126         String[] selectionArguments = { Integer.toString(uid), alias };
    127 
    128         try (
    129             Cursor cursor = db.query(
    130                 KeysEntry.TABLE_NAME,
    131                 projection,
    132                 selection,
    133                 selectionArguments,
    134                 /*groupBy=*/ null,
    135                 /*having=*/ null,
    136                 /*orderBy=*/ null)
    137         ) {
    138             int count = cursor.getCount();
    139             if (count == 0) {
    140                 return null;
    141             }
    142             if (count > 1) {
    143                 Log.wtf(TAG,
    144                         String.format(Locale.US,
    145                                 "%d WrappedKey entries found for uid=%d alias='%s'. "
    146                                         + "Should only ever be 0 or 1.", count, uid, alias));
    147                 return null;
    148             }
    149             cursor.moveToFirst();
    150             byte[] nonce = cursor.getBlob(
    151                     cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
    152             byte[] keyMaterial = cursor.getBlob(
    153                     cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
    154             int generationId = cursor.getInt(
    155                     cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID));
    156             int recoveryStatus = cursor.getInt(
    157                     cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
    158             return new WrappedKey(nonce, keyMaterial, generationId, recoveryStatus);
    159         }
    160     }
    161 
    162     /**
    163      * Removes key with {@code alias} for app with {@code uid}.
    164      *
    165      * @return {@code true} if deleted a row.
    166      */
    167     public boolean removeKey(int uid, String alias) {
    168         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
    169         String selection = KeysEntry.COLUMN_NAME_UID + " = ? AND " +
    170                 KeysEntry.COLUMN_NAME_ALIAS + " = ?";
    171         String[] selectionArgs = { Integer.toString(uid), alias };
    172         return db.delete(KeysEntry.TABLE_NAME, selection, selectionArgs) > 0;
    173     }
    174 
    175     /**
    176      * Returns all statuses for keys {@code uid} and {@code platformKeyGenerationId}.
    177      *
    178      * @param uid of the application
    179      *
    180      * @return Map from Aliases to status.
    181      *
    182      * @hide
    183      */
    184     public @NonNull Map<String, Integer> getStatusForAllKeys(int uid) {
    185         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
    186         String[] projection = {
    187                 KeysEntry._ID,
    188                 KeysEntry.COLUMN_NAME_ALIAS,
    189                 KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
    190         String selection =
    191                 KeysEntry.COLUMN_NAME_UID + " = ?";
    192         String[] selectionArguments = {Integer.toString(uid)};
    193 
    194         try (
    195             Cursor cursor = db.query(
    196                 KeysEntry.TABLE_NAME,
    197                 projection,
    198                 selection,
    199                 selectionArguments,
    200                 /*groupBy=*/ null,
    201                 /*having=*/ null,
    202                 /*orderBy=*/ null)
    203         ) {
    204             HashMap<String, Integer> statuses = new HashMap<>();
    205             while (cursor.moveToNext()) {
    206                 String alias = cursor.getString(
    207                         cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
    208                 int recoveryStatus = cursor.getInt(
    209                         cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
    210                 statuses.put(alias, recoveryStatus);
    211             }
    212             return statuses;
    213         }
    214     }
    215 
    216     /**
    217      * Updates status for given key.
    218      * @param uid of the application
    219      * @param alias of the key
    220      * @param status - new status
    221      * @return number of updated entries.
    222      * @hide
    223      **/
    224     public int setRecoveryStatus(int uid, String alias, int status) {
    225         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
    226         ContentValues values = new ContentValues();
    227         values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS, status);
    228         String selection =
    229                 KeysEntry.COLUMN_NAME_UID + " = ? AND "
    230                 + KeysEntry.COLUMN_NAME_ALIAS + " = ?";
    231         return db.update(KeysEntry.TABLE_NAME, values, selection,
    232             new String[] {String.valueOf(uid), alias});
    233     }
    234 
    235     /**
    236      * Returns all keys for the given {@code userId} {@code recoveryAgentUid}
    237      * and {@code platformKeyGenerationId}.
    238      *
    239      * @param userId User id of the profile to which all the keys are associated.
    240      * @param recoveryAgentUid Uid of the recovery agent which will perform the sync
    241      * @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys.
    242      *     (i.e., this should be the most recent generation ID, as older platform keys are not
    243      *     usable.)
    244      *
    245      * @hide
    246      */
    247     public Map<String, WrappedKey> getAllKeys(int userId, int recoveryAgentUid,
    248             int platformKeyGenerationId) {
    249         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
    250         String[] projection = {
    251                 KeysEntry._ID,
    252                 KeysEntry.COLUMN_NAME_NONCE,
    253                 KeysEntry.COLUMN_NAME_WRAPPED_KEY,
    254                 KeysEntry.COLUMN_NAME_ALIAS,
    255                 KeysEntry.COLUMN_NAME_RECOVERY_STATUS};
    256         String selection =
    257                 KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
    258                 + KeysEntry.COLUMN_NAME_UID + " = ? AND "
    259                 + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?";
    260         String[] selectionArguments = {
    261                 Integer.toString(userId),
    262                 Integer.toString(recoveryAgentUid),
    263                 Integer.toString(platformKeyGenerationId)
    264             };
    265 
    266         try (
    267             Cursor cursor = db.query(
    268                 KeysEntry.TABLE_NAME,
    269                 projection,
    270                 selection,
    271                 selectionArguments,
    272                 /*groupBy=*/ null,
    273                 /*having=*/ null,
    274                 /*orderBy=*/ null)
    275         ) {
    276             HashMap<String, WrappedKey> keys = new HashMap<>();
    277             while (cursor.moveToNext()) {
    278                 byte[] nonce = cursor.getBlob(
    279                         cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE));
    280                 byte[] keyMaterial = cursor.getBlob(
    281                         cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY));
    282                 String alias = cursor.getString(
    283                         cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS));
    284                 int recoveryStatus = cursor.getInt(
    285                         cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_RECOVERY_STATUS));
    286                 keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId,
    287                         recoveryStatus));
    288             }
    289             return keys;
    290         }
    291     }
    292 
    293     /**
    294      * Sets the {@code generationId} of the platform key for user {@code userId}.
    295      *
    296      * @return The primary key ID of the relation.
    297      */
    298     public long setPlatformKeyGenerationId(int userId, int generationId) {
    299         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
    300         ContentValues values = new ContentValues();
    301         values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
    302         values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId);
    303         long result = db.replace(
    304                 UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
    305         if (result != -1) {
    306             invalidateKeysWithOldGenerationId(userId, generationId);
    307         }
    308         return result;
    309     }
    310 
    311     /**
    312      * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
    313      */
    314     public void invalidateKeysWithOldGenerationId(int userId, int newGenerationId) {
    315         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
    316         ContentValues values = new ContentValues();
    317         values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS,
    318                 RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
    319         String selection =
    320                 KeysEntry.COLUMN_NAME_USER_ID + " = ? AND "
    321                 + KeysEntry.COLUMN_NAME_GENERATION_ID + " < ?";
    322         db.update(KeysEntry.TABLE_NAME, values, selection,
    323             new String[] {String.valueOf(userId), String.valueOf(newGenerationId)});
    324     }
    325 
    326     /**
    327      * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
    328      */
    329     public void invalidateKeysForUserIdOnCustomScreenLock(int userId) {
    330         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
    331         ContentValues values = new ContentValues();
    332         values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS,
    333             RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
    334         String selection =
    335             KeysEntry.COLUMN_NAME_USER_ID + " = ?";
    336         db.update(KeysEntry.TABLE_NAME, values, selection,
    337             new String[] {String.valueOf(userId)});
    338     }
    339 
    340     /**
    341      * Returns the generation ID associated with the platform key of the user with {@code userId}.
    342      */
    343     public int getPlatformKeyGenerationId(int userId) {
    344         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
    345         String[] projection = {
    346                 UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID};
    347         String selection =
    348                 UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
    349         String[] selectionArguments = {
    350                 Integer.toString(userId)};
    351 
    352         try (
    353             Cursor cursor = db.query(
    354                 UserMetadataEntry.TABLE_NAME,
    355                 projection,
    356                 selection,
    357                 selectionArguments,
    358                 /*groupBy=*/ null,
    359                 /*having=*/ null,
    360                 /*orderBy=*/ null)
    361         ) {
    362             if (cursor.getCount() == 0) {
    363                 return -1;
    364             }
    365             cursor.moveToFirst();
    366             return cursor.getInt(
    367                     cursor.getColumnIndexOrThrow(
    368                             UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID));
    369         }
    370     }
    371 
    372     /**
    373      * Updates the public key of the recovery service into the database.
    374      *
    375      * @param userId The uid of the profile the application is running under.
    376      * @param uid The uid of the application to whom the key belongs.
    377      * @param publicKey The public key of the recovery service.
    378      * @return The primary key of the inserted row, or -1 if failed.
    379      *
    380      * @hide
    381      */
    382     public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) {
    383         return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY,
    384                 publicKey.getEncoded());
    385     }
    386 
    387     /**
    388      * Returns the serial number of the XML file containing certificates of the recovery service.
    389      *
    390      * @param userId The userId of the profile the application is running under.
    391      * @param uid The uid of the application who initializes the local recovery components.
    392      * @param rootAlias The root of trust alias.
    393      * @return The value that were previously set, or null if there's none.
    394      *
    395      * @hide
    396      */
    397     @Nullable
    398     public Long getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias) {
    399         return getLong(userId, uid, rootAlias,
    400                 RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL);
    401     }
    402 
    403     /**
    404      * Records the serial number of the XML file containing certificates of the recovery service.
    405      *
    406      * @param userId The userId of the profile the application is running under.
    407      * @param uid The uid of the application who initializes the local recovery components.
    408      * @param rootAlias The root of trust alias.
    409      * @param serial The serial number contained in the XML file for recovery service certificates.
    410      * @return The primary key of the inserted row, or -1 if failed.
    411      *
    412      * @hide
    413      */
    414     public long setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias,
    415             long serial) {
    416         return setLong(userId, uid, rootAlias, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_SERIAL,
    417                 serial);
    418     }
    419 
    420     /**
    421      * Returns the {@code CertPath} of the recovery service.
    422      *
    423      * @param userId The userId of the profile the application is running under.
    424      * @param uid The uid of the application who initializes the local recovery components.
    425      * @param rootAlias The root of trust alias.
    426      * @return The value that were previously set, or null if there's none.
    427      *
    428      * @hide
    429      */
    430     @Nullable
    431     public CertPath getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias) {
    432         byte[] bytes = getBytes(userId, uid, rootAlias,
    433                 RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH);
    434         if (bytes == null) {
    435             return null;
    436         }
    437         try {
    438             return decodeCertPath(bytes);
    439         } catch (CertificateException e) {
    440             Log.wtf(TAG,
    441                     String.format(Locale.US,
    442                             "Recovery service CertPath entry cannot be decoded for "
    443                                     + "userId=%d uid=%d.",
    444                             userId, uid), e);
    445             return null;
    446         }
    447     }
    448 
    449     /**
    450      * Sets the {@code CertPath} of the recovery service.
    451      *
    452      * @param userId The userId of the profile the application is running under.
    453      * @param uid The uid of the application who initializes the local recovery components.
    454      * @param rootAlias The root of trust alias.
    455      * @param certPath The certificate path of the recovery service.
    456      * @return The primary key of the inserted row, or -1 if failed.
    457      * @hide
    458      */
    459     public long setRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias,
    460             CertPath certPath) throws CertificateEncodingException {
    461         if (certPath.getCertificates().size() == 0) {
    462             throw new CertificateEncodingException("No certificate contained in the cert path.");
    463         }
    464         return setBytes(userId, uid, rootAlias, RecoveryServiceMetadataEntry.COLUMN_NAME_CERT_PATH,
    465                 certPath.getEncoded(CERT_PATH_ENCODING));
    466     }
    467 
    468     /**
    469      * Returns the list of recovery agents initialized for given {@code userId}
    470      * @param userId The userId of the profile the application is running under.
    471      * @return The list of recovery agents
    472      * @hide
    473      */
    474     public @NonNull List<Integer> getRecoveryAgents(int userId) {
    475         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
    476 
    477         String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID };
    478         String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
    479         String[] selectionArguments = { Integer.toString(userId) };
    480 
    481         try (
    482             Cursor cursor = db.query(
    483                     RecoveryServiceMetadataEntry.TABLE_NAME,
    484                     projection,
    485                     selection,
    486                     selectionArguments,
    487                     /*groupBy=*/ null,
    488                     /*having=*/ null,
    489                     /*orderBy=*/ null)
    490         ) {
    491             int count = cursor.getCount();
    492             ArrayList<Integer> result = new ArrayList<>(count);
    493             while (cursor.moveToNext()) {
    494                 int uid = cursor.getInt(
    495                         cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID));
    496                 result.add(uid);
    497             }
    498             return result;
    499         }
    500     }
    501 
    502     /**
    503      * Returns the public key of the recovery service.
    504      *
    505      * @param userId The userId of the profile the application is running under.
    506      * @param uid The uid of the application who initializes the local recovery components.
    507      *
    508      * @hide
    509      */
    510     @Nullable
    511     public PublicKey getRecoveryServicePublicKey(int userId, int uid) {
    512         byte[] keyBytes =
    513                 getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
    514         if (keyBytes == null) {
    515             return null;
    516         }
    517         try {
    518             return decodeX509Key(keyBytes);
    519         } catch (InvalidKeySpecException e) {
    520             Log.wtf(TAG,
    521                     String.format(Locale.US,
    522                             "Recovery service public key entry cannot be decoded for "
    523                                     + "userId=%d uid=%d.",
    524                             userId, uid));
    525             return null;
    526         }
    527     }
    528 
    529     /**
    530      * Updates the list of user secret types used for end-to-end encryption.
    531      * If no secret types are set, recovery snapshot will not be created.
    532      * See {@code KeyChainProtectionParams}
    533      *
    534      * @param userId The userId of the profile the application is running under.
    535      * @param uid The uid of the application.
    536      * @param secretTypes list of secret types
    537      * @return The primary key of the updated row, or -1 if failed.
    538      *
    539      * @hide
    540      */
    541     public long setRecoverySecretTypes(int userId, int uid, int[] secretTypes) {
    542         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
    543         ContentValues values = new ContentValues();
    544         StringJoiner joiner = new StringJoiner(",");
    545         Arrays.stream(secretTypes).forEach(i -> joiner.add(Integer.toString(i)));
    546         String typesAsCsv = joiner.toString();
    547         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, typesAsCsv);
    548         String selection =
    549                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
    550                 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
    551         ensureRecoveryServiceMetadataEntryExists(userId, uid);
    552         return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, selection,
    553             new String[] {String.valueOf(userId), String.valueOf(uid)});
    554     }
    555 
    556     /**
    557      * Returns the list of secret types used for end-to-end encryption.
    558      *
    559      * @param userId The userId of the profile the application is running under.
    560      * @param uid The uid of the application who initialized the local recovery components.
    561      * @return Secret types or empty array, if types were not set.
    562      *
    563      * @hide
    564      */
    565     public @NonNull int[] getRecoverySecretTypes(int userId, int uid) {
    566         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
    567 
    568         String[] projection = {
    569                 RecoveryServiceMetadataEntry._ID,
    570                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
    571                 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
    572                 RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES};
    573         String selection =
    574                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
    575                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
    576         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
    577 
    578         try (
    579                 Cursor cursor = db.query(
    580                         RecoveryServiceMetadataEntry.TABLE_NAME,
    581                         projection,
    582                         selection,
    583                         selectionArguments,
    584                         /*groupBy=*/ null,
    585                         /*having=*/ null,
    586                         /*orderBy=*/ null)
    587         ) {
    588             int count = cursor.getCount();
    589             if (count == 0) {
    590                 return new int[]{};
    591             }
    592             if (count > 1) {
    593                 Log.wtf(TAG,
    594                         String.format(Locale.US,
    595                                 "%d deviceId entries found for userId=%d uid=%d. "
    596                                         + "Should only ever be 0 or 1.", count, userId, uid));
    597                 return new int[]{};
    598             }
    599             cursor.moveToFirst();
    600             int idx = cursor.getColumnIndexOrThrow(
    601                     RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES);
    602             if (cursor.isNull(idx)) {
    603                 return new int[]{};
    604             }
    605             String csv = cursor.getString(idx);
    606             if (TextUtils.isEmpty(csv)) {
    607                 return new int[]{};
    608             }
    609             String[] types = csv.split(",");
    610             int[] result = new int[types.length];
    611             for (int i = 0; i < types.length; i++) {
    612                 try {
    613                     result[i] = Integer.parseInt(types[i]);
    614                 } catch (NumberFormatException e) {
    615                     Log.wtf(TAG, "String format error " + e);
    616                 }
    617             }
    618             return result;
    619         }
    620     }
    621 
    622     /**
    623      * Active root of trust for the recovery agent.
    624      *
    625      * @param userId The userId of the profile the application is running under.
    626      * @param uid The uid of the application.
    627      * @param rootAlias The root of trust alias.
    628      * @return The primary key of the updated row, or -1 if failed.
    629      *
    630      * @hide
    631      */
    632     public long setActiveRootOfTrust(int userId, int uid, @Nullable String rootAlias) {
    633         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
    634         ContentValues values = new ContentValues();
    635         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST, rootAlias);
    636         String selection =
    637                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
    638                 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
    639         ensureRecoveryServiceMetadataEntryExists(userId, uid);
    640         return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values,
    641             selection, new String[] {String.valueOf(userId), String.valueOf(uid)});
    642     }
    643 
    644     /**
    645      * Active root of trust for the recovery agent.
    646      *
    647      * @param userId The userId of the profile the application is running under.
    648      * @param uid The uid of the application who initialized the local recovery components.
    649      * @return Active root of trust alias of null if it was not set
    650      *
    651      * @hide
    652      */
    653     public @Nullable String getActiveRootOfTrust(int userId, int uid) {
    654         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
    655 
    656         String[] projection = {
    657                 RecoveryServiceMetadataEntry._ID,
    658                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
    659                 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
    660                 RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST};
    661         String selection =
    662                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
    663                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
    664         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
    665 
    666         try (
    667                 Cursor cursor = db.query(
    668                         RecoveryServiceMetadataEntry.TABLE_NAME,
    669                         projection,
    670                         selection,
    671                         selectionArguments,
    672                         /*groupBy=*/ null,
    673                         /*having=*/ null,
    674                         /*orderBy=*/ null)
    675         ) {
    676             int count = cursor.getCount();
    677             if (count == 0) {
    678                 return null;
    679             }
    680             if (count > 1) {
    681                 Log.wtf(TAG,
    682                         String.format(Locale.US,
    683                                 "%d deviceId entries found for userId=%d uid=%d. "
    684                                         + "Should only ever be 0 or 1.", count, userId, uid));
    685                 return null;
    686             }
    687             cursor.moveToFirst();
    688             int idx = cursor.getColumnIndexOrThrow(
    689                     RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST);
    690             if (cursor.isNull(idx)) {
    691                 return null;
    692             }
    693             String result = cursor.getString(idx);
    694             if (TextUtils.isEmpty(result)) {
    695                 return null;
    696             }
    697             return result;
    698         }
    699     }
    700 
    701     /**
    702      * Updates the counterId
    703      *
    704      * @param userId The userId of the profile the application is running under.
    705      * @param uid The uid of the application.
    706      * @param counterId The counterId.
    707      * @return The primary key of the inserted row, or -1 if failed.
    708      *
    709      * @hide
    710      */
    711     public long setCounterId(int userId, int uid, long counterId) {
    712         return setLong(userId, uid,
    713                 RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID, counterId);
    714     }
    715 
    716     /**
    717      * Returns the counter id.
    718      *
    719      * @param userId The userId of the profile the application is running under.
    720      * @param uid The uid of the application who initialized the local recovery components.
    721      * @return The counter id
    722      *
    723      * @hide
    724      */
    725     @Nullable
    726     public Long getCounterId(int userId, int uid) {
    727         return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID);
    728     }
    729 
    730     /**
    731      * Updates the server parameters given by the application initializing the local recovery
    732      * components.
    733      *
    734      * @param userId The userId of the profile the application is running under.
    735      * @param uid The uid of the application.
    736      * @param serverParams The server parameters.
    737      * @return The primary key of the inserted row, or -1 if failed.
    738      *
    739      * @hide
    740      */
    741     public long setServerParams(int userId, int uid, byte[] serverParams) {
    742         return setBytes(userId, uid,
    743                 RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, serverParams);
    744     }
    745 
    746     /**
    747      * Returns the server paramters that was previously set by the application who initialized the
    748      * local recovery service components.
    749      *
    750      * @param userId The userId of the profile the application is running under.
    751      * @param uid The uid of the application who initialized the local recovery components.
    752      * @return The server parameters that were previously set, or null if there's none.
    753      *
    754      * @hide
    755      */
    756     @Nullable
    757     public byte[] getServerParams(int userId, int uid) {
    758         return getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS);
    759     }
    760 
    761     /**
    762      * Updates the snapshot version.
    763      *
    764      * @param userId The userId of the profile the application is running under.
    765      * @param uid The uid of the application.
    766      * @param snapshotVersion The snapshot version
    767      * @return The primary key of the inserted row, or -1 if failed.
    768      *
    769      * @hide
    770      */
    771     public long setSnapshotVersion(int userId, int uid, long snapshotVersion) {
    772         return setLong(userId, uid,
    773                 RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION, snapshotVersion);
    774     }
    775 
    776     /**
    777      * Returns the snapshot version
    778      *
    779      * @param userId The userId of the profile the application is running under.
    780      * @param uid The uid of the application who initialized the local recovery components.
    781      * @return The server parameters that were previously set, or null if there's none.
    782      *
    783      * @hide
    784      */
    785     @Nullable
    786     public Long getSnapshotVersion(int userId, int uid) {
    787         return getLong(userId, uid,
    788             RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION);
    789     }
    790 
    791     /**
    792      * Updates a flag indicating that a new snapshot should be created.
    793      * It will be {@code false} until the first application key is added.
    794      * After that, the flag will be set to true, if one of the following values is updated:
    795      * <ul>
    796      *     <li> List of application keys
    797      *     <li> Server params.
    798      *     <li> Lock-screen secret.
    799      *     <li> Lock-screen secret type.
    800      *     <li> Trusted hardware certificate.
    801      * </ul>
    802      *
    803      * @param userId The userId of the profile the application is running under.
    804      * @param uid The uid of the application.
    805      * @param pending Should create snapshot flag.
    806      * @return The primary key of the inserted row, or -1 if failed.
    807      *
    808      * @hide
    809      */
    810     public long setShouldCreateSnapshot(int userId, int uid, boolean pending) {
    811         return setLong(userId, uid,
    812                 RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT, pending ? 1 : 0);
    813     }
    814 
    815     /**
    816      * Returns {@code true} if new snapshot should be created.
    817      * Returns {@code false} if the flag was never set.
    818      *
    819      * @param userId The userId of the profile the application is running under.
    820      * @param uid The uid of the application who initialized the local recovery components.
    821      * @return should create snapshot flag
    822      *
    823      * @hide
    824      */
    825     public boolean getShouldCreateSnapshot(int userId, int uid) {
    826         Long res = getLong(userId, uid,
    827                 RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT);
    828         return res != null && res != 0L;
    829     }
    830 
    831 
    832     /**
    833      * Returns given long value from the database.
    834      *
    835      * @param userId The userId of the profile the application is running under.
    836      * @param uid The uid of the application who initialized the local recovery components.
    837      * @param key from {@code RecoveryServiceMetadataEntry}
    838      * @return The value that were previously set, or null if there's none.
    839      *
    840      * @hide
    841      */
    842     private Long getLong(int userId, int uid, String key) {
    843         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
    844 
    845         String[] projection = {
    846                 RecoveryServiceMetadataEntry._ID,
    847                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
    848                 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
    849                 key};
    850         String selection =
    851                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
    852                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
    853         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
    854 
    855         try (
    856             Cursor cursor = db.query(
    857                     RecoveryServiceMetadataEntry.TABLE_NAME,
    858                     projection,
    859                     selection,
    860                     selectionArguments,
    861                     /*groupBy=*/ null,
    862                     /*having=*/ null,
    863                     /*orderBy=*/ null)
    864         ) {
    865             int count = cursor.getCount();
    866             if (count == 0) {
    867                 return null;
    868             }
    869             if (count > 1) {
    870                 Log.wtf(TAG,
    871                         String.format(Locale.US,
    872                                 "%d entries found for userId=%d uid=%d. "
    873                                         + "Should only ever be 0 or 1.", count, userId, uid));
    874                 return null;
    875             }
    876             cursor.moveToFirst();
    877             int idx = cursor.getColumnIndexOrThrow(key);
    878             if (cursor.isNull(idx)) {
    879                 return null;
    880             } else {
    881                 return cursor.getLong(idx);
    882             }
    883         }
    884     }
    885 
    886     /**
    887      * Sets a long value in the database.
    888      *
    889      * @param userId The userId of the profile the application is running under.
    890      * @param uid The uid of the application who initialized the local recovery components.
    891      * @param key defined in {@code RecoveryServiceMetadataEntry}
    892      * @param value new value.
    893      * @return The primary key of the inserted row, or -1 if failed.
    894      *
    895      * @hide
    896      */
    897 
    898     private long setLong(int userId, int uid, String key, long value) {
    899         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
    900         ContentValues values = new ContentValues();
    901         values.put(key, value);
    902         String selection =
    903                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
    904                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
    905         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
    906 
    907         ensureRecoveryServiceMetadataEntryExists(userId, uid);
    908         return db.update(
    909                 RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
    910     }
    911 
    912     /**
    913      * Returns given binary value from the database.
    914      *
    915      * @param userId The userId of the profile the application is running under.
    916      * @param uid The uid of the application who initialized the local recovery components.
    917      * @param key from {@code RecoveryServiceMetadataEntry}
    918      * @return The value that were previously set, or null if there's none.
    919      *
    920      * @hide
    921      */
    922     private byte[] getBytes(int userId, int uid, String key) {
    923         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
    924 
    925         String[] projection = {
    926                 RecoveryServiceMetadataEntry._ID,
    927                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
    928                 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
    929                 key};
    930         String selection =
    931                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
    932                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
    933         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
    934 
    935         try (
    936             Cursor cursor = db.query(
    937                     RecoveryServiceMetadataEntry.TABLE_NAME,
    938                     projection,
    939                     selection,
    940                     selectionArguments,
    941                     /*groupBy=*/ null,
    942                     /*having=*/ null,
    943                     /*orderBy=*/ null)
    944         ) {
    945             int count = cursor.getCount();
    946             if (count == 0) {
    947                 return null;
    948             }
    949             if (count > 1) {
    950                 Log.wtf(TAG,
    951                         String.format(Locale.US,
    952                                 "%d entries found for userId=%d uid=%d. "
    953                                         + "Should only ever be 0 or 1.", count, userId, uid));
    954                 return null;
    955             }
    956             cursor.moveToFirst();
    957             int idx = cursor.getColumnIndexOrThrow(key);
    958             if (cursor.isNull(idx)) {
    959                 return null;
    960             } else {
    961                 return cursor.getBlob(idx);
    962             }
    963         }
    964     }
    965 
    966     /**
    967      * Sets a binary value in the database.
    968      *
    969      * @param userId The userId of the profile the application is running under.
    970      * @param uid The uid of the application who initialized the local recovery components.
    971      * @param key defined in {@code RecoveryServiceMetadataEntry}
    972      * @param value new value.
    973      * @return The primary key of the inserted row, or -1 if failed.
    974      *
    975      * @hide
    976      */
    977     private long setBytes(int userId, int uid, String key, byte[] value) {
    978         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
    979         ContentValues values = new ContentValues();
    980         values.put(key, value);
    981         String selection =
    982                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
    983                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
    984         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
    985 
    986         ensureRecoveryServiceMetadataEntryExists(userId, uid);
    987         return db.update(
    988                 RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
    989     }
    990 
    991     /**
    992      * Returns given binary value from the database.
    993      *
    994      * @param userId The userId of the profile the application is running under.
    995      * @param uid The uid of the application who initialized the local recovery components.
    996      * @param rootAlias The root of trust alias.
    997      * @param key from {@code RootOfTrustEntry}
    998      * @return The value that were previously set, or null if there's none.
    999      *
   1000      * @hide
   1001      */
   1002     private byte[] getBytes(int userId, int uid, String rootAlias, String key) {
   1003         rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
   1004         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
   1005 
   1006         String[] projection = {
   1007                 RootOfTrustEntry._ID,
   1008                 RootOfTrustEntry.COLUMN_NAME_USER_ID,
   1009                 RootOfTrustEntry.COLUMN_NAME_UID,
   1010                 RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS,
   1011                 key};
   1012         String selection =
   1013                 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
   1014                         + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
   1015                         + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
   1016         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
   1017 
   1018         try (
   1019             Cursor cursor = db.query(
   1020                     RootOfTrustEntry.TABLE_NAME,
   1021                     projection,
   1022                     selection,
   1023                     selectionArguments,
   1024                     /*groupBy=*/ null,
   1025                     /*having=*/ null,
   1026                     /*orderBy=*/ null)
   1027         ) {
   1028             int count = cursor.getCount();
   1029             if (count == 0) {
   1030                 return null;
   1031             }
   1032             if (count > 1) {
   1033                 Log.wtf(TAG,
   1034                         String.format(Locale.US,
   1035                                 "%d entries found for userId=%d uid=%d. "
   1036                                         + "Should only ever be 0 or 1.", count, userId, uid));
   1037                 return null;
   1038             }
   1039             cursor.moveToFirst();
   1040             int idx = cursor.getColumnIndexOrThrow(key);
   1041             if (cursor.isNull(idx)) {
   1042                 return null;
   1043             } else {
   1044                 return cursor.getBlob(idx);
   1045             }
   1046         }
   1047     }
   1048 
   1049     /**
   1050      * Sets a binary value in the database.
   1051      *
   1052      * @param userId The userId of the profile the application is running under.
   1053      * @param uid The uid of the application who initialized the local recovery components.
   1054      * @param rootAlias The root of trust alias.
   1055      * @param key defined in {@code RootOfTrustEntry}
   1056      * @param value new value.
   1057      * @return The primary key of the inserted row, or -1 if failed.
   1058      *
   1059      * @hide
   1060      */
   1061     private long setBytes(int userId, int uid, String rootAlias, String key, byte[] value) {
   1062         rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
   1063         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
   1064         ContentValues values = new ContentValues();
   1065         values.put(key, value);
   1066         String selection =
   1067                 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
   1068                         + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
   1069                         + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
   1070         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
   1071 
   1072         ensureRootOfTrustEntryExists(userId, uid, rootAlias);
   1073         return db.update(
   1074                 RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments);
   1075     }
   1076 
   1077     /**
   1078      * Returns given long value from the database.
   1079      *
   1080      * @param userId The userId of the profile the application is running under.
   1081      * @param uid The uid of the application who initialized the local recovery components.
   1082      * @param rootAlias The root of trust alias.
   1083      * @param key from {@code RootOfTrustEntry}
   1084      * @return The value that were previously set, or null if there's none.
   1085      *
   1086      * @hide
   1087      */
   1088     private Long getLong(int userId, int uid, String rootAlias, String key) {
   1089         rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
   1090         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
   1091 
   1092         String[] projection = {
   1093                 RootOfTrustEntry._ID,
   1094                 RootOfTrustEntry.COLUMN_NAME_USER_ID,
   1095                 RootOfTrustEntry.COLUMN_NAME_UID,
   1096                 RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS,
   1097                 key};
   1098         String selection =
   1099                 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
   1100                         + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
   1101                         + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
   1102         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
   1103 
   1104         try (
   1105             Cursor cursor = db.query(
   1106                     RootOfTrustEntry.TABLE_NAME,
   1107                     projection,
   1108                     selection,
   1109                     selectionArguments,
   1110                     /*groupBy=*/ null,
   1111                     /*having=*/ null,
   1112                     /*orderBy=*/ null)
   1113         ) {
   1114             int count = cursor.getCount();
   1115             if (count == 0) {
   1116                 return null;
   1117             }
   1118             if (count > 1) {
   1119                 Log.wtf(TAG,
   1120                         String.format(Locale.US,
   1121                                 "%d entries found for userId=%d uid=%d. "
   1122                                         + "Should only ever be 0 or 1.", count, userId, uid));
   1123                 return null;
   1124             }
   1125             cursor.moveToFirst();
   1126             int idx = cursor.getColumnIndexOrThrow(key);
   1127             if (cursor.isNull(idx)) {
   1128                 return null;
   1129             } else {
   1130                 return cursor.getLong(idx);
   1131             }
   1132         }
   1133     }
   1134 
   1135     /**
   1136      * Sets a long value in the database.
   1137      *
   1138      * @param userId The userId of the profile the application is running under.
   1139      * @param uid The uid of the application who initialized the local recovery components.
   1140      * @param rootAlias The root of trust alias.
   1141      * @param key defined in {@code RootOfTrustEntry}
   1142      * @param value new value.
   1143      * @return The primary key of the inserted row, or -1 if failed.
   1144      *
   1145      * @hide
   1146      */
   1147 
   1148     private long setLong(int userId, int uid, String rootAlias, String key, long value) {
   1149         rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
   1150         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
   1151         ContentValues values = new ContentValues();
   1152         values.put(key, value);
   1153         String selection =
   1154                 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
   1155                         + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
   1156                         + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
   1157         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
   1158 
   1159         ensureRootOfTrustEntryExists(userId, uid, rootAlias);
   1160         return db.update(
   1161                 RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments);
   1162     }
   1163 
   1164 
   1165     /**
   1166      * Creates an empty row in the recovery service metadata table if such a row doesn't exist for
   1167      * the given userId and uid, so db.update will succeed.
   1168      */
   1169     private void ensureRecoveryServiceMetadataEntryExists(int userId, int uid) {
   1170         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
   1171         ContentValues values = new ContentValues();
   1172         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, userId);
   1173         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, uid);
   1174         db.insertWithOnConflict(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
   1175                 values, SQLiteDatabase.CONFLICT_IGNORE);
   1176     }
   1177 
   1178     /**
   1179      * Creates an empty row in the root of trust table if such a row doesn't exist for
   1180      * the given userId and uid, so db.update will succeed.
   1181      */
   1182     private void ensureRootOfTrustEntryExists(int userId, int uid, String rootAlias) {
   1183         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
   1184         ContentValues values = new ContentValues();
   1185         values.put(RootOfTrustEntry.COLUMN_NAME_USER_ID, userId);
   1186         values.put(RootOfTrustEntry.COLUMN_NAME_UID, uid);
   1187         values.put(RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, rootAlias);
   1188         db.insertWithOnConflict(RootOfTrustEntry.TABLE_NAME, /*nullColumnHack=*/ null,
   1189                 values, SQLiteDatabase.CONFLICT_IGNORE);
   1190     }
   1191 
   1192     /**
   1193      * Closes all open connections to the database.
   1194      */
   1195     public void close() {
   1196         mKeyStoreDbHelper.close();
   1197     }
   1198 
   1199     @Nullable
   1200     private static PublicKey decodeX509Key(byte[] keyBytes) throws InvalidKeySpecException {
   1201         X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes);
   1202         try {
   1203             return KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
   1204         } catch (NoSuchAlgorithmException e) {
   1205             // Should never happen
   1206             throw new RuntimeException(e);
   1207         }
   1208     }
   1209 
   1210     @Nullable
   1211     private static CertPath decodeCertPath(byte[] bytes) throws CertificateException {
   1212         CertificateFactory certFactory;
   1213         try {
   1214             certFactory = CertificateFactory.getInstance("X.509");
   1215         } catch (CertificateException e) {
   1216             // Should not happen, as X.509 is mandatory for all providers.
   1217             throw new RuntimeException(e);
   1218         }
   1219         return certFactory.generateCertPath(new ByteArrayInputStream(bytes), CERT_PATH_ENCODING);
   1220     }
   1221 }
   1222