Home | History | Annotate | Download | only in watchlist
      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.net.watchlist;
     18 
     19 import android.annotation.Nullable;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.database.Cursor;
     23 import android.database.sqlite.SQLiteDatabase;
     24 import android.database.sqlite.SQLiteOpenHelper;
     25 import android.os.Environment;
     26 import android.util.Pair;
     27 
     28 import com.android.internal.util.HexDump;
     29 
     30 import java.io.File;
     31 import java.util.ArrayList;
     32 import java.util.GregorianCalendar;
     33 import java.util.HashMap;
     34 import java.util.HashSet;
     35 import java.util.List;
     36 import java.util.Set;
     37 
     38 /**
     39  * Helper class to process watchlist read / save watchlist reports.
     40  */
     41 class WatchlistReportDbHelper extends SQLiteOpenHelper {
     42 
     43     private static final String TAG = "WatchlistReportDbHelper";
     44 
     45     private static final String NAME = "watchlist_report.db";
     46     private static final int VERSION = 2;
     47 
     48     private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
     49 
     50     private static class WhiteListReportContract {
     51         private static final String TABLE = "records";
     52         private static final String APP_DIGEST = "app_digest";
     53         private static final String CNC_DOMAIN = "cnc_domain";
     54         private static final String TIMESTAMP = "timestamp";
     55     }
     56 
     57     private static final String CREATE_TABLE_MODEL = "CREATE TABLE "
     58             + WhiteListReportContract.TABLE + "("
     59             + WhiteListReportContract.APP_DIGEST + " BLOB,"
     60             + WhiteListReportContract.CNC_DOMAIN + " TEXT,"
     61             + WhiteListReportContract.TIMESTAMP + " INTEGER DEFAULT 0" + " )";
     62 
     63     private static final int INDEX_DIGEST = 0;
     64     private static final int INDEX_CNC_DOMAIN = 1;
     65     private static final int INDEX_TIMESTAMP = 2;
     66 
     67     private static final String[] DIGEST_DOMAIN_PROJECTION =
     68             new String[] {
     69                     WhiteListReportContract.APP_DIGEST,
     70                     WhiteListReportContract.CNC_DOMAIN
     71             };
     72 
     73     private static WatchlistReportDbHelper sInstance;
     74 
     75     /**
     76      * Aggregated watchlist records.
     77      */
     78     public static class AggregatedResult {
     79         // A list of digests that visited c&c domain or ip before.
     80         final Set<String> appDigestList;
     81 
     82         // The c&c domain or ip visited before.
     83         @Nullable final String cncDomainVisited;
     84 
     85         // A list of app digests and c&c domain visited.
     86         final HashMap<String, String> appDigestCNCList;
     87 
     88         public AggregatedResult(Set<String> appDigestList, String cncDomainVisited,
     89                 HashMap<String, String> appDigestCNCList) {
     90             this.appDigestList = appDigestList;
     91             this.cncDomainVisited = cncDomainVisited;
     92             this.appDigestCNCList = appDigestCNCList;
     93         }
     94     }
     95 
     96     static File getSystemWatchlistDbFile() {
     97         return new File(Environment.getDataSystemDirectory(), NAME);
     98     }
     99 
    100     private WatchlistReportDbHelper(Context context) {
    101         super(context, getSystemWatchlistDbFile().getAbsolutePath(), null, VERSION);
    102         // Memory optimization - close idle connections after 30s of inactivity
    103         setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
    104     }
    105 
    106     public static synchronized WatchlistReportDbHelper getInstance(Context context) {
    107         if (sInstance != null) {
    108             return sInstance;
    109         }
    110         sInstance = new WatchlistReportDbHelper(context);
    111         return sInstance;
    112     }
    113 
    114     @Override
    115     public void onCreate(SQLiteDatabase db) {
    116         db.execSQL(CREATE_TABLE_MODEL);
    117     }
    118 
    119     @Override
    120     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    121         // TODO: For now, drop older tables and recreate new ones.
    122         db.execSQL("DROP TABLE IF EXISTS " + WhiteListReportContract.TABLE);
    123         onCreate(db);
    124     }
    125 
    126     /**
    127      * Insert new watchlist record.
    128      *
    129      * @param appDigest The digest of an app.
    130      * @param cncDomain C&C domain that app visited.
    131      * @return True if success.
    132      */
    133     public boolean insertNewRecord(byte[] appDigest, String cncDomain,
    134             long timestamp) {
    135         final SQLiteDatabase db = getWritableDatabase();
    136         final ContentValues values = new ContentValues();
    137         values.put(WhiteListReportContract.APP_DIGEST, appDigest);
    138         values.put(WhiteListReportContract.CNC_DOMAIN, cncDomain);
    139         values.put(WhiteListReportContract.TIMESTAMP, timestamp);
    140         return db.insert(WhiteListReportContract.TABLE, null, values) != -1;
    141     }
    142 
    143     /**
    144      * Aggregate all records in database before input timestamp, and return a
    145      * rappor encoded result.
    146      */
    147     @Nullable
    148     public AggregatedResult getAggregatedRecords(long untilTimestamp) {
    149         final String selectStatement = WhiteListReportContract.TIMESTAMP + " < ?";
    150 
    151         final SQLiteDatabase db = getReadableDatabase();
    152         Cursor c = null;
    153         try {
    154             c = db.query(true /* distinct */,
    155                     WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement,
    156                     new String[]{Long.toString(untilTimestamp)}, null, null,
    157                     null, null);
    158             if (c == null) {
    159                 return null;
    160             }
    161             final HashSet<String> appDigestList = new HashSet<>();
    162             final HashMap<String, String> appDigestCNCList = new HashMap<>();
    163             String cncDomainVisited = null;
    164             while (c.moveToNext()) {
    165                 // We use hex string here as byte[] cannot be a key in HashMap.
    166                 String digestHexStr = HexDump.toHexString(c.getBlob(INDEX_DIGEST));
    167                 String cncDomain = c.getString(INDEX_CNC_DOMAIN);
    168 
    169                 appDigestList.add(digestHexStr);
    170                 if (cncDomainVisited != null) {
    171                     cncDomainVisited = cncDomain;
    172                 }
    173                 appDigestCNCList.put(digestHexStr, cncDomain);
    174             }
    175             return new AggregatedResult(appDigestList, cncDomainVisited, appDigestCNCList);
    176         } finally {
    177             if (c != null) {
    178                 c.close();
    179             }
    180         }
    181     }
    182 
    183     /**
    184      * Remove all the records before input timestamp.
    185      *
    186      * @return True if success.
    187      */
    188     public boolean cleanup(long untilTimestamp) {
    189         final SQLiteDatabase db = getWritableDatabase();
    190         final String clause = WhiteListReportContract.TIMESTAMP + "< " + untilTimestamp;
    191         return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
    192     }
    193 }