blob: 58531ac1c44c17acd7101a3e8ee40dab2a4f7bd1 [file] [log] [blame]
Brian Carlstrom3e6251d2011-04-11 09:05:06 -07001/*
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
17package com.android.keychain;
18
Fred Quintanafb2e18e2011-07-13 14:54:05 -070019import android.app.IntentService;
20import android.content.ContentValues;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070021import android.content.Context;
22import android.content.Intent;
Fred Quintanafb2e18e2011-07-13 14:54:05 -070023import android.content.pm.PackageManager;
Zoltan Szatmary-Ban3d25b312014-08-18 10:54:19 +010024import android.content.pm.ParceledListSlice;
Fred Quintanafb2e18e2011-07-13 14:54:05 -070025import android.database.Cursor;
26import android.database.DatabaseUtils;
27import android.database.sqlite.SQLiteDatabase;
28import android.database.sqlite.SQLiteOpenHelper;
Kenny Root6f1f03b2012-03-08 10:30:39 -080029import android.os.Binder;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070030import android.os.IBinder;
Kenny Root6f1f03b2012-03-08 10:30:39 -080031import android.os.Process;
Robin Lee93772c32014-09-02 14:53:50 +010032import android.os.UserHandle;
Julia Reynolds3fb74492014-06-30 16:54:50 -040033import android.os.UserManager;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070034import android.security.Credentials;
35import android.security.IKeyChainService;
Selim Gurun39e36e52012-02-14 10:50:42 -080036import android.security.KeyChain;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070037import android.security.KeyStore;
38import android.util.Log;
Zoltan Szatmary-Ban3d25b312014-08-18 10:54:19 +010039import com.android.internal.util.ParcelableString;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070040import java.io.ByteArrayInputStream;
Brian Carlstroma58db542011-05-11 23:02:20 -070041import java.io.IOException;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070042import java.security.cert.CertificateException;
Zoltan Szatmary-Ban3d25b312014-08-18 10:54:19 +010043import java.security.cert.CertificateEncodingException;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070044import java.security.cert.CertificateFactory;
45import java.security.cert.X509Certificate;
Zoltan Szatmary-Ban3d25b312014-08-18 10:54:19 +010046import java.util.Set;
47import java.util.List;
48import java.util.ArrayList;
49import java.util.Collections;
Fred Quintanafb2e18e2011-07-13 14:54:05 -070050
Kenny Root3048b6c2013-04-23 22:38:11 -070051import com.android.org.conscrypt.TrustedCertificateStore;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070052
Fred Quintanafb2e18e2011-07-13 14:54:05 -070053public class KeyChainService extends IntentService {
Selim Gurun39e36e52012-02-14 10:50:42 -080054
Fred Quintanafb2e18e2011-07-13 14:54:05 -070055 private static final String TAG = "KeyChain";
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070056
Fred Quintanafb2e18e2011-07-13 14:54:05 -070057 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";
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070062
Fred Quintanafb2e18e2011-07-13 14:54:05 -070063 /** created in onCreate(), closed in onDestroy() */
64 public DatabaseHelper mDatabaseHelper;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070065
Fred Quintanafb2e18e2011-07-13 14:54:05 -070066 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 public KeyChainService() {
76 super(KeyChainService.class.getSimpleName());
77 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070078
79 @Override public void onCreate() {
80 super.onCreate();
Fred Quintanafb2e18e2011-07-13 14:54:05 -070081 mDatabaseHelper = new DatabaseHelper(this);
82 }
83
84 @Override
85 public void onDestroy() {
86 super.onDestroy();
87 mDatabaseHelper.close();
88 mDatabaseHelper = null;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070089 }
90
91 private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070092 private final KeyStore mKeyStore = KeyStore.getInstance();
Brian Carlstroma58db542011-05-11 23:02:20 -070093 private final TrustedCertificateStore mTrustedCertificateStore
94 = new TrustedCertificateStore();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070095
Kenny Root6f1f03b2012-03-08 10:30:39 -080096 @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 }
Robin Lee93772c32014-09-02 14:53:50 +0100105 final int userHandle = UserHandle.getUserId(uid);
106 final int systemUidForUser = UserHandle.getUid(userHandle, Process.SYSTEM_UID);
Kenny Root6f1f03b2012-03-08 10:30:39 -0800107
108 final StringBuilder sb = new StringBuilder();
Robin Lee93772c32014-09-02 14:53:50 +0100109 sb.append(systemUidForUser);
Kenny Root6f1f03b2012-03-08 10:30:39 -0800110 sb.append('_');
111 sb.append(keystoreAlias);
112
113 return sb.toString();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700114 }
115
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700116 @Override public byte[] getCertificate(String alias) {
Kenny Root6f1f03b2012-03-08 10:30:39 -0800117 checkArgs(alias);
118 return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700119 }
120
Kenny Root6f1f03b2012-03-08 10:30:39 -0800121 private void checkArgs(String alias) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700122 if (alias == null) {
123 throw new NullPointerException("alias == null");
124 }
Kenny Root4ff22962013-02-14 10:17:06 -0800125 if (!mKeyStore.isUnlocked()) {
Nick Kralevichc8b04632012-05-21 15:13:07 -0700126 throw new IllegalStateException("keystore is "
127 + mKeyStore.state().toString());
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700128 }
Nick Kralevichc8b04632012-05-21 15:13:07 -0700129
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700130 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");
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700134 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700135 }
136
Brian Carlstroma58db542011-05-11 23:02:20 -0700137 @Override public void installCaCertificate(byte[] caCertificate) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700138 checkCertInstallerOrSystemCaller();
Julia Reynolds3fb74492014-06-30 16:54:50 -0400139 checkUserRestriction();
Brian Carlstroma58db542011-05-11 23:02:20 -0700140 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 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800149 broadcastStorageChange();
Brian Carlstroma58db542011-05-11 23:02:20 -0700150 }
Brian Carlstrom5aeadd92011-05-17 00:40:33 -0700151
152 private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
153 CertificateFactory cf = CertificateFactory.getInstance("X.509");
154 return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
155 }
156
Brian Carlstroma58db542011-05-11 23:02:20 -0700157 @Override public boolean reset() {
158 // only Settings should be able to reset
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700159 checkSystemCaller();
Julia Reynolds3fb74492014-06-30 16:54:50 -0400160 checkUserRestriction();
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700161 removeAllGrants(mDatabaseHelper.getWritableDatabase());
Brian Carlstroma58db542011-05-11 23:02:20 -0700162 boolean ok = true;
Brian Carlstroma58db542011-05-11 23:02:20 -0700163 synchronized (mTrustedCertificateStore) {
164 // delete user-installed CA certs
165 for (String alias : mTrustedCertificateStore.aliases()) {
166 if (TrustedCertificateStore.isUser(alias)) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700167 if (!deleteCertificateEntry(alias)) {
Brian Carlstroma58db542011-05-11 23:02:20 -0700168 ok = false;
169 }
170 }
171 }
Brian Carlstroma58db542011-05-11 23:02:20 -0700172 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800173 broadcastStorageChange();
174 return ok;
Brian Carlstroma58db542011-05-11 23:02:20 -0700175 }
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700176
177 @Override public boolean deleteCaCertificate(String alias) {
178 // only Settings should be able to delete
179 checkSystemCaller();
Julia Reynolds3fb74492014-06-30 16:54:50 -0400180 checkUserRestriction();
Selim Gurun39e36e52012-02-14 10:50:42 -0800181 boolean ok = true;
182 synchronized (mTrustedCertificateStore) {
183 ok = deleteCertificateEntry(alias);
184 }
185 broadcastStorageChange();
186 return ok;
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700187 }
188
189 private boolean deleteCertificateEntry(String alias) {
190 try {
191 mTrustedCertificateStore.deleteCertificateEntry(alias);
192 return true;
193 } catch (IOException e) {
194 Log.w(TAG, "Problem removing CA certificate " + alias, e);
195 return false;
196 } catch (CertificateException e) {
197 Log.w(TAG, "Problem removing CA certificate " + alias, e);
198 return false;
199 }
200 }
201
202 private void checkCertInstallerOrSystemCaller() {
203 String actual = checkCaller("com.android.certinstaller");
204 if (actual == null) {
205 return;
206 }
207 checkSystemCaller();
208 }
209 private void checkSystemCaller() {
210 String actual = checkCaller("android.uid.system:1000");
211 if (actual != null) {
212 throw new IllegalStateException(actual);
213 }
214 }
Julia Reynolds3fb74492014-06-30 16:54:50 -0400215 private void checkUserRestriction() {
216 UserManager um = (UserManager) getSystemService(USER_SERVICE);
217 if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
218 throw new SecurityException("User cannot modify credentials");
219 }
220 }
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700221 /**
222 * Returns null if actually caller is expected, otherwise return bad package to report
223 */
224 private String checkCaller(String expectedPackage) {
225 String actualPackage = getPackageManager().getNameForUid(getCallingUid());
226 return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
227 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700228
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700229 @Override public boolean hasGrant(int uid, String alias) {
230 checkSystemCaller();
231 return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700232 }
233
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700234 @Override public void setGrant(int uid, String alias, boolean value) {
235 checkSystemCaller();
236 setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
Selim Gurun39e36e52012-02-14 10:50:42 -0800237 broadcastStorageChange();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700238 }
Zoltan Szatmary-Ban3d25b312014-08-18 10:54:19 +0100239
240 private ParceledListSlice<ParcelableString> makeAliasesParcelableSynchronised(
241 Set<String> aliasSet) {
242 List<ParcelableString> aliases = new ArrayList<ParcelableString>(aliasSet.size());
243 for (String alias : aliasSet) {
244 ParcelableString parcelableString = new ParcelableString();
245 parcelableString.string = alias;
246 aliases.add(parcelableString);
247 }
248 return new ParceledListSlice<ParcelableString>(aliases);
249 }
250
251 @Override
252 public ParceledListSlice<ParcelableString> getUserCaAliases() {
253 synchronized (mTrustedCertificateStore) {
254 Set<String> aliasSet = mTrustedCertificateStore.userAliases();
255 return makeAliasesParcelableSynchronised(aliasSet);
256 }
257 }
258
259 @Override
260 public ParceledListSlice<ParcelableString> getSystemCaAliases() {
261 synchronized (mTrustedCertificateStore) {
262 Set<String> aliasSet = mTrustedCertificateStore.allSystemAliases();
263 return makeAliasesParcelableSynchronised(aliasSet);
264 }
265 }
266
267 @Override
268 public boolean containsCaAlias(String alias) {
269 return mTrustedCertificateStore.containsAlias(alias);
270 }
271
272 @Override
273 public byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem) {
274 synchronized (mTrustedCertificateStore) {
275 X509Certificate certificate = (X509Certificate) mTrustedCertificateStore
276 .getCertificate(alias, includeDeletedSystem);
277 if (certificate == null) {
278 Log.w(TAG, "Could not find CA certificate " + alias);
279 return null;
280 }
281 try {
282 return certificate.getEncoded();
283 } catch (CertificateEncodingException e) {
284 Log.w(TAG, "Error while encoding CA certificate " + alias);
285 return null;
286 }
287 }
288 }
289
290 @Override
291 public List<String> getCaCertificateChainAliases(String rootAlias,
292 boolean includeDeletedSystem) {
293 synchronized (mTrustedCertificateStore) {
294 X509Certificate root = (X509Certificate) mTrustedCertificateStore.getCertificate(
295 rootAlias, includeDeletedSystem);
296 try {
297 List<X509Certificate> chain = mTrustedCertificateStore.getCertificateChain(
298 root);
299 List<String> aliases = new ArrayList<String>(chain.size());
300 final int n = chain.size();
301 for (int i = 0; i < n; ++i) {
302 String alias = mTrustedCertificateStore.getCertificateAlias(chain.get(i),
303 true);
304 if (alias != null) {
305 aliases.add(alias);
306 }
307 }
308 return aliases;
309 } catch (CertificateException e) {
310 Log.w(TAG, "Error retrieving cert chain for root " + rootAlias);
311 return Collections.emptyList();
312 }
313 }
314 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700315 };
316
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700317 private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
318 final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
319 new String[]{String.valueOf(uid), alias});
320 return numMatches > 0;
321 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700322
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700323 private void setGrantInternal(final SQLiteDatabase db,
324 final int uid, final String alias, final boolean value) {
325 if (value) {
326 if (!hasGrantInternal(db, uid, alias)) {
327 final ContentValues values = new ContentValues();
328 values.put(GRANTS_ALIAS, alias);
329 values.put(GRANTS_GRANTEE_UID, uid);
330 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
331 }
332 } else {
333 db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
334 new String[]{String.valueOf(uid), alias});
335 }
336 }
337
338 private void removeAllGrants(final SQLiteDatabase db) {
339 db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
340 }
341
342 private class DatabaseHelper extends SQLiteOpenHelper {
343 public DatabaseHelper(Context context) {
344 super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
345 }
346
347 @Override
348 public void onCreate(final SQLiteDatabase db) {
349 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
350 + GRANTS_ALIAS + " STRING NOT NULL, "
351 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
352 + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
353 }
354
355 @Override
356 public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
357 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
358
359 if (oldVersion == 1) {
360 // the first upgrade step goes here
361 oldVersion++;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700362 }
Brian Carlstrom7037b732011-06-30 15:04:49 -0700363 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700364 }
365
366 @Override public IBinder onBind(Intent intent) {
Brian Carlstrom7037b732011-06-30 15:04:49 -0700367 if (IKeyChainService.class.getName().equals(intent.getAction())) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700368 return mIKeyChainService;
369 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700370 return null;
371 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700372
373 @Override
374 protected void onHandleIntent(final Intent intent) {
375 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
376 purgeOldGrants();
377 }
378 }
379
380 private void purgeOldGrants() {
381 final PackageManager packageManager = getPackageManager();
382 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
383 Cursor cursor = null;
384 db.beginTransaction();
385 try {
386 cursor = db.query(TABLE_GRANTS,
387 new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
388 while (cursor.moveToNext()) {
389 final int uid = cursor.getInt(0);
390 final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
391 if (packageExists) {
392 continue;
393 }
394 Log.d(TAG, "deleting grants for UID " + uid
395 + " because its package is no longer installed");
396 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
397 new String[]{Integer.toString(uid)});
398 }
399 db.setTransactionSuccessful();
400 } finally {
401 if (cursor != null) {
402 cursor.close();
403 }
404 db.endTransaction();
405 }
406 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800407
408 private void broadcastStorageChange() {
409 Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
410 sendBroadcast(intent);
411 }
412
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700413}