• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
KeyChainService()76     public KeyChainService() {
77         super(KeyChainService.class.getSimpleName());
78     }
79 
onCreate()80     @Override public void onCreate() {
81         super.onCreate();
82         mDatabaseHelper = new DatabaseHelper(this);
83     }
84 
85     @Override
onDestroy()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 
hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias)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 
setGrantInternal(final SQLiteDatabase db, final int uid, final String alias, final boolean value)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 
removeGrantsForAlias(String alias)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 
removeAllGrants(final SQLiteDatabase db)398     private void removeAllGrants(final SQLiteDatabase db) {
399         db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
400     }
401 
402     private class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context)403         public DatabaseHelper(Context context) {
404             super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
405         }
406 
407         @Override
onCreate(final SQLiteDatabase db)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
onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion)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 
onBind(Intent intent)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
onHandleIntent(final Intent intent)434     protected void onHandleIntent(final Intent intent) {
435         if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
436             purgeOldGrants();
437         }
438     }
439 
purgeOldGrants()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 
broadcastStorageChange()468     private void broadcastStorageChange() {
469         Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
470         sendBroadcastAsUser(intent, new UserHandle(UserHandle.myUserId()));
471     }
472 
473 }
474