summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Carlstrom <bdc@google.com>2011-06-26 16:05:21 -0700
committerBrian Carlstrom <bdc@google.com>2011-06-27 15:16:42 -0700
commitf6f4e303abb8b7883713b0af8484c7767e6af84d (patch)
tree4cb9e65739f540fa66c6cd3302f84652411cc859
parent3a5079823add92343efe4c0ba2ea3cb5d32138ec (diff)
downloadpackages_apps_Settings-f6f4e303abb8b7883713b0af8484c7767e6af84d.zip
packages_apps_Settings-f6f4e303abb8b7883713b0af8484c7767e6af84d.tar.gz
packages_apps_Settings-f6f4e303abb8b7883713b0af8484c7767e6af84d.tar.bz2
CA certificate settings fragment
Change-Id: I8d4b595d6cc8d20be9bea655c7674c86a9183818
-rw-r--r--Android.mk1
-rw-r--r--res/layout/trusted_credential.xml41
-rw-r--r--res/layout/trusted_credential_details.xml34
-rw-r--r--res/layout/trusted_credentials.xml80
-rw-r--r--res/values/strings.xml22
-rw-r--r--res/xml/security_settings_misc.xml5
-rw-r--r--src/com/android/settings/TrustedCredentials.java390
7 files changed, 573 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
index b171dbd..c08be7f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,6 +1,7 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_JAVA_LIBRARIES := bouncycastle
LOCAL_STATIC_JAVA_LIBRARIES := guava
LOCAL_MODULE_TAGS := optional
diff --git a/res/layout/trusted_credential.xml b/res/layout/trusted_credential.xml
new file mode 100644
index 0000000..9955a79
--- /dev/null
+++ b/res/layout/trusted_credential.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:paddingRight="?android:attr/scrollbarSize"
+ android:background="?android:attr/selectableItemBackground"
+ android:padding="15dip"
+ >
+ <TextView
+ android:id="@+id/trusted_credential_subject"
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ />
+ <!-- checkbox is invisible and not gone so that the height is consistent between tabs -->
+ <CheckBox
+ android:id="@+id/trusted_credential_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="invisible"
+ android:clickable="false"
+ android:focusable="false"
+ android:layout_weight="0"
+ />
+</LinearLayout>
diff --git a/res/layout/trusted_credential_details.xml b/res/layout/trusted_credential_details.xml
new file mode 100644
index 0000000..c18d933
--- /dev/null
+++ b/res/layout/trusted_credential_details.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <FrameLayout
+ android:id="@+id/cert_details"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ />
+ <Button
+ android:id="@+id/cert_remove_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="right"
+ android:layout_margin="6dip"
+ />
+</LinearLayout>
diff --git a/res/layout/trusted_credentials.xml b/res/layout/trusted_credentials.xml
new file mode 100644
index 0000000..06ce44b
--- /dev/null
+++ b/res/layout/trusted_credentials.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<TabHost
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="5dp"
+ >
+ <TabWidget
+ android:id="@android:id/tabs"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ />
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="5dp"
+ >
+ <FrameLayout
+ android:id="@+id/system_tab"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <ProgressBar
+ android:id="@+id/system_progress"
+ style="?android:attr/progressBarStyleLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ />
+ <ListView
+ android:id="@+id/system_list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:visibility="gone"
+ >
+ </ListView>
+ </FrameLayout>
+ <FrameLayout
+ android:id="@+id/user_tab"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <ProgressBar
+ android:id="@+id/user_progress"
+ style="?android:attr/progressBarStyleLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ />
+ <ListView
+ android:id="@+id/user_list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:visibility="gone"
+ >
+ </ListView>
+ </FrameLayout>
+ </FrameLayout>
+ </LinearLayout>
+</TabHost>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1193bdc..9bde6cd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3091,6 +3091,10 @@ found in the list of installed applications.</string>
<string name="credentials_reset">Clear credentials</string>
<!-- Summary of preference to reset credential storage [CHAR LIMIT=NONE] -->
<string name="credentials_reset_summary">Remove all certificates</string>
+ <!-- Title of preference to display trusted credentials (aka CA certificates) [CHAR LIMIT=30] -->
+ <string name="trusted_credentials">Trusted credentials</string>
+ <!-- Summary of preference to display trusted credentials (aka CA certificates) [CHAR LIMIT=NONE] -->
+ <string name="trusted_credentials_summary">Display trusted CA certificates</string>
<!-- Title of dialog to enable credential storage [CHAR LIMIT=30] -->
<string name="credentials_unlock">Enter password</string>
@@ -3533,4 +3537,22 @@ found in the list of installed applications.</string>
<string name="vpn_menu_edit">Edit network</string>
<!-- Menu item to delete a VPN network. [CHAR LIMIT=40] -->
<string name="vpn_menu_delete">Delete network</string>
+
+ <!-- Tab label for built-in system CA certificates. -->
+ <string name="trusted_credentials_system_tab">System</string>
+ <!-- Tab label for user added CA certificates. -->
+ <string name="trusted_credentials_user_tab">User</string>
+ <!-- Button label for disabling a system CA certificate. -->
+ <string name="trusted_credentials_disable_label">Disable</string>
+ <!-- Button label for enabling a system CA certificate. -->
+ <string name="trusted_credentials_enable_label">Enable</string>
+ <!-- Button label for removing a user CA certificate. -->
+ <string name="trusted_credentials_remove_label">Remove</string>
+ <!-- Alert dialog confirmation when enabling a system CA certificate. -->
+ <string name="trusted_credentials_enable_confirmation">Enable the system CA certificate?</string>
+ <!-- Alert dialog confirmation when disabling a system CA certificate. -->
+ <string name="trusted_credentials_disable_confirmation">Disable the system CA certificate?</string>
+ <!-- Alert dialog confirmation when removing a user CA certificate. -->
+ <string name="trusted_credentials_remove_confirmation">Permanently remove the user CA certificate?</string>
+
</resources>
diff --git a/res/xml/security_settings_misc.xml b/res/xml/security_settings_misc.xml
index e996f70..3a88d0b 100644
--- a/res/xml/security_settings_misc.xml
+++ b/res/xml/security_settings_misc.xml
@@ -56,6 +56,11 @@
<PreferenceCategory android:title="@string/credentials_title"
android:persistent="false">
+ <Preference android:title="@string/trusted_credentials"
+ android:summary="@string/trusted_credentials_summary"
+ android:persistent="false"
+ android:fragment="com.android.settings.TrustedCredentials"/>
+
<Preference android:title="@string/credentials_install"
android:summary="@string/credentials_install_summary"
android:persistent="false">
diff --git a/src/com/android/settings/TrustedCredentials.java b/src/com/android/settings/TrustedCredentials.java
new file mode 100644
index 0000000..987d2dc
--- /dev/null
+++ b/src/com/android/settings/TrustedCredentials.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2011 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.app.AlertDialog;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.content.DialogInterface;
+import android.net.http.SslCertificate;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
+import android.security.KeyChain.KeyChainConnection;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.FrameLayout;
+import android.widget.ListView;
+import android.widget.TabHost;
+import android.widget.TextView;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
+
+public class TrustedCredentials extends Fragment {
+
+ private static final String TAG = "TrustedCredentials";
+
+ private enum Tab {
+ SYSTEM("system",
+ R.string.trusted_credentials_system_tab,
+ R.id.system_tab,
+ R.id.system_progress,
+ R.id.system_list,
+ true),
+ USER("user",
+ R.string.trusted_credentials_user_tab,
+ R.id.user_tab,
+ R.id.user_progress,
+ R.id.user_list,
+ false);
+
+ private final String mTag;
+ private final int mLabel;
+ private final int mView;
+ private final int mProgress;
+ private final int mList;
+ private final boolean mCheckbox;
+ private Tab(String tag, int label, int view, int progress, int list, boolean checkbox) {
+ mTag = tag;
+ mLabel = label;
+ mView = view;
+ mProgress = progress;
+ mList = list;
+ mCheckbox = checkbox;
+ }
+ private Set<String> getAliases(TrustedCertificateStore store) {
+ switch (this) {
+ case SYSTEM:
+ return store.allSystemAliases();
+ case USER:
+ return store.userAliases();
+ }
+ throw new AssertionError();
+ }
+ private boolean deleted(TrustedCertificateStore store, String alias) {
+ switch (this) {
+ case SYSTEM:
+ return !store.containsAlias(alias);
+ case USER:
+ return false;
+ }
+ throw new AssertionError();
+ }
+ private int getButtonLabel(CertHolder certHolder) {
+ switch (this) {
+ case SYSTEM:
+ if (certHolder.mDeleted) {
+ return R.string.trusted_credentials_enable_label;
+ }
+ return R.string.trusted_credentials_disable_label;
+ case USER:
+ return R.string.trusted_credentials_remove_label;
+ }
+ throw new AssertionError();
+ }
+ private int getButtonConfirmation(CertHolder certHolder) {
+ switch (this) {
+ case SYSTEM:
+ if (certHolder.mDeleted) {
+ return R.string.trusted_credentials_enable_confirmation;
+ }
+ return R.string.trusted_credentials_disable_confirmation;
+ case USER:
+ return R.string.trusted_credentials_remove_confirmation;
+ }
+ throw new AssertionError();
+ }
+ private void postOperationUpdate(boolean ok, CertHolder certHolder) {
+ if (ok) {
+ if (certHolder.mTab.mCheckbox) {
+ certHolder.mDeleted = !certHolder.mDeleted;
+ } else {
+ certHolder.mAdapter.mCertHolders.remove(certHolder);
+ }
+ certHolder.mAdapter.notifyDataSetChanged();
+ } else {
+ // bail, reload to reset to known state
+ certHolder.mAdapter.load();
+ }
+ }
+ }
+
+ // be careful not to use this on the UI thread since it is does file operations
+ private final TrustedCertificateStore mStore = new TrustedCertificateStore();
+
+ private TabHost mTabHost;
+
+ @Override public View onCreateView(
+ LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+ mTabHost = (TabHost) inflater.inflate(R.layout.trusted_credentials, parent, false);
+ mTabHost.setup();
+ addTab(Tab.SYSTEM);
+ // TODO add Install button on Tab.USER to go to CertInstaller like KeyChainActivity
+ addTab(Tab.USER);
+ return mTabHost;
+ }
+
+ private void addTab(Tab tab) {
+ TabHost.TabSpec systemSpec = mTabHost.newTabSpec(tab.mTag)
+ .setIndicator(getActivity().getString(tab.mLabel))
+ .setContent(tab.mView);
+ mTabHost.addTab(systemSpec);
+
+ ListView lv = (ListView) mTabHost.findViewById(tab.mList);
+ final TrustedCertificateAdapter adapter = new TrustedCertificateAdapter(tab);
+ lv.setAdapter(adapter);
+ lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
+ showCertDialog(adapter.getItem(pos));
+ }
+ });
+ }
+
+ private class TrustedCertificateAdapter extends BaseAdapter {
+ private final List<CertHolder> mCertHolders = new ArrayList<CertHolder>();
+ private final Tab mTab;
+ private TrustedCertificateAdapter(Tab tab) {
+ mTab = tab;
+ load();
+ }
+ private void load() {
+ new AliasLoader().execute();
+ }
+ @Override public int getCount() {
+ return mCertHolders.size();
+ }
+ @Override public CertHolder getItem(int position) {
+ return mCertHolders.get(position);
+ }
+ @Override public long getItemId(int position) {
+ return position;
+ }
+ @Override public View getView(int position, View view, ViewGroup parent) {
+ ViewHolder holder;
+ if (view == null) {
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ view = inflater.inflate(R.layout.trusted_credential, parent, false);
+ holder = new ViewHolder();
+ holder.mSubjectView = (TextView)view.findViewById(R.id.trusted_credential_subject);
+ holder.mCheckBox = (CheckBox) view.findViewById(R.id.trusted_credential_status);
+ view.setTag(holder);
+ } else {
+ holder = (ViewHolder) view.getTag();
+ }
+ CertHolder certHolder = mCertHolders.get(position);
+ holder.mSubjectView.setText(certHolder.mSubject);
+ if (mTab.mCheckbox) {
+ holder.mCheckBox.setChecked(!certHolder.mDeleted);
+ holder.mCheckBox.setVisibility(View.VISIBLE);
+ }
+ return view;
+ };
+
+ private class AliasLoader extends AsyncTask<Void, Void, List<CertHolder>> {
+ @Override protected void onPreExecute() {
+ View content = mTabHost.getTabContentView();
+ content.findViewById(mTab.mProgress).setVisibility(View.VISIBLE);
+ content.findViewById(mTab.mList).setVisibility(View.GONE);
+ }
+ @Override protected List<CertHolder> doInBackground(Void... params) {
+ Set<String> aliases = mTab.getAliases(mStore);
+ List<CertHolder> certHolders = new ArrayList<CertHolder>(aliases.size());
+ for (String alias : aliases) {
+ X509Certificate cert = (X509Certificate) mStore.getCertificate(alias, true);
+ certHolders.add(new CertHolder(mStore,
+ TrustedCertificateAdapter.this,
+ mTab,
+ alias,
+ cert));
+ }
+ Collections.sort(certHolders);
+ return certHolders;
+ }
+ @Override protected void onPostExecute(List<CertHolder> certHolders) {
+ mCertHolders.clear();
+ mCertHolders.addAll(certHolders);
+ notifyDataSetChanged();
+ View content = mTabHost.getTabContentView();
+ content.findViewById(mTab.mProgress).setVisibility(View.GONE);
+ content.findViewById(mTab.mList).setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ private static class CertHolder implements Comparable<CertHolder> {
+ private final TrustedCertificateStore mStore;
+ private final TrustedCertificateAdapter mAdapter;
+ private final Tab mTab;
+ private final String mAlias;
+ private final X509Certificate mX509Cert;
+
+ private final SslCertificate mSslCert;
+ private final String mSubject;
+ private boolean mDeleted;
+
+ private CertHolder(TrustedCertificateStore store,
+ TrustedCertificateAdapter adapter,
+ Tab tab,
+ String alias,
+ X509Certificate x509Cert) {
+ mStore = store;
+ mAdapter = adapter;
+ mTab = tab;
+ mAlias = alias;
+ mX509Cert = x509Cert;
+
+ mSslCert = new SslCertificate(x509Cert);
+
+ String cn = mSslCert.getIssuedTo().getCName();
+ String o = mSslCert.getIssuedTo().getOName();
+ String ou = mSslCert.getIssuedTo().getUName();
+ StringBuilder sb = new StringBuilder();
+ if (!cn.isEmpty()) {
+ sb.append("CN=" + cn);
+ }
+ if (!o.isEmpty()) {
+ if (sb.length() != 0) {
+ sb.append(", ");
+ }
+ sb.append("O=" + o);
+ }
+ if (!ou.isEmpty()) {
+ if (sb.length() != 0) {
+ sb.append(", ");
+ }
+ sb.append("OU=" + ou);
+ }
+ if (sb.length() != 0) {
+ mSubject = sb.toString();
+ } else {
+ mSubject = mSslCert.getIssuedTo().getDName();
+ }
+
+ mDeleted = mTab.deleted(mStore, mAlias);
+ }
+ @Override public int compareTo(CertHolder o) {
+ return this.mSubject.compareTo(o.mSubject);
+ }
+ @Override public boolean equals(Object o) {
+ if (!(o instanceof CertHolder)) {
+ return false;
+ }
+ CertHolder other = (CertHolder) o;
+ return mAlias.equals(other.mAlias);
+ }
+ @Override public int hashCode() {
+ return mAlias.hashCode();
+ }
+ }
+
+ private static class ViewHolder {
+ private TextView mSubjectView;
+ private CheckBox mCheckBox;
+ }
+
+ private void showCertDialog(final CertHolder certHolder) {
+ View view = View.inflate(getActivity(), R.layout.trusted_credential_details, null);
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(com.android.internal.R.string.ssl_certificate);
+ builder.setView(view);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ }
+ });
+ final Dialog certDialog = builder.create();
+
+ FrameLayout details = (FrameLayout) view.findViewById(R.id.cert_details);
+ details.addView(certHolder.mSslCert.inflateCertificateView(getActivity()));
+
+ Button removeButton = (Button) view.findViewById(R.id.cert_remove_button);
+ removeButton.setText(certHolder.mTab.getButtonLabel(certHolder));
+ removeButton.setOnClickListener(new View.OnClickListener() {
+ @Override public void onClick(View v) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage(certHolder.mTab.getButtonConfirmation(certHolder));
+ builder.setPositiveButton(
+ android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override public void onClick(DialogInterface dialog, int id) {
+ new AliasOperation(certHolder).execute();
+ dialog.dismiss();
+ certDialog.dismiss();
+ }
+ });
+ builder.setNegativeButton(
+ android.R.string.no, new DialogInterface.OnClickListener() {
+ @Override public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+ });
+
+ certDialog.show();
+ }
+
+ private class AliasOperation extends AsyncTask<Void, Void, Boolean> {
+ private final CertHolder mCertHolder;
+ private AliasOperation(CertHolder certHolder) {
+ mCertHolder = certHolder;
+ }
+ @Override protected Boolean doInBackground(Void... params) {
+ try {
+ KeyChainConnection keyChainConnection = KeyChain.bind(getActivity());
+ IKeyChainService service = keyChainConnection.getService();
+ try {
+ if (mCertHolder.mDeleted) {
+ byte[] bytes = mCertHolder.mX509Cert.getEncoded();
+ service.installCaCertificate(bytes);
+ return true;
+ } else {
+ return service.deleteCaCertificate(mCertHolder.mAlias);
+ }
+ } finally {
+ keyChainConnection.close();
+ }
+ } catch (CertificateEncodingException e) {
+ return false;
+ } catch (IllegalStateException e) {
+ // used by installCaCertificate to report errors
+ return false;
+ } catch (RemoteException e) {
+ return false;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+ @Override protected void onPostExecute(Boolean ok) {
+ mCertHolder.mTab.postOperationUpdate(ok, mCertHolder);
+ }
+ }
+}