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.content.pm.ParceledListSlice;
     25 import android.database.Cursor;
     26 import android.database.DatabaseUtils;
     27 import android.database.sqlite.SQLiteDatabase;
     28 import android.database.sqlite.SQLiteOpenHelper;
     29 import android.os.Binder;
     30 import android.os.IBinder;
     31 import android.os.Process;
     32 import android.os.UserHandle;
     33 import android.os.UserManager;
     34 import android.security.Credentials;
     35 import android.security.IKeyChainService;
     36 import android.security.KeyChain;
     37 import android.security.KeyStore;
     38 import android.util.Log;
     39 import com.android.internal.util.ParcelableString;
     40 import java.io.ByteArrayInputStream;
     41 import java.io.IOException;
     42 import java.security.cert.CertificateException;
     43 import java.security.cert.CertificateEncodingException;
     44 import java.security.cert.CertificateFactory;
     45 import java.security.cert.X509Certificate;
     46 import java.util.Set;
     47 import java.util.List;
     48 import java.util.ArrayList;
     49 import java.util.Collections;
     50 
     51 import com.android.org.conscrypt.TrustedCertificateStore;
     52 
     53 public class KeyChainService extends IntentService {
     54 
     55     private static final String TAG = "KeyChain";
     56 
     57     private static final String DATABASE_NAME = "grants.db";
     58     private static final int DATABASE_VERSION = 1;
     59     private static final String TABLE_GRANTS = "grants";
     60     private static final String GRANTS_ALIAS = "alias";
     61     private static final String GRANTS_GRANTEE_UID = "uid";
     62 
     63     /** created in onCreate(), closed in onDestroy() */
     64     public DatabaseHelper mDatabaseHelper;
     65 
     66     private static final String SELECTION_COUNT_OF_MATCHING_GRANTS =
     67             "SELECT COUNT(*) FROM " + TABLE_GRANTS
     68                     + " WHERE " + GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
     69 
     70     private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
     71             GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
     72 
     73     private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?";
     74 
     75     public KeyChainService() {
     76         super(KeyChainService.class.getSimpleName());
     77     }
     78 
     79     @Override public void onCreate() {
     80         super.onCreate();
     81         mDatabaseHelper = new DatabaseHelper(this);
     82     }
     83 
     84     @Override
     85     public void onDestroy() {
     86         super.onDestroy();
     87         mDatabaseHelper.close();
     88         mDatabaseHelper = null;
     89     }
     90 
     91     private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
     92         private final KeyStore mKeyStore = KeyStore.getInstance();
     93         private final TrustedCertificateStore mTrustedCertificateStore
     94                 = new TrustedCertificateStore();
     95 
     96         @Override
     97         public String requestPrivateKey(String alias) {
     98             checkArgs(alias);
     99 
    100             final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
    101             final int uid = Binder.getCallingUid();
    102             if (!mKeyStore.grant(keystoreAlias, uid)) {
    103                 return null;
    104             }
    105             final int userHandle = UserHandle.getUserId(uid);
    106             final int systemUidForUser = UserHandle.getUid(userHandle, Process.SYSTEM_UID);
    107 
    108             final StringBuilder sb = new StringBuilder();
    109             sb.append(systemUidForUser);
    110             sb.append('_');
    111             sb.append(keystoreAlias);
    112 
    113             return sb.toString();
    114         }
    115 
    116         @Override public byte[] getCertificate(String alias) {
    117             checkArgs(alias);
    118             return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
    119         }
    120 
    121         private void checkArgs(String alias) {
    122             if (alias == null) {
    123                 throw new NullPointerException("alias == null");
    124             }
    125             if (!mKeyStore.isUnlocked()) {
    126                 throw new IllegalStateException("keystore is "
    127                         + mKeyStore.state().toString());
    128             }
    129 
    130             final int callingUid = getCallingUid();
    131             if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
    132                 throw new IllegalStateException("uid " + callingUid
    133                         + " doesn't have permission to access the requested alias");
    134             }
    135         }
    136 
    137         @Override public void installCaCertificate(byte[] caCertificate) {
    138             checkCertInstallerOrSystemCaller();
    139             checkUserRestriction();
    140             try {
    141                 synchronized (mTrustedCertificateStore) {
    142                     mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate));
    143                 }
    144             } catch (IOException e) {
    145                 throw new IllegalStateException(e);
    146             } catch (CertificateException e) {
    147                 throw new IllegalStateException(e);
    148             }
    149             broadcastStorageChange();
    150         }
    151 
    152         @Override public boolean installKeyPair(byte[] privateKey, byte[] userCertificate,
    153                 String alias) {
    154             checkCertInstallerOrSystemCaller();
    155             if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, privateKey, -1,
    156                     KeyStore.FLAG_ENCRYPTED)) {
    157                 Log.e(TAG, "Failed to import private key " + alias);
    158                 return false;
    159             }
    160             if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertificate, -1,
    161                     KeyStore.FLAG_ENCRYPTED)) {
    162                 Log.e(TAG, "Failed to import user certificate " + userCertificate);
    163                 if (!mKeyStore.delKey(Credentials.USER_PRIVATE_KEY + alias)) {
    164                     Log.e(TAG, "Failed to delete private key after certificate importing failed");
    165                 }
    166                 return false;
    167             }
    168             broadcastStorageChange();
    169             return true;
    170         }
    171 
    172         private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
    173             CertificateFactory cf = CertificateFactory.getInstance("X.509");
    174             return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
    175         }
    176 
    177         @Override public boolean reset() {
    178             // only Settings should be able to reset
    179             checkSystemCaller();
    180             checkUserRestriction();
    181             removeAllGrants(mDatabaseHelper.getWritableDatabase());
    182             boolean ok = true;
    183             synchronized (mTrustedCertificateStore) {
    184                 // delete user-installed CA certs
    185                 for (String alias : mTrustedCertificateStore.aliases()) {
    186                     if (TrustedCertificateStore.isUser(alias)) {
    187                         if (!deleteCertificateEntry(alias)) {
    188                             ok = false;
    189                         }
    190                     }
    191                 }
    192             }
    193             broadcastStorageChange();
    194             return ok;
    195         }
    196 
    197         @Override public boolean deleteCaCertificate(String alias) {
    198             // only Settings should be able to delete
    199             checkSystemCaller();
    200             checkUserRestriction();
    201             boolean ok = true;
    202             synchronized (mTrustedCertificateStore) {
    203                 ok = deleteCertificateEntry(alias);
    204             }
    205             broadcastStorageChange();
    206             return ok;
    207         }
    208 
    209         private boolean deleteCertificateEntry(String alias) {
    210             try {
    211                 mTrustedCertificateStore.deleteCertificateEntry(alias);
    212                 return true;
    213             } catch (IOException e) {
    214                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
    215                 return false;
    216             } catch (CertificateException e) {
    217                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
    218                 return false;
    219             }
    220         }
    221 
    222         private void checkCertInstallerOrSystemCaller() {
    223             String actual = checkCaller("com.android.certinstaller");
    224             if (actual == null) {
    225                 return;
    226             }
    227             checkSystemCaller();
    228         }
    229         private void checkSystemCaller() {
    230             String actual = checkCaller("android.uid.system:1000");
    231             if (actual != null) {
    232                 throw new IllegalStateException(actual);
    233             }
    234         }
    235         private void checkUserRestriction() {
    236             UserManager um = (UserManager) getSystemService(USER_SERVICE);
    237             if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
    238                 throw new SecurityException("User cannot modify credentials");
    239             }
    240         }
    241         /**
    242          * Returns null if actually caller is expected, otherwise return bad package to report
    243          */
    244         private String checkCaller(String expectedPackage) {
    245             String actualPackage = getPackageManager().getNameForUid(getCallingUid());
    246             return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
    247         }
    248 
    249         @Override public boolean hasGrant(int uid, String alias) {
    250             checkSystemCaller();
    251             return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
    252         }
    253 
    254         @Override public void setGrant(int uid, String alias, boolean value) {
    255             checkSystemCaller();
    256             setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
    257             broadcastStorageChange();
    258         }
    259 
    260         private ParceledListSlice<ParcelableString> makeAliasesParcelableSynchronised(
    261                 Set<String> aliasSet) {
    262             List<ParcelableString> aliases = new ArrayList<ParcelableString>(aliasSet.size());
    263             for (String alias : aliasSet) {
    264                 ParcelableString parcelableString = new ParcelableString();
    265                 parcelableString.string = alias;
    266                 aliases.add(parcelableString);
    267             }
    268             return new ParceledListSlice<ParcelableString>(aliases);
    269         }
    270 
    271         @Override
    272         public ParceledListSlice<ParcelableString> getUserCaAliases() {
    273             synchronized (mTrustedCertificateStore) {
    274                 Set<String> aliasSet = mTrustedCertificateStore.userAliases();
    275                 return makeAliasesParcelableSynchronised(aliasSet);
    276             }
    277         }
    278 
    279         @Override
    280         public ParceledListSlice<ParcelableString> getSystemCaAliases() {
    281             synchronized (mTrustedCertificateStore) {
    282                 Set<String> aliasSet = mTrustedCertificateStore.allSystemAliases();
    283                 return makeAliasesParcelableSynchronised(aliasSet);
    284             }
    285         }
    286 
    287         @Override
    288         public boolean containsCaAlias(String alias) {
    289             return mTrustedCertificateStore.containsAlias(alias);
    290         }
    291 
    292         @Override
    293         public byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem) {
    294             synchronized (mTrustedCertificateStore) {
    295                 X509Certificate certificate = (X509Certificate) mTrustedCertificateStore
    296                         .getCertificate(alias, includeDeletedSystem);
    297                 if (certificate == null) {
    298                     Log.w(TAG, "Could not find CA certificate " + alias);
    299                     return null;
    300                 }
    301                 try {
    302                     return certificate.getEncoded();
    303                 } catch (CertificateEncodingException e) {
    304                     Log.w(TAG, "Error while encoding CA certificate " + alias);
    305                     return null;
    306                 }
    307             }
    308         }
    309 
    310         @Override
    311         public List<String> getCaCertificateChainAliases(String rootAlias,
    312                 boolean includeDeletedSystem) {
    313             synchronized (mTrustedCertificateStore) {
    314                 X509Certificate root = (X509Certificate) mTrustedCertificateStore.getCertificate(
    315                         rootAlias, includeDeletedSystem);
    316                 try {
    317                     List<X509Certificate> chain = mTrustedCertificateStore.getCertificateChain(
    318                             root);
    319                     List<String> aliases = new ArrayList<String>(chain.size());
    320                     final int n = chain.size();
    321                     for (int i = 0; i < n; ++i) {
    322                         String alias = mTrustedCertificateStore.getCertificateAlias(chain.get(i),
    323                                 true);
    324                         if (alias != null) {
    325                             aliases.add(alias);
    326                         }
    327                     }
    328                     return aliases;
    329                 } catch (CertificateException e) {
    330                     Log.w(TAG, "Error retrieving cert chain for root " + rootAlias);
    331                     return Collections.emptyList();
    332                 }
    333             }
    334         }
    335     };
    336 
    337     private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
    338         final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
    339                 new String[]{String.valueOf(uid), alias});
    340         return numMatches > 0;
    341     }
    342 
    343     private void setGrantInternal(final SQLiteDatabase db,
    344             final int uid, final String alias, final boolean value) {
    345         if (value) {
    346             if (!hasGrantInternal(db, uid, alias)) {
    347                 final ContentValues values = new ContentValues();
    348                 values.put(GRANTS_ALIAS, alias);
    349                 values.put(GRANTS_GRANTEE_UID, uid);
    350                 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
    351             }
    352         } else {
    353             db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
    354                     new String[]{String.valueOf(uid), alias});
    355         }
    356     }
    357 
    358     private void removeAllGrants(final SQLiteDatabase db) {
    359         db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
    360     }
    361 
    362     private class DatabaseHelper extends SQLiteOpenHelper {
    363         public DatabaseHelper(Context context) {
    364             super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
    365         }
    366 
    367         @Override
    368         public void onCreate(final SQLiteDatabase db) {
    369             db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
    370                     + GRANTS_ALIAS + " STRING NOT NULL,  "
    371                     + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
    372                     + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
    373         }
    374 
    375         @Override
    376         public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
    377             Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
    378 
    379             if (oldVersion == 1) {
    380                 // the first upgrade step goes here
    381                 oldVersion++;
    382             }
    383         }
    384     }
    385 
    386     @Override public IBinder onBind(Intent intent) {
    387         if (IKeyChainService.class.getName().equals(intent.getAction())) {
    388             return mIKeyChainService;
    389         }
    390         return null;
    391     }
    392 
    393     @Override
    394     protected void onHandleIntent(final Intent intent) {
    395         if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
    396             purgeOldGrants();
    397         }
    398     }
    399 
    400     private void purgeOldGrants() {
    401         final PackageManager packageManager = getPackageManager();
    402         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    403         Cursor cursor = null;
    404         db.beginTransaction();
    405         try {
    406             cursor = db.query(TABLE_GRANTS,
    407                     new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
    408             while (cursor.moveToNext()) {
    409                 final int uid = cursor.getInt(0);
    410                 final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
    411                 if (packageExists) {
    412                     continue;
    413                 }
    414                 Log.d(TAG, "deleting grants for UID " + uid
    415                         + " because its package is no longer installed");
    416                 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
    417                         new String[]{Integer.toString(uid)});
    418             }
    419             db.setTransactionSuccessful();
    420         } finally {
    421             if (cursor != null) {
    422                 cursor.close();
    423             }
    424             db.endTransaction();
    425         }
    426     }
    427 
    428     private void broadcastStorageChange() {
    429         Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
    430         sendBroadcast(intent);
    431     }
    432 
    433 }
    434