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