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*/
16package com.android.nfc;
17
18
19import android.app.ActivityManager;
20import 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;
28import android.util.Log;
29
30import java.nio.BufferUnderflowException;
31import java.nio.ByteBuffer;
32import java.util.Arrays;
33import 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
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
60    private NfcWifiProtectedSetup() {}
61
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
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                    int fieldSize = payload.getShort() & 0xFFFF;
106                    if (fieldId == CREDENTIAL_FIELD_ID) {
107                        return parseCredential(payload, fieldSize);
108                    }
109                    payload.position(payload.position() + fieldSize);
110                }
111            }
112        }
113        return null;
114    }
115
116    private static WifiConfiguration parseCredential(ByteBuffer payload, int size) {
117        int startPosition = payload.position();
118        WifiConfiguration result = new WifiConfiguration();
119        while (payload.position() < startPosition + size) {
120            short fieldId = payload.getShort();
121            int fieldSize = payload.getShort() & 0xFFFF;
122
123            // sanity check
124            if (payload.position() + fieldSize > startPosition + size) {
125                return null;
126            }
127
128            switch (fieldId) {
129                case SSID_FIELD_ID:
130                    byte[] ssid = new byte[fieldSize];
131                    payload.get(ssid);
132                    result.SSID = "\"" + new String(ssid) + "\"";
133                    break;
134                case NETWORK_KEY_FIELD_ID:
135                    if (fieldSize > MAX_NETWORK_KEY_SIZE_BYTES) {
136                        return null;
137                    }
138                    byte[] networkKey = new byte[fieldSize];
139                    payload.get(networkKey);
140                    result.preSharedKey = "\"" + new String(networkKey) + "\"";
141                    break;
142                case AUTH_TYPE_FIELD_ID:
143                    if (fieldSize != AUTH_TYPE_EXPECTED_SIZE) {
144                        // corrupt data
145                        return null;
146                    }
147
148                    short authType = payload.getShort();
149                    populateAllowedKeyManagement(result.allowedKeyManagement, authType);
150                    break;
151                default:
152                    // unknown / unparsed tag
153                    payload.position(payload.position() + fieldSize);
154                    break;
155            }
156        }
157
158        if (result.preSharedKey != null && result.SSID != null) {
159            return result;
160        }
161
162        return null;
163    }
164
165    private static void populateAllowedKeyManagement(BitSet allowedKeyManagement, short authType) {
166        if (authType == AUTH_TYPE_WPA_PSK || authType == AUTH_TYPE_WPA2_PSK) {
167            allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
168        } else if (authType == AUTH_TYPE_WPA_EAP || authType == AUTH_TYPE_WPA2_EAP) {
169            allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
170        } else if (authType == AUTH_TYPE_OPEN) {
171            allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
172        }
173    }
174}
175