blob: af4dd6f6379314bea9186541ffa566377810364a [file] [log] [blame]
nxpandroid64fd68c2015-09-23 16:45:15 +05301/*
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*/
16package com.android.nfc;
17
18
nxpandroid281eb922016-08-25 20:27:46 +053019import android.app.ActivityManager;
nxpandroid64fd68c2015-09-23 16:45:15 +053020import android.content.Context;
21import android.content.Intent;
22import android.net.wifi.WifiConfiguration;
23import android.nfc.NdefMessage;
24import android.nfc.NdefRecord;
25import android.nfc.tech.Ndef;
26import android.os.UserHandle;
27import android.os.UserManager;
nxf500513a018e72019-04-23 17:11:41 +053028import android.util.Log;
29
nxpandroid64fd68c2015-09-23 16:45:15 +053030import java.nio.BufferUnderflowException;
31import java.nio.ByteBuffer;
nxf500513a018e72019-04-23 17:11:41 +053032import java.util.Arrays;
nxpandroid64fd68c2015-09-23 16:45:15 +053033import java.util.BitSet;
34
35public 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
Suhas Sureshb2365452018-05-15 12:14:02 +053052 private static final short AUTH_TYPE_OPEN = 0x0001;
nxpandroid64fd68c2015-09-23 16:45:15 +053053 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;
nxf500513a018e72019-04-23 17:11:41 +053057 private static final short AUTH_TYPE_WPA_AND_WPA2_PSK = 0x0022;
nxpandroid64fd68c2015-09-23 16:45:15 +053058
59 private static final int MAX_NETWORK_KEY_SIZE_BYTES = 64;
60
61 private NfcWifiProtectedSetup() {}
62
63 public static boolean tryNfcWifiSetup(Ndef ndef, Context context) {
64
65 if (ndef == null || context == null) {
66 return false;
67 }
68
69 NdefMessage cachedNdefMessage = ndef.getCachedNdefMessage();
70 if (cachedNdefMessage == null) {
71 return false;
72 }
73
74 final WifiConfiguration wifiConfiguration;
75 try {
76 wifiConfiguration = parse(cachedNdefMessage);
77 } catch (BufferUnderflowException e) {
78 // malformed payload
79 return false;
80 }
81
82 if (wifiConfiguration != null &&!UserManager.get(context).hasUserRestriction(
nxpandroid281eb922016-08-25 20:27:46 +053083 UserManager.DISALLOW_CONFIG_WIFI,
84 // hasUserRestriction does not support UserHandle.CURRENT.
85 UserHandle.of(ActivityManager.getCurrentUser()))) {
nxpandroid64fd68c2015-09-23 16:45:15 +053086 Intent configureNetworkIntent = new Intent()
87 .putExtra(EXTRA_WIFI_CONFIG, wifiConfiguration)
88 .setClass(context, ConfirmConnectToWifiNetworkActivity.class)
nxpandroid1153eb32015-11-06 18:46:58 +053089 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
nxpandroid64fd68c2015-09-23 16:45:15 +053090
91 context.startActivityAsUser(configureNetworkIntent, UserHandle.CURRENT);
92 return true;
93 }
94
95 return false;
96 }
97
98 private static WifiConfiguration parse(NdefMessage message) {
99 NdefRecord[] records = message.getRecords();
100
101 for (NdefRecord record : records) {
102 if (new String(record.getType()).equals(NFC_TOKEN_MIME_TYPE)) {
103 ByteBuffer payload = ByteBuffer.wrap(record.getPayload());
104 while (payload.hasRemaining()) {
105 short fieldId = payload.getShort();
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530106 int fieldSize = payload.getShort() & 0xFFFF;
nxpandroid64fd68c2015-09-23 16:45:15 +0530107 if (fieldId == CREDENTIAL_FIELD_ID) {
108 return parseCredential(payload, fieldSize);
nxpandroid64fd68c2015-09-23 16:45:15 +0530109 }
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530110 payload.position(payload.position() + fieldSize);
nxpandroid64fd68c2015-09-23 16:45:15 +0530111 }
112 }
113 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530114 return null;
115 }
116
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530117 private static WifiConfiguration parseCredential(ByteBuffer payload, int size) {
nxpandroid64fd68c2015-09-23 16:45:15 +0530118 int startPosition = payload.position();
119 WifiConfiguration result = new WifiConfiguration();
120 while (payload.position() < startPosition + size) {
121 short fieldId = payload.getShort();
nxpandroid6fd9cdb2017-07-12 18:25:41 +0530122 int fieldSize = payload.getShort() & 0xFFFF;
nxpandroid64fd68c2015-09-23 16:45:15 +0530123
124 // sanity check
125 if (payload.position() + fieldSize > startPosition + size) {
126 return null;
127 }
128
129 switch (fieldId) {
130 case SSID_FIELD_ID:
131 byte[] ssid = new byte[fieldSize];
132 payload.get(ssid);
133 result.SSID = "\"" + new String(ssid) + "\"";
134 break;
135 case NETWORK_KEY_FIELD_ID:
136 if (fieldSize > MAX_NETWORK_KEY_SIZE_BYTES) {
137 return null;
138 }
139 byte[] networkKey = new byte[fieldSize];
140 payload.get(networkKey);
Suhas Sureshb2365452018-05-15 12:14:02 +0530141 if (fieldSize > 0) {
nxf500513a018e72019-04-23 17:11:41 +0530142 result.preSharedKey = getPskValidFormat(new String(networkKey));
Suhas Sureshb2365452018-05-15 12:14:02 +0530143 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530144 break;
145 case AUTH_TYPE_FIELD_ID:
146 if (fieldSize != AUTH_TYPE_EXPECTED_SIZE) {
147 // corrupt data
148 return null;
149 }
150
151 short authType = payload.getShort();
152 populateAllowedKeyManagement(result.allowedKeyManagement, authType);
153 break;
154 default:
155 // unknown / unparsed tag
156 payload.position(payload.position() + fieldSize);
157 break;
158 }
159 }
160
Suhas Sureshb2365452018-05-15 12:14:02 +0530161 if (result.SSID != null) {
162 if (result.getAuthType() == WifiConfiguration.KeyMgmt.NONE) {
163 if (result.preSharedKey == null) {
164 return result;
165 }
166 } else {
167 if (result.preSharedKey != null) {
168 return result;
169 }
170 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530171 }
172
173 return null;
174 }
175
176 private static void populateAllowedKeyManagement(BitSet allowedKeyManagement, short authType) {
nxf500513a018e72019-04-23 17:11:41 +0530177 if (authType == AUTH_TYPE_WPA_PSK || authType == AUTH_TYPE_WPA2_PSK
178 || authType == AUTH_TYPE_WPA_AND_WPA2_PSK) {
nxpandroid64fd68c2015-09-23 16:45:15 +0530179 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
180 } else if (authType == AUTH_TYPE_WPA_EAP || authType == AUTH_TYPE_WPA2_EAP) {
181 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
182 } else if (authType == AUTH_TYPE_OPEN) {
183 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
184 }
185 }
nxf500513a018e72019-04-23 17:11:41 +0530186
187 private static String getPskValidFormat(String data) {
188 if (!data.matches("[0-9A-Fa-f]{64}")) { // if not HEX string
189 data = convertToQuotedString(data);
190 }
191 return data;
192 }
193
194 private static String convertToQuotedString(String str) {
195 return '"' + str + '"';
196 }
nxpandroid64fd68c2015-09-23 16:45:15 +0530197}