6 |
--------------------------------------------------------------------------------
/android/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-21
15 |
--------------------------------------------------------------------------------
/android/res/xml/userpreferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/android/android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/android/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 34C3 Wifi Setup
4 |
5 | Please logon
6 | username@realm
7 | password
8 | Create connection entry
9 |
10 | https://twitter.com/c3noc
11 |
12 | This device supports the 5GHz band
13 | (Unfortunately, autodetecting 5GHz support is broken...)
14 | Note: if you have no screen lock, Android refuses to store the security settings!
15 |
16 |
17 | About
18 | Exit
19 |
20 | This small helper app creates a Wifi connection entry for the CCC/EMF/SHA2017 networks. It configures the correct SSL CA and subject name match, making it a little more secure than a hand-created entry.
21 | 34C3 Logo
22 |
23 |
--------------------------------------------------------------------------------
/android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/htdocs/config.inc.php:
--------------------------------------------------------------------------------
1 | .
22 | */
23 | class CertificateStore extends PDO {
24 |
25 | private $dsn;
26 | private $user;
27 | private $pass;
28 | private $assigned_serial;
29 |
30 | public function __construct() {
31 | $in_transaction = false;
32 |
33 | $this->user = SQL_USER;
34 | $this->pass = SQL_PASS;
35 | $this->dsn = SQL_DSN;
36 | parent::__construct($this->dsn, $this->user, $this->pass);
37 | }
38 |
39 | public function assign_serial($username, $device_serial, $device_id, $device_description) {
40 | if (!$this->inTransaction()) {
41 | $this->beginTransaction();
42 | }
43 | $stm = $this->prepare("INSERT INTO certificates ".
44 | "(id, username, device_serial, device_id, device_description, timestamp) VALUES ".
45 | "(NULL, :username, :device_serial, :device_id, :device_description, NOW())");
46 | $stm->execute(array(":username" => $username,
47 | ":device_serial" => $device_serial,
48 | ":device_id" => $device_id,
49 | ":device_description" => $device_description));
50 | $this->assigned_serial = $this->lastInsertId();
51 | return $this->assigned_serial;
52 | }
53 |
54 | public function store($certificate, $expiry_date) {
55 | $stm = $this->prepare("UPDATE certificates SET certificate=:certificate, expires=:expiry_date WHERE id=:serial");
56 | $stm->execute(array(":certificate" => $certificate,
57 | ":expiry_date" => gmstrftime("%Y-%m-%d %H:%M:%S", $expiry_date),
58 | ":serial" => $this->assigned_serial));
59 |
60 |
61 | if ($this->inTransaction()) {
62 | $this->commit();
63 | }
64 | }
65 |
66 | public function __destruct() {
67 | if ($this->inTransaction()) {
68 | $this->rollBack();
69 | }
70 | }
71 |
72 | }
73 | ?>
74 |
--------------------------------------------------------------------------------
/android/res/layout/logon.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
15 |
16 |
22 |
23 |
31 |
32 |
38 |
39 |
44 |
52 |
61 |
62 |
69 |
70 |
80 |
81 |
82 |
83 |
93 |
94 |
95 |
101 |
102 |
106 |
107 |
108 |
109 |
116 |
124 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/htdocs/include/ldap.inc.php:
--------------------------------------------------------------------------------
1 | .
22 | */
23 | class LdapAuthenticatedUser {
24 | public static $authenticated;
25 | public static $expiry_date;
26 | public static $error;
27 |
28 | private function ldap_escape ($str = '') {
29 |
30 | $metaChars = array ("\x00", "\\", "(", ")", "*");
31 | $quotedMetaChars = array ();
32 | foreach ($metaChars as $key => $value) {
33 | $quotedMetaChars[$key] = '\\'. dechex (ord ($value));
34 | }
35 | $str = str_replace (
36 | $metaChars, $quotedMetaChars, $str
37 | );
38 | return ($str);
39 | }
40 |
41 | function __construct($username, $password) {
42 | $this->authenticated = false;
43 | $this->expirydate = 0;
44 | $this->error = null;
45 |
46 | /* Connect to LDAP */
47 | $ldap = ldap_connect(LDAP_HOST, LDAP_PORT);
48 | ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
49 |
50 | /* Retry bind up to 10 times. */
51 | for ($i = 0; $i < 10 && !($rv = @ldap_bind($ldap)); $i++) sleep(1);
52 | if (!$rv) {
53 | $this->error ="Anonymous bind failed";
54 | return;
55 | }
56 |
57 | /* Look for the user */
58 | $res = ldap_search($ldap, LDAP_BASE, "(&(objectclass=posixAccount)(uid=".$this->ldap_escape($_POST['username'])."))");
59 | $entries = ldap_get_entries($ldap, $res);
60 | if ($entries['count'] != 1) {
61 | $this->error ="Incorrect entry count";
62 | return;
63 | }
64 |
65 | /* Close the old LDAP connection */
66 | ldap_close($ldap);
67 | unset($ldap);
68 |
69 | /* Reconnect and bind as supplied user */
70 | $ldap = ldap_connect(LDAP_HOST, LDAP_PORT);
71 | ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
72 | for ($i = 0; $i < 10 && !($rv = @ldap_bind($ldap, $entries[0]['dn'], $_POST['password'])); $i++) sleep(1);
73 | if (!$rv) {
74 | $this->error ="Failed to bind as user.";
75 | return;
76 | }
77 |
78 | /* Check if the user has the right attributes */
79 | $res = ldap_search($ldap, LDAP_BASE,
80 | "(&(objectclass=posixAccount)(uid=".$this->ldap_escape($_POST['username'])."))");
81 | $entries = ldap_get_entries($ldap, $res);
82 | if ($entries['count'] != 1) {
83 | $this->error ="Incorrect entry count";
84 | return;
85 | }
86 | if (!in_array("schacuserstatus", $entries[0]) ||
87 | !in_array("urn:mace:terena.org:schac:userStatus:nikhef.nl:affiliation:active",
88 | $entries[0]['schacuserstatus'])) {
89 | $this->error = "User is not active.";
90 | return;
91 | }
92 | if (!in_array("edupersonaffiliation", $entries[0]) ||
93 | !in_array("member", $entries[0]['edupersonaffiliation'])) {
94 | $this->error = "User is not a member.";
95 | return;
96 | }
97 | if (!in_array("schacexpirydate", $entries[0]) ||
98 | !($ts = strptime($entries[0]['schacexpirydate'][0], "%Y%m%d%H%M%SZ")) ||
99 | !($expiry_date = gmmktime($ts['tm_hour'], $ts['tm_min'], $ts['tm_sec'],
100 | $ts['tm_mon'] + 1, $ts['tm_mday'], $ts['tm_year'] + 1900))) {
101 | $this->error = "User has invalid expiry date";
102 | return;
103 | }
104 | $this->authenticated = true;
105 | $this->expiry_date = $expiry_date;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/htdocs/index.php:
--------------------------------------------------------------------------------
1 | .
22 | *
23 | * TODO:
24 | * - Rate-limiting authentications
25 | */
26 |
27 | require("config.inc.php");
28 | require("include/ldap.inc.php");
29 | require("include/certificatestore.inc.php");
30 |
31 | function exit_error($string) {
32 | global $file;
33 | fprintf($file, "%s\n", $string);
34 | fclose($file);
35 | header("200 OK");
36 | header("Content-Type: application/json");
37 | print json_encode(array(
38 | 'status' => 'error',
39 | 'error' => $string));
40 | die($string);
41 | }
42 |
43 | $file = fopen(LOG_FILE, "a");
44 |
45 | /* Configuration checks */
46 | $stat = stat(PRIVATE_KEY);
47 | if ($stat['mode'] & 066) {
48 | fprintf($file, "Incorrect private key file permissions. Too permissive.\n");
49 | exit_error("Sanity check failed.");
50 | }
51 | if (strpos(PRIVATE_KEY, "/var/www") !== false) {
52 | fprintf($file, "Incorrect private key path. Probably public.\n");
53 | exit_error("Sanity check failed.");
54 | }
55 |
56 |
57 | /* Check incoming variables */
58 | if (!array_key_exists('username', $_POST) ||
59 | !array_key_exists('password', $_POST) ||
60 | !array_key_exists('device_serial', $_POST) ||
61 | !array_key_exists('device_id', $_POST) ||
62 | !array_key_exists('device_description', $_POST) ||
63 | !array_key_exists('csr', $_POST)) {
64 | fprintf($file, "Missing fields.\n");
65 | exit_error("Sanity check failed.");
66 | }
67 |
68 | /* Log username */
69 | if ($_POST['username']) {
70 | fprintf($file, "Got username: %s\n", $_POST['username']);
71 | }
72 |
73 | /* We can't check the CSR, so we sign it first with validity of 1 day, in memory only.. */
74 | $tmpcrt = openssl_csr_sign($_POST['csr'],
75 | "file://".CACERT,
76 | "file://".PRIVATE_KEY,
77 | 1 /* 1 day */,
78 | array("x509_extensions" => "usr_cert"));
79 | $parsed = openssl_x509_parse($tmpcrt);
80 | unset($tmpcrt);
81 | if (array_key_exists('subjectAltName', $parsed['extensions'])) {
82 | fprintf($file, "subjectAltName exists.\n");
83 | exit_error("Sanity check failed.");
84 | }
85 | if (array_key_exists('basicConstraints', $parsed['extensions']) &&
86 | $parsed['extensions']['basicConstraints'] != "CA:FALSE") {
87 | fprintf($file, "basicConstraints invalid: %s\n", $parsed['extensions']['basicConstraints']);
88 | exit_error("Sanity check failed.");
89 | }
90 | $expected_cn = sprintf(CN_MATCH, $_POST['username']);
91 | if ($parsed['subject']['CN'] != $expected_cn) {
92 | fprintf($file, "Subject mismatch: %s != %s\n", $parsed['subject']['CN'], $expected_cn);
93 | fprintf($file, "X509: %s\n", print_r($parsed, true));
94 | exit_error("Sanity check failed.");
95 | }
96 |
97 | /* Authenticate the user */
98 | $user = new LdapAuthenticatedUser($_POST['username'], $_POST['password']);
99 | if (!$user->authenticated) {
100 | fprintf($file, "Authentication failure for user %s from %s: %s\n",
101 | $_POST['username'],
102 | $_SERVER['REMOTE_ADDR'],
103 | $user->error);
104 | exit_error("Authentication failed.");
105 | }
106 |
107 |
108 | /* Make sure the certificate expires when the user expires, with a maximum of 3 years. */
109 | $days = min(365 * 3, floor(($user->expiry_date - time()) / 86400));
110 |
111 | /* Do Serial administration in database. */
112 | $cs = new CertificateStore();
113 | $serial = $cs->assign_serial($_POST['username'],
114 | $_POST['device_serial'],
115 | $_POST['device_id'],
116 | $_POST['device_description']);
117 | if (!$serial) {
118 | exit_error("Certificate store couldn't assign serial.");
119 | }
120 |
121 | /* Sign the certificate */
122 | $cert = openssl_csr_sign($_POST['csr'],
123 | "file://".CACERT,
124 | "file://".PRIVATE_KEY,
125 | $days,
126 | array('private_key_bits' => 2048,
127 | 'private_key_type' => OPENSSL_KEYTYPE_RSA,
128 | 'x509_extensions' => 'usr_cert',
129 | 'digest_alg' => 'sha256'),
130 | $serial);
131 | if (!$cert) exit_error("Certificate signing failed.");
132 |
133 | openssl_x509_export($cert, $certificate);
134 | $rv = $cs->store($certificate, $user->expiry_date);
135 |
136 | fprintf($file, "Signed Certificate:\n%s\n", $certificate);
137 |
138 | /* Serialize a response in JSON */
139 | header("Content-Type: application/json");
140 | print json_encode(array(
141 | 'status' => 'ok',
142 | 'certificate' => $certificate,
143 | 'ca_name' => CA_NAME,
144 | 'ca' => CA,
145 | 'subject_match' => SUBJECT_MATCH,
146 | 'ssid' => SSID,
147 | 'realm' => REALM));
148 |
149 | fclose($file);
150 |
--------------------------------------------------------------------------------
/android/res/raw/apache_license_2.0.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/android/src/tf/nox/wifisetup/WifiSetup.java:
--------------------------------------------------------------------------------
1 | /* Copyright 2013-2017 Wilco Baan Hofman
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | package tf.nox.wifisetup;
17 |
18 | import java.io.BufferedReader;
19 | import java.io.ByteArrayInputStream;
20 | import java.io.ByteArrayOutputStream;
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 | import java.io.InputStreamReader;
24 |
25 | import android.provider.Settings.Secure;
26 | import android.security.KeyChain;
27 |
28 | import java.lang.reflect.Field;
29 | import java.lang.reflect.Method;
30 | import java.net.UnknownHostException;
31 |
32 | import java.security.KeyStore;
33 | import java.security.KeyStoreException;
34 | import java.security.NoSuchAlgorithmException;
35 | import java.security.NoSuchProviderException;
36 | import java.security.Security;
37 | import java.security.cert.Certificate;
38 | import java.security.cert.CertificateException;
39 | import java.security.cert.CertificateFactory;
40 | import java.security.cert.X509Certificate;
41 | import java.util.ArrayList;
42 | import java.util.HashMap;
43 | import java.util.List;
44 |
45 | import org.apache.http.HttpResponse;
46 | import org.apache.http.NameValuePair;
47 | import org.apache.http.client.ClientProtocolException;
48 | import org.apache.http.client.HttpClient;
49 | import org.apache.http.client.entity.UrlEncodedFormEntity;
50 | import org.apache.http.client.methods.HttpPost;
51 | import org.apache.http.impl.client.DefaultHttpClient;
52 | import org.apache.http.message.BasicNameValuePair;
53 | /* import android.util.Base64; */
54 | import org.json.JSONException;
55 | import org.json.JSONObject;
56 | import tf.nox.wifisetup.R;
57 |
58 |
59 | import android.annotation.TargetApi;
60 | import android.app.Activity;
61 | import android.app.AlertDialog;
62 | import android.app.AlertDialog.Builder;
63 | import android.content.DialogInterface;
64 | import android.content.Intent;
65 | import android.content.pm.PackageInfo;
66 | import android.net.wifi.WifiConfiguration;
67 | import android.net.wifi.WifiEnterpriseConfig.Phase2;
68 | import android.net.wifi.WifiManager;
69 | import android.os.Build;
70 | import android.os.Bundle;
71 | import android.os.Handler;
72 | import android.view.Menu;
73 | import android.view.MenuInflater;
74 | import android.view.MenuItem;
75 | import android.view.View;
76 | import android.widget.Button;
77 | import android.widget.EditText;
78 | import android.widget.Toast;
79 | import android.widget.ImageView;
80 | import android.widget.TextView;
81 | import android.widget.CheckBox;
82 | import android.widget.ViewFlipper;
83 |
84 | // API level 18 and up
85 | import android.net.wifi.WifiEnterpriseConfig;
86 | import android.net.wifi.WifiEnterpriseConfig.Eap;
87 |
88 | public class WifiSetup extends Activity {
89 | // FIXME This should be a configuration setting somehow
90 | private static final String INT_EAP = "eap";
91 | private static final String INT_PHASE2 = "phase2";
92 | private static final String INT_ENGINE = "engine";
93 | private static final String INT_ENGINE_ID = "engine_id";
94 | private static final String INT_CLIENT_CERT = "client_cert";
95 | private static final String INT_CA_CERT = "ca_cert";
96 | private static final String INT_PRIVATE_KEY = "private_key";
97 | private static final String INT_PRIVATE_KEY_ID = "key_id";
98 | private static final String INT_SUBJECT_MATCH = "subject_match";
99 | private static final String INT_ALTSUBJECT_MATCH = "altsubject_match";
100 | private static final String INT_PASSWORD = "password";
101 | private static final String INT_IDENTITY = "identity";
102 | private static final String INT_ANONYMOUS_IDENTITY = "anonymous_identity";
103 | private static final String INT_ENTERPRISEFIELD_NAME = "android.net.wifi.WifiConfiguration$EnterpriseField";
104 |
105 | // Because android.security.Credentials cannot be resolved...
106 | private static final String INT_KEYSTORE_URI = "keystore://";
107 | private static final String INT_CA_PREFIX = INT_KEYSTORE_URI + "CACERT_";
108 | private static final String INT_PRIVATE_KEY_PREFIX = INT_KEYSTORE_URI + "USRPKEY_";
109 | private static final String INT_PRIVATE_KEY_ID_PREFIX = "USRPKEY_";
110 | private static final String INT_CLIENT_CERT_PREFIX = INT_KEYSTORE_URI + "USRCERT_";
111 |
112 | protected static final int SHOW_PREFERENCES = 0;
113 | private Handler mHandler = new Handler();
114 | private EditText username;
115 | private EditText password;
116 | private CheckBox check5g;
117 | private Button btn;
118 | private String subject_match;
119 | private String altsubject_match;
120 |
121 | private String realm;
122 | private String ssid;
123 | private boolean busy = false;
124 | private Toast toast = null;
125 | private int logoclicks = 0;
126 | private String s_username;
127 | private String s_password;
128 | private ViewFlipper flipper;
129 |
130 | private void toastText(final String text) {
131 | if (toast != null)
132 | toast.cancel();
133 | toast = Toast.makeText(getBaseContext(), text, Toast.LENGTH_SHORT);
134 | toast.show();
135 | }
136 |
137 | /*
138 | * Unfortunately, this returns false on a LOT of devices :(
139 | *
140 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
141 | private boolean get5G() {
142 | WifiManager wifiManager = (WifiManager) this.getSystemService(WIFI_SERVICE);
143 | return wifiManager.is5GHzBandSupported();
144 | }
145 | */
146 |
147 | // Called when the activity is first created.
148 | @Override
149 | public void onCreate(Bundle savedInstanceState) {
150 | super.onCreate(savedInstanceState);
151 | setContentView(R.layout.logon);
152 |
153 | flipper = (ViewFlipper) findViewById(R.id.viewflipper);
154 | username = (EditText) findViewById(R.id.username);
155 | password = (EditText) findViewById(R.id.password);
156 |
157 | check5g = (CheckBox) findViewById(R.id.check5g);
158 | check5g.setChecked(true);
159 | /*
160 | TextView label5g = (TextView) findViewById(R.id.label5g);
161 | if (android.os.Build.VERSION.SDK_INT >= 21) {
162 | check5g.setChecked(get5G());
163 | label5g.setText("(autodetected value)");
164 | } else {
165 | check5g.setChecked(true);
166 | label5g.setText("(Android 5.0 is needed to autodetect this)");
167 | }
168 | */
169 |
170 | ImageView img = (ImageView) findViewById(R.id.logo);
171 | img.setOnClickListener(new View.OnClickListener() {
172 | @Override
173 | public void onClick(View view) {
174 | logoclicks++;
175 | if (logoclicks == 4) {
176 | toastText("You're cute!");
177 | }
178 | if (logoclicks == 6) {
179 | toastText("Stop that!");
180 | }
181 | if (logoclicks == 7) {
182 | View logindata = findViewById(R.id.logindata);
183 | logindata.setVisibility(View.VISIBLE);
184 | }
185 | }
186 | });
187 |
188 | btn = (Button) findViewById(R.id.button1);
189 | if (btn == null)
190 | throw new RuntimeException("button1 not found. Odd");
191 | btn.setOnClickListener(new Button.OnClickListener() {
192 | public void onClick(View _v) {
193 | if (busy) {
194 | return;
195 | }
196 | busy = true;
197 | _v.setClickable(false);
198 |
199 | // Most of this stuff runs in the background
200 | Thread t = new Thread() {
201 | @Override
202 | public void run() {
203 | try {
204 | if (android.os.Build.VERSION.SDK_INT >= 18) {
205 | saveWifiConfig();
206 | resultStatus(true, "You should now have a wifi connection entry with correct security settings and certificate verification.\n\nMake sure to actually use it!");
207 | // Clear the password field in the UI thread
208 | /*
209 | mHandler.post(new Runnable() {
210 | @Override
211 | public void run() {
212 | password.setText("");
213 | };
214 | });
215 | */
216 | } else {
217 | throw new RuntimeException("What version is this?! API Mismatch");
218 | }
219 | } catch (RuntimeException e) {
220 | resultStatus(false, "Something went wrong: " + e.getMessage());
221 | e.printStackTrace();
222 | } catch (Exception e) {
223 | e.printStackTrace();
224 | }
225 | busy = false;
226 | mHandler.post(new Runnable() {
227 | @Override
228 | public void run() {
229 | btn.setClickable(true);
230 | };
231 | });
232 | }
233 | };
234 | t.start();
235 |
236 | }
237 | });
238 |
239 | }
240 |
241 | private void saveWifiConfig() {
242 | WifiManager wifiManager = (WifiManager) this.getApplicationContext().getSystemService(WIFI_SERVICE);
243 | wifiManager.setWifiEnabled(true);
244 |
245 | WifiConfiguration currentConfig = new WifiConfiguration();
246 |
247 | List configs = null;
248 | for (int i = 0; i < 10 && configs == null; i++) {
249 | configs = wifiManager.getConfiguredNetworks();
250 | try {
251 | Thread.sleep(1);
252 | }
253 | catch(InterruptedException e) {
254 | continue;
255 | }
256 | }
257 |
258 | if (check5g.isChecked()) {
259 | ssid = "34C3";
260 | } else {
261 | ssid = "34C3-legacy";
262 | }
263 | subject_match = "/CN=radius.c3noc.net";
264 | altsubject_match = "DNS:radius.c3noc.net";
265 |
266 | s_username = username.getText().toString();
267 | s_password = password.getText().toString();
268 | realm = "";
269 | if (s_username.equals("") && s_password.equals("")) {
270 | s_username = "34c3";
271 | s_password = "34c3";
272 | } else {
273 | if (s_username.indexOf("@") >= 0) {
274 | int idx = s_username.indexOf("@");
275 | realm = s_username.substring(idx);
276 | }
277 | }
278 |
279 |
280 | // Use the existing eduroam profile if it exists.
281 | boolean ssidExists = false;
282 | if (configs != null) {
283 | for (WifiConfiguration config : configs) {
284 | if (config.SSID.equals(surroundWithQuotes(ssid))) {
285 | currentConfig = config;
286 | ssidExists = true;
287 | break;
288 | }
289 | }
290 | }
291 |
292 | currentConfig.SSID = surroundWithQuotes(ssid);
293 | currentConfig.hiddenSSID = false;
294 | currentConfig.priority = 40;
295 | currentConfig.status = WifiConfiguration.Status.DISABLED;
296 |
297 | currentConfig.allowedKeyManagement.clear();
298 | currentConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
299 |
300 | // GroupCiphers (Allow most ciphers)
301 | currentConfig.allowedGroupCiphers.clear();
302 | currentConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
303 | currentConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
304 | currentConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
305 |
306 |
307 | // PairwiseCiphers (CCMP = WPA2 only)
308 | currentConfig.allowedPairwiseCiphers.clear();
309 | currentConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
310 |
311 | // Authentication Algorithms (OPEN)
312 | currentConfig.allowedAuthAlgorithms.clear();
313 | currentConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
314 |
315 | // Protocols (RSN/WPA2 only)
316 | currentConfig.allowedProtocols.clear();
317 | currentConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
318 |
319 | // Enterprise Settings
320 | HashMap configMap = new HashMap();
321 | configMap.put(INT_SUBJECT_MATCH, subject_match);
322 | configMap.put(INT_ALTSUBJECT_MATCH, altsubject_match);
323 | configMap.put(INT_ANONYMOUS_IDENTITY, "anonymous" + realm);
324 | configMap.put(INT_IDENTITY, s_username);
325 | configMap.put(INT_PASSWORD, s_password);
326 | configMap.put(INT_EAP, "TTLS");
327 | configMap.put(INT_PHASE2, "auth=PAP");
328 | configMap.put(INT_ENGINE, "0");
329 | // configMap.put(INT_CA_CERT, INT_CA_PREFIX + ca_name);
330 |
331 | applyAndroid43EnterpriseSettings(currentConfig, configMap);
332 |
333 | if (!ssidExists) {
334 | int networkId = wifiManager.addNetwork(currentConfig);
335 | wifiManager.enableNetwork(networkId, false);
336 | } else {
337 | wifiManager.updateNetwork(currentConfig);
338 | wifiManager.enableNetwork(currentConfig.networkId, false);
339 | }
340 | wifiManager.saveConfiguration();
341 |
342 | }
343 |
344 |
345 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
346 | private void applyAndroid43EnterpriseSettings(WifiConfiguration currentConfig, HashMap configMap) {
347 | try {
348 | CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
349 | InputStream in = getResources().openRawResource(R.raw.cacert);
350 | // InputStream in = new ByteArrayInputStream(Base64.decode(ca.replaceAll("-----(BEGIN|END) CERTIFICATE-----", ""), 0));
351 | X509Certificate caCert = (X509Certificate) certFactory.generateCertificate(in);
352 |
353 | WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
354 | enterpriseConfig.setPhase2Method(Phase2.PAP);
355 | enterpriseConfig.setAnonymousIdentity(configMap.get(INT_ANONYMOUS_IDENTITY));
356 | enterpriseConfig.setEapMethod(Eap.TTLS);
357 |
358 | enterpriseConfig.setCaCertificate(caCert);
359 | enterpriseConfig.setIdentity(s_username);
360 | enterpriseConfig.setPassword(s_password);
361 | enterpriseConfig.setSubjectMatch(configMap.get(INT_SUBJECT_MATCH));
362 | currentConfig.enterpriseConfig = enterpriseConfig;
363 |
364 | } catch(Exception e) {
365 | e.printStackTrace();
366 | }
367 | }
368 |
369 | @Override
370 | public boolean onCreateOptionsMenu(Menu menu) {
371 | MenuInflater inflater = getMenuInflater();
372 | inflater.inflate(R.menu.options_menu, menu);
373 | return true;
374 | }
375 |
376 | @Override
377 | public boolean onOptionsItemSelected(MenuItem item){
378 | Builder builder = new AlertDialog.Builder(this);
379 | switch (item.getItemId()) {
380 | case R.id.about:
381 | PackageInfo pi = null;
382 | try{
383 | pi = getPackageManager().getPackageInfo(getClass().getPackage().getName(), 0);
384 | } catch(Exception e){
385 | e.printStackTrace();
386 | }
387 | builder.setTitle(getString(R.string.ABOUT_TITLE));
388 | builder.setMessage(getString(R.string.ABOUT_CONTENT)+
389 | "\n\n"+pi.packageName+"\n"+
390 | "V"+pi.versionName+
391 | "C"+pi.versionCode+"-equi");
392 | builder.setPositiveButton(getString(android.R.string.ok), null);
393 | builder.show();
394 |
395 | return true;
396 | case R.id.exit:
397 | System.exit(0);
398 | }
399 | return false;
400 | }
401 |
402 |
403 |
404 | /* Update the status in the main thread */
405 | protected void resultStatus(final boolean success, final String text) {
406 | mHandler.post(new Runnable() {
407 | @Override
408 | public void run() {
409 | TextView res_title = (TextView) findViewById(R.id.resulttitle);
410 | TextView res_text = (TextView) findViewById(R.id.result);
411 |
412 | System.out.println(text);
413 | res_text.setText(text);
414 | if (success)
415 | res_title.setText("Success!");
416 | else
417 | res_title.setText("ERROR!");
418 |
419 | if (toast != null)
420 | toast.cancel();
421 | /* toast = Toast.makeText(getBaseContext(), text, Toast.LENGTH_LONG);
422 | toast.show(); */
423 | flipper.showNext();
424 | };
425 | });
426 | }
427 |
428 | static String removeQuotes(String str) {
429 | int len = str.length();
430 | if ((len > 1) && (str.charAt(0) == '"') && (str.charAt(len - 1) == '"')) {
431 | return str.substring(1, len - 1);
432 | }
433 | return str;
434 | }
435 |
436 | static String surroundWithQuotes(String string) {
437 | return "\"" + string + "\"";
438 | }
439 | }
440 |
--------------------------------------------------------------------------------