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