diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3f66cb8d2d4..0ba07c3a3ca 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1313,6 +1313,11 @@ android:exported="false"> + + + diff --git a/res/layout/app_source_certificate_warning_dialog.xml b/res/layout/app_source_certificate_warning_dialog.xml new file mode 100644 index 00000000000..f0902c7ccf5 --- /dev/null +++ b/res/layout/app_source_certificate_warning_dialog.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + diff --git a/res/layout/user_credential.xml b/res/layout/user_credential.xml index f441bdac825..0ea46f23f4f 100644 --- a/res/layout/user_credential.xml +++ b/res/layout/user_credential.xml @@ -73,5 +73,14 @@ android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorTertiary" android:paddingStart="?android:attr/listPreferredItemPaddingStart"/> + + diff --git a/res/values/strings.xml b/res/values/strings.xml index ecfce4a5559..9a1e8408ecd 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5902,6 +5902,8 @@ Installed for VPN and apps Installed for Wi-Fi + + Installed for app install source verification Remove all the contents? @@ -5916,14 +5918,20 @@ VPN & app user certificate Wi\u2011Fi certificate + + App Source certificate Your privacy is at risk CA certificates are used by websites, apps, and VPNs for encryption. Only install CA certificates from organizations you trust. \n\n If you install a CA certificate, the certificate owner could access your information, such as passwords, messages, or credit card details, from websites you visit or apps you use - even if that information is encrypted. - - Don\u2019t install - - Install anyways + + Don\u2019t install + + Install anyways + + Install this certificate at your own risk + + App source certificates verify that apps are safe for your device. Only install certificates from organizations you trust. Certificate not installed @@ -6549,14 +6557,28 @@ one user certificate one CA certificate - + %d CA certificates + + one App Source certificate Credential details Removed credential: %s No user credentials installed + + Restart to use certificate + + To use this app source certificate, you need to restart your device + + Restart to finish uninstalling + + To uninstall this app source certificate, you need to restart your device + + Restart + + Not now Spell checker diff --git a/res/xml/install_certificate_from_storage.xml b/res/xml/install_certificate_from_storage.xml index 0cf4a36e46d..640cb562c1e 100644 --- a/res/xml/install_certificate_from_storage.xml +++ b/res/xml/install_certificate_from_storage.xml @@ -62,6 +62,18 @@ + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/RebootDialog.java b/src/com/android/settings/RebootDialog.java new file mode 100644 index 00000000000..700d140065c --- /dev/null +++ b/src/com/android/settings/RebootDialog.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import android.annotation.StringRes; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.os.PowerManager; + +import androidx.appcompat.app.AlertDialog; + +/** Dialog to confirm a reboot immediately, or later. */ +public class RebootDialog implements DialogInterface.OnClickListener, + DialogInterface.OnDismissListener { + private final Activity mActivity; + private final AlertDialog mDialog; + private final String mRebootReason; + + public RebootDialog(Activity activity, @StringRes int titleRes, @StringRes int messageRes, + String rebootReason) { + mActivity = activity; + mDialog = new AlertDialog.Builder(activity) + .setTitle(titleRes) + .setMessage(messageRes) + .setPositiveButton(R.string.app_src_cert_reboot_dialog_button_restart, this) + .setNegativeButton(R.string.app_src_cert_reboot_dialog_button_not_now, null) + .setOnDismissListener(this) + .create(); + mRebootReason = rebootReason; + } + + /** Shows the dialog. */ + public void show() { + mDialog.show(); + } + + @Override + public void onClick(DialogInterface dialog, int button) { + if (button == DialogInterface.BUTTON_POSITIVE) { + PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE); + pm.reboot(mRebootReason); + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + mActivity.finish(); + } +} diff --git a/src/com/android/settings/UserCredentialsSettings.java b/src/com/android/settings/UserCredentialsSettings.java index d322819ba98..4983a36ee1d 100644 --- a/src/com/android/settings/UserCredentialsSettings.java +++ b/src/com/android/settings/UserCredentialsSettings.java @@ -193,6 +193,8 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment for (final Credential credential : credentials) { if (credential.isSystem()) { removeGrantsAndDelete(credential); + } else if (credential.isFsverity()) { + deleteAppSourceCredential(credential); } else { deleteWifiCredential(credential); } @@ -219,6 +221,16 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment } } + private void deleteAppSourceCredential(final Credential credential) { + final KeyStore keyStore = KeyStore.getInstance(); + final EnumSet storedTypes = credential.getStoredTypes(); + + if (storedTypes.contains(Credential.Type.APP_SOURCE_CERTIFICATE)) { + keyStore.delete(Credentials.APP_SOURCE_CERTIFICATE + credential.getAlias(), + Process.FSVERITY_CERT_UID); + } + } + private void removeGrantsAndDelete(final Credential credential) { final KeyChainConnection conn; try { @@ -242,10 +254,21 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment protected void onPostExecute(Credential... credentials) { if (targetFragment instanceof UserCredentialsSettings && targetFragment.isAdded()) { final UserCredentialsSettings target = (UserCredentialsSettings) targetFragment; + boolean includeFsverity = false; for (final Credential credential : credentials) { target.announceRemoval(credential.alias); + if (credential.isFsverity()) { + includeFsverity = true; + } } target.refreshItems(); + if (includeFsverity) { + new RebootDialog( + getActivity(), + R.string.app_src_cert_reboot_dialog_uninstall_title, + R.string.app_src_cert_reboot_dialog_uninstall_message, + "Reboot to make new fsverity cert effective").show(); + } } } } @@ -272,10 +295,12 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment final int myUserId = UserHandle.myUserId(); final int systemUid = UserHandle.getUid(myUserId, Process.SYSTEM_UID); final int wifiUid = UserHandle.getUid(myUserId, Process.WIFI_UID); + final int fsverityUid = UserHandle.getUid(myUserId, Process.FSVERITY_CERT_UID); List credentials = new ArrayList<>(); credentials.addAll(getCredentialsForUid(keyStore, systemUid).values()); credentials.addAll(getCredentialsForUid(keyStore, wifiUid).values()); + credentials.addAll(getCredentialsForUid(keyStore, fsverityUid).values()); return credentials; } @@ -402,6 +427,7 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment credentialViewTypes.put(R.id.contents_userkey, Credential.Type.USER_KEY); credentialViewTypes.put(R.id.contents_usercrt, Credential.Type.USER_CERTIFICATE); credentialViewTypes.put(R.id.contents_cacrt, Credential.Type.CA_CERTIFICATE); + credentialViewTypes.put(R.id.contents_appsrccrt, Credential.Type.APP_SOURCE_CERTIFICATE); } protected static View getCredentialView(Credential item, @LayoutRes int layoutResource, @@ -411,9 +437,15 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment } ((TextView) view.findViewById(R.id.alias)).setText(item.alias); - ((TextView) view.findViewById(R.id.purpose)).setText(item.isSystem() - ? R.string.credential_for_vpn_and_apps - : R.string.credential_for_wifi); + int purpose; + if (item.isSystem()) { + purpose = R.string.credential_for_vpn_and_apps; + } else if (item.isFsverity()) { + purpose = R.string.credential_for_fsverity; + } else { + purpose = R.string.credential_for_wifi; + } + ((TextView) view.findViewById(R.id.purpose)).setText(purpose); view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE); if (expanded) { @@ -435,7 +467,8 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment static enum Type { CA_CERTIFICATE (Credentials.CA_CERTIFICATE), USER_CERTIFICATE (Credentials.USER_CERTIFICATE), - USER_KEY(Credentials.USER_PRIVATE_KEY, Credentials.USER_SECRET_KEY); + USER_KEY(Credentials.USER_PRIVATE_KEY, Credentials.USER_SECRET_KEY), + APP_SOURCE_CERTIFICATE(Credentials.APP_SOURCE_CERTIFICATE); final String[] prefix; @@ -452,7 +485,8 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment /** * UID under which this credential is stored. Typically {@link Process#SYSTEM_UID} but can - * also be {@link Process#WIFI_UID} for credentials installed as wifi certificates. + * also be {@link Process#WIFI_UID} for credentials installed as wifi certificates, or + * {@link Process#FSVERITY_CERT_UID} for app source certificates. */ final int uid; @@ -462,6 +496,7 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment *
  • {@link Credentials.CA_CERTIFICATE}
  • *
  • {@link Credentials.USER_CERTIFICATE}
  • *
  • {@link Credentials.USER_KEY}
  • + *
  • {@link Credentials.APP_SOURCE_CERTIFICATE}
  • * */ final EnumSet storedTypes = EnumSet.noneOf(Type.class); @@ -512,6 +547,10 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment return UserHandle.getAppId(uid) == Process.SYSTEM_UID; } + public boolean isFsverity() { + return UserHandle.getAppId(uid) == Process.FSVERITY_CERT_UID; + } + public String getAlias() { return alias; } public EnumSet getStoredTypes() { diff --git a/src/com/android/settings/security/CredentialStorage.java b/src/com/android/settings/security/CredentialStorage.java index 5e647230852..28d4f63bde1 100644 --- a/src/com/android/settings/security/CredentialStorage.java +++ b/src/com/android/settings/security/CredentialStorage.java @@ -44,6 +44,7 @@ import androidx.fragment.app.FragmentActivity; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; +import com.android.settings.RebootDialog; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.vpn2.VpnUtils; @@ -130,10 +131,10 @@ public final class CredentialStorage extends FragmentActivity { if (uid != KeyStore.UID_SELF && !UserHandle.isSameUser(uid, Process.myUid())) { final int dstUserId = UserHandle.getUserId(uid); - // Restrict install target to the wifi uid. - if (uid != Process.WIFI_UID) { + // Restrict install target to the known uid. + if (uid != Process.WIFI_UID && uid != Process.FSVERITY_CERT_UID) { Log.e(TAG, "Failed to install credentials as uid " + uid + ": cross-user installs" - + " may only target wifi uids"); + + " may only target known uids"); return true; } @@ -309,6 +310,16 @@ public final class CredentialStorage extends FragmentActivity { Log.i(TAG, String.format("Successfully installed alias %s to uid %d.", alias, uid)); + if (uid == Process.FSVERITY_CERT_UID) { + new RebootDialog( + this, + R.string.app_src_cert_reboot_dialog_install_title, + R.string.app_src_cert_reboot_dialog_install_message, + "Reboot to make new fsverity cert effective").show(); + setResult(RESULT_OK); + return; + } + // Send the broadcast. final Intent broadcast = new Intent(KeyChain.ACTION_KEYCHAIN_CHANGED); sendBroadcast(broadcast); diff --git a/src/com/android/settings/security/InstallAppSourceCertificatePreferenceController.java b/src/com/android/settings/security/InstallAppSourceCertificatePreferenceController.java new file mode 100644 index 00000000000..86c02cd3dc1 --- /dev/null +++ b/src/com/android/settings/security/InstallAppSourceCertificatePreferenceController.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.security; + +import android.content.Context; +import android.os.SystemProperties; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.core.BasePreferenceController; + +class InstallAppSourceCertificatePreferenceController extends + BasePreferenceController { + + private static final String APK_VERITY_PROPERTY = "ro.apk_verity.mode"; + private static final int APK_VERITY_MODE_ENABLED = 2; + + InstallAppSourceCertificatePreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return isApkVerityEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @VisibleForTesting + static boolean isApkVerityEnabled() { + // TODO(victorhsieh): replace this with a new API in PackageManager once it is landed. + return SystemProperties.getInt(APK_VERITY_PROPERTY, 0) == APK_VERITY_MODE_ENABLED; + } +} diff --git a/src/com/android/settings/security/InstallAppSourceCertificateWarning.java b/src/com/android/settings/security/InstallAppSourceCertificateWarning.java new file mode 100644 index 00000000000..9ea1b0ea2a4 --- /dev/null +++ b/src/com/android/settings/security/InstallAppSourceCertificateWarning.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.security; + +import android.annotation.Nullable; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.security.Credentials; +import android.view.View; +import android.widget.Toast; + +import com.android.settings.R; + +import com.google.android.setupcompat.template.FooterBarMixin; +import com.google.android.setupcompat.template.FooterButton; +import com.google.android.setupdesign.GlifLayout; + +/** + * Creates a warning dialog explaining the consequences of installing a certificate + * This is displayed before an app source certificate can be installed from Settings. + */ +public class InstallAppSourceCertificateWarning extends Activity { + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.app_source_certificate_warning_dialog); + final GlifLayout layout = findViewById(R.id.setup_wizard_layout); + + final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); + mixin.setSecondaryButton( + new FooterButton.Builder(this) + .setText(R.string.certificate_warning_install_anyway) + .setListener(installCertificate()) + .setButtonType(FooterButton.ButtonType.OTHER) + .setTheme(R.style.SudGlifButton_Secondary) + .build() + ); + + mixin.setPrimaryButton( + new FooterButton.Builder(this) + .setText(R.string.certificate_warning_dont_install) + .setListener(returnToInstallCertificateFromStorage()) + .setButtonType(FooterButton.ButtonType.NEXT) + .setTheme(R.style.SudGlifButton_Primary) + .build() + ); + } + + private View.OnClickListener installCertificate() { + return v -> { + final Intent intent = new Intent(); + intent.setAction(Credentials.INSTALL_ACTION); + intent.putExtra(Credentials.EXTRA_CERTIFICATE_USAGE, + Credentials.CERTIFICATE_USAGE_APP_SOURCE); + startActivity(intent); + finish(); + }; + } + + private View.OnClickListener returnToInstallCertificateFromStorage() { + return v -> { + Toast.makeText(this, R.string.cert_not_installed, Toast.LENGTH_SHORT).show(); + finish(); + }; + } + +} diff --git a/src/com/android/settings/security/InstallCaCertificateWarning.java b/src/com/android/settings/security/InstallCaCertificateWarning.java index 701d9f46432..91faae1f6a1 100644 --- a/src/com/android/settings/security/InstallCaCertificateWarning.java +++ b/src/com/android/settings/security/InstallCaCertificateWarning.java @@ -46,7 +46,7 @@ public class InstallCaCertificateWarning extends Activity { final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); mixin.setSecondaryButton( new FooterButton.Builder(this) - .setText(R.string.ca_certificate_warning_install_anyway) + .setText(R.string.certificate_warning_install_anyway) .setListener(installCaCertificate()) .setButtonType(FooterButton.ButtonType.OTHER) .setTheme(R.style.SudGlifButton_Secondary) @@ -55,7 +55,7 @@ public class InstallCaCertificateWarning extends Activity { mixin.setPrimaryButton( new FooterButton.Builder(this) - .setText(R.string.ca_certificate_warning_dont_install) + .setText(R.string.certificate_warning_dont_install) .setListener(returnToInstallCertificateFromStorage()) .setButtonType(FooterButton.ButtonType.NEXT) .setTheme(R.style.SudGlifButton_Primary) diff --git a/src/com/android/settings/security/InstallCertificateFromStorage.java b/src/com/android/settings/security/InstallCertificateFromStorage.java index 3810531815a..c5a93f076e7 100644 --- a/src/com/android/settings/security/InstallCertificateFromStorage.java +++ b/src/com/android/settings/security/InstallCertificateFromStorage.java @@ -60,6 +60,7 @@ public class InstallCertificateFromStorage extends DashboardFragment { private static List buildPreferenceControllers(Context context, Lifecycle lifecycle) { + // TODO(eranm,victorhsieh): use "settings:controller" in xml and remove the following. final List controllers = new ArrayList<>(); controllers.add(new InstallCaCertificatePreferenceController(context)); controllers.add(new InstallUserCertificatePreferenceController(context)); diff --git a/tests/robotests/src/com/android/settings/security/InstallCertificateFromStorageTest.java b/tests/robotests/src/com/android/settings/security/InstallCertificateFromStorageTest.java index 61c39fca933..03bdb088809 100644 --- a/tests/robotests/src/com/android/settings/security/InstallCertificateFromStorageTest.java +++ b/tests/robotests/src/com/android/settings/security/InstallCertificateFromStorageTest.java @@ -67,6 +67,7 @@ public class InstallCertificateFromStorageTest { mTestKeys.add("install_ca_certificate"); mTestKeys.add("install_user_certificate"); mTestKeys.add("install_wifi_certificate"); + mTestKeys.add("install_app_src_certificate"); } @Test diff --git a/tests/robotests/src/com/android/settings/security/RestrictedEncryptionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/RestrictedEncryptionPreferenceControllerTest.java index 78ad5e57510..7d3b1176fbb 100644 --- a/tests/robotests/src/com/android/settings/security/RestrictedEncryptionPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/security/RestrictedEncryptionPreferenceControllerTest.java @@ -37,6 +37,9 @@ import org.robolectric.annotation.Config; @Config(shadows = ShadowUserManager.class) public class RestrictedEncryptionPreferenceControllerTest { + private static final String APK_VERITY_PROPERTY = "ro.apk_verity.mode"; + private static final int APK_VERITY_MODE_ENABLED = 2; + private Context mContext; private ShadowUserManager mUserManager; private CredentialStoragePreferenceController mCredentialStoragePreferenceController; @@ -46,6 +49,8 @@ public class RestrictedEncryptionPreferenceControllerTest { private InstallCaCertificatePreferenceController mInstallCaCertificatePreferenceController; private InstallUserCertificatePreferenceController mInstallUserCertificatePreferenceController; private InstallWifiCertificatePreferenceController mInstallWifiCertificatePreferenceController; + private InstallAppSourceCertificatePreferenceController + mInstallAppSourceCertificatePreferenceController; private Lifecycle mLifecycle; private LifecycleOwner mLifecycleOwner; @@ -64,6 +69,9 @@ public class RestrictedEncryptionPreferenceControllerTest { new UserCredentialsPreferenceController(mContext); mInstallCaCertificatePreferenceController = new InstallCaCertificatePreferenceController(mContext); + mInstallAppSourceCertificatePreferenceController = + new InstallAppSourceCertificatePreferenceController( + mContext, "install_app_src_certificate"); mInstallUserCertificatePreferenceController = new InstallUserCertificatePreferenceController(mContext); mInstallWifiCertificatePreferenceController = @@ -80,6 +88,8 @@ public class RestrictedEncryptionPreferenceControllerTest { assertThat(mInstallCaCertificatePreferenceController.isAvailable()).isTrue(); assertThat(mInstallUserCertificatePreferenceController.isAvailable()).isTrue(); assertThat(mInstallWifiCertificatePreferenceController.isAvailable()).isTrue(); + assertThat(mInstallAppSourceCertificatePreferenceController.isAvailable()) + .isEqualTo(InstallAppSourceCertificatePreferenceController.isApkVerityEnabled()); } @Test