Esempio n. 1
0
def test_is_two_factor_backup_code_valid_matches(user_id: UserId) -> None:
    codes = userdb.make_two_factor_backup_codes(rounds=5)
    credentials = userdb.load_two_factor_credentials(user_id)
    credentials["backup_codes"] = [pwhashed for _password, pwhashed in codes]
    userdb.save_two_factor_credentials(user_id, credentials)
    assert len(credentials["backup_codes"]) == 10

    valid = userdb.is_two_factor_backup_code_valid(user_id, codes[3][0])
    assert valid

    credentials = userdb.load_two_factor_credentials(user_id)
    assert len(credentials["backup_codes"]) == 9
Esempio n. 2
0
def test_is_two_factor_backup_code_valid_matches(user_id) -> None:
    display_codes, store_codes = userdb.make_two_factor_backup_codes()
    credentials = userdb.load_two_factor_credentials(user_id)
    credentials["backup_codes"] = store_codes
    assert len(credentials["backup_codes"]) == 10
    userdb.save_two_factor_credentials(user_id, credentials)

    assert userdb.is_two_factor_backup_code_valid(user_id,
                                                  display_codes[3]) is True

    credentials = userdb.load_two_factor_credentials(user_id)
    assert len(credentials["backup_codes"]) == 9
Esempio n. 3
0
    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"}
Esempio n. 4
0
    def _show_form(self) -> None:
        assert user.id is not None
        credentials = load_two_factor_credentials(user.id)

        credential_id = request.get_ascii_input_mandatory("_edit")
        credential = credentials["webauthn_credentials"].get(credential_id)
        if credential is None:
            raise MKUserError("_edit", _("The credential does not exist"))

        html.begin_form("profile", method="POST")
        html.prevent_password_auto_completion()
        html.open_div(class_="wato")

        self._valuespec(credential).render_input(
            "profile",
            {
                "registered_at": credential["registered_at"],
                "alias": credential["alias"],
            },
        )

        forms.end()
        html.close_div()
        html.hidden_field("_edit", credential_id)
        html.hidden_fields()
        html.end_form()
        html.footer()
Esempio n. 5
0
    def _action(self) -> None:
        assert user.id is not None
        credentials = load_two_factor_credentials(user.id, lock=True)

        credential_id = request.get_ascii_input_mandatory("_edit")
        credential = credentials["webauthn_credentials"].get(credential_id)
        if credential is None:
            raise MKUserError("_edit", _("The credential does not exist"))

        vs = self._valuespec(credential)
        settings = vs.from_html_vars("profile")
        vs.validate_value(settings, "profile")

        credential["alias"] = settings["alias"]

        save_two_factor_credentials(user.id, credentials)

        flash(_("Successfully changed the credential."))

        # In distributed setups with remote sites where the user can login, start the
        # user profile replication now which will redirect the user to the destination
        # page after completion. Otherwise directly open up the destination page.
        origtarget = "user_two_factor_overview.py"
        if user.authorized_login_sites():
            raise redirect(
                makeuri_contextless(request, [("back", origtarget)],
                                    filename="user_profile_replicate.py"))
        raise redirect(origtarget)
Esempio n. 6
0
    def _show_form(self) -> None:
        assert user.id is not None

        credentials = load_two_factor_credentials(user.id)
        webauthn_credentials = credentials["webauthn_credentials"]
        backup_codes = credentials["backup_codes"]

        html.begin_form("two_factor", method="POST")
        html.div("", id_="webauthn_message")
        forms.header(_("Credentials"))

        forms.section(_("Registered credentials"), simple=True)
        if webauthn_credentials:
            self._show_credentials(webauthn_credentials)
        else:
            html.i(_("No credentials registered"))

        forms.section(_("Backup codes"), simple=True)
        if backup_codes:
            html.p(
                _("You have %d unused backup codes left. You can use them as one-time password "
                  "if your key is not available.") % len(backup_codes))
            html.i(
                _("If you regenerate backup codes, you automatically invalidate the existing codes."
                  ))
        else:
            html.i(_("No backup codes created yet."))

        forms.end()

        html.hidden_fields()
        html.end_form()
        html.footer()
Esempio n. 7
0
    def page(self) -> CBORPageResult:
        assert user.id is not None
        user.need_permission("general.manage_2fa")

        raw_data = request.get_data()
        logger.debug("Raw request: %r", raw_data)
        data: dict[str, object] = cbor.decode(raw_data)
        client_data = ClientData(data["clientDataJSON"])
        att_obj = AttestationObject(data["attestationObject"])
        logger.debug("Client data: %r", client_data)
        logger.debug("Attestation object: %r", att_obj)

        auth_data = make_fido2_server().register_complete(
            session.session_info.webauthn_action_state, client_data, att_obj
        )

        ident = auth_data.credential_data.credential_id.hex()
        credentials = load_two_factor_credentials(user.id, lock=True)

        if ident in credentials["webauthn_credentials"]:
            raise MKGeneralException(_("Your WebAuthn credetial is already in use"))

        credentials["webauthn_credentials"][ident] = WebAuthnCredential(
            {
                "credential_id": ident,
                "registered_at": int(time.time()),
                "alias": "",
                "credential_data": bytes(auth_data.credential_data),
            }
        )
        save_two_factor_credentials(user.id, credentials)

        flash(_("Registration successful"))
        return {"status": "OK"}
Esempio n. 8
0
    def _action(self) -> None:
        assert user.id is not None
        credentials = load_two_factor_credentials(user.id)

        if credential_id := request.get_ascii_input("_delete"):
            if credential_id not in credentials["webauthn_credentials"]:
                return
            del credentials["webauthn_credentials"][credential_id]
            save_two_factor_credentials(user.id, credentials)
            flash(_("Credential has been deleted"))
Esempio n. 9
0
    def _show_form(self) -> None:
        assert user.id is not None

        credentials = load_two_factor_credentials(user.id)

        html.begin_form("two_factor", method="POST")

        self._show_webauthn_credentials(credentials["webauthn_credentials"])
        self._show_backup_codes(credentials["backup_codes"])

        html.hidden_fields()
        html.end_form()
        html.footer()
Esempio n. 10
0
    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"))

        auth_data, state = make_fido2_server().authenticate_begin(
            [
                AttestedCredentialData.unpack_from(v["credential_data"])[0]
                for v in load_two_factor_credentials(user.id)["webauthn_credentials"].values()
            ],
            user_verification="discouraged",
        )

        session.session_info.webauthn_action_state = state
        logger.debug("Authentication data: %r", auth_data)
        return auth_data
Esempio n. 11
0
    def page(self) -> CBORPageResult:
        assert user.id is not None
        user.need_permission("general.manage_2fa")

        raw_data = request.get_data()
        logger.debug("Raw request: %r", raw_data)
        data: dict[str, object] = cbor.decode(raw_data)
        client_data = ClientData(data["clientDataJSON"])
        att_obj = AttestationObject(data["attestationObject"])
        logger.debug("Client data: %r", client_data)
        logger.debug("Attestation object: %r", att_obj)

        try:
            auth_data = make_fido2_server().register_complete(
                session.session_info.webauthn_action_state, client_data,
                att_obj)
        except ValueError as e:
            if "Invalid origin in ClientData" in str(e):
                raise MKGeneralException(
                    "The origin %r is not valid. You need to access the UI via HTTPS "
                    "and you need to use a valid host or domain name. See werk #13325 for "
                    "further information" % client_data.get("origin")) from e
            raise

        ident = auth_data.credential_data.credential_id.hex()
        credentials = load_two_factor_credentials(user.id, lock=True)

        if ident in credentials["webauthn_credentials"]:
            raise MKGeneralException(
                _("Your WebAuthn credential is already in use"))

        credentials["webauthn_credentials"][ident] = WebAuthnCredential({
            "credential_id":
            ident,
            "registered_at":
            int(time.time()),
            "alias":
            "",
            "credential_data":
            bytes(auth_data.credential_data),
        })
        save_two_factor_credentials(user.id, credentials)

        flash(_("Registration successful"))
        return {"status": "OK"}
Esempio n. 12
0
def test_save_two_factor_credentials(user_id: UserId) -> None:
    credentials = userdb.TwoFactorCredentials({
        "webauthn_credentials": {
            "id":
            userdb.WebAuthnCredential({
                "credential_id": "id",
                "registered_at": 1337,
                "alias": "Steckding",
                "credential_data": b"whatever",
            }),
        },
        "backup_codes": [
            "asdr2ar2a2ra2rara2",
            "dddddddddddddddddd",
        ],
    })
    userdb.save_two_factor_credentials(user_id, credentials)
    assert userdb.load_two_factor_credentials(user_id) == credentials
Esempio n. 13
0
    def page(self) -> CBORPageResult:
        assert user.id is not None
        user.need_permission("general.manage_2fa")

        registration_data, state = make_fido2_server().register_begin(
            {
                "id": user.id.encode("utf-8"),
                "name": user.id,
                "displayName": user.alias,
                "icon": "",
            },
            [
                AttestedCredentialData.unpack_from(v["credential_data"])[0]
                for v in load_two_factor_credentials(user.id)["webauthn_credentials"].values()
            ],
            user_verification="discouraged",
            authenticator_attachment="cross-platform",
        )

        session.session_info.webauthn_action_state = state
        logger.debug("Registration data: %r", registration_data)
        return registration_data
Esempio n. 14
0
def test_load_two_factor_credentials_unset(user_id: UserId) -> None:
    assert userdb.load_two_factor_credentials(user_id) == {
        "webauthn_credentials": {},
        "backup_codes": [],
    }