summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml7
-rw-r--r--res/layout/wifi_detail_dialog.xml33
-rw-r--r--res/layout/wifi_main_dialog.xml41
-rw-r--r--res/values/strings.xml13
-rw-r--r--src/com/android/certinstaller/CertInstallerMain.java97
-rw-r--r--src/com/android/certinstaller/WiFiInstaller.java228
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();
+ }
+}