KeyChainService.java revision 031612ec11a5bd212a1cdcb824576d5542270b2d
1/* 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 19import android.app.IntentService; 20import android.content.ContentValues; 21import android.content.Context; 22import android.content.Intent; 23import android.content.pm.PackageManager; 24import android.content.pm.ParceledListSlice; 25import android.database.Cursor; 26import android.database.DatabaseUtils; 27import android.database.sqlite.SQLiteDatabase; 28import android.database.sqlite.SQLiteOpenHelper; 29import android.os.Binder; 30import android.os.IBinder; 31import android.os.Process; 32import android.os.UserManager; 33import android.security.Credentials; 34import android.security.IKeyChainService; 35import android.security.KeyChain; 36import android.security.KeyStore; 37import android.util.Log; 38import com.android.internal.util.ParcelableString; 39 40import java.io.ByteArrayInputStream; 41import java.io.IOException; 42import java.security.cert.CertificateException; 43import java.security.cert.CertificateEncodingException; 44import java.security.cert.CertificateFactory; 45import java.security.cert.X509Certificate; 46import java.util.Set; 47import java.util.List; 48import java.util.ArrayList; 49import java.util.Collections; 50 51import com.android.org.conscrypt.TrustedCertificateStore; 52 53public class KeyChainService extends IntentService { 54 55 private static final String TAG = "KeyChain"; 56 57 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"; 62 63 /** created in onCreate(), closed in onDestroy() */ 64 public DatabaseHelper mDatabaseHelper; 65 66 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 } 78 79 @Override public void onCreate() { 80 super.onCreate(); 81 mDatabaseHelper = new DatabaseHelper(this); 82 } 83 84 @Override 85 public void onDestroy() { 86 super.onDestroy(); 87 mDatabaseHelper.close(); 88 mDatabaseHelper = null; 89 } 90 91 private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() { 92 private final KeyStore mKeyStore = KeyStore.getInstance(); 93 private final TrustedCertificateStore mTrustedCertificateStore 94 = new TrustedCertificateStore(); 95 96 @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 } 105 106 final StringBuilder sb = new StringBuilder(); 107 sb.append(Process.SYSTEM_UID); 108 sb.append('_'); 109 sb.append(keystoreAlias); 110 111 return sb.toString(); 112 } 113 114 @Override public byte[] getCertificate(String alias) { 115 checkArgs(alias); 116 return mKeyStore.get(Credentials.USER_CERTIFICATE + alias); 117 } 118 119 private void checkArgs(String alias) { 120 if (alias == null) { 121 throw new NullPointerException("alias == null"); 122 } 123 if (!mKeyStore.isUnlocked()) { 124 throw new IllegalStateException("keystore is " 125 + mKeyStore.state().toString()); 126 } 127 128 final int callingUid = getCallingUid(); 129 if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) { 130 throw new IllegalStateException("uid " + callingUid 131 + " doesn't have permission to access the requested alias"); 132 } 133 } 134 135 @Override public void installCaCertificate(byte[] caCertificate) { 136 checkCertInstallerOrSystemCaller(); 137 checkUserRestriction(); 138 try { 139 synchronized (mTrustedCertificateStore) { 140 mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate)); 141 } 142 } catch (IOException e) { 143 throw new IllegalStateException(e); 144 } catch (CertificateException e) { 145 throw new IllegalStateException(e); 146 } 147 broadcastStorageChange(); 148 } 149 150 private X509Certificate parseCertificate(byte[] bytes) throws CertificateException { 151 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 152 return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes)); 153 } 154 155 @Override public boolean reset() { 156 // only Settings should be able to reset 157 checkSystemCaller(); 158 checkUserRestriction(); 159 removeAllGrants(mDatabaseHelper.getWritableDatabase()); 160 boolean ok = true; 161 synchronized (mTrustedCertificateStore) { 162 // delete user-installed CA certs 163 for (String alias : mTrustedCertificateStore.aliases()) { 164 if (TrustedCertificateStore.isUser(alias)) { 165 if (!deleteCertificateEntry(alias)) { 166 ok = false; 167 } 168 } 169 } 170 } 171 broadcastStorageChange(); 172 return ok; 173 } 174 175 @Override public boolean deleteCaCertificate(String alias) { 176 // only Settings should be able to delete 177 checkSystemCaller(); 178 checkUserRestriction(); 179 boolean ok = true; 180 synchronized (mTrustedCertificateStore) { 181 ok = deleteCertificateEntry(alias); 182 } 183 broadcastStorageChange(); 184 return ok; 185 } 186 187 private boolean deleteCertificateEntry(String alias) { 188 try { 189 mTrustedCertificateStore.deleteCertificateEntry(alias); 190 return true; 191 } catch (IOException e) { 192 Log.w(TAG, "Problem removing CA certificate " + alias, e); 193 return false; 194 } catch (CertificateException e) { 195 Log.w(TAG, "Problem removing CA certificate " + alias, e); 196 return false; 197 } 198 } 199 200 private void checkCertInstallerOrSystemCaller() { 201 String actual = checkCaller("com.android.certinstaller"); 202 if (actual == null) { 203 return; 204 } 205 checkSystemCaller(); 206 } 207 private void checkSystemCaller() { 208 String actual = checkCaller("android.uid.system:1000"); 209 if (actual != null) { 210 throw new IllegalStateException(actual); 211 } 212 } 213 private void checkUserRestriction() { 214 UserManager um = (UserManager) getSystemService(USER_SERVICE); 215 if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) { 216 throw new SecurityException("User cannot modify credentials"); 217 } 218 } 219 /** 220 * Returns null if actually caller is expected, otherwise return bad package to report 221 */ 222 private String checkCaller(String expectedPackage) { 223 String actualPackage = getPackageManager().getNameForUid(getCallingUid()); 224 return (!expectedPackage.equals(actualPackage)) ? actualPackage : null; 225 } 226 227 @Override public boolean hasGrant(int uid, String alias) { 228 checkSystemCaller(); 229 return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias); 230 } 231 232 @Override public void setGrant(int uid, String alias, boolean value) { 233 checkSystemCaller(); 234 setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value); 235 broadcastStorageChange(); 236 } 237 238 @Override 239 public ParceledListSlice<ParcelableString> getUserCaAliases() { 240 synchronized (mTrustedCertificateStore) { 241 Set<String> aliasSet = mTrustedCertificateStore.userAliases(); 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 252 @Override 253 public ParceledListSlice<ParcelableString> getSystemCaAliases() { 254 synchronized (mTrustedCertificateStore) { 255 Set<String> aliasSet = mTrustedCertificateStore.allSystemAliases(); 256 List<ParcelableString> aliases = new ArrayList<ParcelableString>(aliasSet.size()); 257 for (String alias : aliasSet) { 258 ParcelableString parcelableString = new ParcelableString(); 259 parcelableString.string = alias; 260 aliases.add(parcelableString); 261 } 262 return new ParceledListSlice<ParcelableString>(aliases); 263 } 264 } 265 266 @Override 267 public boolean containsCaAlias(String alias) { 268 return mTrustedCertificateStore.containsAlias(alias); 269 } 270 271 @Override 272 public byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem) { 273 synchronized (mTrustedCertificateStore) { 274 X509Certificate certificate = (X509Certificate) mTrustedCertificateStore 275 .getCertificate(alias, includeDeletedSystem); 276 if (certificate == null) { 277 return null; 278 } 279 try { 280 return certificate.getEncoded(); 281 } catch (CertificateEncodingException e) { 282 return null; 283 } 284 } 285 } 286 287 @Override 288 public List<String> getCaCertificateChainAliases(String rootAlias, 289 boolean includeDeletedSystem) { 290 synchronized (mTrustedCertificateStore) { 291 X509Certificate root = (X509Certificate) mTrustedCertificateStore.getCertificate( 292 rootAlias, includeDeletedSystem); 293 try { 294 List<X509Certificate> chain = mTrustedCertificateStore.getCertificateChain( 295 root); 296 List<String> aliases = new ArrayList<String>(chain.size()); 297 final int n = chain.size(); 298 for (int i = 0; i < n; ++i) { 299 String alias = mTrustedCertificateStore.getCertificateAlias(chain.get(i), 300 true); 301 if (alias != null) { 302 aliases.add(alias); 303 } 304 } 305 return aliases; 306 } catch (CertificateException e) { 307 return Collections.emptyList(); 308 } 309 } 310 } 311 }; 312 313 private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) { 314 final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS, 315 new String[]{String.valueOf(uid), alias}); 316 return numMatches > 0; 317 } 318 319 private void setGrantInternal(final SQLiteDatabase db, 320 final int uid, final String alias, final boolean value) { 321 if (value) { 322 if (!hasGrantInternal(db, uid, alias)) { 323 final ContentValues values = new ContentValues(); 324 values.put(GRANTS_ALIAS, alias); 325 values.put(GRANTS_GRANTEE_UID, uid); 326 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values); 327 } 328 } else { 329 db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS, 330 new String[]{String.valueOf(uid), alias}); 331 } 332 } 333 334 private void removeAllGrants(final SQLiteDatabase db) { 335 db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */); 336 } 337 338 private class DatabaseHelper extends SQLiteOpenHelper { 339 public DatabaseHelper(Context context) { 340 super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION); 341 } 342 343 @Override 344 public void onCreate(final SQLiteDatabase db) { 345 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( " 346 + GRANTS_ALIAS + " STRING NOT NULL, " 347 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, " 348 + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))"); 349 } 350 351 @Override 352 public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) { 353 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); 354 355 if (oldVersion == 1) { 356 // the first upgrade step goes here 357 oldVersion++; 358 } 359 } 360 } 361 362 @Override public IBinder onBind(Intent intent) { 363 if (IKeyChainService.class.getName().equals(intent.getAction())) { 364 return mIKeyChainService; 365 } 366 return null; 367 } 368 369 @Override 370 protected void onHandleIntent(final Intent intent) { 371 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { 372 purgeOldGrants(); 373 } 374 } 375 376 private void purgeOldGrants() { 377 final PackageManager packageManager = getPackageManager(); 378 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); 379 Cursor cursor = null; 380 db.beginTransaction(); 381 try { 382 cursor = db.query(TABLE_GRANTS, 383 new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null); 384 while (cursor.moveToNext()) { 385 final int uid = cursor.getInt(0); 386 final boolean packageExists = packageManager.getPackagesForUid(uid) != null; 387 if (packageExists) { 388 continue; 389 } 390 Log.d(TAG, "deleting grants for UID " + uid 391 + " because its package is no longer installed"); 392 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID, 393 new String[]{Integer.toString(uid)}); 394 } 395 db.setTransactionSuccessful(); 396 } finally { 397 if (cursor != null) { 398 cursor.close(); 399 } 400 db.endTransaction(); 401 } 402 } 403 404 private void broadcastStorageChange() { 405 Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED); 406 sendBroadcast(intent); 407 } 408 409} 410