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