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