diff options
-rw-r--r-- | AndroidManifest.xml | 7 | ||||
-rw-r--r-- | res/layout/wifi_detail_dialog.xml | 33 | ||||
-rw-r--r-- | res/layout/wifi_main_dialog.xml | 41 | ||||
-rw-r--r-- | res/values/strings.xml | 13 | ||||
-rw-r--r-- | src/com/android/certinstaller/CertInstallerMain.java | 97 | ||||
-rw-r--r-- | src/com/android/certinstaller/WiFiInstaller.java | 228 |
6 files changed, 384 insertions, 35 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2ac35bd..5185e51 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5,6 +5,7 @@ <permission android:name="com.android.certinstaller.INSTALL_AS_USER" android:protectionLevel="signature" /> + <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <application android:label="@string/app_name" android:allowBackup="false"> @@ -24,6 +25,7 @@ <data android:mimeType="application/x-pkcs12" /> <data android:mimeType="application/application/x-pem-file" /> <data android:mimeType="application/pkix-cert" /> + <data android:mimeType="application/x-wifi-config" /> </intent-filter> </activity> @@ -41,5 +43,10 @@ android:configChanges="orientation|keyboardHidden" android:exported="false"> </activity> + <activity android:name=".WiFiInstaller" + android:theme="@style/Transparent" + android:configChanges="orientation|keyboardHidden" + android:exported="false"> + </activity> </application> </manifest> diff --git a/res/layout/wifi_detail_dialog.xml b/res/layout/wifi_detail_dialog.xml new file mode 100644 index 0000000..af152ca --- /dev/null +++ b/res/layout/wifi_detail_dialog.xml @@ -0,0 +1,33 @@ +<?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" + android:scrollbars="horizontal" > + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="15dip"> + + <TextView android:id="@+id/wifi_detail_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> +</ScrollView> diff --git a/res/layout/wifi_main_dialog.xml b/res/layout/wifi_main_dialog.xml new file mode 100644 index 0000000..8f680b8 --- /dev/null +++ b/res/layout/wifi_main_dialog.xml @@ -0,0 +1,41 @@ +<?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:gravity="center" + android:padding="15dip"> + + <TextView android:id="@+id/wifi_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textStyle="bold" /> + + <Button android:id="@+id/wifi_detail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="12dp" + android:text="@string/wifi_detail_label" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + </LinearLayout> +</ScrollView> diff --git a/res/values/strings.xml b/res/values/strings.xml index e0bd560..6b5f389 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -85,4 +85,17 @@ <item>VPN and apps</item> <item>Wi-Fi</item> </string-array> + + <string name="wifi_title">Wi-Fi Profile</string> + <string name="wifi_detail_title">Details for %s</string> + <string name="wifi_detail_label">Details</string> + <string name="wifi_install_label">Install</string> + <string name="wifi_cancel_label">Cancel</string> + <string name="wifi_dismiss_label">Dismiss</string> + <string name="wifi_no_config">None</string> + <string name="wifi_config_text">Name: %1$s\nFQDN: %2$s\nRoaming Consortiums: %3$s\nRealm: %4$s\nAuth method: EAP-%5$s\n</string> + <string name="wifi_ttls_config_text">User name: %s\n</string> + <string name="wifi_tls_config_text">Client certificate:\n%1$s\nKey: %2$s\n</string> + <string name="wifi_sim_config_text">SIM: %s\n</string> + <string name="wifi_trust_config_text">Trust certificate:\n%s\n</string> </resources> diff --git a/src/com/android/certinstaller/CertInstallerMain.java b/src/com/android/certinstaller/CertInstallerMain.java index e184ae6..f95bb84 100644 --- a/src/com/android/certinstaller/CertInstallerMain.java +++ b/src/com/android/certinstaller/CertInstallerMain.java @@ -28,11 +28,14 @@ import android.security.KeyChain; import android.util.Log; import android.widget.Toast; -import libcore.io.IoUtils; -import libcore.io.Streams; - +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import libcore.io.IoUtils; +import libcore.io.Streams; /** * The main class for installing certificates to the system keystore. It reacts @@ -46,19 +49,28 @@ public class CertInstallerMain extends PreferenceActivity { private static final String INSTALL_CERT_AS_USER_CLASS = ".InstallCertAsUser"; - private static final String[] ACCEPT_MIME_TYPES = { - "application/x-pkcs12", - "application/x-x509-ca-cert", - "application/x-x509-user-cert", - "application/x-x509-server-cert", - "application/x-pem-file", - "application/pkix-cert" - }; + public static final String WIFI_CONFIG = "wifi-config"; + public static final String WIFI_CONFIG_DATA = "wifi-config-data"; + public static final String WIFI_CONFIG_FILE = "wifi-config-file"; + + private static Map<String,String> MIME_MAPPINGS = new HashMap<>(); + + static { + MIME_MAPPINGS.put("application/x-x509-ca-cert", KeyChain.EXTRA_CERTIFICATE); + MIME_MAPPINGS.put("application/x-x509-user-cert", KeyChain.EXTRA_CERTIFICATE); + MIME_MAPPINGS.put("application/x-x509-server-cert", KeyChain.EXTRA_CERTIFICATE); + MIME_MAPPINGS.put("application/x-pem-file", KeyChain.EXTRA_CERTIFICATE); + MIME_MAPPINGS.put("application/pkix-cert", KeyChain.EXTRA_CERTIFICATE); + MIME_MAPPINGS.put("application/x-pkcs12", KeyChain.EXTRA_PKCS12); + MIME_MAPPINGS.put("application/x-wifi-config", WIFI_CONFIG); + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Log.d("WFII", "Created!"); + setResult(RESULT_CANCELED); UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); @@ -96,7 +108,7 @@ public class CertInstallerMain extends PreferenceActivity { || bundle.containsKey(Credentials.EXTRA_INSTALL_AS_UID)))) { final Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); openIntent.setType("*/*"); - openIntent.putExtra(Intent.EXTRA_MIME_TYPES, ACCEPT_MIME_TYPES); + openIntent.putExtra(Intent.EXTRA_MIME_TYPES, MIME_MAPPINGS.keySet().toArray()); openIntent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true); startActivityForResult(openIntent, REQUEST_OPEN_DOCUMENT); } else { @@ -114,38 +126,53 @@ public class CertInstallerMain extends PreferenceActivity { mimeType = getContentResolver().getType(uri); } - InputStream in = null; - try { - in = getContentResolver().openInputStream(uri); - - final byte[] raw = Streams.readFully(in); - startInstallActivity(mimeType, raw); + String target = MIME_MAPPINGS.get(mimeType); + if (target == null) { + throw new IllegalArgumentException("Unknown MIME type: " + mimeType); + } - } catch (IOException e) { - Log.e(TAG, "Failed to read certificate: " + e); - Toast.makeText(this, R.string.cert_read_error, Toast.LENGTH_LONG).show(); - } finally { - IoUtils.closeQuietly(in); + if (WIFI_CONFIG.equals(target)) { + startWifiInstallActivity(mimeType, uri); + } + else { + InputStream in = null; + try { + in = getContentResolver().openInputStream(uri); + + final byte[] raw = Streams.readFully(in); + startInstallActivity(target, raw); + + } catch (IOException e) { + Log.e(TAG, "Failed to read certificate: " + e); + Toast.makeText(this, R.string.cert_read_error, Toast.LENGTH_LONG).show(); + } finally { + IoUtils.closeQuietly(in); + } } } - private void startInstallActivity(String mimeType, byte[] value) { + private void startInstallActivity(String target, byte[] value) { Intent intent = new Intent(this, CertInstaller.class); - if ("application/x-pkcs12".equals(mimeType)) { - intent.putExtra(KeyChain.EXTRA_PKCS12, value); - } else if ("application/x-x509-ca-cert".equals(mimeType) - || "application/x-x509-user-cert".equals(mimeType) - || "application/x-x509-server-cert".equals(mimeType) - || "application/x-pem-file".equals(mimeType) - || "application/pkix-cert".equals(mimeType)) { - intent.putExtra(KeyChain.EXTRA_CERTIFICATE, value); - } else { - throw new IllegalArgumentException("Unknown MIME type: " + mimeType); - } + 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 = + new BufferedInputStream(getContentResolver().openInputStream(uri))) { + byte[] data = Streams.readFully(in); + intent.putExtra(WIFI_CONFIG_FILE, uri.toString()); + intent.putExtra(WIFI_CONFIG_DATA, data); + intent.putExtra(WIFI_CONFIG, mimeType); + startActivityForResult(intent, REQUEST_INSTALL); + } catch (IOException e) { + Log.e(TAG, "Failed to read wifi config: " + e); + Toast.makeText(this, R.string.cert_read_error, Toast.LENGTH_LONG).show(); + } + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_OPEN_DOCUMENT) { diff --git a/src/com/android/certinstaller/WiFiInstaller.java b/src/com/android/certinstaller/WiFiInstaller.java new file mode 100644 index 0000000..727134f --- /dev/null +++ b/src/com/android/certinstaller/WiFiInstaller.java @@ -0,0 +1,228 @@ +package com.android.certinstaller; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiEnterpriseConfig; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import java.security.PrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.util.Collection; + +public class WiFiInstaller extends Activity { + + @Override + protected void onCreate(Bundle savedStates) { + super.onCreate(savedStates); + + Bundle bundle = getIntent().getExtras(); + String uriString = bundle.getString(CertInstallerMain.WIFI_CONFIG_FILE); + String mimeType = bundle.getString(CertInstallerMain.WIFI_CONFIG); + byte[] data = bundle.getByteArray(CertInstallerMain.WIFI_CONFIG_DATA); + + Log.d("WFII", "WiFi data for " + CertInstallerMain.WIFI_CONFIG + ": " + + mimeType + " is " + (data != null ? data.length : "-")); + + WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); + WifiConfiguration wifiConfiguration = wifiManager.buildWifiConfig(uriString, mimeType, data); + createMainDialog(wifiManager, wifiConfiguration); + } + + private static String roamingConsortiumsToString(Collection<Long> ois) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (long oi : ois) { + if (first) { + first = false; + } else { + sb.append(", "); + } + if (Long.numberOfLeadingZeros(oi) > 40) { + sb.append(String.format("%06x", oi)); + } else { + sb.append(String.format("%010x", oi)); + } + } + return sb.toString(); + } + + private static String mapEAPMethod(WifiEnterpriseConfig config) { + switch (config.getEapMethod()) { + case WifiEnterpriseConfig.Eap.TTLS: + return "TTLS+" + mapInnerMethod(config.getPhase2Method()); + case WifiEnterpriseConfig.Eap.TLS: + return "TLS"; + case WifiEnterpriseConfig.Eap.SIM: + return "SIM"; + case WifiEnterpriseConfig.Eap.AKA: + return "AKA"; + case WifiEnterpriseConfig.Eap.AKA_PRIME: + return "AKA'"; + default: + return String.format("Unsupported method %d", config.getEapMethod()); + } + } + + private static String mapInnerMethod(int id) { + switch (id) { + case WifiEnterpriseConfig.Phase2.NONE: + return "none"; + case WifiEnterpriseConfig.Phase2.PAP: + return "PAP"; + case WifiEnterpriseConfig.Phase2.MSCHAP: + return "MS-CHAP"; + case WifiEnterpriseConfig.Phase2.MSCHAPV2: + return "MS-CHAPv2"; + case WifiEnterpriseConfig.Phase2.GTC: + return "GTC"; + default: + return String.format("Unsupported inner method %d", id); + } + } + + private void createMainDialog(final WifiManager wifiManager, final WifiConfiguration config) { + Resources res = getResources(); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + View layout = getLayoutInflater().inflate(R.layout.wifi_main_dialog, null); + builder.setView(layout); + + TextView text = (TextView) layout.findViewById(R.id.wifi_info); + text.setText(String.format("WiFi configuration from %s (%s)", + config.providerFriendlyName, config.FQDN)); + + Button detailButton = (Button) layout.findViewById(R.id.wifi_detail); + + detailButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + createDetailDialog(config); + } + }); + + builder.setTitle(res.getString(R.string.wifi_title)); + + builder.setPositiveButton(R.string.wifi_install_label, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Log.d("WFDL", "OK"); + wifiManager.addNetwork(config); + wifiManager.saveConfiguration(); + dialog.dismiss(); + finish(); + } + }); + + builder.setNegativeButton(R.string.wifi_cancel_label, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Log.d("WFDL", "Cancel"); + dialog.dismiss(); + finish(); + } + }); + + builder.create().show(); + } + + private void createDetailDialog(WifiConfiguration config) { + Resources res = getResources(); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + View layout = getLayoutInflater().inflate(R.layout.wifi_detail_dialog, null); + layout.setHorizontalScrollBarEnabled(true); + builder.setView(layout); + + TextView text = (TextView) layout.findViewById(R.id.wifi_detail_text); + text.setText(getDetailedInfo(res, config)); + + builder.setTitle(String.format(res.getString(R.string.wifi_detail_title), + config.providerFriendlyName)); + + builder.setPositiveButton(R.string.wifi_dismiss_label, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Log.d("WFDL", "detail dismiss"); + dialog.dismiss(); + } + }); + + builder.create().show(); + } + + private static String getDetailedInfo(Resources res, WifiConfiguration wifiConfiguration) { + if (wifiConfiguration == null) { + return res.getString(R.string.wifi_no_config); + } + + StringBuilder details = new StringBuilder(); + WifiEnterpriseConfig enterpriseConfig = wifiConfiguration.enterpriseConfig; + details.append(String.format(res.getString(R.string.wifi_config_text), + wifiConfiguration.providerFriendlyName, + wifiConfiguration.FQDN, + roamingConsortiumsToString(wifiConfiguration.roamingConsortiumIds), + enterpriseConfig.getRealm(), + mapEAPMethod(enterpriseConfig))); + + switch (enterpriseConfig.getEapMethod()) { + case WifiEnterpriseConfig.Eap.TTLS: + details.append(String.format(res.getString(R.string.wifi_ttls_config_text), enterpriseConfig.getIdentity())); + details.append("Password: ").append(enterpriseConfig.getPassword()).append('\n'); // !!! + if (enterpriseConfig.getCaCertificate() != null) { + details.append(String.format(res.getString(R.string.wifi_trust_config_text), enterpriseConfig.getCaCertificate())); + } + break; + case WifiEnterpriseConfig.Eap.TLS: + PrivateKey key = enterpriseConfig.getClientPrivateKey(); + String keyInfo; + if (key instanceof RSAPrivateKey) { + RSAPrivateKey rsaKey = (RSAPrivateKey) key; + int bits = rsaKey.getModulus().bitLength(); + keyInfo = "RSA " + (((bits + 7) / 8)*8) + " bits"; + } + else { + keyInfo = key.getAlgorithm(); + } + details.append(String.format(res.getString(R.string.wifi_tls_config_text), enterpriseConfig.getClientCertificate(), keyInfo)); + if (enterpriseConfig.getCaCertificate() != null) { + details.append(String.format(res.getString(R.string.wifi_trust_config_text), enterpriseConfig.getCaCertificate())); + } + break; + case WifiEnterpriseConfig.Eap.SIM: + case WifiEnterpriseConfig.Eap.AKA: + case WifiEnterpriseConfig.Eap.AKA_PRIME: + details.append(String.format(res.getString(R.string.wifi_sim_config_text), enterpriseConfig.getPlmn())); + break; + } + return details.toString(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onSaveInstanceState(Bundle outStates) { + super.onSaveInstanceState(outStates); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + finish(); + } +} |