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