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 17 package com.android.keychain; 18 19 import android.app.IntentService; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ParceledListSlice; 25 import android.database.Cursor; 26 import android.database.DatabaseUtils; 27 import android.database.sqlite.SQLiteDatabase; 28 import android.database.sqlite.SQLiteOpenHelper; 29 import android.os.Binder; 30 import android.os.IBinder; 31 import android.os.Process; 32 import android.os.UserHandle; 33 import android.os.UserManager; 34 import android.security.Credentials; 35 import android.security.IKeyChainService; 36 import android.security.KeyChain; 37 import android.security.KeyStore; 38 import android.util.Log; 39 import com.android.internal.util.ParcelableString; 40 import java.io.ByteArrayInputStream; 41 import java.io.IOException; 42 import java.security.cert.CertificateException; 43 import java.security.cert.CertificateEncodingException; 44 import java.security.cert.CertificateFactory; 45 import java.security.cert.X509Certificate; 46 import java.util.Set; 47 import java.util.List; 48 import java.util.ArrayList; 49 import java.util.Collections; 50 51 import com.android.org.conscrypt.TrustedCertificateStore; 52 53 public 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 private static final String SELECTION_GRANTS_BY_ALIAS = GRANTS_ALIAS + "=?"; 76 KeyChainService()77 public KeyChainService() { 78 super(KeyChainService.class.getSimpleName()); 79 } 80 onCreate()81 @Override public void onCreate() { 82 super.onCreate(); 83 mDatabaseHelper = new DatabaseHelper(this); 84 } 85 86 @Override onDestroy()87 public void onDestroy() { 88 super.onDestroy(); 89 mDatabaseHelper.close(); 90 mDatabaseHelper = null; 91 } 92 93 private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() { 94 private final KeyStore mKeyStore = KeyStore.getInstance(); 95 private final TrustedCertificateStore mTrustedCertificateStore 96 = new TrustedCertificateStore(); 97 98 @Override 99 public String requestPrivateKey(String alias) { 100 checkArgs(alias); 101 102 final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias; 103 final int uid = Binder.getCallingUid(); 104 if (!mKeyStore.grant(keystoreAlias, uid)) { 105 return null; 106 } 107 final int userHandle = UserHandle.getUserId(uid); 108 final int systemUidForUser = UserHandle.getUid(userHandle, Process.SYSTEM_UID); 109 110 final StringBuilder sb = new StringBuilder(); 111 sb.append(systemUidForUser); 112 sb.append('_'); 113 sb.append(keystoreAlias); 114 115 return sb.toString(); 116 } 117 118 @Override public byte[] getCertificate(String alias) { 119 checkArgs(alias); 120 return mKeyStore.get(Credentials.USER_CERTIFICATE + alias); 121 } 122 123 @Override public byte[] getCaCertificates(String alias) { 124 checkArgs(alias); 125 return mKeyStore.get(Credentials.CA_CERTIFICATE + alias); 126 } 127 128 private void checkArgs(String alias) { 129 if (alias == null) { 130 throw new NullPointerException("alias == null"); 131 } 132 if (!mKeyStore.isUnlocked()) { 133 throw new IllegalStateException("keystore is " 134 + mKeyStore.state().toString()); 135 } 136 137 final int callingUid = getCallingUid(); 138 if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) { 139 throw new IllegalStateException("uid " + callingUid 140 + " doesn't have permission to access the requested alias"); 141 } 142 } 143 144 @Override public void installCaCertificate(byte[] caCertificate) { 145 checkCertInstallerOrSystemCaller(); 146 checkUserRestriction(); 147 try { 148 synchronized (mTrustedCertificateStore) { 149 mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate)); 150 } 151 } catch (IOException e) { 152 throw new IllegalStateException(e); 153 } catch (CertificateException e) { 154 throw new IllegalStateException(e); 155 } 156 broadcastStorageChange(); 157 } 158 159 /** 160 * Install a key pair to the keystore. 161 * 162 * @param privateKey The private key associated with the client certificate 163 * @param userCertificate The client certificate to be installed 164 * @param userCertificateChain The rest of the chain for the client certificate 165 * @param alias The alias under which the key pair is installed 166 * @return Whether the operation succeeded or not. 167 */ 168 @Override public boolean installKeyPair(byte[] privateKey, byte[] userCertificate, 169 byte[] userCertificateChain, String alias) { 170 checkCertInstallerOrSystemCaller(); 171 if (!mKeyStore.isUnlocked()) { 172 Log.e(TAG, "Keystore is " + mKeyStore.state().toString() + ". Credentials cannot" 173 + " be installed until device is unlocked"); 174 return false; 175 } 176 if (!removeKeyPair(alias)) { 177 return false; 178 } 179 if (!mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, privateKey, -1, 180 KeyStore.FLAG_ENCRYPTED)) { 181 Log.e(TAG, "Failed to import private key " + alias); 182 return false; 183 } 184 if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertificate, -1, 185 KeyStore.FLAG_ENCRYPTED)) { 186 Log.e(TAG, "Failed to import user certificate " + userCertificate); 187 if (!mKeyStore.delete(Credentials.USER_PRIVATE_KEY + alias)) { 188 Log.e(TAG, "Failed to delete private key after certificate importing failed"); 189 } 190 return false; 191 } 192 if (userCertificateChain != null && userCertificateChain.length > 0) { 193 if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, userCertificateChain, -1, 194 KeyStore.FLAG_ENCRYPTED)) { 195 Log.e(TAG, "Failed to import certificate chain" + userCertificateChain); 196 if (!removeKeyPair(alias)) { 197 Log.e(TAG, "Failed to clean up key chain after certificate chain" 198 + " importing failed"); 199 } 200 return false; 201 } 202 } 203 broadcastStorageChange(); 204 return true; 205 } 206 207 @Override public boolean removeKeyPair(String alias) { 208 checkCertInstallerOrSystemCaller(); 209 if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) { 210 return false; 211 } 212 removeGrantsForAlias(alias); 213 broadcastStorageChange(); 214 return true; 215 } 216 217 private X509Certificate parseCertificate(byte[] bytes) throws CertificateException { 218 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 219 return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes)); 220 } 221 222 @Override public boolean reset() { 223 // only Settings should be able to reset 224 checkSystemCaller(); 225 checkUserRestriction(); 226 removeAllGrants(mDatabaseHelper.getWritableDatabase()); 227 boolean ok = true; 228 synchronized (mTrustedCertificateStore) { 229 // delete user-installed CA certs 230 for (String alias : mTrustedCertificateStore.aliases()) { 231 if (TrustedCertificateStore.isUser(alias)) { 232 if (!deleteCertificateEntry(alias)) { 233 ok = false; 234 } 235 } 236 } 237 } 238 broadcastStorageChange(); 239 return ok; 240 } 241 242 @Override public boolean deleteCaCertificate(String alias) { 243 // only Settings should be able to delete 244 checkSystemCaller(); 245 checkUserRestriction(); 246 boolean ok = true; 247 synchronized (mTrustedCertificateStore) { 248 ok = deleteCertificateEntry(alias); 249 } 250 broadcastStorageChange(); 251 return ok; 252 } 253 254 private boolean deleteCertificateEntry(String alias) { 255 try { 256 mTrustedCertificateStore.deleteCertificateEntry(alias); 257 return true; 258 } catch (IOException e) { 259 Log.w(TAG, "Problem removing CA certificate " + alias, e); 260 return false; 261 } catch (CertificateException e) { 262 Log.w(TAG, "Problem removing CA certificate " + alias, e); 263 return false; 264 } 265 } 266 267 private void checkCertInstallerOrSystemCaller() { 268 String actual = checkCaller("com.android.certinstaller"); 269 if (actual == null) { 270 return; 271 } 272 checkSystemCaller(); 273 } 274 private void checkSystemCaller() { 275 String actual = checkCaller("android.uid.system:1000"); 276 if (actual != null) { 277 throw new IllegalStateException(actual); 278 } 279 } 280 private void checkUserRestriction() { 281 UserManager um = (UserManager) getSystemService(USER_SERVICE); 282 if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) { 283 throw new SecurityException("User cannot modify credentials"); 284 } 285 } 286 /** 287 * Returns null if actually caller is expected, otherwise return bad package to report 288 */ 289 private String checkCaller(String expectedPackage) { 290 String actualPackage = getPackageManager().getNameForUid(getCallingUid()); 291 return (!expectedPackage.equals(actualPackage)) ? actualPackage : null; 292 } 293 294 @Override public boolean hasGrant(int uid, String alias) { 295 checkSystemCaller(); 296 return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias); 297 } 298 299 @Override public void setGrant(int uid, String alias, boolean value) { 300 checkSystemCaller(); 301 setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value); 302 broadcastStorageChange(); 303 } 304 305 private ParceledListSlice<ParcelableString> makeAliasesParcelableSynchronised( 306 Set<String> aliasSet) { 307 List<ParcelableString> aliases = new ArrayList<ParcelableString>(aliasSet.size()); 308 for (String alias : aliasSet) { 309 ParcelableString parcelableString = new ParcelableString(); 310 parcelableString.string = alias; 311 aliases.add(parcelableString); 312 } 313 return new ParceledListSlice<ParcelableString>(aliases); 314 } 315 316 @Override 317 public ParceledListSlice<ParcelableString> getUserCaAliases() { 318 synchronized (mTrustedCertificateStore) { 319 Set<String> aliasSet = mTrustedCertificateStore.userAliases(); 320 return makeAliasesParcelableSynchronised(aliasSet); 321 } 322 } 323 324 @Override 325 public ParceledListSlice<ParcelableString> getSystemCaAliases() { 326 synchronized (mTrustedCertificateStore) { 327 Set<String> aliasSet = mTrustedCertificateStore.allSystemAliases(); 328 return makeAliasesParcelableSynchronised(aliasSet); 329 } 330 } 331 332 @Override 333 public boolean containsCaAlias(String alias) { 334 return mTrustedCertificateStore.containsAlias(alias); 335 } 336 337 @Override 338 public byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem) { 339 synchronized (mTrustedCertificateStore) { 340 X509Certificate certificate = (X509Certificate) mTrustedCertificateStore 341 .getCertificate(alias, includeDeletedSystem); 342 if (certificate == null) { 343 Log.w(TAG, "Could not find CA certificate " + alias); 344 return null; 345 } 346 try { 347 return certificate.getEncoded(); 348 } catch (CertificateEncodingException e) { 349 Log.w(TAG, "Error while encoding CA certificate " + alias); 350 return null; 351 } 352 } 353 } 354 355 @Override 356 public List<String> getCaCertificateChainAliases(String rootAlias, 357 boolean includeDeletedSystem) { 358 synchronized (mTrustedCertificateStore) { 359 X509Certificate root = (X509Certificate) mTrustedCertificateStore.getCertificate( 360 rootAlias, includeDeletedSystem); 361 try { 362 List<X509Certificate> chain = mTrustedCertificateStore.getCertificateChain( 363 root); 364 List<String> aliases = new ArrayList<String>(chain.size()); 365 final int n = chain.size(); 366 for (int i = 0; i < n; ++i) { 367 String alias = mTrustedCertificateStore.getCertificateAlias(chain.get(i), 368 true); 369 if (alias != null) { 370 aliases.add(alias); 371 } 372 } 373 return aliases; 374 } catch (CertificateException e) { 375 Log.w(TAG, "Error retrieving cert chain for root " + rootAlias); 376 return Collections.emptyList(); 377 } 378 } 379 } 380 }; 381 hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias)382 private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) { 383 final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS, 384 new String[]{String.valueOf(uid), alias}); 385 return numMatches > 0; 386 } 387 setGrantInternal(final SQLiteDatabase db, final int uid, final String alias, final boolean value)388 private void setGrantInternal(final SQLiteDatabase db, 389 final int uid, final String alias, final boolean value) { 390 if (value) { 391 if (!hasGrantInternal(db, uid, alias)) { 392 final ContentValues values = new ContentValues(); 393 values.put(GRANTS_ALIAS, alias); 394 values.put(GRANTS_GRANTEE_UID, uid); 395 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values); 396 } 397 } else { 398 db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS, 399 new String[]{String.valueOf(uid), alias}); 400 } 401 } 402 removeGrantsForAlias(String alias)403 private void removeGrantsForAlias(String alias) { 404 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); 405 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_ALIAS, new String[] {alias}); 406 } 407 removeAllGrants(final SQLiteDatabase db)408 private void removeAllGrants(final SQLiteDatabase db) { 409 db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */); 410 } 411 412 private class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context)413 public DatabaseHelper(Context context) { 414 super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION); 415 } 416 417 @Override onCreate(final SQLiteDatabase db)418 public void onCreate(final SQLiteDatabase db) { 419 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( " 420 + GRANTS_ALIAS + " STRING NOT NULL, " 421 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, " 422 + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))"); 423 } 424 425 @Override onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion)426 public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) { 427 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); 428 429 if (oldVersion == 1) { 430 // the first upgrade step goes here 431 oldVersion++; 432 } 433 } 434 } 435 onBind(Intent intent)436 @Override public IBinder onBind(Intent intent) { 437 if (IKeyChainService.class.getName().equals(intent.getAction())) { 438 return mIKeyChainService; 439 } 440 return null; 441 } 442 443 @Override onHandleIntent(final Intent intent)444 protected void onHandleIntent(final Intent intent) { 445 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { 446 purgeOldGrants(); 447 } 448 } 449 purgeOldGrants()450 private void purgeOldGrants() { 451 final PackageManager packageManager = getPackageManager(); 452 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); 453 Cursor cursor = null; 454 db.beginTransaction(); 455 try { 456 cursor = db.query(TABLE_GRANTS, 457 new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null); 458 while (cursor.moveToNext()) { 459 final int uid = cursor.getInt(0); 460 final boolean packageExists = packageManager.getPackagesForUid(uid) != null; 461 if (packageExists) { 462 continue; 463 } 464 Log.d(TAG, "deleting grants for UID " + uid 465 + " because its package is no longer installed"); 466 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID, 467 new String[]{Integer.toString(uid)}); 468 } 469 db.setTransactionSuccessful(); 470 } finally { 471 if (cursor != null) { 472 cursor.close(); 473 } 474 db.endTransaction(); 475 } 476 } 477 broadcastStorageChange()478 private void broadcastStorageChange() { 479 Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED); 480 sendBroadcastAsUser(intent, new UserHandle(UserHandle.myUserId())); 481 } 482 483 } 484