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 import android.util.Log;
29 
30 import java.nio.BufferUnderflowException;
31 import java.nio.ByteBuffer;
32 import java.util.Arrays;
33 import java.util.BitSet;
34 
35 public final class NfcWifiProtectedSetup {
36 
37     public static final String NFC_TOKEN_MIME_TYPE = "application/vnd.wfa.wsc";
38 
39     public static final String EXTRA_WIFI_CONFIG = "com.android.nfc.WIFI_CONFIG_EXTRA";
40 
41     /*
42      * ID into configuration record for SSID and Network Key in hex.
43      * Obtained from WFA Wifi Simple Configuration Technical Specification v2.0.2.1.
44      */
45     private static final short CREDENTIAL_FIELD_ID = 0x100E;
46     private static final short SSID_FIELD_ID = 0x1045;
47     private static final short NETWORK_KEY_FIELD_ID = 0x1027;
48     private static final short AUTH_TYPE_FIELD_ID = 0x1003;
49 
50     private static final short AUTH_TYPE_EXPECTED_SIZE = 2;
51 
52     private static final short AUTH_TYPE_OPEN = 0;
53     private static final short AUTH_TYPE_WPA_PSK = 0x0002;
54     private static final short AUTH_TYPE_WPA_EAP =  0x0008;
55     private static final short AUTH_TYPE_WPA2_EAP = 0x0010;
56     private static final short AUTH_TYPE_WPA2_PSK = 0x0020;
57 
58     private static final int MAX_NETWORK_KEY_SIZE_BYTES = 64;
59 
NfcWifiProtectedSetup()60     private NfcWifiProtectedSetup() {}
61 
tryNfcWifiSetup(Ndef ndef, Context context)62     public static boolean tryNfcWifiSetup(Ndef ndef, Context context) {
63 
64         if (ndef == null || context == null) {
65             return false;
66         }
67 
68         NdefMessage cachedNdefMessage = ndef.getCachedNdefMessage();
69         if (cachedNdefMessage == null) {
70             return false;
71         }
72 
73         final WifiConfiguration wifiConfiguration;
74         try {
75             wifiConfiguration = parse(cachedNdefMessage);
76         } catch (BufferUnderflowException e) {
77             // malformed payload
78             return false;
79         }
80 
81         if (wifiConfiguration != null &&!UserManager.get(context).hasUserRestriction(
82                 UserManager.DISALLOW_CONFIG_WIFI,
83                 // hasUserRestriction does not support UserHandle.CURRENT.
84                 UserHandle.of(ActivityManager.getCurrentUser()))) {
85             Intent configureNetworkIntent = new Intent()
86                     .putExtra(EXTRA_WIFI_CONFIG, wifiConfiguration)
87                     .setClass(context, ConfirmConnectToWifiNetworkActivity.class)
88                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
89 
90             context.startActivityAsUser(configureNetworkIntent, UserHandle.CURRENT);
91             return true;
92         }
93 
94         return false;
95     }
96 
parse(NdefMessage message)97     private static WifiConfiguration parse(NdefMessage message) {
98         NdefRecord[] records = message.getRecords();
99 
100         for (NdefRecord record : records) {
101             if (new String(record.getType()).equals(NFC_TOKEN_MIME_TYPE)) {
102                 ByteBuffer payload = ByteBuffer.wrap(record.getPayload());
103                 while (payload.hasRemaining()) {
104                     short fieldId = payload.getShort();
105                     short fieldSize = payload.getShort();
106                     if (fieldId == CREDENTIAL_FIELD_ID) {
107                         return parseCredential(payload, fieldSize);
108                     }
109                 }
110             }
111         }
112         return null;
113     }
114 
parseCredential(ByteBuffer payload, short size)115     private static WifiConfiguration parseCredential(ByteBuffer payload, short size) {
116         int startPosition = payload.position();
117         WifiConfiguration result = new WifiConfiguration();
118         while (payload.position() < startPosition + size) {
119             short fieldId = payload.getShort();
120             short fieldSize = payload.getShort();
121 
122             // sanity check
123             if (payload.position() + fieldSize > startPosition + size) {
124                 return null;
125             }
126 
127             switch (fieldId) {
128                 case SSID_FIELD_ID:
129                     byte[] ssid = new byte[fieldSize];
130                     payload.get(ssid);
131                     result.SSID = "\"" + new String(ssid) + "\"";
132                     break;
133                 case NETWORK_KEY_FIELD_ID:
134                     if (fieldSize > MAX_NETWORK_KEY_SIZE_BYTES) {
135                         return null;
136                     }
137                     byte[] networkKey = new byte[fieldSize];
138                     payload.get(networkKey);
139                     result.preSharedKey = "\"" + new String(networkKey) + "\"";
140                     break;
141                 case AUTH_TYPE_FIELD_ID:
142                     if (fieldSize != AUTH_TYPE_EXPECTED_SIZE) {
143                         // corrupt data
144                         return null;
145                     }
146 
147                     short authType = payload.getShort();
148                     populateAllowedKeyManagement(result.allowedKeyManagement, authType);
149                     break;
150                 default:
151                     // unknown / unparsed tag
152                     payload.position(payload.position() + fieldSize);
153                     break;
154             }
155         }
156 
157         if (result.preSharedKey != null && result.SSID != null) {
158             return result;
159         }
160 
161         return null;
162     }
163 
populateAllowedKeyManagement(BitSet allowedKeyManagement, short authType)164     private static void populateAllowedKeyManagement(BitSet allowedKeyManagement, short authType) {
165         if (authType == AUTH_TYPE_WPA_PSK || authType == AUTH_TYPE_WPA2_PSK) {
166             allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
167         } else if (authType == AUTH_TYPE_WPA_EAP || authType == AUTH_TYPE_WPA2_EAP) {
168             allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
169         } else if (authType == AUTH_TYPE_OPEN) {
170             allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
171         }
172     }
173 }
174