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 (!mKeyStore.isUnlocked()) {
    115                 throw new IllegalStateException("keystore is "
    116                         + mKeyStore.state().toString());
    117             }
    118 
    119             final int callingUid = getCallingUid();
    120             if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
    121                 throw new IllegalStateException("uid " + callingUid
    122                         + " doesn't have permission to access the requested alias");
    123             }
    124         }
    125 
    126         @Override public void installCaCertificate(byte[] caCertificate) {
    127             checkCertInstallerOrSystemCaller();
    128             try {
    129                 synchronized (mTrustedCertificateStore) {
    130                     mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate));
    131                 }
    132             } catch (IOException e) {
    133                 throw new IllegalStateException(e);
    134             } catch (CertificateException e) {
    135                 throw new IllegalStateException(e);
    136             }
    137             broadcastStorageChange();
    138         }
    139 
    140         private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
    141             CertificateFactory cf = CertificateFactory.getInstance("X.509");
    142             return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
    143         }
    144 
    145         @Override public boolean reset() {
    146             // only Settings should be able to reset
    147             checkSystemCaller();
    148             removeAllGrants(mDatabaseHelper.getWritableDatabase());
    149             boolean ok = true;
    150             synchronized (mTrustedCertificateStore) {
    151                 // delete user-installed CA certs
    152                 for (String alias : mTrustedCertificateStore.aliases()) {
    153                     if (TrustedCertificateStore.isUser(alias)) {
    154                         if (!deleteCertificateEntry(alias)) {
    155                             ok = false;
    156                         }
    157                     }
    158                 }
    159             }
    160             broadcastStorageChange();
    161             return ok;
    162         }
    163 
    164         @Override public boolean deleteCaCertificate(String alias) {
    165             // only Settings should be able to delete
    166             checkSystemCaller();
    167             boolean ok = true;
    168             synchronized (mTrustedCertificateStore) {
    169                 ok = deleteCertificateEntry(alias);
    170             }
    171             broadcastStorageChange();
    172             return ok;
    173         }
    174 
    175         private boolean deleteCertificateEntry(String alias) {
    176             try {
    177                 mTrustedCertificateStore.deleteCertificateEntry(alias);
    178                 return true;
    179             } catch (IOException e) {
    180                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
    181                 return false;
    182             } catch (CertificateException e) {
    183                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
    184                 return false;
    185             }
    186         }
    187 
    188         private void checkCertInstallerOrSystemCaller() {
    189             String actual = checkCaller("com.android.certinstaller");
    190             if (actual == null) {
    191                 return;
    192             }
    193             checkSystemCaller();
    194         }
    195         private void checkSystemCaller() {
    196             String actual = checkCaller("android.uid.system:1000");
    197             if (actual != null) {
    198                 throw new IllegalStateException(actual);
    199             }
    200         }
    201         /**
    202          * Returns null if actually caller is expected, otherwise return bad package to report
    203          */
    204         private String checkCaller(String expectedPackage) {
    205             String actualPackage = getPackageManager().getNameForUid(getCallingUid());
    206             return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
    207         }
    208 
    209         @Override public boolean hasGrant(int uid, String alias) {
    210             checkSystemCaller();
    211             return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
    212         }
    213 
    214         @Override public void setGrant(int uid, String alias, boolean value) {
    215             checkSystemCaller();
    216             setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
    217             broadcastStorageChange();
    218         }
    219     };
    220 
    221     private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
    222         final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
    223                 new String[]{String.valueOf(uid), alias});
    224         return numMatches > 0;
    225     }
    226 
    227     private void setGrantInternal(final SQLiteDatabase db,
    228             final int uid, final String alias, final boolean value) {
    229         if (value) {
    230             if (!hasGrantInternal(db, uid, alias)) {
    231                 final ContentValues values = new ContentValues();
    232                 values.put(GRANTS_ALIAS, alias);
    233                 values.put(GRANTS_GRANTEE_UID, uid);
    234                 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
    235             }
    236         } else {
    237             db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
    238                     new String[]{String.valueOf(uid), alias});
    239         }
    240     }
    241 
    242     private void removeAllGrants(final SQLiteDatabase db) {
    243         db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
    244     }
    245 
    246     private class DatabaseHelper extends SQLiteOpenHelper {
    247         public DatabaseHelper(Context context) {
    248             super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
    249         }
    250 
    251         @Override
    252         public void onCreate(final SQLiteDatabase db) {
    253             db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
    254                     + GRANTS_ALIAS + " STRING NOT NULL,  "
    255                     + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
    256                     + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
    257         }
    258 
    259         @Override
    260         public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
    261             Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
    262 
    263             if (oldVersion == 1) {
    264                 // the first upgrade step goes here
    265                 oldVersion++;
    266             }
    267         }
    268     }
    269 
    270     @Override public IBinder onBind(Intent intent) {
    271         if (IKeyChainService.class.getName().equals(intent.getAction())) {
    272             return mIKeyChainService;
    273         }
    274         return null;
    275     }
    276 
    277     @Override
    278     protected void onHandleIntent(final Intent intent) {
    279         if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
    280             purgeOldGrants();
    281         }
    282     }
    283 
    284     private void purgeOldGrants() {
    285         final PackageManager packageManager = getPackageManager();
    286         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    287         Cursor cursor = null;
    288         db.beginTransaction();
    289         try {
    290             cursor = db.query(TABLE_GRANTS,
    291                     new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
    292             while (cursor.moveToNext()) {
    293                 final int uid = cursor.getInt(0);
    294                 final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
    295                 if (packageExists) {
    296                     continue;
    297                 }
    298                 Log.d(TAG, "deleting grants for UID " + uid
    299                         + " because its package is no longer installed");
    300                 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
    301                         new String[]{Integer.toString(uid)});
    302             }
    303             db.setTransactionSuccessful();
    304         } finally {
    305             if (cursor != null) {
    306                 cursor.close();
    307             }
    308             db.endTransaction();
    309         }
    310     }
    311 
    312     private void broadcastStorageChange() {
    313         Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
    314         sendBroadcast(intent);
    315     }
    316 
    317 }
    318