blob: 3b3d144097d365585369b0d05db56329b9d0b8a8 [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;
44import org.apache.harmony.luni.util.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 Carlstrom5aeadd92011-05-17 00:40:33 -070085 if (!mAccountManager.peekAuthToken(mAccount, alias).equals(authToken)) {
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070086 throw new IllegalStateException("authtoken mismatch");
87 }
88 String key = type + alias;
89 byte[] bytes = mKeyStore.get(key.getBytes(Charsets.UTF_8));
90 if (bytes == null) {
91 throw new IllegalStateException("keystore value missing");
92 }
93 return bytes;
94 }
95
Brian Carlstrom5aeadd92011-05-17 00:40:33 -070096 private boolean isKeyStoreUnlocked() {
97 return (mKeyStore.test() == KeyStore.NO_ERROR);
Brian Carlstrom3e6251d2011-04-11 09:05:06 -070098 }
Brian Carlstroma58db542011-05-11 23:02:20 -070099
100 @Override public void installCaCertificate(byte[] caCertificate) {
101 // only the CertInstaller should be able to add new trusted CAs
102 final String expectedPackage = "com.android.certinstaller";
103 final String actualPackage = getPackageManager().getNameForUid(getCallingUid());
104 if (!expectedPackage.equals(actualPackage)) {
105 throw new IllegalStateException(actualPackage);
106 }
107 try {
108 synchronized (mTrustedCertificateStore) {
109 mTrustedCertificateStore.installCertificate(parseCertificate(caCertificate));
110 }
111 } catch (IOException e) {
112 throw new IllegalStateException(e);
113 } catch (CertificateException e) {
114 throw new IllegalStateException(e);
115 }
116 }
Brian Carlstrom5aeadd92011-05-17 00:40:33 -0700117
118 private X509Certificate parseCertificate(byte[] bytes) throws CertificateException {
119 CertificateFactory cf = CertificateFactory.getInstance("X.509");
120 return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
121 }
122
Brian Carlstroma58db542011-05-11 23:02:20 -0700123 @Override public boolean reset() {
124 // only Settings should be able to reset
125 final String expectedPackage = "android.uid.system:1000";
126 final String actualPackage = getPackageManager().getNameForUid(getCallingUid());
127 if (!expectedPackage.equals(actualPackage)) {
128 throw new IllegalStateException(actualPackage);
129 }
130 boolean ok = true;
131
132 synchronized (mAccountLock) {
133 // remote Accounts from AccountManager to revoke any
134 // granted credential grants to applications
135 Account[] accounts = mAccountManager.getAccountsByType(KeyChain.ACCOUNT_TYPE);
136 for (Account a : accounts) {
137 try {
138 if (!mAccountManager.removeAccount(a, null, null).getResult()) {
139 ok = false;
140 }
141 } catch (AccountsException e) {
142 Log.w(TAG, "Problem removing account " + a, e);
143 ok = false;
144 } catch (IOException e) {
145 Log.w(TAG, "Problem removing account " + a, e);
146 ok = false;
147 }
148 }
149 }
150
151 synchronized (mTrustedCertificateStore) {
152 // delete user-installed CA certs
153 for (String alias : mTrustedCertificateStore.aliases()) {
154 if (TrustedCertificateStore.isUser(alias)) {
155 try {
156 mTrustedCertificateStore.deleteCertificateEntry(alias);
157 } catch (IOException e) {
158 Log.w(TAG, "Problem removing CA certificate " + alias, e);
159 ok = false;
160 } catch (CertificateException e) {
161 Log.w(TAG, "Problem removing CA certificate " + alias, e);
162 ok = false;
163 }
164 }
165 }
166 return ok;
167 }
168 }
Brian Carlstrom3e6251d2011-04-11 09:05:06 -0700169 };
170
171 private class KeyChainAccountAuthenticator extends AbstractAccountAuthenticator {
172
173 /**
174 * 264 was picked becuase it is the length in bytes of Google
175 * authtokens which seems sufficiently long and guaranteed to
176 * be storable by AccountManager.
177 */
178 private final int AUTHTOKEN_LENGTH = 264;
179 private final SecureRandom mSecureRandom = new SecureRandom();
180
181 private KeyChainAccountAuthenticator(Context context) {
182 super(context);
183 }
184
185 @Override public Bundle editProperties(AccountAuthenticatorResponse response,
186 String accountType) {
187 return null;
188 }
189
190 @Override public Bundle addAccount(AccountAuthenticatorResponse response,
191 String accountType,
192 String authTokenType,
193 String[] requiredFeatures,
194 Bundle options) throws NetworkErrorException {
195 return null;
196 }
197
198 @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response,
199 Account account,
200 Bundle options) throws NetworkErrorException {
201 return null;
202 }
203
204 /**
205 * Called on an AccountManager cache miss, so generate a new value.
206 */
207 @Override public Bundle getAuthToken(AccountAuthenticatorResponse response,
208 Account account,
209 String authTokenType,
210 Bundle options) throws NetworkErrorException {
211 byte[] bytes = new byte[AUTHTOKEN_LENGTH];
212 mSecureRandom.nextBytes(bytes);
213 String authToken = Base64.encode(bytes, Charsets.US_ASCII);
214 Bundle bundle = new Bundle();
215 bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
216 bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, KeyChain.ACCOUNT_TYPE);
217 bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken);
218 return bundle;
219 }
220
221 @Override public String getAuthTokenLabel(String authTokenType) {
222 // return authTokenType unchanged, it was a user specified
223 // alias name, doesn't need to be localized
224 return authTokenType;
225 }
226
227 @Override public Bundle updateCredentials(AccountAuthenticatorResponse response,
228 Account account,
229 String authTokenType,
230 Bundle options) throws NetworkErrorException {
231 return null;
232 }
233
234 @Override public Bundle hasFeatures(AccountAuthenticatorResponse response,
235 Account account,
236 String[] features) throws NetworkErrorException {
237 return null;
238 }
239 };
240
241 private final IBinder mAuthenticator = new KeyChainAccountAuthenticator(this).getIBinder();
242
243 @Override public IBinder onBind(Intent intent) {
244 if (IKeyChainService.class.getName().equals(intent.getAction())) {
245
246 // ensure singleton keychain account exists
247 synchronized (mAccountLock) {
248 Account[] accounts = mAccountManager.getAccountsByType(KeyChain.ACCOUNT_TYPE);
249 if (accounts.length == 0) {
250 // TODO localize account name
251 mAccount = new Account("Android Key Chain", KeyChain.ACCOUNT_TYPE);
252 mAccountManager.addAccountExplicitly(mAccount, null, null);
253 } else if (accounts.length == 1) {
254 mAccount = accounts[0];
255 } else {
256 throw new IllegalStateException();
257 }
258 }
259
260 return mIKeyChainService;
261 }
262
263 if (AccountManager.ACTION_AUTHENTICATOR_INTENT.equals(intent.getAction())) {
264 return mAuthenticator;
265 }
266
267 return null;
268 }
269}