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