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     /**
396      * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
397      */
invalidateKeysForUser(int userId)398     public void invalidateKeysForUser(int userId) {
399         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
400         ContentValues values = new ContentValues();
401         values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS,
402                 RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
403         String selection = KeysEntry.COLUMN_NAME_USER_ID + " = ?";
404         db.update(KeysEntry.TABLE_NAME, values, selection, new String[] {String.valueOf(userId)});
405     }
406 
407     /**
408      * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
409      */
invalidateKeysForUserIdOnCustomScreenLock(int userId)410     public void invalidateKeysForUserIdOnCustomScreenLock(int userId) {
411         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
412         ContentValues values = new ContentValues();
413         values.put(KeysEntry.COLUMN_NAME_RECOVERY_STATUS,
414             RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
415         String selection =
416             KeysEntry.COLUMN_NAME_USER_ID + " = ?";
417         db.update(KeysEntry.TABLE_NAME, values, selection,
418             new String[] {String.valueOf(userId)});
419     }
420 
421     /**
422      * Returns the generation ID associated with the platform key of the user with {@code userId}.
423      */
getPlatformKeyGenerationId(int userId)424     public int getPlatformKeyGenerationId(int userId) {
425         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
426         String[] projection = {
427                 UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID};
428         String selection =
429                 UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
430         String[] selectionArguments = {
431                 Integer.toString(userId)};
432 
433         try (
434             Cursor cursor = db.query(
435                 UserMetadataEntry.TABLE_NAME,
436                 projection,
437                 selection,
438                 selectionArguments,
439                 /*groupBy=*/ null,
440                 /*having=*/ null,
441                 /*orderBy=*/ null)
442         ) {
443             if (cursor.getCount() == 0) {
444                 return -1;
445             }
446             cursor.moveToFirst();
447             return cursor.getInt(
448                     cursor.getColumnIndexOrThrow(
449                             UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID));
450         }
451     }
452 
453     /**
454      * Updates the public key of the recovery service into the database.
455      *
456      * @param userId The uid of the profile the application is running under.
457      * @param uid The uid of the application to whom the key belongs.
458      * @param publicKey The public key of the recovery service.
459      * @return The primary key of the inserted row, or -1 if failed.
460      *
461      * @hide
462      */
setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey)463     public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) {
464         return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY,
465                 publicKey.getEncoded());
466     }
467 
468     /**
469      * Returns the serial number of the XML file containing certificates of the recovery service.
470      *
471      * @param userId The userId of the profile the application is running under.
472      * @param uid The uid of the application who initializes the local recovery components.
473      * @param rootAlias The root of trust alias.
474      * @return The value that were previously set, or null if there's none.
475      *
476      * @hide
477      */
478     @Nullable
getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias)479     public Long getRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias) {
480         return getLong(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL);
481     }
482 
483     /**
484      * Records the serial number of the XML file containing certificates of the recovery service.
485      *
486      * @param userId The userId of the profile the application is running under.
487      * @param uid The uid of the application who initializes the local recovery components.
488      * @param rootAlias The root of trust alias.
489      * @param serial The serial number contained in the XML file for recovery service certificates.
490      * @return The primary key of the inserted row, or -1 if failed.
491      *
492      * @hide
493      */
setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias, long serial)494     public long setRecoveryServiceCertSerial(int userId, int uid, @NonNull String rootAlias,
495             long serial) {
496         return setLong(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_SERIAL,
497                 serial);
498     }
499 
500     /**
501      * Returns the {@code CertPath} of the recovery service.
502      *
503      * @param userId The userId of the profile the application is running under.
504      * @param uid The uid of the application who initializes the local recovery components.
505      * @param rootAlias The root of trust alias.
506      * @return The value that were previously set, or null if there's none.
507      *
508      * @hide
509      */
510     @Nullable
getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias)511     public CertPath getRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias) {
512         byte[] bytes = getBytes(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_PATH);
513         if (bytes == null) {
514             return null;
515         }
516         try {
517             return decodeCertPath(bytes);
518         } catch (CertificateException e) {
519             Log.wtf(TAG,
520                     String.format(Locale.US,
521                             "Recovery service CertPath entry cannot be decoded for "
522                                     + "userId=%d uid=%d.",
523                             userId, uid), e);
524             return null;
525         }
526     }
527 
528     /**
529      * Sets the {@code CertPath} of the recovery service.
530      *
531      * @param userId The userId of the profile the application is running under.
532      * @param uid The uid of the application who initializes the local recovery components.
533      * @param rootAlias The root of trust alias.
534      * @param certPath The certificate path of the recovery service.
535      * @return The primary key of the inserted row, or -1 if failed.
536      * @hide
537      */
setRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias, CertPath certPath)538     public long setRecoveryServiceCertPath(int userId, int uid, @NonNull String rootAlias,
539             CertPath certPath) throws CertificateEncodingException {
540         if (certPath.getCertificates().size() == 0) {
541             throw new CertificateEncodingException("No certificate contained in the cert path.");
542         }
543         return setBytes(userId, uid, rootAlias, RootOfTrustEntry.COLUMN_NAME_CERT_PATH,
544                 certPath.getEncoded(CERT_PATH_ENCODING));
545     }
546 
547     /**
548      * Returns the list of recovery agents initialized for given {@code userId}
549      * @param userId The userId of the profile the application is running under.
550      * @return The list of recovery agents
551      * @hide
552      */
getRecoveryAgents(int userId)553     public @NonNull List<Integer> getRecoveryAgents(int userId) {
554         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
555 
556         String[] projection = { RecoveryServiceMetadataEntry.COLUMN_NAME_UID };
557         String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
558         String[] selectionArguments = { Integer.toString(userId) };
559 
560         try (
561             Cursor cursor = db.query(
562                     RecoveryServiceMetadataEntry.TABLE_NAME,
563                     projection,
564                     selection,
565                     selectionArguments,
566                     /*groupBy=*/ null,
567                     /*having=*/ null,
568                     /*orderBy=*/ null)
569         ) {
570             int count = cursor.getCount();
571             ArrayList<Integer> result = new ArrayList<>(count);
572             while (cursor.moveToNext()) {
573                 int uid = cursor.getInt(
574                         cursor.getColumnIndexOrThrow(RecoveryServiceMetadataEntry.COLUMN_NAME_UID));
575                 result.add(uid);
576             }
577             return result;
578         }
579     }
580 
581     /**
582      * Returns the public key of the recovery service.
583      *
584      * @param userId The userId of the profile the application is running under.
585      * @param uid The uid of the application who initializes the local recovery components.
586      *
587      * @hide
588      */
589     @Nullable
getRecoveryServicePublicKey(int userId, int uid)590     public PublicKey getRecoveryServicePublicKey(int userId, int uid) {
591         byte[] keyBytes =
592                 getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
593         if (keyBytes == null) {
594             return null;
595         }
596         try {
597             return decodeX509Key(keyBytes);
598         } catch (InvalidKeySpecException e) {
599             Log.wtf(TAG,
600                     String.format(Locale.US,
601                             "Recovery service public key entry cannot be decoded for "
602                                     + "userId=%d uid=%d.",
603                             userId, uid));
604             return null;
605         }
606     }
607 
608     /**
609      * Updates the list of user secret types used for end-to-end encryption.
610      * If no secret types are set, recovery snapshot will not be created.
611      * See {@code KeyChainProtectionParams}
612      *
613      * @param userId The userId of the profile the application is running under.
614      * @param uid The uid of the application.
615      * @param secretTypes list of secret types
616      * @return The primary key of the updated row, or -1 if failed.
617      *
618      * @hide
619      */
setRecoverySecretTypes(int userId, int uid, int[] secretTypes)620     public long setRecoverySecretTypes(int userId, int uid, int[] secretTypes) {
621         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
622         ContentValues values = new ContentValues();
623         StringJoiner joiner = new StringJoiner(",");
624         Arrays.stream(secretTypes).forEach(i -> joiner.add(Integer.toString(i)));
625         String typesAsCsv = joiner.toString();
626         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES, typesAsCsv);
627         String selection =
628                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
629                 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
630         ensureRecoveryServiceMetadataEntryExists(userId, uid);
631         return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values, selection,
632             new String[] {String.valueOf(userId), String.valueOf(uid)});
633     }
634 
635     /**
636      * Returns the list of secret types used for end-to-end encryption.
637      *
638      * @param userId The userId of the profile the application is running under.
639      * @param uid The uid of the application who initialized the local recovery components.
640      * @return Secret types or empty array, if types were not set.
641      *
642      * @hide
643      */
getRecoverySecretTypes(int userId, int uid)644     public @NonNull int[] getRecoverySecretTypes(int userId, int uid) {
645         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
646 
647         String[] projection = {
648                 RecoveryServiceMetadataEntry._ID,
649                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
650                 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
651                 RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES};
652         String selection =
653                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
654                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
655         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
656 
657         try (
658                 Cursor cursor = db.query(
659                         RecoveryServiceMetadataEntry.TABLE_NAME,
660                         projection,
661                         selection,
662                         selectionArguments,
663                         /*groupBy=*/ null,
664                         /*having=*/ null,
665                         /*orderBy=*/ null)
666         ) {
667             int count = cursor.getCount();
668             if (count == 0) {
669                 return new int[]{};
670             }
671             if (count > 1) {
672                 Log.wtf(TAG,
673                         String.format(Locale.US,
674                                 "%d deviceId entries found for userId=%d uid=%d. "
675                                         + "Should only ever be 0 or 1.", count, userId, uid));
676                 return new int[]{};
677             }
678             cursor.moveToFirst();
679             int idx = cursor.getColumnIndexOrThrow(
680                     RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES);
681             if (cursor.isNull(idx)) {
682                 return new int[]{};
683             }
684             String csv = cursor.getString(idx);
685             if (TextUtils.isEmpty(csv)) {
686                 return new int[]{};
687             }
688             String[] types = csv.split(",");
689             int[] result = new int[types.length];
690             for (int i = 0; i < types.length; i++) {
691                 try {
692                     result[i] = Integer.parseInt(types[i]);
693                 } catch (NumberFormatException e) {
694                     Log.wtf(TAG, "String format error " + e);
695                 }
696             }
697             return result;
698         }
699     }
700 
701     /**
702      * Active root of trust for the recovery agent.
703      *
704      * @param userId The userId of the profile the application is running under.
705      * @param uid The uid of the application.
706      * @param rootAlias The root of trust alias.
707      * @return The primary key of the updated row, or -1 if failed.
708      *
709      * @hide
710      */
setActiveRootOfTrust(int userId, int uid, @Nullable String rootAlias)711     public long setActiveRootOfTrust(int userId, int uid, @Nullable String rootAlias) {
712         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
713         ContentValues values = new ContentValues();
714         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST, rootAlias);
715         String selection =
716                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
717                 + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
718         ensureRecoveryServiceMetadataEntryExists(userId, uid);
719         return db.update(RecoveryServiceMetadataEntry.TABLE_NAME, values,
720             selection, new String[] {String.valueOf(userId), String.valueOf(uid)});
721     }
722 
723     /**
724      * Active root of trust for the recovery agent.
725      *
726      * @param userId The userId of the profile the application is running under.
727      * @param uid The uid of the application who initialized the local recovery components.
728      * @return Active root of trust alias of null if it was not set
729      *
730      * @hide
731      */
getActiveRootOfTrust(int userId, int uid)732     public @Nullable String getActiveRootOfTrust(int userId, int uid) {
733         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
734 
735         String[] projection = {
736                 RecoveryServiceMetadataEntry._ID,
737                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
738                 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
739                 RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST};
740         String selection =
741                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
742                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
743         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
744 
745         try (
746                 Cursor cursor = db.query(
747                         RecoveryServiceMetadataEntry.TABLE_NAME,
748                         projection,
749                         selection,
750                         selectionArguments,
751                         /*groupBy=*/ null,
752                         /*having=*/ null,
753                         /*orderBy=*/ null)
754         ) {
755             int count = cursor.getCount();
756             if (count == 0) {
757                 return null;
758             }
759             if (count > 1) {
760                 Log.wtf(TAG,
761                         String.format(Locale.US,
762                                 "%d deviceId entries found for userId=%d uid=%d. "
763                                         + "Should only ever be 0 or 1.", count, userId, uid));
764                 return null;
765             }
766             cursor.moveToFirst();
767             int idx = cursor.getColumnIndexOrThrow(
768                     RecoveryServiceMetadataEntry.COLUMN_NAME_ACTIVE_ROOT_OF_TRUST);
769             if (cursor.isNull(idx)) {
770                 return null;
771             }
772             String result = cursor.getString(idx);
773             if (TextUtils.isEmpty(result)) {
774                 return null;
775             }
776             return result;
777         }
778     }
779 
780     /**
781      * Updates the counterId
782      *
783      * @param userId The userId of the profile the application is running under.
784      * @param uid The uid of the application.
785      * @param counterId The counterId.
786      * @return The primary key of the inserted row, or -1 if failed.
787      *
788      * @hide
789      */
setCounterId(int userId, int uid, long counterId)790     public long setCounterId(int userId, int uid, long counterId) {
791         return setLong(userId, uid,
792                 RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID, counterId);
793     }
794 
795     /**
796      * Returns the counter id.
797      *
798      * @param userId The userId of the profile the application is running under.
799      * @param uid The uid of the application who initialized the local recovery components.
800      * @return The counter id
801      *
802      * @hide
803      */
804     @Nullable
getCounterId(int userId, int uid)805     public Long getCounterId(int userId, int uid) {
806         return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID);
807     }
808 
809     /**
810      * Updates the server parameters given by the application initializing the local recovery
811      * components.
812      *
813      * @param userId The userId of the profile the application is running under.
814      * @param uid The uid of the application.
815      * @param serverParams The server parameters.
816      * @return The primary key of the inserted row, or -1 if failed.
817      *
818      * @hide
819      */
setServerParams(int userId, int uid, byte[] serverParams)820     public long setServerParams(int userId, int uid, byte[] serverParams) {
821         return setBytes(userId, uid,
822                 RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, serverParams);
823     }
824 
825     /**
826      * Returns the server paramters that was previously set by the application who initialized the
827      * local recovery service components.
828      *
829      * @param userId The userId of the profile the application is running under.
830      * @param uid The uid of the application who initialized the local recovery components.
831      * @return The server parameters that were previously set, or null if there's none.
832      *
833      * @hide
834      */
835     @Nullable
getServerParams(int userId, int uid)836     public byte[] getServerParams(int userId, int uid) {
837         return getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS);
838     }
839 
840     /**
841      * Updates the snapshot version.
842      *
843      * @param userId The userId of the profile the application is running under.
844      * @param uid The uid of the application.
845      * @param snapshotVersion The snapshot version
846      * @return The primary key of the inserted row, or -1 if failed.
847      *
848      * @hide
849      */
setSnapshotVersion(int userId, int uid, long snapshotVersion)850     public long setSnapshotVersion(int userId, int uid, long snapshotVersion) {
851         return setLong(userId, uid,
852                 RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION, snapshotVersion);
853     }
854 
855     /**
856      * Returns the snapshot version
857      *
858      * @param userId The userId of the profile the application is running under.
859      * @param uid The uid of the application who initialized the local recovery components.
860      * @return The server parameters that were previously set, or null if there's none.
861      *
862      * @hide
863      */
864     @Nullable
getSnapshotVersion(int userId, int uid)865     public Long getSnapshotVersion(int userId, int uid) {
866         return getLong(userId, uid,
867             RecoveryServiceMetadataEntry.COLUMN_NAME_SNAPSHOT_VERSION);
868     }
869 
870     /**
871      * Updates a flag indicating that a new snapshot should be created.
872      * It will be {@code false} until the first application key is added.
873      * After that, the flag will be set to true, if one of the following values is updated:
874      * <ul>
875      *     <li> List of application keys
876      *     <li> Server params.
877      *     <li> Lock-screen secret.
878      *     <li> Lock-screen secret type.
879      *     <li> Trusted hardware certificate.
880      * </ul>
881      *
882      * @param userId The userId of the profile the application is running under.
883      * @param uid The uid of the application.
884      * @param pending Should create snapshot flag.
885      * @return The primary key of the inserted row, or -1 if failed.
886      *
887      * @hide
888      */
setShouldCreateSnapshot(int userId, int uid, boolean pending)889     public long setShouldCreateSnapshot(int userId, int uid, boolean pending) {
890         return setLong(userId, uid,
891                 RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT, pending ? 1 : 0);
892     }
893 
894     /**
895      * Returns {@code true} if new snapshot should be created.
896      * Returns {@code false} if the flag was never set.
897      *
898      * @param userId The userId of the profile the application is running under.
899      * @param uid The uid of the application who initialized the local recovery components.
900      * @return should create snapshot flag
901      *
902      * @hide
903      */
getShouldCreateSnapshot(int userId, int uid)904     public boolean getShouldCreateSnapshot(int userId, int uid) {
905         Long res = getLong(userId, uid,
906                 RecoveryServiceMetadataEntry.COLUMN_NAME_SHOULD_CREATE_SNAPSHOT);
907         return res != null && res != 0L;
908     }
909 
910 
911     /**
912      * Returns given long value from the database.
913      *
914      * @param userId The userId of the profile the application is running under.
915      * @param uid The uid of the application who initialized the local recovery components.
916      * @param key from {@code RecoveryServiceMetadataEntry}
917      * @return The value that were previously set, or null if there's none.
918      *
919      * @hide
920      */
getLong(int userId, int uid, String key)921     private Long getLong(int userId, int uid, String key) {
922         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
923 
924         String[] projection = {
925                 RecoveryServiceMetadataEntry._ID,
926                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
927                 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
928                 key};
929         String selection =
930                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
931                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
932         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
933 
934         try (
935             Cursor cursor = db.query(
936                     RecoveryServiceMetadataEntry.TABLE_NAME,
937                     projection,
938                     selection,
939                     selectionArguments,
940                     /*groupBy=*/ null,
941                     /*having=*/ null,
942                     /*orderBy=*/ null)
943         ) {
944             int count = cursor.getCount();
945             if (count == 0) {
946                 return null;
947             }
948             if (count > 1) {
949                 Log.wtf(TAG,
950                         String.format(Locale.US,
951                                 "%d entries found for userId=%d uid=%d. "
952                                         + "Should only ever be 0 or 1.", count, userId, uid));
953                 return null;
954             }
955             cursor.moveToFirst();
956             int idx = cursor.getColumnIndexOrThrow(key);
957             if (cursor.isNull(idx)) {
958                 return null;
959             } else {
960                 return cursor.getLong(idx);
961             }
962         }
963     }
964 
965     /**
966      * Sets a long value in the database.
967      *
968      * @param userId The userId of the profile the application is running under.
969      * @param uid The uid of the application who initialized the local recovery components.
970      * @param key defined in {@code RecoveryServiceMetadataEntry}
971      * @param value new value.
972      * @return The primary key of the inserted row, or -1 if failed.
973      *
974      * @hide
975      */
976 
setLong(int userId, int uid, String key, long value)977     private long setLong(int userId, int uid, String key, long 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 key from {@code RecoveryServiceMetadataEntry}
997      * @return The value that were previously set, or null if there's none.
998      *
999      * @hide
1000      */
getBytes(int userId, int uid, String key)1001     private byte[] getBytes(int userId, int uid, String key) {
1002         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
1003 
1004         String[] projection = {
1005                 RecoveryServiceMetadataEntry._ID,
1006                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
1007                 RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
1008                 key};
1009         String selection =
1010                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
1011                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
1012         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
1013 
1014         try (
1015             Cursor cursor = db.query(
1016                     RecoveryServiceMetadataEntry.TABLE_NAME,
1017                     projection,
1018                     selection,
1019                     selectionArguments,
1020                     /*groupBy=*/ null,
1021                     /*having=*/ null,
1022                     /*orderBy=*/ null)
1023         ) {
1024             int count = cursor.getCount();
1025             if (count == 0) {
1026                 return null;
1027             }
1028             if (count > 1) {
1029                 Log.wtf(TAG,
1030                         String.format(Locale.US,
1031                                 "%d entries found for userId=%d uid=%d. "
1032                                         + "Should only ever be 0 or 1.", count, userId, uid));
1033                 return null;
1034             }
1035             cursor.moveToFirst();
1036             int idx = cursor.getColumnIndexOrThrow(key);
1037             if (cursor.isNull(idx)) {
1038                 return null;
1039             } else {
1040                 return cursor.getBlob(idx);
1041             }
1042         }
1043     }
1044 
1045     /**
1046      * Sets a binary value in the database.
1047      *
1048      * @param userId The userId of the profile the application is running under.
1049      * @param uid The uid of the application who initialized the local recovery components.
1050      * @param key defined in {@code RecoveryServiceMetadataEntry}
1051      * @param value new value.
1052      * @return The primary key of the inserted row, or -1 if failed.
1053      *
1054      * @hide
1055      */
setBytes(int userId, int uid, String key, byte[] value)1056     private long setBytes(int userId, int uid, String key, byte[] value) {
1057         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1058         ContentValues values = new ContentValues();
1059         values.put(key, value);
1060         String selection =
1061                 RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
1062                         + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
1063         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
1064 
1065         ensureRecoveryServiceMetadataEntryExists(userId, uid);
1066         return db.update(
1067                 RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
1068     }
1069 
1070     /**
1071      * Returns given binary value from the database.
1072      *
1073      * @param userId The userId of the profile the application is running under.
1074      * @param uid The uid of the application who initialized the local recovery components.
1075      * @param rootAlias The root of trust alias.
1076      * @param key from {@code RootOfTrustEntry}
1077      * @return The value that were previously set, or null if there's none.
1078      *
1079      * @hide
1080      */
getBytes(int userId, int uid, String rootAlias, String key)1081     private byte[] getBytes(int userId, int uid, String rootAlias, String key) {
1082         rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
1083         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
1084 
1085         String[] projection = {
1086                 RootOfTrustEntry._ID,
1087                 RootOfTrustEntry.COLUMN_NAME_USER_ID,
1088                 RootOfTrustEntry.COLUMN_NAME_UID,
1089                 RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS,
1090                 key};
1091         String selection =
1092                 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
1093                         + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
1094                         + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
1095         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
1096 
1097         try (
1098             Cursor cursor = db.query(
1099                     RootOfTrustEntry.TABLE_NAME,
1100                     projection,
1101                     selection,
1102                     selectionArguments,
1103                     /*groupBy=*/ null,
1104                     /*having=*/ null,
1105                     /*orderBy=*/ null)
1106         ) {
1107             int count = cursor.getCount();
1108             if (count == 0) {
1109                 return null;
1110             }
1111             if (count > 1) {
1112                 Log.wtf(TAG,
1113                         String.format(Locale.US,
1114                                 "%d entries found for userId=%d uid=%d. "
1115                                         + "Should only ever be 0 or 1.", count, userId, uid));
1116                 return null;
1117             }
1118             cursor.moveToFirst();
1119             int idx = cursor.getColumnIndexOrThrow(key);
1120             if (cursor.isNull(idx)) {
1121                 return null;
1122             } else {
1123                 return cursor.getBlob(idx);
1124             }
1125         }
1126     }
1127 
1128     /**
1129      * Sets a binary value in the database.
1130      *
1131      * @param userId The userId of the profile the application is running under.
1132      * @param uid The uid of the application who initialized the local recovery components.
1133      * @param rootAlias The root of trust alias.
1134      * @param key defined in {@code RootOfTrustEntry}
1135      * @param value new value.
1136      * @return The primary key of the inserted row, or -1 if failed.
1137      *
1138      * @hide
1139      */
setBytes(int userId, int uid, String rootAlias, String key, byte[] value)1140     private long setBytes(int userId, int uid, String rootAlias, String key, byte[] value) {
1141         rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
1142         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1143         ContentValues values = new ContentValues();
1144         values.put(key, value);
1145         String selection =
1146                 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
1147                         + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
1148                         + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
1149         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
1150 
1151         ensureRootOfTrustEntryExists(userId, uid, rootAlias);
1152         return db.update(
1153                 RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments);
1154     }
1155 
1156     /**
1157      * Returns given long value from the database.
1158      *
1159      * @param userId The userId of the profile the application is running under.
1160      * @param uid The uid of the application who initialized the local recovery components.
1161      * @param rootAlias The root of trust alias.
1162      * @param key from {@code RootOfTrustEntry}
1163      * @return The value that were previously set, or null if there's none.
1164      *
1165      * @hide
1166      */
getLong(int userId, int uid, String rootAlias, String key)1167     private Long getLong(int userId, int uid, String rootAlias, String key) {
1168         rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
1169         SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
1170 
1171         String[] projection = {
1172                 RootOfTrustEntry._ID,
1173                 RootOfTrustEntry.COLUMN_NAME_USER_ID,
1174                 RootOfTrustEntry.COLUMN_NAME_UID,
1175                 RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS,
1176                 key};
1177         String selection =
1178                 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
1179                         + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
1180                         + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
1181         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
1182 
1183         try (
1184             Cursor cursor = db.query(
1185                     RootOfTrustEntry.TABLE_NAME,
1186                     projection,
1187                     selection,
1188                     selectionArguments,
1189                     /*groupBy=*/ null,
1190                     /*having=*/ null,
1191                     /*orderBy=*/ null)
1192         ) {
1193             int count = cursor.getCount();
1194             if (count == 0) {
1195                 return null;
1196             }
1197             if (count > 1) {
1198                 Log.wtf(TAG,
1199                         String.format(Locale.US,
1200                                 "%d entries found for userId=%d uid=%d. "
1201                                         + "Should only ever be 0 or 1.", count, userId, uid));
1202                 return null;
1203             }
1204             cursor.moveToFirst();
1205             int idx = cursor.getColumnIndexOrThrow(key);
1206             if (cursor.isNull(idx)) {
1207                 return null;
1208             } else {
1209                 return cursor.getLong(idx);
1210             }
1211         }
1212     }
1213 
1214     /**
1215      * Sets a long value in the database.
1216      *
1217      * @param userId The userId of the profile the application is running under.
1218      * @param uid The uid of the application who initialized the local recovery components.
1219      * @param rootAlias The root of trust alias.
1220      * @param key defined in {@code RootOfTrustEntry}
1221      * @param value new value.
1222      * @return The primary key of the inserted row, or -1 if failed.
1223      *
1224      * @hide
1225      */
1226 
setLong(int userId, int uid, String rootAlias, String key, long value)1227     private long setLong(int userId, int uid, String rootAlias, String key, long value) {
1228         rootAlias = mTestOnlyInsecureCertificateHelper.getDefaultCertificateAliasIfEmpty(rootAlias);
1229         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1230         ContentValues values = new ContentValues();
1231         values.put(key, value);
1232         String selection =
1233                 RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ? AND "
1234                         + RootOfTrustEntry.COLUMN_NAME_UID + " = ? AND "
1235                         + RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS + " = ?";
1236         String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid), rootAlias};
1237 
1238         ensureRootOfTrustEntryExists(userId, uid, rootAlias);
1239         return db.update(
1240                 RootOfTrustEntry.TABLE_NAME, values, selection, selectionArguments);
1241     }
1242 
1243     /**
1244      * Removes all entries for given {@code userId}.
1245      */
removeUserFromAllTables(int userId)1246     public void removeUserFromAllTables(int userId) {
1247         removeUserFromKeysTable(userId);
1248         removeUserFromUserMetadataTable(userId);
1249         removeUserFromRecoveryServiceMetadataTable(userId);
1250         removeUserFromRootOfTrustTable(userId);
1251     }
1252 
1253     /**
1254      * Removes all entries for given userId from Keys table.
1255      *
1256      * @return {@code true} if deleted a row.
1257      */
removeUserFromKeysTable(int userId)1258     private boolean removeUserFromKeysTable(int userId) {
1259         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1260         String selection = KeysEntry.COLUMN_NAME_USER_ID + " = ?";
1261         String[] selectionArgs = {Integer.toString(userId)};
1262         return db.delete(KeysEntry.TABLE_NAME, selection, selectionArgs) > 0;
1263     }
1264 
1265     /**
1266      * Removes all entries for given userId from UserMetadata table.
1267      *
1268      * @return {@code true} if deleted a row.
1269      */
removeUserFromUserMetadataTable(int userId)1270     private boolean removeUserFromUserMetadataTable(int userId) {
1271         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1272         String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
1273         String[] selectionArgs = {Integer.toString(userId)};
1274         return db.delete(UserMetadataEntry.TABLE_NAME, selection, selectionArgs) > 0;
1275     }
1276 
1277     /**
1278      * Removes all entries for given userId from RecoveryServiceMetadata table.
1279      *
1280      * @return {@code true} if deleted a row.
1281      */
removeUserFromRecoveryServiceMetadataTable(int userId)1282     private boolean removeUserFromRecoveryServiceMetadataTable(int userId) {
1283         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1284         String selection = RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
1285         String[] selectionArgs = {Integer.toString(userId)};
1286         return db.delete(RecoveryServiceMetadataEntry.TABLE_NAME, selection, selectionArgs) > 0;
1287     }
1288 
1289     /**
1290      * Removes all entries for given userId from RootOfTrust table.
1291      *
1292      * @return {@code true} if deleted a row.
1293      */
removeUserFromRootOfTrustTable(int userId)1294     private boolean removeUserFromRootOfTrustTable(int userId) {
1295         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1296         String selection = RootOfTrustEntry.COLUMN_NAME_USER_ID + " = ?";
1297         String[] selectionArgs = {Integer.toString(userId)};
1298         return db.delete(RootOfTrustEntry.TABLE_NAME, selection, selectionArgs) > 0;
1299     }
1300 
1301     /**
1302      * Creates an empty row in the recovery service metadata table if such a row doesn't exist for
1303      * the given userId and uid, so db.update will succeed.
1304      */
ensureRecoveryServiceMetadataEntryExists(int userId, int uid)1305     private void ensureRecoveryServiceMetadataEntryExists(int userId, int uid) {
1306         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1307         ContentValues values = new ContentValues();
1308         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID, userId);
1309         values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_UID, uid);
1310         db.insertWithOnConflict(RecoveryServiceMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
1311                 values, SQLiteDatabase.CONFLICT_IGNORE);
1312     }
1313 
1314     /**
1315      * Creates an empty row in the root of trust table if such a row doesn't exist for
1316      * the given userId and uid, so db.update will succeed.
1317      */
ensureRootOfTrustEntryExists(int userId, int uid, String rootAlias)1318     private void ensureRootOfTrustEntryExists(int userId, int uid, String rootAlias) {
1319         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1320         ContentValues values = new ContentValues();
1321         values.put(RootOfTrustEntry.COLUMN_NAME_USER_ID, userId);
1322         values.put(RootOfTrustEntry.COLUMN_NAME_UID, uid);
1323         values.put(RootOfTrustEntry.COLUMN_NAME_ROOT_ALIAS, rootAlias);
1324         db.insertWithOnConflict(RootOfTrustEntry.TABLE_NAME, /*nullColumnHack=*/ null,
1325                 values, SQLiteDatabase.CONFLICT_IGNORE);
1326     }
1327 
1328     /**
1329      * Creates an empty row in the user metadata table if such a row doesn't exist for
1330      * the given userId, so db.update will succeed.
1331      */
ensureUserMetadataEntryExists(int userId)1332     private void ensureUserMetadataEntryExists(int userId) {
1333         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
1334         ContentValues values = new ContentValues();
1335         values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
1336         db.insertWithOnConflict(UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
1337                 values, SQLiteDatabase.CONFLICT_IGNORE);
1338     }
1339 
1340     /**
1341      * Closes all open connections to the database.
1342      */
close()1343     public void close() {
1344         mKeyStoreDbHelper.close();
1345     }
1346 
1347     @Nullable
decodeX509Key(byte[] keyBytes)1348     private static PublicKey decodeX509Key(byte[] keyBytes) throws InvalidKeySpecException {
1349         X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes);
1350         try {
1351             return KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
1352         } catch (NoSuchAlgorithmException e) {
1353             // Should never happen
1354             throw new RuntimeException(e);
1355         }
1356     }
1357 
1358     @Nullable
decodeCertPath(byte[] bytes)1359     private static CertPath decodeCertPath(byte[] bytes) throws CertificateException {
1360         CertificateFactory certFactory;
1361         try {
1362             certFactory = CertificateFactory.getInstance("X.509");
1363         } catch (CertificateException e) {
1364             // Should not happen, as X.509 is mandatory for all providers.
1365             throw new RuntimeException(e);
1366         }
1367         return certFactory.generateCertPath(new ByteArrayInputStream(bytes), CERT_PATH_ENCODING);
1368     }
1369 }
1370