Example #1
0
    def __init__(self,
                 ui,
                 okta_org_url,
                 challenge,
                 credential_id=None,
                 timeout_ms=30_000):
        """
        :param okta_org_url: Base URL string for Okta IDP.
        :param challenge: Challenge
        :param credential_id: FIDO credential ID
        """
        self.ui = ui
        self._okta_org_url = okta_org_url
        self._clients = None
        self._has_prompted = False
        self._challenge = websafe_decode(challenge)
        self._timeout_ms = timeout_ms
        self._event = Event()
        self._assertions = None
        self._client_data = None
        self._rp = {'id': okta_org_url[8:], 'name': okta_org_url[8:]}

        if credential_id:
            self._allow_list = [
                PublicKeyCredentialDescriptor(
                    PublicKeyCredentialType.PUBLIC_KEY,
                    websafe_decode(credential_id))
            ]
    def _get_assertion_request(
        rp_id: str = 'pasten.com',
        client_data: Optional[ClientData] = None,
        allowed_cred_ids: Optional[List[bytes]] = None,
        user_verification_required=False,
    ) -> dict:

        client_data = client_data or ClientData.build(
            typ=WEBAUTHN_TYPE.GET_ASSERTION,
            origin=rp_id,
            challenge=websafe_encode(b'pasten-challenge'),
        )
        req = {
            CtapGetAssertionRequest.RP_ID_KEY: rp_id,
            CtapGetAssertionRequest.CLIENT_DATA_HASH_KEY: client_data.hash,
        }

        if user_verification_required:
            req[CtapGetAssertionRequest.OPTIONS_KEY] = {
                CtapOptions.USER_VERIFICATION: True
            }

        if allowed_cred_ids:
            req[CtapGetAssertionRequest.ALLOW_LIST_KEY] = [
                PublicKeyCredentialDescriptor(
                    PublicKeyCredentialType.PUBLIC_KEY, cred_id
                )
                for cred_id in allowed_cred_ids
            ]

        return req
Example #3
0
def cred(rp, user, credset=None, attset=None, prompt=None):
    assert set(['id', 'name']) <= set(rp.keys()) <= set(['id', 'name', 'icon'])
    assert set(['id', 'name']) <= set(user.keys())
    assert set(user.keys()) <= set(['id', 'name', 'icon', 'display_name'])
    assert isinstance(user['id'], bytes)
    credset_dict = {} if credset is None else credset_decode(credset)
    attset_dict = {} if attset is None else attset_decode(attset)

    server = cred_server(rp)
    challenge = cred_challenge(rp, user)
    create_options, state = server.register_begin(
        user,
        credentials=[
            PublicKeyCredentialDescriptor(
                type=PublicKeyCredentialType.PUBLIC_KEY,
                id=credential_id,
            ) for credential_id in sorted(credset_dict.keys())
        ],
        challenge=challenge,
    )

    lock = threading.Lock()
    prompted = [False]

    def on_keepalive(status):
        if status == STATUS.UPNEEDED:
            done = False
            with lock:
                done = prompted[0]
                prompted[0] = True
            if not done:
                prompt()

    def per_device(dev, cancel_ev=None):
        client = Fido2Client(dev, fidosig_origin(rp['id']), verify_origin)
        return client.make_credential(
            create_options['publicKey'],
            on_keepalive=on_keepalive if prompt is not None else None,
            **({} if cancel_ev is None else {
                'event': cancel_ev
            }))

    attestation_object, client_data = iterdevs(per_device)

    auth_data = server.register_complete(state, client_data,
                                         attestation_object)

    credential_id = auth_data.credential_data.credential_id
    public_key = auth_data.credential_data.public_key

    # XXX Check rather than assert?  Overwrite?
    if credential_id in credset_dict or credential_id in attset_dict:
        raise Exception('Duplicate credential id')
    credset_dict[credential_id] = public_key
    attset_dict[credential_id] = attestation_object
    return credset_encode(credset_dict), attset_encode(attset_dict)
Example #4
0
 def create(cls, get_assertion_req: dict):
     # noinspection PyProtectedMember
     return CtapGetAssertionRequest(
         rp_id=get_assertion_req.get(cls.RP_ID_KEY),
         client_data_hash=get_assertion_req.get(cls.CLIENT_DATA_HASH_KEY),
         allow_list=PublicKeyCredentialDescriptor._wrap_list(
             get_assertion_req.get(cls.ALLOW_LIST_KEY)),
         extensions=get_assertion_req.get(cls.EXTENSIONS_KEY),
         options=get_assertion_req.get(cls.OPTIONS_KEY),
         pin_auth=get_assertion_req.get(cls.PIN_AUTH_KEY),
         pin_protocol=get_assertion_req.get(cls.PIN_PROTOCOL_KEY),
     )
Example #5
0
    def test_descriptor(self):
        o = PublicKeyCredentialDescriptor("public-key", b"credential_id")
        self.assertEqual(o, {"type": "public-key", "id": b"credential_id"})
        self.assertEqual(o.type, "public-key")
        self.assertEqual(o.id, b"credential_id")
        self.assertIsNone(o.transports)

        o = PublicKeyCredentialDescriptor(
            "public-key", b"credential_id", ["usb", "nfc"]
        )
        self.assertEqual(
            o,
            {
                "type": "public-key",
                "id": b"credential_id",
                "transports": ["usb", "nfc"],
            },
        )
        self.assertEqual(o.transports, ["usb", "nfc"])

        PublicKeyCredentialDescriptor("public-key", b"credential_id", ["valid_value"])

        with self.assertRaises(ValueError):
            PublicKeyCredentialDescriptor("wrong-type", b"credential_id")

        with self.assertRaises(TypeError):
            PublicKeyCredentialDescriptor("wrong-type")

        with self.assertRaises(TypeError):
            PublicKeyCredentialDescriptor()
Example #6
0
def softsign(softkey,
             rp,
             credset,
             msg,
             sigset=None,
             header=None,
             randomization=None,
             prompt=None):
    seed = softkey_decode(softkey)
    credset_dict = credset_decode(credset)
    sigset_dict = {} if sigset is None else sigset_decode(sigset)
    if header is None:
        header = b''
    assert isinstance(header, bytes)  # bytes, not Unicode text
    if randomization is None:
        randomization = os.urandom(24)

    credential_ids = [
        credential_id for credential_id in sorted(credset_dict.keys())
        if softkey_valid_credential_id(seed, credential_id)
    ]
    if len(credential_ids) == 0:
        raise FileNotFoundError

    server = sign_server(rp)
    challenge = sign_challenge(randomization, header, msg)
    descriptors = [
        PublicKeyCredentialDescriptor(
            type=PublicKeyCredentialType.PUBLIC_KEY,
            id=credential_id,
        ) for credential_id in credential_ids
    ]

    request_options, state = server.authenticate_begin(
        credentials=descriptors,
        user_verification=UserVerificationRequirement.DISCOURAGED,
        challenge=challenge,
    )

    assertions, client_data = \
        _get_assertions(seed, rp, request_options['publicKey'])
    assert len(assertions) == len(credential_ids)

    for credential_id, assertion in zip(credential_ids, assertions):
        # XXX Warn if we're overwriting?
        sigset_dict[credential_id] = {
            SIGENTRY.RANDOMIZATION: randomization,
            SIGENTRY.AUTH_DATA: assertion.auth_data,
            SIGENTRY.SIGNATURE: assertion.signature,
        }

    return sigset_encode(sigset_dict)
Example #7
0
def derivekey(pkconfs, rp, user, prompt=None):
    server = _fidokdf_server(rp)
    challenge = os.urandom(32)
    descriptors = [
        PublicKeyCredentialDescriptor(
            type=PublicKeyCredentialType.PUBLIC_KEY,
            id=credential_id,
        ) for credential_id in sorted(pkconfs.keys())
    ]

    request_options, state = server.authenticate_begin(
        credentials=descriptors,
        user_verification=UserVerificationRequirement.DISCOURAGED,
        challenge=challenge,
    )

    lock = threading.Lock()
    prompted = [False]

    def on_keepalive(status):
        if status == STATUS.UPNEEDED:
            done = False
            with lock:
                done = prompted[0]
                prompted[0] = True
            if not done:
                prompt()

    def per_device(dev, cancel_ev=None):
        client = Fido2Client(dev, fidokdf_origin(rp['id']), verify_origin)
        return client.get_assertion(
            request_options['publicKey'],
            on_keepalive=on_keepalive if prompt is not None else None,
            **({} if cancel_ev is None else {
                'event': cancel_ev
            }))

    assertions, client_data = iterdevs(per_device)

    return {
        assertion.credential['id']: server.authenticate_complete_kdf(
            state,
            pkconfs,
            assertion.credential['id'],
            client_data,
            assertion.auth_data,
            assertion.signature,
        )
        for assertion in assertions
    }
Example #8
0
def encrypt(payload, rp, user, exclude_credential_ids=set(), prompt=None):
    server = _fidocrypt_server(rp)
    challenge = os.urandom(32)
    create_options, state = server.register_begin(
        user,
        credentials=[
            PublicKeyCredentialDescriptor(
                type=PublicKeyCredentialType.PUBLIC_KEY,
                id=credential_id,
            ) for credential_id in sorted(exclude_credential_ids)
        ],
        challenge=challenge,
    )

    lock = threading.Lock()
    prompted = [False]

    def on_keepalive(status):
        if status == STATUS.UPNEEDED:
            done = False
            with lock:
                done = prompted[0]
                prompted[0] = True
            if not done:
                prompt()

    def per_device(dev, cancel_ev=None):
        client = Fido2Client(dev, fidocrypt_origin(rp['id']), verify_origin)
        return client.make_credential(
            create_options['publicKey'],
            on_keepalive=on_keepalive if prompt is not None else None,
            **({} if cancel_ev is None else {
                'event': cancel_ev
            }))

    attestation_object, client_data = iterdevs(per_device)

    # Strip out the device attestation for privacy.
    attestation_object = AttestationObject.create(
        NoneAttestation.FORMAT,
        attestation_object.auth_data,
        {},
    )

    return server.register_complete_encrypt(state, payload, client_data,
                                            attestation_object)
Example #9
0
 def _get_assertion(self, request: CtapGetAssertionRequest,
                    ctx: CtapGetNextAssertionContext) -> AssertionResponse:
     cred = ctx.get_next_cred()
     authenticator_data = self._make_authenticator_data(
         request.rp_id, attested_credential_data=None)
     signature = self._generate_signature(authenticator_data,
                                          request.client_data_hash,
                                          cred.private_key)
     response = AssertionResponse.create(
         PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY,
                                       cred.id),
         authenticator_data,
         signature,
         user=PublicKeyCredentialUserEntity(cred.user_id, name=''),
         n_creds=len(ctx.creds),
     )
     return response
    def _get_make_credential_request(
        algorithm: int = cose.RS256.ALGORITHM,
        user_id: str = '*****@*****.**',
        user_name: str = 'pasten',
        rp_id: str = 'pasten.com',
        rp_name: str = 'Pasten LTD',
        client_data: Optional[ClientData] = None,
        excluded_cred_ids: Optional[List[bytes]] = None,
    ) -> dict:

        client_data = client_data or ClientData.build(
            typ=WEBAUTHN_TYPE.MAKE_CREDENTIAL,
            origin=rp_id,
            challenge=websafe_encode(b'pasten-challenge'),
        )

        req = {
            CtapMakeCredentialRequest.CLIENT_DATA_HASH_KEY: hashlib.sha256(
                client_data
            ).digest(),
            CtapMakeCredentialRequest.RP_KEY: PublicKeyCredentialRpEntity(
                rp_id, rp_name
            ),
            CtapMakeCredentialRequest.USER_KEY: PublicKeyCredentialUserEntity(
                user_id, user_name
            ),
            CtapMakeCredentialRequest.PUBLIC_KEY_CREDENTIAL_PARAMS_KEY: [
                PublicKeyCredentialParameters(
                    PublicKeyCredentialType.PUBLIC_KEY, algorithm
                )
            ],
        }

        if excluded_cred_ids:
            req[CtapMakeCredentialRequest.EXCLUDE_LIST_KEY] = [
                PublicKeyCredentialDescriptor(
                    PublicKeyCredentialType.PUBLIC_KEY, cred_id
                )
                for cred_id in excluded_cred_ids
            ]

        return req
 def __init__(
     self,
     challenge,
     timeout=None,
     rp_id=None,
     allow_credentials=None,
     user_verification=None,
     extensions=None,
     user_presence=None,
 ):
     super(PublicKeyCredentialRequestOptions, self).__init__(
         challenge=challenge,
         timeout=timeout,
         rp_id=rp_id,
         allow_credentials=PublicKeyCredentialDescriptor._wrap_list(
             allow_credentials),
         user_verification=UserVerificationRequirement._wrap(
             user_verification),
         extensions=extensions,
         user_presence=UserPresenceRequirement._wrap(user_presence))
Example #12
0
 def create(cls, make_credential_request: dict):
     # noinspection PyProtectedMember
     return CtapMakeCredentialRequest(
         client_data_hash=make_credential_request.get(
             cls.CLIENT_DATA_HASH_KEY),
         rp=PublicKeyCredentialRpEntity._wrap(
             make_credential_request.get(cls.RP_KEY)),
         user=PublicKeyCredentialUserEntity._wrap(
             make_credential_request.get(cls.USER_KEY)),
         public_key_credential_params=PublicKeyCredentialParameters.
         _wrap_list(
             make_credential_request.get(
                 cls.PUBLIC_KEY_CREDENTIAL_PARAMS_KEY)),
         exclude_list=PublicKeyCredentialDescriptor._wrap_list(
             make_credential_request.get(cls.EXCLUDE_LIST_KEY)),
         extensions=make_credential_request.get(cls.EXTENSIONS_KEY),
         options=make_credential_request.get(cls.OPTIONS_KEY),
         pin_auth=make_credential_request.get(cls.PIN_AUTH_KEY),
         pin_protocol=make_credential_request.get(cls.PIN_PROTOCOL_KEY),
     )
def test_cryptserver_register():
    server = Fido2CryptServer(
        RP,
        verify_origin=rp_origin_verifier(RP['id']),
    )
    create_options, state = server.register_begin(
        USER,
        credentials=[PublicKeyCredentialDescriptor(
            type=PublicKeyCredentialType.PUBLIC_KEY,
            id=CREDENTIAL_ID,
        )],
        challenge=REGISTRATION['challenge'],
    )

    registered_credentials = server.register_complete_encrypt(
        state,
        PAYLOAD,
        REGISTRATION['client_data'],
        REGISTRATION['attestation_object'],
    )
    assert registered_credentials[CREDENTIAL_ID] == CIPHERTEXT
def test_cryptserver_authenticate():
    server = Fido2CryptServer(
        RP,
        verify_origin=rp_origin_verifier(RP['id']),
    )
    request_options, state = server.authenticate_begin(
        credentials=[PublicKeyCredentialDescriptor(
            type=PublicKeyCredentialType.PUBLIC_KEY,
            id=CREDENTIAL_ID,
        )],
        user_verification=UserVerificationRequirement.DISCOURAGED,
        challenge=AUTHENTICATION['challenge'],
    )
    payload = server.authenticate_complete_decrypt(
        state,
        {CREDENTIAL_ID: CIPHERTEXT},
        CREDENTIAL_ID,
        AUTHENTICATION['client_data'],
        AUTHENTICATION['auth_data'],
        AUTHENTICATION['signature'],
    )
    assert payload == PAYLOAD
Example #15
0
def test_kdfserver_register():
    server = Fido2KDFServer(
        RP,
        verify_origin=rp_origin_verifier(RP['id']),
    )
    create_options, state = server.register_begin(
        USER,
        credentials=[
            PublicKeyCredentialDescriptor(
                type=PublicKeyCredentialType.PUBLIC_KEY,
                id=CREDENTIAL_ID,
            )
        ],
        challenge=REGISTRATION['challenge'],
    )

    registered_credentials, key = server.register_complete_kdf(
        state,
        REGISTRATION['client_data'],
        REGISTRATION['attestation_object'],
    )
    assert registered_credentials == {CREDENTIAL_ID: PKCONF}
    assert key == KEY
Example #16
0
def test_kdfserver_authenticate():
    server = Fido2KDFServer(
        RP,
        verify_origin=rp_origin_verifier(RP['id']),
    )
    request_options, state = server.authenticate_begin(
        credentials=[
            PublicKeyCredentialDescriptor(
                type=PublicKeyCredentialType.PUBLIC_KEY,
                id=CREDENTIAL_ID,
            )
        ],
        user_verification=UserVerificationRequirement.DISCOURAGED,
        challenge=AUTHENTICATION['challenge'],
    )
    key = server.authenticate_complete_kdf(
        state,
        {CREDENTIAL_ID: PKCONF},
        CREDENTIAL_ID,
        AUTHENTICATION['client_data'],
        AUTHENTICATION['auth_data'],
        AUTHENTICATION['signature'],
    )
    assert key == KEY
Example #17
0
def sign(rp,
         credset,
         msg,
         sigset=None,
         header=None,
         randomization=None,
         prompt=None):
    credset_dict = credset_decode(credset)
    sigset_dict = {} if sigset is None else sigset_decode(sigset)
    if header is None:
        header = b''
    assert isinstance(header, bytes)  # bytes, not Unicode text
    if randomization is None:
        randomization = os.urandom(24)

    server = sign_server(rp)
    challenge = sign_challenge(randomization, header, msg)
    descriptors = [
        PublicKeyCredentialDescriptor(
            type=PublicKeyCredentialType.PUBLIC_KEY,
            id=credential_id,
        ) for credential_id in sorted(credset_dict.keys())
    ]

    request_options, state = server.authenticate_begin(
        credentials=descriptors,
        user_verification=UserVerificationRequirement.DISCOURAGED,
        challenge=challenge,
    )

    lock = threading.Lock()
    prompted = [False]

    def on_keepalive(status):
        if status == STATUS.UPNEEDED:
            done = False
            with lock:
                done = prompted[0]
                prompted[0] = True
            if not done:
                prompt()

    def per_device(dev, cancel_ev=None):
        client = Fido2Client(dev, fidosig_origin(rp['id']), verify_origin)
        return client.get_assertion(
            request_options['publicKey'],
            on_keepalive=on_keepalive if prompt is not None else None,
            **({} if cancel_ev is None else {
                'event': cancel_ev
            }))

    assertions, client_data = iterdevs(per_device)
    assert len(assertions) >= 1

    for assertion in assertions:
        credential_id = assertion.credential['id']
        auth_data = assertion.auth_data
        signature = assertion.signature

        # Verify the signature before we return it.
        if credential_id not in credset_dict:
            raise Exception('unknown credential')
        credentials = [
            AttestedCredentialData.create(
                b'\0' * 16,
                credential_id,
                credset_dict[credential_id],
            )
        ]
        server.authenticate_complete(
            state,
            credentials,
            credential_id,
            client_data,
            auth_data,
            signature,
        )

        # XXX Warn if we're overwriting?
        sigset_dict[credential_id] = {
            SIGENTRY.RANDOMIZATION: randomization,
            SIGENTRY.AUTH_DATA: assertions[0].auth_data,
            SIGENTRY.SIGNATURE: assertions[0].signature,
        }

    return sigset_encode(sigset_dict)
Example #18
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