• 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.security;
18 
19 import android.app.Activity;
20 import android.app.admin.DevicePolicyManager;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
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.Process;
30 import android.os.RemoteException;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.security.Credentials;
34 import android.security.IKeyChainService;
35 import android.security.KeyChain;
36 import android.security.KeyChain.KeyChainConnection;
37 import android.security.KeyStore;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.widget.Toast;
41 
42 import androidx.appcompat.app.AlertDialog;
43 import androidx.fragment.app.FragmentActivity;
44 
45 import com.android.internal.widget.LockPatternUtils;
46 import com.android.settings.R;
47 import com.android.settings.password.ChooseLockSettingsHelper;
48 import com.android.settings.vpn2.VpnUtils;
49 import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin;
50 
51 /**
52  * CredentialStorage handles resetting and installing keys into KeyStore.
53  */
54 public final class CredentialStorage extends FragmentActivity {
55 
56     private static final String TAG = "CredentialStorage";
57 
58     public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
59     public static final String ACTION_RESET = "com.android.credentials.RESET";
60 
61     // This is the minimum acceptable password quality.  If the current password quality is
62     // lower than this, keystore should not be activated.
63     public static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
64 
65     private static final int CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST = 1;
66 
67     private LockPatternUtils mUtils;
68 
69     /**
70      * When non-null, the bundle containing credentials to install.
71      */
72     private Bundle mInstallBundle;
73 
74     @Override
onCreate(Bundle savedState)75     protected void onCreate(Bundle savedState) {
76         super.onCreate(savedState);
77         mUtils = new LockPatternUtils(this);
78         getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
79     }
80 
81     @Override
onResume()82     protected void onResume() {
83         super.onResume();
84 
85         final Intent intent = getIntent();
86         final String action = intent.getAction();
87         final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
88         if (!userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
89             if (ACTION_RESET.equals(action)) {
90                 new ResetDialog();
91             } else {
92                 if (ACTION_INSTALL.equals(action) && checkCallerIsCertInstallerOrSelfInProfile()) {
93                     mInstallBundle = intent.getExtras();
94                 }
95                 handleInstall();
96             }
97         } else {
98             finish();
99         }
100     }
101 
102     /**
103      * Install credentials from mInstallBundle into Keystore.
104      */
handleInstall()105     private void handleInstall() {
106         // something already decided we are done, do not proceed
107         if (isFinishing()) {
108             return;
109         }
110         if (installIfAvailable()) {
111             finish();
112         }
113     }
114 
115     /**
116      * Install credentials if available, otherwise do nothing.
117      *
118      * @return true if the installation is done and the activity should be finished, false if
119      * an asynchronous task is pending and will finish the activity when it's done.
120      */
installIfAvailable()121     private boolean installIfAvailable() {
122         if (mInstallBundle == null || mInstallBundle.isEmpty()) {
123             return true;
124         }
125 
126         final Bundle bundle = mInstallBundle;
127         mInstallBundle = null;
128 
129         final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, KeyStore.UID_SELF);
130 
131         if (uid != KeyStore.UID_SELF && !UserHandle.isSameUser(uid, Process.myUid())) {
132             final int dstUserId = UserHandle.getUserId(uid);
133 
134             // Restrict install target to the wifi uid.
135             if (uid != Process.WIFI_UID) {
136                 Log.e(TAG, "Failed to install credentials as uid " + uid + ": cross-user installs"
137                         + " may only target wifi uids");
138                 return true;
139             }
140 
141             final Intent installIntent = new Intent(ACTION_INSTALL)
142                     .setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
143                     .putExtras(bundle);
144             startActivityAsUser(installIntent, new UserHandle(dstUserId));
145             return true;
146         }
147 
148         String alias = bundle.getString(Credentials.EXTRA_USER_KEY_ALIAS, null);
149         if (TextUtils.isEmpty(alias)) {
150             Log.e(TAG, "Cannot install key without an alias");
151             return true;
152         }
153 
154         final byte[] privateKeyData = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
155         final byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
156         final byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
157         new InstallKeyInKeyChain(alias, privateKeyData, certData, caListData, uid).execute();
158 
159         return false;
160     }
161 
162     /**
163      * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
164      */
165     private class ResetDialog
166             implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
167         private boolean mResetConfirmed;
168 
ResetDialog()169         private ResetDialog() {
170             final AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
171                     .setTitle(android.R.string.dialog_alert_title)
172                     .setMessage(R.string.credentials_reset_hint)
173                     .setPositiveButton(android.R.string.ok, this)
174                     .setNegativeButton(android.R.string.cancel, this)
175                     .create();
176             dialog.setOnDismissListener(this);
177             dialog.show();
178         }
179 
180         @Override
onClick(DialogInterface dialog, int button)181         public void onClick(DialogInterface dialog, int button) {
182             mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
183         }
184 
185         @Override
onDismiss(DialogInterface dialog)186         public void onDismiss(DialogInterface dialog) {
187             if (!mResetConfirmed) {
188                 finish();
189                 return;
190             }
191 
192             mResetConfirmed = false;
193             if (!mUtils.isSecure(UserHandle.myUserId())) {
194                 // This task will call finish() in the end.
195                 new ResetKeyStoreAndKeyChain().execute();
196             } else if (!confirmKeyGuard(CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST)) {
197                 Log.w(TAG, "Failed to launch credential confirmation for a secure user.");
198                 finish();
199             }
200             // Confirmation result will be handled in onActivityResult if needed.
201         }
202     }
203 
204     /**
205      * Background task to handle reset of both keystore and user installed CAs.
206      */
207     private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
208 
209         @Override
doInBackground(Void... unused)210         protected Boolean doInBackground(Void... unused) {
211 
212             // Clear all the users credentials could have been installed in for this user.
213             mUtils.resetKeyStore(UserHandle.myUserId());
214 
215             try {
216                 final KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
217                 try {
218                     return keyChainConnection.getService().reset();
219                 } catch (RemoteException e) {
220                     return false;
221                 } finally {
222                     keyChainConnection.close();
223                 }
224             } catch (InterruptedException e) {
225                 Thread.currentThread().interrupt();
226                 return false;
227             }
228         }
229 
230         @Override
onPostExecute(Boolean success)231         protected void onPostExecute(Boolean success) {
232             if (success) {
233                 Toast.makeText(CredentialStorage.this,
234                         R.string.credentials_erased, Toast.LENGTH_SHORT).show();
235                 clearLegacyVpnIfEstablished();
236             } else {
237                 Toast.makeText(CredentialStorage.this,
238                         R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
239             }
240             finish();
241         }
242     }
243 
clearLegacyVpnIfEstablished()244     private void clearLegacyVpnIfEstablished() {
245         final boolean isDone = VpnUtils.disconnectLegacyVpn(getApplicationContext());
246         if (isDone) {
247             Toast.makeText(CredentialStorage.this, R.string.vpn_disconnected,
248                     Toast.LENGTH_SHORT).show();
249         }
250     }
251 
252     /**
253      * Background task to install a certificate into KeyChain or the WiFi Keystore.
254      */
255     private class InstallKeyInKeyChain extends AsyncTask<Void, Void, Boolean> {
256         final String mAlias;
257         private final byte[] mKeyData;
258         private final byte[] mCertData;
259         private final byte[] mCaListData;
260         private final int mUid;
261 
InstallKeyInKeyChain(String alias, byte[] keyData, byte[] certData, byte[] caListData, int uid)262         InstallKeyInKeyChain(String alias, byte[] keyData, byte[] certData, byte[] caListData,
263                 int uid) {
264             mAlias = alias;
265             mKeyData = keyData;
266             mCertData = certData;
267             mCaListData = caListData;
268             mUid = uid;
269         }
270 
271         @Override
doInBackground(Void... unused)272         protected Boolean doInBackground(Void... unused) {
273             try (KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this)) {
274                 IKeyChainService service = keyChainConnection.getService();
275                 if (!service.installKeyPair(mKeyData, mCertData, mCaListData, mAlias, mUid)) {
276                     Log.w(TAG, String.format("Failed installing key %s", mAlias));
277                     return false;
278                 }
279 
280                 // If this is not a WiFi key, mark  it as user-selectable, so that it can be
281                 // selected by users from the Certificate Selection prompt.
282                 if (mUid == Process.SYSTEM_UID || mUid == KeyStore.UID_SELF) {
283                     service.setUserSelectable(mAlias, true);
284                 }
285 
286                 return true;
287             } catch (RemoteException e) {
288                 Log.w(TAG, String.format("Failed to install key %s to uid %d", mAlias, mUid), e);
289                 return false;
290             } catch (InterruptedException e) {
291                 Log.w(TAG, String.format("Interrupted while installing key %s", mAlias), e);
292                 Thread.currentThread().interrupt();
293                 return false;
294             }
295         }
296 
297         @Override
onPostExecute(Boolean result)298         protected void onPostExecute(Boolean result) {
299             CredentialStorage.this.onKeyInstalled(mAlias, mUid, result);
300         }
301     }
302 
onKeyInstalled(String alias, int uid, boolean result)303     private void onKeyInstalled(String alias, int uid, boolean result) {
304         if (!result) {
305             Log.w(TAG, String.format("Error installing alias %s for uid %d", alias, uid));
306             finish();
307             return;
308         }
309 
310         Log.i(TAG, String.format("Successfully installed alias %s to uid %d.",
311                 alias, uid));
312 
313         // Send the broadcast.
314         final Intent broadcast = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED);
315         sendBroadcast(broadcast);
316         setResult(RESULT_OK);
317 
318         finish();
319     }
320 
321     /**
322      * Check that the caller is either certinstaller or Settings running in a profile of this user.
323      */
checkCallerIsCertInstallerOrSelfInProfile()324     private boolean checkCallerIsCertInstallerOrSelfInProfile() {
325         if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) {
326             // CertInstaller is allowed to install credentials if it has the same signature as
327             // Settings package.
328             return getPackageManager().checkSignatures(
329                     getCallingPackage(), getPackageName()) == PackageManager.SIGNATURE_MATCH;
330         }
331 
332         final int launchedFromUserId;
333         try {
334             final int launchedFromUid = android.app.ActivityManager.getService()
335                     .getLaunchedFromUid(getActivityToken());
336             if (launchedFromUid == -1) {
337                 Log.e(TAG, ACTION_INSTALL + " must be started with startActivityForResult");
338                 return false;
339             }
340             if (!UserHandle.isSameApp(launchedFromUid, Process.myUid())) {
341                 // Not the same app
342                 return false;
343             }
344             launchedFromUserId = UserHandle.getUserId(launchedFromUid);
345         } catch (RemoteException re) {
346             // Error talking to ActivityManager, just give up
347             return false;
348         }
349 
350         final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
351         final UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId);
352         // Caller is running in a profile of this user
353         return ((parentInfo != null) && (parentInfo.id == UserHandle.myUserId()));
354     }
355 
356     /**
357      * Confirm existing key guard, returning password via onActivityResult.
358      */
confirmKeyGuard(int requestCode)359     private boolean confirmKeyGuard(int requestCode) {
360         final Resources res = getResources();
361         final ChooseLockSettingsHelper.Builder builder =
362                 new ChooseLockSettingsHelper.Builder(this);
363         return builder.setRequestCode(requestCode)
364                 .setTitle(res.getText(R.string.credentials_title))
365                 .show();
366     }
367 
368     @Override
onActivityResult(int requestCode, int resultCode, Intent data)369     public void onActivityResult(int requestCode, int resultCode, Intent data) {
370         super.onActivityResult(requestCode, resultCode, data);
371         if (requestCode == CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST) {
372             if (resultCode == Activity.RESULT_OK) {
373                 new ResetKeyStoreAndKeyChain().execute();
374                 return;
375             }
376             // failed confirmation, bail
377             finish();
378         }
379     }
380 }
381