blob: 6e7735744ff7948461e48af45d7c92b7adcc0c89 [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;
24import android.database.Cursor;
25import android.database.DatabaseUtils;
26import android.database.sqlite.SQLiteDatabase;
27import android.database.sqlite.SQLiteOpenHelper;
Kenny Root6f1f03b2012-03-08 10:30:39 -080028import android.os.Binder;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070029import android.os.IBinder;
Kenny Root6f1f03b2012-03-08 10:30:39 -080030import android.os.Process;
Robin Lee93772c32014-09-02 14:53:50 +010031import android.os.UserHandle;
Julia Reynolds3fb74492014-06-30 16:54:50 -040032import android.os.UserManager;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070033import android.security.Credentials;
34import android.security.IKeyChainService;
Selim Gurun39e36e52012-02-14 10:50:42 -080035import android.security.KeyChain;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070036import android.security.KeyStore;
37import android.util.Log;
38import java.io.ByteArrayInputStream;
Brian Carlstroma58db542011-05-11 23:02:20 -070039import java.io.IOException;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070040import java.security.cert.CertificateException;
41import java.security.cert.CertificateFactory;
42import java.security.cert.X509Certificate;
Fred Quintanafb2e18e2011-07-13 14:54:05 -070043
Kenny Root3048b6c2013-04-23 22:38:11 -070044import com.android.org.conscrypt.TrustedCertificateStore;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070045
Fred Quintanafb2e18e2011-07-13 14:54:05 -070046public class KeyChainService extends IntentService {
Selim Gurun39e36e52012-02-14 10:50:42 -080047
Fred Quintanafb2e18e2011-07-13 14:54:05 -070048 private static final String TAG = "KeyChain";
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070049
Fred Quintanafb2e18e2011-07-13 14:54:05 -070050 private static final String DATABASE_NAME = "grants.db";
51 private static final int DATABASE_VERSION = 1;
52 private static final String TABLE_GRANTS = "grants";
53 private static final String GRANTS_ALIAS = "alias";
54 private static final String GRANTS_GRANTEE_UID = "uid";
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070055
Fred Quintanafb2e18e2011-07-13 14:54:05 -070056 /** created in onCreate(), closed in onDestroy() */
57 public DatabaseHelper mDatabaseHelper;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070058
Fred Quintanafb2e18e2011-07-13 14:54:05 -070059 private static final String SELECTION_COUNT_OF_MATCHING_GRANTS =
60 "SELECT COUNT(*) FROM " + TABLE_GRANTS
61 + " WHERE " + GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
62
63 private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
64 GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
65
66 private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?";
67
68 public KeyChainService() {
69 super(KeyChainService.class.getSimpleName());
70 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070071
72 @Override public void onCreate() {
73 super.onCreate();
Fred Quintanafb2e18e2011-07-13 14:54:05 -070074 mDatabaseHelper = new DatabaseHelper(this);
75 }
76
77 @Override
78 public void onDestroy() {
79 super.onDestroy();
80 mDatabaseHelper.close();
81 mDatabaseHelper = null;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070082 }
83
84 private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070085 private final KeyStore mKeyStore = KeyStore.getInstance();
Brian Carlstroma58db542011-05-11 23:02:20 -070086 private final TrustedCertificateStore mTrustedCertificateStore
87 = new TrustedCertificateStore();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070088
Kenny Root6f1f03b2012-03-08 10:30:39 -080089 @Override
90 public String requestPrivateKey(String alias) {
91 checkArgs(alias);
92
93 final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
94 final int uid = Binder.getCallingUid();
95 if (!mKeyStore.grant(keystoreAlias, uid)) {
96 return null;
97 }
Robin Lee93772c32014-09-02 14:53:50 +010098 final int userHandle = UserHandle.getUserId(uid);
99 final int systemUidForUser = UserHandle.getUid(userHandle, Process.SYSTEM_UID);
Kenny Root6f1f03b2012-03-08 10:30:39 -0800100
101 final StringBuilder sb = new StringBuilder();
Robin Lee93772c32014-09-02 14:53:50 +0100102 sb.append(systemUidForUser);
Kenny Root6f1f03b2012-03-08 10:30:39 -0800103 sb.append('_');
104 sb.append(keystoreAlias);
105
106 return sb.toString();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700107 }
108
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700109 @Override public byte[] getCertificate(String alias) {
Kenny Root6f1f03b2012-03-08 10:30:39 -0800110 checkArgs(alias);
111 return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700112 }
113
Kenny Root6f1f03b2012-03-08 10:30:39 -0800114 private void checkArgs(String alias) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700115 if (alias == null) {
116 throw new NullPointerException("alias == null");
117 }
Kenny Root4ff22962013-02-14 10:17:06 -0800118 if (!mKeyStore.isUnlocked()) {
Nick Kralevichc8b04632012-05-21 15:13:07 -0700119 throw new IllegalStateException("keystore is "
120 + mKeyStore.state().toString());
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700121 }
Nick Kralevichc8b04632012-05-21 15:13:07 -0700122
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700123 final int callingUid = getCallingUid();
124 if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
125 throw new IllegalStateException("uid " + callingUid
126 + " doesn't have permission to access the requested alias");
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700127 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700128 }
129
Brian Carlstroma58db542011-05-11 23:02:20 -0700130 @Override public void installCaCertificate(byte[] caCertificate) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700131 checkCertInstallerOrSystemCaller();
Julia Reynolds3fb74492014-06-30 16:54:50 -0400132 checkUserRestriction();
Brian Carlstroma58db542011-05-11 23:02:20 -0700133 try {
134 synchronized (mTrustedCertificateStore) {
135 mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate));
136 }
137 } catch (IOException e) {
138 throw new IllegalStateException(e);
139 } catch (CertificateException e) {
140 throw new IllegalStateException(e);
141 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800142 broadcastStorageChange();
Brian Carlstroma58db542011-05-11 23:02:20 -0700143 }
Brian Carlstrom5aeadd92011-05-17 00:40:33 -0700144
145 private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
146 CertificateFactory cf = CertificateFactory.getInstance("X.509");
147 return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
148 }
149
Brian Carlstroma58db542011-05-11 23:02:20 -0700150 @Override public boolean reset() {
151 // only Settings should be able to reset
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700152 checkSystemCaller();
Julia Reynolds3fb74492014-06-30 16:54:50 -0400153 checkUserRestriction();
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700154 removeAllGrants(mDatabaseHelper.getWritableDatabase());
Brian Carlstroma58db542011-05-11 23:02:20 -0700155 boolean ok = true;
Brian Carlstroma58db542011-05-11 23:02:20 -0700156 synchronized (mTrustedCertificateStore) {
157 // delete user-installed CA certs
158 for (String alias : mTrustedCertificateStore.aliases()) {
159 if (TrustedCertificateStore.isUser(alias)) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700160 if (!deleteCertificateEntry(alias)) {
Brian Carlstroma58db542011-05-11 23:02:20 -0700161 ok = false;
162 }
163 }
164 }
Brian Carlstroma58db542011-05-11 23:02:20 -0700165 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800166 broadcastStorageChange();
167 return ok;
Brian Carlstroma58db542011-05-11 23:02:20 -0700168 }
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700169
170 @Override public boolean deleteCaCertificate(String alias) {
171 // only Settings should be able to delete
172 checkSystemCaller();
Julia Reynolds3fb74492014-06-30 16:54:50 -0400173 checkUserRestriction();
Selim Gurun39e36e52012-02-14 10:50:42 -0800174 boolean ok = true;
175 synchronized (mTrustedCertificateStore) {
176 ok = deleteCertificateEntry(alias);
177 }
178 broadcastStorageChange();
179 return ok;
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700180 }
181
182 private boolean deleteCertificateEntry(String alias) {
183 try {
184 mTrustedCertificateStore.deleteCertificateEntry(alias);
185 return true;
186 } catch (IOException e) {
187 Log.w(TAG, "Problem removing CA certificate " + alias, e);
188 return false;
189 } catch (CertificateException e) {
190 Log.w(TAG, "Problem removing CA certificate " + alias, e);
191 return false;
192 }
193 }
194
195 private void checkCertInstallerOrSystemCaller() {
196 String actual = checkCaller("com.android.certinstaller");
197 if (actual == null) {
198 return;
199 }
200 checkSystemCaller();
201 }
202 private void checkSystemCaller() {
203 String actual = checkCaller("android.uid.system:1000");
204 if (actual != null) {
205 throw new IllegalStateException(actual);
206 }
207 }
Julia Reynolds3fb74492014-06-30 16:54:50 -0400208 private void checkUserRestriction() {
209 UserManager um = (UserManager) getSystemService(USER_SERVICE);
210 if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
211 throw new SecurityException("User cannot modify credentials");
212 }
213 }
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700214 /**
215 * Returns null if actually caller is expected, otherwise return bad package to report
216 */
217 private String checkCaller(String expectedPackage) {
218 String actualPackage = getPackageManager().getNameForUid(getCallingUid());
219 return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
220 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700221
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700222 @Override public boolean hasGrant(int uid, String alias) {
223 checkSystemCaller();
224 return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700225 }
226
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700227 @Override public void setGrant(int uid, String alias, boolean value) {
228 checkSystemCaller();
229 setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
Selim Gurun39e36e52012-02-14 10:50:42 -0800230 broadcastStorageChange();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700231 }
232 };
233
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700234 private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
235 final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
236 new String[]{String.valueOf(uid), alias});
237 return numMatches > 0;
238 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700239
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700240 private void setGrantInternal(final SQLiteDatabase db,
241 final int uid, final String alias, final boolean value) {
242 if (value) {
243 if (!hasGrantInternal(db, uid, alias)) {
244 final ContentValues values = new ContentValues();
245 values.put(GRANTS_ALIAS, alias);
246 values.put(GRANTS_GRANTEE_UID, uid);
247 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
248 }
249 } else {
250 db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
251 new String[]{String.valueOf(uid), alias});
252 }
253 }
254
255 private void removeAllGrants(final SQLiteDatabase db) {
256 db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
257 }
258
259 private class DatabaseHelper extends SQLiteOpenHelper {
260 public DatabaseHelper(Context context) {
261 super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
262 }
263
264 @Override
265 public void onCreate(final SQLiteDatabase db) {
266 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
267 + GRANTS_ALIAS + " STRING NOT NULL, "
268 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
269 + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
270 }
271
272 @Override
273 public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
274 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
275
276 if (oldVersion == 1) {
277 // the first upgrade step goes here
278 oldVersion++;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700279 }
Brian Carlstrom7037b732011-06-30 15:04:49 -0700280 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700281 }
282
283 @Override public IBinder onBind(Intent intent) {
Brian Carlstrom7037b732011-06-30 15:04:49 -0700284 if (IKeyChainService.class.getName().equals(intent.getAction())) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700285 return mIKeyChainService;
286 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700287 return null;
288 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700289
290 @Override
291 protected void onHandleIntent(final Intent intent) {
292 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
293 purgeOldGrants();
294 }
295 }
296
297 private void purgeOldGrants() {
298 final PackageManager packageManager = getPackageManager();
299 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
300 Cursor cursor = null;
301 db.beginTransaction();
302 try {
303 cursor = db.query(TABLE_GRANTS,
304 new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
305 while (cursor.moveToNext()) {
306 final int uid = cursor.getInt(0);
307 final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
308 if (packageExists) {
309 continue;
310 }
311 Log.d(TAG, "deleting grants for UID " + uid
312 + " because its package is no longer installed");
313 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
314 new String[]{Integer.toString(uid)});
315 }
316 db.setTransactionSuccessful();
317 } finally {
318 if (cursor != null) {
319 cursor.close();
320 }
321 db.endTransaction();
322 }
323 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800324
325 private void broadcastStorageChange() {
326 Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
327 sendBroadcast(intent);
328 }
329
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700330}