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