def test_rp_entity(self): o = PublicKeyCredentialRpEntity("Example", "example.com") self.assertEqual(o, {"id": "example.com", "name": "Example"}) self.assertEqual(o.id, "example.com") self.assertEqual(o.name, "Example") with self.assertRaises(TypeError): PublicKeyCredentialRpEntity(id="example.com") with self.assertRaises(TypeError): PublicKeyCredentialRpEntity()
def test_update_value(self): o = PublicKeyCredentialRpEntity("example.com", "Example") self.assertEqual(o, {"id": "example.com", "name": "Example"}) self.assertEqual(o.id, "example.com") self.assertEqual(o.name, "Example") o.id = "new-id.com" self.assertEqual(o, {"id": "new-id.com", "name": "Example"}) self.assertEqual(o.id, "new-id.com") o["name"] = "New Name" self.assertEqual(o, {"id": "new-id.com", "name": "New Name"}) self.assertEqual(o.name, "New Name")
def test_authenticate_complete_invalid_signature(self): rp = PublicKeyCredentialRpEntity("example.com", "Example") server = Fido2Server(rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.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 test_u2f(self): rp = PublicKeyCredentialRpEntity("example.com", "Example", "http://example.com/icon.svg") app_id = b"https://example.com" server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.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")) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, )
def test_authenticate_complete_invalid_signature(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://example.com", ) _AUTH_DATA = bytes.fromhex( "A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D" ) with self.assertRaisesRegex(ValueError, "Invalid signature."): server.authenticate_complete( state, [AttestedCredentialData(_ATT_CRED_DATA)], _CRED_ID, client_data, AuthenticatorData(_AUTH_DATA), b"INVALID", )
def test_u2f(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") app_id = b"https://example.com" server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://example.com", ) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, )
def test_register_begin_custom_challenge_too_short(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) challenge = b"123456789012345" with self.assertRaises(ValueError): request, state = server.register_begin(USER, challenge=challenge)
def test_u2f_facets(self): rp = PublicKeyCredentialRpEntity("example.com", "Example", "http://example.com/icon.svg") app_id = b"https://www.example.com/facets.json" def verify_u2f_origin(origin): return origin in ("https://oauth.example.com", "https://admin.example.com") server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=rp, verify_u2f_origin=verify_u2f_origin) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data_dict = { "challenge": "GAZPACHO!", "origin": "https://oauth.example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, ) # Now with something not whitelisted client_data_dict = { "challenge": "GAZPACHO!", "origin": "https://publicthingy.example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) authenticator_data, signature = device.sign(client_data) with six.assertRaisesRegex(self, ValueError, "Invalid origin in " "ClientData."): server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, )
def test_register_begin_custom_challenge(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) challenge = b"1234567890123456" request, state = server.register_begin(USER, challenge=challenge) self.assertEqual(request["publicKey"]["challenge"], challenge)
def test_register_begin_rp(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) request, state = server.register_begin(USER) self.assertEqual(request["publicKey"]["rp"], { "id": "example.com", "name": "Example" })
def setUp(self): self.u2f = U2fInterface() self.login_as(user=self.user) rp = PublicKeyCredentialRpEntity("richardmasentry.ngrok.io", "Sentry") self.test_registration_server = Fido2Server(rp, verify_origin=verifiy_origin) self.test_authentication_server = U2FFido2Server( app_id="http://richardmasentry.ngrok.io/auth/2fa/u2fappid.json", rp={"id": "richardmasentry.ngrok.io", "name": "Sentry"}, verify_u2f_origin=verifiy_origin, )
def server(self) -> Fido2Server: """Return FIDO 2 server instance.""" rp = PublicKeyCredentialRpEntity(self.get_rp_id(), self.get_rp_name()) if AttestationVerifier is None: return Fido2Server(rp, attestation=self.attestation, attestation_types=self.attestation_types) elif self.verify_attestation is None: if self.attestation_types is not None: warnings.warn('You have defined `attestation_types` but not `verify_attestation`, this means that the ' '`attestation_types` setting is being iognored.', DeprecationWarning) return Fido2Server(rp, attestation=self.attestation) else: return Fido2Server(rp, attestation=self.attestation, verify_attestation=self.verify_attestation(self.attestation_types))
def test_register_begin_rp_icon(self): rp = PublicKeyCredentialRpEntity("example.com", "Example", "http://example.com/icon.svg") server = Fido2Server(rp) request, state = server.register_begin(USER) data = { "id": "example.com", "name": "Example", "icon": "http://example.com/icon.svg", } self.assertEqual(request["publicKey"]["rp"], data)
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 test_authenticate(): """test authentication""" device = SoftWebauthnDevice() device.cred_init('example.org', b'randomhandle') registered_credential = device.cred_as_attested() server = Fido2Server( PublicKeyCredentialRpEntity('example.org', 'test server')) options, state = server.authenticate_begin([registered_credential]) assertion = device.get(options, 'https://example.org') server.authenticate_complete( state, [registered_credential], assertion['rawId'], ClientData(assertion['response']['clientDataJSON']), AuthenticatorData(assertion['response']['authenticatorData']), assertion['response']['signature'])
def server(self): rp_id = getattr(settings, 'OTP_U2F_RP_ID', None) rp_name = getattr(settings, 'OTP_U2F_RP_NAME', None) app_id = getattr(settings, 'OTP_U2F_APP_ID', None) if rp_id is None: site = get_current_site(self.request) rp_id = site.domain rp_name = site.name if app_id is None: if self.request is not None: app_id = self.request.build_absolute_uri('/')[:-1] else: app_id = f'https://{rp_id}' return U2FFido2Server( app_id, rp=PublicKeyCredentialRpEntity(rp_id, rp_name), attestation='direct')
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 test_register(): """test registering generated credential""" device = SoftWebauthnDevice() server = Fido2Server( PublicKeyCredentialRpEntity('example.org', 'test server')) exclude_credentials = [] options, state = server.register_begin( { 'id': b'randomhandle', 'name': 'username', 'displayName': 'User Name' }, exclude_credentials) attestation = device.create(options, 'https://example.org') auth_data = server.register_complete( state, ClientData(attestation['response']['clientDataJSON']), AttestationObject(attestation['response']['attestationObject'])) assert isinstance(auth_data, AuthenticatorData)
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), )
import segno POST_LOGIN_ROUTES = ( "hc-checks", "hc-details", "hc-log", "hc-channels", "hc-add-slack", "hc-add-pushover", "hc-add-telegram", "hc-project-settings", "hc-uncloak", ) FIDO2_SERVER = Fido2Server( PublicKeyCredentialRpEntity(settings.RP_ID, "healthchecks")) def _allow_redirect(redirect_url): if not redirect_url: return False parsed = urlparse(redirect_url) if parsed.netloc: # Allow redirects only to relative URLs return False try: match = resolve(parsed.path) except Resolver404: return False
class U2fInterface(AuthenticatorInterface): type = 3 interface_id = "u2f" configure_button = _("Configure") name = _("U2F (Universal 2nd Factor)") description = _( "Authenticate with a U2F hardware device. This is a " "device like a Yubikey or something similar which " "supports FIDO's U2F specification. This also requires " "a browser which supports this system (like Google " "Chrome)." ) allow_multi_enrollment = True # rp is a relying party for webauthn, this would be sentry.io for SAAS # and the prefix for on-premise / dev environments rp_id = urlparse(options.get("system.url-prefix")).hostname rp = PublicKeyCredentialRpEntity(rp_id, "Sentry") webauthn_registration_server = Fido2Server(rp) def __init__(self, authenticator=None, status=EnrollmentStatus.EXISTING): super().__init__(authenticator, status) self.webauthn_authentication_server = U2FFido2Server( app_id=self.u2f_app_id, rp={"id": self.rp_id, "name": "Sentry"} ) @classproperty def u2f_app_id(cls): rv = options.get("u2f.app-id") return rv or absolute_uri(reverse("sentry-u2f-app-id")) @classproperty def u2f_facets(cls): facets = options.get("u2f.facets") if not facets: return [options.get("system.url-prefix")] return [x.rstrip("/") for x in facets] @classproperty def is_available(cls): url_prefix = options.get("system.url-prefix") return url_prefix and url_prefix.startswith("https://") def _get_kept_devices(self, key): def _key_does_not_match(device): if type(device["binding"]) == AuthenticatorData: return decode_credential_id(device) != key else: return device["binding"]["keyHandle"] != key return [device for device in self.config.get("devices", ()) if _key_does_not_match(device)] def generate_new_config(self): return {} def start_enrollment(self, user, has_webauthn_register): if has_webauthn_register: credentials = [] for registeredKey in self.get_u2f_devices(): if type(registeredKey) == AuthenticatorData: credentials.append(registeredKey.credential_data) else: c = create_credential_object(registeredKey) credentials.append(c) registration_data, state = self.webauthn_registration_server.register_begin( user={ "id": user.id.to_bytes(64, byteorder="big"), "name": user.username, "displayName": user.username, }, credentials=credentials, # user_verification is where the authenticator verifies that the user is authorized # to use the authenticator, this isn't needed for our usecase so set a discouraged user_verification="discouraged", ) return cbor.encode(registration_data), state return u2f.begin_registration(self.u2f_app_id, self.get_u2f_devices()).data_for_client def get_u2f_devices(self): rv = [] for data in self.config.get("devices", ()): # XXX: The previous version of python-u2flib-server didn't store # the `version` in the device binding. Defaulting to `U2F_V2` here # so that we don't break existing u2f registrations. if type(data["binding"]) == AuthenticatorData: rv.append(data["binding"]) else: data["binding"].setdefault("version", "U2F_V2") rv.append(DeviceRegistration(data["binding"])) return rv def remove_u2f_device(self, key): """Removes a U2F device but never removes the last one. This returns False if the last device would be removed. """ devices = self._get_kept_devices(key) if devices: self.config["devices"] = devices return True return False def get_device_name(self, key): for device in self.config.get("devices", ()): if type(device["binding"]) == AuthenticatorData: if decode_credential_id(device) == key: return device["name"] elif device["binding"]["keyHandle"] == key: return device["name"] def get_registered_devices(self): rv = [] for device in self.config.get("devices", ()): if type(device["binding"]) == AuthenticatorData: rv.append( { "timestamp": to_datetime(device["ts"]), "name": device["name"], "key_handle": decode_credential_id(device), "app_id": self.rp_id, } ) else: rv.append( { "timestamp": to_datetime(device["ts"]), "name": device["name"], "key_handle": device["binding"]["keyHandle"], "app_id": device["binding"]["appId"], } ) rv.sort(key=lambda x: x["name"]) return rv def try_enroll( self, enrollment_data, response_data, has_webauthn_register, device_name=None, state=None ): if has_webauthn_register: data = json.loads(response_data) client_data = ClientData(websafe_decode(data["response"]["clientDataJSON"])) att_obj = base.AttestationObject(websafe_decode(data["response"]["attestationObject"])) binding = self.webauthn_registration_server.register_complete( state, client_data, att_obj ) else: data, cert = u2f.complete_registration(enrollment_data, response_data, self.u2f_facets) binding = dict(data) devices = self.config.setdefault("devices", []) devices.append( {"name": device_name or "Security Key", "ts": int(time()), "binding": binding} ) def activate(self, request: Request, is_webauthn_signin_ff_enabled): if not is_webauthn_signin_ff_enabled: challenge = dict(u2f.begin_authentication(self.u2f_app_id, self.get_u2f_devices())) # XXX: Upgrading python-u2flib-server to 5.0.0 changes the response # format. Our current js u2f library expects the old format, so # massaging the data to include the old `authenticateRequests` key here. authenticate_requests = [] for registered_key in challenge["registeredKeys"]: authenticate_requests.append( { "challenge": challenge["challenge"], "version": registered_key["version"], "keyHandle": registered_key["keyHandle"], "appId": registered_key["appId"], } ) challenge["authenticateRequests"] = authenticate_requests return ActivationChallengeResult(challenge=challenge) credentials = [] for device in self.get_u2f_devices(): if type(device) == AuthenticatorData: credentials.append(device.credential_data) else: credentials.append(create_credential_object(device)) challenge, state = self.webauthn_authentication_server.authenticate_begin( credentials=credentials ) request.session["webauthn_authentication_state"] = state return ActivationChallengeResult(challenge=cbor.encode(challenge["publicKey"])) def validate_response(self, request: Request, challenge, response, has_webauthn_register): try: if not has_webauthn_register: u2f.complete_authentication(challenge, response, self.u2f_facets) return True credentials = [] for device in self.get_u2f_devices(): if type(device) == AuthenticatorData: credentials.append(device.credential_data) else: credentials.append(create_credential_object(device)) self.webauthn_authentication_server.authenticate_complete( state=request.session["webauthn_authentication_state"], credentials=credentials, credential_id=websafe_decode(response["keyHandle"]), client_data=ClientData(websafe_decode(response["clientData"])), auth_data=AuthenticatorData(websafe_decode(response["authenticatorData"])), signature=websafe_decode(response["signatureData"]), ) except (InvalidSignature, InvalidKey, StopIteration): return False return True
def make_fido2_server() -> Fido2Server: rp_id = request.host logger.debug("Using %r as relaying party ID", rp_id) return Fido2Server(PublicKeyCredentialRpEntity(rp_id, "Checkmk"))
PublicKeyCredentialRpEntity, AttestationObject, AuthenticatorData, ) from fido2.server import U2FFido2Server from fido2.ctap1 import RegistrationData from fido2.utils import sha256, websafe_encode, websafe_decode from fido2 import cbor from flask import Flask, session, request, redirect, abort import os app = Flask(__name__, static_url_path="") app.secret_key = os.urandom(32) # Used for session. rp = PublicKeyCredentialRpEntity(name="Demo server", id="localhost") # By using the U2FFido2Server class, we can support existing credentials # registered by the legacy u2f.register API for an appId. server = U2FFido2Server("https://*****:*****@app.route("/") def index(): return redirect("/index-u2f.html") @app.route("/api/register/begin", methods=["POST"])
def __init__(self, fido2Origin, fido2Name): rp = PublicKeyCredentialRpEntity(fido2Origin, fido2Name) self.server = Fido2Server(rp, verify_origin=verify_origin_for_rp_openvpn) self.credentials = {} self.credsfile = 'creds.pickle' self.loadCredentials(self.credsfile)
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_GET, require_POST from fido2.webauthn import PublicKeyCredentialRpEntity from fido2.client import ClientData from fido2.server import Fido2Server from fido2.ctap2 import AttestationObject, AuthenticatorData from fido2 import cbor from .models import FIDOCredential if hasattr(settings, 'FIDO2_APP_ID'): appId = settings.FIDO2_APP_ID else: appId = "localhost" rp = PublicKeyCredentialRpEntity(appId, "Ledger Web") fido_server = Fido2Server(rp) class LoginView(auth_views.LoginView): def post(self, request): user = authenticate(request=request, username=request.POST['username'], password=request.POST['password']) if user is not None: fido_credentials = user.fidocredential_set.count() if fido_credentials > 0: request.session['user'] = user.id return render( request, 'registration/login-fido.html',
def server(self) -> Fido2Server: """Return FIDO 2 server instance.""" rp = PublicKeyCredentialRpEntity(self.get_rp_id(), self.get_rp_name()) return Fido2Server(rp, attestation=self.attestation, attestation_types=self.attestation_types)
from .db.schema import User, WebAuthnEntry # the api router which will be later registered in the main file router = APIRouter() # the cookie used to store the session token cookie_sec = APIKeyCookie(name="session") # Used to store the state while doing webauthn webauthn_state = APIKeyCookie(name="_state", auto_error=True) # argon2 password hasher and verifier ph = PasswordHasher() # webauthn rp = PublicKeyCredentialRpEntity(config.WEBAUTHN_RP_ID, config.WEBAUTHN_RP_NAME) fido2server = Fido2Server(rp) # on get render the html page @router.get("/login", response_class=HTMLResponse) async def view_login_page(request: Request): return main.templates.TemplateResponse("login_form.html", {"request": request}) # on post try to log the user in @router.post("/login", response_class=HTMLResponse) async def login( request: Request, email: EmailStr = Form(...), password: SecretStr = Form(...) ): successful = False
class TestFido2AuthenticationBackend(TestCase): """Test `Fido2AuthenticationBackend` class.""" backend = Fido2AuthenticationBackend() 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) 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, self.user, 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_counter(self): self.device.counter = 160 self.device.save() request = RequestFactory().get('/dummy/') request._messages = CookieStorage(request) self.assertRaisesMessage(PermissionDenied, "Counter didn't increase.", self.backend.authenticate, request, self.user, self.server, self.state, self.fido2_response) self.assertQuerysetEqual(Authenticator.objects.values_list( 'user', 'counter'), [(self.user.pk, 160)], transform=tuple) 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)) def test_mark_device_used(self): self.backend.mark_device_used(self.device, 42) self.assertQuerysetEqual(Authenticator.objects.values_list( 'user', 'counter'), [(self.user.pk, 42)], transform=tuple) def test_mark_device_used_equal(self): # Test device returned the same counter. self.device.counter = 42 self.device.save() self.assertRaisesMessage(ValueError, "Counter didn't increase.", self.backend.mark_device_used, self.device, 42) self.assertQuerysetEqual(Authenticator.objects.values_list( 'user', 'counter'), [(self.user.pk, 42)], transform=tuple) def test_mark_device_used_decrease(self): # Test device returned lower counter. self.device.counter = 42 self.device.save() self.assertRaisesMessage(ValueError, "Counter didn't increase.", self.backend.mark_device_used, self.device, 41) self.assertQuerysetEqual(Authenticator.objects.values_list( 'user', 'counter'), [(self.user.pk, 42)], transform=tuple) def test_get_user(self): self.assertEqual(self.backend.get_user(self.user.pk), self.user) def test_get_user_unknown(self): self.assertIsNone(self.backend.get_user(42))
from fido2 import cbor from fido2.client import ClientData from fido2.ctap2 import AttestedCredentialData, AuthenticatorData from fido2.server import U2FFido2Server from fido2.utils import websafe_decode from fido2.webauthn import PublicKeyCredentialRpEntity from assemblyline.common.security import get_totp_token from assemblyline_ui.config import config, APP_ID from assemblyline_ui.http_exceptions import AuthenticationException rp = PublicKeyCredentialRpEntity(config.ui.fqdn, "Assemblyline server") server = U2FFido2Server(f"https://{config.ui.fqdn}", rp) # noinspection PyBroadException def validate_2fa(username, otp_token, state, webauthn_auth_resp, storage): security_token_enabled = False otp_enabled = False security_token_error = False otp_error = False report_errors = False # Get user user_data = storage.user.get(username) # Test Security Tokens if config.auth.allow_security_tokens: security_tokens = user_data.security_tokens credentials = [AttestedCredentialData(websafe_decode(x)) for x in security_tokens.values()]
from fido2 import cbor from fido2.client import ClientData from fido2.ctap2 import AttestationObject, AuthenticatorData, AttestedCredentialData from fido2.server import Fido2Server from fido2.webauthn import PublicKeyCredentialRpEntity from flask import request, session, flash from flask_login import current_user, login_required, login_user from flask_socketio import emit from peewee import DoesNotExist from home.settings import BASE_URL from home.web import app, send_message, socketio, ws_login_required from home.web.models import FIDOToken, User rp = PublicKeyCredentialRpEntity( re.match(r'https?:\/\/([a-zA-Z0-9.-]+)(?::[0-9]{1,5})?\/.*', BASE_URL)[1], "Home server") server = Fido2Server(rp) def get_all_tokens(user): return [AttestedCredentialData(token.data) for token in user.fido_tokens] @app.route("/api/user/fido2/register", methods=["POST"]) @login_required def register_begin(): registration_data, state = server.register_begin( { "id": str(current_user.id).encode(), "name": current_user.username, "displayName": current_user.username,