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, 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 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, 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 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, 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 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, 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, 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