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