Example #1
0
    def test_sign_await_touch(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = 'U2F_V2'
        client.ctap.authenticate.side_effect = [
            ApduError(APDU.USE_NOT_SATISFIED),
            ApduError(APDU.USE_NOT_SATISFIED),
            ApduError(APDU.USE_NOT_SATISFIED),
            ApduError(APDU.USE_NOT_SATISFIED), SIG_DATA
        ]

        event = Event()
        event.wait = mock.MagicMock()

        resp = client.sign(APP_ID,
                           'challenge', [{
                               'version': 'U2F_V2',
                               'keyHandle': 'a2V5'
                           }],
                           timeout=event)

        event.wait.assert_called()

        client.ctap.get_version.assert_called_with()
        client.ctap.authenticate.assert_called()
        client_param, app_param, key_handle = \
            client.ctap.authenticate.call_args[0]

        self.assertEqual(client_param,
                         sha256(websafe_decode(resp['clientData'])))
        self.assertEqual(app_param, sha256(APP_ID.encode()))
        self.assertEqual(key_handle, b'key')
        self.assertEqual(websafe_decode(resp['signatureData']), SIG_DATA)
def _u2f_sign(device, u2f_app_id, u2f_challenge, u2f_sign_requests, duo_host,
              sid, u2f_response, session, ssl_verification_enabled, cancel,
              rq):
    click.echo("Activate your FIDO U2F authenticator now: '{}'".format(device),
               err=True)
    client = U2fClient(device, u2f_app_id)
    try:
        u2f_response.update(
            client.sign(u2f_app_id,
                        u2f_challenge,
                        u2f_sign_requests,
                        event=cancel))

        # Cancel the other U2F prompts
        cancel.set()

        click.echo(
            "Got response from FIDO U2F authenticator: '{}'".format(device),
            err=True)
        rq.put(
            _submit_u2f_response(duo_host, sid, u2f_response, session,
                                 ssl_verification_enabled))
    except:
        pass
    finally:
        device.close()
Example #3
0
    def test_register_await_timeout(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = "U2F_V2"
        client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA)
        client.ctap.register.side_effect = ApduError(APDU.USE_NOT_SATISFIED)

        client.poll_delay = 0.01
        event = Event()
        timer = Timer(0.1, event.set)
        timer.start()
        try:
            client.register(
                APP_ID,
                [{
                    "version": "U2F_V2",
                    "challenge": "foobar"
                }],
                [{
                    "version": "U2F_V2",
                    "keyHandle": "a2V5"
                }],
                event=event,
            )
        except ClientError as e:
            self.assertEqual(e.code, ClientError.ERR.TIMEOUT)
Example #4
0
    def test_sign_await_touch(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = "U2F_V2"
        client.ctap.authenticate.side_effect = [
            ApduError(APDU.USE_NOT_SATISFIED),
            ApduError(APDU.USE_NOT_SATISFIED),
            ApduError(APDU.USE_NOT_SATISFIED),
            ApduError(APDU.USE_NOT_SATISFIED),
            SIG_DATA,
        ]

        event = Event()
        event.wait = mock.MagicMock()

        resp = client.sign(
            APP_ID,
            "challenge",
            [{"version": "U2F_V2", "keyHandle": "a2V5"}],
            event=event,
        )

        event.wait.assert_called()

        client.ctap.get_version.assert_called_with()
        client.ctap.authenticate.assert_called()
        client_param, app_param, key_handle = client.ctap.authenticate.call_args[0]

        self.assertEqual(client_param, sha256(websafe_decode(resp["clientData"])))
        self.assertEqual(app_param, sha256(APP_ID.encode()))
        self.assertEqual(key_handle, b"key")
        self.assertEqual(websafe_decode(resp["signatureData"]), SIG_DATA)
Example #5
0
    def test_register_existing_key(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = "U2F_V2"
        client.ctap.authenticate.side_effect = ApduError(
            APDU.USE_NOT_SATISFIED)

        try:
            client.register(
                APP_ID,
                [{
                    "version": "U2F_V2",
                    "challenge": "foobar"
                }],
                [{
                    "version": "U2F_V2",
                    "keyHandle": "a2V5"
                }],
                timeout=1,
            )
            self.fail("register did not raise error")
        except ClientError as e:
            self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE)

        client.ctap.get_version.assert_called_with()
        client.ctap.authenticate.assert_called_once()
        # Check keyHandle
        self.assertEqual(client.ctap.authenticate.call_args[0][2], b"key")
        # Ensure check-only was set
        self.assertTrue(client.ctap.authenticate.call_args[0][3])
Example #6
0
    def test_register(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = "U2F_V2"
        client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA)
        client.ctap.register.return_value = REG_DATA

        resp = client.register(
            APP_ID,
            [{
                "version": "U2F_V2",
                "challenge": "foobar"
            }],
            [{
                "version": "U2F_V2",
                "keyHandle": "a2V5"
            }],
        )

        client.ctap.get_version.assert_called_with()
        client.ctap.authenticate.assert_called_once()
        client.ctap.register.assert_called_once()

        client_param, app_param = client.ctap.register.call_args[0]
        self.assertEqual(sha256(websafe_decode(resp["clientData"])),
                         client_param)
        self.assertEqual(websafe_decode(resp["registrationData"]), REG_DATA)
        self.assertEqual(sha256(APP_ID.encode()), app_param)
Example #7
0
    def test_sign(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = 'U2F_V2'
        client.ctap.authenticate.return_value = SIG_DATA

        resp = client.sign(
            APP_ID,
            'challenge',
            [{
                'version': 'U2F_V2',
                'keyHandle': 'a2V5'
            }],
        )

        client.ctap.get_version.assert_called_with()
        client.ctap.authenticate.assert_called_once()
        client_param, app_param, key_handle = \
            client.ctap.authenticate.call_args[0]

        self.assertEqual(client_param,
                         sha256(websafe_decode(resp['clientData'])))
        self.assertEqual(app_param, sha256(APP_ID.encode()))
        self.assertEqual(key_handle, b'key')
        self.assertEqual(websafe_decode(resp['signatureData']), SIG_DATA)
Example #8
0
    def test_register_await_touch(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = 'U2F_V2'
        client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA)
        client.ctap.register.side_effect = [
            ApduError(APDU.USE_NOT_SATISFIED),
            ApduError(APDU.USE_NOT_SATISFIED),
            ApduError(APDU.USE_NOT_SATISFIED),
            ApduError(APDU.USE_NOT_SATISFIED), REG_DATA
        ]

        event = Event()
        event.wait = mock.MagicMock()
        resp = client.register(APP_ID, [{
            'version': 'U2F_V2',
            'challenge': 'foobar'
        }], [{
            'version': 'U2F_V2',
            'keyHandle': 'a2V5'
        }],
                               timeout=event)

        event.wait.assert_called()

        client.ctap.get_version.assert_called_with()
        client.ctap.authenticate.assert_called_once()
        client.ctap.register.assert_called()

        client_param, app_param = client.ctap.register.call_args[0]
        self.assertEqual(sha256(websafe_decode(resp['clientData'])),
                         client_param)
        self.assertEqual(websafe_decode(resp['registrationData']), REG_DATA)
        self.assertEqual(sha256(APP_ID.encode()), app_param)
Example #9
0
 def test_register_wrong_app_id(self):
     client = U2fClient(None, APP_ID)
     try:
         client.register(
             "https://bar.example.com",
             [{"version": "U2F_V2", "challenge": "foobar"}],
             [],
         )
         self.fail("register did not raise error")
     except ClientError as e:
         self.assertEqual(e.code, ClientError.ERR.BAD_REQUEST)
Example #10
0
 def test_register_wrong_app_id(self):
     client = U2fClient(None, APP_ID)
     try:
         client.register('https://bar.example.com', [{
             'version': 'U2F_V2',
             'challenge': 'foobar'
         }], [],
                         timeout=1)
         self.fail('register did not raise error')
     except ClientError as e:
         self.assertEqual(e.code, ClientError.ERR.BAD_REQUEST)
Example #11
0
    def test_register_unsupported_version(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = "U2F_XXX"

        try:
            client.register(APP_ID, [{"version": "U2F_V2", "challenge": "foobar"}], [])
            self.fail("register did not raise error")
        except ClientError as e:
            self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE)

        client.ctap.get_version.assert_called_with()
Example #12
0
    def test_sign_wrong_app_id(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = "U2F_V2"

        try:
            client.sign(
                "http://foo.example.com",
                "challenge",
                [{"version": "U2F_V2", "keyHandle": "a2V5"}],
            )
            self.fail("sign did not raise error")
        except ClientError as e:
            self.assertEqual(e.code, ClientError.ERR.BAD_REQUEST)
Example #13
0
    def test_sign_unsupported_version(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = "U2F_XXX"

        try:
            client.sign(
                APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}]
            )
            self.fail("sign did not raise error")
        except ClientError as e:
            self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE)

        client.ctap.get_version.assert_called_with()
Example #14
0
    def test_sign_wrong_app_id(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = 'U2F_V2'

        try:
            client.sign('http://foo.example.com', 'challenge',
                        [{
                            'version': 'U2F_V2',
                            'keyHandle': 'a2V5'
                        }])
            self.fail('sign did not raise error')
        except ClientError as e:
            self.assertEqual(e.code, ClientError.ERR.BAD_REQUEST)
Example #15
0
    def test_sign_unsupported_version(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = 'U2F_XXX'

        try:
            client.sign(APP_ID, 'challenge', [{
                'version': 'U2F_V2',
                'keyHandle': 'a2V5'
            }])
            self.fail('sign did not raise error')
        except ClientError as e:
            self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE)

        client.ctap.get_version.assert_called_with()
Example #16
0
    def test_register_unsupported_version(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = 'U2F_XXX'

        try:
            client.register(APP_ID, [{
                'version': 'U2F_V2',
                'challenge': 'foobar'
            }], [],
                            timeout=1)
            self.fail('register did not raise error')
        except ClientError as e:
            self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE)

        client.ctap.get_version.assert_called_with()
Example #17
0
    def test_sign(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = "U2F_V2"
        client.ctap.authenticate.return_value = SIG_DATA

        resp = client.sign(
            APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}]
        )

        client.ctap.get_version.assert_called_with()
        client.ctap.authenticate.assert_called_once()
        client_param, app_param, key_handle = client.ctap.authenticate.call_args[0]

        self.assertEqual(client_param, sha256(websafe_decode(resp["clientData"])))
        self.assertEqual(app_param, sha256(APP_ID.encode()))
        self.assertEqual(key_handle, b"key")
        self.assertEqual(websafe_decode(resp["signatureData"]), SIG_DATA)
Example #18
0
    def test_sign_missing_key(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = "U2F_V2"
        client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA)

        try:
            client.sign(
                APP_ID, "challenge", [{"version": "U2F_V2", "keyHandle": "a2V5"}]
            )
            self.fail("sign did not raise error")
        except ClientError as e:
            self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE)

        client.ctap.get_version.assert_called_with()
        client.ctap.authenticate.assert_called_once()
        _, app_param, key_handle = client.ctap.authenticate.call_args[0]
        self.assertEqual(app_param, sha256(APP_ID.encode()))
        self.assertEqual(key_handle, b"key")
Example #19
0
    def test_register_await_timeout(self):
        client = U2fClient(None, APP_ID)
        client.ctap = mock.MagicMock()
        client.ctap.get_version.return_value = 'U2F_V2'
        client.ctap.authenticate.side_effect = ApduError(APDU.WRONG_DATA)
        client.ctap.register.side_effect = ApduError(APDU.USE_NOT_SATISFIED)

        client.poll_delay = 0.01
        try:
            client.register(APP_ID, [{
                'version': 'U2F_V2',
                'challenge': 'foobar'
            }], [{
                'version': 'U2F_V2',
                'keyHandle': 'a2V5'
            }],
                            timeout=0.1)
        except ClientError as e:
            self.assertEqual(e.code, ClientError.ERR.TIMEOUT)
Example #20
0
def yubikey_authenticate(request):  # type: (dict) -> Optional[dict]
    auth_func = None  # type: Optional[Callable[[], Union[AuthenticatorAssertionResponse, dict, None]]]
    evt = threading.Event()
    response = None  # type: Optional[str]

    if 'authenticateRequests' in request:  # U2F

        options = request['authenticateRequests']
        origin = options[0].get('appId') or ''
        challenge = options[0]['challenge']
        keys = [{
            'version': x.get('version') or '',
            'keyHandle': x['keyHandle']
        } for x in options if 'keyHandle' in x]

        dev = next(CtapHidDevice.list_devices(), None)
        if not dev:
            logging.warning("No Security Key detected")
            return
        client = U2fClient(dev, origin)

        def auth_func():
            nonlocal response
            response = client.sign(origin, challenge, keys, event=evt)

    elif 'publicKeyCredentialRequestOptions' in request:  # WebAuthN
        origin = ''
        options = request['publicKeyCredentialRequestOptions']
        if 'extensions' in options:
            extensions = options['extensions']
            origin = extensions.get('appid') or ''

        credentials = options.get('allowCredentials') or []
        for c in credentials:
            if isinstance(c.get('id'), str):
                c['id'] = utils.base64_url_decode(c['id'])

        rq_options = PublicKeyCredentialRequestOptions(
            utils.base64_url_decode(options['challenge']),
            rp_id=options['rpId'],
            user_verification='discouraged',
            allow_credentials=credentials)

        if WindowsClient.is_available():
            client = WindowsClient(origin, verify=verify_rp_id_none)
        else:
            dev = next(CtapHidDevice.list_devices(), None)
            if not dev:
                logging.warning("No Security Key detected")
                return
            client = Fido2Client(dev, origin, verify=verify_rp_id_none)

        def auth_func():
            nonlocal response
            nonlocal rq_options
            attempt = 0
            while attempt < 2:
                attempt += 1
                try:
                    rs = client.get_assertion(rq_options, event=evt)
                    response = rs.get_response(0)
                    break
                except ClientError as err:
                    if isinstance(err.cause, CtapError) and attempt == 1:
                        if err.cause.code == CtapError.ERR.NO_CREDENTIALS:
                            print(
                                '\n\nKeeper Security stopped supporting U2F security keys starting February 2022.\n'
                                'If you registered your security key prior to this date please re-register it within the Web Vault.\n'
                                'For information on using security keys with Keeper see the documentation: \n'
                                'https://docs.keeper.io/enterprise-guide/two-factor-authentication#security-keys-fido-webauthn\n'
                                'Commander will use the fallback security key authentication method.\n\n'
                                'To use your Yubikey with Commander, please touch the flashing Security key one more time.\n'
                            )
                            rq_options = PublicKeyCredentialRequestOptions(
                                utils.base64_url_decode(options['challenge']),
                                rp_id=origin,
                                user_verification='discouraged',
                                allow_credentials=credentials)
                            continue
                    raise err
    else:
        logging.warning('Invalid Security Key request')
        return

    prompt_session = None

    def func():
        nonlocal prompt_session
        nonlocal evt
        try:
            time.sleep(0.1)
            auth_func()
        except:
            pass
        if prompt_session:
            evt = None
            prompt_session.app.exit()
        elif evt:
            print('\npress Enter to resume...')

    th = threading.Thread(target=func)
    th.start()
    try:
        prompt = 'Touch the flashing Security key to authenticate or press Enter to resume with the primary two factor authentication...'
        if os.isatty(0) and os.isatty(1):
            prompt_session = PromptSession(multiline=False,
                                           complete_while_typing=False)
            prompt_session.prompt(prompt)
            prompt_session = None
        else:
            input(prompt)
    except KeyboardInterrupt:
        prompt_session = None
    if evt:
        evt.set()
        evt = None
    th.join()

    return response
Example #21
0
 def _get_client(self, dev, appId):
     return U2fClient(dev, appId)