1 1.1 christos #!/usr/bin/python 2 1.1 christos # 3 1.1 christos # Example nfcpy to hostapd wrapper for WPS NFC operations 4 1.1 christos # Copyright (c) 2012-2013, Jouni Malinen <j (at] w1.fi> 5 1.1 christos # 6 1.1 christos # This software may be distributed under the terms of the BSD license. 7 1.1 christos # See README for more details. 8 1.1 christos 9 1.1 christos import os 10 1.1 christos import sys 11 1.1 christos import time 12 1.1 christos import argparse 13 1.1 christos 14 1.1 christos import nfc 15 1.1 christos import nfc.ndef 16 1.1 christos import nfc.llcp 17 1.1 christos import nfc.handover 18 1.1 christos 19 1.1 christos import logging 20 1.1 christos 21 1.1 christos import wpaspy 22 1.1 christos 23 1.1 christos wpas_ctrl = '/var/run/hostapd' 24 1.1 christos continue_loop = True 25 1.1 christos summary_file = None 26 1.1 christos success_file = None 27 1.1 christos 28 1.1 christos def summary(txt): 29 1.1.1.2 christos print(txt) 30 1.1 christos if summary_file: 31 1.1 christos with open(summary_file, 'a') as f: 32 1.1 christos f.write(txt + "\n") 33 1.1 christos 34 1.1 christos def success_report(txt): 35 1.1 christos summary(txt) 36 1.1 christos if success_file: 37 1.1 christos with open(success_file, 'a') as f: 38 1.1 christos f.write(txt + "\n") 39 1.1 christos 40 1.1 christos def wpas_connect(): 41 1.1 christos ifaces = [] 42 1.1 christos if os.path.isdir(wpas_ctrl): 43 1.1 christos try: 44 1.1 christos ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)] 45 1.1.1.2 christos except OSError as error: 46 1.1.1.2 christos print("Could not find hostapd: ", error) 47 1.1 christos return None 48 1.1 christos 49 1.1 christos if len(ifaces) < 1: 50 1.1.1.2 christos print("No hostapd control interface found") 51 1.1 christos return None 52 1.1 christos 53 1.1 christos for ctrl in ifaces: 54 1.1 christos try: 55 1.1 christos wpas = wpaspy.Ctrl(ctrl) 56 1.1 christos return wpas 57 1.1.1.2 christos except Exception as e: 58 1.1 christos pass 59 1.1 christos return None 60 1.1 christos 61 1.1 christos 62 1.1 christos def wpas_tag_read(message): 63 1.1 christos wpas = wpas_connect() 64 1.1 christos if (wpas == None): 65 1.1 christos return False 66 1.1 christos if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + str(message).encode("hex")): 67 1.1 christos return False 68 1.1 christos return True 69 1.1 christos 70 1.1 christos 71 1.1 christos def wpas_get_config_token(): 72 1.1 christos wpas = wpas_connect() 73 1.1 christos if (wpas == None): 74 1.1 christos return None 75 1.1 christos ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF") 76 1.1 christos if "FAIL" in ret: 77 1.1 christos return None 78 1.1 christos return ret.rstrip().decode("hex") 79 1.1 christos 80 1.1 christos 81 1.1 christos def wpas_get_password_token(): 82 1.1 christos wpas = wpas_connect() 83 1.1 christos if (wpas == None): 84 1.1 christos return None 85 1.1 christos ret = wpas.request("WPS_NFC_TOKEN NDEF") 86 1.1 christos if "FAIL" in ret: 87 1.1 christos return None 88 1.1 christos return ret.rstrip().decode("hex") 89 1.1 christos 90 1.1 christos 91 1.1 christos def wpas_get_handover_sel(): 92 1.1 christos wpas = wpas_connect() 93 1.1 christos if (wpas == None): 94 1.1 christos return None 95 1.1 christos ret = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR") 96 1.1 christos if "FAIL" in ret: 97 1.1 christos return None 98 1.1 christos return ret.rstrip().decode("hex") 99 1.1 christos 100 1.1 christos 101 1.1 christos def wpas_report_handover(req, sel): 102 1.1 christos wpas = wpas_connect() 103 1.1 christos if (wpas == None): 104 1.1 christos return None 105 1.1 christos return wpas.request("NFC_REPORT_HANDOVER RESP WPS " + 106 1.1 christos str(req).encode("hex") + " " + 107 1.1 christos str(sel).encode("hex")) 108 1.1 christos 109 1.1 christos 110 1.1 christos class HandoverServer(nfc.handover.HandoverServer): 111 1.1 christos def __init__(self, llc): 112 1.1 christos super(HandoverServer, self).__init__(llc) 113 1.1 christos self.ho_server_processing = False 114 1.1 christos self.success = False 115 1.1 christos 116 1.1 christos # override to avoid parser error in request/response.pretty() in nfcpy 117 1.1 christos # due to new WSC handover format 118 1.1 christos def _process_request(self, request): 119 1.1 christos summary("received handover request {}".format(request.type)) 120 1.1 christos response = nfc.ndef.Message("\xd1\x02\x01Hs\x12") 121 1.1 christos if not request.type == 'urn:nfc:wkt:Hr': 122 1.1 christos summary("not a handover request") 123 1.1 christos else: 124 1.1 christos try: 125 1.1 christos request = nfc.ndef.HandoverRequestMessage(request) 126 1.1 christos except nfc.ndef.DecodeError as e: 127 1.1 christos summary("error decoding 'Hr' message: {}".format(e)) 128 1.1 christos else: 129 1.1 christos response = self.process_request(request) 130 1.1 christos summary("send handover response {}".format(response.type)) 131 1.1 christos return response 132 1.1 christos 133 1.1 christos def process_request(self, request): 134 1.1 christos summary("HandoverServer - request received") 135 1.1 christos try: 136 1.1.1.2 christos print("Parsed handover request: " + request.pretty()) 137 1.1.1.2 christos except Exception as e: 138 1.1.1.2 christos print(e) 139 1.1.1.2 christos print(str(request).encode("hex")) 140 1.1 christos 141 1.1 christos sel = nfc.ndef.HandoverSelectMessage(version="1.2") 142 1.1 christos 143 1.1 christos for carrier in request.carriers: 144 1.1.1.2 christos print("Remote carrier type: " + carrier.type) 145 1.1 christos if carrier.type == "application/vnd.wfa.wsc": 146 1.1 christos summary("WPS carrier type match - add WPS carrier record") 147 1.1 christos data = wpas_get_handover_sel() 148 1.1 christos if data is None: 149 1.1 christos summary("Could not get handover select carrier record from hostapd") 150 1.1 christos continue 151 1.1.1.2 christos print("Handover select carrier record from hostapd:") 152 1.1.1.2 christos print(data.encode("hex")) 153 1.1 christos if "OK" in wpas_report_handover(carrier.record, data): 154 1.1 christos success_report("Handover reported successfully") 155 1.1 christos else: 156 1.1 christos summary("Handover report rejected") 157 1.1 christos 158 1.1 christos message = nfc.ndef.Message(data); 159 1.1 christos sel.add_carrier(message[0], "active", message[1:]) 160 1.1 christos 161 1.1.1.2 christos print("Handover select:") 162 1.1 christos try: 163 1.1.1.2 christos print(sel.pretty()) 164 1.1.1.2 christos except Exception as e: 165 1.1.1.2 christos print(e) 166 1.1.1.2 christos print(str(sel).encode("hex")) 167 1.1 christos 168 1.1 christos summary("Sending handover select") 169 1.1 christos self.success = True 170 1.1 christos return sel 171 1.1 christos 172 1.1 christos 173 1.1 christos def wps_tag_read(tag): 174 1.1 christos success = False 175 1.1 christos if len(tag.ndef.message): 176 1.1 christos for record in tag.ndef.message: 177 1.1.1.2 christos print("record type " + record.type) 178 1.1 christos if record.type == "application/vnd.wfa.wsc": 179 1.1 christos summary("WPS tag - send to hostapd") 180 1.1 christos success = wpas_tag_read(tag.ndef.message) 181 1.1 christos break 182 1.1 christos else: 183 1.1 christos summary("Empty tag") 184 1.1 christos 185 1.1 christos if success: 186 1.1 christos success_report("Tag read succeeded") 187 1.1 christos 188 1.1 christos return success 189 1.1 christos 190 1.1 christos 191 1.1 christos def rdwr_connected_write(tag): 192 1.1 christos summary("Tag found - writing - " + str(tag)) 193 1.1 christos global write_data 194 1.1 christos tag.ndef.message = str(write_data) 195 1.1 christos success_report("Tag write succeeded") 196 1.1.1.2 christos print("Done - remove tag") 197 1.1 christos global only_one 198 1.1 christos if only_one: 199 1.1 christos global continue_loop 200 1.1 christos continue_loop = False 201 1.1 christos global write_wait_remove 202 1.1 christos while write_wait_remove and tag.is_present: 203 1.1 christos time.sleep(0.1) 204 1.1 christos 205 1.1 christos def wps_write_config_tag(clf, wait_remove=True): 206 1.1 christos summary("Write WPS config token") 207 1.1 christos global write_data, write_wait_remove 208 1.1 christos write_wait_remove = wait_remove 209 1.1 christos write_data = wpas_get_config_token() 210 1.1 christos if write_data == None: 211 1.1 christos summary("Could not get WPS config token from hostapd") 212 1.1 christos return 213 1.1 christos 214 1.1.1.2 christos print("Touch an NFC tag") 215 1.1 christos clf.connect(rdwr={'on-connect': rdwr_connected_write}) 216 1.1 christos 217 1.1 christos 218 1.1 christos def wps_write_password_tag(clf, wait_remove=True): 219 1.1 christos summary("Write WPS password token") 220 1.1 christos global write_data, write_wait_remove 221 1.1 christos write_wait_remove = wait_remove 222 1.1 christos write_data = wpas_get_password_token() 223 1.1 christos if write_data == None: 224 1.1 christos summary("Could not get WPS password token from hostapd") 225 1.1 christos return 226 1.1 christos 227 1.1.1.2 christos print("Touch an NFC tag") 228 1.1 christos clf.connect(rdwr={'on-connect': rdwr_connected_write}) 229 1.1 christos 230 1.1 christos 231 1.1 christos def rdwr_connected(tag): 232 1.1 christos global only_one, no_wait 233 1.1 christos summary("Tag connected: " + str(tag)) 234 1.1 christos 235 1.1 christos if tag.ndef: 236 1.1.1.2 christos print("NDEF tag: " + tag.type) 237 1.1 christos try: 238 1.1.1.2 christos print(tag.ndef.message.pretty()) 239 1.1.1.2 christos except Exception as e: 240 1.1.1.2 christos print(e) 241 1.1 christos success = wps_tag_read(tag) 242 1.1 christos if only_one and success: 243 1.1 christos global continue_loop 244 1.1 christos continue_loop = False 245 1.1 christos else: 246 1.1 christos summary("Not an NDEF tag - remove tag") 247 1.1 christos return True 248 1.1 christos 249 1.1 christos return not no_wait 250 1.1 christos 251 1.1 christos 252 1.1 christos def llcp_startup(clf, llc): 253 1.1.1.2 christos print("Start LLCP server") 254 1.1 christos global srv 255 1.1 christos srv = HandoverServer(llc) 256 1.1 christos return llc 257 1.1 christos 258 1.1 christos def llcp_connected(llc): 259 1.1.1.2 christos print("P2P LLCP connected") 260 1.1 christos global wait_connection 261 1.1 christos wait_connection = False 262 1.1 christos global srv 263 1.1 christos srv.start() 264 1.1 christos return True 265 1.1 christos 266 1.1 christos 267 1.1 christos def main(): 268 1.1 christos clf = nfc.ContactlessFrontend() 269 1.1 christos 270 1.1 christos parser = argparse.ArgumentParser(description='nfcpy to hostapd integration for WPS NFC operations') 271 1.1 christos parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO, 272 1.1 christos action='store_const', dest='loglevel', 273 1.1 christos help='verbose debug output') 274 1.1 christos parser.add_argument('-q', const=logging.WARNING, action='store_const', 275 1.1 christos dest='loglevel', help='be quiet') 276 1.1 christos parser.add_argument('--only-one', '-1', action='store_true', 277 1.1 christos help='run only one operation and exit') 278 1.1 christos parser.add_argument('--no-wait', action='store_true', 279 1.1 christos help='do not wait for tag to be removed before exiting') 280 1.1 christos parser.add_argument('--summary', 281 1.1 christos help='summary file for writing status updates') 282 1.1 christos parser.add_argument('--success', 283 1.1 christos help='success file for writing success update') 284 1.1 christos parser.add_argument('command', choices=['write-config', 285 1.1 christos 'write-password'], 286 1.1 christos nargs='?') 287 1.1 christos args = parser.parse_args() 288 1.1 christos 289 1.1 christos global only_one 290 1.1 christos only_one = args.only_one 291 1.1 christos 292 1.1 christos global no_wait 293 1.1 christos no_wait = args.no_wait 294 1.1 christos 295 1.1 christos if args.summary: 296 1.1 christos global summary_file 297 1.1 christos summary_file = args.summary 298 1.1 christos 299 1.1 christos if args.success: 300 1.1 christos global success_file 301 1.1 christos success_file = args.success 302 1.1 christos 303 1.1 christos logging.basicConfig(level=args.loglevel) 304 1.1 christos 305 1.1 christos try: 306 1.1 christos if not clf.open("usb"): 307 1.1.1.2 christos print("Could not open connection with an NFC device") 308 1.1 christos raise SystemExit 309 1.1 christos 310 1.1 christos if args.command == "write-config": 311 1.1 christos wps_write_config_tag(clf, wait_remove=not args.no_wait) 312 1.1 christos raise SystemExit 313 1.1 christos 314 1.1 christos if args.command == "write-password": 315 1.1 christos wps_write_password_tag(clf, wait_remove=not args.no_wait) 316 1.1 christos raise SystemExit 317 1.1 christos 318 1.1 christos global continue_loop 319 1.1 christos while continue_loop: 320 1.1.1.2 christos print("Waiting for a tag or peer to be touched") 321 1.1 christos wait_connection = True 322 1.1 christos try: 323 1.1 christos if not clf.connect(rdwr={'on-connect': rdwr_connected}, 324 1.1 christos llcp={'on-startup': llcp_startup, 325 1.1 christos 'on-connect': llcp_connected}): 326 1.1 christos break 327 1.1.1.2 christos except Exception as e: 328 1.1.1.2 christos print("clf.connect failed") 329 1.1 christos 330 1.1 christos global srv 331 1.1 christos if only_one and srv and srv.success: 332 1.1 christos raise SystemExit 333 1.1 christos 334 1.1 christos except KeyboardInterrupt: 335 1.1 christos raise SystemExit 336 1.1 christos finally: 337 1.1 christos clf.close() 338 1.1 christos 339 1.1 christos raise SystemExit 340 1.1 christos 341 1.1 christos if __name__ == '__main__': 342 1.1 christos main() 343