wps-nfc.py revision 33e38bfa7159cef089d6ee0d904778e184c72c47
1#!/usr/bin/python
2#
3# Example nfcpy to wpa_supplicant wrapper for WPS NFC operations
4# Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi>
5#
6# This software may be distributed under the terms of the BSD license.
7# See README for more details.
8
9import os
10import sys
11import time
12import random
13import StringIO
14
15import nfc
16import nfc.ndef
17import nfc.llcp
18import nfc.handover
19
20import logging
21logging.basicConfig()
22
23import wpactrl
24
25wpas_ctrl = '/var/run/wpa_supplicant'
26
27def wpas_connect():
28    ifaces = []
29    if os.path.isdir(wpas_ctrl):
30        try:
31            ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
32        except OSError, error:
33            print "Could not find wpa_supplicant: ", error
34            return None
35
36    if len(ifaces) < 1:
37        print "No wpa_supplicant control interface found"
38        return None
39
40    for ctrl in ifaces:
41        try:
42            wpas = wpactrl.WPACtrl(ctrl)
43            return wpas
44        except wpactrl.error, error:
45            print "Error: ", error
46            pass
47    return None
48
49
50def wpas_tag_read(message):
51    wpas = wpas_connect()
52    if (wpas == None):
53        return
54    print wpas.request("WPS_NFC_TAG_READ " + message.encode("hex"))
55
56
57def wpas_get_config_token():
58    wpas = wpas_connect()
59    if (wpas == None):
60        return None
61    return wpas.request("WPS_NFC_CONFIG_TOKEN NDEF").rstrip().decode("hex")
62
63
64def wpas_get_er_config_token(uuid):
65    wpas = wpas_connect()
66    if (wpas == None):
67        return None
68    return wpas.request("WPS_ER_NFC_CONFIG_TOKEN NDEF " + uuid).rstrip().decode("hex")
69
70
71def wpas_get_password_token():
72    wpas = wpas_connect()
73    if (wpas == None):
74        return None
75    return wpas.request("WPS_NFC_TOKEN NDEF").rstrip().decode("hex")
76
77
78def wpas_get_handover_req():
79    wpas = wpas_connect()
80    if (wpas == None):
81        return None
82    return wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR").rstrip().decode("hex")
83
84
85def wpas_get_handover_sel(uuid):
86    wpas = wpas_connect()
87    if (wpas == None):
88        return None
89    if uuid is None:
90        return wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR").rstrip().decode("hex")
91    return wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR " + uuid).rstrip().decode("hex")
92
93
94def wpas_report_handover(req, sel, type):
95    wpas = wpas_connect()
96    if (wpas == None):
97        return None
98    return wpas.request("NFC_REPORT_HANDOVER " + type + " WPS " +
99                        str(req).encode("hex") + " " +
100                        str(sel).encode("hex"))
101
102
103class HandoverServer(nfc.handover.HandoverServer):
104    def __init__(self):
105        super(HandoverServer, self).__init__()
106
107    def process_request(self, request):
108        print "HandoverServer - request received"
109        print "Parsed handover request: " + request.pretty()
110
111        sel = nfc.ndef.HandoverSelectMessage(version="1.2")
112
113        for carrier in request.carriers:
114            print "Remote carrier type: " + carrier.type
115            if carrier.type == "application/vnd.wfa.wsc":
116                print "WPS carrier type match - add WPS carrier record"
117                self.received_carrier = carrier.record
118                data = wpas_get_handover_sel(self.uuid)
119                if data is None:
120                    print "Could not get handover select carrier record from wpa_supplicant"
121                    continue
122                print "Handover select carrier record from wpa_supplicant:"
123                print data.encode("hex")
124                self.sent_carrier = data
125
126                message = nfc.ndef.Message(data);
127                sel.add_carrier(message[0], "active", message[1:])
128
129        print "Handover select:"
130        print sel.pretty()
131        print str(sel).encode("hex")
132
133        print "Sending handover select"
134        return sel
135
136
137def wps_handover_resp(peer, uuid):
138    if uuid is None:
139        print "Trying to handle WPS handover"
140    else:
141        print "Trying to handle WPS handover with AP " + uuid
142
143    srv = HandoverServer()
144    srv.sent_carrier = None
145    srv.uuid = uuid
146
147    nfc.llcp.activate(peer);
148
149    try:
150        print "Trying handover";
151        srv.start()
152        print "Wait for disconnect"
153        while nfc.llcp.connected():
154            time.sleep(0.1)
155        print "Disconnected after handover"
156    except nfc.llcp.ConnectRefused:
157        print "Handover connection refused"
158        nfc.llcp.shutdown()
159        return
160
161    if srv.sent_carrier:
162        wpas_report_handover(srv.received_carrier, srv.sent_carrier, "RESP")
163
164    print "Remove peer"
165    nfc.llcp.shutdown()
166    print "Done with handover"
167    time.sleep(1)
168
169
170def wps_handover_init(peer):
171    print "Trying to initiate WPS handover"
172
173    data = wpas_get_handover_req()
174    if (data == None):
175        print "Could not get handover request carrier record from wpa_supplicant"
176        return
177    print "Handover request carrier record from wpa_supplicant: " + data.encode("hex")
178    record = nfc.ndef.Record()
179    f = StringIO.StringIO(data)
180    record._read(f)
181    record = nfc.ndef.HandoverCarrierRecord(record)
182    print "Parsed handover request carrier record:"
183    print record.pretty()
184
185    message = nfc.ndef.HandoverRequestMessage(version="1.2")
186    message.nonce = random.randint(0, 0xffff)
187    message.add_carrier(record, "active")
188
189    print "Handover request:"
190    print message.pretty()
191
192    nfc.llcp.activate(peer);
193
194    client = nfc.handover.HandoverClient()
195    try:
196        print "Trying handover";
197        client.connect()
198        print "Connected for handover"
199    except nfc.llcp.ConnectRefused:
200        print "Handover connection refused"
201        nfc.llcp.shutdown()
202        client.close()
203        return
204
205    print "Sending handover request"
206
207    if not client.send(message):
208        print "Failed to send handover request"
209
210    print "Receiving handover response"
211    message = client._recv()
212    if message is None:
213        print "No response received"
214        nfc.llcp.shutdown()
215        client.close()
216        return
217    if message.type != "urn:nfc:wkt:Hs":
218        print "Response was not Hs - received: " + message.type
219        nfc.llcp.shutdown()
220        client.close()
221        return
222
223    print "Received message"
224    print message.pretty()
225    message = nfc.ndef.HandoverSelectMessage(message)
226    print "Handover select received"
227    print message.pretty()
228
229    for carrier in message.carriers:
230        print "Remote carrier type: " + carrier.type
231        if carrier.type == "application/vnd.wfa.wsc":
232            print "WPS carrier type match - send to wpa_supplicant"
233            wpas_report_handover(data, carrier.record, "INIT")
234            wifi = nfc.ndef.WifiConfigRecord(carrier.record)
235            print wifi.pretty()
236
237    print "Remove peer"
238    nfc.llcp.shutdown()
239    client.close()
240    print "Done with handover"
241
242
243def wps_tag_read(tag):
244    if len(tag.ndef.message):
245        message = nfc.ndef.Message(tag.ndef.message)
246        print "message type " + message.type
247
248        for record in message:
249            print "record type " + record.type
250            if record.type == "application/vnd.wfa.wsc":
251                print "WPS tag - send to wpa_supplicant"
252                wpas_tag_read(tag.ndef.message)
253                break
254    else:
255        print "Empty tag"
256
257    print "Remove tag"
258    while tag.is_present:
259        time.sleep(0.1)
260
261
262def wps_write_config_tag(clf):
263    print "Write WPS config token"
264    data = wpas_get_config_token()
265    if (data == None):
266        print "Could not get WPS config token from wpa_supplicant"
267        return
268
269    print "Touch an NFC tag"
270    while True:
271        tag = clf.poll()
272        if tag == None:
273            time.sleep(0.1)
274            continue
275        break
276
277    print "Tag found - writing"
278    tag.ndef.message = data
279    print "Done - remove tag"
280    while tag.is_present:
281        time.sleep(0.1)
282
283
284def wps_write_er_config_tag(clf, uuid):
285    print "Write WPS ER config token"
286    data = wpas_get_er_config_token(uuid)
287    if (data == None):
288        print "Could not get WPS config token from wpa_supplicant"
289        return
290
291    print "Touch an NFC tag"
292    while True:
293        tag = clf.poll()
294        if tag == None:
295            time.sleep(0.1)
296            continue
297        break
298
299    print "Tag found - writing"
300    tag.ndef.message = data
301    print "Done - remove tag"
302    while tag.is_present:
303        time.sleep(0.1)
304
305
306def wps_write_password_tag(clf):
307    print "Write WPS password token"
308    data = wpas_get_password_token()
309    if (data == None):
310        print "Could not get WPS password token from wpa_supplicant"
311        return
312
313    print "Touch an NFC tag"
314    while True:
315        tag = clf.poll()
316        if tag == None:
317            time.sleep(0.1)
318            continue
319        break
320
321    print "Tag found - writing"
322    tag.ndef.message = data
323    print "Done - remove tag"
324    while tag.is_present:
325        time.sleep(0.1)
326
327
328def find_peer(clf):
329    while True:
330        if nfc.llcp.connected():
331            print "LLCP connected"
332        general_bytes = nfc.llcp.startup({})
333        peer = clf.listen(ord(os.urandom(1)) + 250, general_bytes)
334        if isinstance(peer, nfc.DEP):
335            print "listen -> DEP";
336            if peer.general_bytes.startswith("Ffm"):
337                print "Found DEP"
338                return peer
339            print "mismatch in general_bytes"
340            print peer.general_bytes
341
342        peer = clf.poll(general_bytes)
343        if isinstance(peer, nfc.DEP):
344            print "poll -> DEP";
345            if peer.general_bytes.startswith("Ffm"):
346                print "Found DEP"
347                return peer
348            print "mismatch in general_bytes"
349            print peer.general_bytes
350
351        if peer:
352            print "Found tag"
353            return peer
354
355
356def main():
357    clf = nfc.ContactlessFrontend()
358
359    try:
360        arg_uuid = None
361        if len(sys.argv) > 1:
362            arg_uuid = sys.argv[1]
363
364        if len(sys.argv) > 1 and sys.argv[1] == "write-config":
365            wps_write_config_tag(clf)
366            raise SystemExit
367
368        if len(sys.argv) > 2 and sys.argv[1] == "write-er-config":
369            wps_write_er_config_tag(clf, sys.argv[2])
370            raise SystemExit
371
372        if len(sys.argv) > 1 and sys.argv[1] == "write-password":
373            wps_write_password_tag(clf)
374            raise SystemExit
375
376        while True:
377            print "Waiting for a tag or peer to be touched"
378
379            tag = find_peer(clf)
380            if isinstance(tag, nfc.DEP):
381                if arg_uuid is None:
382                    wps_handover_init(tag)
383                elif arg_uuid is "ap":
384                    wps_handover_resp(tag, None)
385                else:
386                    wps_handover_resp(tag, arg_uuid)
387                continue
388
389            if tag.ndef:
390                wps_tag_read(tag)
391                continue
392
393            print "Not an NDEF tag - remove tag"
394            while tag.is_present:
395                time.sleep(0.1)
396
397    except KeyboardInterrupt:
398        raise SystemExit
399    finally:
400        clf.close()
401
402    raise SystemExit
403
404if __name__ == '__main__':
405    main()
406