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 threading 14import argparse 15 16import nfc 17import nfc.ndef 18import nfc.llcp 19import nfc.handover 20 21import logging 22 23import wpaspy 24 25wpas_ctrl = '/var/run/wpa_supplicant' 26srv = None 27continue_loop = True 28terminate_now = False 29summary_file = None 30success_file = None 31 32def summary(txt): 33 print(txt) 34 if summary_file: 35 with open(summary_file, 'a') as f: 36 f.write(txt + "\n") 37 38def success_report(txt): 39 summary(txt) 40 if success_file: 41 with open(success_file, 'a') as f: 42 f.write(txt + "\n") 43 44def wpas_connect(): 45 ifaces = [] 46 if os.path.isdir(wpas_ctrl): 47 try: 48 ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)] 49 except OSError as error: 50 print("Could not find wpa_supplicant: ", error) 51 return None 52 53 if len(ifaces) < 1: 54 print("No wpa_supplicant control interface found") 55 return None 56 57 for ctrl in ifaces: 58 try: 59 wpas = wpaspy.Ctrl(ctrl) 60 return wpas 61 except Exception as e: 62 pass 63 return None 64 65 66def wpas_tag_read(message): 67 wpas = wpas_connect() 68 if (wpas == None): 69 return False 70 if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + str(message).encode("hex")): 71 return False 72 return True 73 74def wpas_get_config_token(id=None): 75 wpas = wpas_connect() 76 if (wpas == None): 77 return None 78 if id: 79 ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF " + id) 80 else: 81 ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF") 82 if "FAIL" in ret: 83 return None 84 return ret.rstrip().decode("hex") 85 86 87def wpas_get_er_config_token(uuid): 88 wpas = wpas_connect() 89 if (wpas == None): 90 return None 91 ret = wpas.request("WPS_ER_NFC_CONFIG_TOKEN NDEF " + uuid) 92 if "FAIL" in ret: 93 return None 94 return ret.rstrip().decode("hex") 95 96 97def wpas_get_password_token(): 98 wpas = wpas_connect() 99 if (wpas == None): 100 return None 101 ret = wpas.request("WPS_NFC_TOKEN NDEF") 102 if "FAIL" in ret: 103 return None 104 return ret.rstrip().decode("hex") 105 106def wpas_get_handover_req(): 107 wpas = wpas_connect() 108 if (wpas == None): 109 return None 110 ret = wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR") 111 if "FAIL" in ret: 112 return None 113 return ret.rstrip().decode("hex") 114 115 116def wpas_get_handover_sel(uuid): 117 wpas = wpas_connect() 118 if (wpas == None): 119 return None 120 if uuid is None: 121 res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR").rstrip() 122 else: 123 res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR " + uuid).rstrip() 124 if "FAIL" in res: 125 return None 126 return res.decode("hex") 127 128 129def wpas_report_handover(req, sel, type): 130 wpas = wpas_connect() 131 if (wpas == None): 132 return None 133 return wpas.request("NFC_REPORT_HANDOVER " + type + " WPS " + 134 str(req).encode("hex") + " " + 135 str(sel).encode("hex")) 136 137 138class HandoverServer(nfc.handover.HandoverServer): 139 def __init__(self, llc): 140 super(HandoverServer, self).__init__(llc) 141 self.sent_carrier = None 142 self.ho_server_processing = False 143 self.success = False 144 145 # override to avoid parser error in request/response.pretty() in nfcpy 146 # due to new WSC handover format 147 def _process_request(self, request): 148 summary("received handover request {}".format(request.type)) 149 response = nfc.ndef.Message("\xd1\x02\x01Hs\x12") 150 if not request.type == 'urn:nfc:wkt:Hr': 151 summary("not a handover request") 152 else: 153 try: 154 request = nfc.ndef.HandoverRequestMessage(request) 155 except nfc.ndef.DecodeError as e: 156 summary("error decoding 'Hr' message: {}".format(e)) 157 else: 158 response = self.process_request(request) 159 summary("send handover response {}".format(response.type)) 160 return response 161 162 def process_request(self, request): 163 self.ho_server_processing = True 164 summary("HandoverServer - request received") 165 try: 166 print("Parsed handover request: " + request.pretty()) 167 except Exception as e: 168 print(e) 169 170 sel = nfc.ndef.HandoverSelectMessage(version="1.2") 171 172 for carrier in request.carriers: 173 print("Remote carrier type: " + carrier.type) 174 if carrier.type == "application/vnd.wfa.wsc": 175 summary("WPS carrier type match - add WPS carrier record") 176 data = wpas_get_handover_sel(self.uuid) 177 if data is None: 178 summary("Could not get handover select carrier record from wpa_supplicant") 179 continue 180 print("Handover select carrier record from wpa_supplicant:") 181 print(data.encode("hex")) 182 self.sent_carrier = data 183 if "OK" in wpas_report_handover(carrier.record, self.sent_carrier, "RESP"): 184 success_report("Handover reported successfully (responder)") 185 else: 186 summary("Handover report rejected (responder)") 187 188 message = nfc.ndef.Message(data); 189 sel.add_carrier(message[0], "active", message[1:]) 190 191 print("Handover select:") 192 try: 193 print(sel.pretty()) 194 except Exception as e: 195 print(e) 196 print(str(sel).encode("hex")) 197 198 summary("Sending handover select") 199 self.success = True 200 return sel 201 202 203def wps_handover_init(llc): 204 summary("Trying to initiate WPS handover") 205 206 data = wpas_get_handover_req() 207 if (data == None): 208 summary("Could not get handover request carrier record from wpa_supplicant") 209 return 210 print("Handover request carrier record from wpa_supplicant: " + data.encode("hex")) 211 212 message = nfc.ndef.HandoverRequestMessage(version="1.2") 213 message.nonce = random.randint(0, 0xffff) 214 datamsg = nfc.ndef.Message(data) 215 message.add_carrier(datamsg[0], "active", datamsg[1:]) 216 217 print("Handover request:") 218 try: 219 print(message.pretty()) 220 except Exception as e: 221 print(e) 222 print(str(message).encode("hex")) 223 224 client = nfc.handover.HandoverClient(llc) 225 try: 226 summary("Trying to initiate NFC connection handover") 227 client.connect() 228 summary("Connected for handover") 229 except nfc.llcp.ConnectRefused: 230 summary("Handover connection refused") 231 client.close() 232 return 233 except Exception as e: 234 summary("Other exception: " + str(e)) 235 client.close() 236 return 237 238 summary("Sending handover request") 239 240 if not client.send(message): 241 summary("Failed to send handover request") 242 client.close() 243 return 244 245 summary("Receiving handover response") 246 message = client._recv() 247 if message is None: 248 summary("No response received") 249 client.close() 250 return 251 if message.type != "urn:nfc:wkt:Hs": 252 summary("Response was not Hs - received: " + message.type) 253 client.close() 254 return 255 256 print("Received message") 257 try: 258 print(message.pretty()) 259 except Exception as e: 260 print(e) 261 print(str(message).encode("hex")) 262 message = nfc.ndef.HandoverSelectMessage(message) 263 summary("Handover select received") 264 try: 265 print(message.pretty()) 266 except Exception as e: 267 print(e) 268 269 for carrier in message.carriers: 270 print("Remote carrier type: " + carrier.type) 271 if carrier.type == "application/vnd.wfa.wsc": 272 print("WPS carrier type match - send to wpa_supplicant") 273 if "OK" in wpas_report_handover(data, carrier.record, "INIT"): 274 success_report("Handover reported successfully (initiator)") 275 else: 276 summary("Handover report rejected (initiator)") 277 # nfcpy does not support the new format.. 278 #wifi = nfc.ndef.WifiConfigRecord(carrier.record) 279 #print wifi.pretty() 280 281 print("Remove peer") 282 client.close() 283 print("Done with handover") 284 global only_one 285 if only_one: 286 global continue_loop 287 continue_loop = False 288 289 global no_wait 290 if no_wait: 291 print("Trying to exit..") 292 global terminate_now 293 terminate_now = True 294 295def wps_tag_read(tag, wait_remove=True): 296 success = False 297 if len(tag.ndef.message): 298 for record in tag.ndef.message: 299 print("record type " + record.type) 300 if record.type == "application/vnd.wfa.wsc": 301 summary("WPS tag - send to wpa_supplicant") 302 success = wpas_tag_read(tag.ndef.message) 303 break 304 else: 305 summary("Empty tag") 306 307 if success: 308 success_report("Tag read succeeded") 309 310 if wait_remove: 311 print("Remove tag") 312 while tag.is_present: 313 time.sleep(0.1) 314 315 return success 316 317 318def rdwr_connected_write(tag): 319 summary("Tag found - writing - " + str(tag)) 320 global write_data 321 tag.ndef.message = str(write_data) 322 success_report("Tag write succeeded") 323 print("Done - remove tag") 324 global only_one 325 if only_one: 326 global continue_loop 327 continue_loop = False 328 global write_wait_remove 329 while write_wait_remove and tag.is_present: 330 time.sleep(0.1) 331 332def wps_write_config_tag(clf, id=None, wait_remove=True): 333 print("Write WPS config token") 334 global write_data, write_wait_remove 335 write_wait_remove = wait_remove 336 write_data = wpas_get_config_token(id) 337 if write_data == None: 338 print("Could not get WPS config token from wpa_supplicant") 339 sys.exit(1) 340 return 341 print("Touch an NFC tag") 342 clf.connect(rdwr={'on-connect': rdwr_connected_write}) 343 344 345def wps_write_er_config_tag(clf, uuid, wait_remove=True): 346 print("Write WPS ER config token") 347 global write_data, write_wait_remove 348 write_wait_remove = wait_remove 349 write_data = wpas_get_er_config_token(uuid) 350 if write_data == None: 351 print("Could not get WPS config token from wpa_supplicant") 352 return 353 354 print("Touch an NFC tag") 355 clf.connect(rdwr={'on-connect': rdwr_connected_write}) 356 357 358def wps_write_password_tag(clf, wait_remove=True): 359 print("Write WPS password token") 360 global write_data, write_wait_remove 361 write_wait_remove = wait_remove 362 write_data = wpas_get_password_token() 363 if write_data == None: 364 print("Could not get WPS password token from wpa_supplicant") 365 return 366 367 print("Touch an NFC tag") 368 clf.connect(rdwr={'on-connect': rdwr_connected_write}) 369 370 371def rdwr_connected(tag): 372 global only_one, no_wait 373 summary("Tag connected: " + str(tag)) 374 375 if tag.ndef: 376 print("NDEF tag: " + tag.type) 377 try: 378 print(tag.ndef.message.pretty()) 379 except Exception as e: 380 print(e) 381 success = wps_tag_read(tag, not only_one) 382 if only_one and success: 383 global continue_loop 384 continue_loop = False 385 else: 386 summary("Not an NDEF tag - remove tag") 387 return True 388 389 return not no_wait 390 391 392def llcp_worker(llc): 393 global arg_uuid 394 if arg_uuid is None: 395 wps_handover_init(llc) 396 print("Exiting llcp_worker thread") 397 return 398 399 global srv 400 global wait_connection 401 while not wait_connection and srv.sent_carrier is None: 402 if srv.ho_server_processing: 403 time.sleep(0.025) 404 405def llcp_startup(clf, llc): 406 global arg_uuid 407 if arg_uuid: 408 print("Start LLCP server") 409 global srv 410 srv = HandoverServer(llc) 411 if arg_uuid is "ap": 412 print("Trying to handle WPS handover") 413 srv.uuid = None 414 else: 415 print("Trying to handle WPS handover with AP " + arg_uuid) 416 srv.uuid = arg_uuid 417 return llc 418 419def llcp_connected(llc): 420 print("P2P LLCP connected") 421 global wait_connection 422 wait_connection = False 423 global arg_uuid 424 if arg_uuid: 425 global srv 426 srv.start() 427 else: 428 threading.Thread(target=llcp_worker, args=(llc,)).start() 429 print("llcp_connected returning") 430 return True 431 432 433def terminate_loop(): 434 global terminate_now 435 return terminate_now 436 437def main(): 438 clf = nfc.ContactlessFrontend() 439 440 parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for WPS NFC operations') 441 parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO, 442 action='store_const', dest='loglevel', 443 help='verbose debug output') 444 parser.add_argument('-q', const=logging.WARNING, action='store_const', 445 dest='loglevel', help='be quiet') 446 parser.add_argument('--only-one', '-1', action='store_true', 447 help='run only one operation and exit') 448 parser.add_argument('--no-wait', action='store_true', 449 help='do not wait for tag to be removed before exiting') 450 parser.add_argument('--uuid', 451 help='UUID of an AP (used for WPS ER operations)') 452 parser.add_argument('--id', 453 help='network id (used for WPS ER operations)') 454 parser.add_argument('--summary', 455 help='summary file for writing status updates') 456 parser.add_argument('--success', 457 help='success file for writing success update') 458 parser.add_argument('command', choices=['write-config', 459 'write-er-config', 460 'write-password'], 461 nargs='?') 462 args = parser.parse_args() 463 464 global arg_uuid 465 arg_uuid = args.uuid 466 467 global only_one 468 only_one = args.only_one 469 470 global no_wait 471 no_wait = args.no_wait 472 473 if args.summary: 474 global summary_file 475 summary_file = args.summary 476 477 if args.success: 478 global success_file 479 success_file = args.success 480 481 logging.basicConfig(level=args.loglevel) 482 483 try: 484 if not clf.open("usb"): 485 print("Could not open connection with an NFC device") 486 raise SystemExit 487 488 if args.command == "write-config": 489 wps_write_config_tag(clf, id=args.id, wait_remove=not args.no_wait) 490 raise SystemExit 491 492 if args.command == "write-er-config": 493 wps_write_er_config_tag(clf, args.uuid, wait_remove=not args.no_wait) 494 raise SystemExit 495 496 if args.command == "write-password": 497 wps_write_password_tag(clf, wait_remove=not args.no_wait) 498 raise SystemExit 499 500 global continue_loop 501 while continue_loop: 502 print("Waiting for a tag or peer to be touched") 503 wait_connection = True 504 try: 505 if not clf.connect(rdwr={'on-connect': rdwr_connected}, 506 llcp={'on-startup': llcp_startup, 507 'on-connect': llcp_connected}, 508 terminate=terminate_loop): 509 break 510 except Exception as e: 511 print("clf.connect failed") 512 513 global srv 514 if only_one and srv and srv.success: 515 raise SystemExit 516 517 except KeyboardInterrupt: 518 raise SystemExit 519 finally: 520 clf.close() 521 522 raise SystemExit 523 524if __name__ == '__main__': 525 main() 526