blob: 8d26643a5f4ef3d678feb58479ce991f413aff2c [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;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070031import android.security.Credentials;
32import android.security.IKeyChainService;
Selim Gurun39e36e52012-02-14 10:50:42 -080033import android.security.KeyChain;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070034import android.security.KeyStore;
35import android.util.Log;
36import java.io.ByteArrayInputStream;
Brian Carlstroma58db542011-05-11 23:02:20 -070037import java.io.IOException;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070038import java.security.cert.CertificateException;
39import java.security.cert.CertificateFactory;
40import java.security.cert.X509Certificate;
Fred Quintanafb2e18e2011-07-13 14:54:05 -070041
Brian Carlstroma58db542011-05-11 23:02:20 -070042import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070043
Fred Quintanafb2e18e2011-07-13 14:54:05 -070044public class KeyChainService extends IntentService {
Selim Gurun39e36e52012-02-14 10:50:42 -080045
Fred Quintanafb2e18e2011-07-13 14:54:05 -070046 private static final String TAG = "KeyChain";
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070047
Fred Quintanafb2e18e2011-07-13 14:54:05 -070048 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";
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070053
Fred Quintanafb2e18e2011-07-13 14:54:05 -070054 /** created in onCreate(), closed in onDestroy() */
55 public DatabaseHelper mDatabaseHelper;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070056
Fred Quintanafb2e18e2011-07-13 14:54:05 -070057 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
66 public KeyChainService() {
67 super(KeyChainService.class.getSimpleName());
68 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070069
70 @Override public void onCreate() {
71 super.onCreate();
Fred Quintanafb2e18e2011-07-13 14:54:05 -070072 mDatabaseHelper = new DatabaseHelper(this);
73 }
74
75 @Override
76 public void onDestroy() {
77 super.onDestroy();
78 mDatabaseHelper.close();
79 mDatabaseHelper = null;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070080 }
81
82 private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070083 private final KeyStore mKeyStore = KeyStore.getInstance();
Brian Carlstroma58db542011-05-11 23:02:20 -070084 private final TrustedCertificateStore mTrustedCertificateStore
85 = new TrustedCertificateStore();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070086
Kenny Root6f1f03b2012-03-08 10:30:39 -080087 @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();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700103 }
104
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700105 @Override public byte[] getCertificate(String alias) {
Kenny Root6f1f03b2012-03-08 10:30:39 -0800106 checkArgs(alias);
107 return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700108 }
109
Kenny Root6f1f03b2012-03-08 10:30:39 -0800110 private void checkArgs(String alias) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700111 if (alias == null) {
112 throw new NullPointerException("alias == null");
113 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700114 if (!isKeyStoreUnlocked()) {
115 throw new IllegalStateException("keystore locked");
116 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700117 final int callingUid = getCallingUid();
118 if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
119 throw new IllegalStateException("uid " + callingUid
120 + " doesn't have permission to access the requested alias");
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700121 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700122 }
123
Brian Carlstrom5aeadd92011-05-17 00:40:33 -0700124 private boolean isKeyStoreUnlocked() {
Brian Carlstrome3b33902011-05-31 01:06:20 -0700125 return (mKeyStore.state() == KeyStore.State.UNLOCKED);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700126 }
Brian Carlstroma58db542011-05-11 23:02:20 -0700127
128 @Override public void installCaCertificate(byte[] caCertificate) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700129 checkCertInstallerOrSystemCaller();
Brian Carlstroma58db542011-05-11 23:02:20 -0700130 try {
131 synchronized (mTrustedCertificateStore) {
132 mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate));
133 }
134 } catch (IOException e) {
135 throw new IllegalStateException(e);
136 } catch (CertificateException e) {
137 throw new IllegalStateException(e);
138 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800139 broadcastStorageChange();
Brian Carlstroma58db542011-05-11 23:02:20 -0700140 }
Brian Carlstrom5aeadd92011-05-17 00:40:33 -0700141
142 private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
143 CertificateFactory cf = CertificateFactory.getInstance("X.509");
144 return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
145 }
146
Brian Carlstroma58db542011-05-11 23:02:20 -0700147 @Override public boolean reset() {
148 // only Settings should be able to reset
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700149 checkSystemCaller();
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700150 removeAllGrants(mDatabaseHelper.getWritableDatabase());
Brian Carlstroma58db542011-05-11 23:02:20 -0700151 boolean ok = true;
Brian Carlstroma58db542011-05-11 23:02:20 -0700152 synchronized (mTrustedCertificateStore) {
153 // delete user-installed CA certs
154 for (String alias : mTrustedCertificateStore.aliases()) {
155 if (TrustedCertificateStore.isUser(alias)) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700156 if (!deleteCertificateEntry(alias)) {
Brian Carlstroma58db542011-05-11 23:02:20 -0700157 ok = false;
158 }
159 }
160 }
Brian Carlstroma58db542011-05-11 23:02:20 -0700161 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800162 broadcastStorageChange();
163 return ok;
Brian Carlstroma58db542011-05-11 23:02:20 -0700164 }
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700165
166 @Override public boolean deleteCaCertificate(String alias) {
167 // only Settings should be able to delete
168 checkSystemCaller();
Selim Gurun39e36e52012-02-14 10:50:42 -0800169 boolean ok = true;
170 synchronized (mTrustedCertificateStore) {
171 ok = deleteCertificateEntry(alias);
172 }
173 broadcastStorageChange();
174 return ok;
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700175 }
176
177 private boolean deleteCertificateEntry(String alias) {
178 try {
179 mTrustedCertificateStore.deleteCertificateEntry(alias);
180 return true;
181 } catch (IOException e) {
182 Log.w(TAG, "Problem removing CA certificate " + alias, e);
183 return false;
184 } catch (CertificateException e) {
185 Log.w(TAG, "Problem removing CA certificate " + alias, e);
186 return false;
187 }
188 }
189
190 private void checkCertInstallerOrSystemCaller() {
191 String actual = checkCaller("com.android.certinstaller");
192 if (actual == null) {
193 return;
194 }
195 checkSystemCaller();
196 }
197 private void checkSystemCaller() {
198 String actual = checkCaller("android.uid.system:1000");
199 if (actual != null) {
200 throw new IllegalStateException(actual);
201 }
202 }
203 /**
204 * Returns null if actually caller is expected, otherwise return bad package to report
205 */
206 private String checkCaller(String expectedPackage) {
207 String actualPackage = getPackageManager().getNameForUid(getCallingUid());
208 return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
209 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700210
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700211 @Override public boolean hasGrant(int uid, String alias) {
212 checkSystemCaller();
213 return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700214 }
215
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700216 @Override public void setGrant(int uid, String alias, boolean value) {
217 checkSystemCaller();
218 setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
Selim Gurun39e36e52012-02-14 10:50:42 -0800219 broadcastStorageChange();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700220 }
221 };
222
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700223 private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
224 final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
225 new String[]{String.valueOf(uid), alias});
226 return numMatches > 0;
227 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700228
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700229 private void setGrantInternal(final SQLiteDatabase db,
230 final int uid, final String alias, final boolean value) {
231 if (value) {
232 if (!hasGrantInternal(db, uid, alias)) {
233 final ContentValues values = new ContentValues();
234 values.put(GRANTS_ALIAS, alias);
235 values.put(GRANTS_GRANTEE_UID, uid);
236 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
237 }
238 } else {
239 db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
240 new String[]{String.valueOf(uid), alias});
241 }
242 }
243
244 private void removeAllGrants(final SQLiteDatabase db) {
245 db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
246 }
247
248 private class DatabaseHelper extends SQLiteOpenHelper {
249 public DatabaseHelper(Context context) {
250 super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
251 }
252
253 @Override
254 public void onCreate(final SQLiteDatabase db) {
255 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
256 + GRANTS_ALIAS + " STRING NOT NULL, "
257 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
258 + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
259 }
260
261 @Override
262 public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
263 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
264
265 if (oldVersion == 1) {
266 // the first upgrade step goes here
267 oldVersion++;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700268 }
Brian Carlstrom7037b732011-06-30 15:04:49 -0700269 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700270 }
271
272 @Override public IBinder onBind(Intent intent) {
Brian Carlstrom7037b732011-06-30 15:04:49 -0700273 if (IKeyChainService.class.getName().equals(intent.getAction())) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700274 return mIKeyChainService;
275 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700276 return null;
277 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700278
279 @Override
280 protected void onHandleIntent(final Intent intent) {
281 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
282 purgeOldGrants();
283 }
284 }
285
286 private void purgeOldGrants() {
287 final PackageManager packageManager = getPackageManager();
288 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
289 Cursor cursor = null;
290 db.beginTransaction();
291 try {
292 cursor = db.query(TABLE_GRANTS,
293 new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
294 while (cursor.moveToNext()) {
295 final int uid = cursor.getInt(0);
296 final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
297 if (packageExists) {
298 continue;
299 }
300 Log.d(TAG, "deleting grants for UID " + uid
301 + " because its package is no longer installed");
302 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
303 new String[]{Integer.toString(uid)});
304 }
305 db.setTransactionSuccessful();
306 } finally {
307 if (cursor != null) {
308 cursor.close();
309 }
310 db.endTransaction();
311 }
312 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800313
314 private void broadcastStorageChange() {
315 Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
316 sendBroadcast(intent);
317 }
318
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700319}