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()
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)
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)
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])
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)
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)
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)
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)
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)
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()
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)
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()
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)
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()
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()
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)
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")
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)
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
def _get_client(self, dev, appId): return U2fClient(dev, appId)