OSDN Git Service

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