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