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