[DO NOT MERGE] Rollup changes from R am: 7830ab3251
Original change: https://android-review.googlesource.com/c/platform/packages/apps/CertInstaller/+/1363500
Change-Id: Iaf727935c6195a71d684188629e5c9cafae48b46
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7d543fb..886b7ca 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -38,6 +38,7 @@
<activity-alias android:name=".InstallCertAsUser"
android:targetActivity=".CertInstallerMain"
+ android:exported="true"
android:permission="com.android.certinstaller.INSTALL_AS_USER">
<intent-filter>
<action android:name="android.credentials.INSTALL_AS_USER"/>
diff --git a/res/layout/name_certificate_dialog.xml b/res/layout/name_certificate_dialog.xml
new file mode 100644
index 0000000..baa15d9
--- /dev/null
+++ b/res/layout/name_certificate_dialog.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp">
+
+ <TextView android:id="@+id/error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="@color/red"
+ android:textStyle="bold"
+ android:visibility="gone" />
+
+ <TextView android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/certificate_name"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/colorAccent"/>
+
+ <EditText android:id="@+id/certificate_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/dialog_edit_text"/>
+
+ </LinearLayout>
+</ScrollView>
diff --git a/res/layout/name_credential_dialog.xml b/res/layout/name_credential_dialog.xml
deleted file mode 100644
index 6dd46ff..0000000
--- a/res/layout/name_credential_dialog.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="15dip">
-
- <TextView android:id="@+id/error"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textColor="@color/red"
- android:textStyle="bold"
- android:visibility="gone" />
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/credential_name"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <EditText android:id="@+id/credential_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="True"/>
-
- <LinearLayout
- android:id="@+id/credential_usage_group"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical" >
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="12dp"
- android:text="@string/credential_usage_label"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <Spinner
- android:id="@+id/credential_usage"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:entries="@array/credential_usage" />
-
- <TextView
- android:id="@+id/credential_capabilities_warning"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="12dp"
- android:text="@string/certificate_capabilities_warning"
- android:textColor="@color/red" />
-
- </LinearLayout>
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="12dp"
- android:text="@string/credential_info" />
-
- <TextView android:id="@+id/credential_info"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-
- </LinearLayout>
-</ScrollView>
diff --git a/res/layout/select_certificate_usage_dialog.xml b/res/layout/select_certificate_usage_dialog.xml
new file mode 100644
index 0000000..6a053a4
--- /dev/null
+++ b/res/layout/select_certificate_usage_dialog.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp">
+
+ <LinearLayout
+ android:id="@+id/certificate_usage_group"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/select_certificate_usage_title"
+ style="@style/dialog_title"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/dividerHorizontal"/>
+
+ <RadioGroup
+ android:id="@+id/certificate_usage"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp">
+
+ <RadioButton
+ android:id="@+id/user_certificate"
+ style="@style/dialog_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/user_certificate"
+ android:checked="true"/>
+
+ <RadioButton
+ android:id="@+id/wifi_certificate"
+ style="@style/dialog_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/wifi_certificate"/>
+
+ </RadioGroup>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/dividerHorizontal"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 062c1ce..25f6905 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -15,8 +15,6 @@
-->
<resources>
- <bool name="config_auto_cert_approval">true</bool>
-
<string name="config_system_install_component" translatable="false">com.android.settings/com.android.settings.security.CredentialStorage</string>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 020a014..7cf78ce 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -26,10 +26,10 @@
<string name="extracting_pkcs12">Extracting\u2026</string>
<!-- Title of dialog to enter password for pkcs12 file -->
<string name="pkcs12_file_password_dialog_title">Extract from %s</string>
- <!-- Title of dialog to name a credential -->
- <string name="name_credential_dialog_title">Name the certificate</string>
- <!-- Description for the credential name input box -->
- <string name="credential_name">Certificate name:</string>
+ <!-- Title of a dialog. This title is asking the user to name the certificate they're installing on their device. -->
+ <string name="name_credential_dialog_title">Name this certificate</string>
+ <!-- Input field in a dialog. This dialog is asking the user to name the certificate they are installing on their device. -->
+ <string name="certificate_name">Certificate name</string>
<!-- Title of the credential info -->
<!-- Description for the credential password input box -->
<string name="credential_password">Type the password to extract the certificates.</string>
@@ -58,14 +58,49 @@
<string name="action_missing_private_key">Private key required to install a certificate</string>
<string name="action_missing_user_cert">Certificate required to install a private key</string>
- <!-- toast message -->
- <string name="cert_is_added"><xliff:g id="credential">%s</xliff:g> is installed.</string>
+ <!-- Title of dialog to select the certificate usage when installing a certificate outside Settings -->
+ <string name="select_certificate_usage_title">Choose a certificate type</string>
+ <!-- Title of dialog that lets the user know that they must install CA certificates
+ in Settings. -->
+ <string name="redirect_ca_certificate_title">Install CA certificates in Settings</string>
+ <!-- Message of dialog that lets the user know that they must install CA certificates in
+ Settings. The placeholder is the name of the app that was trying to install the CA
+ certificate. -->
+ <string name="redirect_ca_certificate_with_app_info_message">This certificate from
+ <xliff:g id="requesting_app" example="Some App">%1$s</xliff:g> must be installed in
+ Settings. Only install CA certificates from organizations you trust.</string>
+ <!-- Button of dialog to redirect user when installing a CA certificate outside Settings -->
+ <string name="redirect_ca_certificate_close_button">Close</string>
+ <!-- Title of dialog to inform user that certificate selected is invalid -->
+ <string name="invalid_certificate_title">Can\'t use this file</string>
+ <!-- Button of dialog to inform user that certificate selected is invalid -->
+ <string name="invalid_certificate_close_button">Close</string>
+ <!-- Message of dialog to inform user that certificate selected is invalid -->
+ <string name="invalid_certificate_message">This file can\'t be used as a <xliff:g id="certificate_usage">%1$s</xliff:g></string>
+ <!-- Default certificate type used in dialog to inform user that certificate selected is invalid -->
+ <string name="certificate">Certificate</string>
+ <!-- CA certificate type used in dialog to inform user that certificate selected is invalid -->
+ <string name="ca_certificate">CA certificate</string>
+ <!-- User certificate type used in dialog to inform user that certificate selected is invalid -->
+ <string name="user_certificate">VPN & app user certificate</string>
+ <!-- WiFi certificate type used in dialog to inform user that certificate selected is invalid -->
+ <string name="wifi_certificate">Wi\u2011Fi certificate</string>
+
+
+ <!-- Confirmation toast. This toast lets the user know that they successfully installed a CA certificate on their device. -->
+ <string name="ca_cert_is_added">CA certificate installed</string>
+ <!-- Confirmation toast. This toast lets the user know that they successfully installed a user certificate on their device. -->
+ <string name="user_cert_is_added">User certificate installed</string>
+ <!-- Confirmation toast. This toast lets the user know that they successfully installed a Wi-Fi certificate on their device. -->
+ <string name="wifi_cert_is_added">Wi\u2011Fi certificate installed</string>
<!-- toast message -->
<string name="cert_too_large_error">Couldn\'t install because the certificate size is too large.</string>
<!-- toast message -->
<string name="cert_missing_error">Couldn\'t install because the certificate file couldn\'t be located.</string>
<!-- toast message -->
<string name="cert_read_error">Couldn\'t install because the certificate file couldn\'t be read.</string>
+ <!-- toast message -->
+ <string name="cert_temp_error">Temporary failure. Please try again later.</string>
<!-- Message displayed when a user other than the owner on a multi-user system tries to
install a certificate into the certificate store. [CHAR LIMIT=NONE] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index b1d493e..7bac29f 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -28,4 +28,20 @@
<item name="android:textAppearance">@android:style/TextAppearance.Material.Subhead</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
+ <style name="dialog_title">
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">20sp</item>
+ <item name="android:layout_marginBottom">16dp</item>
+ </style>
+ <style name="dialog_edit_text">
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:backgroundTint">?android:attr/colorAccent</item>
+ <item name="android:singleLine">True</item>
+ </style>
+ <style name="dialog_button">
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:layout_marginBottom">12dp</item>
+ <item name="android:layout_marginLeft">4dp</item>
+ </style>
</resources>
diff --git a/src/com/android/certinstaller/CertInstaller.java b/src/com/android/certinstaller/CertInstaller.java
index dd849a7..3a6b7b3 100644
--- a/src/com/android/certinstaller/CertInstaller.java
+++ b/src/com/android/certinstaller/CertInstaller.java
@@ -21,25 +21,24 @@
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.KeyguardManager;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Process;
+import android.security.Credentials;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
-import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Slog;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.EditText;
-import android.widget.Spinner;
+import android.widget.RadioGroup;
import android.widget.Toast;
import java.io.Serializable;
@@ -57,17 +56,15 @@
private static final int NAME_CREDENTIAL_DIALOG = 1;
private static final int PKCS12_PASSWORD_DIALOG = 2;
private static final int PROGRESS_BAR_DIALOG = 3;
+ private static final int REDIRECT_CA_CERTIFICATE_DIALOG = 4;
+ private static final int SELECT_CERTIFICATE_USAGE_DIALOG = 5;
+ private static final int INVALID_CERTIFICATE_DIALOG = 6;
private static final int REQUEST_SYSTEM_INSTALL_CODE = 1;
- private static final int REQUEST_CONFIRM_CREDENTIALS = 2;
// key to states Bundle
private static final String NEXT_ACTION_KEY = "na";
- // Values for usage type spinner
- private static final int USAGE_TYPE_SYSTEM = 0;
- private static final int USAGE_TYPE_WIFI = 1;
-
private final ViewHelper mView = new ViewHelper();
private int mState;
@@ -98,20 +95,8 @@
toastErrorAndFinish(R.string.no_cert_to_saved);
finish();
} else {
- // Confirm credentials if there's _only_ a CA certificate
- // NOTE: This will affect WiFi CA certificates - those should not require
- // confirming the lock screen credentials but the code currently cannot skip the
- // confirmation for WiFi CA certificates because the user designates the certificate
- // to a UID only after this stage.
- if (mCredentials.hasCaCerts() && !mCredentials.hasPrivateKey() &&
- !mCredentials.hasUserCertificate()) {
- KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
- Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null);
- if (intent == null) { // No screenlock
- extractPkcs12OrInstall();
- } else {
- startActivityForResult(intent, REQUEST_CONFIRM_CREDENTIALS);
- }
+ if (installingCaCertificate()) {
+ extractPkcs12OrInstall();
} else {
if (mCredentials.hasUserCertificate() && !mCredentials.hasPrivateKey()) {
toastErrorAndFinish(R.string.action_missing_private_key);
@@ -129,6 +114,11 @@
}
}
+ private boolean installingCaCertificate() {
+ return mCredentials.hasCaCerts() && !mCredentials.hasPrivateKey() &&
+ !mCredentials.hasUserCertificate();
+ }
+
@Override
protected void onResume() {
super.onResume();
@@ -164,7 +154,7 @@
return createPkcs12PasswordDialog();
case NAME_CREDENTIAL_DIALOG:
- return createNameCredentialDialog();
+ return createNameCertificateDialog();
case PROGRESS_BAR_DIALOG:
ProgressDialog dialog = new ProgressDialog(this);
@@ -173,6 +163,15 @@
dialog.setCancelable(false);
return dialog;
+ case REDIRECT_CA_CERTIFICATE_DIALOG:
+ return createRedirectCaCertificateDialog();
+
+ case SELECT_CERTIFICATE_USAGE_DIALOG:
+ return createSelectCertificateUsageDialog();
+
+ case INVALID_CERTIFICATE_DIALOG:
+ return createInvalidCertificateDialog();
+
default:
return null;
}
@@ -189,25 +188,14 @@
}
Log.d(TAG, "credential is added: " + mCredentials.getName());
- Toast.makeText(this, getString(R.string.cert_is_added, mCredentials.getName()),
- Toast.LENGTH_LONG).show();
-
- if (mCredentials.includesVpnAndAppsTrustAnchors()) {
- // more work to do, don't finish just yet
- new InstallVpnAndAppsTrustAnchorsTask().execute();
- return;
+ if (mCredentials.getCertUsageSelected().equals(Credentials.CERTIFICATE_USAGE_WIFI)) {
+ Toast.makeText(this, R.string.wifi_cert_is_added, Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(this, R.string.user_cert_is_added, Toast.LENGTH_LONG).show();
}
setResult(RESULT_OK);
finish();
break;
- case REQUEST_CONFIRM_CREDENTIALS:
- if (resultCode == RESULT_OK) {
- extractPkcs12OrInstall();
- return;
- }
- // Failed to confirm credentials, do nothing.
- finish();
- break;
default:
Log.w(TAG, "unknown request code: " + requestCode);
finish();
@@ -223,14 +211,19 @@
new Pkcs12ExtractAction("").run(this);
}
} else {
- MyAction action = new InstallOthersAction();
- action.run(this);
+ if (mCredentials.calledBySettings()) {
+ MyAction action = new InstallOthersAction();
+ action.run(this);
+ } else {
+ createRedirectOrSelectUsageDialog();
+ }
}
}
private class InstallVpnAndAppsTrustAnchorsTask extends AsyncTask<Void, Void, Boolean> {
- @Override protected Boolean doInBackground(Void... unused) {
+ @Override
+ protected Boolean doInBackground(Void... unused) {
try {
try (KeyChainConnection keyChainConnection = KeyChain.bind(CertInstaller.this)) {
return mCredentials.installVpnAndAppsTrustAnchors(CertInstaller.this,
@@ -242,8 +235,11 @@
}
}
- @Override protected void onPostExecute(Boolean success) {
+ @Override
+ protected void onPostExecute(Boolean success) {
if (success) {
+ Toast.makeText(getApplicationContext(), R.string.ca_cert_is_added,
+ Toast.LENGTH_LONG).show();
setResult(RESULT_OK);
}
finish();
@@ -266,13 +262,38 @@
return;
}
- nameCredential();
+ if (validCertificateSelected()) {
+ installCertificateOrShowNameDialog();
+ } else {
+ showDialog(INVALID_CERTIFICATE_DIALOG);
+ }
}
- private void nameCredential() {
+ private boolean validCertificateSelected() {
+ switch (mCredentials.getCertUsageSelected()) {
+ case Credentials.CERTIFICATE_USAGE_CA:
+ return mCredentials.hasOnlyVpnAndAppsTrustAnchors();
+ case Credentials.CERTIFICATE_USAGE_USER:
+ return mCredentials.hasUserCertificate()
+ && !mCredentials.hasOnlyVpnAndAppsTrustAnchors();
+ case Credentials.CERTIFICATE_USAGE_WIFI:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void installCertificateOrShowNameDialog() {
if (!mCredentials.hasAnyForSystemInstall()) {
toastErrorAndFinish(R.string.no_cert_to_saved);
+ } else if (mCredentials.hasOnlyVpnAndAppsTrustAnchors()) {
+ // If there's only a CA certificate to install, then it's going to be used
+ // as a trust anchor. Install it and skip importing to Keystore.
+
+ // more work to do, don't finish just yet
+ new InstallVpnAndAppsTrustAnchorsTask().execute();
} else {
+ // Name is required if installing User certificate
showDialog(NAME_CREDENTIAL_DIALOG);
}
}
@@ -302,7 +323,15 @@
removeDialog(PROGRESS_BAR_DIALOG);
if (success) {
removeDialog(PKCS12_PASSWORD_DIALOG);
- nameCredential();
+ if (mCredentials.calledBySettings()) {
+ if (validCertificateSelected()) {
+ installCertificateOrShowNameDialog();
+ } else {
+ showDialog(INVALID_CERTIFICATE_DIALOG);
+ }
+ } else {
+ createRedirectOrSelectUsageDialog();
+ }
} else {
showDialog(PKCS12_PASSWORD_DIALOG);
mView.setText(R.id.credential_password, "");
@@ -310,6 +339,103 @@
}
}
+ private void createRedirectOrSelectUsageDialog() {
+ if (mCredentials.hasOnlyVpnAndAppsTrustAnchors()) {
+ showDialog(REDIRECT_CA_CERTIFICATE_DIALOG);
+ } else {
+ showDialog(SELECT_CERTIFICATE_USAGE_DIALOG);
+ }
+ }
+
+ public CharSequence getCallingAppLabel() {
+ final String callingPkg = mCredentials.getReferrer();
+ if (callingPkg == null) {
+ Log.e(TAG, "Cannot get calling calling AppPackage");
+ return null;
+ }
+
+ final PackageManager pm = getPackageManager();
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = pm.getApplicationInfo(callingPkg, PackageManager.MATCH_DISABLED_COMPONENTS);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to find info for package: " + callingPkg);
+ return null;
+ }
+
+ return appInfo.loadLabel(pm);
+ }
+
+ private Dialog createRedirectCaCertificateDialog() {
+ final String message = getString(
+ R.string.redirect_ca_certificate_with_app_info_message, getCallingAppLabel());
+ Dialog d = new AlertDialog.Builder(this)
+ .setTitle(R.string.redirect_ca_certificate_title)
+ .setMessage(message)
+ .setPositiveButton(R.string.redirect_ca_certificate_close_button,
+ (dialog, id) -> toastErrorAndFinish(R.string.cert_not_saved))
+ .create();
+ d.setOnCancelListener(dialog -> toastErrorAndFinish(R.string.cert_not_saved));
+ return d;
+ }
+
+ private Dialog createSelectCertificateUsageDialog() {
+ ViewGroup view = (ViewGroup) View.inflate(this, R.layout.select_certificate_usage_dialog,
+ null);
+ mView.setView(view);
+
+ RadioGroup radioGroup = view.findViewById(R.id.certificate_usage);
+ radioGroup.setOnCheckedChangeListener((group, checkedId) -> {
+ switch (checkedId) {
+ case R.id.user_certificate:
+ mCredentials.setCertUsageSelectedAndUid(Credentials.CERTIFICATE_USAGE_USER);
+ break;
+ case R.id.wifi_certificate:
+ mCredentials.setCertUsageSelectedAndUid(Credentials.CERTIFICATE_USAGE_WIFI);
+ default:
+ Slog.i(TAG, "Unknown selection for scope");
+ }
+ });
+
+
+ final Context appContext = getApplicationContext();
+ Dialog d = new AlertDialog.Builder(this)
+ .setView(view)
+ .setPositiveButton(android.R.string.ok, (dialog, id) -> {
+ showDialog(NAME_CREDENTIAL_DIALOG);
+ })
+ .setNegativeButton(android.R.string.cancel,
+ (dialog, id) -> toastErrorAndFinish(R.string.cert_not_saved))
+ .create();
+ d.setOnCancelListener(dialog -> toastErrorAndFinish(R.string.cert_not_saved));
+ return d;
+ }
+
+ private Dialog createInvalidCertificateDialog() {
+ Dialog d = new AlertDialog.Builder(this)
+ .setTitle(R.string.invalid_certificate_title)
+ .setMessage(getString(R.string.invalid_certificate_message,
+ getCertificateUsageName()))
+ .setPositiveButton(R.string.invalid_certificate_close_button,
+ (dialog, id) -> toastErrorAndFinish(R.string.cert_not_saved))
+ .create();
+ d.setOnCancelListener(dialog -> finish());
+ return d;
+ }
+
+ String getCertificateUsageName() {
+ switch (mCredentials.getCertUsageSelected()) {
+ case Credentials.CERTIFICATE_USAGE_CA:
+ return getString(R.string.ca_certificate);
+ case Credentials.CERTIFICATE_USAGE_USER:
+ return getString(R.string.user_certificate);
+ case Credentials.CERTIFICATE_USAGE_WIFI:
+ return getString(R.string.wifi_certificate);
+ default:
+ return getString(R.string.certificate);
+ }
+ }
+
private Dialog createPkcs12PasswordDialog() {
View view = View.inflate(this, R.layout.password_dialog, null);
mView.setView(view);
@@ -337,53 +463,23 @@
return d;
}
- private Dialog createNameCredentialDialog() {
- ViewGroup view = (ViewGroup) View.inflate(this, R.layout.name_credential_dialog, null);
+ private Dialog createNameCertificateDialog() {
+ ViewGroup view = (ViewGroup) View.inflate(this, R.layout.name_certificate_dialog, null);
mView.setView(view);
if (mView.getHasEmptyError()) {
mView.showError(R.string.name_empty_error);
mView.setHasEmptyError(false);
}
- mView.setText(R.id.credential_info, mCredentials.getDescription(this).toString());
- final EditText nameInput = view.findViewById(R.id.credential_name);
- if (mCredentials.isInstallAsUidSet()) {
- view.findViewById(R.id.credential_usage_group).setVisibility(View.GONE);
- } else {
- final Spinner usageSpinner = view.findViewById(R.id.credential_usage);
- final View ca_capabilities_warning = view.findViewById(R.id.credential_capabilities_warning);
-
- usageSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- switch ((int) id) {
- case USAGE_TYPE_SYSTEM:
- ca_capabilities_warning.setVisibility(
- mCredentials.includesVpnAndAppsTrustAnchors() ?
- View.VISIBLE : View.GONE);
- mCredentials.setInstallAsUid(KeyStore.UID_SELF);
- break;
- case USAGE_TYPE_WIFI:
- ca_capabilities_warning.setVisibility(View.GONE);
- mCredentials.setInstallAsUid(Process.WIFI_UID);
- break;
- default:
- Log.w(TAG, "Unknown selection for scope: " + id);
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- }
- });
- }
+ final EditText nameInput = view.findViewById(R.id.certificate_name);
nameInput.setText(getDefaultName());
nameInput.selectAll();
final Context appContext = getApplicationContext();
+
Dialog d = new AlertDialog.Builder(this)
.setView(view)
.setTitle(R.string.name_credential_dialog_title)
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
- String name = mView.getText(R.id.credential_name);
+ String name = mView.getText(R.id.certificate_name);
if (TextUtils.isEmpty(name)) {
mView.setHasEmptyError(true);
removeDialog(NAME_CREDENTIAL_DIALOG);
@@ -391,16 +487,7 @@
} else {
removeDialog(NAME_CREDENTIAL_DIALOG);
mCredentials.setName(name);
-
- // install everything to system keystore
- try {
- startActivityForResult(
- mCredentials.createSystemInstallIntent(appContext),
- REQUEST_SYSTEM_INSTALL_CODE);
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "systemInstall(): " + e);
- toastErrorAndFinish(R.string.cert_not_saved);
- }
+ installCertificateToKeystore(appContext);
}
})
.setNegativeButton(android.R.string.cancel,
@@ -410,6 +497,17 @@
return d;
}
+ private void installCertificateToKeystore(Context context) {
+ try {
+ startActivityForResult(
+ mCredentials.createSystemInstallIntent(context),
+ REQUEST_SYSTEM_INSTALL_CODE);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "installCertificateToKeystore(): ", e);
+ toastErrorAndFinish(R.string.cert_not_saved);
+ }
+ }
+
private String getDefaultName() {
String name = mCredentials.getName();
if (TextUtils.isEmpty(name)) {
diff --git a/src/com/android/certinstaller/CertInstallerMain.java b/src/com/android/certinstaller/CertInstallerMain.java
index 7e93884..4cec5fc 100644
--- a/src/com/android/certinstaller/CertInstallerMain.java
+++ b/src/com/android/certinstaller/CertInstallerMain.java
@@ -16,10 +16,14 @@
package com.android.certinstaller;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.UserManager;
import android.preference.PreferenceActivity;
import android.provider.DocumentsContract;
@@ -28,6 +32,8 @@
import android.util.Log;
import android.widget.Toast;
+import libcore.io.IoUtils;
+
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -35,8 +41,6 @@
import java.util.HashMap;
import java.util.Map;
-import libcore.io.IoUtils;
-
/**
* The main class for installing certificates to the system keystore. It reacts
* to the public {@link Credentials#INSTALL_ACTION} intent.
@@ -46,6 +50,7 @@
private static final int REQUEST_INSTALL = 1;
private static final int REQUEST_OPEN_DOCUMENT = 2;
+ private static final int REQUEST_CONFIRM_CREDENTIALS = 3;
private static final String INSTALL_CERT_AS_USER_CLASS = ".InstallCertAsUser";
@@ -72,7 +77,8 @@
setResult(RESULT_CANCELED);
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
- if (userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
+ if (userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)
+ || userManager.isGuestUser()) {
finish();
return;
}
@@ -99,27 +105,56 @@
// If bundle is empty of any actual credentials, ask user to open.
// Otherwise, pass extras to CertInstaller to install those credentials.
// Either way, we use KeyChain.EXTRA_NAME as the default name if available.
- if (bundle == null
- || bundle.isEmpty()
- || (bundle.size() == 1
- && (bundle.containsKey(KeyChain.EXTRA_NAME)
- || bundle.containsKey(Credentials.EXTRA_INSTALL_AS_UID)))) {
- final String[] mimeTypes = MIME_MAPPINGS.keySet().toArray(new String[0]);
- final Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- openIntent.setType("*/*");
- openIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
- openIntent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
- startActivityForResult(openIntent, REQUEST_OPEN_DOCUMENT);
+ if (nullOrEmptyBundle(bundle) || bundleContainsNameOnly(bundle)
+ || bundleContainsInstallAsUidOnly(bundle)
+ || bundleContainsExtraCertificateUsageOnly(bundle)) {
+
+ // Confirm credentials if there's only a CA certificate
+ if (installingCaCertificate(bundle)) {
+ confirmDeviceCredential();
+ } else {
+ startOpenDocumentActivity();
+ }
} else {
- final Intent installIntent = new Intent(this, CertInstaller.class);
- installIntent.putExtras(intent);
- startActivityForResult(installIntent, REQUEST_INSTALL);
+ startInstallActivity(intent);
}
} else if (Intent.ACTION_VIEW.equals(action)) {
startInstallActivity(intent.getType(), intent.getData());
}
}
+ private boolean nullOrEmptyBundle(Bundle bundle) {
+ return bundle == null || bundle.isEmpty();
+ }
+
+ private boolean bundleContainsNameOnly(Bundle bundle) {
+ return bundle.size() == 1 && bundle.containsKey(KeyChain.EXTRA_NAME);
+ }
+
+ private boolean bundleContainsInstallAsUidOnly(Bundle bundle) {
+ return bundle.size() == 1 && bundle.containsKey(Credentials.EXTRA_INSTALL_AS_UID);
+ }
+
+ private boolean bundleContainsExtraCertificateUsageOnly(Bundle bundle) {
+ return bundle.size() == 1 && bundle.containsKey(Credentials.EXTRA_CERTIFICATE_USAGE);
+ }
+
+ private boolean installingCaCertificate(Bundle bundle) {
+ return bundle != null && bundle.size() == 1 && Credentials.CERTIFICATE_USAGE_CA.equals(
+ bundle.getString(Credentials.EXTRA_CERTIFICATE_USAGE));
+ }
+
+ private void confirmDeviceCredential() {
+ KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
+ Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null,
+ null);
+ if (intent == null) { // No screenlock
+ startOpenDocumentActivity();
+ } else {
+ startActivityForResult(intent, REQUEST_CONFIRM_CREDENTIALS);
+ }
+ }
+
// The maximum amount of data to read into memory before aborting.
// Without a limit, a sufficiently-large file will run us out of memory. A
// typical certificate or WiFi config is under 10k, so 10MiB should be more
@@ -146,6 +181,33 @@
return bytes.toByteArray();
}
+ private void startInstallActivity(Intent intent) {
+ final Intent installIntent = new Intent(this, CertInstaller.class);
+ if (intent.getExtras() != null && intent.getExtras().getString(Intent.EXTRA_REFERRER)
+ != null) {
+ Log.v(TAG, String.format(
+ "Removing referrer extra with value %s which was not meant to be included",
+ intent.getBundleExtra(Intent.EXTRA_REFERRER)));
+ intent.removeExtra(Intent.EXTRA_REFERRER);
+ }
+ installIntent.putExtras(intent);
+
+ try {
+ // The referrer is passed as an extra because the launched-from package needs to be
+ // obtained here and not in the CertInstaller.
+ // It is also safe to add the referrer as an extra because the CertInstaller activity
+ // is not exported, which means it cannot be called from other apps.
+ IActivityTaskManager activityTaskManager = ActivityTaskManager.getService();
+ installIntent.putExtra(Intent.EXTRA_REFERRER,
+ activityTaskManager.getLaunchedFromPackage(getActivityToken()));
+ startActivityForResult(installIntent, REQUEST_INSTALL);
+ } catch (RemoteException e) {
+ Log.v(TAG, "Could not talk to activity manager.", e);
+ Toast.makeText(this, R.string.cert_temp_error, Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+
private void startInstallActivity(String mimeType, Uri uri) {
if (mimeType == null) {
mimeType = getContentResolver().getType(uri);
@@ -165,8 +227,10 @@
in = getContentResolver().openInputStream(uri);
final byte[] raw = readWithLimit(in);
- startInstallActivity(target, raw);
+ Intent intent = getIntent();
+ intent.putExtra(target, raw);
+ startInstallActivity(intent);
} catch (IOException e) {
Log.e(TAG, "Failed to read certificate: " + e);
Toast.makeText(this, R.string.cert_read_error, Toast.LENGTH_LONG).show();
@@ -176,13 +240,6 @@
}
}
- private void startInstallActivity(String target, byte[] value) {
- Intent intent = new Intent(this, CertInstaller.class);
- intent.putExtra(target, value);
-
- startActivityForResult(intent, REQUEST_INSTALL);
- }
-
private void startWifiInstallActivity(String mimeType, Uri uri) {
Intent intent = new Intent(this, WiFiInstaller.class);
try (BufferedInputStream in =
@@ -198,19 +255,40 @@
}
}
+ private void startOpenDocumentActivity() {
+ final String[] mimeTypes = MIME_MAPPINGS.keySet().toArray(new String[0]);
+ final Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ openIntent.setType("*/*");
+ openIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
+ openIntent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
+ startActivityForResult(openIntent, REQUEST_OPEN_DOCUMENT);
+ }
+
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == REQUEST_OPEN_DOCUMENT) {
- if (resultCode == RESULT_OK) {
- startInstallActivity(null, data.getData());
- } else {
+ switch (requestCode) {
+ case REQUEST_INSTALL:
+ setResult(resultCode);
finish();
- }
- } else if (requestCode == REQUEST_INSTALL) {
- setResult(resultCode);
- finish();
- } else {
- Log.w(TAG, "unknown request code: " + requestCode);
+ break;
+ case REQUEST_OPEN_DOCUMENT:
+ if (resultCode == RESULT_OK) {
+ startInstallActivity(null, data.getData());
+ } else {
+ finish();
+ }
+ break;
+ case REQUEST_CONFIRM_CREDENTIALS:
+ if (resultCode == RESULT_OK) {
+ startOpenDocumentActivity();
+ return;
+ }
+ // Failed to confirm credentials, do nothing.
+ finish();
+ break;
+ default:
+ Log.w(TAG, "unknown request code: " + requestCode);
+ break;
}
}
}
diff --git a/src/com/android/certinstaller/CredentialHelper.java b/src/com/android/certinstaller/CredentialHelper.java
index 4ec2e7e..a1e9314 100644
--- a/src/com/android/certinstaller/CredentialHelper.java
+++ b/src/com/android/certinstaller/CredentialHelper.java
@@ -16,20 +16,24 @@
package com.android.certinstaller;
+import static android.security.KeyStore.UID_SELF;
+
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.security.Credentials;
-import android.security.KeyChain;
import android.security.IKeyChainService;
+import android.security.KeyChain;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
+
import com.android.org.bouncycastle.asn1.ASN1InputStream;
import com.android.org.bouncycastle.asn1.ASN1Sequence;
import com.android.org.bouncycastle.asn1.DEROctetString;
@@ -39,9 +43,9 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyFactory;
+import java.security.KeyStore;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
-import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
@@ -64,6 +68,7 @@
private static final String DATA_KEY = "data";
private static final String CERTS_KEY = "crts";
private static final String USER_KEY_ALGORITHM = "user_key_algorithm";
+ private static final String SETTINGS_PACKAGE = "com.android.settings";
private static final String TAG = "CredentialHelper";
@@ -71,6 +76,8 @@
private HashMap<String, byte[]> mBundle = new HashMap<String, byte[]>();
private String mName = "";
+ private String mCertUsageSelected = "";
+ private String mReferrer = "";
private int mUid = -1;
private PrivateKey mUserKey;
private X509Certificate mUserCert;
@@ -91,9 +98,21 @@
mName = name;
}
- mUid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1);
+ String certUsageSelected = bundle.getString(Credentials.EXTRA_CERTIFICATE_USAGE);
+ bundle.remove(Credentials.EXTRA_CERTIFICATE_USAGE);
+ if (certUsageSelected != null) {
+ setCertUsageSelectedAndUid(certUsageSelected);
+ } else {
+ mUid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1);
+ }
bundle.remove(Credentials.EXTRA_INSTALL_AS_UID);
+ String referrer = bundle.getString(Intent.EXTRA_REFERRER);
+ bundle.remove(Intent.EXTRA_REFERRER);
+ if (referrer != null) {
+ mReferrer = referrer;
+ }
+
Log.d(TAG, "# extras: " + bundle.size());
for (String key : bundle.keySet()) {
byte[] bytes = bundle.getByteArray(key);
@@ -196,6 +215,14 @@
return mBundle.containsKey(Credentials.EXTRA_PRIVATE_KEY);
}
+ int getUidFromCertificateUsage(String certUsage) {
+ if (Credentials.CERTIFICATE_USAGE_WIFI.equals(certUsage)) {
+ return Process.WIFI_UID;
+ } else {
+ return UID_SELF;
+ }
+ }
+
boolean hasUserCertificate() {
return (mUserCert != null);
}
@@ -262,16 +289,17 @@
return mName;
}
- void setInstallAsUid(int uid) {
- mUid = uid;
+ void setCertUsageSelectedAndUid(String certUsageSelected) {
+ mCertUsageSelected = certUsageSelected;
+ mUid = getUidFromCertificateUsage(certUsageSelected);
}
- boolean isInstallAsUidSet() {
- return mUid != -1;
+ String getCertUsageSelected() {
+ return mCertUsageSelected;
}
- int getInstallAsUid() {
- return mUid;
+ boolean calledBySettings() {
+ return mReferrer != null && mReferrer.equals(SETTINGS_PACKAGE);
}
Intent createSystemInstallIntent(final Context context) {
@@ -281,22 +309,17 @@
intent.setComponent(ComponentName.unflattenFromString(
context.getString(R.string.config_system_install_component)));
intent.putExtra(Credentials.EXTRA_INSTALL_AS_UID, mUid);
+ intent.putExtra(Credentials.EXTRA_USER_KEY_ALIAS, mName);
try {
if (mUserKey != null) {
- intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_NAME,
- Credentials.USER_PRIVATE_KEY + mName);
intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_DATA,
mUserKey.getEncoded());
}
if (mUserCert != null) {
- intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_NAME,
- Credentials.USER_CERTIFICATE + mName);
intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_DATA,
Credentials.convertToPem(mUserCert));
}
if (!mCaCerts.isEmpty()) {
- intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_NAME,
- Credentials.CA_CERTIFICATE + mName);
X509Certificate[] caCerts
= mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_DATA,
@@ -340,12 +363,8 @@
}
private void maybeApproveCaCert(Context context, String alias) {
- // Some CTS verifier test asks testers to reset auto approved CA cert by removing
- // lock sreen, but it's not possible if we don't have Android lock screen. (e.g.
- // Android is running in the container). In this case, disable auto cert approval.
final KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
- if (keyguardManager.isDeviceSecure(UserHandle.myUserId())
- && context.getResources().getBoolean(R.bool.config_auto_cert_approval)) {
+ if (keyguardManager.isDeviceSecure(UserHandle.myUserId())) {
// Since the cert is installed by real user, the cert is approved by the user
final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
dpm.approveCaCert(alias, UserHandle.myUserId(), true);
@@ -432,14 +451,14 @@
}
/**
- * Returns whether this credential contains CA certificates to be used as trust anchors
+ * Returns true if this credential contains _only_ CA certificates to be used as trust anchors
* for VPN and apps.
*/
- public boolean includesVpnAndAppsTrustAnchors() {
+ public boolean hasOnlyVpnAndAppsTrustAnchors() {
if (!hasCaCerts()) {
return false;
}
- if (getInstallAsUid() != android.security.KeyStore.UID_SELF) {
+ if (mUid != UID_SELF) {
// VPN and Apps trust anchors can only be installed under UID_SELF
return false;
}
@@ -452,4 +471,8 @@
return true;
}
}
+
+ public String getReferrer() {
+ return mReferrer;
+ }
}
diff --git a/src/com/android/certinstaller/WiFiInstaller.java b/src/com/android/certinstaller/WiFiInstaller.java
index 90b8eb7..41827f6 100644
--- a/src/com/android/certinstaller/WiFiInstaller.java
+++ b/src/com/android/certinstaller/WiFiInstaller.java
@@ -93,6 +93,13 @@
public void run() {
boolean success = true;
try {
+ mWifiManager.removePasspointConfiguration(
+ mPasspointConfig.getHomeSp().getFqdn());
+ } catch (IllegalArgumentException e) {
+ // Do nothing. This is expected if a profile with this FQDN does not
+ // exist.
+ }
+ try {
mWifiManager.addOrUpdatePasspointConfiguration(mPasspointConfig);
} catch (RuntimeException rte) {
Log.w(TAG, "Caught exception while installing wifi config: " +