OSDN Git Service

Import translations. DO NOT MERGE
[android-x86/packages-apps-Settings.git] / src / com / android / settings / CredentialStorage.java
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.settings;
18
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.res.Resources;
26 import android.os.AsyncTask;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.os.Process;
30 import android.os.UserManager;
31 import android.security.Credentials;
32 import android.security.KeyChain.KeyChainConnection;
33 import android.security.KeyChain;
34 import android.security.KeyStore;
35 import android.text.Editable;
36 import android.text.TextUtils;
37 import android.text.TextWatcher;
38 import android.util.Log;
39 import android.view.View;
40 import android.widget.Button;
41 import android.widget.TextView;
42 import android.widget.Toast;
43
44 import com.android.internal.widget.LockPatternUtils;
45 import com.android.org.bouncycastle.asn1.ASN1InputStream;
46 import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
47
48 import org.apache.harmony.security.utils.AlgNameMapper;
49
50 import java.io.ByteArrayInputStream;
51 import java.io.IOException;
52
53 /**
54  * CredentialStorage handles KeyStore reset, unlock, and install.
55  *
56  * CredentialStorage has a pretty convoluted state machine to migrate
57  * from the old style separate keystore password to a new key guard
58  * based password, as well as to deal with setting up the key guard if
59  * necessary.
60  *
61  * KeyStore: UNINITALIZED
62  * KeyGuard: OFF
63  * Action:   set up key guard
64  * Notes:    factory state
65  *
66  * KeyStore: UNINITALIZED
67  * KeyGuard: ON
68  * Action:   confirm key guard
69  * Notes:    user had key guard but no keystore and upgraded from pre-ICS
70  *           OR user had key guard and pre-ICS keystore password which was then reset
71  *
72  * KeyStore: LOCKED
73  * KeyGuard: OFF/ON
74  * Action:   old unlock dialog
75  * Notes:    assume old password, need to use it to unlock.
76  *           if unlock, ensure key guard before install.
77  *           if reset, treat as UNINITALIZED/OFF
78  *
79  * KeyStore: UNLOCKED
80  * KeyGuard: OFF
81  * Action:   set up key guard
82  * Notes:    ensure key guard, then proceed
83  *
84  * KeyStore: UNLOCKED
85  * keyguard: ON
86  * Action:   normal unlock/install
87  * Notes:    this is the common case
88  */
89 public final class CredentialStorage extends Activity {
90
91     private static final String TAG = "CredentialStorage";
92
93     public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
94     public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
95     public static final String ACTION_RESET = "com.android.credentials.RESET";
96
97     // This is the minimum acceptable password quality.  If the current password quality is
98     // lower than this, keystore should not be activated.
99     static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
100
101     private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
102
103     private final KeyStore mKeyStore = KeyStore.getInstance();
104
105     /**
106      * When non-null, the bundle containing credentials to install.
107      */
108     private Bundle mInstallBundle;
109
110     /**
111      * After unsuccessful KeyStore.unlock, the number of unlock
112      * attempts remaining before the KeyStore will reset itself.
113      *
114      * Reset to -1 on successful unlock or reset.
115      */
116     private int mRetriesRemaining = -1;
117
118     @Override
119     protected void onResume() {
120         super.onResume();
121
122         Intent intent = getIntent();
123         String action = intent.getAction();
124         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
125         if (!userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
126             if (ACTION_RESET.equals(action)) {
127                 new ResetDialog();
128             } else {
129                 if (ACTION_INSTALL.equals(action)
130                         && "com.android.certinstaller".equals(getCallingPackage())) {
131                     mInstallBundle = intent.getExtras();
132                 }
133                 // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
134                 handleUnlockOrInstall();
135             }
136         } else {
137             finish();
138         }
139     }
140
141     /**
142      * Based on the current state of the KeyStore and key guard, try to
143      * make progress on unlocking or installing to the keystore.
144      */
145     private void handleUnlockOrInstall() {
146         // something already decided we are done, do not proceed
147         if (isFinishing()) {
148             return;
149         }
150         switch (mKeyStore.state()) {
151             case UNINITIALIZED: {
152                 ensureKeyGuard();
153                 return;
154             }
155             case LOCKED: {
156                 new UnlockDialog();
157                 return;
158             }
159             case UNLOCKED: {
160                 if (!checkKeyGuardQuality()) {
161                     new ConfigureKeyGuardDialog();
162                     return;
163                 }
164                 installIfAvailable();
165                 finish();
166                 return;
167             }
168         }
169     }
170
171     /**
172      * Make sure the user enters the key guard to set or change the
173      * keystore password. This can be used in UNINITIALIZED to set the
174      * keystore password or UNLOCKED to change the password (as is the
175      * case after unlocking with an old-style password).
176      */
177     private void ensureKeyGuard() {
178         if (!checkKeyGuardQuality()) {
179             // key guard not setup, doing so will initialize keystore
180             new ConfigureKeyGuardDialog();
181             // will return to onResume after Activity
182             return;
183         }
184         // force key guard confirmation
185         if (confirmKeyGuard()) {
186             // will return password value via onActivityResult
187             return;
188         }
189         finish();
190     }
191
192     /**
193      * Returns true if the currently set key guard matches our minimum quality requirements.
194      */
195     private boolean checkKeyGuardQuality() {
196         int quality = new LockPatternUtils(this).getActivePasswordQuality();
197         return (quality >= MIN_PASSWORD_QUALITY);
198     }
199
200     private boolean isHardwareBackedKey(byte[] keyData) {
201         try {
202             ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData));
203             PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
204             String algId = pki.getAlgorithmId().getAlgorithm().getId();
205             String algName = AlgNameMapper.map2AlgName(algId);
206
207             return KeyChain.isBoundKeyAlgorithm(algName);
208         } catch (IOException e) {
209             Log.e(TAG, "Failed to parse key data");
210             return false;
211         }
212     }
213
214     /**
215      * Install credentials if available, otherwise do nothing.
216      */
217     private void installIfAvailable() {
218         if (mInstallBundle != null && !mInstallBundle.isEmpty()) {
219             Bundle bundle = mInstallBundle;
220             mInstallBundle = null;
221
222             final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1);
223
224             if (bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) {
225                 String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
226                 byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
227
228                 int flags = KeyStore.FLAG_ENCRYPTED;
229                 if (uid == Process.WIFI_UID && isHardwareBackedKey(value)) {
230                     // Hardware backed keystore is secure enough to allow for WIFI stack
231                     // to enable access to secure networks without user intervention
232                     Log.d(TAG, "Saving private key with FLAG_NONE for WIFI_UID");
233                     flags = KeyStore.FLAG_NONE;
234                 }
235
236                 if (!mKeyStore.importKey(key, value, uid, flags)) {
237                     Log.e(TAG, "Failed to install " + key + " as user " + uid);
238                     return;
239                 }
240             }
241
242             int flags = (uid == Process.WIFI_UID) ? KeyStore.FLAG_NONE : KeyStore.FLAG_ENCRYPTED;
243
244             if (bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)) {
245                 String certName = bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME);
246                 byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
247
248                 if (!mKeyStore.put(certName, certData, uid, flags)) {
249                     Log.e(TAG, "Failed to install " + certName + " as user " + uid);
250                     return;
251                 }
252             }
253
254             if (bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)) {
255                 String caListName = bundle.getString(Credentials.EXTRA_CA_CERTIFICATES_NAME);
256                 byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
257
258                 if (!mKeyStore.put(caListName, caListData, uid, flags)) {
259                     Log.e(TAG, "Failed to install " + caListName + " as user " + uid);
260                     return;
261                 }
262             }
263
264             setResult(RESULT_OK);
265         }
266     }
267
268     /**
269      * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
270      */
271     private class ResetDialog
272             implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
273     {
274         private boolean mResetConfirmed;
275
276         private ResetDialog() {
277             AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
278                     .setTitle(android.R.string.dialog_alert_title)
279                     .setMessage(R.string.credentials_reset_hint)
280                     .setPositiveButton(android.R.string.ok, this)
281                     .setNegativeButton(android.R.string.cancel, this)
282                     .create();
283             dialog.setOnDismissListener(this);
284             dialog.show();
285         }
286
287         @Override public void onClick(DialogInterface dialog, int button) {
288             mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
289         }
290
291         @Override public void onDismiss(DialogInterface dialog) {
292             if (mResetConfirmed) {
293                 mResetConfirmed = false;
294                 new ResetKeyStoreAndKeyChain().execute();
295                 return;
296             }
297             finish();
298         }
299     }
300
301     /**
302      * Background task to handle reset of both keystore and user installed CAs.
303      */
304     private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
305
306         @Override protected Boolean doInBackground(Void... unused) {
307
308             mKeyStore.reset();
309
310             try {
311                 KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
312                 try {
313                     return keyChainConnection.getService().reset();
314                 } catch (RemoteException e) {
315                     return false;
316                 } finally {
317                     keyChainConnection.close();
318                 }
319             } catch (InterruptedException e) {
320                 Thread.currentThread().interrupt();
321                 return false;
322             }
323         }
324
325         @Override protected void onPostExecute(Boolean success) {
326             if (success) {
327                 Toast.makeText(CredentialStorage.this,
328                                R.string.credentials_erased, Toast.LENGTH_SHORT).show();
329             } else {
330                 Toast.makeText(CredentialStorage.this,
331                                R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
332             }
333             finish();
334         }
335     }
336
337     /**
338      * Prompt for key guard configuration confirmation.
339      */
340     private class ConfigureKeyGuardDialog
341             implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
342     {
343         private boolean mConfigureConfirmed;
344
345         private ConfigureKeyGuardDialog() {
346             AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
347                     .setTitle(android.R.string.dialog_alert_title)
348                     .setMessage(R.string.credentials_configure_lock_screen_hint)
349                     .setPositiveButton(android.R.string.ok, this)
350                     .setNegativeButton(android.R.string.cancel, this)
351                     .create();
352             dialog.setOnDismissListener(this);
353             dialog.show();
354         }
355
356         @Override public void onClick(DialogInterface dialog, int button) {
357             mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
358         }
359
360         @Override public void onDismiss(DialogInterface dialog) {
361             if (mConfigureConfirmed) {
362                 mConfigureConfirmed = false;
363                 Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
364                 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
365                                 MIN_PASSWORD_QUALITY);
366                 startActivity(intent);
367                 return;
368             }
369             finish();
370         }
371     }
372
373     /**
374      * Confirm existing key guard, returning password via onActivityResult.
375      */
376     private boolean confirmKeyGuard() {
377         Resources res = getResources();
378         boolean launched = new ChooseLockSettingsHelper(this)
379                 .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST,
380                                             res.getText(R.string.credentials_install_gesture_prompt),
381                                             res.getText(R.string.credentials_install_gesture_explanation),
382                                             true);
383         return launched;
384     }
385
386     @Override
387     public void onActivityResult(int requestCode, int resultCode, Intent data) {
388         super.onActivityResult(requestCode, resultCode, data);
389
390         /**
391          * Receive key guard password initiated by confirmKeyGuard.
392          */
393         if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
394             if (resultCode == Activity.RESULT_OK) {
395                 String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
396                 if (!TextUtils.isEmpty(password)) {
397                     // success
398                     mKeyStore.password(password);
399                     // return to onResume
400                     return;
401                 }
402             }
403             // failed confirmation, bail
404             finish();
405         }
406     }
407
408     /**
409      * Prompt for unlock with old-style password.
410      *
411      * On successful unlock, ensure migration to key guard before continuing.
412      * On unsuccessful unlock, retry by calling handleUnlockOrInstall.
413      */
414     private class UnlockDialog implements TextWatcher,
415             DialogInterface.OnClickListener, DialogInterface.OnDismissListener
416     {
417         private boolean mUnlockConfirmed;
418
419         private final Button mButton;
420         private final TextView mOldPassword;
421         private final TextView mError;
422
423         private UnlockDialog() {
424             View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null);
425
426             CharSequence text;
427             if (mRetriesRemaining == -1) {
428                 text = getResources().getText(R.string.credentials_unlock_hint);
429             } else if (mRetriesRemaining > 3) {
430                 text = getResources().getText(R.string.credentials_wrong_password);
431             } else if (mRetriesRemaining == 1) {
432                 text = getResources().getText(R.string.credentials_reset_warning);
433             } else {
434                 text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
435             }
436
437             ((TextView) view.findViewById(R.id.hint)).setText(text);
438             mOldPassword = (TextView) view.findViewById(R.id.old_password);
439             mOldPassword.setVisibility(View.VISIBLE);
440             mOldPassword.addTextChangedListener(this);
441             mError = (TextView) view.findViewById(R.id.error);
442
443             AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
444                     .setView(view)
445                     .setTitle(R.string.credentials_unlock)
446                     .setPositiveButton(android.R.string.ok, this)
447                     .setNegativeButton(android.R.string.cancel, this)
448                     .create();
449             dialog.setOnDismissListener(this);
450             dialog.show();
451             mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
452             mButton.setEnabled(false);
453         }
454
455         @Override public void afterTextChanged(Editable editable) {
456             mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
457         }
458
459         @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
460         }
461
462         @Override public void onTextChanged(CharSequence s,int start, int before, int count) {
463         }
464
465         @Override public void onClick(DialogInterface dialog, int button) {
466             mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
467         }
468
469         @Override public void onDismiss(DialogInterface dialog) {
470             if (mUnlockConfirmed) {
471                 mUnlockConfirmed = false;
472                 mError.setVisibility(View.VISIBLE);
473                 mKeyStore.unlock(mOldPassword.getText().toString());
474                 int error = mKeyStore.getLastError();
475                 if (error == KeyStore.NO_ERROR) {
476                     mRetriesRemaining = -1;
477                     Toast.makeText(CredentialStorage.this,
478                                    R.string.credentials_enabled,
479                                    Toast.LENGTH_SHORT).show();
480                     // aha, now we are unlocked, switch to key guard.
481                     // we'll end up back in onResume to install
482                     ensureKeyGuard();
483                 } else if (error == KeyStore.UNINITIALIZED) {
484                     mRetriesRemaining = -1;
485                     Toast.makeText(CredentialStorage.this,
486                                    R.string.credentials_erased,
487                                    Toast.LENGTH_SHORT).show();
488                     // we are reset, we can now set new password with key guard
489                     handleUnlockOrInstall();
490                 } else if (error >= KeyStore.WRONG_PASSWORD) {
491                     // we need to try again
492                     mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
493                     handleUnlockOrInstall();
494                 }
495                 return;
496             }
497             finish();
498         }
499     }
500 }