blob: 827f27833bc99311d34f1154f252a84c1738a77d [file] [log] [blame]
Brian Carlstrom3e6251d2011-04-11 09:05:06 -07001/*
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;
Brian Carlstroma58db542011-05-11 23:02:20 -070023import android.accounts.AccountsException;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070024import android.accounts.NetworkErrorException;
25import android.app.Service;
26import android.content.Context;
27import android.content.Intent;
28import android.os.Bundle;
29import android.os.IBinder;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070030import android.security.Credentials;
31import android.security.IKeyChainService;
32import android.security.KeyChain;
33import android.security.KeyStore;
34import android.util.Log;
35import java.io.ByteArrayInputStream;
Brian Carlstroma58db542011-05-11 23:02:20 -070036import java.io.IOException;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070037import java.nio.charset.Charsets;
38import java.security.SecureRandom;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070039import java.security.cert.CertificateException;
40import java.security.cert.CertificateFactory;
41import java.security.cert.X509Certificate;
Brian Carlstroma58db542011-05-11 23:02:20 -070042import java.util.Collections;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070043import javax.security.auth.x500.X500Principal;
Brian Carlstrom8334df72011-05-27 00:51:17 -070044import libcore.io.Base64;
Brian Carlstroma58db542011-05-11 23:02:20 -070045import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070046
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();
Brian Carlstroma58db542011-05-11 23:02:20 -070064 private final TrustedCertificateStore mTrustedCertificateStore
65 = new TrustedCertificateStore();
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070066
Brian Carlstrom5aeadd92011-05-17 00:40:33 -070067 @Override public byte[] getPrivateKey(String alias, String authToken) {
68 return getKeyStoreEntry(Credentials.USER_PRIVATE_KEY, alias, authToken);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070069 }
70
Brian Carlstroma58db542011-05-11 23:02:20 -070071 @Override public byte[] getCertificate(String alias, String authToken) {
Brian Carlstrom5aeadd92011-05-17 00:40:33 -070072 return getKeyStoreEntry(Credentials.USER_CERTIFICATE, alias, authToken);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070073 }
74
Brian Carlstrom5aeadd92011-05-17 00:40:33 -070075 private byte[] getKeyStoreEntry(String type, String alias, String authToken) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070076 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 }
Brian Carlstrom31c9afb2011-06-08 10:07:52 -070085 String peekedAuthToken = mAccountManager.peekAuthToken(mAccount, alias);
86 if (peekedAuthToken == null) {
87 throw new IllegalStateException("peekedAuthToken == null");
88 }
89 if (!peekedAuthToken.equals(authToken)) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070090 throw new IllegalStateException("authtoken mismatch");
91 }
92 String key = type + alias;
Brian Carlstrome3b33902011-05-31 01:06:20 -070093 byte[] bytes = mKeyStore.get(key);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070094 if (bytes == null) {
Brian Carlstromf5b50a42011-06-09 16:05:09 -070095 return null;
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070096 }
97 return bytes;
98 }
99
Brian Carlstrom5aeadd92011-05-17 00:40:33 -0700100 private boolean isKeyStoreUnlocked() {
Brian Carlstrome3b33902011-05-31 01:06:20 -0700101 return (mKeyStore.state() == KeyStore.State.UNLOCKED);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700102 }
Brian Carlstroma58db542011-05-11 23:02:20 -0700103
104 @Override public void installCaCertificate(byte[] caCertificate) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700105 checkCertInstallerOrSystemCaller();
Brian Carlstroma58db542011-05-11 23:02:20 -0700106 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 }
Brian Carlstrom5aeadd92011-05-17 00:40:33 -0700116
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
Brian Carlstroma58db542011-05-11 23:02:20 -0700122 @Override public boolean reset() {
123 // only Settings should be able to reset
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700124 checkSystemCaller();
Brian Carlstroma58db542011-05-11 23:02:20 -0700125 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)) {
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700150 if (!deleteCertificateEntry(alias)) {
Brian Carlstroma58db542011-05-11 23:02:20 -0700151 ok = false;
152 }
153 }
154 }
155 return ok;
156 }
157 }
Brian Carlstrom43f5b772011-06-27 02:27:16 -0700158
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 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700198 };
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,
Brian Carlstrom2a858832011-05-26 09:30:26 -0700223 Bundle options) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700224 return null;
225 }
226
227 @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response,
228 Account account,
Brian Carlstrom2a858832011-05-26 09:30:26 -0700229 Bundle options) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700230 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,
Brian Carlstrom2a858832011-05-26 09:30:26 -0700239 Bundle options) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700240 byte[] bytes = new byte[AUTHTOKEN_LENGTH];
241 mSecureRandom.nextBytes(bytes);
Brian Carlstrom8334df72011-05-27 00:51:17 -0700242 String authToken = Base64.encode(bytes);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700243 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,
Brian Carlstrom2a858832011-05-26 09:30:26 -0700259 Bundle options) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700260 return null;
261 }
262
263 @Override public Bundle hasFeatures(AccountAuthenticatorResponse response,
264 Account account,
Brian Carlstrom2a858832011-05-26 09:30:26 -0700265 String[] features) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700266 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}