• 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.database.Cursor;
25 import android.database.DatabaseUtils;
26 import android.database.sqlite.SQLiteDatabase;
27 import android.database.sqlite.SQLiteOpenHelper;
28 import android.os.Binder;
29 import android.os.IBinder;
30 import android.os.Process;
31 import android.security.Credentials;
32 import android.security.IKeyChainService;
33 import android.security.KeyChain;
34 import android.security.KeyStore;
35 import android.util.Log;
36 import java.io.ByteArrayInputStream;
37 import java.io.IOException;
38 import java.security.cert.CertificateException;
39 import java.security.cert.CertificateFactory;
40 import java.security.cert.X509Certificate;
41 
42 import com.android.org.conscrypt.TrustedCertificateStore;
43 
44 public class KeyChainService extends IntentService {
45 
46     private static final String TAG = "KeyChain";
47 
48     private static final String DATABASE_NAME = "grants.db";
49     private static final int DATABASE_VERSION = 1;
50     private static final String TABLE_GRANTS = "grants";
51     private static final String GRANTS_ALIAS = "alias";
52     private static final String GRANTS_GRANTEE_UID = "uid";
53 
54     /** created in onCreate(), closed in onDestroy() */
55     public DatabaseHelper mDatabaseHelper;
56 
57     private static final String SELECTION_COUNT_OF_MATCHING_GRANTS =
58             "SELECT COUNT(*) FROM " + TABLE_GRANTS
59                     + " WHERE " + GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
60 
61     private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
62             GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
63 
64     private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?";
65 
KeyChainService()66     public KeyChainService() {
67         super(KeyChainService.class.getSimpleName());
68     }
69 
onCreate()70     @Override public void onCreate() {
71         super.onCreate();
72         mDatabaseHelper = new DatabaseHelper(this);
73     }
74 
75     @Override
onDestroy()76     public void onDestroy() {
77         super.onDestroy();
78         mDatabaseHelper.close();
79         mDatabaseHelper = null;
80     }
81 
82     private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
83         private final KeyStore mKeyStore = KeyStore.getInstance();
84         private final TrustedCertificateStore mTrustedCertificateStore
85                 = new TrustedCertificateStore();
86 
87         @Override
88         public String requestPrivateKey(String alias) {
89             checkArgs(alias);
90 
91             final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
92             final int uid = Binder.getCallingUid();
93             if (!mKeyStore.grant(keystoreAlias, uid)) {
94                 return null;
95             }
96 
97             final StringBuilder sb = new StringBuilder();
98             sb.append(Process.SYSTEM_UID);
99             sb.append('_');
100             sb.append(keystoreAlias);
101 
102             return sb.toString();
103         }
104 
105         @Override public byte[] getCertificate(String alias) {
106             checkArgs(alias);
107             return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
108         }
109 
110         private void checkArgs(String alias) {
111             if (alias == null) {
112                 throw new NullPointerException("alias == null");
113             }
114             if (!mKeyStore.isUnlocked()) {
115                 throw new IllegalStateException("keystore is "
116                         + mKeyStore.state().toString());
117             }
118 
119             final int callingUid = getCallingUid();
120             if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
121                 throw new IllegalStateException("uid " + callingUid
122                         + " doesn't have permission to access the requested alias");
123             }
124         }
125 
126         @Override public void installCaCertificate(byte[] caCertificate) {
127             checkCertInstallerOrSystemCaller();
128             try {
129                 synchronized (mTrustedCertificateStore) {
130                     mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate));
131                 }
132             } catch (IOException e) {
133                 throw new IllegalStateException(e);
134             } catch (CertificateException e) {
135                 throw new IllegalStateException(e);
136             }
137             broadcastStorageChange();
138         }
139 
140         private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
141             CertificateFactory cf = CertificateFactory.getInstance("X.509");
142             return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
143         }
144 
145         @Override public boolean reset() {
146             // only Settings should be able to reset
147             checkSystemCaller();
148             removeAllGrants(mDatabaseHelper.getWritableDatabase());
149             boolean ok = true;
150             synchronized (mTrustedCertificateStore) {
151                 // delete user-installed CA certs
152                 for (String alias : mTrustedCertificateStore.aliases()) {
153                     if (TrustedCertificateStore.isUser(alias)) {
154                         if (!deleteCertificateEntry(alias)) {
155                             ok = false;
156                         }
157                     }
158                 }
159             }
160             broadcastStorageChange();
161             return ok;
162         }
163 
164         @Override public boolean deleteCaCertificate(String alias) {
165             // only Settings should be able to delete
166             checkSystemCaller();
167             boolean ok = true;
168             synchronized (mTrustedCertificateStore) {
169                 ok = deleteCertificateEntry(alias);
170             }
171             broadcastStorageChange();
172             return ok;
173         }
174 
175         private boolean deleteCertificateEntry(String alias) {
176             try {
177                 mTrustedCertificateStore.deleteCertificateEntry(alias);
178                 return true;
179             } catch (IOException e) {
180                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
181                 return false;
182             } catch (CertificateException e) {
183                 Log.w(TAG, "Problem removing CA certificate " + alias, e);
184                 return false;
185             }
186         }
187 
188         private void checkCertInstallerOrSystemCaller() {
189             String actual = checkCaller("com.android.certinstaller");
190             if (actual == null) {
191                 return;
192             }
193             checkSystemCaller();
194         }
195         private void checkSystemCaller() {
196             String actual = checkCaller("android.uid.system:1000");
197             if (actual != null) {
198                 throw new IllegalStateException(actual);
199             }
200         }
201         /**
202          * Returns null if actually caller is expected, otherwise return bad package to report
203          */
204         private String checkCaller(String expectedPackage) {
205             String actualPackage = getPackageManager().getNameForUid(getCallingUid());
206             return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
207         }
208 
209         @Override public boolean hasGrant(int uid, String alias) {
210             checkSystemCaller();
211             return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
212         }
213 
214         @Override public void setGrant(int uid, String alias, boolean value) {
215             checkSystemCaller();
216             setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
217             broadcastStorageChange();
218         }
219     };
220 
hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias)221     private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
222         final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
223                 new String[]{String.valueOf(uid), alias});
224         return numMatches > 0;
225     }
226 
setGrantInternal(final SQLiteDatabase db, final int uid, final String alias, final boolean value)227     private void setGrantInternal(final SQLiteDatabase db,
228             final int uid, final String alias, final boolean value) {
229         if (value) {
230             if (!hasGrantInternal(db, uid, alias)) {
231                 final ContentValues values = new ContentValues();
232                 values.put(GRANTS_ALIAS, alias);
233                 values.put(GRANTS_GRANTEE_UID, uid);
234                 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
235             }
236         } else {
237             db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
238                     new String[]{String.valueOf(uid), alias});
239         }
240     }
241 
removeAllGrants(final SQLiteDatabase db)242     private void removeAllGrants(final SQLiteDatabase db) {
243         db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
244     }
245 
246     private class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context)247         public DatabaseHelper(Context context) {
248             super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
249         }
250 
251         @Override
onCreate(final SQLiteDatabase db)252         public void onCreate(final SQLiteDatabase db) {
253             db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
254                     + GRANTS_ALIAS + " STRING NOT NULL,  "
255                     + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
256                     + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
257         }
258 
259         @Override
onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion)260         public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
261             Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
262 
263             if (oldVersion == 1) {
264                 // the first upgrade step goes here
265                 oldVersion++;
266             }
267         }
268     }
269 
onBind(Intent intent)270     @Override public IBinder onBind(Intent intent) {
271         if (IKeyChainService.class.getName().equals(intent.getAction())) {
272             return mIKeyChainService;
273         }
274         return null;
275     }
276 
277     @Override
onHandleIntent(final Intent intent)278     protected void onHandleIntent(final Intent intent) {
279         if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
280             purgeOldGrants();
281         }
282     }
283 
purgeOldGrants()284     private void purgeOldGrants() {
285         final PackageManager packageManager = getPackageManager();
286         final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
287         Cursor cursor = null;
288         db.beginTransaction();
289         try {
290             cursor = db.query(TABLE_GRANTS,
291                     new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
292             while (cursor.moveToNext()) {
293                 final int uid = cursor.getInt(0);
294                 final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
295                 if (packageExists) {
296                     continue;
297                 }
298                 Log.d(TAG, "deleting grants for UID " + uid
299                         + " because its package is no longer installed");
300                 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
301                         new String[]{Integer.toString(uid)});
302             }
303             db.setTransactionSuccessful();
304         } finally {
305             if (cursor != null) {
306                 cursor.close();
307             }
308             db.endTransaction();
309         }
310     }
311 
broadcastStorageChange()312     private void broadcastStorageChange() {
313         Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
314         sendBroadcast(intent);
315     }
316 
317 }
318