1 /*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.nfc;
17 
18 
19 import android.app.ActivityManager;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.net.wifi.WifiConfiguration;
23 import android.nfc.NdefMessage;
24 import android.nfc.NdefRecord;
25 import android.nfc.tech.Ndef;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 
29 import java.nio.BufferUnderflowException;
30 import java.nio.ByteBuffer;
31 import java.util.BitSet;
32 
33 public final class NfcWifiProtectedSetup {
34 
35     public static final String NFC_TOKEN_MIME_TYPE = "application/vnd.wfa.wsc";
36 
37     public static final String EXTRA_WIFI_CONFIG = "com.android.nfc.WIFI_CONFIG_EXTRA";
38 
39     /*
40      * ID into configuration record for SSID and Network Key in hex.
41      * Obtained from WFA Wifi Simple Configuration Technical Specification v2.0.2.1.
42      */
43     private static final short CREDENTIAL_FIELD_ID = 0x100E;
44     private static final short SSID_FIELD_ID = 0x1045;
45     private static final short NETWORK_KEY_FIELD_ID = 0x1027;
46     private static final short AUTH_TYPE_FIELD_ID = 0x1003;
47 
48     private static final short AUTH_TYPE_EXPECTED_SIZE = 2;
49 
50     private static final short AUTH_TYPE_OPEN = 0x0001;
51     private static final short AUTH_TYPE_WPA_PSK = 0x0002;
52     private static final short AUTH_TYPE_WPA_EAP =  0x0008;
53     private static final short AUTH_TYPE_WPA2_EAP = 0x0010;
54     private static final short AUTH_TYPE_WPA2_PSK = 0x0020;
55     private static final short AUTH_TYPE_WPA_AND_WPA2_PSK = 0x0022;
56 
57     private static final int MAX_NETWORK_KEY_SIZE_BYTES = 64;
58 
NfcWifiProtectedSetup()59     private NfcWifiProtectedSetup() {}
60 
tryNfcWifiSetup(Ndef ndef, Context context)61     public static boolean tryNfcWifiSetup(Ndef ndef, Context context) {
62 
63         if (ndef == null || context == null) {
64             return false;
65         }
66 
67         NdefMessage cachedNdefMessage = ndef.getCachedNdefMessage();
68         if (cachedNdefMessage == null) {
69             return false;
70         }
71 
72         final WifiConfiguration wifiConfiguration;
73         try {
74             wifiConfiguration = parse(cachedNdefMessage);
75         } catch (BufferUnderflowException e) {
76             // malformed payload
77             return false;
78         }
79 
80         UserManager um = context.getSystemService(UserManager.class);
81         if (wifiConfiguration != null && !um.hasUserRestrictionForUser(
82                 UserManager.DISALLOW_CONFIG_WIFI,
83                 // hasUserRestriction does not support UserHandle.CURRENT.
84                 UserHandle.of(ActivityManager.getCurrentUser()))
85                 && !um.hasUserRestrictionForUser(
86                         UserManager.DISALLOW_ADD_WIFI_CONFIG,
87                         // hasUserRestriction does not support UserHandle.CURRENT.
88                         UserHandle.of(ActivityManager.getCurrentUser())
89         )) {
90             Intent configureNetworkIntent = new Intent()
91                     .putExtra(EXTRA_WIFI_CONFIG, wifiConfiguration)
92                     .setClass(context, ConfirmConnectToWifiNetworkActivity.class)
93                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
94 
95             context.startActivityAsUser(configureNetworkIntent, UserHandle.CURRENT);
96             return true;
97         }
98 
99         return false;
100     }
101 
parse(NdefMessage message)102     private static WifiConfiguration parse(NdefMessage message) {
103         NdefRecord[] records = message.getRecords();
104 
105         for (NdefRecord record : records) {
106             if (new String(record.getType()).equals(NFC_TOKEN_MIME_TYPE)) {
107                 ByteBuffer payload = ByteBuffer.wrap(record.getPayload());
108                 while (payload.hasRemaining()) {
109                     short fieldId = payload.getShort();
110                     int fieldSize = payload.getShort() & 0xFFFF;
111                     if (fieldId == CREDENTIAL_FIELD_ID) {
112                         return parseCredential(payload, fieldSize);
113                     }
114                     payload.position(payload.position() + fieldSize);
115                 }
116             }
117         }
118         return null;
119     }
120 
parseCredential(ByteBuffer payload, int size)121     private static WifiConfiguration parseCredential(ByteBuffer payload, int size) {
122         int startPosition = payload.position();
123         WifiConfiguration result = new WifiConfiguration();
124         while (payload.position() < startPosition + size) {
125             short fieldId = payload.getShort();
126             int fieldSize = payload.getShort() & 0xFFFF;
127 
128             // Quick check
129             if (payload.position() + fieldSize > startPosition + size) {
130                 return null;
131             }
132 
133             switch (fieldId) {
134                 case SSID_FIELD_ID:
135                     byte[] ssid = new byte[fieldSize];
136                     payload.get(ssid);
137                     result.SSID = "\"" + new String(ssid) + "\"";
138                     break;
139                 case NETWORK_KEY_FIELD_ID:
140                     if (fieldSize > MAX_NETWORK_KEY_SIZE_BYTES) {
141                         return null;
142                     }
143                     byte[] networkKey = new byte[fieldSize];
144                     payload.get(networkKey);
145                     if (fieldSize > 0) {
146                         result.preSharedKey = getPskValidFormat(new String(networkKey));
147                     }
148                     break;
149                 case AUTH_TYPE_FIELD_ID:
150                     if (fieldSize != AUTH_TYPE_EXPECTED_SIZE) {
151                         // corrupt data
152                         return null;
153                     }
154 
155                     short authType = payload.getShort();
156                     populateAllowedKeyManagement(result.allowedKeyManagement, authType);
157                     break;
158                 default:
159                     // unknown / unparsed tag
160                     payload.position(payload.position() + fieldSize);
161                     break;
162             }
163         }
164 
165         if (result.SSID != null) {
166             if (result.getAuthType() == WifiConfiguration.KeyMgmt.NONE) {
167                 if (result.preSharedKey == null) {
168                     return result;
169                 }
170             } else {
171                 if (result.preSharedKey != null) {
172                     return result;
173                 }
174             }
175         }
176 
177         return null;
178     }
179 
populateAllowedKeyManagement(BitSet allowedKeyManagement, short authType)180     private static void populateAllowedKeyManagement(BitSet allowedKeyManagement, short authType) {
181         if (authType == AUTH_TYPE_WPA_PSK || authType == AUTH_TYPE_WPA2_PSK
182                 || authType == AUTH_TYPE_WPA_AND_WPA2_PSK) {
183             allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
184         } else if (authType == AUTH_TYPE_WPA_EAP || authType == AUTH_TYPE_WPA2_EAP) {
185             allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
186         } else if (authType == AUTH_TYPE_OPEN) {
187             allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
188         }
189     }
190 
getPskValidFormat(String data)191     private static String getPskValidFormat(String data) {
192         if (!data.matches("[0-9A-Fa-f]{64}")) { // if not HEX string
193             data = convertToQuotedString(data);
194         }
195         return data;
196     }
197 
convertToQuotedString(String str)198     private static String convertToQuotedString(String str) {
199         return '"' + str + '"';
200     }
201 }
202