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