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