xref: /freebsd/contrib/wpa/hostapd/wps-ap-nfc.py (revision 4bc52338)
15b9c547cSRui Paulo#!/usr/bin/python
25b9c547cSRui Paulo#
35b9c547cSRui Paulo# Example nfcpy to hostapd wrapper for WPS NFC operations
45b9c547cSRui Paulo# Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi>
55b9c547cSRui Paulo#
65b9c547cSRui Paulo# This software may be distributed under the terms of the BSD license.
75b9c547cSRui Paulo# See README for more details.
85b9c547cSRui Paulo
95b9c547cSRui Pauloimport os
105b9c547cSRui Pauloimport sys
115b9c547cSRui Pauloimport time
125b9c547cSRui Pauloimport argparse
135b9c547cSRui Paulo
145b9c547cSRui Pauloimport nfc
155b9c547cSRui Pauloimport nfc.ndef
165b9c547cSRui Pauloimport nfc.llcp
175b9c547cSRui Pauloimport nfc.handover
185b9c547cSRui Paulo
195b9c547cSRui Pauloimport logging
205b9c547cSRui Paulo
215b9c547cSRui Pauloimport wpaspy
225b9c547cSRui Paulo
235b9c547cSRui Paulowpas_ctrl = '/var/run/hostapd'
245b9c547cSRui Paulocontinue_loop = True
255b9c547cSRui Paulosummary_file = None
265b9c547cSRui Paulosuccess_file = None
275b9c547cSRui Paulo
285b9c547cSRui Paulodef summary(txt):
29*4bc52338SCy Schubert    print(txt)
305b9c547cSRui Paulo    if summary_file:
315b9c547cSRui Paulo        with open(summary_file, 'a') as f:
325b9c547cSRui Paulo            f.write(txt + "\n")
335b9c547cSRui Paulo
345b9c547cSRui Paulodef success_report(txt):
355b9c547cSRui Paulo    summary(txt)
365b9c547cSRui Paulo    if success_file:
375b9c547cSRui Paulo        with open(success_file, 'a') as f:
385b9c547cSRui Paulo            f.write(txt + "\n")
395b9c547cSRui Paulo
405b9c547cSRui Paulodef wpas_connect():
415b9c547cSRui Paulo    ifaces = []
425b9c547cSRui Paulo    if os.path.isdir(wpas_ctrl):
435b9c547cSRui Paulo        try:
445b9c547cSRui Paulo            ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
45*4bc52338SCy Schubert        except OSError as error:
46*4bc52338SCy Schubert            print("Could not find hostapd: ", error)
475b9c547cSRui Paulo            return None
485b9c547cSRui Paulo
495b9c547cSRui Paulo    if len(ifaces) < 1:
50*4bc52338SCy Schubert        print("No hostapd control interface found")
515b9c547cSRui Paulo        return None
525b9c547cSRui Paulo
535b9c547cSRui Paulo    for ctrl in ifaces:
545b9c547cSRui Paulo        try:
555b9c547cSRui Paulo            wpas = wpaspy.Ctrl(ctrl)
565b9c547cSRui Paulo            return wpas
57*4bc52338SCy Schubert        except Exception as e:
585b9c547cSRui Paulo            pass
595b9c547cSRui Paulo    return None
605b9c547cSRui Paulo
615b9c547cSRui Paulo
625b9c547cSRui Paulodef wpas_tag_read(message):
635b9c547cSRui Paulo    wpas = wpas_connect()
645b9c547cSRui Paulo    if (wpas == None):
655b9c547cSRui Paulo        return False
665b9c547cSRui Paulo    if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + str(message).encode("hex")):
675b9c547cSRui Paulo        return False
685b9c547cSRui Paulo    return True
695b9c547cSRui Paulo
705b9c547cSRui Paulo
715b9c547cSRui Paulodef wpas_get_config_token():
725b9c547cSRui Paulo    wpas = wpas_connect()
735b9c547cSRui Paulo    if (wpas == None):
745b9c547cSRui Paulo        return None
755b9c547cSRui Paulo    ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF")
765b9c547cSRui Paulo    if "FAIL" in ret:
775b9c547cSRui Paulo        return None
785b9c547cSRui Paulo    return ret.rstrip().decode("hex")
795b9c547cSRui Paulo
805b9c547cSRui Paulo
815b9c547cSRui Paulodef wpas_get_password_token():
825b9c547cSRui Paulo    wpas = wpas_connect()
835b9c547cSRui Paulo    if (wpas == None):
845b9c547cSRui Paulo        return None
855b9c547cSRui Paulo    ret = wpas.request("WPS_NFC_TOKEN NDEF")
865b9c547cSRui Paulo    if "FAIL" in ret:
875b9c547cSRui Paulo        return None
885b9c547cSRui Paulo    return ret.rstrip().decode("hex")
895b9c547cSRui Paulo
905b9c547cSRui Paulo
915b9c547cSRui Paulodef wpas_get_handover_sel():
925b9c547cSRui Paulo    wpas = wpas_connect()
935b9c547cSRui Paulo    if (wpas == None):
945b9c547cSRui Paulo        return None
955b9c547cSRui Paulo    ret = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR")
965b9c547cSRui Paulo    if "FAIL" in ret:
975b9c547cSRui Paulo        return None
985b9c547cSRui Paulo    return ret.rstrip().decode("hex")
995b9c547cSRui Paulo
1005b9c547cSRui Paulo
1015b9c547cSRui Paulodef wpas_report_handover(req, sel):
1025b9c547cSRui Paulo    wpas = wpas_connect()
1035b9c547cSRui Paulo    if (wpas == None):
1045b9c547cSRui Paulo        return None
1055b9c547cSRui Paulo    return wpas.request("NFC_REPORT_HANDOVER RESP WPS " +
1065b9c547cSRui Paulo                        str(req).encode("hex") + " " +
1075b9c547cSRui Paulo                        str(sel).encode("hex"))
1085b9c547cSRui Paulo
1095b9c547cSRui Paulo
1105b9c547cSRui Pauloclass HandoverServer(nfc.handover.HandoverServer):
1115b9c547cSRui Paulo    def __init__(self, llc):
1125b9c547cSRui Paulo        super(HandoverServer, self).__init__(llc)
1135b9c547cSRui Paulo        self.ho_server_processing = False
1145b9c547cSRui Paulo        self.success = False
1155b9c547cSRui Paulo
1165b9c547cSRui Paulo    # override to avoid parser error in request/response.pretty() in nfcpy
1175b9c547cSRui Paulo    # due to new WSC handover format
1185b9c547cSRui Paulo    def _process_request(self, request):
1195b9c547cSRui Paulo        summary("received handover request {}".format(request.type))
1205b9c547cSRui Paulo        response = nfc.ndef.Message("\xd1\x02\x01Hs\x12")
1215b9c547cSRui Paulo        if not request.type == 'urn:nfc:wkt:Hr':
1225b9c547cSRui Paulo            summary("not a handover request")
1235b9c547cSRui Paulo        else:
1245b9c547cSRui Paulo            try:
1255b9c547cSRui Paulo                request = nfc.ndef.HandoverRequestMessage(request)
1265b9c547cSRui Paulo            except nfc.ndef.DecodeError as e:
1275b9c547cSRui Paulo                summary("error decoding 'Hr' message: {}".format(e))
1285b9c547cSRui Paulo            else:
1295b9c547cSRui Paulo                response = self.process_request(request)
1305b9c547cSRui Paulo        summary("send handover response {}".format(response.type))
1315b9c547cSRui Paulo        return response
1325b9c547cSRui Paulo
1335b9c547cSRui Paulo    def process_request(self, request):
1345b9c547cSRui Paulo        summary("HandoverServer - request received")
1355b9c547cSRui Paulo        try:
136*4bc52338SCy Schubert            print("Parsed handover request: " + request.pretty())
137*4bc52338SCy Schubert        except Exception as e:
138*4bc52338SCy Schubert            print(e)
139*4bc52338SCy Schubert        print(str(request).encode("hex"))
1405b9c547cSRui Paulo
1415b9c547cSRui Paulo        sel = nfc.ndef.HandoverSelectMessage(version="1.2")
1425b9c547cSRui Paulo
1435b9c547cSRui Paulo        for carrier in request.carriers:
144*4bc52338SCy Schubert            print("Remote carrier type: " + carrier.type)
1455b9c547cSRui Paulo            if carrier.type == "application/vnd.wfa.wsc":
1465b9c547cSRui Paulo                summary("WPS carrier type match - add WPS carrier record")
1475b9c547cSRui Paulo                data = wpas_get_handover_sel()
1485b9c547cSRui Paulo                if data is None:
1495b9c547cSRui Paulo                    summary("Could not get handover select carrier record from hostapd")
1505b9c547cSRui Paulo                    continue
151*4bc52338SCy Schubert                print("Handover select carrier record from hostapd:")
152*4bc52338SCy Schubert                print(data.encode("hex"))
1535b9c547cSRui Paulo                if "OK" in wpas_report_handover(carrier.record, data):
1545b9c547cSRui Paulo                    success_report("Handover reported successfully")
1555b9c547cSRui Paulo                else:
1565b9c547cSRui Paulo                    summary("Handover report rejected")
1575b9c547cSRui Paulo
1585b9c547cSRui Paulo                message = nfc.ndef.Message(data);
1595b9c547cSRui Paulo                sel.add_carrier(message[0], "active", message[1:])
1605b9c547cSRui Paulo
161*4bc52338SCy Schubert        print("Handover select:")
1625b9c547cSRui Paulo        try:
163*4bc52338SCy Schubert            print(sel.pretty())
164*4bc52338SCy Schubert        except Exception as e:
165*4bc52338SCy Schubert            print(e)
166*4bc52338SCy Schubert        print(str(sel).encode("hex"))
1675b9c547cSRui Paulo
1685b9c547cSRui Paulo        summary("Sending handover select")
1695b9c547cSRui Paulo        self.success = True
1705b9c547cSRui Paulo        return sel
1715b9c547cSRui Paulo
1725b9c547cSRui Paulo
1735b9c547cSRui Paulodef wps_tag_read(tag):
1745b9c547cSRui Paulo    success = False
1755b9c547cSRui Paulo    if len(tag.ndef.message):
1765b9c547cSRui Paulo        for record in tag.ndef.message:
177*4bc52338SCy Schubert            print("record type " + record.type)
1785b9c547cSRui Paulo            if record.type == "application/vnd.wfa.wsc":
1795b9c547cSRui Paulo                summary("WPS tag - send to hostapd")
1805b9c547cSRui Paulo                success = wpas_tag_read(tag.ndef.message)
1815b9c547cSRui Paulo                break
1825b9c547cSRui Paulo    else:
1835b9c547cSRui Paulo        summary("Empty tag")
1845b9c547cSRui Paulo
1855b9c547cSRui Paulo    if success:
1865b9c547cSRui Paulo        success_report("Tag read succeeded")
1875b9c547cSRui Paulo
1885b9c547cSRui Paulo    return success
1895b9c547cSRui Paulo
1905b9c547cSRui Paulo
1915b9c547cSRui Paulodef rdwr_connected_write(tag):
1925b9c547cSRui Paulo    summary("Tag found - writing - " + str(tag))
1935b9c547cSRui Paulo    global write_data
1945b9c547cSRui Paulo    tag.ndef.message = str(write_data)
1955b9c547cSRui Paulo    success_report("Tag write succeeded")
196*4bc52338SCy Schubert    print("Done - remove tag")
1975b9c547cSRui Paulo    global only_one
1985b9c547cSRui Paulo    if only_one:
1995b9c547cSRui Paulo        global continue_loop
2005b9c547cSRui Paulo        continue_loop = False
2015b9c547cSRui Paulo    global write_wait_remove
2025b9c547cSRui Paulo    while write_wait_remove and tag.is_present:
2035b9c547cSRui Paulo        time.sleep(0.1)
2045b9c547cSRui Paulo
2055b9c547cSRui Paulodef wps_write_config_tag(clf, wait_remove=True):
2065b9c547cSRui Paulo    summary("Write WPS config token")
2075b9c547cSRui Paulo    global write_data, write_wait_remove
2085b9c547cSRui Paulo    write_wait_remove = wait_remove
2095b9c547cSRui Paulo    write_data = wpas_get_config_token()
2105b9c547cSRui Paulo    if write_data == None:
2115b9c547cSRui Paulo        summary("Could not get WPS config token from hostapd")
2125b9c547cSRui Paulo        return
2135b9c547cSRui Paulo
214*4bc52338SCy Schubert    print("Touch an NFC tag")
2155b9c547cSRui Paulo    clf.connect(rdwr={'on-connect': rdwr_connected_write})
2165b9c547cSRui Paulo
2175b9c547cSRui Paulo
2185b9c547cSRui Paulodef wps_write_password_tag(clf, wait_remove=True):
2195b9c547cSRui Paulo    summary("Write WPS password token")
2205b9c547cSRui Paulo    global write_data, write_wait_remove
2215b9c547cSRui Paulo    write_wait_remove = wait_remove
2225b9c547cSRui Paulo    write_data = wpas_get_password_token()
2235b9c547cSRui Paulo    if write_data == None:
2245b9c547cSRui Paulo        summary("Could not get WPS password token from hostapd")
2255b9c547cSRui Paulo        return
2265b9c547cSRui Paulo
227*4bc52338SCy Schubert    print("Touch an NFC tag")
2285b9c547cSRui Paulo    clf.connect(rdwr={'on-connect': rdwr_connected_write})
2295b9c547cSRui Paulo
2305b9c547cSRui Paulo
2315b9c547cSRui Paulodef rdwr_connected(tag):
2325b9c547cSRui Paulo    global only_one, no_wait
2335b9c547cSRui Paulo    summary("Tag connected: " + str(tag))
2345b9c547cSRui Paulo
2355b9c547cSRui Paulo    if tag.ndef:
236*4bc52338SCy Schubert        print("NDEF tag: " + tag.type)
2375b9c547cSRui Paulo        try:
238*4bc52338SCy Schubert            print(tag.ndef.message.pretty())
239*4bc52338SCy Schubert        except Exception as e:
240*4bc52338SCy Schubert            print(e)
2415b9c547cSRui Paulo        success = wps_tag_read(tag)
2425b9c547cSRui Paulo        if only_one and success:
2435b9c547cSRui Paulo            global continue_loop
2445b9c547cSRui Paulo            continue_loop = False
2455b9c547cSRui Paulo    else:
2465b9c547cSRui Paulo        summary("Not an NDEF tag - remove tag")
2475b9c547cSRui Paulo        return True
2485b9c547cSRui Paulo
2495b9c547cSRui Paulo    return not no_wait
2505b9c547cSRui Paulo
2515b9c547cSRui Paulo
2525b9c547cSRui Paulodef llcp_startup(clf, llc):
253*4bc52338SCy Schubert    print("Start LLCP server")
2545b9c547cSRui Paulo    global srv
2555b9c547cSRui Paulo    srv = HandoverServer(llc)
2565b9c547cSRui Paulo    return llc
2575b9c547cSRui Paulo
2585b9c547cSRui Paulodef llcp_connected(llc):
259*4bc52338SCy Schubert    print("P2P LLCP connected")
2605b9c547cSRui Paulo    global wait_connection
2615b9c547cSRui Paulo    wait_connection = False
2625b9c547cSRui Paulo    global srv
2635b9c547cSRui Paulo    srv.start()
2645b9c547cSRui Paulo    return True
2655b9c547cSRui Paulo
2665b9c547cSRui Paulo
2675b9c547cSRui Paulodef main():
2685b9c547cSRui Paulo    clf = nfc.ContactlessFrontend()
2695b9c547cSRui Paulo
2705b9c547cSRui Paulo    parser = argparse.ArgumentParser(description='nfcpy to hostapd integration for WPS NFC operations')
2715b9c547cSRui Paulo    parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
2725b9c547cSRui Paulo                        action='store_const', dest='loglevel',
2735b9c547cSRui Paulo                        help='verbose debug output')
2745b9c547cSRui Paulo    parser.add_argument('-q', const=logging.WARNING, action='store_const',
2755b9c547cSRui Paulo                        dest='loglevel', help='be quiet')
2765b9c547cSRui Paulo    parser.add_argument('--only-one', '-1', action='store_true',
2775b9c547cSRui Paulo                        help='run only one operation and exit')
2785b9c547cSRui Paulo    parser.add_argument('--no-wait', action='store_true',
2795b9c547cSRui Paulo                        help='do not wait for tag to be removed before exiting')
2805b9c547cSRui Paulo    parser.add_argument('--summary',
2815b9c547cSRui Paulo                        help='summary file for writing status updates')
2825b9c547cSRui Paulo    parser.add_argument('--success',
2835b9c547cSRui Paulo                        help='success file for writing success update')
2845b9c547cSRui Paulo    parser.add_argument('command', choices=['write-config',
2855b9c547cSRui Paulo                                            'write-password'],
2865b9c547cSRui Paulo                        nargs='?')
2875b9c547cSRui Paulo    args = parser.parse_args()
2885b9c547cSRui Paulo
2895b9c547cSRui Paulo    global only_one
2905b9c547cSRui Paulo    only_one = args.only_one
2915b9c547cSRui Paulo
2925b9c547cSRui Paulo    global no_wait
2935b9c547cSRui Paulo    no_wait = args.no_wait
2945b9c547cSRui Paulo
2955b9c547cSRui Paulo    if args.summary:
2965b9c547cSRui Paulo        global summary_file
2975b9c547cSRui Paulo        summary_file = args.summary
2985b9c547cSRui Paulo
2995b9c547cSRui Paulo    if args.success:
3005b9c547cSRui Paulo        global success_file
3015b9c547cSRui Paulo        success_file = args.success
3025b9c547cSRui Paulo
3035b9c547cSRui Paulo    logging.basicConfig(level=args.loglevel)
3045b9c547cSRui Paulo
3055b9c547cSRui Paulo    try:
3065b9c547cSRui Paulo        if not clf.open("usb"):
307*4bc52338SCy Schubert            print("Could not open connection with an NFC device")
3085b9c547cSRui Paulo            raise SystemExit
3095b9c547cSRui Paulo
3105b9c547cSRui Paulo        if args.command == "write-config":
3115b9c547cSRui Paulo            wps_write_config_tag(clf, wait_remove=not args.no_wait)
3125b9c547cSRui Paulo            raise SystemExit
3135b9c547cSRui Paulo
3145b9c547cSRui Paulo        if args.command == "write-password":
3155b9c547cSRui Paulo            wps_write_password_tag(clf, wait_remove=not args.no_wait)
3165b9c547cSRui Paulo            raise SystemExit
3175b9c547cSRui Paulo
3185b9c547cSRui Paulo        global continue_loop
3195b9c547cSRui Paulo        while continue_loop:
320*4bc52338SCy Schubert            print("Waiting for a tag or peer to be touched")
3215b9c547cSRui Paulo            wait_connection = True
3225b9c547cSRui Paulo            try:
3235b9c547cSRui Paulo                if not clf.connect(rdwr={'on-connect': rdwr_connected},
3245b9c547cSRui Paulo                                   llcp={'on-startup': llcp_startup,
3255b9c547cSRui Paulo                                         'on-connect': llcp_connected}):
3265b9c547cSRui Paulo                    break
327*4bc52338SCy Schubert            except Exception as e:
328*4bc52338SCy Schubert                print("clf.connect failed")
3295b9c547cSRui Paulo
3305b9c547cSRui Paulo            global srv
3315b9c547cSRui Paulo            if only_one and srv and srv.success:
3325b9c547cSRui Paulo                raise SystemExit
3335b9c547cSRui Paulo
3345b9c547cSRui Paulo    except KeyboardInterrupt:
3355b9c547cSRui Paulo        raise SystemExit
3365b9c547cSRui Paulo    finally:
3375b9c547cSRui Paulo        clf.close()
3385b9c547cSRui Paulo
3395b9c547cSRui Paulo    raise SystemExit
3405b9c547cSRui Paulo
3415b9c547cSRui Pauloif __name__ == '__main__':
3425b9c547cSRui Paulo    main()
343