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.BroadcastOptions;
     20 import android.app.IntentService;
     21 import android.content.ContentValues;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.StringParceledListSlice;
     26 import android.database.Cursor;
     27 import android.database.DatabaseUtils;
     28 import android.database.sqlite.SQLiteDatabase;
     29 import android.database.sqlite.SQLiteOpenHelper;
     30 import android.os.Binder;
     31 import android.os.Build;
     32 import android.os.IBinder;
     33 import android.os.Process;
     34 import android.os.UserHandle;
     35 import android.security.Credentials;
     36 import android.security.IKeyChainService;
     37 import android.security.KeyChain;
     38 import android.security.KeyStore;
     39 import android.util.Log;
     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     private static final String SELECTION_GRANTS_BY_ALIAS = GRANTS_ALIAS + "=?";
     76 
     77     public KeyChainService() {
     78         super(KeyChainService.class.getSimpleName());
     79     }
     80 
     81     @Override public void onCreate() {
     82         super.onCreate();
     83         mDatabaseHelper = new DatabaseHelper(this);
     84     }
     85 
     86     @Override
     87     public void onDestroy() {
     88         super.onDestroy();
     89         mDatabaseHelper.close();
     90         mDatabaseHelper = null;
     91     }
     92 
     93     private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
     94         private final KeyStore mKeyStore = KeyStore.getInstance();
     95         private final TrustedCertificateStore mTrustedCertificateStore
     96                 = new TrustedCertificateStore();
     97 
     98         @Override
     99         public String requestPrivateKey(String alias) {
    100             checkArgs(alias);
    101 
    102             final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
    103             final int uid = Binder.getCallingUid();
    104             return mKeyStore.grant(keystoreAlias, uid);
    105         }
    106 
    107         @Override public byte[] getCertificate(String alias) {
    108             checkArgs(alias);
    109             return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
    110         }
    111 
    112         @Override public byte[] getCaCertificates(String alias) {
    113             checkArgs(alias);
    114             return mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
    115         }
    116 
    117         private void checkArgs(String alias) {
    118             if (alias == null) {
    119                 throw new NullPointerException("alias == null");
    120             }
    121             if (!mKeyStore.isUnlocked()) {
    122                 throw new IllegalStateException("keystore is "
    123                         + mKeyStore.state().toString());
    124             }
    125 
    126             final int callingUid = getCallingUid();
    127             if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
    128                 throw new IllegalStateException("uid " + callingUid
    129                         + " doesn't have permission to access the requested alias");
    130             }
    131         }
    132 
    133         @Override public String installCaCertificate(byte[] caCertificate) {
    134             checkCertInstallerOrSystemCaller();
    135             final String alias;
    136             try {
    137                 final X509Certificate cert = parseCertificate(caCertificate);
    138                 synchronized (mTrustedCertificateStore) {
    139                     mTrustedCertificateStore.installCertificate(cert);
    140                     alias = mTrustedCertificateStore.getCertificateAlias(cert);
    141                 }
    142             } catch (IOException e) {
    143                 throw new IllegalStateException(e);
    144             } catch (CertificateException e) {
    145                 throw new IllegalStateException(e);
    146             }
    147             broadcastLegacyStorageChange();
    148             broadcastTrustStoreChange();
    149             return alias;
    150         }
    151 
    152         /**
    153          * Install a key pair to the keystore.
    154          *
    155          * @param privateKey The private key associated with the client certificate
    156          * @param userCertificate The client certificate to be installed
    157          * @param userCertificateChain The rest of the chain for the client certificate
    158          * @param alias The alias under which the key pair is installed
    159          * @return Whether the operation succeeded or not.
    160          */
    161         @Override public boolean installKeyPair(byte[] privateKey, byte[] userCertificate,
    162                 byte[] userCertificateChain, String alias) {
    163             checkCertInstallerOrSystemCaller();
    164             if (!mKeyStore.isUnlocked()) {
    165                 Log.e(TAG, "Keystore is " + mKeyStore.state().toString() + ". Credentials cannot"
    166                         + " be installed until device is unlocked");
    167                 return false;
    168             }
    169             if (!removeKeyPair(alias)) {
    170                 return false;
    171             }
    172             if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, privateKey, -1,
    173                     KeyStore.FLAG_ENCRYPTED)) {
    174                 Log.e(TAG, "Failed to import private key " + alias);
    175                 return false;
    176             }
    177             if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertificate, -1,
    178                     KeyStore.FLAG_ENCRYPTED)) {
    179                 Log.e(TAG, "Failed to import user certificate " + userCertificate);
    180                 if (!mKeyStore.delete(Credentials.USER_PRIVATE_KEY + alias)) {
    181                     Log.e(TAG, "Failed to delete private key after certificate importing failed");
    182                 }
    183                 return false;
    184             }
    185             if (userCertificateChain != null && userCertificateChain.length > 0) {
    186                 if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, userCertificateChain, -1,
    187                         KeyStore.FLAG_ENCRYPTED)) {
    188                     Log.e(TAG, "Failed to import certificate chain" + userCertificateChain);
    189                     if (!removeKeyPair(alias)) {
    190                         Log.e(TAG, "Failed to clean up key chain after certificate chain"
    191                                 + " importing failed");
    192                     }
    193                     return false;
    194                 }
    195             }
    196             broadcastKeychainChange();
    197             broadcastLegacyStorageChange();
    198             return true;
    199         }
    200 
    201         @Override public boolean removeKeyPair(String alias) {
    202             checkCertInstallerOrSystemCaller();
    203             if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
    204                 return false;
    205             }
    206             removeGrantsForAlias(alias);
    207             broadcastKeychainChange();
    208             broadcastLegacyStorageChange();
    209             return true;
    210         }
    211 
    212         private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
    213             CertificateFactory cf = CertificateFactory.getInstance("X.509");
    214             return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
    215         }
    216 
    217         @Override public boolean reset() {
    218             // only Settings should be able to reset
    219             checkSystemCaller();
    220             removeAllGrants(mDatabaseHelper.getWritableDatabase());
    221             boolean ok = true;
    222             synchronized (mTrustedCertificateStore) {
    223                 // delete user-installed CA certs
    224                 for (String alias : mTrustedCertificateStore.aliases()) {
    225                     if (TrustedCertificateStore.isUser(alias)) {
    226                         if (!deleteCertificateEntry(alias)) {
    227                             ok = false;
    228                         }
    229                     }
    230                 }
    231             }
    232             broadcastTrustStoreChange();
    233             broadcastKeychainChange();
    234             broadcastLegacyStorageChange();
    235             return ok;
    236         }
    237 
    238         @Override public boolean deleteCaCertificate(String alias) {
    239             // only Settings should be able to delete
    240             checkSystemCaller();
    241             boolean ok = true;
    242             synchronized (mTrustedCertificateStore) {
    243                 ok = deleteCertificateEntry(alias);
    244             }
    245             broadcastTrustStoreChange();
    246             broadcastLegacyStorageChange();
    247             return ok;
    248         }
    249 
    250         private boolean deleteCertificateEntry(String alias) {
    251             try {
    252                 mTrustedCertificateStore.deleteCertificateEntry(alias);
    253                 return true;
    254             } catch (IOException e) {
    255                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
    256                 return false;
    257             } catch (CertificateException e) {
    258                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
    259                 return false;
    260             }
    261         }
    262 
    263         private void checkCertInstallerOrSystemCaller() {
    264             String actual = checkCaller("com.android.certinstaller");
    265             if (actual == null) {
    266                 return;
    267             }
    268             checkSystemCaller();
    269         }
    270         private void checkSystemCaller() {
    271             String actual = checkCaller("android.uid.system:1000");
    272             if (actual != null) {
    273                 throw new IllegalStateException(actual);
    274             }
    275         }
    276         /**
    277          * Returns null if actually caller is expected, otherwise return bad package to report
    278          */
    279         private String checkCaller(String expectedPackage) {
    280             String actualPackage = getPackageManager().getNameForUid(getCallingUid());
    281             return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
    282         }
    283 
    284         @Override public boolean hasGrant(int uid, String alias) {
    285             checkSystemCaller();
    286             return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
    287         }
    288 
    289         @Override public void setGrant(int uid, String alias, boolean value) {
    290             checkSystemCaller();
    291             setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
    292             broadcastPermissionChange(uid, alias, value);
    293             broadcastLegacyStorageChange();
    294         }
    295 
    296         @Override
    297         public StringParceledListSlice getUserCaAliases() {
    298             synchronized (mTrustedCertificateStore) {
    299                 return new StringParceledListSlice(new ArrayList<String>(
    300                         mTrustedCertificateStore.userAliases()));
    301             }
    302         }
    303 
    304         @Override
    305         public StringParceledListSlice getSystemCaAliases() {
    306             synchronized (mTrustedCertificateStore) {
    307                 return new StringParceledListSlice(new ArrayList<String>(
    308                         mTrustedCertificateStore.allSystemAliases()));
    309             }
    310         }
    311 
    312         @Override
    313         public boolean containsCaAlias(String alias) {
    314             return mTrustedCertificateStore.containsAlias(alias);
    315         }
    316 
    317         @Override
    318         public byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem) {
    319             synchronized (mTrustedCertificateStore) {
    320                 X509Certificate certificate = (X509Certificate) mTrustedCertificateStore
    321                         .getCertificate(alias, includeDeletedSystem);
    322                 if (certificate == null) {
    323                     Log.w(TAG, "Could not find CA certificate " + alias);
    324                     return null;
    325                 }
    326                 try {
    327                     return certificate.getEncoded();
    328                 } catch (CertificateEncodingException e) {
    329                     Log.w(TAG, "Error while encoding CA certificate " + alias);
    330                     return null;
    331                 }
    332             }
    333         }
    334 
    335         @Override
    336         public List<String> getCaCertificateChainAliases(String rootAlias,
    337                 boolean includeDeletedSystem) {
    338             synchronized (mTrustedCertificateStore) {
    339                 X509Certificate root = (X509Certificate) mTrustedCertificateStore.getCertificate(
    340                         rootAlias, includeDeletedSystem);
    341                 try {
    342                     List<X509Certificate> chain = mTrustedCertificateStore.getCertificateChain(
    343                             root);
    344                     List<String> aliases = new ArrayList<String>(chain.size());
    345                     final int n = chain.size();
    346                     for (int i = 0; i < n; ++i) {
    347                         String alias = mTrustedCertificateStore.getCertificateAlias(chain.get(i),
    348                                 true);
    349                         if (alias != null) {
    350                             aliases.add(alias);
    351                         }
    352                     }
    353                     return aliases;
    354                 } catch (CertificateException e) {
    355                     Log.w(TAG, "Error retrieving cert chain for root " + rootAlias);
    356                     return Collections.emptyList();
    357                 }
    358             }
    359         }
    360     };
    361 
    362     private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
    363         final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
    364                 new String[]{String.valueOf(uid), alias});
    365         return numMatches > 0;
    366     }
    367 
    368     private void setGrantInternal(final SQLiteDatabase db,
    369             final int uid, final String alias, final boolean value) {
    370         if (value) {
    371             if (!hasGrantInternal(db, uid, alias)) {
    372                 final ContentValues values = new ContentValues();
    373                 values.put(GRANTS_ALIAS, alias);
    374                 values.put(GRANTS_GRANTEE_UID, uid);
    375                 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
    376             }
    377         } else {
    378             db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
    379                     new String[]{String.valueOf(uid), alias});
    380         }
    381     }
    382 
    383     private void removeGrantsForAlias(String alias) {
    384         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    385         db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_ALIAS, new String[] {alias});
    386     }
    387 
    388     private void removeAllGrants(final SQLiteDatabase db) {
    389         db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
    390     }
    391 
    392     private class DatabaseHelper extends SQLiteOpenHelper {
    393         public DatabaseHelper(Context context) {
    394             super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
    395         }
    396 
    397         @Override
    398         public void onCreate(final SQLiteDatabase db) {
    399             db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
    400                     + GRANTS_ALIAS + " STRING NOT NULL,  "
    401                     + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
    402                     + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
    403         }
    404 
    405         @Override
    406         public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
    407             Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
    408 
    409             if (oldVersion == 1) {
    410                 // the first upgrade step goes here
    411                 oldVersion++;
    412             }
    413         }
    414     }
    415 
    416     @Override public IBinder onBind(Intent intent) {
    417         if (IKeyChainService.class.getName().equals(intent.getAction())) {
    418             return mIKeyChainService;
    419         }
    420         return null;
    421     }
    422 
    423     @Override
    424     protected void onHandleIntent(final Intent intent) {
    425         if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
    426             purgeOldGrants();
    427         }
    428     }
    429 
    430     private void purgeOldGrants() {
    431         final PackageManager packageManager = getPackageManager();
    432         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
    433         Cursor cursor = null;
    434         db.beginTransaction();
    435         try {
    436             cursor = db.query(TABLE_GRANTS,
    437                     new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
    438             while (cursor.moveToNext()) {
    439                 final int uid = cursor.getInt(0);
    440                 final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
    441                 if (packageExists) {
    442                     continue;
    443                 }
    444                 Log.d(TAG, "deleting grants for UID " + uid
    445                         + " because its package is no longer installed");
    446                 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
    447                         new String[]{Integer.toString(uid)});
    448             }
    449             db.setTransactionSuccessful();
    450         } finally {
    451             if (cursor != null) {
    452                 cursor.close();
    453             }
    454             db.endTransaction();
    455         }
    456     }
    457 
    458     private void broadcastLegacyStorageChange() {
    459         Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
    460         BroadcastOptions opts = BroadcastOptions.makeBasic();
    461         opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.N_MR1);
    462         sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()), null, opts.toBundle());
    463     }
    464 
    465     private void broadcastKeychainChange() {
    466         Intent intent = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED);
    467         sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()));
    468     }
    469 
    470     private void broadcastTrustStoreChange() {
    471         Intent intent = new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED);
    472         sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()));
    473     }
    474 
    475     private void broadcastPermissionChange(int uid, String alias, boolean access) {
    476         // Since the permission change only impacts one uid only send to that uid's packages.
    477         final PackageManager packageManager = getPackageManager();
    478         String[] packages = packageManager.getPackagesForUid(uid);
    479         if (packages == null) {
    480             return;
    481         }
    482         for (String pckg : packages) {
    483             Intent intent = new Intent(KeyChain.ACTION_KEY_ACCESS_CHANGED);
    484             intent.putExtra(KeyChain.EXTRA_KEY_ALIAS, alias);
    485             intent.putExtra(KeyChain.EXTRA_KEY_ACCESSIBLE, access);
    486             intent.setPackage(pckg);
    487             sendBroadcastAsUser(intent, UserHandle.of(UserHandle.myUserId()));
    488         }
    489     }
    490 }
    491