diff options
author | Brian Carlstrom <bdc@google.com> | 2011-06-26 16:05:21 -0700 |
---|---|---|
committer | Brian Carlstrom <bdc@google.com> | 2011-06-27 15:16:42 -0700 |
commit | f6f4e303abb8b7883713b0af8484c7767e6af84d (patch) | |
tree | 4cb9e65739f540fa66c6cd3302f84652411cc859 | |
parent | 3a5079823add92343efe4c0ba2ea3cb5d32138ec (diff) | |
download | packages_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.mk | 1 | ||||
-rw-r--r-- | res/layout/trusted_credential.xml | 41 | ||||
-rw-r--r-- | res/layout/trusted_credential_details.xml | 34 | ||||
-rw-r--r-- | res/layout/trusted_credentials.xml | 80 | ||||
-rw-r--r-- | res/values/strings.xml | 22 | ||||
-rw-r--r-- | res/xml/security_settings_misc.xml | 5 | ||||
-rw-r--r-- | src/com/android/settings/TrustedCredentials.java | 390 |
7 files changed, 573 insertions, 0 deletions
@@ -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); + } + } +} |