OSDN Git Service

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