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.KeyProperties;
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) && checkCallerIsSelf()) {
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, KeyProperties.UID_SELF);
130 
131         if (uid != KeyProperties.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                     .setPackage(getPackageName())
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 == KeyProperties.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 Settings.
324      */
checkCallerIsSelf()325     private boolean checkCallerIsSelf() {
326         try {
327             return Process.myUid() == android.app.ActivityManager.getService()
328                     .getLaunchedFromUid(getActivityToken());
329         } catch (RemoteException re) {
330             // Error talking to ActivityManager, just give up
331             return false;
332         }
333     }
334 
335     /**
336      * Check that the caller is either certinstaller or Settings running in a profile of this user.
337      */
checkCallerIsCertInstallerOrSelfInProfile()338     private boolean checkCallerIsCertInstallerOrSelfInProfile() {
339         if (TextUtils.equals("com.android.certinstaller", getCallingPackage())) {
340             // CertInstaller is allowed to install credentials if it has the same signature as
341             // Settings package.
342             return getPackageManager().checkSignatures(
343                     getCallingPackage(), getPackageName()) == PackageManager.SIGNATURE_MATCH;
344         }
345 
346         final int launchedFromUserId;
347         try {
348             final int launchedFromUid = android.app.ActivityManager.getService()
349                     .getLaunchedFromUid(getActivityToken());
350             if (launchedFromUid == -1) {
351                 Log.e(TAG, ACTION_INSTALL + " must be started with startActivityForResult");
352                 return false;
353             }
354             if (!UserHandle.isSameApp(launchedFromUid, Process.myUid())) {
355                 // Not the same app
356                 return false;
357             }
358             launchedFromUserId = UserHandle.getUserId(launchedFromUid);
359         } catch (RemoteException re) {
360             // Error talking to ActivityManager, just give up
361             return false;
362         }
363 
364         final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
365         final UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId);
366         // Caller is running in a profile of this user
367         return ((parentInfo != null) && (parentInfo.id == UserHandle.myUserId()));
368     }
369 
370     /**
371      * Confirm existing key guard, returning password via onActivityResult.
372      */
confirmKeyGuard(int requestCode)373     private boolean confirmKeyGuard(int requestCode) {
374         final Resources res = getResources();
375         final ChooseLockSettingsHelper.Builder builder =
376                 new ChooseLockSettingsHelper.Builder(this);
377         return builder.setRequestCode(requestCode)
378                 .setTitle(res.getText(R.string.credentials_title))
379                 .show();
380     }
381 
382     @Override
onActivityResult(int requestCode, int resultCode, Intent data)383     public void onActivityResult(int requestCode, int resultCode, Intent data) {
384         super.onActivityResult(requestCode, resultCode, data);
385         if (requestCode == CONFIRM_CLEAR_SYSTEM_CREDENTIAL_REQUEST) {
386             if (resultCode == Activity.RESULT_OK) {
387                 new ResetKeyStoreAndKeyChain().execute();
388                 return;
389             }
390             // failed confirmation, bail
391             finish();
392         }
393     }
394 }
395