Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 3
0
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")
Esempio n. 4
0
def enumerate_devices():
    for dev in CtapHidDevice.list_devices():
        yield dev
    if CtapPcscDevice:
        for dev in CtapPcscDevice.list_devices():
            yield dev
Esempio n. 5
0
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
Esempio n. 6
0
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))
Esempio n. 7
0
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
        )
    )
Esempio n. 8
0
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))