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