def login_webauthn_route(): """login webauthn route""" user = User.query.filter( User.id == session.get('webauthn_login_user_id')).one_or_none() if not user: return login_manager.unauthorized() form = WebauthnLoginForm() if form.validate_on_submit(): try: assertion = cbor.decode(b64decode(form.assertion.data)) webauthn.authenticate_complete( session.pop('webauthn_login_state'), webauthn_credentials(user), assertion['credentialRawId'], ClientData(assertion['clientDataJSON']), AuthenticatorData(assertion['authenticatorData']), assertion['signature']) regenerate_session() login_user(user) return redirect_after_login() except (KeyError, ValueError) as e: current_app.logger.exception(e) flash('Login error during Webauthn authentication.', 'error') return render_template('auth/login_webauthn.html', form=form)
def authenticate_complete(request): server = get_server() data = cbor.decode(request.body) credential_id = data['credentialId'] client_data = ClientData(data['clientDataJSON']) auth_data = AuthenticatorData(data['authenticatorData']) signature = data['signature'] cred = server.authenticate_complete(request.session.pop('fido_state'), get_user_credentials(request), credential_id, client_data, auth_data, signature) keys = UserKey.objects.filter( user=request.user, key_type=KEY_TYPE_FIDO2, enabled=True, ) for key in keys: if AttestedCredentialData(websafe_decode( key.properties["device"])).credential_id == cred.credential_id: write_session(request, key) res = login(request) return JsonResponse({'status': "OK", "redirect": res["location"]}) return JsonResponse({'status': "err"})
def authenticate_complete(request): credentials = [] username = request.session.get("base_username", request.user.get_username()) server = get_server() credentials = get_user_credentials(username) data = cbor.decode(request.body) credential_id = data['credentialId'] client_data = ClientData(data['clientDataJSON']) auth_data = AuthenticatorData(data['authenticatorData']) signature = data['signature'] cred = server.authenticate_complete(request.session.pop('fido_state'), credentials, credential_id, client_data, auth_data, signature) for k in UserKey.objects.filter(username=username, key_type="FIDO2", enabled=1): if AttestedCredentialData(websafe_decode( k.properties["device"])).credential_id == cred.credential_id: k.last_used = timezone.now() k.save() mfa = {"verified": True, "method": "FIDO2", 'id': k.id} if getattr(settings, "MFA_RECHECK", False): mfa["next_check"] = next_check() request.session["mfa"] = mfa res = login(request) return JsonResponse({'status': "OK", "redirect": res["location"]}) return JsonResponse({'status': "err"})
def authenticate_complete(request): credentials = [] username=request.session.get("base_username",request.user.username) server=getServer() credentials=getUserCredentials(username) data = cbor.loads(request.body)[0] credential_id = data['credentialId'] client_data = ClientData(data['clientDataJSON']) auth_data = AuthenticatorData(data['authenticatorData']) signature = data['signature'] cred = server.authenticate_complete( request.session.pop('fido_state'), credentials, credential_id, client_data, auth_data, signature ) keys = User_Keys.objects.filter(username=username, key_type="FIDO2",enabled=1) import random for k in keys: if AttestedCredentialData(websafe_decode(k.properties["device"])).credential_id == cred.credential_id: k.last_used = timezone.now() k.save() mfa = {"verified": True, "method": "FIDO2"} if getattr(settings, "MFA_RECHECK", False): mfa["next_check"] = int((datetime.datetime.now()+ datetime.timedelta( seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s")) request.session["mfa"] = mfa res=login(request) return HttpResponse(simplejson.dumps({'status':"OK","redirect":res["location"]}),content_type="application/json") return HttpResponse(simplejson.dumps({'status': "err"}))
def authenticate(): global credential, last_challenge if not credential: return HTML.format(content='No credential registered!') if request.method == 'POST': data = request.get_json(force=True) client_data = ClientData(from_js_array(data['clientData'])) auth_data = AuthenticatorData(from_js_array(data['authenticatorData'])) signature = from_js_array(data['signature']) print('clientData', client_data) print('AuthenticatorData', auth_data) # Verify the challenge if client_data.challenge != last_challenge: raise ValueError('Challenge mismatch!') # Verify the signature credential.public_key.verify(auth_data + client_data.hash, signature) print('ASSERTION OK') return 'OK' last_challenge = os.urandom(32) return AUTH_HTML.format(challenge=to_js_array(last_challenge), credential_id=to_js_array( credential.credential_id))
def test_apple_attestation(self): attestation = Attestation.for_type("apple")() self.assertIsInstance(attestation, AppleAttestation) statement = { "alg": -7, "x5c": [ a2b_hex( "30820242308201c9a00302010202060176af5359ff300a06082a8648ce3d0403023048311c301a06035504030c134170706c6520576562417574686e204341203131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e6961301e170d3230313232383136323732345a170d3230313233313136323732345a3081913149304706035504030c4038303966626331313065613835663233613862323435616563363136333530663337646665393632313232373336653431663862646365663334366138306439311a3018060355040b0c114141412043657274696669636174696f6e31133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e69613059301306072a8648ce3d020106082a8648ce3d030107034200041f46a2f159fde354598cdd47e005f1b6e7c9f00ed2a941ec7a88d222f5bcf55d6b078bc5b0be9552d85a974921f5bb848e2bbc3aecd6f71a386d2c87d6eafd37a3553053300c0603551d130101ff04023000300e0603551d0f0101ff0404030204f0303306092a864886f76364080204263024a1220420e56fb6212b3aae885294464fb10184b7fea62c48a6d78e61194e07ae6dacc132300a06082a8648ce3d040302036700306402301de8f0f238eee4f5ae80c59290b51e8c3f79397bf198e444ba162d4fccaab8558b072cf00a7c662f9058ff2a98af61ae0230149403b9643066e73a98d3659563dc4da49bf84e82b2b5bbeaf57755646fa243f36344d44b80a5798203bca023e030c7" # noqa E501 ), a2b_hex( "30820234308201baa003020102021056255395c7a7fb40ebe228d8260853b6300a06082a8648ce3d040303304b311f301d06035504030c164170706c6520576562417574686e20526f6f7420434131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e6961301e170d3230303331383138333830315a170d3330303331333030303030305a3048311c301a06035504030c134170706c6520576562417574686e204341203131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e69613076301006072a8648ce3d020106052b8104002203620004832e872f261491810225b9f5fcd6bb6378b5f55f3fcb045bc735993475fd549044df9bfe19211765c69a1dda050b38d45083401a434fb24d112d56c3e1cfbfcb9891fec0696081bef96cbc77c88dddaf46a5aee1dd515b5afaab93be9c0b2691a366306430120603551d130101ff040830060101ff020100301f0603551d2304183016801426d764d9c578c25a67d1a7de6b12d01b63f1c6d7301d0603551d0e04160414ebae82c4ffa1ac5b51d4cf24610500be63bd7788300e0603551d0f0101ff040403020106300a06082a8648ce3d0403030368003065023100dd8b1a3481a5fad9dbb4e7657b841e144c27b75b876a4186c2b1475750337227efe554457ef648950c632e5c483e70c102302c8a6044dc201fcfe59bc34d2930c1487851d960ed6a75f1eb4acabe38cd25b897d0c805bef0c7f78b07a571c6e80e07" # noqa E501 ), ], } auth_data = AuthenticatorData( a2b_hex( b"c46cef82ad1b546477591d008b08759ec3e6d2ecb4f39474bfea6969925d03b7450000000000000000000000000000000000000000001473d9429f4052d84debd035eb5bb7e716e3b81863a50102032620012158201f46a2f159fde354598cdd47e005f1b6e7c9f00ed2a941ec7a88d222f5bcf55d2258206b078bc5b0be9552d85a974921f5bb848e2bbc3aecd6f71a386d2c87d6eafd37" # noqa E501 )) client_param = a2b_hex( b"0d3ce80fabbc3adb9dd891deabb8db84603ea1fe2da8b5d4b46d6591aab342f3" ) res = attestation.verify(statement, auth_data, client_param) self.assertEqual(res.attestation_type, AttestationType.ANON_CA) self.assertEqual(len(res.trust_path), 2) verify_x509_chain(res.trust_path)
def fido2_login_complete(req): user = None logreq('fido2_login_complete', req) username = req.session.get(FIDO2_USER_KEY, None) if username: user = get_user_model().objects.get(username=username) if user: creds = get_fido2_credentials(user) data = cbor.decode(req.body) crid = data['credentialId'] cdat = ClientData(data['clientDataJSON']) adat = AuthenticatorData(data['authenticatorData']) csig = data['signature'] stat = get_fido2_state(req) try: SERVER.authenticate_complete(stat, creds, crid, cdat, adat, csig) except ValueError as e: logger.warn('Failed fido2 authentication', e) return HttpResponseForbidden() found = False for a in user.authenticators.all(): if a.crid == crid: a.inc_counter() found = True break if not found: logger.warn( f'Failed to bump fido2 counter for cred-id "{crid}" owned by "{user.username}".' ) auth.login(req, user) messages.info( req, f'Welcome back {user.get_full_name() or user.username}.') logger.info(f'login_complete successful for user "{user.username}".') return get_cbor_resp(redirect='/') return HttpResponseForbidden()
def test_packed_attestation(self): attestation = Attestation.for_type('packed')() self.assertIsInstance(attestation, PackedAttestation) statement = { 'alg': -7, 'sig': a2b_hex( b'304502200D15DAF337D727AB4719B4027114A2AC43CD565D394CED62C3D9D1D90825F0B3022100989615E7394C87F4AD91F8FDAE86F7A3326DF332B3633DB088AAC76BFFB9A46B' ), # noqa 'x5c': [ a2b_hex( b'308202B73082019FA00302010202041D31330D300D06092A864886F70D01010B0500302A3128302606035504030C1F59756269636F2050726576696577204649444F204174746573746174696F6E301E170D3138303332383036333932345A170D3139303332383036333932345A306E310B300906035504061302534531123010060355040A0C0959756269636F20414231223020060355040B0C1941757468656E74696361746F72204174746573746174696F6E3127302506035504030C1E59756269636F205532462045452053657269616C203438393736333539373059301306072A8648CE3D020106082A8648CE3D030107034200047D71E8367CAFD0EA6CF0D61E4C6A416BA5BB6D8FAD52DB2389AD07969F0F463BFDDDDDC29D39D3199163EE49575A3336C04B3309D607F6160C81E023373E0197A36C306A302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C0201010404030204303021060B2B0601040182E51C01010404120410F8A011F38C0A4D15800617111F9EDC7D300C0603551D130101FF04023000300D06092A864886F70D01010B050003820101009B904CEADBE1F1985486FEAD02BAEAA77E5AB4E6E52B7E6A2666A4DC06E241578169193B63DADEC5B2B78605A128B2E03F7FE2A98EAEB4219F52220995F400CE15D630CF0598BA662D7162459F1AD1FC623067376D4E4091BE65AC1A33D8561B9996C0529EC1816D1710786384D5E8783AA1F7474CB99FE8F5A63A79FF454380361C299D67CB5CC7C79F0D8C09F8849B0500F6D625408C77CBBC26DDEE11CB581BEB7947137AD4F05AAF38BD98DA10042DDCAC277604A395A5B3EAA88A5C8BB27AB59C8127D59D6BBBA5F11506BF7B75FDA7561A0837C46F025FD54DCF1014FC8D17C859507AC57D4B1DEA99485DF0BA8F34D00103C3EEF2EF3BBFEC7A6613DE' ) ] # noqa } auth_data = AuthenticatorData( a2b_hex( b'0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE124100000003F8A011F38C0A4D15800617111F9EDC7D004060A386206A3AACECBDBB22D601853D955FDC5D11ADFBD1AA6A950D966B348C7663D40173714A9F987DF6461BEADFB9CD6419FFDFE4D4CF2EEC1AA605A4F59BDAA50102032620012158200EDB27580389494D74D2373B8F8C2E8B76FA135946D4F30D0E187E120B423349225820E03400D189E85A55DE9AB0F538ED60736EB750F5F0306A80060FE1B13010560D' )) # noqa client_param = a2b_hex( b'985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF' ) # noqa attestation.verify(statement, auth_data, client_param) statement['sig'] = b'a' * len(statement['sig']) with self.assertRaises(InvalidSignature): attestation.verify(statement, auth_data, client_param)
def authenticate_complete(): if not User.query.count(): abort(404) try: data = cbor.decode(request.get_data()) credential_id = data["credentialId"] client_data = ClientData(data["clientDataJSON"]) auth_data = AuthenticatorData(data["authenticatorData"]) signature = data["signature"] except IndexError: abort(400) user = User.get_by(credential_id) if user is None: abort(401) server.authenticate_complete( session.pop("state"), [user.create_data], credential_id, client_data, auth_data, signature, ) return cbor.encode({"status": "OK"})
def verify_token(self, token): data = cbor2.loads(base64.b64decode(token["token"])) credential_id = data["credentialId"] client_data = ClientData(data["clientDataJSON"]) auth_data = AuthenticatorData(data["authenticatorData"]) signature = data["signature"] state = token["state"] domain = token["domain"] credentials_query = Fido2Device.objects.filter(user=self.user) credentials = [ AttestedCredentialData(cbor2.loads(c.authenticator_data)) for c in credentials_query ] rp = PublicKeyCredentialRpEntity(domain, settings.FIDO2_RP_NAME) fido2 = Fido2Server(rp) try: fido2.authenticate_complete( state, credentials, credential_id, client_data, auth_data, signature, ) return True except ValueError: logger.exception("Error in FIDO2 final authentication") return False
def test_authenticate_complete_invalid_signature(self): rp = RelyingParty("example.com", "Example") server = Fido2Server(rp) state = { "challenge": "GAZPACHO!", "user_verification": USER_VERIFICATION.PREFERRED, } client_data_dict = { "challenge": "GAZPACHO!", "origin": "https://example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) _AUTH_DATA = a2b_hex( "A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D" ) with six.assertRaisesRegex(self, ValueError, "Invalid signature."): server.authenticate_complete( state, [AttestedCredentialData(_ATT_CRED_DATA)], _CRED_ID, client_data, AuthenticatorData(_AUTH_DATA), b"INVALID", )
def assertion_response(): app.logger.debug("/assertion/response") data = cbor.decode(request.get_data()) app.logger.debug('post_data:\n%s', pp.pformat(data)) credential_id = data['id'] raw_id = data['rawId'] client_data = ClientData(data['clientDataJSON']) app.logger.debug('client_data:\n%s', pp.pformat(client_data)) auth_data = AuthenticatorData(data['authenticatorData']) app.logger.debug('auth_data:\n%s', pp.pformat(auth_data)) signature = data['signature'] credential = Credential.get(credential_id) if not credential: abort(404) credential_data = credential.to_credential_data() server.authenticate_complete(session.pop('state'), [credential_data], raw_id, client_data, auth_data, signature) # Checking signCount if auth_data.counter > credential.counter: # TODO: flask_ldapconn is not allow to store integer value credential.counter = str(auth_data.counter) credential.save() else: app.logger.warn('wrong counter: stored counter=%d but %d asserted', credential.counter, auth_data.counter) abort(404) user = credential.user if not user: abort(404) login_user(user) return cbor.encode({'status': 'OK'})
def authenticate_complete(cls, response_data, state_data, user): response = cbor.decode(b64decode(response_data)) state = signing.loads(state_data, salt=U2FDevice.WEB_AUTHN_SALT) credential_id = response["credentialId"] client_data = ClientData(response["clientDataJSON"]) auth_data = AuthenticatorData(response["authenticatorData"]) signature = response["signature"] credentials = U2FDevice.credentials(user) cred = cls.server.authenticate_complete( state, credentials, credential_id, client_data, auth_data, signature, ) credential_id = websafe_encode(cred.credential_id) device = U2FDevice.objects.get(user=user, credential_id=credential_id) if auth_data.counter <= device.counter: # verify counter is increasing raise ValueError( f"login counter is not increasing. {auth_data.counter} <= {device.counter} " ) device.last_used = timezone.now() device.counter = auth_data.counter device.save(update_fields=["last_used", "counter"]) return device
def fido2_keys_user_validate(user_id): keys = list_fido2_keys(user_id) credentials = list(map(lambda k: pickle.loads(base64.b64decode(k.key)), keys)) data = request.get_json() cbor_data = cbor.decode(base64.b64decode(data["payload"])) credential_id = cbor_data['credentialId'] client_data = ClientData(cbor_data['clientDataJSON']) auth_data = AuthenticatorData(cbor_data['authenticatorData']) signature = cbor_data['signature'] Config.FIDO2_SERVER.authenticate_complete( get_fido2_session(user_id), credentials, credential_id, client_data, auth_data, signature ) user_to_verify = get_user_by_id(user_id=user_id) user_to_verify.current_session_id = str(uuid.uuid4()) user_to_verify.logged_in_at = datetime.utcnow() user_to_verify.failed_login_count = 0 save_model_user(user_to_verify) return jsonify({'status': 'OK'})
def test_parse_bytes_get_assertion(self): data = AuthenticatorData(_AUTH_DATA_GA) self.assertEqual(data.rp_id_hash, _RP_ID_HASH) self.assertEqual(data.flags, 0x01) self.assertEqual(data.counter, 29) self.assertIsNone(data.credential_data) self.assertIsNone(data.extensions)
def test_fido_u2f_attestation(self): attestation = Attestation.for_type('fido-u2f')() self.assertIsInstance(attestation, FidoU2FAttestation) statement = { 'sig': a2b_hex( b'30450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA' ), # noqa 'x5c': [ a2b_hex( b'3082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F5' ) ] # noqa } auth_data = AuthenticatorData( a2b_hex( b'1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE41000000000000000000000000000000000000000000403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE420038A5010203262001215820E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1422582027DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91' )) # noqa client_param = a2b_hex( b'687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141' ) # noqa attestation.verify(statement, auth_data, client_param) statement['sig'] = b'a' * len(statement['sig']) with self.assertRaises(InvalidSignature): attestation.verify(statement, auth_data, client_param)
def test_parse_bytes_make_credential(self): data = AuthenticatorData(_AUTH_DATA_MC) self.assertEqual(data.rp_id_hash, _RP_ID_HASH) self.assertEqual(data.flags, 0x41) self.assertEqual(data.counter, 28) self.assertEqual(data.credential_data, _ATT_CRED_DATA) self.assertIsNone(data.extensions)
def page(self) -> CBORPageResult: assert user.id is not None if not is_two_factor_login_enabled(user.id): raise MKGeneralException( _("Two-factor authentication not enabled")) data: dict[str, object] = cbor.decode(request.get_data()) credential_id = data["credentialId"] client_data = ClientData(data["clientDataJSON"]) auth_data = AuthenticatorData(data["authenticatorData"]) signature = data["signature"] logger.debug("ClientData: %r", client_data) logger.debug("AuthenticatorData: %r", auth_data) make_fido2_server().authenticate_complete( session.session_info.webauthn_action_state, [ AttestedCredentialData.unpack_from(v["credential_data"])[0] for v in load_two_factor_credentials(user.id) ["webauthn_credentials"].values() ], credential_id, client_data, auth_data, signature, ) session.session_info.webauthn_action_state = None set_two_factor_completed() return {"status": "OK"}
def authenticate_complete(request): data = cbor.decode(request.body) credential_id = data["credentialId"] client_data = ClientData(data["clientDataJSON"]) auth_data = AuthenticatorData(data["authenticatorData"]) signature = data["signature"] credentials = [ row.credential for row in User.objects.get( pk=request.session['user']).fidocredential_set.all() ] fido_server.authenticate_complete( request.session.pop("state"), credentials, credential_id, client_data, auth_data, signature, ) try: user = User.objects.get(pk=request.session.pop('user')) except User.DoesNotExist: raise Http404 else: # If we're here, we've already confirmed the user has the # username and the password. login(request, user) return HttpResponse( cbor.encode({"status": "OK"}), content_type='application/cbor', )
def clean_authenticator_data(self) -> AuthenticatorData: """Return decoded authenticator data.""" value = self.cleaned_data['authenticator_data'] try: return AuthenticatorData(base64.b64decode(value)) except ValueError: raise ValidationError(_('FIDO 2 response is malformed.'), code='invalid')
def authenticate_complete(self, state, data, user): auth_data = AuthenticatorData(data['authenticatorData']) credential = self.server.authenticate_complete( state, credentials=U2fDevice.get_credentials(user), credential_id=data['credentialId'], client_data=ClientData(data['clientDataJSON']), auth_data=auth_data, signature=data['signature']) return (credential, auth_data)
def sign(self, client_data): authenticator_data = AuthenticatorData.create( sha256(self.app_id), flags=AuthenticatorData.FLAG.USER_PRESENT, counter=0) signature = self.priv_key.sign(authenticator_data + client_data.hash, ec.ECDSA(hashes.SHA256())) return authenticator_data, signature
def _get_assertion_1(seed, rp_id_hash, descriptor, client_data_hash): credential_id = descriptor.id assert softkey_valid_credential_id(seed, credential_id) auth_data = AuthenticatorData.create( rp_id_hash=rp_id_hash, flags=AuthenticatorData.FLAG.USER_PRESENT, counter=0xdeadbeef, ) ed25519priv = softkey_derive_ed25519priv(seed, credential_id) signature = ed25519priv.sign(auth_data + client_data_hash) return AssertionResponse.create(descriptor, auth_data, signature)
def test_none_attestation(self): attestation = Attestation.for_type('none')() self.assertIsInstance(attestation, NoneAttestation) auth_data = AuthenticatorData( a2b_hex( b'0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12410000002BF8A011F38C0A4D15800617111F9EDC7D0040A17370D9C1759005700C8DE77E7DFD3A0A5300E0A26E5213AA40D6DF10EE4028B58B5F34167035D840BEBAE0C5CE8FD05AD9BD33E3BE7D1C558D81AB4803570BA5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C' )) # noqa attestation.verify({}, auth_data, b'deadbeef' * 8) with self.assertRaises(InvalidData): attestation.verify({'not': 'empty'}, auth_data, b'deadbeef' * 8)
def test_none_windows_hello_attestation(self): attestation = Attestation.for_type("none")() self.assertIsInstance(attestation, NoneAttestation) auth_data = AuthenticatorData( a2b_hex( b"54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd945000000006028b017b1d44c02b4b3afcdafc96bb200201decfcd6d6a05c2826d52348afdc70a9800df007845047b1a23706aa6e2f315ca401030339010020590100af59f4ad4f71da800bb91045b267e240e06317f7b2b1d76f78e239a433811faeca58a1869fb00225eb2727f81b6b20cbc18c0ad8d38fa450e8df11b4ad3bc3ee5d13c77ed172fa3af0195ec6ac0c4bac8c950115dfce6d38737cbafefbe117d8401cd56c638043a0d585131bc48a153b17a8dcb96671e15a90ba1b4ff810b138b77ac0a050b039b87b6089dd8dfa45611b992109d554aad3e6b72ac82d801496e4d2d230aa466090bbbf4f5632fe4b588e4f571462378fa6f514a536a5945b223c8d98f730b7cf85de86b98c217090f9e9ebf9643cf3feceeacb837d7a18542e03271cd8c70cf81186cdb63e4cbf4efc0cbbd3c93231b06f19580d0a980264d12143010001" # noqa )) # noqa attestation.verify({}, auth_data, b"deadbeef" * 8) with self.assertRaises(InvalidData): attestation.verify({"not": "empty"}, auth_data, b"deadbeef" * 8)
def authenticate_complete(): data = cbor.loads(request.get_data())[0] credential_id = data['credentialId'] client_data = ClientData(data['clientDataJSON']) auth_data = AuthenticatorData(data['authenticatorData']) signature = data['signature'] credentials = session['userobj']['credentials'] fidoserver.authenticate_complete(credentials, credential_id, session.pop('challenge'), client_data, auth_data, signature) return cbor.dumps({'status': 'OK'})
def _make_authenticator_data( self, rp_id: str, attested_credential_data: Optional[AttestedCredentialData] ) -> AuthenticatorData: flags = (AuthenticatorData.FLAG.USER_PRESENT | AuthenticatorData.FLAG.USER_VERIFIED) if attested_credential_data: flags |= AuthenticatorData.FLAG.ATTESTED rp_id_hash = sha256(rp_id.encode('utf-8')).digest() sig_counter = self._get_timestamp_signature_counter() return AuthenticatorData.create(rp_id_hash, flags, sig_counter, attested_credential_data or b'')
def authenticate_complete(): token = request.cookies.get("token") user_identity = current_user.get_id() database_id, remember_me = get_current_user_info(token, user_identity) if database_id is None: abort(401) user = User.query.filter_by(did=database_id).first() credentials = get_credentials(database_id) if not credentials: abort(401) data = cbor.decode(request.get_data()) credential_id = data["credentialId"] client_data = ClientData(data["clientDataJSON"]) auth_data = AuthenticatorData(data["authenticatorData"]) signature = data["signature"] server.authenticate_complete( session.pop("state"), credentials, credential_id, client_data, auth_data, signature, ) current_counter = int(auth_data.counter) keys = Key.query.filter_by(user_id=database_id).all() for key in keys: if key.credential_id == credential_id: last_counter = key.counter if last_counter is None or last_counter >= current_counter: # Cloned => untrusted key! return cbor.encode({ "status": "error", "reason": "invalid counter" }) key.last_access = datetime.utcnow() key.counter = current_counter break db.session.add(key) db.session.commit() login_user(user, remember=remember_me) return cbor.encode({"status": "OK"})
def test_authenticate_invalid_response(self): fido2_response = { 'client_data': ClientData(base64.b64decode(AUTHENTICATION_CLIENT_DATA)), 'credential_id': base64.b64decode(CREDENTIAL_ID), 'authenticator_data': AuthenticatorData(base64.b64decode(AUTHENTICATOR_DATA)), 'signature': b'INVALID' } self.assertIsNone( self.backend.authenticate(sentinel.request, self.user, self.server, self.state, fido2_response))
class TestFido2GeneralAuthenticationBackend(TestCase): """Test `Fido2GeneralAuthenticationBackend` class.""" backend = Fido2GeneralAuthenticationBackend() server = Fido2Server(PublicKeyCredentialRpEntity(HOSTNAME, HOSTNAME)) state = { 'challenge': AUTHENTICATION_CHALLENGE, 'user_verification': UserVerificationRequirement.PREFERRED } fido2_response = { 'client_data': ClientData(base64.b64decode(AUTHENTICATION_CLIENT_DATA)), 'credential_id': base64.b64decode(CREDENTIAL_ID), 'authenticator_data': AuthenticatorData(base64.b64decode(AUTHENTICATOR_DATA)), 'signature': base64.b64decode(SIGNATURE) } def setUp(self): self.user = User.objects.create_user(USERNAME, password=PASSWORD) self.device = Authenticator.objects.create( user=self.user, credential_id_data=CREDENTIAL_ID, attestation_data=ATTESTATION_OBJECT) def test_authenticate(self): authenticated_user = self.backend.authenticate(sentinel.request, USERNAME, PASSWORD, self.server, self.state, self.fido2_response) self.assertEqual(authenticated_user, self.user) self.assertQuerysetEqual(Authenticator.objects.values_list( 'user', 'counter'), [(self.user.pk, 152)], transform=tuple) def test_authenticate_wrong_password(self): authenticated_user = self.backend.authenticate(sentinel.request, USERNAME, 'wrong_password', self.server, self.state, self.fido2_response) self.assertEqual(authenticated_user, None) self.assertQuerysetEqual(Authenticator.objects.values_list( 'user', 'counter'), [(self.user.pk, 0)], transform=tuple)
def perform_step(self, action): current_app.logger.debug('Performing MFA step') if current_app.config['MFA_TESTING']: current_app.logger.debug('Test mode is on, faking authentication') return { 'success': True, 'testing': True, } if action.old_format: userid = action.user_id user = current_app.central_userdb.get_user_by_id(userid, raise_on_missing=False) else: eppn = action.eppn user = current_app.central_userdb.get_user_by_eppn(eppn, raise_on_missing=False) current_app.logger.debug('Loaded User {} from db (in perform_action)'.format(user)) # Third party service MFA if session.mfa_action.success is True: # Explicit check that success is the boolean True issuer = session.mfa_action.issuer authn_instant = session.mfa_action.authn_instant authn_context = session.mfa_action.authn_context current_app.logger.info('User {} logged in using external mfa service {}'.format(user, issuer)) action.result = { 'success': True, 'issuer': issuer, 'authn_instant': authn_instant, 'authn_context': authn_context } current_app.actions_db.update_action(action) return action.result req_json = request.get_json() if not req_json: current_app.logger.error('No data in request to authn {}'.format(user)) raise self.ActionError('mfa.no-request-data') # Process POSTed data if 'tokenResponse' in req_json: # CTAP1/U2F token_response = request.get_json().get('tokenResponse', '') current_app.logger.debug('U2F token response: {}'.format(token_response)) challenge = session.get(self.PACKAGE_NAME + '.u2f.challenge') current_app.logger.debug('Challenge: {!r}'.format(challenge)) device, counter, touch = complete_authentication(challenge, token_response, current_app.config['U2F_VALID_FACETS']) current_app.logger.debug('U2F authentication data: {}'.format({ 'keyHandle': device['keyHandle'], 'touch': touch, 'counter': counter, })) for this in user.credentials.filter(U2F).to_list(): if this.keyhandle == device['keyHandle']: current_app.logger.info('User {} logged in using U2F token {} (touch: {}, counter {})'.format( user, this, touch, counter)) action.result = {'success': True, 'touch': touch, 'counter': counter, RESULT_CREDENTIAL_KEY_NAME: this.key, } current_app.actions_db.update_action(action) return action.result elif 'authenticatorData' in req_json: # CTAP2/Webauthn req = {} for this in ['credentialId', 'clientDataJSON', 'authenticatorData', 'signature']: try: req_json[this] += ('=' * (len(req_json[this]) % 4)) req[this] = base64.urlsafe_b64decode(req_json[this]) except: current_app.logger.error('Failed to find/b64decode Webauthn parameter {}: {}'.format( this, req_json.get(this))) raise self.ActionError('mfa.bad-token-response') # XXX add bad-token-response to frontend current_app.logger.debug('Webauthn request after decoding:\n{}'.format(pprint.pformat(req))) client_data = ClientData(req['clientDataJSON']) auth_data = AuthenticatorData(req['authenticatorData']) credentials = _get_user_credentials(user) fido2state = json.loads(session[self.PACKAGE_NAME + '.webauthn.state']) rp_id = current_app.config['FIDO2_RP_ID'] fido2rp = RelyingParty(rp_id, 'eduID') fido2server = _get_fido2server(credentials, fido2rp) matching_credentials = [(v['webauthn'], k) for k,v in credentials.items() if v['webauthn'].credential_id == req['credentialId']] if not matching_credentials: current_app.logger.error('Could not find webauthn credential {} on user {}'.format( req['credentialId'], user)) raise self.ActionError('mfa.unknown-token') authn_cred = fido2server.authenticate_complete( fido2state, [mc[0] for mc in matching_credentials], req['credentialId'], client_data, auth_data, req['signature'], ) current_app.logger.debug('Authenticated Webauthn credential: {}'.format(authn_cred)) cred_key = [mc[1] for mc in matching_credentials][0] touch = auth_data.flags counter = auth_data.counter current_app.logger.info('User {} logged in using Webauthn token {} (touch: {}, counter {})'.format( user, cred_key, touch, counter)) action.result = {'success': True, 'touch': auth_data.is_user_present() or auth_data.is_user_verified(), 'user_present': auth_data.is_user_present(), 'user_verified': auth_data.is_user_verified(), 'counter': counter, RESULT_CREDENTIAL_KEY_NAME: cred_key, } current_app.actions_db.update_action(action) return action.result else: current_app.logger.error('Neither U2F nor Webauthn data in request to authn {}'.format(user)) current_app.logger.debug('Request: {}'.format(req_json)) raise self.ActionError('mfa.no-token-response') raise self.ActionError('mfa.unknown-token')