1 #!/usr/bin/python3 2 # 3 # Example nfcpy to wpa_supplicant wrapper for DPP NFC operations 4 # Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi> 5 # Copyright (c) 2019-2020, The Linux Foundation 6 # 7 # This software may be distributed under the terms of the BSD license. 8 # See README for more details. 9 10 import binascii 11 import errno 12 import os 13 import struct 14 import sys 15 import time 16 import threading 17 import argparse 18 19 import nfc 20 import ndef 21 22 import logging 23 24 scriptsdir = os.path.dirname(os.path.realpath(sys.modules[__name__].__file__)) 25 sys.path.append(os.path.join(scriptsdir, '..', '..', 'wpaspy')) 26 import wpaspy 27 28 wpas_ctrl = '/var/run/wpa_supplicant' 29 ifname = None 30 init_on_touch = False 31 in_raw_mode = False 32 prev_tcgetattr = 0 33 no_input = False 34 continue_loop = True 35 terminate_now = False 36 summary_file = None 37 success_file = None 38 netrole = None 39 operation_success = False 40 mutex = threading.Lock() 41 42 C_NORMAL = '\033[0m' 43 C_RED = '\033[91m' 44 C_GREEN = '\033[92m' 45 C_YELLOW = '\033[93m' 46 C_BLUE = '\033[94m' 47 C_MAGENTA = '\033[95m' 48 C_CYAN = '\033[96m' 49 50 def summary(txt, color=None): 51 with mutex: 52 if color: 53 print(color + txt + C_NORMAL) 54 else: 55 print(txt) 56 if summary_file: 57 with open(summary_file, 'a') as f: 58 f.write(txt + "\n") 59 60 def success_report(txt): 61 summary(txt) 62 if success_file: 63 with open(success_file, 'a') as f: 64 f.write(txt + "\n") 65 66 def wpas_connect(): 67 ifaces = [] 68 if os.path.isdir(wpas_ctrl): 69 try: 70 ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)] 71 except OSError as error: 72 summary("Could not find wpa_supplicant: %s", str(error)) 73 return None 74 75 if len(ifaces) < 1: 76 summary("No wpa_supplicant control interface found") 77 return None 78 79 for ctrl in ifaces: 80 if ifname and ifname not in ctrl: 81 continue 82 if os.path.basename(ctrl).startswith("p2p-dev-"): 83 # skip P2P management interface 84 continue 85 try: 86 summary("Trying to use control interface " + ctrl) 87 wpas = wpaspy.Ctrl(ctrl) 88 return wpas 89 except Exception as e: 90 pass 91 summary("Could not connect to wpa_supplicant") 92 return None 93 94 def dpp_nfc_uri_process(uri): 95 wpas = wpas_connect() 96 if wpas is None: 97 return False 98 peer_id = wpas.request("DPP_NFC_URI " + uri) 99 if "FAIL" in peer_id: 100 summary("Could not parse DPP URI from NFC URI record", color=C_RED) 101 return False 102 peer_id = int(peer_id) 103 summary("peer_id=%d for URI from NFC Tag: %s" % (peer_id, uri)) 104 cmd = "DPP_AUTH_INIT peer=%d" % peer_id 105 global enrollee_only, configurator_only, config_params 106 if enrollee_only: 107 cmd += " role=enrollee" 108 elif configurator_only: 109 cmd += " role=configurator" 110 if config_params: 111 cmd += " " + config_params 112 summary("Initiate DPP authentication: " + cmd) 113 res = wpas.request(cmd) 114 if "OK" not in res: 115 summary("Failed to initiate DPP Authentication", color=C_RED) 116 return False 117 summary("DPP Authentication initiated") 118 return True 119 120 def dpp_hs_tag_read(record): 121 wpas = wpas_connect() 122 if wpas is None: 123 return False 124 summary(record) 125 if len(record.data) < 5: 126 summary("Too short DPP HS", color=C_RED) 127 return False 128 if record.data[0] != 0: 129 summary("Unexpected URI Identifier Code", color=C_RED) 130 return False 131 uribuf = record.data[1:] 132 try: 133 uri = uribuf.decode() 134 except: 135 summary("Invalid URI payload", color=C_RED) 136 return False 137 summary("URI: " + uri) 138 if not uri.startswith("DPP:"): 139 summary("Not a DPP URI", color=C_RED) 140 return False 141 return dpp_nfc_uri_process(uri) 142 143 def get_status(wpas, extra=None): 144 if extra: 145 extra = "-" + extra 146 else: 147 extra = "" 148 res = wpas.request("STATUS" + extra) 149 lines = res.splitlines() 150 vals = dict() 151 for l in lines: 152 try: 153 [name, value] = l.split('=', 1) 154 except ValueError: 155 summary("Ignore unexpected status line: %s" % l) 156 continue 157 vals[name] = value 158 return vals 159 160 def get_status_field(wpas, field, extra=None): 161 vals = get_status(wpas, extra) 162 if field in vals: 163 return vals[field] 164 return None 165 166 def own_addr(wpas): 167 addr = get_status_field(wpas, "address") 168 if addr is None: 169 addr = get_status_field(wpas, "bssid[0]") 170 return addr 171 172 def dpp_bootstrap_gen(wpas, type="qrcode", chan=None, mac=None, info=None, 173 curve=None, key=None): 174 cmd = "DPP_BOOTSTRAP_GEN type=" + type 175 if chan: 176 cmd += " chan=" + chan 177 if mac: 178 if mac is True: 179 mac = own_addr(wpas) 180 if mac is None: 181 summary("Could not determine local MAC address for bootstrap info") 182 else: 183 cmd += " mac=" + mac.replace(':', '') 184 if info: 185 cmd += " info=" + info 186 if curve: 187 cmd += " curve=" + curve 188 if key: 189 cmd += " key=" + key 190 res = wpas.request(cmd) 191 if "FAIL" in res: 192 raise Exception("Failed to generate bootstrapping info") 193 return int(res) 194 195 def dpp_start_listen(wpas, freq): 196 if get_status_field(wpas, "bssid[0]"): 197 summary("Own AP freq: %s MHz" % str(get_status_field(wpas, "freq"))) 198 if get_status_field(wpas, "beacon_set", extra="DRIVER") is None: 199 summary("Enable beaconing to have radio ready for RX") 200 wpas.request("DISABLE") 201 wpas.request("SET start_disabled 0") 202 wpas.request("ENABLE") 203 cmd = "DPP_LISTEN %d" % freq 204 global enrollee_only 205 global configurator_only 206 if enrollee_only: 207 cmd += " role=enrollee" 208 elif configurator_only: 209 cmd += " role=configurator" 210 global netrole 211 if netrole: 212 cmd += " netrole=" + netrole 213 summary(cmd) 214 res = wpas.request(cmd) 215 if "OK" not in res: 216 summary("Failed to start DPP listen", color=C_RED) 217 return False 218 return True 219 220 def wpas_get_nfc_uri(start_listen=True, pick_channel=False, chan_override=None): 221 listen_freq = 2412 222 wpas = wpas_connect() 223 if wpas is None: 224 return None 225 global own_id, chanlist 226 if chan_override: 227 chan = chan_override 228 else: 229 chan = chanlist 230 if chan and chan.startswith("81/"): 231 listen_freq = int(chan[3:].split(',')[0]) * 5 + 2407 232 if chan is None and get_status_field(wpas, "bssid[0]"): 233 freq = get_status_field(wpas, "freq") 234 if freq: 235 freq = int(freq) 236 if freq >= 2412 and freq <= 2462: 237 chan = "81/%d" % ((freq - 2407) / 5) 238 summary("Use current AP operating channel (%d MHz) as the URI channel list (%s)" % (freq, chan)) 239 listen_freq = freq 240 if chan is None and pick_channel: 241 chan = "81/6" 242 summary("Use channel 2437 MHz since no other preference provided") 243 listen_freq = 2437 244 own_id = dpp_bootstrap_gen(wpas, type="nfc-uri", chan=chan, mac=True) 245 res = wpas.request("DPP_BOOTSTRAP_GET_URI %d" % own_id).rstrip() 246 if "FAIL" in res: 247 return None 248 if start_listen: 249 if not dpp_start_listen(wpas, listen_freq): 250 raise Exception("Failed to start listen operation on %d MHz" % listen_freq) 251 return res 252 253 def wpas_report_handover_req(uri): 254 wpas = wpas_connect() 255 if wpas is None: 256 return None 257 global own_id 258 cmd = "DPP_NFC_HANDOVER_REQ own=%d uri=%s" % (own_id, uri) 259 return wpas.request(cmd) 260 261 def wpas_report_handover_sel(uri): 262 wpas = wpas_connect() 263 if wpas is None: 264 return None 265 global own_id 266 cmd = "DPP_NFC_HANDOVER_SEL own=%d uri=%s" % (own_id, uri) 267 return wpas.request(cmd) 268 269 def dpp_handover_client(handover, alt=False): 270 summary("About to start run_dpp_handover_client (alt=%s)" % str(alt)) 271 if alt: 272 handover.i_m_selector = False 273 run_dpp_handover_client(handover, alt) 274 summary("Done run_dpp_handover_client (alt=%s)" % str(alt)) 275 276 def run_client_alt(handover, alt): 277 if handover.start_client_alt and not alt: 278 handover.start_client_alt = False 279 summary("Try to send alternative handover request") 280 dpp_handover_client(handover, alt=True) 281 282 class HandoverClient(nfc.handover.HandoverClient): 283 def __init__(self, handover, llc): 284 super(HandoverClient, self).__init__(llc) 285 self.handover = handover 286 287 def recv_records(self, timeout=None): 288 msg = self.recv_octets(timeout) 289 if msg is None: 290 return None 291 records = list(ndef.message_decoder(msg, 'relax')) 292 if records and records[0].type == 'urn:nfc:wkt:Hs': 293 summary("Handover client received message '{0}'".format(records[0].type)) 294 return list(ndef.message_decoder(msg, 'relax')) 295 summary("Handover client received invalid message: %s" + binascii.hexlify(msg)) 296 return None 297 298 def recv_octets(self, timeout=None): 299 start = time.time() 300 msg = bytearray() 301 while True: 302 poll_timeout = 0.1 if timeout is None or timeout > 0.1 else timeout 303 if not self.socket.poll('recv', poll_timeout): 304 if timeout: 305 timeout -= time.time() - start 306 if timeout <= 0: 307 return None 308 start = time.time() 309 continue 310 try: 311 r = self.socket.recv() 312 if r is None: 313 return None 314 msg += r 315 except TypeError: 316 return b'' 317 try: 318 list(ndef.message_decoder(msg, 'strict', {})) 319 return bytes(msg) 320 except ndef.DecodeError: 321 if timeout: 322 timeout -= time.time() - start 323 if timeout <= 0: 324 return None 325 start = time.time() 326 continue 327 return None 328 329 def run_dpp_handover_client(handover, alt=False): 330 chan_override = None 331 if alt: 332 chan_override = handover.altchanlist 333 handover.alt_proposal_used = True 334 global test_uri, test_alt_uri 335 if test_uri: 336 summary("TEST MODE: Using specified URI (alt=%s)" % str(alt)) 337 uri = test_alt_uri if alt else test_uri 338 else: 339 uri = wpas_get_nfc_uri(start_listen=False, chan_override=chan_override) 340 if uri is None: 341 summary("Cannot start handover client - no bootstrap URI available", 342 color=C_RED) 343 return 344 handover.my_uri = uri 345 uri = ndef.UriRecord(uri) 346 summary("NFC URI record for DPP: " + str(uri)) 347 carrier = ndef.Record('application/vnd.wfa.dpp', 'A', uri.data) 348 global test_crn 349 if test_crn: 350 prev, = struct.unpack('>H', test_crn) 351 summary("TEST MODE: Use specified crn %d" % prev) 352 crn = test_crn 353 test_crn = struct.pack('>H', prev + 0x10) 354 else: 355 crn = os.urandom(2) 356 hr = ndef.HandoverRequestRecord(version="1.4", crn=crn) 357 hr.add_alternative_carrier('active', carrier.name) 358 message = [hr, carrier] 359 summary("NFC Handover Request message for DPP: " + str(message)) 360 361 if handover.peer_crn is not None and not alt: 362 summary("NFC handover request from peer was already received - do not send own[1]") 363 return 364 if handover.client: 365 summary("Use already started handover client") 366 client = handover.client 367 else: 368 summary("Start handover client") 369 client = HandoverClient(handover, handover.llc) 370 try: 371 summary("Trying to initiate NFC connection handover") 372 client.connect() 373 summary("Connected for handover") 374 except nfc.llcp.ConnectRefused: 375 summary("Handover connection refused") 376 client.close() 377 return 378 except Exception as e: 379 summary("Other exception: " + str(e)) 380 client.close() 381 return 382 handover.client = client 383 384 if handover.peer_crn is not None and not alt: 385 summary("NFC handover request from peer was already received - do not send own[2] (except alt)") 386 run_client_alt(handover, alt) 387 return 388 389 summary("Sending handover request") 390 391 handover.my_crn_ready = True 392 393 if not client.send_records(message): 394 handover.my_crn_ready = False 395 summary("Failed to send handover request", color=C_RED) 396 run_client_alt(handover, alt) 397 return 398 399 handover.my_crn, = struct.unpack('>H', crn) 400 401 summary("Receiving handover response") 402 try: 403 start = time.time() 404 message = client.recv_records(timeout=3.0) 405 end = time.time() 406 summary("Received {} record(s) in {} seconds".format(len(message) if message is not None else -1, end - start)) 407 except Exception as e: 408 # This is fine if we are the handover selector 409 if handover.hs_sent: 410 summary("Client receive failed as expected since I'm the handover server: %s" % str(e)) 411 elif handover.alt_proposal_used and not alt: 412 summary("Client received failed for initial proposal as expected since alternative proposal was also used: %s" % str(e)) 413 else: 414 summary("Client receive failed: %s" % str(e), color=C_RED) 415 message = None 416 if message is None: 417 if handover.hs_sent: 418 summary("No response received as expected since I'm the handover server") 419 elif handover.alt_proposal_used and not alt: 420 summary("No response received for initial proposal as expected since alternative proposal was also used") 421 elif handover.try_own and not alt: 422 summary("No response received for initial proposal as expected since alternative proposal will also be sent") 423 else: 424 summary("No response received", color=C_RED) 425 run_client_alt(handover, alt) 426 return 427 summary("Received message: " + str(message)) 428 if len(message) < 1 or \ 429 not isinstance(message[0], ndef.HandoverSelectRecord): 430 summary("Response was not Hs - received: " + message.type) 431 return 432 433 summary("Received handover select message") 434 summary("alternative carriers: " + str(message[0].alternative_carriers)) 435 if handover.i_m_selector: 436 summary("Ignore the received select since I'm the handover selector") 437 run_client_alt(handover, alt) 438 return 439 440 if handover.alt_proposal_used and not alt: 441 summary("Ignore received handover select for the initial proposal since alternative proposal was sent") 442 client.close() 443 return 444 445 dpp_found = False 446 for carrier in message: 447 if isinstance(carrier, ndef.HandoverSelectRecord): 448 continue 449 summary("Remote carrier type: " + carrier.type) 450 if carrier.type == "application/vnd.wfa.dpp": 451 if len(carrier.data) == 0 or carrier.data[0] != 0: 452 summary("URI Identifier Code 'None' not seen", color=C_RED) 453 continue 454 summary("DPP carrier type match - send to wpa_supplicant") 455 dpp_found = True 456 uri = carrier.data[1:].decode("utf-8") 457 summary("DPP URI: " + uri) 458 handover.peer_uri = uri 459 if test_uri: 460 summary("TEST MODE: Fake processing") 461 break 462 res = wpas_report_handover_sel(uri) 463 if res is None or "FAIL" in res: 464 summary("DPP handover report rejected", color=C_RED) 465 break 466 467 success_report("DPP handover reported successfully (initiator)") 468 summary("peer_id=" + res) 469 peer_id = int(res) 470 wpas = wpas_connect() 471 if wpas is None: 472 break 473 474 global enrollee_only 475 global config_params 476 if enrollee_only: 477 extra = " role=enrollee" 478 elif config_params: 479 extra = " role=configurator " + config_params 480 else: 481 # TODO: Single Configurator instance 482 res = wpas.request("DPP_CONFIGURATOR_ADD") 483 if "FAIL" in res: 484 summary("Failed to initiate Configurator", color=C_RED) 485 break 486 conf_id = int(res) 487 extra = " conf=sta-dpp configurator=%d" % conf_id 488 global own_id 489 summary("Initiate DPP authentication") 490 cmd = "DPP_AUTH_INIT peer=%d own=%d" % (peer_id, own_id) 491 cmd += extra 492 res = wpas.request(cmd) 493 if "FAIL" in res: 494 summary("Failed to initiate DPP authentication", color=C_RED) 495 break 496 497 if not dpp_found and handover.no_alt_proposal: 498 summary("DPP carrier not seen in response - do not allow alternative proposal anymore") 499 elif not dpp_found: 500 summary("DPP carrier not seen in response - allow peer to initiate a new handover with different parameters") 501 handover.alt_proposal = True 502 handover.my_crn_ready = False 503 handover.my_crn = None 504 handover.peer_crn = None 505 handover.hs_sent = False 506 summary("Returning from dpp_handover_client") 507 return 508 509 summary("Remove peer") 510 handover.close() 511 summary("Done with handover") 512 global only_one 513 if only_one: 514 print("only_one -> stop loop") 515 global continue_loop 516 continue_loop = False 517 518 global no_wait 519 if no_wait or only_one: 520 summary("Trying to exit..") 521 global terminate_now 522 terminate_now = True 523 524 summary("Returning from dpp_handover_client") 525 526 class HandoverServer(nfc.handover.HandoverServer): 527 def __init__(self, handover, llc): 528 super(HandoverServer, self).__init__(llc) 529 self.sent_carrier = None 530 self.ho_server_processing = False 531 self.success = False 532 self.llc = llc 533 self.handover = handover 534 535 def serve(self, socket): 536 peer_sap = socket.getpeername() 537 summary("Serving handover client on remote sap {0}".format(peer_sap)) 538 send_miu = socket.getsockopt(nfc.llcp.SO_SNDMIU) 539 try: 540 while socket.poll("recv"): 541 req = bytearray() 542 while socket.poll("recv"): 543 r = socket.recv() 544 if r is None: 545 return None 546 summary("Received %d octets" % len(r)) 547 req += r 548 if len(req) == 0: 549 continue 550 try: 551 list(ndef.message_decoder(req, 'strict', {})) 552 except ndef.DecodeError: 553 continue 554 summary("Full message received") 555 resp = self._process_request_data(req) 556 if resp is None or len(resp) == 0: 557 summary("No handover select to send out - wait for a possible alternative handover request") 558 handover.alt_proposal = True 559 req = bytearray() 560 continue 561 562 for offset in range(0, len(resp), send_miu): 563 if not socket.send(resp[offset:offset + send_miu]): 564 summary("Failed to send handover select - connection closed") 565 return 566 summary("Sent out full handover select") 567 if handover.terminate_on_hs_send_completion: 568 handover.delayed_exit() 569 570 except nfc.llcp.Error as e: 571 global terminate_now 572 summary("HandoverServer exception: %s" % e, 573 color=None if e.errno == errno.EPIPE or terminate_now else C_RED) 574 finally: 575 socket.close() 576 summary("Handover serve thread exiting") 577 578 def process_handover_request_message(self, records): 579 handover = self.handover 580 self.ho_server_processing = True 581 global in_raw_mode 582 was_in_raw_mode = in_raw_mode 583 clear_raw_mode() 584 if was_in_raw_mode: 585 print("\n") 586 summary("HandoverServer - request received: " + str(records)) 587 588 for carrier in records: 589 if not isinstance(carrier, ndef.HandoverRequestRecord): 590 continue 591 if carrier.collision_resolution_number: 592 handover.peer_crn = carrier.collision_resolution_number 593 summary("peer_crn: %d" % handover.peer_crn) 594 595 if handover.my_crn is None and handover.my_crn_ready: 596 summary("Still trying to send own handover request - wait a moment to see if that succeeds before checking crn values") 597 for i in range(10): 598 if handover.my_crn is not None: 599 break 600 time.sleep(0.01) 601 if handover.my_crn is not None: 602 summary("my_crn: %d" % handover.my_crn) 603 604 if handover.my_crn is not None and handover.peer_crn is not None: 605 if handover.my_crn == handover.peer_crn: 606 summary("Same crn used - automatic collision resolution failed") 607 # TODO: Should generate a new Handover Request message 608 return '' 609 if ((handover.my_crn & 1) == (handover.peer_crn & 1) and \ 610 handover.my_crn > handover.peer_crn) or \ 611 ((handover.my_crn & 1) != (handover.peer_crn & 1) and \ 612 handover.my_crn < handover.peer_crn): 613 summary("I'm the Handover Selector Device") 614 handover.i_m_selector = True 615 else: 616 summary("Peer is the Handover Selector device") 617 summary("Ignore the received request.") 618 return '' 619 620 hs = ndef.HandoverSelectRecord('1.4') 621 sel = [hs] 622 623 found = False 624 625 for carrier in records: 626 if isinstance(carrier, ndef.HandoverRequestRecord): 627 continue 628 summary("Remote carrier type: " + carrier.type) 629 if carrier.type == "application/vnd.wfa.dpp": 630 summary("DPP carrier type match - add DPP carrier record") 631 if len(carrier.data) == 0 or carrier.data[0] != 0: 632 summary("URI Identifier Code 'None' not seen", color=C_RED) 633 continue 634 uri = carrier.data[1:].decode("utf-8") 635 summary("Received DPP URI: " + uri) 636 637 global test_uri, test_alt_uri 638 if test_uri: 639 summary("TEST MODE: Using specified URI") 640 data = test_sel_uri if test_sel_uri else test_uri 641 elif handover.alt_proposal and handover.altchanlist: 642 summary("Use alternative channel list while processing alternative proposal from peer") 643 data = wpas_get_nfc_uri(start_listen=False, 644 chan_override=handover.altchanlist, 645 pick_channel=True) 646 else: 647 data = wpas_get_nfc_uri(start_listen=False, 648 pick_channel=True) 649 summary("Own URI (pre-processing): %s" % data) 650 651 if test_uri: 652 summary("TEST MODE: Fake processing") 653 res = "OK" 654 data += " [%s]" % uri 655 else: 656 res = wpas_report_handover_req(uri) 657 if res is None or "FAIL" in res: 658 summary("DPP handover request processing failed", 659 color=C_RED) 660 if handover.altchanlist: 661 data = wpas_get_nfc_uri(start_listen=False, 662 chan_override=handover.altchanlist) 663 summary("Own URI (try another channel list): %s" % data) 664 continue 665 666 if test_alt_uri: 667 summary("TEST MODE: Reject initial proposal") 668 continue 669 670 found = True 671 672 if not test_uri: 673 wpas = wpas_connect() 674 if wpas is None: 675 continue 676 global own_id 677 data = wpas.request("DPP_BOOTSTRAP_GET_URI %d" % own_id).rstrip() 678 if "FAIL" in data: 679 continue 680 summary("Own URI (post-processing): %s" % data) 681 handover.my_uri = data 682 handover.peer_uri = uri 683 uri = ndef.UriRecord(data) 684 summary("Own bootstrapping NFC URI record: " + str(uri)) 685 686 if not test_uri: 687 info = wpas.request("DPP_BOOTSTRAP_INFO %d" % own_id) 688 freq = None 689 for line in info.splitlines(): 690 if line.startswith("use_freq="): 691 freq = int(line.split('=')[1]) 692 if freq is None or freq == 0: 693 summary("No channel negotiated over NFC - use channel 6") 694 freq = 2437 695 else: 696 summary("Negotiated channel: %d MHz" % freq) 697 if not dpp_start_listen(wpas, freq): 698 break 699 700 carrier = ndef.Record('application/vnd.wfa.dpp', 'A', uri.data) 701 summary("Own DPP carrier record: " + str(carrier)) 702 hs.add_alternative_carrier('active', carrier.name) 703 sel = [hs, carrier] 704 break 705 706 summary("Sending handover select: " + str(sel)) 707 if found: 708 summary("Handover completed successfully") 709 handover.terminate_on_hs_send_completion = True 710 self.success = True 711 handover.hs_sent = True 712 handover.i_m_selector = True 713 elif handover.no_alt_proposal: 714 summary("Do not try alternative proposal anymore - handover failed", 715 color=C_RED) 716 handover.hs_sent = True 717 else: 718 summary("Try to initiate with alternative parameters") 719 handover.try_own = True 720 handover.hs_sent = False 721 handover.no_alt_proposal = True 722 if handover.client_thread: 723 handover.start_client_alt = True 724 else: 725 handover.client_thread = threading.Thread(target=llcp_worker, 726 args=(self.llc, True)) 727 handover.client_thread.start() 728 return sel 729 730 def clear_raw_mode(): 731 import sys, tty, termios 732 global prev_tcgetattr, in_raw_mode 733 if not in_raw_mode: 734 return 735 fd = sys.stdin.fileno() 736 termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr) 737 in_raw_mode = False 738 739 def getch(): 740 import sys, tty, termios, select 741 global prev_tcgetattr, in_raw_mode 742 fd = sys.stdin.fileno() 743 prev_tcgetattr = termios.tcgetattr(fd) 744 ch = None 745 try: 746 tty.setraw(fd) 747 in_raw_mode = True 748 [i, o, e] = select.select([fd], [], [], 0.05) 749 if i: 750 ch = sys.stdin.read(1) 751 finally: 752 termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr) 753 in_raw_mode = False 754 return ch 755 756 def dpp_tag_read(tag): 757 success = False 758 for record in tag.ndef.records: 759 summary(record) 760 summary("record type " + record.type) 761 if record.type == "application/vnd.wfa.dpp": 762 summary("DPP HS tag - send to wpa_supplicant") 763 success = dpp_hs_tag_read(record) 764 break 765 if isinstance(record, ndef.UriRecord): 766 summary("URI record: uri=" + record.uri) 767 summary("URI record: iri=" + record.iri) 768 if record.iri.startswith("DPP:"): 769 summary("DPP URI") 770 if not dpp_nfc_uri_process(record.iri): 771 break 772 success = True 773 else: 774 summary("Ignore unknown URI") 775 break 776 777 if success: 778 success_report("Tag read succeeded") 779 780 return success 781 782 def rdwr_connected_write_tag(tag): 783 summary("Tag found - writing - " + str(tag)) 784 if not tag.ndef: 785 summary("Not a formatted NDEF tag", color=C_RED) 786 return 787 if not tag.ndef.is_writeable: 788 summary("Not a writable tag", color=C_RED) 789 return 790 global dpp_tag_data 791 if tag.ndef.capacity < len(dpp_tag_data): 792 summary("Not enough room for the message") 793 return 794 try: 795 tag.ndef.records = dpp_tag_data 796 except ValueError as e: 797 summary("Writing the tag failed: %s" % str(e), color=C_RED) 798 return 799 success_report("Tag write succeeded") 800 summary("Tag writing completed - remove tag", color=C_GREEN) 801 global only_one, operation_success 802 operation_success = True 803 if only_one: 804 global continue_loop 805 continue_loop = False 806 global dpp_sel_wait_remove 807 return dpp_sel_wait_remove 808 809 def write_nfc_uri(clf, wait_remove=True): 810 summary("Write NFC URI record") 811 data = wpas_get_nfc_uri() 812 if data is None: 813 summary("Could not get NFC URI from wpa_supplicant", color=C_RED) 814 return 815 816 global dpp_sel_wait_remove 817 dpp_sel_wait_remove = wait_remove 818 summary("URI: %s" % data) 819 uri = ndef.UriRecord(data) 820 summary(uri) 821 822 summary("Touch an NFC tag to write URI record", color=C_CYAN) 823 global dpp_tag_data 824 dpp_tag_data = [uri] 825 clf.connect(rdwr={'on-connect': rdwr_connected_write_tag}) 826 827 def write_nfc_hs(clf, wait_remove=True): 828 summary("Write NFC Handover Select record on a tag") 829 data = wpas_get_nfc_uri() 830 if data is None: 831 summary("Could not get NFC URI from wpa_supplicant", color=C_RED) 832 return 833 834 global dpp_sel_wait_remove 835 dpp_sel_wait_remove = wait_remove 836 summary("URI: %s" % data) 837 uri = ndef.UriRecord(data) 838 summary(uri) 839 carrier = ndef.Record('application/vnd.wfa.dpp', 'A', uri.data) 840 hs = ndef.HandoverSelectRecord('1.4') 841 hs.add_alternative_carrier('active', carrier.name) 842 summary(hs) 843 summary(carrier) 844 845 summary("Touch an NFC tag to write HS record", color=C_CYAN) 846 global dpp_tag_data 847 dpp_tag_data = [hs, carrier] 848 summary(dpp_tag_data) 849 clf.connect(rdwr={'on-connect': rdwr_connected_write_tag}) 850 851 def rdwr_connected(tag): 852 global only_one, no_wait 853 summary("Tag connected: " + str(tag)) 854 855 if tag.ndef: 856 summary("NDEF tag: " + tag.type) 857 summary(tag.ndef.records) 858 success = dpp_tag_read(tag) 859 if only_one and success: 860 global continue_loop 861 continue_loop = False 862 else: 863 summary("Not an NDEF tag - remove tag", color=C_RED) 864 return True 865 866 return not no_wait 867 868 def llcp_worker(llc, try_alt): 869 global handover 870 print("Start of llcp_worker()") 871 if try_alt: 872 summary("Starting handover client (try_alt)") 873 dpp_handover_client(handover, alt=True) 874 summary("Exiting llcp_worker thread (try_alt)") 875 return 876 global init_on_touch 877 if init_on_touch: 878 summary("Starting handover client (init_on_touch)") 879 dpp_handover_client(handover) 880 summary("llcp_worker init_on_touch processing completed: try_own={} hs_sent={} no_alt_proposal={} start_client_alt={}".format(handover.try_own, handover.hs_sent, handover.no_alt_proposal, handover.start_client_alt)) 881 if handover.start_client_alt and not handover.hs_sent: 882 summary("Try alternative handover request before exiting llcp_worker") 883 handover.start_client_alt = False 884 dpp_handover_client(handover, alt=True) 885 summary("Exiting llcp_worker thread (init_on_touch)") 886 return 887 888 global no_input 889 if no_input: 890 summary("Wait for handover to complete") 891 else: 892 print("Wait for handover to complete - press 'i' to initiate") 893 while not handover.wait_connection and handover.srv.sent_carrier is None: 894 if handover.try_own: 895 handover.try_own = False 896 summary("Try to initiate another handover with own parameters") 897 handover.my_crn_ready = False 898 handover.my_crn = None 899 handover.peer_crn = None 900 handover.hs_sent = False 901 dpp_handover_client(handover, alt=True) 902 summary("Exiting llcp_worker thread (retry with own parameters)") 903 return 904 if handover.srv.ho_server_processing: 905 time.sleep(0.025) 906 elif no_input: 907 time.sleep(0.5) 908 else: 909 res = getch() 910 if res != 'i': 911 continue 912 clear_raw_mode() 913 summary("Starting handover client") 914 dpp_handover_client(handover) 915 summary("Exiting llcp_worker thread (manual init)") 916 return 917 918 global in_raw_mode 919 was_in_raw_mode = in_raw_mode 920 clear_raw_mode() 921 if was_in_raw_mode: 922 print("\r") 923 summary("Exiting llcp_worker thread") 924 925 class ConnectionHandover(): 926 def __init__(self): 927 self.client = None 928 self.client_thread = None 929 self.reset() 930 self.exit_thread = None 931 932 def reset(self): 933 self.wait_connection = False 934 self.my_crn_ready = False 935 self.my_crn = None 936 self.peer_crn = None 937 self.hs_sent = False 938 self.no_alt_proposal = False 939 self.alt_proposal_used = False 940 self.i_m_selector = False 941 self.start_client_alt = False 942 self.terminate_on_hs_send_completion = False 943 self.try_own = False 944 self.my_uri = None 945 self.peer_uri = None 946 self.connected = False 947 self.alt_proposal = False 948 949 def start_handover_server(self, llc): 950 summary("Start handover server") 951 self.llc = llc 952 self.srv = HandoverServer(self, llc) 953 954 def close(self): 955 if self.client: 956 self.client.close() 957 self.client = None 958 959 def run_delayed_exit(self): 960 summary("Trying to exit (delayed)..") 961 time.sleep(0.25) 962 summary("Trying to exit (after wait)..") 963 global terminate_now 964 terminate_now = True 965 966 def delayed_exit(self): 967 global only_one 968 if only_one: 969 self.exit_thread = threading.Thread(target=self.run_delayed_exit) 970 self.exit_thread.start() 971 972 def llcp_startup(llc): 973 global handover 974 handover.start_handover_server(llc) 975 return llc 976 977 def llcp_connected(llc): 978 summary("P2P LLCP connected") 979 global handover 980 handover.connected = True 981 handover.srv.start() 982 if init_on_touch or not no_input: 983 handover.client_thread = threading.Thread(target=llcp_worker, 984 args=(llc, False)) 985 handover.client_thread.start() 986 return True 987 988 def llcp_release(llc): 989 summary("LLCP release") 990 global handover 991 handover.close() 992 return True 993 994 def terminate_loop(): 995 global terminate_now 996 return terminate_now 997 998 def main(): 999 clf = nfc.ContactlessFrontend() 1000 1001 parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for DPP NFC operations') 1002 parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO, 1003 action='store_const', dest='loglevel', 1004 help='verbose debug output') 1005 parser.add_argument('-q', const=logging.WARNING, action='store_const', 1006 dest='loglevel', help='be quiet') 1007 parser.add_argument('--only-one', '-1', action='store_true', 1008 help='run only one operation and exit') 1009 parser.add_argument('--init-on-touch', '-I', action='store_true', 1010 help='initiate handover on touch') 1011 parser.add_argument('--no-wait', action='store_true', 1012 help='do not wait for tag to be removed before exiting') 1013 parser.add_argument('--ifname', '-i', 1014 help='network interface name') 1015 parser.add_argument('--no-input', '-a', action='store_true', 1016 help='do not use stdout input to initiate handover') 1017 parser.add_argument('--tag-read-only', '-t', action='store_true', 1018 help='tag read only (do not allow connection handover)') 1019 parser.add_argument('--handover-only', action='store_true', 1020 help='connection handover only (do not allow tag read)') 1021 parser.add_argument('--enrollee', action='store_true', 1022 help='run as Enrollee-only') 1023 parser.add_argument('--configurator', action='store_true', 1024 help='run as Configurator-only') 1025 parser.add_argument('--config-params', default='', 1026 help='configurator parameters') 1027 parser.add_argument('--ctrl', default='/var/run/wpa_supplicant', 1028 help='wpa_supplicant/hostapd control interface') 1029 parser.add_argument('--summary', 1030 help='summary file for writing status updates') 1031 parser.add_argument('--success', 1032 help='success file for writing success update') 1033 parser.add_argument('--device', default='usb', help='NFC device to open') 1034 parser.add_argument('--chan', default=None, help='channel list') 1035 parser.add_argument('--altchan', default=None, help='alternative channel list') 1036 parser.add_argument('--netrole', default=None, help='netrole for Enrollee') 1037 parser.add_argument('--test-uri', default=None, 1038 help='test mode: initial URI') 1039 parser.add_argument('--test-alt-uri', default=None, 1040 help='test mode: alternative URI') 1041 parser.add_argument('--test-sel-uri', default=None, 1042 help='test mode: handover select URI') 1043 parser.add_argument('--test-crn', default=None, 1044 help='test mode: hardcoded crn') 1045 parser.add_argument('command', choices=['write-nfc-uri', 1046 'write-nfc-hs'], 1047 nargs='?') 1048 args = parser.parse_args() 1049 summary(args) 1050 1051 global handover 1052 handover = ConnectionHandover() 1053 1054 global only_one 1055 only_one = args.only_one 1056 1057 global no_wait 1058 no_wait = args.no_wait 1059 1060 global chanlist, netrole, test_uri, test_alt_uri, test_sel_uri 1061 global test_crn 1062 chanlist = args.chan 1063 handover.altchanlist = args.altchan 1064 netrole = args.netrole 1065 test_uri = args.test_uri 1066 test_alt_uri = args.test_alt_uri 1067 test_sel_uri = args.test_sel_uri 1068 if args.test_crn: 1069 test_crn = struct.pack('>H', int(args.test_crn)) 1070 else: 1071 test_crn = None 1072 1073 logging.basicConfig(level=args.loglevel) 1074 for l in ['nfc.clf.rcs380', 1075 'nfc.clf.transport', 1076 'nfc.clf.device', 1077 'nfc.clf.__init__', 1078 'nfc.llcp', 1079 'nfc.handover']: 1080 log = logging.getLogger(l) 1081 log.setLevel(args.loglevel) 1082 1083 global init_on_touch 1084 init_on_touch = args.init_on_touch 1085 1086 global enrollee_only 1087 enrollee_only = args.enrollee 1088 1089 global configurator_only 1090 configurator_only = args.configurator 1091 1092 global config_params 1093 config_params = args.config_params 1094 1095 if args.ifname: 1096 global ifname 1097 ifname = args.ifname 1098 summary("Selected ifname " + ifname) 1099 1100 if args.ctrl: 1101 global wpas_ctrl 1102 wpas_ctrl = args.ctrl 1103 1104 if args.summary: 1105 global summary_file 1106 summary_file = args.summary 1107 1108 if args.success: 1109 global success_file 1110 success_file = args.success 1111 1112 if args.no_input: 1113 global no_input 1114 no_input = True 1115 1116 clf = nfc.ContactlessFrontend() 1117 1118 try: 1119 if not clf.open(args.device): 1120 summary("Could not open connection with an NFC device", color=C_RED) 1121 raise SystemExit(1) 1122 1123 if args.command == "write-nfc-uri": 1124 write_nfc_uri(clf, wait_remove=not args.no_wait) 1125 if not operation_success: 1126 raise SystemExit(1) 1127 raise SystemExit 1128 1129 if args.command == "write-nfc-hs": 1130 write_nfc_hs(clf, wait_remove=not args.no_wait) 1131 if not operation_success: 1132 raise SystemExit(1) 1133 raise SystemExit 1134 1135 global continue_loop 1136 while continue_loop: 1137 global in_raw_mode 1138 was_in_raw_mode = in_raw_mode 1139 clear_raw_mode() 1140 if was_in_raw_mode: 1141 print("\r") 1142 if args.handover_only: 1143 summary("Waiting a peer to be touched", color=C_MAGENTA) 1144 elif args.tag_read_only: 1145 summary("Waiting for a tag to be touched", color=C_BLUE) 1146 else: 1147 summary("Waiting for a tag or peer to be touched", 1148 color=C_GREEN) 1149 handover.wait_connection = True 1150 try: 1151 if args.tag_read_only: 1152 if not clf.connect(rdwr={'on-connect': rdwr_connected}): 1153 break 1154 elif args.handover_only: 1155 if not clf.connect(llcp={'on-startup': llcp_startup, 1156 'on-connect': llcp_connected, 1157 'on-release': llcp_release}, 1158 terminate=terminate_loop): 1159 break 1160 else: 1161 if not clf.connect(rdwr={'on-connect': rdwr_connected}, 1162 llcp={'on-startup': llcp_startup, 1163 'on-connect': llcp_connected, 1164 'on-release': llcp_release}, 1165 terminate=terminate_loop): 1166 break 1167 except Exception as e: 1168 summary("clf.connect failed: " + str(e)) 1169 break 1170 1171 if only_one and handover.connected: 1172 role = "selector" if handover.i_m_selector else "requestor" 1173 summary("Connection handover result: I'm the %s" % role, 1174 color=C_YELLOW) 1175 if handover.peer_uri: 1176 summary("Peer URI: " + handover.peer_uri, color=C_YELLOW) 1177 if handover.my_uri: 1178 summary("My URI: " + handover.my_uri, color=C_YELLOW) 1179 if not (handover.peer_uri and handover.my_uri): 1180 summary("Negotiated connection handover failed", 1181 color=C_YELLOW) 1182 break 1183 1184 except KeyboardInterrupt: 1185 raise SystemExit 1186 finally: 1187 clf.close() 1188 1189 raise SystemExit 1190 1191 if __name__ == '__main__': 1192 main() 1193