blob: 418f575b2151b21783e446e186436e51d04a23a3 [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;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070028import android.os.IBinder;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070029import android.security.Credentials;
30import android.security.IKeyChainService;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070031import android.security.KeyStore;
32import android.util.Log;
33import java.io.ByteArrayInputStream;
Brian Carlstroma58db542011-05-11 23:02:20 -070034import java.io.IOException;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070035import java.security.cert.CertificateException;
36import java.security.cert.CertificateFactory;
37import java.security.cert.X509Certificate;
Fred Quintanafb2e18e2011-07-13 14:54:05 -070038
Brian Carlstroma58db542011-05-11 23:02:20 -070039import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070040
Fred Quintanafb2e18e2011-07-13 14:54:05 -070041public class KeyChainService extends IntentService {
42 private static final String TAG = "KeyChain";
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070043
Fred Quintanafb2e18e2011-07-13 14:54:05 -070044 private static final String DATABASE_NAME = "grants.db";
45 private static final int DATABASE_VERSION = 1;
46 private static final String TABLE_GRANTS = "grants";
47 private static final String GRANTS_ALIAS = "alias";
48 private static final String GRANTS_GRANTEE_UID = "uid";
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070049
Fred Quintanafb2e18e2011-07-13 14:54:05 -070050 /** created in onCreate(), closed in onDestroy() */
51 public DatabaseHelper mDatabaseHelper;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070052
Fred Quintanafb2e18e2011-07-13 14:54:05 -070053 private static final String SELECTION_COUNT_OF_MATCHING_GRANTS =
54 "SELECT COUNT(*) FROM " + TABLE_GRANTS
55 + " WHERE " + GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
56
57 private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
58 GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
59
60 private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?";
61
62 public KeyChainService() {
63 super(KeyChainService.class.getSimpleName());
64 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070065
66 @Override public void onCreate() {
67 super.onCreate();
Fred Quintanafb2e18e2011-07-13 14:54:05 -070068 mDatabaseHelper = new DatabaseHelper(this);
69 }
70
71 @Override
72 public void onDestroy() {
73 super.onDestroy();
74 mDatabaseHelper.close();
75 mDatabaseHelper = null;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070076 }
77
78 private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070079 private final KeyStore mKeyStore = KeyStore.getInstance();
Brian Carlstroma58db542011-05-11 23:02:20 -070080 private final TrustedCertificateStore mTrustedCertificateStore
81 = new TrustedCertificateStore();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070082
Fred Quintanafb2e18e2011-07-13 14:54:05 -070083 @Override public byte[] getPrivateKey(String alias) {
84 return getKeyStoreEntry(Credentials.USER_PRIVATE_KEY, alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070085 }
86
Fred Quintanafb2e18e2011-07-13 14:54:05 -070087 @Override public byte[] getCertificate(String alias) {
88 return getKeyStoreEntry(Credentials.USER_CERTIFICATE, alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070089 }
90
Fred Quintanafb2e18e2011-07-13 14:54:05 -070091 private byte[] getKeyStoreEntry(String type, String alias) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070092 if (alias == null) {
93 throw new NullPointerException("alias == null");
94 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070095 if (!isKeyStoreUnlocked()) {
96 throw new IllegalStateException("keystore locked");
97 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -070098 final int callingUid = getCallingUid();
99 if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
100 throw new IllegalStateException("uid " + callingUid
101 + " doesn't have permission to access the requested alias");
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700102 }
103 String key = type + alias;
Brian Carlstrome3b33902011-05-31 01:06:20 -0700104 byte[] bytes = mKeyStore.get(key);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700105 if (bytes == null) {
Brian Carlstromf5b50a42011-06-09 16:05:09 -0700106 return null;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700107 }
108 return bytes;
109 }
110
Brian Carlstrom5aeadd92011-05-17 00:40:33 -0700111 private boolean isKeyStoreUnlocked() {
Brian Carlstrome3b33902011-05-31 01:06:20 -0700112 return (mKeyStore.state() == KeyStore.State.UNLOCKED);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700113 }
Brian Carlstroma58db542011-05-11 23:02:20 -0700114
115 @Override public void installCaCertificate(byte[] caCertificate) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700116 checkCertInstallerOrSystemCaller();
Brian Carlstroma58db542011-05-11 23:02:20 -0700117 try {
118 synchronized (mTrustedCertificateStore) {
119 mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate));
120 }
121 } catch (IOException e) {
122 throw new IllegalStateException(e);
123 } catch (CertificateException e) {
124 throw new IllegalStateException(e);
125 }
126 }
Brian Carlstrom5aeadd92011-05-17 00:40:33 -0700127
128 private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
129 CertificateFactory cf = CertificateFactory.getInstance("X.509");
130 return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
131 }
132
Brian Carlstroma58db542011-05-11 23:02:20 -0700133 @Override public boolean reset() {
134 // only Settings should be able to reset
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700135 checkSystemCaller();
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700136 removeAllGrants(mDatabaseHelper.getWritableDatabase());
Brian Carlstroma58db542011-05-11 23:02:20 -0700137 boolean ok = true;
Brian Carlstroma58db542011-05-11 23:02:20 -0700138 synchronized (mTrustedCertificateStore) {
139 // delete user-installed CA certs
140 for (String alias : mTrustedCertificateStore.aliases()) {
141 if (TrustedCertificateStore.isUser(alias)) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700142 if (!deleteCertificateEntry(alias)) {
Brian Carlstroma58db542011-05-11 23:02:20 -0700143 ok = false;
144 }
145 }
146 }
147 return ok;
148 }
149 }
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700150
151 @Override public boolean deleteCaCertificate(String alias) {
152 // only Settings should be able to delete
153 checkSystemCaller();
154 return deleteCertificateEntry(alias);
155 }
156
157 private boolean deleteCertificateEntry(String alias) {
158 try {
159 mTrustedCertificateStore.deleteCertificateEntry(alias);
160 return true;
161 } catch (IOException e) {
162 Log.w(TAG, "Problem removing CA certificate " + alias, e);
163 return false;
164 } catch (CertificateException e) {
165 Log.w(TAG, "Problem removing CA certificate " + alias, e);
166 return false;
167 }
168 }
169
170 private void checkCertInstallerOrSystemCaller() {
171 String actual = checkCaller("com.android.certinstaller");
172 if (actual == null) {
173 return;
174 }
175 checkSystemCaller();
176 }
177 private void checkSystemCaller() {
178 String actual = checkCaller("android.uid.system:1000");
179 if (actual != null) {
180 throw new IllegalStateException(actual);
181 }
182 }
183 /**
184 * Returns null if actually caller is expected, otherwise return bad package to report
185 */
186 private String checkCaller(String expectedPackage) {
187 String actualPackage = getPackageManager().getNameForUid(getCallingUid());
188 return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
189 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700190
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700191 @Override public boolean hasGrant(int uid, String alias) {
192 checkSystemCaller();
193 return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700194 }
195
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700196 @Override public void setGrant(int uid, String alias, boolean value) {
197 checkSystemCaller();
198 setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700199 }
200 };
201
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700202 private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
203 final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
204 new String[]{String.valueOf(uid), alias});
205 return numMatches > 0;
206 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700207
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700208 private void setGrantInternal(final SQLiteDatabase db,
209 final int uid, final String alias, final boolean value) {
210 if (value) {
211 if (!hasGrantInternal(db, uid, alias)) {
212 final ContentValues values = new ContentValues();
213 values.put(GRANTS_ALIAS, alias);
214 values.put(GRANTS_GRANTEE_UID, uid);
215 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
216 }
217 } else {
218 db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
219 new String[]{String.valueOf(uid), alias});
220 }
221 }
222
223 private void removeAllGrants(final SQLiteDatabase db) {
224 db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
225 }
226
227 private class DatabaseHelper extends SQLiteOpenHelper {
228 public DatabaseHelper(Context context) {
229 super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
230 }
231
232 @Override
233 public void onCreate(final SQLiteDatabase db) {
234 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
235 + GRANTS_ALIAS + " STRING NOT NULL, "
236 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
237 + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
238 }
239
240 @Override
241 public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
242 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
243
244 if (oldVersion == 1) {
245 // the first upgrade step goes here
246 oldVersion++;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700247 }
Brian Carlstrom7037b732011-06-30 15:04:49 -0700248 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700249 }
250
251 @Override public IBinder onBind(Intent intent) {
Brian Carlstrom7037b732011-06-30 15:04:49 -0700252 if (IKeyChainService.class.getName().equals(intent.getAction())) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700253 return mIKeyChainService;
254 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700255 return null;
256 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700257
258 @Override
259 protected void onHandleIntent(final Intent intent) {
260 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
261 purgeOldGrants();
262 }
263 }
264
265 private void purgeOldGrants() {
266 final PackageManager packageManager = getPackageManager();
267 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
268 Cursor cursor = null;
269 db.beginTransaction();
270 try {
271 cursor = db.query(TABLE_GRANTS,
272 new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
273 while (cursor.moveToNext()) {
274 final int uid = cursor.getInt(0);
275 final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
276 if (packageExists) {
277 continue;
278 }
279 Log.d(TAG, "deleting grants for UID " + uid
280 + " because its package is no longer installed");
281 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
282 new String[]{Integer.toString(uid)});
283 }
284 db.setTransactionSuccessful();
285 } finally {
286 if (cursor != null) {
287 cursor.close();
288 }
289 db.endTransaction();
290 }
291 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700292}