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