1 /*
2  * Copyright (C) 2023 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.federatedcompute.services.data;
18 
19 import static com.android.federatedcompute.services.data.FederatedComputeEncryptionKeyContract.ENCRYPTION_KEY_TABLE;
20 
21 import android.annotation.NonNull;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.database.Cursor;
25 import android.database.sqlite.SQLiteDatabase;
26 import android.database.sqlite.SQLiteException;
27 
28 import com.android.federatedcompute.internal.util.LogUtil;
29 import com.android.federatedcompute.services.data.FederatedComputeEncryptionKeyContract.FederatedComputeEncryptionColumns;
30 import com.android.odp.module.common.Clock;
31 import com.android.odp.module.common.MonotonicClock;
32 
33 import com.google.common.annotations.VisibleForTesting;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 
38 /** DAO for accessing encryption key table */
39 public class FederatedComputeEncryptionKeyDao {
40     private static final String TAG = FederatedComputeEncryptionKeyDao.class.getSimpleName();
41 
42     private final FederatedComputeDbHelper mDbHelper;
43 
44     private final Clock mClock;
45 
46     private static volatile FederatedComputeEncryptionKeyDao sSingletonInstance;
47 
FederatedComputeEncryptionKeyDao(FederatedComputeDbHelper dbHelper, Clock clock)48     private FederatedComputeEncryptionKeyDao(FederatedComputeDbHelper dbHelper, Clock clock) {
49         mDbHelper = dbHelper;
50         mClock = clock;
51     }
52 
53     /**
54      * @return an instance of FederatedComputeEncryptionKeyDao given a context
55      */
56     @NonNull
getInstance(Context context)57     public static FederatedComputeEncryptionKeyDao getInstance(Context context) {
58         if (sSingletonInstance == null) {
59             synchronized (FederatedComputeEncryptionKeyDao.class) {
60                 if (sSingletonInstance == null) {
61                     sSingletonInstance =
62                             new FederatedComputeEncryptionKeyDao(
63                                     FederatedComputeDbHelper.getInstance(context),
64                                     MonotonicClock.getInstance());
65                 }
66             }
67         }
68         return sSingletonInstance;
69     }
70 
71     /** It is only public to unit test. */
72     @VisibleForTesting
getInstanceForTest(Context context)73     public static FederatedComputeEncryptionKeyDao getInstanceForTest(Context context) {
74         if (sSingletonInstance == null) {
75             synchronized (FederatedComputeEncryptionKeyDao.class) {
76                 if (sSingletonInstance == null) {
77                     FederatedComputeDbHelper dbHelper =
78                             FederatedComputeDbHelper.getInstanceForTest(context);
79                     Clock clk = MonotonicClock.getInstance();
80                     sSingletonInstance = new FederatedComputeEncryptionKeyDao(dbHelper, clk);
81                 }
82             }
83         }
84         return sSingletonInstance;
85     }
86 
87     /** Insert a key to the encryption_key table. */
insertEncryptionKey(FederatedComputeEncryptionKey key)88     public boolean insertEncryptionKey(FederatedComputeEncryptionKey key) {
89         SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
90         if (db == null) {
91             throw new SQLiteException(TAG + ": Failed to open database.");
92         }
93 
94         ContentValues values = new ContentValues();
95         values.put(FederatedComputeEncryptionColumns.KEY_IDENTIFIER, key.getKeyIdentifier());
96         values.put(FederatedComputeEncryptionColumns.PUBLIC_KEY, key.getPublicKey());
97         values.put(FederatedComputeEncryptionColumns.KEY_TYPE, key.getKeyType());
98         values.put(FederatedComputeEncryptionColumns.CREATION_TIME, key.getCreationTime());
99         values.put(FederatedComputeEncryptionColumns.EXPIRY_TIME, key.getExpiryTime());
100 
101         long jobId =
102                 db.insertWithOnConflict(
103                         ENCRYPTION_KEY_TABLE, "", values, SQLiteDatabase.CONFLICT_REPLACE);
104         return jobId != -1;
105     }
106 
107     /**
108      * Read from encryption key table given selection, order and limit conidtions.
109      *
110      * @return a list of {@link FederatedComputeEncryptionKey}.
111      */
112     @VisibleForTesting
readFederatedComputeEncryptionKeysFromDatabase( String selection, String[] selectionArgs, String orderBy, int count)113     public List<FederatedComputeEncryptionKey> readFederatedComputeEncryptionKeysFromDatabase(
114             String selection, String[] selectionArgs, String orderBy, int count) {
115         List<FederatedComputeEncryptionKey> keyList = new ArrayList<>();
116         SQLiteDatabase db = mDbHelper.safeGetReadableDatabase();
117         if (db == null) {
118             throw new SQLiteException(TAG + ": Failed to open database.");
119         }
120 
121         String[] selectColumns = {
122             FederatedComputeEncryptionColumns.KEY_IDENTIFIER,
123             FederatedComputeEncryptionColumns.PUBLIC_KEY,
124             FederatedComputeEncryptionColumns.KEY_TYPE,
125             FederatedComputeEncryptionColumns.CREATION_TIME,
126             FederatedComputeEncryptionColumns.EXPIRY_TIME
127         };
128 
129         Cursor cursor = null;
130         try {
131             cursor =
132                     db.query(
133                             ENCRYPTION_KEY_TABLE,
134                             selectColumns,
135                             selection,
136                             selectionArgs,
137                             /* groupBy= */ null,
138                             /* having= */ null,
139                             /* orderBy= */ orderBy,
140                             /* limit= */ String.valueOf(count));
141             while (cursor.moveToNext()) {
142                 FederatedComputeEncryptionKey.Builder encryptionKeyBuilder =
143                         new FederatedComputeEncryptionKey.Builder()
144                                 .setKeyIdentifier(
145                                         cursor.getString(
146                                                 cursor.getColumnIndexOrThrow(
147                                                         FederatedComputeEncryptionColumns
148                                                                 .KEY_IDENTIFIER)))
149                                 .setPublicKey(
150                                         cursor.getString(
151                                                 cursor.getColumnIndexOrThrow(
152                                                         FederatedComputeEncryptionColumns
153                                                                 .PUBLIC_KEY)))
154                                 .setKeyType(
155                                         cursor.getInt(
156                                                 cursor.getColumnIndexOrThrow(
157                                                         FederatedComputeEncryptionColumns
158                                                                 .KEY_TYPE)))
159                                 .setCreationTime(
160                                         cursor.getLong(
161                                                 cursor.getColumnIndexOrThrow(
162                                                         FederatedComputeEncryptionColumns
163                                                                 .CREATION_TIME)))
164                                 .setExpiryTime(
165                                         cursor.getLong(
166                                                 cursor.getColumnIndexOrThrow(
167                                                         FederatedComputeEncryptionColumns
168                                                                 .EXPIRY_TIME)));
169                 keyList.add(encryptionKeyBuilder.build());
170             }
171         } finally {
172             if (cursor != null) {
173                 cursor.close();
174             }
175         }
176         return keyList;
177     }
178 
179     /**
180      * @return latest expired keys (order by expiry time).
181      */
getLatestExpiryNKeys(int count)182     public List<FederatedComputeEncryptionKey> getLatestExpiryNKeys(int count) {
183         String selection = FederatedComputeEncryptionColumns.EXPIRY_TIME + " > ?";
184         String[] selectionArgs = {String.valueOf(mClock.currentTimeMillis())};
185         // reverse order of expiry time
186         String orderBy = FederatedComputeEncryptionColumns.EXPIRY_TIME + " DESC";
187         return readFederatedComputeEncryptionKeysFromDatabase(
188                 selection, selectionArgs, orderBy, count);
189     }
190 
191     /**
192      * Delete expired keys.
193      *
194      * @return number of keys deleted.
195      */
deleteExpiredKeys()196     public int deleteExpiredKeys() {
197         SQLiteDatabase db = mDbHelper.safeGetWritableDatabase();
198         if (db == null) {
199             throw new SQLiteException(TAG + ": Failed to open database.");
200         }
201         String whereClause = FederatedComputeEncryptionColumns.EXPIRY_TIME + " < ?";
202         String[] whereArgs = {String.valueOf(mClock.currentTimeMillis())};
203         int deletedRows = db.delete(ENCRYPTION_KEY_TABLE, whereClause, whereArgs);
204         LogUtil.d(TAG, "Deleted %s expired keys from database", deletedRows);
205         return deletedRows;
206     }
207 }
208