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