def find_device(self, nfcInterfaceOnly=False): if self.dev is not None: return dev = None self.nfc_interface_only = nfcInterfaceOnly if not nfcInterfaceOnly: # print("--- HID ---") # print(list(CtapHidDevice.list_devices())) dev = next(CtapHidDevice.list_devices(), None) if not dev: from fido2.pcsc import CtapPcscDevice # print("--- NFC ---") # print(list(CtapPcscDevice.list_devices())) dev = next(CtapPcscDevice.list_devices(), None) if dev: self.is_nfc = True if not dev: raise RuntimeError("No FIDO device found") self.dev = dev self.client = Fido2Client(dev, self.origin) self.ctap2 = self.client.ctap2 self.ctap1 = CTAP1(dev)
def find_device(self, nfcInterfaceOnly=False): dev = None if not nfcInterfaceOnly: print("--- HID ---") print(list(CtapHidDevice.list_devices())) dev = next(CtapHidDevice.list_devices(), None) if not dev: try: from fido2.pcsc import CtapPcscDevice print("--- NFC ---") print(list(CtapPcscDevice.list_devices())) dev = next(CtapPcscDevice.list_devices(), None) except (ModuleNotFoundError, ImportError): print("One of NFC library is not installed properly.") if not dev: raise RuntimeError("No FIDO device found") self.dev = dev self.client = Fido2Client(dev, self.origin) self.ctap = self.client.ctap2 self.ctap1 = CTAP1(dev)
uv = "preferred" 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 sign_request(public_key, authn_select): """Signs a WebAuthn challenge and returns the data. :param public_key dict containing `rpId` the relying party and `challenge` the received challenge :param authn_select string, that contains the allowed public key of the user :return dict containing clientDataJSON, authenticatorData, signature, credentialId and userHandle if available. """ use_prompt = False pin = None uv = "discouraged" if WindowsClient.is_available( ) and not ctypes.windll.shell32.IsUserAnAdmin(): # Use the Windows WebAuthn API if available, and we're not running as admin client = WindowsClient("https://example.com") else: 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) client = Fido2Client(dev, "http://localhost:8080", verify=lambda x, y: True) # Prefer UV if supported if client.info.options.get("uv"): uv = "preferred" print("Authenticator supports User Verification") elif client.info.options.get("clientPin"): # Prompt for PIN if needed pin = getpass("Please enter PIN: ") else: print("PIN not set, won't use") # the base64 library does not work when padding is missing, so append some allowed_key = base64.urlsafe_b64decode(authn_select + '===') pubKey = PublicKeyCredentialRequestOptions( public_key['challenge'], rp_id=public_key['rpId'], allow_credentials=[ PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, allowed_key) ]) # Authenticate the credential if use_prompt: print("\nTouch your authenticator device now...\n") # Only one cred in allowCredentials, only one response. result = client.get_assertion(pubKey, pin=pin).get_response(0) data = { "clientDataJSON": b64encode(result.client_data), "authenticatorData": b64encode(result.authenticator_data), "signature": b64encode(result.signature), "credentialId": b64encode(result.credential_id), } if result.user_handle: data['userHandle'] = b64encode(result.user_handle) return data
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, 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, 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))