]>
Commit | Line | Data |
---|---|---|
d4f612b7 JM |
1 | #!/usr/bin/python |
2 | # | |
3 | # Example nfcpy to wpa_supplicant wrapper for WPS NFC operations | |
8140ae96 | 4 | # Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi> |
d4f612b7 JM |
5 | # |
6 | # This software may be distributed under the terms of the BSD license. | |
7 | # See README for more details. | |
8 | ||
9 | import os | |
10 | import sys | |
11 | import time | |
bbaaaee1 JM |
12 | import random |
13 | import StringIO | |
6f8fa6e5 | 14 | import threading |
ab1db08c | 15 | import argparse |
d4f612b7 JM |
16 | |
17 | import nfc | |
18 | import nfc.ndef | |
e50d01b4 JM |
19 | import nfc.llcp |
20 | import nfc.handover | |
d4f612b7 | 21 | |
8140ae96 | 22 | import logging |
8140ae96 | 23 | |
c3aa4da9 | 24 | import wpaspy |
d4f612b7 JM |
25 | |
26 | wpas_ctrl = '/var/run/wpa_supplicant' | |
6f8fa6e5 JM |
27 | srv = None |
28 | continue_loop = True | |
1f1b5b31 | 29 | terminate_now = False |
d4f612b7 | 30 | |
ec4f5a37 | 31 | def wpas_connect(): |
d4f612b7 JM |
32 | ifaces = [] |
33 | if os.path.isdir(wpas_ctrl): | |
34 | try: | |
35 | ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)] | |
36 | except OSError, error: | |
37 | print "Could not find wpa_supplicant: ", error | |
ec4f5a37 | 38 | return None |
d4f612b7 JM |
39 | |
40 | if len(ifaces) < 1: | |
41 | print "No wpa_supplicant control interface found" | |
ec4f5a37 | 42 | return None |
d4f612b7 JM |
43 | |
44 | for ctrl in ifaces: | |
45 | try: | |
c3aa4da9 | 46 | wpas = wpaspy.Ctrl(ctrl) |
ec4f5a37 | 47 | return wpas |
c3aa4da9 | 48 | except Exception, e: |
d4f612b7 | 49 | pass |
ec4f5a37 JM |
50 | return None |
51 | ||
52 | ||
53 | def wpas_tag_read(message): | |
54 | wpas = wpas_connect() | |
55 | if (wpas == None): | |
04382f7d | 56 | return False |
6f8fa6e5 | 57 | if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + str(message).encode("hex")): |
04382f7d JM |
58 | return False |
59 | return True | |
dab710c4 | 60 | |
88c8bf31 | 61 | def wpas_get_config_token(id=None): |
bbf41865 JM |
62 | wpas = wpas_connect() |
63 | if (wpas == None): | |
64 | return None | |
88c8bf31 | 65 | if id: |
04382f7d JM |
66 | ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF " + id) |
67 | else: | |
68 | ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF") | |
69 | if "FAIL" in ret: | |
70 | return None | |
71 | return ret.rstrip().decode("hex") | |
bbf41865 JM |
72 | |
73 | ||
c39fdb85 JM |
74 | def wpas_get_er_config_token(uuid): |
75 | wpas = wpas_connect() | |
76 | if (wpas == None): | |
77 | return None | |
79ede5a7 JM |
78 | ret = wpas.request("WPS_ER_NFC_CONFIG_TOKEN NDEF " + uuid) |
79 | if "FAIL" in ret: | |
80 | return None | |
81 | return ret.rstrip().decode("hex") | |
c39fdb85 JM |
82 | |
83 | ||
23ffcaf1 JM |
84 | def wpas_get_password_token(): |
85 | wpas = wpas_connect() | |
86 | if (wpas == None): | |
87 | return None | |
88 | return wpas.request("WPS_NFC_TOKEN NDEF").rstrip().decode("hex") | |
89 | ||
90 | ||
e50d01b4 JM |
91 | def wpas_get_handover_req(): |
92 | wpas = wpas_connect() | |
93 | if (wpas == None): | |
94 | return None | |
bbaaaee1 | 95 | return wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR").rstrip().decode("hex") |
e50d01b4 JM |
96 | |
97 | ||
f3f2ba2e | 98 | def wpas_get_handover_sel(uuid): |
e50d01b4 JM |
99 | wpas = wpas_connect() |
100 | if (wpas == None): | |
e4758827 | 101 | return None |
f23ce1f0 | 102 | if uuid is None: |
79ede5a7 JM |
103 | res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR").rstrip() |
104 | else: | |
105 | res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR " + uuid).rstrip() | |
106 | if "FAIL" in res: | |
107 | return None | |
108 | return res.decode("hex") | |
f3f2ba2e JM |
109 | |
110 | ||
111 | def wpas_report_handover(req, sel, type): | |
112 | wpas = wpas_connect() | |
113 | if (wpas == None): | |
114 | return None | |
115 | return wpas.request("NFC_REPORT_HANDOVER " + type + " WPS " + | |
e4758827 JM |
116 | str(req).encode("hex") + " " + |
117 | str(sel).encode("hex")) | |
e50d01b4 JM |
118 | |
119 | ||
f3f2ba2e | 120 | class HandoverServer(nfc.handover.HandoverServer): |
6f8fa6e5 JM |
121 | def __init__(self, llc): |
122 | super(HandoverServer, self).__init__(llc) | |
123 | self.sent_carrier = None | |
124 | self.ho_server_processing = False | |
125 | self.success = False | |
f3f2ba2e JM |
126 | |
127 | def process_request(self, request): | |
6f8fa6e5 | 128 | self.ho_server_processing = True |
f3f2ba2e JM |
129 | print "HandoverServer - request received" |
130 | print "Parsed handover request: " + request.pretty() | |
131 | ||
132 | sel = nfc.ndef.HandoverSelectMessage(version="1.2") | |
133 | ||
134 | for carrier in request.carriers: | |
135 | print "Remote carrier type: " + carrier.type | |
136 | if carrier.type == "application/vnd.wfa.wsc": | |
137 | print "WPS carrier type match - add WPS carrier record" | |
f3f2ba2e JM |
138 | data = wpas_get_handover_sel(self.uuid) |
139 | if data is None: | |
140 | print "Could not get handover select carrier record from wpa_supplicant" | |
141 | continue | |
142 | print "Handover select carrier record from wpa_supplicant:" | |
143 | print data.encode("hex") | |
144 | self.sent_carrier = data | |
6f8fa6e5 | 145 | wpas_report_handover(carrier.record, self.sent_carrier, "RESP") |
f3f2ba2e JM |
146 | |
147 | message = nfc.ndef.Message(data); | |
148 | sel.add_carrier(message[0], "active", message[1:]) | |
149 | ||
150 | print "Handover select:" | |
151 | print sel.pretty() | |
152 | print str(sel).encode("hex") | |
153 | ||
154 | print "Sending handover select" | |
6f8fa6e5 | 155 | self.success = True |
f3f2ba2e JM |
156 | return sel |
157 | ||
158 | ||
6f8fa6e5 | 159 | def wps_handover_init(llc): |
e50d01b4 JM |
160 | print "Trying to initiate WPS handover" |
161 | ||
162 | data = wpas_get_handover_req() | |
163 | if (data == None): | |
bbaaaee1 | 164 | print "Could not get handover request carrier record from wpa_supplicant" |
e50d01b4 | 165 | return |
bbaaaee1 JM |
166 | print "Handover request carrier record from wpa_supplicant: " + data.encode("hex") |
167 | record = nfc.ndef.Record() | |
168 | f = StringIO.StringIO(data) | |
169 | record._read(f) | |
170 | record = nfc.ndef.HandoverCarrierRecord(record) | |
171 | print "Parsed handover request carrier record:" | |
172 | print record.pretty() | |
173 | ||
174 | message = nfc.ndef.HandoverRequestMessage(version="1.2") | |
175 | message.nonce = random.randint(0, 0xffff) | |
176 | message.add_carrier(record, "active") | |
177 | ||
178 | print "Handover request:" | |
cf78e2ac | 179 | print message.pretty() |
e50d01b4 | 180 | |
6f8fa6e5 | 181 | client = nfc.handover.HandoverClient(llc) |
e50d01b4 JM |
182 | try: |
183 | print "Trying handover"; | |
184 | client.connect() | |
185 | print "Connected for handover" | |
186 | except nfc.llcp.ConnectRefused: | |
187 | print "Handover connection refused" | |
e50d01b4 JM |
188 | client.close() |
189 | return | |
190 | ||
191 | print "Sending handover request" | |
192 | ||
193 | if not client.send(message): | |
194 | print "Failed to send handover request" | |
195 | ||
196 | print "Receiving handover response" | |
197 | message = client._recv() | |
cf78e2ac JM |
198 | if message is None: |
199 | print "No response received" | |
cf78e2ac JM |
200 | client.close() |
201 | return | |
b8dbc5d6 JM |
202 | if message.type != "urn:nfc:wkt:Hs": |
203 | print "Response was not Hs - received: " + message.type | |
b8dbc5d6 JM |
204 | client.close() |
205 | return | |
cf78e2ac | 206 | |
b8dbc5d6 JM |
207 | print "Received message" |
208 | print message.pretty() | |
209 | message = nfc.ndef.HandoverSelectMessage(message) | |
e50d01b4 JM |
210 | print "Handover select received" |
211 | print message.pretty() | |
b8dbc5d6 JM |
212 | |
213 | for carrier in message.carriers: | |
214 | print "Remote carrier type: " + carrier.type | |
215 | if carrier.type == "application/vnd.wfa.wsc": | |
216 | print "WPS carrier type match - send to wpa_supplicant" | |
f3f2ba2e | 217 | wpas_report_handover(data, carrier.record, "INIT") |
b8dbc5d6 JM |
218 | wifi = nfc.ndef.WifiConfigRecord(carrier.record) |
219 | print wifi.pretty() | |
e50d01b4 JM |
220 | |
221 | print "Remove peer" | |
e50d01b4 JM |
222 | client.close() |
223 | print "Done with handover" | |
6f8fa6e5 JM |
224 | global only_one |
225 | if only_one: | |
226 | global continue_loop | |
227 | continue_loop = False | |
e50d01b4 | 228 | |
1f1b5b31 JM |
229 | global no_wait |
230 | if no_wait: | |
231 | print "Trying to exit.." | |
232 | global terminate_now | |
233 | terminate_now = True | |
e50d01b4 | 234 | |
04382f7d JM |
235 | def wps_tag_read(tag, wait_remove=True): |
236 | success = False | |
dab710c4 | 237 | if len(tag.ndef.message): |
6f8fa6e5 | 238 | for record in tag.ndef.message: |
dab710c4 JM |
239 | print "record type " + record.type |
240 | if record.type == "application/vnd.wfa.wsc": | |
241 | print "WPS tag - send to wpa_supplicant" | |
04382f7d | 242 | success = wpas_tag_read(tag.ndef.message) |
dab710c4 JM |
243 | break |
244 | else: | |
245 | print "Empty tag" | |
246 | ||
04382f7d JM |
247 | if wait_remove: |
248 | print "Remove tag" | |
249 | while tag.is_present: | |
250 | time.sleep(0.1) | |
251 | ||
252 | return success | |
dab710c4 JM |
253 | |
254 | ||
6f8fa6e5 JM |
255 | def rdwr_connected_write(tag): |
256 | print "Tag found - writing" | |
257 | global write_data | |
258 | tag.ndef.message = str(write_data) | |
259 | print "Done - remove tag" | |
260 | global only_one | |
261 | if only_one: | |
262 | global continue_loop | |
263 | continue_loop = False | |
264 | global write_wait_remove | |
265 | while write_wait_remove and tag.is_present: | |
266 | time.sleep(0.1) | |
267 | ||
04382f7d | 268 | def wps_write_config_tag(clf, id=None, wait_remove=True): |
bbf41865 | 269 | print "Write WPS config token" |
6f8fa6e5 JM |
270 | global write_data, write_wait_remove |
271 | write_wait_remove = wait_remove | |
272 | write_data = wpas_get_config_token(id) | |
273 | if write_data == None: | |
bbf41865 | 274 | print "Could not get WPS config token from wpa_supplicant" |
04382f7d | 275 | sys.exit(1) |
bbf41865 | 276 | return |
bbf41865 | 277 | print "Touch an NFC tag" |
6f8fa6e5 | 278 | clf.connect(rdwr={'on-connect': rdwr_connected_write}) |
bbf41865 JM |
279 | |
280 | ||
ab1db08c | 281 | def wps_write_er_config_tag(clf, uuid, wait_remove=True): |
c39fdb85 | 282 | print "Write WPS ER config token" |
6f8fa6e5 | 283 | global write_data, write_wait_remove |
ab1db08c | 284 | write_wait_remove = wait_remove |
6f8fa6e5 JM |
285 | write_data = wpas_get_er_config_token(uuid) |
286 | if write_data == None: | |
c39fdb85 JM |
287 | print "Could not get WPS config token from wpa_supplicant" |
288 | return | |
289 | ||
290 | print "Touch an NFC tag" | |
6f8fa6e5 | 291 | clf.connect(rdwr={'on-connect': rdwr_connected_write}) |
c39fdb85 JM |
292 | |
293 | ||
04382f7d | 294 | def wps_write_password_tag(clf, wait_remove=True): |
23ffcaf1 | 295 | print "Write WPS password token" |
6f8fa6e5 JM |
296 | global write_data, write_wait_remove |
297 | write_wait_remove = wait_remove | |
298 | write_data = wpas_get_password_token() | |
299 | if write_data == None: | |
23ffcaf1 JM |
300 | print "Could not get WPS password token from wpa_supplicant" |
301 | return | |
302 | ||
303 | print "Touch an NFC tag" | |
6f8fa6e5 JM |
304 | clf.connect(rdwr={'on-connect': rdwr_connected_write}) |
305 | ||
306 | ||
307 | def rdwr_connected(tag): | |
1f1b5b31 | 308 | global only_one, no_wait |
6f8fa6e5 JM |
309 | print "Tag connected: " + str(tag) |
310 | ||
311 | if tag.ndef: | |
312 | print "NDEF tag: " + tag.type | |
313 | try: | |
314 | print tag.ndef.message.pretty() | |
315 | except Exception, e: | |
316 | print e | |
317 | success = wps_tag_read(tag, not only_one) | |
318 | if only_one and success: | |
319 | global continue_loop | |
320 | continue_loop = False | |
321 | else: | |
322 | print "Not an NDEF tag - remove tag" | |
1f1b5b31 JM |
323 | |
324 | return not no_wait | |
23ffcaf1 | 325 | |
23ffcaf1 | 326 | |
6f8fa6e5 JM |
327 | def llcp_worker(llc): |
328 | global arg_uuid | |
329 | if arg_uuid is None: | |
330 | wps_handover_init(llc) | |
1f1b5b31 | 331 | print "Exiting llcp_worker thread" |
6f8fa6e5 | 332 | return |
23ffcaf1 | 333 | |
6f8fa6e5 JM |
334 | global srv |
335 | global wait_connection | |
336 | while not wait_connection and srv.sent_carrier is None: | |
337 | if srv.ho_server_processing: | |
338 | time.sleep(0.025) | |
339 | ||
340 | def llcp_startup(clf, llc): | |
341 | global arg_uuid | |
342 | if arg_uuid: | |
343 | print "Start LLCP server" | |
344 | global srv | |
345 | srv = HandoverServer(llc) | |
346 | if arg_uuid is "ap": | |
347 | print "Trying to handle WPS handover" | |
348 | srv.uuid = None | |
349 | else: | |
350 | print "Trying to handle WPS handover with AP " + arg_uuid | |
351 | srv.uuid = arg_uuid | |
352 | return llc | |
353 | ||
354 | def llcp_connected(llc): | |
355 | print "P2P LLCP connected" | |
356 | global wait_connection | |
357 | wait_connection = False | |
358 | global arg_uuid | |
359 | if arg_uuid: | |
360 | global srv | |
361 | srv.start() | |
362 | else: | |
363 | threading.Thread(target=llcp_worker, args=(llc,)).start() | |
1f1b5b31 | 364 | print "llcp_connected returning" |
6f8fa6e5 | 365 | return True |
dc6bda11 JM |
366 | |
367 | ||
1f1b5b31 JM |
368 | def terminate_loop(): |
369 | global terminate_now | |
370 | return terminate_now | |
371 | ||
d4f612b7 JM |
372 | def main(): |
373 | clf = nfc.ContactlessFrontend() | |
374 | ||
ab1db08c | 375 | parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for WPS NFC operations') |
6202500f JM |
376 | parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO, |
377 | action='store_const', dest='loglevel', | |
378 | help='verbose debug output') | |
379 | parser.add_argument('-q', const=logging.WARNING, action='store_const', | |
380 | dest='loglevel', help='be quiet') | |
ab1db08c JM |
381 | parser.add_argument('--only-one', '-1', action='store_true', |
382 | help='run only one operation and exit') | |
383 | parser.add_argument('--no-wait', action='store_true', | |
384 | help='do not wait for tag to be removed before exiting') | |
385 | parser.add_argument('--uuid', | |
386 | help='UUID of an AP (used for WPS ER operations)') | |
387 | parser.add_argument('--id', | |
388 | help='network id (used for WPS ER operations)') | |
389 | parser.add_argument('command', choices=['write-config', | |
390 | 'write-er-config', | |
391 | 'write-password'], | |
392 | nargs='?') | |
393 | args = parser.parse_args() | |
6f8fa6e5 | 394 | |
ab1db08c JM |
395 | global arg_uuid |
396 | arg_uuid = args.uuid | |
bbf41865 | 397 | |
ab1db08c JM |
398 | global only_one |
399 | only_one = args.only_one | |
04382f7d | 400 | |
1f1b5b31 JM |
401 | global no_wait |
402 | no_wait = args.no_wait | |
403 | ||
6202500f JM |
404 | logging.basicConfig(level=args.loglevel) |
405 | ||
ab1db08c JM |
406 | try: |
407 | if not clf.open("usb"): | |
408 | print "Could not open connection with an NFC device" | |
88c8bf31 JM |
409 | raise SystemExit |
410 | ||
ab1db08c JM |
411 | if args.command == "write-config": |
412 | wps_write_config_tag(clf, id=args.id, wait_remove=not args.no_wait) | |
c39fdb85 JM |
413 | raise SystemExit |
414 | ||
ab1db08c JM |
415 | if args.command == "write-er-config": |
416 | wps_write_er_config_tag(clf, args.uuid, wait_remove=not args.no_wait) | |
23ffcaf1 JM |
417 | raise SystemExit |
418 | ||
ab1db08c JM |
419 | if args.command == "write-password": |
420 | wps_write_password_tag(clf, wait_remove=not args.no_wait) | |
04382f7d JM |
421 | raise SystemExit |
422 | ||
6f8fa6e5 JM |
423 | global continue_loop |
424 | while continue_loop: | |
e50d01b4 | 425 | print "Waiting for a tag or peer to be touched" |
6f8fa6e5 JM |
426 | wait_connection = True |
427 | try: | |
428 | if not clf.connect(rdwr={'on-connect': rdwr_connected}, | |
429 | llcp={'on-startup': llcp_startup, | |
1f1b5b31 JM |
430 | 'on-connect': llcp_connected}, |
431 | terminate=terminate_loop): | |
04382f7d | 432 | break |
6f8fa6e5 JM |
433 | except Exception, e: |
434 | print "clf.connect failed" | |
dc6bda11 | 435 | |
6f8fa6e5 JM |
436 | global srv |
437 | if only_one and srv and srv.success: | |
438 | raise SystemExit | |
d4f612b7 JM |
439 | |
440 | except KeyboardInterrupt: | |
441 | raise SystemExit | |
442 | finally: | |
443 | clf.close() | |
444 | ||
445 | raise SystemExit | |
446 | ||
447 | if __name__ == '__main__': | |
448 | main() |