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") 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") 386* return 387* 388* summary("Sending handover request") 389* 390* handover.my_crn_ready = True 391* 392* if not client.send_records(message): 393* handover.my_crn_ready = False 394* summary("Failed to send handover request", color=C_RED) 395* run_client_alt(handover, alt) 396* return 397* 398* handover.my_crn, = struct.unpack('>H', crn) 399* 400* summary("Receiving handover response") 401* try: 402* start = time.time() 403* message = client.recv_records(timeout=3.0) 404* end = time.time() 405* summary("Received {} record(s) in {} seconds".format(len(message) if message is not None else -1, end - start)) 406* except Exception as e: 407* # This is fine if we are the handover selector 408* if handover.hs_sent: 409* summary("Client receive failed as expected since I'm the handover server: %s" % str(e)) 410* elif handover.alt_proposal_used and not alt: 411* summary("Client received failed for initial proposal as expected since alternative proposal was also used: %s" % str(e)) 412* else: 413* summary("Client receive failed: %s" % str(e), color=C_RED) 414* message = None 415* if message is None: 416* if handover.hs_sent: 417* summary("No response received as expected since I'm the handover server") 418* elif handover.alt_proposal_used and not alt: 419* summary("No response received for initial proposal as expected since alternative proposal was also used") 420* elif handover.try_own and not alt: 421* summary("No response received for initial proposal as expected since alternative proposal will also be sent") 422* else: 423* summary("No response received", color=C_RED) 424* run_client_alt(handover, alt) 425* return 426* summary("Received message: " + str(message)) 427* if len(message) < 1 or \ 428* not isinstance(message[0], ndef.HandoverSelectRecord): 429* summary("Response was not Hs - received: " + message.type) 430* return 431* 432* summary("Received handover select message") 433* summary("alternative carriers: " + str(message[0].alternative_carriers)) 434* if handover.i_m_selector: 435* summary("Ignore the received select since I'm the handover selector") 436* run_client_alt(handover, alt) 437* return 438* 439* if handover.alt_proposal_used and not alt: 440* summary("Ignore received handover select for the initial proposal since alternative proposal was sent") 441* client.close() 442* return 443* 444* dpp_found = False 445* for carrier in message: 446* if isinstance(carrier, ndef.HandoverSelectRecord): 447* continue 448* summary("Remote carrier type: " + carrier.type) 449* if carrier.type == "application/vnd.wfa.dpp": 450* if len(carrier.data) == 0 or carrier.data[0] != 0: 451* summary("URI Identifier Code 'None' not seen", color=C_RED) 452* continue 453* summary("DPP carrier type match - send to wpa_supplicant") 454* dpp_found = True 455* uri = carrier.data[1:].decode("utf-8") 456* summary("DPP URI: " + uri) 457* handover.peer_uri = uri 458* if test_uri: 459* summary("TEST MODE: Fake processing") 460* break 461* res = wpas_report_handover_sel(uri) 462* if res is None or "FAIL" in res: 463* summary("DPP handover report rejected", color=C_RED) 464* break 465* 466* success_report("DPP handover reported successfully (initiator)") 467* summary("peer_id=" + res) 468* peer_id = int(res) 469* wpas = wpas_connect() 470* if wpas is None: 471* break 472* 473* global enrollee_only 474* global config_params 475* if enrollee_only: 476* extra = " role=enrollee" 477* elif config_params: 478* extra = " role=configurator " + config_params 479* else: 480* # TODO: Single Configurator instance 481* res = wpas.request("DPP_CONFIGURATOR_ADD") 482* if "FAIL" in res: 483* summary("Failed to initiate Configurator", color=C_RED) 484* break 485* conf_id = int(res) 486* extra = " conf=sta-dpp configurator=%d" % conf_id 487* global own_id 488* summary("Initiate DPP authentication") 489* cmd = "DPP_AUTH_INIT peer=%d own=%d" % (peer_id, own_id) 490* cmd += extra 491* res = wpas.request(cmd) 492* if "FAIL" in res: 493* summary("Failed to initiate DPP authentication", color=C_RED) 494* break 495* 496* if not dpp_found and handover.no_alt_proposal: 497* summary("DPP carrier not seen in response - do not allow alternative proposal anymore") 498* elif not dpp_found: 499* summary("DPP carrier not seen in response - allow peer to initiate a new handover with different parameters") 500* handover.alt_proposal = True 501* handover.my_crn_ready = False 502* handover.my_crn = None 503* handover.peer_crn = None 504* handover.hs_sent = False 505* summary("Returning from dpp_handover_client") 506* return 507* 508* summary("Remove peer") 509* handover.close() 510* summary("Done with handover") 511* global only_one 512* if only_one: 513* print("only_one -> stop loop") 514* global continue_loop 515* continue_loop = False 516* 517* global no_wait 518* if no_wait or only_one: 519* summary("Trying to exit..") 520* global terminate_now 521* terminate_now = True 522* 523* summary("Returning from dpp_handover_client") 524* 525*class HandoverServer(nfc.handover.HandoverServer): 526* def __init__(self, handover, llc): 527* super(HandoverServer, self).__init__(llc) 528* self.sent_carrier = None 529* self.ho_server_processing = False 530* self.success = False 531* self.llc = llc 532* self.handover = handover 533* 534* def serve(self, socket): 535* peer_sap = socket.getpeername() 536* summary("Serving handover client on remote sap {0}".format(peer_sap)) 537* send_miu = socket.getsockopt(nfc.llcp.SO_SNDMIU) 538* try: 539* while socket.poll("recv"): 540* req = bytearray() 541* while socket.poll("recv"): 542* r = socket.recv() 543* if r is None: 544* return None 545* summary("Received %d octets" % len(r)) 546* req += r 547* if len(req) == 0: 548* continue 549* try: 550* list(ndef.message_decoder(req, 'strict', {})) 551* except ndef.DecodeError: 552* continue 553* summary("Full message received") 554* resp = self._process_request_data(req) 555* if resp is None or len(resp) == 0: 556* summary("No handover select to send out - wait for a possible alternative handover request") 557* handover.alt_proposal = True 558* req = bytearray() 559* continue 560* 561* for offset in range(0, len(resp), send_miu): 562* if not socket.send(resp[offset:offset + send_miu]): 563* summary("Failed to send handover select - connection closed") 564* return 565* summary("Sent out full handover select") 566* if handover.terminate_on_hs_send_completion: 567* handover.delayed_exit() 568* 569* except nfc.llcp.Error as e: 570* global terminate_now 571* summary("HandoverServer exception: %s" % e, 572* color=None if e.errno == errno.EPIPE or terminate_now else C_RED) 573* finally: 574* socket.close() 575* summary("Handover serve thread exiting") 576* 577* def process_handover_request_message(self, records): 578* handover = self.handover 579* self.ho_server_processing = True 580* global in_raw_mode 581* was_in_raw_mode = in_raw_mode 582* clear_raw_mode() 583* if was_in_raw_mode: 584* print("\n") 585* summary("HandoverServer - request received: " + str(records)) 586* 587* for carrier in records: 588* if not isinstance(carrier, ndef.HandoverRequestRecord): 589* continue 590* if carrier.collision_resolution_number: 591* handover.peer_crn = carrier.collision_resolution_number 592* summary("peer_crn: %d" % handover.peer_crn) 593* 594* if handover.my_crn is None and handover.my_crn_ready: 595* summary("Still trying to send own handover request - wait a moment to see if that succeeds before checking crn values") 596* for i in range(10): 597* if handover.my_crn is not None: 598* break 599* time.sleep(0.01) 600* if handover.my_crn is not None: 601* summary("my_crn: %d" % handover.my_crn) 602* 603* if handover.my_crn is not None and handover.peer_crn is not None: 604* if handover.my_crn == handover.peer_crn: 605* summary("Same crn used - automatic collision resolution failed") 606* # TODO: Should generate a new Handover Request message 607* return '' 608* if ((handover.my_crn & 1) == (handover.peer_crn & 1) and \ 609* handover.my_crn > handover.peer_crn) or \ 610* ((handover.my_crn & 1) != (handover.peer_crn & 1) and \ 611* handover.my_crn < handover.peer_crn): 612* summary("I'm the Handover Selector Device") 613* handover.i_m_selector = True 614* else: 615* summary("Peer is the Handover Selector device") 616* summary("Ignore the received request.") 617* return '' 618* 619* hs = ndef.HandoverSelectRecord('1.4') 620* sel = [hs] 621* 622* found = False 623* 624* for carrier in records: 625* if isinstance(carrier, ndef.HandoverRequestRecord): 626* continue 627* summary("Remote carrier type: " + carrier.type) 628* if carrier.type == "application/vnd.wfa.dpp": 629* summary("DPP carrier type match - add DPP carrier record") 630* if len(carrier.data) == 0 or carrier.data[0] != 0: 631* summary("URI Identifier Code 'None' not seen", color=C_RED) 632* continue 633* uri = carrier.data[1:].decode("utf-8") 634* summary("Received DPP URI: " + uri) 635* 636* global test_uri, test_alt_uri 637* if test_uri: 638* summary("TEST MODE: Using specified URI") 639* data = test_sel_uri if test_sel_uri else test_uri 640* elif handover.alt_proposal and handover.altchanlist: 641* summary("Use alternative channel list while processing alternative proposal from peer") 642* data = wpas_get_nfc_uri(start_listen=False, 643* chan_override=handover.altchanlist, 644* pick_channel=True) 645* else: 646* data = wpas_get_nfc_uri(start_listen=False, 647* pick_channel=True) 648* summary("Own URI (pre-processing): %s" % data) 649* 650* if test_uri: 651* summary("TEST MODE: Fake processing") 652* res = "OK" 653* data += " [%s]" % uri 654* else: 655* res = wpas_report_handover_req(uri) 656* if res is None or "FAIL" in res: 657* summary("DPP handover request processing failed", 658* color=C_RED) 659* if handover.altchanlist: 660* data = wpas_get_nfc_uri(start_listen=False, 661* chan_override=handover.altchanlist) 662* summary("Own URI (try another channel list): %s" % data) 663* continue 664* 665* if test_alt_uri: 666* summary("TEST MODE: Reject initial proposal") 667* continue 668* 669* found = True 670* 671* if not test_uri: 672* wpas = wpas_connect() 673* if wpas is None: 674* continue 675* global own_id 676* data = wpas.request("DPP_BOOTSTRAP_GET_URI %d" % own_id).rstrip() 677* if "FAIL" in data: 678* continue 679* summary("Own URI (post-processing): %s" % data) 680* handover.my_uri = data 681* handover.peer_uri = uri 682* uri = ndef.UriRecord(data) 683* summary("Own bootstrapping NFC URI record: " + str(uri)) 684* 685* if not test_uri: 686* info = wpas.request("DPP_BOOTSTRAP_INFO %d" % own_id) 687* freq = None 688* for line in info.splitlines(): 689* if line.startswith("use_freq="): 690* freq = int(line.split('=')[1]) 691* if freq is None or freq == 0: 692* summary("No channel negotiated over NFC - use channel 6") 693* freq = 2437 694* else: 695* summary("Negotiated channel: %d MHz" % freq) 696* if not dpp_start_listen(wpas, freq): 697* break 698* 699* carrier = ndef.Record('application/vnd.wfa.dpp', 'A', uri.data) 700* summary("Own DPP carrier record: " + str(carrier)) 701* hs.add_alternative_carrier('active', carrier.name) 702* sel = [hs, carrier] 703* break 704* 705* summary("Sending handover select: " + str(sel)) 706* if found: 707* summary("Handover completed successfully") 708* handover.terminate_on_hs_send_completion = True 709* self.success = True 710* handover.hs_sent = True 711* handover.i_m_selector = True 712* elif handover.no_alt_proposal: 713* summary("Do not try alternative proposal anymore - handover failed", 714* color=C_RED) 715* handover.hs_sent = True 716* else: 717* summary("Try to initiate with alternative parameters") 718* handover.try_own = True 719* handover.hs_sent = False 720* handover.no_alt_proposal = True 721* if handover.client_thread: 722* handover.start_client_alt = True 723* else: 724* handover.client_thread = threading.Thread(target=llcp_worker, 725* args=(self.llc, True)) 726* handover.client_thread.start() 727* return sel 728* 729*def clear_raw_mode(): 730* import sys, tty, termios 731* global prev_tcgetattr, in_raw_mode 732* if not in_raw_mode: 733* return 734* fd = sys.stdin.fileno() 735* termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr) 736* in_raw_mode = False 737* 738*def getch(): 739* import sys, tty, termios, select 740* global prev_tcgetattr, in_raw_mode 741* fd = sys.stdin.fileno() 742* prev_tcgetattr = termios.tcgetattr(fd) 743* ch = None 744* try: 745* tty.setraw(fd) 746* in_raw_mode = True 747* [i, o, e] = select.select([fd], [], [], 0.05) 748* if i: 749* ch = sys.stdin.read(1) 750* finally: 751* termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr) 752* in_raw_mode = False 753* return ch 754* 755*def dpp_tag_read(tag): 756* success = False 757* for record in tag.ndef.records: 758* summary(record) 759* summary("record type " + record.type) 760* if record.type == "application/vnd.wfa.dpp": 761* summary("DPP HS tag - send to wpa_supplicant") 762* success = dpp_hs_tag_read(record) 763* break 764* if isinstance(record, ndef.UriRecord): 765* summary("URI record: uri=" + record.uri) 766* summary("URI record: iri=" + record.iri) 767* if record.iri.startswith("DPP:"): 768* summary("DPP URI") 769* if not dpp_nfc_uri_process(record.iri): 770* break 771* success = True 772* else: 773* summary("Ignore unknown URI") 774* break 775* 776* if success: 777* success_report("Tag read succeeded") 778* 779* return success 780* 781*def rdwr_connected_write_tag(tag): 782* summary("Tag found - writing - " + str(tag)) 783* if not tag.ndef: 784* summary("Not a formatted NDEF tag", color=C_RED) 785* return 786* if not tag.ndef.is_writeable: 787* summary("Not a writable tag", color=C_RED) 788* return 789* global dpp_tag_data 790* if tag.ndef.capacity < len(dpp_tag_data): 791* summary("Not enough room for the message") 792* return 793* try: 794* tag.ndef.records = dpp_tag_data 795* except ValueError as e: 796* summary("Writing the tag failed: %s" % str(e), color=C_RED) 797* return 798* success_report("Tag write succeeded") 799* summary("Tag writing completed - remove tag", color=C_GREEN) 800* global only_one, operation_success 801* operation_success = True 802* if only_one: 803* global continue_loop 804* continue_loop = False 805* global dpp_sel_wait_remove 806* return dpp_sel_wait_remove 807* 808*def write_nfc_uri(clf, wait_remove=True): 809* summary("Write NFC URI record") 810* data = wpas_get_nfc_uri() 811* if data is None: 812* summary("Could not get NFC URI from wpa_supplicant", color=C_RED) 813* return 814* 815* global dpp_sel_wait_remove 816* dpp_sel_wait_remove = wait_remove 817* summary("URI: %s" % data) 818* uri = ndef.UriRecord(data) 819* summary(uri) 820* 821* summary("Touch an NFC tag to write URI record", color=C_CYAN) 822* global dpp_tag_data 823* dpp_tag_data = [uri] 824* clf.connect(rdwr={'on-connect': rdwr_connected_write_tag}) 825* 826*def write_nfc_hs(clf, wait_remove=True): 827* summary("Write NFC Handover Select record on a tag") 828* data = wpas_get_nfc_uri() 829* if data is None: 830* summary("Could not get NFC URI from wpa_supplicant", color=C_RED) 831* return 832* 833* global dpp_sel_wait_remove 834* dpp_sel_wait_remove = wait_remove 835* summary("URI: %s" % data) 836* uri = ndef.UriRecord(data) 837* summary(uri) 838* carrier = ndef.Record('application/vnd.wfa.dpp', 'A', uri.data) 839* hs = ndef.HandoverSelectRecord('1.4') 840* hs.add_alternative_carrier('active', carrier.name) 841* summary(hs) 842* summary(carrier) 843* 844* summary("Touch an NFC tag to write HS record", color=C_CYAN) 845* global dpp_tag_data 846* dpp_tag_data = [hs, carrier] 847* summary(dpp_tag_data) 848* clf.connect(rdwr={'on-connect': rdwr_connected_write_tag}) 849* 850*def rdwr_connected(tag): 851* global only_one, no_wait 852* summary("Tag connected: " + str(tag)) 853* 854* if tag.ndef: 855* summary("NDEF tag: " + tag.type) 856* summary(tag.ndef.records) 857* success = dpp_tag_read(tag) 858* if only_one and success: 859* global continue_loop 860* continue_loop = False 861* else: 862* summary("Not an NDEF tag - remove tag", color=C_RED) 863* return True 864* 865* return not no_wait 866* 867*def llcp_worker(llc, try_alt): 868* global handover 869* print("Start of llcp_worker()") 870* if try_alt: 871* summary("Starting handover client (try_alt)") 872* dpp_handover_client(handover, alt=True) 873* summary("Exiting llcp_worker thread (try_alt)") 874* return 875* global init_on_touch 876* if init_on_touch: 877* summary("Starting handover client (init_on_touch)") 878* dpp_handover_client(handover) 879* summary("Exiting llcp_worker thread (init_on_touch)") 880* return 881* 882* global no_input 883* if no_input: 884* summary("Wait for handover to complete") 885* else: 886* print("Wait for handover to complete - press 'i' to initiate") 887* while not handover.wait_connection and handover.srv.sent_carrier is None: 888* if handover.try_own: 889* handover.try_own = False 890* summary("Try to initiate another handover with own parameters") 891* handover.my_crn_ready = False 892* handover.my_crn = None 893* handover.peer_crn = None 894* handover.hs_sent = False 895* dpp_handover_client(handover, alt=True) 896* summary("Exiting llcp_worker thread (retry with own parameters)") 897* return 898* if handover.srv.ho_server_processing: 899* time.sleep(0.025) 900* elif no_input: 901* time.sleep(0.5) 902* else: 903* res = getch() 904* if res != 'i': 905* continue 906* clear_raw_mode() 907* summary("Starting handover client") 908* dpp_handover_client(handover) 909* summary("Exiting llcp_worker thread (manual init)") 910* return 911* 912* global in_raw_mode 913* was_in_raw_mode = in_raw_mode 914* clear_raw_mode() 915* if was_in_raw_mode: 916* print("\r") 917* summary("Exiting llcp_worker thread") 918* 919*class ConnectionHandover(): 920* def __init__(self): 921* self.client = None 922* self.client_thread = None 923* self.reset() 924* self.exit_thread = None 925* 926* def reset(self): 927* self.wait_connection = False 928* self.my_crn_ready = False 929* self.my_crn = None 930* self.peer_crn = None 931* self.hs_sent = False 932* self.no_alt_proposal = False 933* self.alt_proposal_used = False 934* self.i_m_selector = False 935* self.start_client_alt = False 936* self.terminate_on_hs_send_completion = False 937* self.try_own = False 938* self.my_uri = None 939* self.peer_uri = None 940* self.connected = False 941* self.alt_proposal = False 942* 943* def start_handover_server(self, llc): 944* summary("Start handover server") 945* self.llc = llc 946* self.srv = HandoverServer(self, llc) 947* 948* def close(self): 949* if self.client: 950* self.client.close() 951* self.client = None 952* 953* def run_delayed_exit(self): 954* summary("Trying to exit (delayed)..") 955* time.sleep(0.25) 956* summary("Trying to exit (after wait)..") 957* global terminate_now 958* terminate_now = True 959* 960* def delayed_exit(self): 961* global only_one 962* if only_one: 963* self.exit_thread = threading.Thread(target=self.run_delayed_exit) 964* self.exit_thread.start() 965* 966*def llcp_startup(llc): 967* global handover 968* handover.start_handover_server(llc) 969* return llc 970* 971*def llcp_connected(llc): 972* summary("P2P LLCP connected") 973* global handover 974* handover.connected = True 975* handover.srv.start() 976* if init_on_touch or not no_input: 977* handover.client_thread = threading.Thread(target=llcp_worker, 978* args=(llc, False)) 979* handover.client_thread.start() 980* return True 981* 982*def llcp_release(llc): 983* summary("LLCP release") 984* global handover 985* handover.close() 986* return True 987* 988*def terminate_loop(): 989* global terminate_now 990* return terminate_now 991* 992*def main(): 993* clf = nfc.ContactlessFrontend() 994* 995* parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for DPP NFC operations') 996* parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO, 997* action='store_const', dest='loglevel', 998* help='verbose debug output') 999* parser.add_argument('-q', const=logging.WARNING, action='store_const', 1000* dest='loglevel', help='be quiet') 1001* parser.add_argument('--only-one', '-1', action='store_true', 1002* help='run only one operation and exit') 1003* parser.add_argument('--init-on-touch', '-I', action='store_true', 1004* help='initiate handover on touch') 1005* parser.add_argument('--no-wait', action='store_true', 1006* help='do not wait for tag to be removed before exiting') 1007* parser.add_argument('--ifname', '-i', 1008* help='network interface name') 1009* parser.add_argument('--no-input', '-a', action='store_true', 1010* help='do not use stdout input to initiate handover') 1011* parser.add_argument('--tag-read-only', '-t', action='store_true', 1012* help='tag read only (do not allow connection handover)') 1013* parser.add_argument('--handover-only', action='store_true', 1014* help='connection handover only (do not allow tag read)') 1015* parser.add_argument('--enrollee', action='store_true', 1016* help='run as Enrollee-only') 1017* parser.add_argument('--configurator', action='store_true', 1018* help='run as Configurator-only') 1019* parser.add_argument('--config-params', default='', 1020* help='configurator parameters') 1021* parser.add_argument('--ctrl', default='/var/run/wpa_supplicant', 1022* help='wpa_supplicant/hostapd control interface') 1023* parser.add_argument('--summary', 1024* help='summary file for writing status updates') 1025* parser.add_argument('--success', 1026* help='success file for writing success update') 1027* parser.add_argument('--device', default='usb', help='NFC device to open') 1028* parser.add_argument('--chan', default=None, help='channel list') 1029* parser.add_argument('--altchan', default=None, help='alternative channel list') 1030* parser.add_argument('--netrole', default=None, help='netrole for Enrollee') 1031* parser.add_argument('--test-uri', default=None, 1032* help='test mode: initial URI') 1033* parser.add_argument('--test-alt-uri', default=None, 1034* help='test mode: alternative URI') 1035* parser.add_argument('--test-sel-uri', default=None, 1036* help='test mode: handover select URI') 1037* parser.add_argument('--test-crn', default=None, 1038* help='test mode: hardcoded crn') 1039* parser.add_argument('command', choices=['write-nfc-uri', 1040* 'write-nfc-hs'], 1041* nargs='?') 1042* args = parser.parse_args() 1043* summary(args) 1044* 1045* global handover 1046* handover = ConnectionHandover() 1047* 1048* global only_one 1049* only_one = args.only_one 1050* 1051* global no_wait 1052* no_wait = args.no_wait 1053* 1054* global chanlist, netrole, test_uri, test_alt_uri, test_sel_uri 1055* global test_crn 1056* chanlist = args.chan 1057* handover.altchanlist = args.altchan 1058* netrole = args.netrole 1059* test_uri = args.test_uri 1060* test_alt_uri = args.test_alt_uri 1061* test_sel_uri = args.test_sel_uri 1062* if args.test_crn: 1063* test_crn = struct.pack('>H', int(args.test_crn)) 1064* else: 1065* test_crn = None 1066* 1067* logging.basicConfig(level=args.loglevel) 1068* for l in ['nfc.clf.rcs380', 1069* 'nfc.clf.transport', 1070* 'nfc.clf.device', 1071* 'nfc.clf.__init__', 1072* 'nfc.llcp', 1073* 'nfc.handover']: 1074* log = logging.getLogger(l) 1075* log.setLevel(args.loglevel) 1076* 1077* global init_on_touch 1078* init_on_touch = args.init_on_touch 1079* 1080* global enrollee_only 1081* enrollee_only = args.enrollee 1082* 1083* global configurator_only 1084* configurator_only = args.configurator 1085* 1086* global config_params 1087* config_params = args.config_params 1088* 1089* if args.ifname: 1090* global ifname 1091* ifname = args.ifname 1092* summary("Selected ifname " + ifname) 1093* 1094* if args.ctrl: 1095* global wpas_ctrl 1096* wpas_ctrl = args.ctrl 1097* 1098* if args.summary: 1099* global summary_file 1100* summary_file = args.summary 1101* 1102* if args.success: 1103* global success_file 1104* success_file = args.success 1105* 1106* if args.no_input: 1107* global no_input 1108* no_input = True 1109* 1110* clf = nfc.ContactlessFrontend() 1111* 1112* try: 1113* if not clf.open(args.device): 1114* summary("Could not open connection with an NFC device", color=C_RED) 1115* raise SystemExit(1) 1116* 1117* if args.command == "write-nfc-uri": 1118* write_nfc_uri(clf, wait_remove=not args.no_wait) 1119* if not operation_success: 1120* raise SystemExit(1) 1121* raise SystemExit 1122* 1123* if args.command == "write-nfc-hs": 1124* write_nfc_hs(clf, wait_remove=not args.no_wait) 1125* if not operation_success: 1126* raise SystemExit(1) 1127* raise SystemExit 1128* 1129* global continue_loop 1130* while continue_loop: 1131* global in_raw_mode 1132* was_in_raw_mode = in_raw_mode 1133* clear_raw_mode() 1134* if was_in_raw_mode: 1135* print("\r") 1136* if args.handover_only: 1137* summary("Waiting a peer to be touched", color=C_MAGENTA) 1138* elif args.tag_read_only: 1139* summary("Waiting for a tag to be touched", color=C_BLUE) 1140* else: 1141* summary("Waiting for a tag or peer to be touched", 1142* color=C_GREEN) 1143* handover.wait_connection = True 1144* try: 1145* if args.tag_read_only: 1146* if not clf.connect(rdwr={'on-connect': rdwr_connected}): 1147* break 1148* elif args.handover_only: 1149* if not clf.connect(llcp={'on-startup': llcp_startup, 1150* 'on-connect': llcp_connected, 1151* 'on-release': llcp_release}, 1152* terminate=terminate_loop): 1153* break 1154* else: 1155* if not clf.connect(rdwr={'on-connect': rdwr_connected}, 1156* llcp={'on-startup': llcp_startup, 1157* 'on-connect': llcp_connected, 1158* 'on-release': llcp_release}, 1159* terminate=terminate_loop): 1160* break 1161* except Exception as e: 1162* summary("clf.connect failed: " + str(e)) 1163* break 1164* 1165* if only_one and handover.connected: 1166* role = "selector" if handover.i_m_selector else "requestor" 1167* summary("Connection handover result: I'm the %s" % role, 1168* color=C_YELLOW) 1169* if handover.peer_uri: 1170* summary("Peer URI: " + handover.peer_uri, color=C_YELLOW) 1171* if handover.my_uri: 1172* summary("My URI: " + handover.my_uri, color=C_YELLOW) 1173* if not (handover.peer_uri and handover.my_uri): 1174* summary("Negotiated connection handover failed", 1175* color=C_YELLOW) 1176* break 1177* 1178* except KeyboardInterrupt: 1179* raise SystemExit 1180* finally: 1181* clf.close() 1182* 1183* raise SystemExit 1184* 1185*if __name__ == '__main__': 1186* main() 1187*