Home | History | Annotate | Download | only in keychain
      1 /*
      2  * Copyright (C) 2011 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.keychain;
     18 
     19 import android.app.IntentService;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.pm.PackageManager;
     24 import android.database.Cursor;
     25 import android.database.DatabaseUtils;
     26 import android.database.sqlite.SQLiteDatabase;
     27 import android.database.sqlite.SQLiteOpenHelper;
     28 import android.os.IBinder;
     29 import android.security.Credentials;
     30 import android.security.IKeyChainService;
     31 import android.security.KeyStore;
     32 import android.util.Log;
     33 import java.io.ByteArrayInputStream;
     34 import java.io.IOException;
     35 import java.security.cert.CertificateException;
     36 import java.security.cert.CertificateFactory;
     37 import java.security.cert.X509Certificate;
     38 
     39 import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
     40 
     41 public class KeyChainService extends IntentService {
     42     private static final String TAG = "KeyChain";
     43 
     44     private static final String DATABASE_NAME = "grants.db";
     45     private static final int DATABASE_VERSION = 1;
     46     private static final String TABLE_GRANTS = "grants";
     47     private static final String GRANTS_ALIAS = "alias";
     48     private static final String GRANTS_GRANTEE_UID = "uid";
     49 
     50     /** created in onCreate(), closed in onDestroy() */
     51     public DatabaseHelper mDatabaseHelper;
     52 
     53     private static final String SELECTION_COUNT_OF_MATCHING_GRANTS =
     54             "SELECT COUNT(*) FROM " + TABLE_GRANTS
     55                     + " WHERE " + GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
     56 
     57     private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
     58             GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
     59 
     60     private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?";
     61 
     62     public KeyChainService() {
     63         super(KeyChainService.class.getSimpleName());
     64     }
     65 
     66     @Override public void onCreate() {
     67         super.onCreate();
     68         mDatabaseHelper = new DatabaseHelper(this);
     69     }
     70 
     71     @Override
     72     public void onDestroy() {
     73         super.onDestroy();
     74         mDatabaseHelper.close();
     75         mDatabaseHelper = null;
     76     }
     77 
     78     private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
     79         private final KeyStore mKeyStore = KeyStore.getInstance();
     80         private final TrustedCertificateStore mTrustedCertificateStore
     81                 = new TrustedCertificateStore();
     82 
     83         @Override public byte[] getPrivateKey(String alias) {
     84             return getKeyStoreEntry(Credentials.USER_PRIVATE_KEY, alias);
     85         }
     86 
     87         @Override public byte[] getCertificate(String alias) {
     88             return getKeyStoreEntry(Credentials.USER_CERTIFICATE, alias);
     89         }
     90 
     91         private byte[] getKeyStoreEntry(String type, String alias) {
     92             if (alias == null) {
     93                 throw new NullPointerException("alias == null");
     94             }
     95             if (!isKeyStoreUnlocked()) {
     96                 throw new IllegalStateException("keystore locked");
     97             }
     98             final int callingUid = getCallingUid();
     99             if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
    100                 throw new IllegalStateException("uid " + callingUid
    101                         + " doesn't have permission to access the requested alias");
    102             }
    103             String key = type + alias;
    104             byte[] bytes =  mKeyStore.get(key);
    105             if (bytes == null) {
    106                 return null;
    107             }
    108             return bytes;
    109         }
    110 
    111         private boolean isKeyStoreUnlocked() {
    112             return (mKeyStore.state() == KeyStore.State.UNLOCKED);
    113         }
    114 
    115         @Override public void installCaCertificate(byte[] caCertificate) {
    116             checkCertInstallerOrSystemCaller();
    117             try {
    118                 synchronized (mTrustedCertificateStore) {
    119                     mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate));
    120                 }
    121             } catch (IOException e) {
    122                 throw new IllegalStateException(e);
    123             } catch (CertificateException e) {
    124                 throw new IllegalStateException(e);
    125             }
    126         }
    127 
    128         private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
    129             CertificateFactory cf = CertificateFactory.getInstance("X.509");
    130             return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
    131         }
    132 
    133         @Override public boolean reset() {
    134             // only Settings should be able to reset
    135             checkSystemCaller();
    136             removeAllGrants(mDatabaseHelper.getWritableDatabase());
    137             boolean ok = true;
    138             synchronized (mTrustedCertificateStore) {
    139                 // delete user-installed CA certs
    140                 for (String alias : mTrustedCertificateStore.aliases()) {
    141                     if (TrustedCertificateStore.isUser(alias)) {
    142                         if (!deleteCertificateEntry(alias)) {
    143                             ok = false;
    144                         }
    145                     }
    146                 }
    147                 return ok;
    148             }
    149         }
    150 
    151         @Override public boolean deleteCaCertificate(String alias) {
    152             // only Settings should be able to delete
    153             checkSystemCaller();
    154             return deleteCertificateEntry(alias);
    155         }
    156 
    157         private boolean deleteCertificateEntry(String alias) {
    158             try {
    159                 mTrustedCertificateStore.deleteCertificateEntry(alias);
    160                 return true;
    161             } catch (IOException e) {
    162                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
    163                 return false;
    164             } catch (CertificateException e) {
    165                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
    166                 return false;
    167             }
    168         }
    169 
    170         private void checkCertInstallerOrSystemCaller() {
    171             String actual = checkCaller("com.android.certinstaller");
    172             if (actual == null) {
    173                 return;
    174             }
    175             checkSystemCaller();
    176         }
    177         private void checkSystemCaller() {
    178             String actual = checkCaller("android.uid.system:1000");
    179             if (actual != null) {
    180                 throw new IllegalStateException(actual);
    181             }
    182         }
    183         /**
    184          * Returns null if actually caller is expected, otherwise return bad package to report
    185          */
    186         private String checkCaller(String expectedPackage) {
    187             String actualPackage = getPackageManager().getNameForUid(getCallingUid());
    188             return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
    189         }
    190 
    191         @Override public boolean hasGrant(int uid, String alias) {
    192             checkSystemCaller();
    193             return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
    194         }
    195 
    196         @Override public void setGrant(int uid, String alias, boolean value) {
    197             checkSystemCaller();
    198             setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
    199         }
    200     };
    201 
    202     private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
    203         final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
    204                 new String[]{String.valueOf(uid), alias});
    205         return numMatches > 0;
    206     }
    207 
    208     private void setGrantInternal(final SQLiteDatabase db,
    209             final int uid, final String alias, final boolean value) {
    210         if (value) {
    211             if (!hasGrantInternal(db, uid, alias)) {
    212                 final ContentValues values = new ContentValues();
    213                 values.put(GRANTS_ALIAS, alias);
    214                 values.put(GRANTS_GRANTEE_UID, uid);
    215                 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
    216             }
    217         } else {
    218             db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
    219                     new String[]{String.valueOf(uid), alias});
    220         }
    221     }
    222 
    223     private void removeAllGrants(final SQLiteDatabase db) {
    224         db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
    225     }
    226 
    227     private class DatabaseHelper extends SQLiteOpenHelper {
    228         public DatabaseHelper(Context context) {
    229             super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
    230         }
    231 
    232         @Override
    233         public void onCreate(final SQLiteDatabase db) {
    234             db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
    235                     + GRANTS_ALIAS + " STRING NOT NULL,  "
    236                     + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
    237                     + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
    238         }
    239 
    240         @Override
    241         public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
    242             Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
    243 
    244             if (oldVersion == 1) {
    245                 // the first upgrade step goes here
    246                 oldVersion++;
    247             }
    248         }
    249     }
    250 
    251     @Override public IBinder onBind(Intent intent) {
    252         if (IKeyChainService.class.getName().equals(intent.getAction())) {
    253             return mIKeyChainService;
    254         }
    255         return null;
    256     }
    257 
    258     @Override
    259     protected void onHandleIntent(final Intent intent) {
    260         if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
    261             purgeOldGrants();
    262         }
    263     }
    264 
    265     private void purgeOldGrants() {
    266         final PackageManager packageManager = getPackageManager();
    267         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    268         Cursor cursor = null;
    269         db.beginTransaction();
    270         try {
    271             cursor = db.query(TABLE_GRANTS,
    272                     new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
    273             while (cursor.moveToNext()) {
    274                 final int uid = cursor.getInt(0);
    275                 final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
    276                 if (packageExists) {
    277                     continue;
    278                 }
    279                 Log.d(TAG, "deleting grants for UID " + uid
    280                         + " because its package is no longer installed");
    281                 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
    282                         new String[]{Integer.toString(uid)});
    283             }
    284             db.setTransactionSuccessful();
    285         } finally {
    286             if (cursor != null) {
    287                 cursor.close();
    288             }
    289             db.endTransaction();
    290         }
    291     }
    292 }
    293