blob: 2564d03bfe7ea9dbe19d70b485773494154762ad [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;
Julia Reynolds3fb74492014-06-30 16:54:50 -040031import android.os.UserManager;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070032import android.security.Credentials;
33import android.security.IKeyChainService;
Selim Gurun39e36e52012-02-14 10:50:42 -080034import android.security.KeyChain;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070035import android.security.KeyStore;
36import android.util.Log;
37import java.io.ByteArrayInputStream;
Brian Carlstroma58db542011-05-11 23:02:20 -070038import java.io.IOException;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070039import java.security.cert.CertificateException;
40import java.security.cert.CertificateFactory;
41import java.security.cert.X509Certificate;
Fred Quintanafb2e18e2011-07-13 14:54:05 -070042
Kenny Root3048b6c2013-04-23 22:38:11 -070043import com.android.org.conscrypt.TrustedCertificateStore;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070044
Fred Quintanafb2e18e2011-07-13 14:54:05 -070045public class KeyChainService extends IntentService {
Selim Gurun39e36e52012-02-14 10:50:42 -080046
Fred Quintanafb2e18e2011-07-13 14:54:05 -070047 private static final String TAG = "KeyChain";
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070048
Fred Quintanafb2e18e2011-07-13 14:54:05 -070049 private static final String DATABASE_NAME = "grants.db";
50 private static final int DATABASE_VERSION = 1;
51 private static final String TABLE_GRANTS = "grants";
52 private static final String GRANTS_ALIAS = "alias";
53 private static final String GRANTS_GRANTEE_UID = "uid";
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070054
Fred Quintanafb2e18e2011-07-13 14:54:05 -070055 /** created in onCreate(), closed in onDestroy() */
56 public DatabaseHelper mDatabaseHelper;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070057
Fred Quintanafb2e18e2011-07-13 14:54:05 -070058 private static final String SELECTION_COUNT_OF_MATCHING_GRANTS =
59 "SELECT COUNT(*) FROM " + TABLE_GRANTS
60 + " WHERE " + GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
61
62 private static final String SELECT_GRANTS_BY_UID_AND_ALIAS =
63 GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?";
64
65 private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?";
66
67 public KeyChainService() {
68 super(KeyChainService.class.getSimpleName());
69 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070070
71 @Override public void onCreate() {
72 super.onCreate();
Fred Quintanafb2e18e2011-07-13 14:54:05 -070073 mDatabaseHelper = new DatabaseHelper(this);
74 }
75
76 @Override
77 public void onDestroy() {
78 super.onDestroy();
79 mDatabaseHelper.close();
80 mDatabaseHelper = null;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070081 }
82
83 private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070084 private final KeyStore mKeyStore = KeyStore.getInstance();
Brian Carlstroma58db542011-05-11 23:02:20 -070085 private final TrustedCertificateStore mTrustedCertificateStore
86 = new TrustedCertificateStore();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070087
Kenny Root6f1f03b2012-03-08 10:30:39 -080088 @Override
89 public String requestPrivateKey(String alias) {
90 checkArgs(alias);
91
92 final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias;
93 final int uid = Binder.getCallingUid();
94 if (!mKeyStore.grant(keystoreAlias, uid)) {
95 return null;
96 }
97
98 final StringBuilder sb = new StringBuilder();
99 sb.append(Process.SYSTEM_UID);
100 sb.append('_');
101 sb.append(keystoreAlias);
102
103 return sb.toString();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700104 }
105
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700106 @Override public byte[] getCertificate(String alias) {
Kenny Root6f1f03b2012-03-08 10:30:39 -0800107 checkArgs(alias);
108 return mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700109 }
110
Kenny Root6f1f03b2012-03-08 10:30:39 -0800111 private void checkArgs(String alias) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700112 if (alias == null) {
113 throw new NullPointerException("alias == null");
114 }
Kenny Root4ff22962013-02-14 10:17:06 -0800115 if (!mKeyStore.isUnlocked()) {
Nick Kralevichc8b04632012-05-21 15:13:07 -0700116 throw new IllegalStateException("keystore is "
117 + mKeyStore.state().toString());
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700118 }
Nick Kralevichc8b04632012-05-21 15:13:07 -0700119
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700120 final int callingUid = getCallingUid();
121 if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) {
122 throw new IllegalStateException("uid " + callingUid
123 + " doesn't have permission to access the requested alias");
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700124 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700125 }
126
Brian Carlstroma58db542011-05-11 23:02:20 -0700127 @Override public void installCaCertificate(byte[] caCertificate) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700128 checkCertInstallerOrSystemCaller();
Julia Reynolds3fb74492014-06-30 16:54:50 -0400129 checkUserRestriction();
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();
Julia Reynolds3fb74492014-06-30 16:54:50 -0400150 checkUserRestriction();
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700151 removeAllGrants(mDatabaseHelper.getWritableDatabase());
Brian Carlstroma58db542011-05-11 23:02:20 -0700152 boolean ok = true;
Brian Carlstroma58db542011-05-11 23:02:20 -0700153 synchronized (mTrustedCertificateStore) {
154 // delete user-installed CA certs
155 for (String alias : mTrustedCertificateStore.aliases()) {
156 if (TrustedCertificateStore.isUser(alias)) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700157 if (!deleteCertificateEntry(alias)) {
Brian Carlstroma58db542011-05-11 23:02:20 -0700158 ok = false;
159 }
160 }
161 }
Brian Carlstroma58db542011-05-11 23:02:20 -0700162 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800163 broadcastStorageChange();
164 return ok;
Brian Carlstroma58db542011-05-11 23:02:20 -0700165 }
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700166
167 @Override public boolean deleteCaCertificate(String alias) {
168 // only Settings should be able to delete
169 checkSystemCaller();
Julia Reynolds3fb74492014-06-30 16:54:50 -0400170 checkUserRestriction();
Selim Gurun39e36e52012-02-14 10:50:42 -0800171 boolean ok = true;
172 synchronized (mTrustedCertificateStore) {
173 ok = deleteCertificateEntry(alias);
174 }
175 broadcastStorageChange();
176 return ok;
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700177 }
178
179 private boolean deleteCertificateEntry(String alias) {
180 try {
181 mTrustedCertificateStore.deleteCertificateEntry(alias);
182 return true;
183 } catch (IOException e) {
184 Log.w(TAG, "Problem removing CA certificate " + alias, e);
185 return false;
186 } catch (CertificateException e) {
187 Log.w(TAG, "Problem removing CA certificate " + alias, e);
188 return false;
189 }
190 }
191
192 private void checkCertInstallerOrSystemCaller() {
193 String actual = checkCaller("com.android.certinstaller");
194 if (actual == null) {
195 return;
196 }
197 checkSystemCaller();
198 }
199 private void checkSystemCaller() {
200 String actual = checkCaller("android.uid.system:1000");
201 if (actual != null) {
202 throw new IllegalStateException(actual);
203 }
204 }
Julia Reynolds3fb74492014-06-30 16:54:50 -0400205 private void checkUserRestriction() {
206 UserManager um = (UserManager) getSystemService(USER_SERVICE);
207 if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
208 throw new SecurityException("User cannot modify credentials");
209 }
210 }
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700211 /**
212 * Returns null if actually caller is expected, otherwise return bad package to report
213 */
214 private String checkCaller(String expectedPackage) {
215 String actualPackage = getPackageManager().getNameForUid(getCallingUid());
216 return (!expectedPackage.equals(actualPackage)) ? actualPackage : null;
217 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700218
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700219 @Override public boolean hasGrant(int uid, String alias) {
220 checkSystemCaller();
221 return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700222 }
223
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700224 @Override public void setGrant(int uid, String alias, boolean value) {
225 checkSystemCaller();
226 setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value);
Selim Gurun39e36e52012-02-14 10:50:42 -0800227 broadcastStorageChange();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700228 }
229 };
230
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700231 private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) {
232 final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS,
233 new String[]{String.valueOf(uid), alias});
234 return numMatches > 0;
235 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700236
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700237 private void setGrantInternal(final SQLiteDatabase db,
238 final int uid, final String alias, final boolean value) {
239 if (value) {
240 if (!hasGrantInternal(db, uid, alias)) {
241 final ContentValues values = new ContentValues();
242 values.put(GRANTS_ALIAS, alias);
243 values.put(GRANTS_GRANTEE_UID, uid);
244 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values);
245 }
246 } else {
247 db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS,
248 new String[]{String.valueOf(uid), alias});
249 }
250 }
251
252 private void removeAllGrants(final SQLiteDatabase db) {
253 db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */);
254 }
255
256 private class DatabaseHelper extends SQLiteOpenHelper {
257 public DatabaseHelper(Context context) {
258 super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION);
259 }
260
261 @Override
262 public void onCreate(final SQLiteDatabase db) {
263 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
264 + GRANTS_ALIAS + " STRING NOT NULL, "
265 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
266 + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))");
267 }
268
269 @Override
270 public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) {
271 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
272
273 if (oldVersion == 1) {
274 // the first upgrade step goes here
275 oldVersion++;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700276 }
Brian Carlstrom7037b732011-06-30 15:04:49 -0700277 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700278 }
279
280 @Override public IBinder onBind(Intent intent) {
Brian Carlstrom7037b732011-06-30 15:04:49 -0700281 if (IKeyChainService.class.getName().equals(intent.getAction())) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700282 return mIKeyChainService;
283 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700284 return null;
285 }
Fred Quintanafb2e18e2011-07-13 14:54:05 -0700286
287 @Override
288 protected void onHandleIntent(final Intent intent) {
289 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
290 purgeOldGrants();
291 }
292 }
293
294 private void purgeOldGrants() {
295 final PackageManager packageManager = getPackageManager();
296 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
297 Cursor cursor = null;
298 db.beginTransaction();
299 try {
300 cursor = db.query(TABLE_GRANTS,
301 new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null);
302 while (cursor.moveToNext()) {
303 final int uid = cursor.getInt(0);
304 final boolean packageExists = packageManager.getPackagesForUid(uid) != null;
305 if (packageExists) {
306 continue;
307 }
308 Log.d(TAG, "deleting grants for UID " + uid
309 + " because its package is no longer installed");
310 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID,
311 new String[]{Integer.toString(uid)});
312 }
313 db.setTransactionSuccessful();
314 } finally {
315 if (cursor != null) {
316 cursor.close();
317 }
318 db.endTransaction();
319 }
320 }
Selim Gurun39e36e52012-02-14 10:50:42 -0800321
322 private void broadcastStorageChange() {
323 Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED);
324 sendBroadcast(intent);
325 }
326
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700327}