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