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