uv = "discouraged" if WindowsClient.is_available(): # Use the Windows WebAuthn API if available client = WindowsClient("https://example.com") else: # Locate a device dev = next(CtapHidDevice.list_devices(), None) if dev is not None: print("Use USB HID channel.") use_prompt = True else: try: from fido2.pcsc import CtapPcscDevice dev = next(CtapPcscDevice.list_devices(), None) print("Use NFC channel.") except Exception as e: print("NFC channel search error:", e) if not dev: print("No FIDO device found") sys.exit(1) # Set up a FIDO 2 client using the origin https://example.com client = Fido2Client(dev, "https://example.com") # Prefer UV if supported if client.info.options.get("uv"): uv = "preferred" print("Authenticator supports User Verification")
def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev
def _verify_authentication_status(duo_host, sid, duo_transaction_id, session, ssl_verification_enabled): responses = [] while len(responses) < 10: status_for_url = "https://{}/frame/status".format(duo_host) response = session.post( status_for_url, verify=ssl_verification_enabled, headers=_headers, data={ 'sid': sid, 'txid': duo_transaction_id } ) logging.debug(u'''Request: * url: {} * headers: {} Response: * status: {} * headers: {} * body: {} '''.format(status_for_url, response.request.headers, response.status_code, response.headers, response.text)) if response.status_code != 200: raise click.ClickException( u'Issues during second factor verification. The error response {}'.format( response ) ) json_response = response.json() if json_response['stat'] != 'OK': raise click.ClickException( u'There was an issue during second factor verification. The error response: {}'.format( response.text ) ) if json_response['response']['status_code'] not in ['answered', 'calling', 'pushed', 'u2f_sent']: raise click.ClickException( u'There was an issue during second factor verification. The error response: {}'.format( response.text ) ) if json_response['response']['status_code'] in ['pushed', 'answered', 'allow']: return duo_transaction_id if json_response['response']['status_code'] == 'u2f_sent' and len(json_response['response']['u2f_sign_request']) > 0: u2f_sign_requests = json_response['response']['u2f_sign_request'] # appId, challenge and session is the same for all requests, get them from the first u2f_app_id = u2f_sign_requests[0]['appId'] u2f_challenge = u2f_sign_requests[0]['challenge'] u2f_session_id = u2f_sign_requests[0]['sessionId'] devices = list(CtapHidDevice.list_devices()) if CtapPcscDevice: devices.extend(list(CtapPcscDevice.list_devices())) if not devices: click.echo("No FIDO U2F authenticator is eligible.") return "cancelled" threads = [] u2f_response = { "sessionId": u2f_session_id } rq = queue.Queue() cancel = Event() for device in devices: t = Thread( target=_u2f_sign, args=( device, u2f_app_id, u2f_challenge, u2f_sign_requests, duo_host, sid, u2f_response, session, ssl_verification_enabled, cancel, rq ) ) t.daemon = True threads.append(t) t.start() # Wait for first answer return rq.get() responses.append(response.text) raise click.ClickException( u'There was an issue during second factor verification. The responses: {}'.format( responses ) )
def _verify_authentication_status(duo_host, sid, duo_transaction_id, session, ssl_verification_enabled): responses = [] while len(responses) < 10: status_for_url = "https://{}/frame/status".format(duo_host) data = {'sid': sid, 'txid': duo_transaction_id} response = session.post(status_for_url, verify=ssl_verification_enabled, headers=_headers, data=data) trace_http_request(response) if response.status_code != 200: raise click.ClickException( u'Issues during second factor verification. The error response {}' .format(response)) json_response = response.json() if json_response['stat'] != 'OK': raise click.ClickException( u'There was an issue during second factor verification. The error response: {}' .format(response.text)) if json_response['response']['status_code'] not in [ 'answered', 'calling', 'pushed', 'webauthn_sent' ]: raise click.ClickException( u'There was an issue during second factor verification. The error response: {}' .format(response.text)) if json_response['response']['status_code'] in [ 'pushed', 'answered', 'allow' ]: return duo_transaction_id if json_response['response']['status_code'] == 'webauthn_sent' and len( json_response['response'] ['webauthn_credential_request_options']) > 0: webauthn_credential_request_options = json_response['response'][ 'webauthn_credential_request_options'] webauthn_credential_request_options[ "challenge"] = base64.b64decode( webauthn_credential_request_options["challenge"]) for cred in webauthn_credential_request_options[ "allowCredentials"]: cred["id"] = base64.urlsafe_b64decode( cred["id"] + "==" ) # Add arbitrary padding characters, unnecessary ones are ignored cred.pop("transports") webauthn_session_id = webauthn_credential_request_options.pop( 'sessionId') devices = list(CtapHidDevice.list_devices()) if CtapPcscDevice: devices.extend(list(CtapPcscDevice.list_devices())) if not devices: click.echo("No FIDO U2F / FIDO2 authenticator is eligible.") return "cancelled" threads = [] webauthn_response = {"sessionId": webauthn_session_id} rq = queue.Queue() cancel = Event() for device in devices: t = Thread(target=_webauthn_get_assertion, args=(device, webauthn_credential_request_options, duo_host, sid, webauthn_response, session, ssl_verification_enabled, cancel, rq)) t.daemon = True threads.append(t) t.start() # Wait for first answer return rq.get() responses.append(response.text) raise click.ClickException( u'There was an issue during second factor verification. The responses: {}' .format(responses))
def _verify_authentication_status(duo_host, sid, txid, session, ssl_verification_enabled): status_for_url = duo_host + "/frame/v4/status" responses = [] while len(responses) < 10: data = {"sid": sid, "txid": txid} response = session.post(status_for_url, verify=ssl_verification_enabled, headers=_headers, data=data) trace_http_request(response) if response.status_code != 200: raise click.ClickException("Issues during second factor verification. The error response {}".format(response)) json_response = response.json() if json_response["stat"] != "OK": raise click.ClickException( "There was an issue during second factor verification. The error response: {}".format(response.text) ) if json_response["response"]["status_code"] not in [ "answered", "calling", "pushed", "webauthn_sent", ]: raise click.ClickException( "There was an issue during second factor verification. The error response: {}".format(response.text) ) if json_response["response"]["status_code"] in ["pushed", "answered", "allow"]: return txid if ( json_response["response"]["status_code"] == "webauthn_sent" and len(json_response["response"]["webauthn_credential_request_options"]) > 0 ): webauthn_credential_request_options = json_response["response"]["webauthn_credential_request_options"] webauthn_credential_request_options["challenge"] = base64.b64decode(webauthn_credential_request_options["challenge"]) for cred in webauthn_credential_request_options["allowCredentials"]: cred["id"] = base64.urlsafe_b64decode( cred["id"] + "==" ) # Add arbitrary padding characters, unnecessary ones are ignored cred.pop("transports") webauthn_session_id = webauthn_credential_request_options.pop("sessionId") devices = list(CtapHidDevice.list_devices()) if CtapPcscDevice: devices.extend(list(CtapPcscDevice.list_devices())) if not devices: click.echo("No FIDO U2F / FIDO2 authenticator is eligible.") return "cancelled" threads = [] webauthn_response = {"sessionId": webauthn_session_id} rq = queue.Queue() cancel = Event() for device in devices: t = Thread( target=_webauthn_get_assertion, args=( device, webauthn_credential_request_options, duo_host, sid, webauthn_response, session, ssl_verification_enabled, cancel, rq, ), ) t.daemon = True threads.append(t) t.start() # Wait for first answer return rq.get() responses.append(response.text) raise click.ClickException("There was an issue during second factor verification. The responses: {}".format(responses))