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.database.Cursor; 25 import android.database.DatabaseUtils; 26 import android.database.sqlite.SQLiteDatabase; 27 import android.database.sqlite.SQLiteOpenHelper; 28 import android.os.Binder; 29 import android.os.IBinder; 30 import android.os.Process; 31 import android.security.Credentials; 32 import android.security.IKeyChainService; 33 import android.security.KeyChain; 34 import android.security.KeyStore; 35 import android.util.Log; 36 import java.io.ByteArrayInputStream; 37 import java.io.IOException; 38 import java.security.cert.CertificateException; 39 import java.security.cert.CertificateFactory; 40 import java.security.cert.X509Certificate; 41 42 import com.android.org.conscrypt.TrustedCertificateStore; 43 44 public class KeyChainService extends IntentService { 45 46 private static final String TAG = "KeyChain"; 47 48 private static final String DATABASE_NAME = "grants.db"; 49 private static final int DATABASE_VERSION = 1; 50 private static final String TABLE_GRANTS = "grants"; 51 private static final String GRANTS_ALIAS = "alias"; 52 private static final String GRANTS_GRANTEE_UID = "uid"; 53 54 /** created in onCreate(), closed in onDestroy() */ 55 public DatabaseHelper mDatabaseHelper; 56 57 private static final String SELECTION_COUNT_OF_MATCHING_GRANTS = 58 "SELECT COUNT(*) FROM " + TABLE_GRANTS 59 + " WHERE " + GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?"; 60 61 private static final String SELECT_GRANTS_BY_UID_AND_ALIAS = 62 GRANTS_GRANTEE_UID + "=? AND " + GRANTS_ALIAS + "=?"; 63 64 private static final String SELECTION_GRANTS_BY_UID = GRANTS_GRANTEE_UID + "=?"; 65 KeyChainService()66 public KeyChainService() { 67 super(KeyChainService.class.getSimpleName()); 68 } 69 onCreate()70 @Override public void onCreate() { 71 super.onCreate(); 72 mDatabaseHelper = new DatabaseHelper(this); 73 } 74 75 @Override onDestroy()76 public void onDestroy() { 77 super.onDestroy(); 78 mDatabaseHelper.close(); 79 mDatabaseHelper = null; 80 } 81 82 private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() { 83 private final KeyStore mKeyStore = KeyStore.getInstance(); 84 private final TrustedCertificateStore mTrustedCertificateStore 85 = new TrustedCertificateStore(); 86 87 @Override 88 public String requestPrivateKey(String alias) { 89 checkArgs(alias); 90 91 final String keystoreAlias = Credentials.USER_PRIVATE_KEY + alias; 92 final int uid = Binder.getCallingUid(); 93 if (!mKeyStore.grant(keystoreAlias, uid)) { 94 return null; 95 } 96 97 final StringBuilder sb = new StringBuilder(); 98 sb.append(Process.SYSTEM_UID); 99 sb.append('_'); 100 sb.append(keystoreAlias); 101 102 return sb.toString(); 103 } 104 105 @Override public byte[] getCertificate(String alias) { 106 checkArgs(alias); 107 return mKeyStore.get(Credentials.USER_CERTIFICATE + alias); 108 } 109 110 private void checkArgs(String alias) { 111 if (alias == null) { 112 throw new NullPointerException("alias == null"); 113 } 114 if (!mKeyStore.isUnlocked()) { 115 throw new IllegalStateException("keystore is " 116 + mKeyStore.state().toString()); 117 } 118 119 final int callingUid = getCallingUid(); 120 if (!hasGrantInternal(mDatabaseHelper.getReadableDatabase(), callingUid, alias)) { 121 throw new IllegalStateException("uid " + callingUid 122 + " doesn't have permission to access the requested alias"); 123 } 124 } 125 126 @Override public void installCaCertificate(byte[] caCertificate) { 127 checkCertInstallerOrSystemCaller(); 128 try { 129 synchronized (mTrustedCertificateStore) { 130 mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate)); 131 } 132 } catch (IOException e) { 133 throw new IllegalStateException(e); 134 } catch (CertificateException e) { 135 throw new IllegalStateException(e); 136 } 137 broadcastStorageChange(); 138 } 139 140 private X509Certificate parseCertificate(byte[] bytes) throws CertificateException { 141 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 142 return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes)); 143 } 144 145 @Override public boolean reset() { 146 // only Settings should be able to reset 147 checkSystemCaller(); 148 removeAllGrants(mDatabaseHelper.getWritableDatabase()); 149 boolean ok = true; 150 synchronized (mTrustedCertificateStore) { 151 // delete user-installed CA certs 152 for (String alias : mTrustedCertificateStore.aliases()) { 153 if (TrustedCertificateStore.isUser(alias)) { 154 if (!deleteCertificateEntry(alias)) { 155 ok = false; 156 } 157 } 158 } 159 } 160 broadcastStorageChange(); 161 return ok; 162 } 163 164 @Override public boolean deleteCaCertificate(String alias) { 165 // only Settings should be able to delete 166 checkSystemCaller(); 167 boolean ok = true; 168 synchronized (mTrustedCertificateStore) { 169 ok = deleteCertificateEntry(alias); 170 } 171 broadcastStorageChange(); 172 return ok; 173 } 174 175 private boolean deleteCertificateEntry(String alias) { 176 try { 177 mTrustedCertificateStore.deleteCertificateEntry(alias); 178 return true; 179 } catch (IOException e) { 180 Log.w(TAG, "Problem removing CA certificate " + alias, e); 181 return false; 182 } catch (CertificateException e) { 183 Log.w(TAG, "Problem removing CA certificate " + alias, e); 184 return false; 185 } 186 } 187 188 private void checkCertInstallerOrSystemCaller() { 189 String actual = checkCaller("com.android.certinstaller"); 190 if (actual == null) { 191 return; 192 } 193 checkSystemCaller(); 194 } 195 private void checkSystemCaller() { 196 String actual = checkCaller("android.uid.system:1000"); 197 if (actual != null) { 198 throw new IllegalStateException(actual); 199 } 200 } 201 /** 202 * Returns null if actually caller is expected, otherwise return bad package to report 203 */ 204 private String checkCaller(String expectedPackage) { 205 String actualPackage = getPackageManager().getNameForUid(getCallingUid()); 206 return (!expectedPackage.equals(actualPackage)) ? actualPackage : null; 207 } 208 209 @Override public boolean hasGrant(int uid, String alias) { 210 checkSystemCaller(); 211 return hasGrantInternal(mDatabaseHelper.getReadableDatabase(), uid, alias); 212 } 213 214 @Override public void setGrant(int uid, String alias, boolean value) { 215 checkSystemCaller(); 216 setGrantInternal(mDatabaseHelper.getWritableDatabase(), uid, alias, value); 217 broadcastStorageChange(); 218 } 219 }; 220 hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias)221 private boolean hasGrantInternal(final SQLiteDatabase db, final int uid, final String alias) { 222 final long numMatches = DatabaseUtils.longForQuery(db, SELECTION_COUNT_OF_MATCHING_GRANTS, 223 new String[]{String.valueOf(uid), alias}); 224 return numMatches > 0; 225 } 226 setGrantInternal(final SQLiteDatabase db, final int uid, final String alias, final boolean value)227 private void setGrantInternal(final SQLiteDatabase db, 228 final int uid, final String alias, final boolean value) { 229 if (value) { 230 if (!hasGrantInternal(db, uid, alias)) { 231 final ContentValues values = new ContentValues(); 232 values.put(GRANTS_ALIAS, alias); 233 values.put(GRANTS_GRANTEE_UID, uid); 234 db.insert(TABLE_GRANTS, GRANTS_ALIAS, values); 235 } 236 } else { 237 db.delete(TABLE_GRANTS, SELECT_GRANTS_BY_UID_AND_ALIAS, 238 new String[]{String.valueOf(uid), alias}); 239 } 240 } 241 removeAllGrants(final SQLiteDatabase db)242 private void removeAllGrants(final SQLiteDatabase db) { 243 db.delete(TABLE_GRANTS, null /* whereClause */, null /* whereArgs */); 244 } 245 246 private class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context)247 public DatabaseHelper(Context context) { 248 super(context, DATABASE_NAME, null /* CursorFactory */, DATABASE_VERSION); 249 } 250 251 @Override onCreate(final SQLiteDatabase db)252 public void onCreate(final SQLiteDatabase db) { 253 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( " 254 + GRANTS_ALIAS + " STRING NOT NULL, " 255 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, " 256 + "UNIQUE (" + GRANTS_ALIAS + "," + GRANTS_GRANTEE_UID + "))"); 257 } 258 259 @Override onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion)260 public void onUpgrade(final SQLiteDatabase db, int oldVersion, final int newVersion) { 261 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); 262 263 if (oldVersion == 1) { 264 // the first upgrade step goes here 265 oldVersion++; 266 } 267 } 268 } 269 onBind(Intent intent)270 @Override public IBinder onBind(Intent intent) { 271 if (IKeyChainService.class.getName().equals(intent.getAction())) { 272 return mIKeyChainService; 273 } 274 return null; 275 } 276 277 @Override onHandleIntent(final Intent intent)278 protected void onHandleIntent(final Intent intent) { 279 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { 280 purgeOldGrants(); 281 } 282 } 283 purgeOldGrants()284 private void purgeOldGrants() { 285 final PackageManager packageManager = getPackageManager(); 286 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); 287 Cursor cursor = null; 288 db.beginTransaction(); 289 try { 290 cursor = db.query(TABLE_GRANTS, 291 new String[]{GRANTS_GRANTEE_UID}, null, null, GRANTS_GRANTEE_UID, null, null); 292 while (cursor.moveToNext()) { 293 final int uid = cursor.getInt(0); 294 final boolean packageExists = packageManager.getPackagesForUid(uid) != null; 295 if (packageExists) { 296 continue; 297 } 298 Log.d(TAG, "deleting grants for UID " + uid 299 + " because its package is no longer installed"); 300 db.delete(TABLE_GRANTS, SELECTION_GRANTS_BY_UID, 301 new String[]{Integer.toString(uid)}); 302 } 303 db.setTransactionSuccessful(); 304 } finally { 305 if (cursor != null) { 306 cursor.close(); 307 } 308 db.endTransaction(); 309 } 310 } 311 broadcastStorageChange()312 private void broadcastStorageChange() { 313 Intent intent = new Intent(KeyChain.ACTION_STORAGE_CHANGED); 314 sendBroadcast(intent); 315 } 316 317 } 318