OSDN Git Service

am 46eb1857: (-s ours) 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.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             finish();
139         }
140     }
141
142     /**
143      * Based on the current state of the KeyStore and key guard, try to
144      * make progress on unlocking or installing to the keystore.
145      */
146     private void handleUnlockOrInstall() {
147         // something already decided we are done, do not proceed
148         if (isFinishing()) {
149             return;
150         }
151         switch (mKeyStore.state()) {
152             case UNINITIALIZED: {
153                 ensureKeyGuard();
154                 return;
155             }
156             case LOCKED: {
157                 new UnlockDialog();
158                 return;
159             }
160             case UNLOCKED: {
161                 if (!checkKeyGuardQuality()) {
162                     new ConfigureKeyGuardDialog();
163                     return;
164                 }
165                 installIfAvailable();
166                 finish();
167                 return;
168             }
169         }
170     }
171
172     /**
173      * Make sure the user enters the key guard to set or change the
174      * keystore password. This can be used in UNINITIALIZED to set the
175      * keystore password or UNLOCKED to change the password (as is the
176      * case after unlocking with an old-style password).
177      */
178     private void ensureKeyGuard() {
179         if (!checkKeyGuardQuality()) {
180             // key guard not setup, doing so will initialize keystore
181             new ConfigureKeyGuardDialog();
182             // will return to onResume after Activity
183             return;
184         }
185         // force key guard confirmation
186         if (confirmKeyGuard()) {
187             // will return password value via onActivityResult
188             return;
189         }
190         finish();
191     }
192
193     /**
194      * Returns true if the currently set key guard matches our minimum quality requirements.
195      */
196     private boolean checkKeyGuardQuality() {
197         int quality = new LockPatternUtils(this).getActivePasswordQuality();
198         return (quality >= MIN_PASSWORD_QUALITY);
199     }
200
201     private boolean isHardwareBackedKey(byte[] keyData) {
202         try {
203             ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData));
204             PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
205             String algId = pki.getAlgorithmId().getAlgorithm().getId();
206             String algName = AlgNameMapper.map2AlgName(algId);
207
208             return KeyChain.isBoundKeyAlgorithm(algName);
209         } catch (IOException e) {
210             Log.e(TAG, "Failed to parse key data");
211             return false;
212         }
213     }
214
215     /**
216      * Install credentials if available, otherwise do nothing.
217      */
218     private void installIfAvailable() {
219         if (mInstallBundle == null || mInstallBundle.isEmpty()) {
220             return;
221         }
222
223         Bundle bundle = mInstallBundle;
224         mInstallBundle = null;
225
226         final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, KeyStore.UID_SELF);
227
228         if (uid != KeyStore.UID_SELF && !UserHandle.isSameUser(uid, Process.myUid())) {
229             int dstUserId = UserHandle.getUserId(uid);
230             int myUserId = UserHandle.myUserId();
231
232             // Restrict install target to the wifi uid.
233             if (uid != Process.WIFI_UID) {
234                 Log.e(TAG, "Failed to install credentials as uid " + uid + ": cross-user installs"
235                         + " may only target wifi uids");
236                 return;
237             }
238
239             Intent installIntent = new Intent(ACTION_INSTALL)
240                     .setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
241                     .putExtras(bundle);
242             startActivityAsUser(installIntent, new UserHandle(dstUserId));
243             return;
244         }
245
246         if (bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) {
247             String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
248             byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
249
250             int flags = KeyStore.FLAG_ENCRYPTED;
251             if (uid == Process.WIFI_UID && isHardwareBackedKey(value)) {
252                 // Hardware backed keystore is secure enough to allow for WIFI stack
253                 // to enable access to secure networks without user intervention
254                 Log.d(TAG, "Saving private key with FLAG_NONE for WIFI_UID");
255                 flags = KeyStore.FLAG_NONE;
256             }
257
258             if (!mKeyStore.importKey(key, value, uid, flags)) {
259                 Log.e(TAG, "Failed to install " + key + " as uid " + uid);
260                 return;
261             }
262         }
263
264         int flags = (uid == Process.WIFI_UID) ? KeyStore.FLAG_NONE : KeyStore.FLAG_ENCRYPTED;
265
266         if (bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)) {
267             String certName = bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME);
268             byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
269
270             if (!mKeyStore.put(certName, certData, uid, flags)) {
271                 Log.e(TAG, "Failed to install " + certName + " as uid " + uid);
272                 return;
273             }
274         }
275
276         if (bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)) {
277             String caListName = bundle.getString(Credentials.EXTRA_CA_CERTIFICATES_NAME);
278             byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
279
280             if (!mKeyStore.put(caListName, caListData, uid, flags)) {
281                 Log.e(TAG, "Failed to install " + caListName + " as uid " + uid);
282                 return;
283             }
284         }
285
286         setResult(RESULT_OK);
287     }
288
289     /**
290      * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
291      */
292     private class ResetDialog
293             implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
294     {
295         private boolean mResetConfirmed;
296
297         private ResetDialog() {
298             AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
299                     .setTitle(android.R.string.dialog_alert_title)
300                     .setMessage(R.string.credentials_reset_hint)
301                     .setPositiveButton(android.R.string.ok, this)
302                     .setNegativeButton(android.R.string.cancel, this)
303                     .create();
304             dialog.setOnDismissListener(this);
305             dialog.show();
306         }
307
308         @Override public void onClick(DialogInterface dialog, int button) {
309             mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
310         }
311
312         @Override public void onDismiss(DialogInterface dialog) {
313             if (mResetConfirmed) {
314                 mResetConfirmed = false;
315                 new ResetKeyStoreAndKeyChain().execute();
316                 return;
317             }
318             finish();
319         }
320     }
321
322     /**
323      * Background task to handle reset of both keystore and user installed CAs.
324      */
325     private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
326
327         @Override protected Boolean doInBackground(Void... unused) {
328
329             mKeyStore.reset();
330
331             try {
332                 KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
333                 try {
334                     return keyChainConnection.getService().reset();
335                 } catch (RemoteException e) {
336                     return false;
337                 } finally {
338                     keyChainConnection.close();
339                 }
340             } catch (InterruptedException e) {
341                 Thread.currentThread().interrupt();
342                 return false;
343             }
344         }
345
346         @Override protected void onPostExecute(Boolean success) {
347             if (success) {
348                 Toast.makeText(CredentialStorage.this,
349                                R.string.credentials_erased, Toast.LENGTH_SHORT).show();
350             } else {
351                 Toast.makeText(CredentialStorage.this,
352                                R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
353             }
354             finish();
355         }
356     }
357
358     /**
359      * Prompt for key guard configuration confirmation.
360      */
361     private class ConfigureKeyGuardDialog
362             implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
363     {
364         private boolean mConfigureConfirmed;
365
366         private ConfigureKeyGuardDialog() {
367             AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
368                     .setTitle(android.R.string.dialog_alert_title)
369                     .setMessage(R.string.credentials_configure_lock_screen_hint)
370                     .setPositiveButton(android.R.string.ok, this)
371                     .setNegativeButton(android.R.string.cancel, this)
372                     .create();
373             dialog.setOnDismissListener(this);
374             dialog.show();
375         }
376
377         @Override public void onClick(DialogInterface dialog, int button) {
378             mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
379         }
380
381         @Override public void onDismiss(DialogInterface dialog) {
382             if (mConfigureConfirmed) {
383                 mConfigureConfirmed = false;
384                 Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
385                 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
386                                 MIN_PASSWORD_QUALITY);
387                 startActivity(intent);
388                 return;
389             }
390             finish();
391         }
392     }
393
394     /**
395      * Check that the caller is either certinstaller or Settings running in a profile of this user.
396      */
397     private boolean checkCallerIsCertInstallerOrSelfInProfile() {
398         if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) {
399             // CertInstaller is allowed to install credentials
400             return true;
401         }
402
403         final int launchedFromUserId;
404         try {
405             int launchedFromUid = android.app.ActivityManagerNative.getDefault()
406                     .getLaunchedFromUid(getActivityToken());
407             if (launchedFromUid == -1) {
408                 Log.e(TAG, ACTION_INSTALL + " must be started with startActivityForResult");
409                 return false;
410             }
411             if (!UserHandle.isSameApp(launchedFromUid, Process.myUid())) {
412                 // Not the same app
413                 return false;
414             }
415             launchedFromUserId = UserHandle.getUserId(launchedFromUid);
416         } catch (RemoteException re) {
417             // Error talking to ActivityManager, just give up
418             return false;
419         }
420
421         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
422         UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId);
423         if (parentInfo == null || parentInfo.id != UserHandle.myUserId()) {
424             // Caller is not running in a profile of this user
425             return false;
426         }
427         return true;
428     }
429
430     /**
431      * Confirm existing key guard, returning password via onActivityResult.
432      */
433     private boolean confirmKeyGuard() {
434         Resources res = getResources();
435         boolean launched = new ChooseLockSettingsHelper(this)
436                 .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST, null,
437                                             res.getText(R.string.credentials_install_gesture_explanation),
438                                             true);
439         return launched;
440     }
441
442     @Override
443     public void onActivityResult(int requestCode, int resultCode, Intent data) {
444         super.onActivityResult(requestCode, resultCode, data);
445
446         /**
447          * Receive key guard password initiated by confirmKeyGuard.
448          */
449         if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
450             if (resultCode == Activity.RESULT_OK) {
451                 String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
452                 if (!TextUtils.isEmpty(password)) {
453                     // success
454                     mKeyStore.password(password);
455                     // return to onResume
456                     return;
457                 }
458             }
459             // failed confirmation, bail
460             finish();
461         }
462     }
463
464     /**
465      * Prompt for unlock with old-style password.
466      *
467      * On successful unlock, ensure migration to key guard before continuing.
468      * On unsuccessful unlock, retry by calling handleUnlockOrInstall.
469      */
470     private class UnlockDialog implements TextWatcher,
471             DialogInterface.OnClickListener, DialogInterface.OnDismissListener
472     {
473         private boolean mUnlockConfirmed;
474
475         private final Button mButton;
476         private final TextView mOldPassword;
477         private final TextView mError;
478
479         private UnlockDialog() {
480             View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null);
481
482             CharSequence text;
483             if (mRetriesRemaining == -1) {
484                 text = getResources().getText(R.string.credentials_unlock_hint);
485             } else if (mRetriesRemaining > 3) {
486                 text = getResources().getText(R.string.credentials_wrong_password);
487             } else if (mRetriesRemaining == 1) {
488                 text = getResources().getText(R.string.credentials_reset_warning);
489             } else {
490                 text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
491             }
492
493             ((TextView) view.findViewById(R.id.hint)).setText(text);
494             mOldPassword = (TextView) view.findViewById(R.id.old_password);
495             mOldPassword.setVisibility(View.VISIBLE);
496             mOldPassword.addTextChangedListener(this);
497             mError = (TextView) view.findViewById(R.id.error);
498
499             AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
500                     .setView(view)
501                     .setTitle(R.string.credentials_unlock)
502                     .setPositiveButton(android.R.string.ok, this)
503                     .setNegativeButton(android.R.string.cancel, this)
504                     .create();
505             dialog.setOnDismissListener(this);
506             dialog.show();
507             mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
508             mButton.setEnabled(false);
509         }
510
511         @Override public void afterTextChanged(Editable editable) {
512             mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
513         }
514
515         @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
516         }
517
518         @Override public void onTextChanged(CharSequence s,int start, int before, int count) {
519         }
520
521         @Override public void onClick(DialogInterface dialog, int button) {
522             mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
523         }
524
525         @Override public void onDismiss(DialogInterface dialog) {
526             if (mUnlockConfirmed) {
527                 mUnlockConfirmed = false;
528                 mError.setVisibility(View.VISIBLE);
529                 mKeyStore.unlock(mOldPassword.getText().toString());
530                 int error = mKeyStore.getLastError();
531                 if (error == KeyStore.NO_ERROR) {
532                     mRetriesRemaining = -1;
533                     Toast.makeText(CredentialStorage.this,
534                                    R.string.credentials_enabled,
535                                    Toast.LENGTH_SHORT).show();
536                     // aha, now we are unlocked, switch to key guard.
537                     // we'll end up back in onResume to install
538                     ensureKeyGuard();
539                 } else if (error == KeyStore.UNINITIALIZED) {
540                     mRetriesRemaining = -1;
541                     Toast.makeText(CredentialStorage.this,
542                                    R.string.credentials_erased,
543                                    Toast.LENGTH_SHORT).show();
544                     // we are reset, we can now set new password with key guard
545                     handleUnlockOrInstall();
546                 } else if (error >= KeyStore.WRONG_PASSWORD) {
547                     // we need to try again
548                     mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
549                     handleUnlockOrInstall();
550                 }
551                 return;
552             }
553             finish();
554         }
555     }
556 }