KeyChainService.java revision 43f5b77dbbff264f7f521dbf5361f07a5e253c70
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.accounts.AbstractAccountAuthenticator; 20import android.accounts.Account; 21import android.accounts.AccountAuthenticatorResponse; 22import android.accounts.AccountManager; 23import android.accounts.AccountsException; 24import android.accounts.NetworkErrorException; 25import android.app.Service; 26import android.content.Context; 27import android.content.Intent; 28import android.os.Bundle; 29import android.os.IBinder; 30import android.security.Credentials; 31import android.security.IKeyChainService; 32import android.security.KeyChain; 33import android.security.KeyStore; 34import android.util.Log; 35import java.io.ByteArrayInputStream; 36import java.io.IOException; 37import java.nio.charset.Charsets; 38import java.security.SecureRandom; 39import java.security.cert.CertificateException; 40import java.security.cert.CertificateFactory; 41import java.security.cert.X509Certificate; 42import java.util.Collections; 43import javax.security.auth.x500.X500Principal; 44import libcore.io.Base64; 45import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore; 46 47public class KeyChainService extends Service { 48 49 private static final String TAG = "KeyChainService"; 50 51 private AccountManager mAccountManager; 52 53 private final Object mAccountLock = new Object(); 54 private Account mAccount; 55 56 @Override public void onCreate() { 57 super.onCreate(); 58 mAccountManager = AccountManager.get(this); 59 } 60 61 private final IKeyChainService.Stub mIKeyChainService = new IKeyChainService.Stub() { 62 63 private final KeyStore mKeyStore = KeyStore.getInstance(); 64 private final TrustedCertificateStore mTrustedCertificateStore 65 = new TrustedCertificateStore(); 66 67 @Override public byte[] getPrivateKey(String alias, String authToken) { 68 return getKeyStoreEntry(Credentials.USER_PRIVATE_KEY, alias, authToken); 69 } 70 71 @Override public byte[] getCertificate(String alias, String authToken) { 72 return getKeyStoreEntry(Credentials.USER_CERTIFICATE, alias, authToken); 73 } 74 75 private byte[] getKeyStoreEntry(String type, String alias, String authToken) { 76 if (alias == null) { 77 throw new NullPointerException("alias == null"); 78 } 79 if (authToken == null) { 80 throw new NullPointerException("authtoken == null"); 81 } 82 if (!isKeyStoreUnlocked()) { 83 throw new IllegalStateException("keystore locked"); 84 } 85 String peekedAuthToken = mAccountManager.peekAuthToken(mAccount, alias); 86 if (peekedAuthToken == null) { 87 throw new IllegalStateException("peekedAuthToken == null"); 88 } 89 if (!peekedAuthToken.equals(authToken)) { 90 throw new IllegalStateException("authtoken mismatch"); 91 } 92 String key = type + alias; 93 byte[] bytes = mKeyStore.get(key); 94 if (bytes == null) { 95 return null; 96 } 97 return bytes; 98 } 99 100 private boolean isKeyStoreUnlocked() { 101 return (mKeyStore.state() == KeyStore.State.UNLOCKED); 102 } 103 104 @Override public void installCaCertificate(byte[] caCertificate) { 105 checkCertInstallerOrSystemCaller(); 106 try { 107 synchronized (mTrustedCertificateStore) { 108 mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate)); 109 } 110 } catch (IOException e) { 111 throw new IllegalStateException(e); 112 } catch (CertificateException e) { 113 throw new IllegalStateException(e); 114 } 115 } 116 117 private X509Certificate parseCertificate(byte[] bytes) throws CertificateException { 118 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 119 return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes)); 120 } 121 122 @Override public boolean reset() { 123 // only Settings should be able to reset 124 checkSystemCaller(); 125 boolean ok = true; 126 127 synchronized (mAccountLock) { 128 // remote Accounts from AccountManager to revoke any 129 // granted credential grants to applications 130 Account[] accounts = mAccountManager.getAccountsByType(KeyChain.ACCOUNT_TYPE); 131 for (Account a : accounts) { 132 try { 133 if (!mAccountManager.removeAccount(a, null, null).getResult()) { 134 ok = false; 135 } 136 } catch (AccountsException e) { 137 Log.w(TAG, "Problem removing account " + a, e); 138 ok = false; 139 } catch (IOException e) { 140 Log.w(TAG, "Problem removing account " + a, e); 141 ok = false; 142 } 143 } 144 } 145 146 synchronized (mTrustedCertificateStore) { 147 // delete user-installed CA certs 148 for (String alias : mTrustedCertificateStore.aliases()) { 149 if (TrustedCertificateStore.isUser(alias)) { 150 if (!deleteCertificateEntry(alias)) { 151 ok = false; 152 } 153 } 154 } 155 return ok; 156 } 157 } 158 159 @Override public boolean deleteCaCertificate(String alias) { 160 // only Settings should be able to delete 161 checkSystemCaller(); 162 return deleteCertificateEntry(alias); 163 } 164 165 private boolean deleteCertificateEntry(String alias) { 166 try { 167 mTrustedCertificateStore.deleteCertificateEntry(alias); 168 return true; 169 } catch (IOException e) { 170 Log.w(TAG, "Problem removing CA certificate " + alias, e); 171 return false; 172 } catch (CertificateException e) { 173 Log.w(TAG, "Problem removing CA certificate " + alias, e); 174 return false; 175 } 176 } 177 178 private void checkCertInstallerOrSystemCaller() { 179 String actual = checkCaller("com.android.certinstaller"); 180 if (actual == null) { 181 return; 182 } 183 checkSystemCaller(); 184 } 185 private void checkSystemCaller() { 186 String actual = checkCaller("android.uid.system:1000"); 187 if (actual != null) { 188 throw new IllegalStateException(actual); 189 } 190 } 191 /** 192 * Returns null if actually caller is expected, otherwise return bad package to report 193 */ 194 private String checkCaller(String expectedPackage) { 195 String actualPackage = getPackageManager().getNameForUid(getCallingUid()); 196 return (!expectedPackage.equals(actualPackage)) ? actualPackage : null; 197 } 198 }; 199 200 private class KeyChainAccountAuthenticator extends AbstractAccountAuthenticator { 201 202 /** 203 * 264 was picked becuase it is the length in bytes of Google 204 * authtokens which seems sufficiently long and guaranteed to 205 * be storable by AccountManager. 206 */ 207 private final int AUTHTOKEN_LENGTH = 264; 208 private final SecureRandom mSecureRandom = new SecureRandom(); 209 210 private KeyChainAccountAuthenticator(Context context) { 211 super(context); 212 } 213 214 @Override public Bundle editProperties(AccountAuthenticatorResponse response, 215 String accountType) { 216 return null; 217 } 218 219 @Override public Bundle addAccount(AccountAuthenticatorResponse response, 220 String accountType, 221 String authTokenType, 222 String[] requiredFeatures, 223 Bundle options) { 224 return null; 225 } 226 227 @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response, 228 Account account, 229 Bundle options) { 230 return null; 231 } 232 233 /** 234 * Called on an AccountManager cache miss, so generate a new value. 235 */ 236 @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, 237 Account account, 238 String authTokenType, 239 Bundle options) { 240 byte[] bytes = new byte[AUTHTOKEN_LENGTH]; 241 mSecureRandom.nextBytes(bytes); 242 String authToken = Base64.encode(bytes); 243 Bundle bundle = new Bundle(); 244 bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); 245 bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, KeyChain.ACCOUNT_TYPE); 246 bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken); 247 return bundle; 248 } 249 250 @Override public String getAuthTokenLabel(String authTokenType) { 251 // return authTokenType unchanged, it was a user specified 252 // alias name, doesn't need to be localized 253 return authTokenType; 254 } 255 256 @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, 257 Account account, 258 String authTokenType, 259 Bundle options) { 260 return null; 261 } 262 263 @Override public Bundle hasFeatures(AccountAuthenticatorResponse response, 264 Account account, 265 String[] features) { 266 return null; 267 } 268 }; 269 270 private final IBinder mAuthenticator = new KeyChainAccountAuthenticator(this).getIBinder(); 271 272 @Override public IBinder onBind(Intent intent) { 273 if (IKeyChainService.class.getName().equals(intent.getAction())) { 274 275 // ensure singleton keychain account exists 276 synchronized (mAccountLock) { 277 Account[] accounts = mAccountManager.getAccountsByType(KeyChain.ACCOUNT_TYPE); 278 if (accounts.length == 0) { 279 // TODO localize account name 280 mAccount = new Account("Android Key Chain", KeyChain.ACCOUNT_TYPE); 281 mAccountManager.addAccountExplicitly(mAccount, null, null); 282 } else if (accounts.length == 1) { 283 mAccount = accounts[0]; 284 } else { 285 throw new IllegalStateException(); 286 } 287 } 288 289 return mIKeyChainService; 290 } 291 292 if (AccountManager.ACTION_AUTHENTICATOR_INTENT.equals(intent.getAction())) { 293 return mAuthenticator; 294 } 295 296 return null; 297 } 298} 299