1 #!/usr/bin/python 2 # 3 # Example nfcpy to wpa_supplicant wrapper for WPS NFC operations 4 # Copyright (c) 2012-2013, Jouni Malinen <j (at] w1.fi> 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 12 import random 13 import threading 14 import argparse 15 16 import nfc 17 import nfc.ndef 18 import nfc.llcp 19 import nfc.handover 20 21 import logging 22 23 import wpaspy 24 25 wpas_ctrl = '/var/run/wpa_supplicant' 26 srv = None 27 continue_loop = True 28 terminate_now = False 29 summary_file = None 30 success_file = None 31 32 def summary(txt): 33 print txt 34 if summary_file: 35 with open(summary_file, 'a') as f: 36 f.write(txt + "\n") 37 38 def success_report(txt): 39 summary(txt) 40 if success_file: 41 with open(success_file, 'a') as f: 42 f.write(txt + "\n") 43 44 def 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, 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, e: 62 pass 63 return None 64 65 66 def 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 74 def 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 87 def 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 97 def 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 106 def 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 116 def 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 129 def 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 138 class 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, 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, 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 203 def 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, 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, 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, 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, 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 295 def 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 318 def 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 332 def 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 345 def 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 358 def 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 371 def 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, 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 392 def 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 405 def 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 419 def 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 433 def terminate_loop(): 434 global terminate_now 435 return terminate_now 436 437 def 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, 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 524 if __name__ == '__main__': 525 main() 526