wps-nfc.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. #!/usr/bin/python
  2. #
  3. # Example nfcpy to wpa_supplicant wrapper for WPS NFC operations
  4. # Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi>
  5. #
  6. # This software may be distributed under the terms of the BSD license.
  7. # See README for more details.
  8. import os
  9. import sys
  10. import time
  11. import random
  12. import threading
  13. import argparse
  14. import nfc
  15. import nfc.ndef
  16. import nfc.llcp
  17. import nfc.handover
  18. import logging
  19. import wpaspy
  20. wpas_ctrl = '/var/run/wpa_supplicant'
  21. srv = None
  22. continue_loop = True
  23. terminate_now = False
  24. summary_file = None
  25. success_file = None
  26. def summary(txt):
  27. print txt
  28. if summary_file:
  29. with open(summary_file, 'a') as f:
  30. f.write(txt + "\n")
  31. def success_report(txt):
  32. summary(txt)
  33. if success_file:
  34. with open(success_file, 'a') as f:
  35. f.write(txt + "\n")
  36. def wpas_connect():
  37. ifaces = []
  38. if os.path.isdir(wpas_ctrl):
  39. try:
  40. ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
  41. except OSError, error:
  42. print "Could not find wpa_supplicant: ", error
  43. return None
  44. if len(ifaces) < 1:
  45. print "No wpa_supplicant control interface found"
  46. return None
  47. for ctrl in ifaces:
  48. try:
  49. wpas = wpaspy.Ctrl(ctrl)
  50. return wpas
  51. except Exception, e:
  52. pass
  53. return None
  54. def wpas_tag_read(message):
  55. wpas = wpas_connect()
  56. if (wpas == None):
  57. return False
  58. if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + str(message).encode("hex")):
  59. return False
  60. return True
  61. def wpas_get_config_token(id=None):
  62. wpas = wpas_connect()
  63. if (wpas == None):
  64. return None
  65. if id:
  66. ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF " + id)
  67. else:
  68. ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF")
  69. if "FAIL" in ret:
  70. return None
  71. return ret.rstrip().decode("hex")
  72. def wpas_get_er_config_token(uuid):
  73. wpas = wpas_connect()
  74. if (wpas == None):
  75. return None
  76. ret = wpas.request("WPS_ER_NFC_CONFIG_TOKEN NDEF " + uuid)
  77. if "FAIL" in ret:
  78. return None
  79. return ret.rstrip().decode("hex")
  80. def wpas_get_password_token():
  81. wpas = wpas_connect()
  82. if (wpas == None):
  83. return None
  84. ret = wpas.request("WPS_NFC_TOKEN NDEF")
  85. if "FAIL" in ret:
  86. return None
  87. return ret.rstrip().decode("hex")
  88. def wpas_get_handover_req():
  89. wpas = wpas_connect()
  90. if (wpas == None):
  91. return None
  92. ret = wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR")
  93. if "FAIL" in ret:
  94. return None
  95. return ret.rstrip().decode("hex")
  96. def wpas_get_handover_sel(uuid):
  97. wpas = wpas_connect()
  98. if (wpas == None):
  99. return None
  100. if uuid is None:
  101. res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR").rstrip()
  102. else:
  103. res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR " + uuid).rstrip()
  104. if "FAIL" in res:
  105. return None
  106. return res.decode("hex")
  107. def wpas_report_handover(req, sel, type):
  108. wpas = wpas_connect()
  109. if (wpas == None):
  110. return None
  111. return wpas.request("NFC_REPORT_HANDOVER " + type + " WPS " +
  112. str(req).encode("hex") + " " +
  113. str(sel).encode("hex"))
  114. class HandoverServer(nfc.handover.HandoverServer):
  115. def __init__(self, llc):
  116. super(HandoverServer, self).__init__(llc)
  117. self.sent_carrier = None
  118. self.ho_server_processing = False
  119. self.success = False
  120. # override to avoid parser error in request/response.pretty() in nfcpy
  121. # due to new WSC handover format
  122. def _process_request(self, request):
  123. summary("received handover request {}".format(request.type))
  124. response = nfc.ndef.Message("\xd1\x02\x01Hs\x12")
  125. if not request.type == 'urn:nfc:wkt:Hr':
  126. summary("not a handover request")
  127. else:
  128. try:
  129. request = nfc.ndef.HandoverRequestMessage(request)
  130. except nfc.ndef.DecodeError as e:
  131. summary("error decoding 'Hr' message: {}".format(e))
  132. else:
  133. response = self.process_request(request)
  134. summary("send handover response {}".format(response.type))
  135. return response
  136. def process_request(self, request):
  137. self.ho_server_processing = True
  138. summary("HandoverServer - request received")
  139. try:
  140. print "Parsed handover request: " + request.pretty()
  141. except Exception, e:
  142. print e
  143. sel = nfc.ndef.HandoverSelectMessage(version="1.2")
  144. for carrier in request.carriers:
  145. print "Remote carrier type: " + carrier.type
  146. if carrier.type == "application/vnd.wfa.wsc":
  147. summary("WPS carrier type match - add WPS carrier record")
  148. data = wpas_get_handover_sel(self.uuid)
  149. if data is None:
  150. summary("Could not get handover select carrier record from wpa_supplicant")
  151. continue
  152. print "Handover select carrier record from wpa_supplicant:"
  153. print data.encode("hex")
  154. self.sent_carrier = data
  155. if "OK" in wpas_report_handover(carrier.record, self.sent_carrier, "RESP"):
  156. success_report("Handover reported successfully (responder)")
  157. else:
  158. summary("Handover report rejected (responder)")
  159. message = nfc.ndef.Message(data);
  160. sel.add_carrier(message[0], "active", message[1:])
  161. print "Handover select:"
  162. try:
  163. print sel.pretty()
  164. except Exception, e:
  165. print e
  166. print str(sel).encode("hex")
  167. summary("Sending handover select")
  168. self.success = True
  169. return sel
  170. def wps_handover_init(llc):
  171. summary("Trying to initiate WPS handover")
  172. data = wpas_get_handover_req()
  173. if (data == None):
  174. summary("Could not get handover request carrier record from wpa_supplicant")
  175. return
  176. print "Handover request carrier record from wpa_supplicant: " + data.encode("hex")
  177. message = nfc.ndef.HandoverRequestMessage(version="1.2")
  178. message.nonce = random.randint(0, 0xffff)
  179. datamsg = nfc.ndef.Message(data)
  180. message.add_carrier(datamsg[0], "active", datamsg[1:])
  181. print "Handover request:"
  182. try:
  183. print message.pretty()
  184. except Exception, e:
  185. print e
  186. print str(message).encode("hex")
  187. client = nfc.handover.HandoverClient(llc)
  188. try:
  189. summary("Trying to initiate NFC connection handover")
  190. client.connect()
  191. summary("Connected for handover")
  192. except nfc.llcp.ConnectRefused:
  193. summary("Handover connection refused")
  194. client.close()
  195. return
  196. except Exception, e:
  197. summary("Other exception: " + str(e))
  198. client.close()
  199. return
  200. summary("Sending handover request")
  201. if not client.send(message):
  202. summary("Failed to send handover request")
  203. client.close()
  204. return
  205. summary("Receiving handover response")
  206. message = client._recv()
  207. if message is None:
  208. summary("No response received")
  209. client.close()
  210. return
  211. if message.type != "urn:nfc:wkt:Hs":
  212. summary("Response was not Hs - received: " + message.type)
  213. client.close()
  214. return
  215. print "Received message"
  216. try:
  217. print message.pretty()
  218. except Exception, e:
  219. print e
  220. print str(message).encode("hex")
  221. message = nfc.ndef.HandoverSelectMessage(message)
  222. summary("Handover select received")
  223. try:
  224. print message.pretty()
  225. except Exception, e:
  226. print e
  227. for carrier in message.carriers:
  228. print "Remote carrier type: " + carrier.type
  229. if carrier.type == "application/vnd.wfa.wsc":
  230. print "WPS carrier type match - send to wpa_supplicant"
  231. if "OK" in wpas_report_handover(data, carrier.record, "INIT"):
  232. success_report("Handover reported successfully (initiator)")
  233. else:
  234. summary("Handover report rejected (initiator)")
  235. # nfcpy does not support the new format..
  236. #wifi = nfc.ndef.WifiConfigRecord(carrier.record)
  237. #print wifi.pretty()
  238. print "Remove peer"
  239. client.close()
  240. print "Done with handover"
  241. global only_one
  242. if only_one:
  243. global continue_loop
  244. continue_loop = False
  245. global no_wait
  246. if no_wait:
  247. print "Trying to exit.."
  248. global terminate_now
  249. terminate_now = True
  250. def wps_tag_read(tag, wait_remove=True):
  251. success = False
  252. if len(tag.ndef.message):
  253. for record in tag.ndef.message:
  254. print "record type " + record.type
  255. if record.type == "application/vnd.wfa.wsc":
  256. summary("WPS tag - send to wpa_supplicant")
  257. success = wpas_tag_read(tag.ndef.message)
  258. break
  259. else:
  260. summary("Empty tag")
  261. if success:
  262. success_report("Tag read succeeded")
  263. if wait_remove:
  264. print "Remove tag"
  265. while tag.is_present:
  266. time.sleep(0.1)
  267. return success
  268. def rdwr_connected_write(tag):
  269. summary("Tag found - writing - " + str(tag))
  270. global write_data
  271. tag.ndef.message = str(write_data)
  272. success_report("Tag write succeeded")
  273. print "Done - remove tag"
  274. global only_one
  275. if only_one:
  276. global continue_loop
  277. continue_loop = False
  278. global write_wait_remove
  279. while write_wait_remove and tag.is_present:
  280. time.sleep(0.1)
  281. def wps_write_config_tag(clf, id=None, wait_remove=True):
  282. print "Write WPS config token"
  283. global write_data, write_wait_remove
  284. write_wait_remove = wait_remove
  285. write_data = wpas_get_config_token(id)
  286. if write_data == None:
  287. print "Could not get WPS config token from wpa_supplicant"
  288. sys.exit(1)
  289. return
  290. print "Touch an NFC tag"
  291. clf.connect(rdwr={'on-connect': rdwr_connected_write})
  292. def wps_write_er_config_tag(clf, uuid, wait_remove=True):
  293. print "Write WPS ER config token"
  294. global write_data, write_wait_remove
  295. write_wait_remove = wait_remove
  296. write_data = wpas_get_er_config_token(uuid)
  297. if write_data == None:
  298. print "Could not get WPS config token from wpa_supplicant"
  299. return
  300. print "Touch an NFC tag"
  301. clf.connect(rdwr={'on-connect': rdwr_connected_write})
  302. def wps_write_password_tag(clf, wait_remove=True):
  303. print "Write WPS password token"
  304. global write_data, write_wait_remove
  305. write_wait_remove = wait_remove
  306. write_data = wpas_get_password_token()
  307. if write_data == None:
  308. print "Could not get WPS password token from wpa_supplicant"
  309. return
  310. print "Touch an NFC tag"
  311. clf.connect(rdwr={'on-connect': rdwr_connected_write})
  312. def rdwr_connected(tag):
  313. global only_one, no_wait
  314. summary("Tag connected: " + str(tag))
  315. if tag.ndef:
  316. print "NDEF tag: " + tag.type
  317. try:
  318. print tag.ndef.message.pretty()
  319. except Exception, e:
  320. print e
  321. success = wps_tag_read(tag, not only_one)
  322. if only_one and success:
  323. global continue_loop
  324. continue_loop = False
  325. else:
  326. summary("Not an NDEF tag - remove tag")
  327. return True
  328. return not no_wait
  329. def llcp_worker(llc):
  330. global arg_uuid
  331. if arg_uuid is None:
  332. wps_handover_init(llc)
  333. print "Exiting llcp_worker thread"
  334. return
  335. global srv
  336. global wait_connection
  337. while not wait_connection and srv.sent_carrier is None:
  338. if srv.ho_server_processing:
  339. time.sleep(0.025)
  340. def llcp_startup(clf, llc):
  341. global arg_uuid
  342. if arg_uuid:
  343. print "Start LLCP server"
  344. global srv
  345. srv = HandoverServer(llc)
  346. if arg_uuid is "ap":
  347. print "Trying to handle WPS handover"
  348. srv.uuid = None
  349. else:
  350. print "Trying to handle WPS handover with AP " + arg_uuid
  351. srv.uuid = arg_uuid
  352. return llc
  353. def llcp_connected(llc):
  354. print "P2P LLCP connected"
  355. global wait_connection
  356. wait_connection = False
  357. global arg_uuid
  358. if arg_uuid:
  359. global srv
  360. srv.start()
  361. else:
  362. threading.Thread(target=llcp_worker, args=(llc,)).start()
  363. print "llcp_connected returning"
  364. return True
  365. def terminate_loop():
  366. global terminate_now
  367. return terminate_now
  368. def main():
  369. clf = nfc.ContactlessFrontend()
  370. parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for WPS NFC operations')
  371. parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
  372. action='store_const', dest='loglevel',
  373. help='verbose debug output')
  374. parser.add_argument('-q', const=logging.WARNING, action='store_const',
  375. dest='loglevel', help='be quiet')
  376. parser.add_argument('--only-one', '-1', action='store_true',
  377. help='run only one operation and exit')
  378. parser.add_argument('--no-wait', action='store_true',
  379. help='do not wait for tag to be removed before exiting')
  380. parser.add_argument('--uuid',
  381. help='UUID of an AP (used for WPS ER operations)')
  382. parser.add_argument('--id',
  383. help='network id (used for WPS ER operations)')
  384. parser.add_argument('--summary',
  385. help='summary file for writing status updates')
  386. parser.add_argument('--success',
  387. help='success file for writing success update')
  388. parser.add_argument('command', choices=['write-config',
  389. 'write-er-config',
  390. 'write-password'],
  391. nargs='?')
  392. args = parser.parse_args()
  393. global arg_uuid
  394. arg_uuid = args.uuid
  395. global only_one
  396. only_one = args.only_one
  397. global no_wait
  398. no_wait = args.no_wait
  399. if args.summary:
  400. global summary_file
  401. summary_file = args.summary
  402. if args.success:
  403. global success_file
  404. success_file = args.success
  405. logging.basicConfig(level=args.loglevel)
  406. try:
  407. if not clf.open("usb"):
  408. print "Could not open connection with an NFC device"
  409. raise SystemExit
  410. if args.command == "write-config":
  411. wps_write_config_tag(clf, id=args.id, wait_remove=not args.no_wait)
  412. raise SystemExit
  413. if args.command == "write-er-config":
  414. wps_write_er_config_tag(clf, args.uuid, wait_remove=not args.no_wait)
  415. raise SystemExit
  416. if args.command == "write-password":
  417. wps_write_password_tag(clf, wait_remove=not args.no_wait)
  418. raise SystemExit
  419. global continue_loop
  420. while continue_loop:
  421. print "Waiting for a tag or peer to be touched"
  422. wait_connection = True
  423. try:
  424. if not clf.connect(rdwr={'on-connect': rdwr_connected},
  425. llcp={'on-startup': llcp_startup,
  426. 'on-connect': llcp_connected},
  427. terminate=terminate_loop):
  428. break
  429. except Exception, e:
  430. print "clf.connect failed"
  431. global srv
  432. if only_one and srv and srv.success:
  433. raise SystemExit
  434. except KeyboardInterrupt:
  435. raise SystemExit
  436. finally:
  437. clf.close()
  438. raise SystemExit
  439. if __name__ == '__main__':
  440. main()