示例#1
0
    def create_challenge(self, transactionid=None, options=None):
        """
        create a challenge, which is submitted to the user

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :return: tuple of (bool, message and data)
                 bool, if submit was successful
                 message is submitted to the user
                 data is preserved in the challenge
                 attributes - additional attributes, which are displayed in the
                    output
        """
        success = False
        sms = ""
        options = options or {}
        return_message = "Enter the OTP from the SMS:"
        attributes = {'state': transactionid}
        validity = self._get_sms_timeout()

        if self.is_active() is True:
            counter = self.get_otp_count()
            log.debug("counter={0!r}".format(counter))
            self.inc_otp_counter(counter, reset=False)
            # At this point we must not bail out in case of an
            # Gateway error, since checkPIN is successful. A bail
            # out would cancel the checking of the other tokens
            try:
                message_template = self._get_sms_text(options)
                success, sent_message = self._send_sms(
                    message=message_template)

                # Create the challenge in the database
                db_challenge = Challenge(self.token.serial,
                                         transaction_id=transactionid,
                                         challenge=options.get("challenge"),
                                         session=options.get("session"),
                                         validitytime=validity)
                db_challenge.save()
                transactionid = transactionid or db_challenge.transaction_id
            except Exception as e:
                info = ("The PIN was correct, but the "
                        "SMS could not be sent: %r" % e)
                log.warning(info)
                return_message = info

        validity = self._get_sms_timeout()
        expiry_date = datetime.datetime.now() + \
                                    datetime.timedelta(seconds=validity)
        attributes['valid_until'] = "{0!s}".format(expiry_date)

        return success, return_message, transactionid, attributes
示例#2
0
    def test_13_challenges_transaction(self):
        transaction_id = "some_id"
        challenge = Challenge("hotptoken",
                              transaction_id=transaction_id,
                              challenge="You dont guess this")
        challenge.save()

        serial = get_tokenserial_of_transaction(transaction_id)
        self.assertTrue(serial == "hotptoken", serial)

        # Challenge does not exist
        serial = get_tokenserial_of_transaction("other id")
        self.assertTrue(serial is None, serial)
示例#3
0
    def create_challenge(self, transactionid=None, options=None):
        """
        This method creates a challenge, which is submitted to the user.
        The submitted challenge will be preserved in the challenge
        database.

        The challenge is a randomly selected question of the available
        questions for this token.

        If no transaction id is given, the system will create a transaction
        id and return it, so that the response can refer to this transaction.

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :type options: dict
        :return: tuple of (bool, message, transactionid, attributes)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional ``attributes``, which are displayed in the JSON response.
        """
        options = options or {}

        # Get a random question
        questions = []
        tinfo = self.get_tokeninfo()
        for question, answer in tinfo.iteritems():
            if question.endswith(".type") and answer == "password":
                # This is "Question1?.type" of type "password"
                # So this is actually a question and we add the question to
                # the list
                questions.append(question.strip(".type"))
        message = random.choice(questions)
        attributes = None

        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        # Maybe there is a QUESTIONChallengeValidityTime...
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=transactionid,
                                 challenge=message,
                                 validitytime=validity)
        db_challenge.save()
        self.challenge_janitor()
        return True, message, db_challenge.transaction_id, attributes
示例#4
0
    def create_challenge(self, transactionid=None, options=None):
        """
        create a challenge, which is submitted to the user

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :return: tuple of (bool, message and data)
                 bool, if submit was successful
                 message is submitted to the user
                 data is preserved in the challenge
                 attributes - additional attributes, which are displayed in the
                    output
        """
        success = False
        options = options or {}
        return_message = "Enter the OTP from the Email:"
        attributes = {'state': transactionid}

        if self.is_active() is True:
            counter = self.get_otp_count()
            log.debug("counter={0!r}".format(counter))
            self.inc_otp_counter(counter, reset=False)
            # At this point we must not bail out in case of an
            # Gateway error, since checkPIN is successful. A bail
            # out would cancel the checking of the other tokens
            try:
                message_template = self._get_email_text_or_subject(options)
                subject_template = self._get_email_text_or_subject(
                    options, EMAILACTION.EMAILSUBJECT, "Your OTP")
                validity = int(get_from_config("email.validtime", 120))

                # Create the challenge in the database
                db_challenge = Challenge(self.token.serial,
                                         transaction_id=transactionid,
                                         challenge=options.get("challenge"),
                                         session=options.get("session"),
                                         validitytime=validity)
                db_challenge.save()
                transactionid = transactionid or db_challenge.transaction_id
                # We send the email after creating the challenge for testing.
                success, sent_message = self._compose_email(
                    message=message_template, subject=subject_template)

            except Exception as e:
                info = ("The PIN was correct, but the "
                        "EMail could not be sent: %r" % e)
                log.warning(info)
                log.debug("{0!s}".format(traceback.format_exc(e)))
                return_message = info

        return success, return_message, transactionid, attributes
示例#5
0
    def test_18_challenges(self):
        db_token = Token.query.filter_by(serial=self.serial1).first()
        token = TotpTokenClass(db_token)
        resp = token.is_challenge_response(User(login="******",
                                                realm=self.realm1),
                                            "test123456")
        self.assertFalse(resp, resp)
        resp = token.is_challenge_response(User(login="******",
                                                realm=self.realm1),
                                            "test123456",
                                            options={"transaction_id": "123456789"})
        self.assertTrue(resp, resp)

        # test if challenge is valid
        C = Challenge("S123455", transaction_id="tid", challenge="Who are you?")
        C.save()
示例#6
0
    def test_18_challenges(self):
        db_token = Token.query.filter_by(serial=self.serial1).first()
        token = TokenClass(db_token)
        transaction_id = "123456789"

        db_token.set_pin("test")
        # No challenge request, since we have the wrong pin
        req = token.is_challenge_request(
            "testX", User(login="******", realm=self.realm1))
        self.assertFalse(req, req)
        # A challenge request, since we have the correct pin
        req = token.is_challenge_request(
            "test", User(login="******", realm=self.realm1))
        self.assertTrue(req, req)

        resp = token.is_challenge_response(
            User(login="******", realm=self.realm1), "test123456")
        self.assertFalse(resp, resp)

        # A the token has not DB entry in the challenges table, basically
        # "is_cahllenge_response"
        resp = token.is_challenge_response(
            User(login="******", realm=self.realm1),
            "test123456",
            options={"transaction_id": transaction_id})
        self.assertTrue(resp)
        # ... but is does not "has_db_challenge_response"
        resp = token.has_db_challenge_response(
            User(login="******", realm=self.realm1),
            "test123456",
            options={"transaction_id": transaction_id})
        self.assertFalse(resp)

        # Create a challenge
        C = Challenge(serial=self.serial1,
                      transaction_id=transaction_id,
                      challenge="12")
        C.save()
        resp = token.is_challenge_response(
            User(login="******", realm=self.realm1),
            "test123456",
            options={"transaction_id": transaction_id})
        self.assertTrue(resp)

        # test if challenge is valid
        self.assertTrue(C.is_valid())
    def test_20_check_challenge_response(self):
        db_token = Token.query.filter_by(serial=self.serial1).first()
        db_token.set_pin("test")
        token = HotpTokenClass(db_token)
        r = token.check_challenge_response(user=None, passw="123454")
        # check empty challenges
        self.assertTrue(r == -1, r)

        # create a challenge and match the transaction_id
        c = Challenge(self.serial1,
                      transaction_id="mytransaction",
                      challenge="Blah, what now?")
        # save challenge to the database
        c.save()
        r = token.check_challenge_response(user=None,
                                           passw="123454",
                                           options={"state": "mytransaction"})
        # The challenge matches, but the OTP does not match!
        self.assertTrue(r == -1, r)
def _create_pin_reset_challenge(token_obj, message, challenge_data=None):
    validity = int(get_from_config('DefaultChallengeValidityTime', 120))
    validity = int(get_from_config('PinResetChallengeValidityTime', validity))
    db_challenge = Challenge(token_obj.token.serial,
                             challenge=CHALLENGE_TYPE.PIN_RESET,
                             data=challenge_data,
                             validitytime=validity)
    db_challenge.save()
    token_obj.challenge_janitor()
    reply_dict = {}
    reply_dict["multi_challenge"] = [{"transaction_id": db_challenge.transaction_id,
                                      "message": message,
                                      "serial": token_obj.token.serial,
                                      "type": token_obj.token.tokentype}]
    reply_dict["message"] = message
    reply_dict["transaction_id"] = db_challenge.transaction_id
    reply_dict["transaction_ids"] = [db_challenge.transaction_id]

    return reply_dict
示例#9
0
    def test_18_challenges(self):
        db_token = Token.query.filter_by(serial=self.serial1).first()
        token = DaplugTokenClass(db_token)
        resp = token.is_challenge_response(User(login="******",
                                                realm=self.realm1),
                                            "test"+_digi2daplug("123456"))
        self.assertFalse(resp, resp)

        transaction_id = "123456789"
        C = Challenge(self.serial1, transaction_id=transaction_id, challenge="Who are you?")
        C.save()
        resp = token.is_challenge_response(User(login="******",
                                                realm=self.realm1),
                                            "test"+_digi2daplug("123456"),
                                            options={"transaction_id":
                                                         transaction_id})
        self.assertTrue(resp, resp)

        # test if challenge is valid
        C.is_valid()
示例#10
0
    def test_18_challenges(self):
        db_token = Token.query.filter_by(serial=self.serial1).first()
        token = TokenClass(db_token)
        transaction_id = "123456789"

        db_token.set_pin("test")
        # No challenge request
        req = token.is_challenge_request(
            "test", User(login="******", realm=self.realm1))
        self.assertFalse(req, req)
        # A challenge request
        req = token.is_challenge_request(
            "test", User(login="******", realm=self.realm1),
            {"data": "a challenge"})
        self.assertTrue(req, req)

        resp = token.is_challenge_response(
            User(login="******", realm=self.realm1), "test123456")
        self.assertFalse(resp, resp)
        resp = token.is_challenge_response(
            User(login="******", realm=self.realm1),
            "test123456",
            options={"transaction_id": transaction_id})
        # The token has not DB entry in the challenges table
        self.assertFalse(resp)

        # Create a challenge
        C = Challenge(serial=self.serial1,
                      transaction_id=transaction_id,
                      challenge="12")
        C.save()
        resp = token.is_challenge_response(
            User(login="******", realm=self.realm1),
            "test123456",
            options={"transaction_id": transaction_id})
        self.assertTrue(resp)

        # test if challenge is valid
        self.assertTrue(C.is_valid())
示例#11
0
    def test_16_remove_token(self):
        self.assertRaises(ParameterError, remove_token)

        count1 = get_tokens(count=True)
        tokenobject = init_token({
            "type": "hotp",
            "otpkey": "1234567890123456",
            "realm": self.realm1
        })
        count2 = get_tokens(count=True)
        self.assertTrue(count2 == count1 + 1, count2)
        # check for the token association
        token_id = tokenobject.token.id
        realm_assoc = TokenRealm.query.filter(TokenRealm.token_id == \
            token_id).count()
        self.assertTrue(realm_assoc == 1, realm_assoc)
        # Add a challenge for this token
        challenge = Challenge(tokenobject.get_serial(),
                              transaction_id="918273")
        challenge.save()
        chall_count = Challenge.query.filter(
            Challenge.serial == tokenobject.get_serial()).count()
        self.assertTrue(chall_count == 1, chall_count)

        # remove the token
        count_remove = remove_token(serial=tokenobject.get_serial())
        self.assertTrue(count_remove == 1, count_remove)
        self.assertTrue(get_tokens(count=True) == count1)
        # check for the realm association
        realm_assoc = TokenRealm.query.filter(TokenRealm.token_id == \
            token_id).count()
        self.assertTrue(realm_assoc == 0, realm_assoc)
        # check if the challenge is removed
        chall_count = Challenge.query.filter(
            Challenge.serial == tokenobject.get_serial()).count()
        self.assertTrue(chall_count == 0, chall_count)
示例#12
0
    def test_05_u2f_auth_fails_wrong_issuer(self):
        # test data taken from
        # https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-u2f-raw-message-formats-ps-20141009.html#examples
        serial = "U2F0010BF6F"
        set_privacyidea_config("u2f.appId", "https://puck.az.intern")
        pin = "test"
        # Registration data
        client_data = "eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCIsImNoYWxsZW5nZSI6ImpIakIxaEM2VjA3dDl4ZnNNaDRfOEQ3U1JuSHRFY1BqUTdsaVl3cWxkX009Iiwib3JpZ2luIjoiaHR0cHM6Ly9wdWNrLmF6LmludGVybiIsImNpZF9wdWJrZXkiOiJ1bnVzZWQifQ"
        reg_data = "BQRHjwxEYFCkLHz3xdrmifKOHl2h17BmRJQ_S1Y9PRAhS2R186T391YE-ryqWis9HSmdp0XpRqUaKk9L8lxJTPpTQF_xFJ_LAsKkPTzKIwUlPIjGZDsLmv0en2Iya17Yz8X8OS89fuxwZOvEok-NQOKUTJP3att_RVe3dEAbq_iOtyAwggJEMIIBLqADAgECAgRVYr6gMAsGCSqGSIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTQzMjUzNDY4ODBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEszH3c9gUS5mVy-RYVRfhdYOqR2I2lcvoWsSCyAGfLJuUZ64EWw5m8TGy6jJDyR_aYC4xjz_F2NKnq65yvRQwmjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS41MBMGCysGAQQBguUcAgEBBAQDAgUgMAsGCSqGSIb3DQEBCwOCAQEArBbZs262s6m3bXWUs09Z9Pc-28n96yk162tFHKv0HSXT5xYU10cmBMpypXjjI-23YARoXwXn0bm-BdtulED6xc_JMqbK-uhSmXcu2wJ4ICA81BQdPutvaizpnjlXgDJjq6uNbsSAp98IStLLp7fW13yUw-vAsWb5YFfK9f46Yx6iakM3YqNvvs9M9EUJYl_VrxBJqnyLx2iaZlnpr13o8NcsKIJRdMUOBqt_ageQg3ttsyq_3LyoNcu7CQ7x8NmeCGm_6eVnZMQjDmwFdymwEN4OxfnM5MkcKCYhjqgIGruWkVHsFnJa8qjZXneVvKoiepuUQyDEJ2GcqvhU2YKY1zBFAiEA4ZkIXXyjEPExcMGtW6kJXqYv7UHgjxJR5h3H9w9FV7gCIFGdhxZDqwCQKplDi-LU4WJ45OyCpNK6lGa72eZqUR_k"
        # Authentication data
        transaction_id = "05871369157706202013"
        challenge = "1616515928c389ba9e028d83eb5f63782cbf351ca6abbc81aeb0dddd4895b609"
        # challenge = "FhZRWSjDibqeAo2D619jeCy_NRymq7yBrrDd3UiVtgk"
        key_handle = "X_EUn8sCwqQ9PMojBSU8iMZkOwua_R6fYjJrXtjPxfw5Lz1-7HBk68SiT41A4pRMk_dq239FV7d0QBur-I63IA"
        client_data_auth = "eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoiRmhaUldTakRpYnFlQW8yRDYxOWplQ3lfTlJ5bXE3eUJyckRkM1VpVnRnayIsIm9yaWdpbiI6Imh0dHBzOi8vcHVjay5hei5pbnRlcm4iLCJjaWRfcHVia2V5IjoidW51c2VkIn0"
        signature_data = "AQAAAAMwRQIgU8d6waOIRVVydg_AXxediEZGkfFioUjd6FG3OxH2wUMCIQDpxzavJyxRlMwgNmD1Kw-iw_oP2egdshU9hrpxFHTRzQ"

        # step 1
        with self.app.test_request_context('/token/init',
                                           method='POST',
                                           data={
                                               "type": "u2f",
                                               "user": "******",
                                               "realm": self.realm1,
                                               "serial": serial
                                           },
                                           headers={'Authorization': self.at}):
            res = self.app.full_dispatch_request()
            self.assertEqual(res.status_code, 200)
            result = json.loads(res.data.decode('utf8')).get("result")
            detail = json.loads(res.data.decode('utf8')).get("detail")
            self.assertEqual(result.get("status"), True)
            self.assertEqual(result.get("value"), True)

        # Init step 2
        with self.app.test_request_context('/token/init',
                                           method='POST',
                                           data={
                                               "type": "u2f",
                                               "serial": serial,
                                               "regdata": reg_data,
                                               "clientdata": client_data
                                           },
                                           headers={'Authorization': self.at}):
            res = self.app.full_dispatch_request()
            self.assertEqual(res.status_code, 200)
            result = json.loads(res.data.decode('utf8')).get("result")
            detail = json.loads(res.data.decode('utf8')).get("detail")
            self.assertEqual(result.get("status"), True)
            self.assertEqual(result.get("value"), True)

        # create a challenge in the database
        db_challenge = Challenge(serial,
                                 transaction_id=transaction_id,
                                 challenge=challenge,
                                 data=None)
        db_challenge.save()

        set_policy(name="u2f01",
                   scope=SCOPE.AUTHZ,
                   action="{0!s}=issuer/.*Plugup.*/".format(U2FACTION.REQ))

        # Successful C/R authentication
        with self.app.test_request_context('/validate/check',
                                           method='POST',
                                           data={
                                               "serial": serial,
                                               "pass": "",
                                               "transaction_id":
                                               transaction_id,
                                               "clientdata": client_data_auth,
                                               "signaturedata": signature_data
                                           }):
            res = self.app.full_dispatch_request()
            self.assertEqual(res.status_code, 403)
            result = json.loads(res.data.decode('utf8')).get("result")
            self.assertEqual(result.get("status"), False)
            self.assertEqual(result.get("error").get("code"), ERROR.POLICY)
            self.assertEqual(
                result.get("error").get("message"),
                u'The U2F device is not allowed to authenticate due to policy restriction.'
            )

        delete_policy("u2f01")
        remove_token(serial)
示例#13
0
    def create_challenge(self, transactionid=None, options=None):
        """
        This method creates a challenge, which is submitted to the user.
        The submitted challenge will be preserved in the challenge
        database.

        If no transaction id is given, the system will create a transaction
        id and return it, so that the response can refer to this transaction.

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :type options: dict
        :return: tuple of (bool, message, transactionid, attributes)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
        """
        options = options or {}
        message = get_action_values_from_options(
            SCOPE.AUTH, ACTION.CHALLENGETEXT,
            options) or DEFAULT_CHALLENGE_TEXT

        reply_dict = {}
        data = None
        # Initially we assume there is no error from Firebase
        res = True
        fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
        if fb_identifier:
            challenge = b32encode_and_unicode(geturandom())
            if fb_identifier != POLL_ONLY:
                # We only push to Firebase if this tokens does NOT POLL_ONLY.
                fb_gateway = create_sms_instance(fb_identifier)
                registration_url = get_action_values_from_options(
                    SCOPE.ENROLL,
                    PUSH_ACTION.REGISTRATION_URL,
                    options=options)
                pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER)
                smartphone_data = _build_smartphone_data(
                    self.token.serial, challenge, registration_url,
                    pem_privkey, options)
                res = fb_gateway.submit_message(
                    self.get_tokeninfo("firebase_token"), smartphone_data)

            # Create the challenge in the challenge table if either the message
            # was successfully submitted to the Firebase API or if polling is
            # allowed in general or for this specific token.
            allow_polling = get_action_values_from_options(
                SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING,
                options=options) or PushAllowPolling.ALLOW
            if ((allow_polling == PushAllowPolling.ALLOW or
                 (allow_polling == PushAllowPolling.TOKEN and is_true(
                     self.get_tokeninfo(POLLING_ALLOWED, default='True'))))
                    or res):
                validity = int(
                    get_from_config('DefaultChallengeValidityTime', 120))
                tokentype = self.get_tokentype().lower()
                # Maybe there is a PushChallengeValidityTime...
                lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
                validity = int(get_from_config(lookup_for, validity))

                # Create the challenge in the database
                db_challenge = Challenge(self.token.serial,
                                         transaction_id=transactionid,
                                         challenge=challenge,
                                         data=data,
                                         session=options.get("session"),
                                         validitytime=validity)
                db_challenge.save()
                self.challenge_janitor()

            # If sending the Push message failed, we still raise an error and a warning.
            if not res:
                log.warning(
                    u"Failed to submit message to Firebase service for token {0!s}."
                    .format(self.token.serial))
                raise ValidateError(
                    "Failed to submit message to Firebase service.")
        else:
            log.warning(u"The token {0!s} has no tokeninfo {1!s}. "
                        u"The message could not be sent.".format(
                            self.token.serial, PUSH_ACTION.FIREBASE_CONFIG))
            raise ValidateError(
                "The token has no tokeninfo. Can not send via Firebase service."
            )

        return True, message, db_challenge.transaction_id, reply_dict
示例#14
0
    def create_challenge(self, transactionid=None, options=None):
        """
        This method creates a challenge, which is submitted to the user.
        The submitted challenge will be preserved in the challenge
        database.

        If no transaction id is given, the system will create a transaction
        id and return it, so that the response can refer to this transaction.

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :type options: dict
        :return: tuple of (bool, message, transactionid, attributes)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional ``attributes``, which are displayed in the JSON response.
        """
        options = options or {}
        message = get_action_values_from_options(
            SCOPE.AUTH, ACTION.CHALLENGETEXT,
            options) or DEFAULT_CHALLENGE_TEXT

        attributes = None
        data = None
        fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
        if fb_identifier:
            challenge = b32encode_and_unicode(geturandom())
            fb_gateway = create_sms_instance(fb_identifier)
            pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER)
            smartphone_data = _build_smartphone_data(self.token.serial,
                                                     challenge, fb_gateway,
                                                     pem_privkey, options)
            res = fb_gateway.submit_message(
                self.get_tokeninfo("firebase_token"), smartphone_data)
            if not res:
                raise ValidateError(
                    "Failed to submit message to firebase service.")
        else:
            log.warning(u"The token {0!s} has no tokeninfo {1!s}. "
                        u"The message could not be sent.".format(
                            self.token.serial, PUSH_ACTION.FIREBASE_CONFIG))
            raise ValidateError(
                "The token has no tokeninfo. Can not send via firebase service."
            )

        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        # Maybe there is a PushChallengeValidityTime...
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=transactionid,
                                 challenge=challenge,
                                 data=data,
                                 session=options.get("session"),
                                 validitytime=validity)
        db_challenge.save()
        self.challenge_janitor()
        return True, message, db_challenge.transaction_id, attributes
示例#15
0
    def create_challenge(self, transactionid=None, options=None):
        """
        This method creates a challenge, which is submitted to the user.
        The submitted challenge will be preserved in the challenge
        database.

        If no transaction id is given, the system will create a transaction
        id and return it, so that the response can refer to this transaction.

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :type options: dict
        :return: tuple of (bool, message, transactionid, attributes)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional ``attributes``, which are displayed in the JSON response.
        """
        res = False
        options = options or {}
        message = get_action_values_from_options(
            SCOPE.AUTH, ACTION.CHALLENGETEXT,
            options) or DEFAULT_CHALLENGE_TEXT

        sslverify = get_action_values_from_options(
            SCOPE.AUTH, PUSH_ACTION.SSL_VERIFY, options) or "1"
        sslverify = getParam({"sslverify": sslverify},
                             "sslverify",
                             allowed_values=["0", "1"],
                             default="1")

        attributes = None
        data = None
        challenge = b32encode_and_unicode(geturandom())
        fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
        if fb_identifier:
            # We send the challenge to the Firebase service
            fb_gateway = create_sms_instance(fb_identifier)
            url = fb_gateway.smsgateway.option_dict.get(
                FIREBASE_CONFIG.REGISTRATION_URL)
            message_on_mobile = get_action_values_from_options(
                SCOPE.AUTH, PUSH_ACTION.MOBILE_TEXT,
                options) or DEFAULT_MOBILE_TEXT
            title = get_action_values_from_options(
                SCOPE.AUTH, PUSH_ACTION.MOBILE_TITLE, options) or "privacyIDEA"
            smartphone_data = {
                "nonce": challenge,
                "question": message_on_mobile,
                "serial": self.token.serial,
                "title": title,
                "sslverify": sslverify,
                "url": url
            }
            # Create the signature.
            # value to string
            sign_string = u"{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format(
                **smartphone_data)

            pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER)
            privkey_obj = serialization.load_pem_private_key(
                to_bytes(pem_privkey), None, default_backend())

            # Sign the data with PKCS1 padding. Not all Androids support PSS padding.
            signature = privkey_obj.sign(sign_string.encode("utf8"),
                                         padding.PKCS1v15(), hashes.SHA256())
            smartphone_data["signature"] = b32encode_and_unicode(signature)

            res = fb_gateway.submit_message(
                self.get_tokeninfo("firebase_token"), smartphone_data)
            if not res:
                raise ValidateError(
                    "Failed to submit message to firebase service.")
        else:
            log.warning(u"The token {0!s} has no tokeninfo {1!s}. "
                        u"The message could not be sent.".format(
                            self.token.serial, PUSH_ACTION.FIREBASE_CONFIG))
            raise ValidateError(
                "The token has no tokeninfo. Can not send via firebase service."
            )

        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        # Maybe there is a PushChallengeValidityTime...
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=transactionid,
                                 challenge=challenge,
                                 data=data,
                                 session=options.get("session"),
                                 validitytime=validity)
        db_challenge.save()
        self.challenge_janitor()
        return True, message, db_challenge.transaction_id, attributes
示例#16
0
    def create_challenge(self, transactionid=None, options=None):
        """
        create a challenge, which is submitted to the user

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
                You can pass exception=1 to raise an exception, if
                the SMS could not be sent. Otherwise the message is contained in the response.
        :return: tuple of (bool, message and data)
                 bool, if submit was successful
                 message is submitted to the user
                 data is preserved in the challenge
                 attributes - additional attributes, which are displayed in the
                    output
        """
        success = False
        options = options or {}
        return_message = get_action_values_from_options(
            SCOPE.AUTH, "{0!s}_{1!s}".format(self.get_class_type(),
                                             ACTION.CHALLENGETEXT),
            options) or _("Enter the OTP from the SMS:")
        attributes = {'state': transactionid}
        validity = self._get_sms_timeout()

        if self.is_active() is True:
            counter = self.get_otp_count()
            log.debug("counter={0!r}".format(counter))
            self.inc_otp_counter(counter, reset=False)
            # At this point we must not bail out in case of an
            # Gateway error, since checkPIN is successful. A bail
            # out would cancel the checking of the other tokens
            try:
                message_template = self._get_sms_text(options)
                success, sent_message = self._send_sms(
                    message=message_template)

                # Create the challenge in the database
                if is_true(get_from_config("sms.concurrent_challenges")):
                    data = self.get_otp()[2]
                else:
                    data = None
                db_challenge = Challenge(self.token.serial,
                                         transaction_id=transactionid,
                                         challenge=options.get("challenge"),
                                         data=data,
                                         session=options.get("session"),
                                         validitytime=validity)
                db_challenge.save()
                transactionid = transactionid or db_challenge.transaction_id
            except Exception as e:
                info = ("The PIN was correct, but the "
                        "SMS could not be sent: %r" % e)
                log.warning(info)
                log.debug("{0!s}".format(traceback.format_exc()))
                return_message = info
                if is_true(options.get("exception")):
                    raise Exception(info)

        expiry_date = datetime.datetime.now() + \
                                    datetime.timedelta(seconds=validity)
        attributes['valid_until'] = "{0!s}".format(expiry_date)

        return success, return_message, transactionid, attributes
示例#17
0
    def create_challenge(self, transactionid=None, options=None):
        """
        This method creates a challenge, which is submitted to the user.
        The submitted challenge will be preserved in the challenge
        database.

        If no transaction id is given, the system will create a transaction
        id and return it, so that the response can refer to this transaction.

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :type options: dict
        :return: tuple of (bool, message, transactionid, reply_dict)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
        """
        options = options or {}
        message = 'Please answer the challenge'
        attributes = {}

        # Get ValidityTime=120s. Maybe there is a OCRAChallengeValidityTime...
        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        # Get the OCRASUITE from the token information
        ocrasuite = self.get_tokeninfo("ocrasuite") or OCRA_DEFAULT_SUITE

        challenge = options.get("challenge")
        # TODO: we could add an additional parameter to hash the challenge
        # cleartext -> sha1
        if not challenge:
            # If no challenge is given in the Request, we create a random
            # challenge based on the OCRA-SUITE
            os = OCRASuite(ocrasuite)
            challenge = os.create_challenge()
        else:
            # Add a random challenge
            if options.get("addrandomchallenge"):
                challenge += get_alphanum_str(
                    int(options.get("addrandomchallenge")))
            attributes["original_challenge"] = challenge
            attributes["qrcode"] = create_img(challenge)
            if options.get("hashchallenge", "").lower() == "sha256":
                challenge = hexlify_and_unicode(
                    hashlib.sha256(to_bytes(challenge)).digest())
            elif options.get("hashchallenge", "").lower() == "sha512":
                challenge = hexlify_and_unicode(
                    hashlib.sha512(to_bytes(challenge)).digest())
            elif options.get("hashchallenge"):
                challenge = hexlify_and_unicode(
                    hashlib.sha1(to_bytes(challenge)).digest())

        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=None,
                                 challenge=challenge,
                                 data=None,
                                 session=None,
                                 validitytime=validity)
        db_challenge.save()

        attributes["challenge"] = challenge
        reply_dict = {"attributes": attributes}

        return True, message, db_challenge.transaction_id, reply_dict
示例#18
0
    def create_challenge(self, transactionid=None, options=None):
        """
        create a challenge, which is submitted to the user

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
            You can pass ``exception=1`` to raise an exception, if
            the SMS could not be sent. Otherwise the message is contained in the response.
        :return: tuple
            of (success, message, transactionid, attributes)

                * success: if submit was successful
                * message: the text submitted to the user
                * transactionid: the given or generated transactionid
                * reply_dict: additional dictionary, which is added to the response
        :rtype: tuple(bool, str, str, dict)
        """
        success = False
        options = options or {}
        return_message = get_action_values_from_options(
            SCOPE.AUTH, "{0!s}_{1!s}".format(self.get_class_type(),
                                             ACTION.CHALLENGETEXT),
            options) or _("Enter the OTP from the Email:")
        reply_dict = {'attributes': {'state': transactionid}}
        validity = int(get_from_config("email.validtime", 120))

        if self.is_active() is True:
            counter = self.get_otp_count()
            log.debug("counter={0!r}".format(counter))
            self.inc_otp_counter(counter, reset=False)
            # At this point we must not bail out in case of an
            # Gateway error, since checkPIN is successful. A bail
            # out would cancel the checking of the other tokens
            try:
                message_template, mimetype = self._get_email_text_or_subject(
                    options)
                subject_template, _n = self._get_email_text_or_subject(
                    options, EMAILACTION.EMAILSUBJECT, "Your OTP")

                # Create the challenge in the database
                if is_true(get_from_config("email.concurrent_challenges")):
                    data = self.get_otp()[2]
                else:
                    data = None
                db_challenge = Challenge(self.token.serial,
                                         transaction_id=transactionid,
                                         challenge=options.get("challenge"),
                                         data=data,
                                         session=options.get("session"),
                                         validitytime=validity)
                db_challenge.save()
                transactionid = transactionid or db_challenge.transaction_id
                # We send the email after creating the challenge for testing.
                success, sent_message = self._compose_email(
                    message=message_template,
                    subject=subject_template,
                    mimetype=mimetype)

            except Exception as e:
                info = ("The PIN was correct, but the "
                        "EMail could not be sent: %r" % e)
                log.warning(info)
                log.debug(u"{0!s}".format(traceback.format_exc()))
                return_message = info
                if is_true(options.get("exception")):
                    raise Exception(info)

        expiry_date = datetime.datetime.now() + \
                                    datetime.timedelta(seconds=validity)
        reply_dict['attributes']['valid_until'] = "{0!s}".format(expiry_date)

        return success, return_message, transactionid, reply_dict
示例#19
0
    def create_challenge(self, transactionid=None, options=None):
        """
        This method creates a challenge, which is submitted to the user.
        The submitted challenge will be preserved in the challenge
        database.

        The challenge is a randomly selected question of the available
        questions for this token.

        If no transaction id is given, the system will create a transaction
        id and return it, so that the response can refer to this transaction.

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :type options: dict
        :return: tuple of (bool, message, transactionid, attributes)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional ``attributes``, which are displayed in the JSON response.
        """
        options = options or {}
        questions = {}
        attributes = {}

        # Get an integer list of the already used questions
        used_questions = [
            int(x) for x in options.get("data", "").split(",")
            if options.get("data")
        ]
        # Fill the questions of the token
        for tinfo in self.token.info_list:
            if tinfo.Type == "password":
                # Append a tuple of the DB Id and the actual question
                questions[tinfo.id] = tinfo.Key
        # if all questions are used up, make a new round
        if len(questions) == len(used_questions):
            log.info(
                u"User has only {0!s} questions in his token. Reusing questions now."
                .format(len(questions)))
            used_questions = []
        # Reduce the allowed questions
        remaining_questions = {
            k: v
            for (k, v) in questions.items() if k not in used_questions
        }
        message_id = random.choice(list(remaining_questions))
        message = remaining_questions[message_id]
        used_questions = (options.get("data", "") +
                          ",{0!s}".format(message_id)).strip(",")

        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        # Maybe there is a QUESTIONChallengeValidityTime...
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=transactionid,
                                 data=used_questions,
                                 session=options.get("session"),
                                 challenge=message,
                                 validitytime=validity)
        db_challenge.save()
        expiry_date = datetime.datetime.now() + \
                      datetime.timedelta(seconds=validity)
        attributes['valid_until'] = "{0!s}".format(expiry_date)
        return True, message, db_challenge.transaction_id, attributes
    def create_challenge(self, transactionid=None, options=None):
        """
        create a challenge, which is submitted to the user

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :return: tuple of (bool, message, transactionid, attributes)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional ``attributes``, which are displayed in the JSON response.
        """
        options = options or {}
        return_message = get_action_values_from_options(
            SCOPE.AUTH, "{0!s}_{1!s}".format(self.get_class_type(),
                                             ACTION.CHALLENGETEXT),
            options) or DEFAULT_CHALLENGE_TEXT

        if self.get_tokeninfo("multichallenge"):
            # In case of multichallenge we ask only once.
            position_count = 1
        else:
            position_count = int(
                get_action_values_from_options(
                    SCOPE.AUTH, "{0!s}_{1!s}".format(
                        self.get_class_type(), PIIXACTION.COUNT), options)
                or DEFAULT_POSITION_COUNT)

        attributes = {'state': transactionid}
        validity = 120

        if self.is_active() is True:
            # We need to get a number of random positions from the secret string
            secret_length = len(self.token.get_otpkey().getKey())
            if not secret_length:
                raise ValidateError(
                    "The indexedsecret token has an empty secret and "
                    "can not be used for authentication.")
            random_positions = [
                urandom.randint(1, secret_length)
                for _x in range(0, position_count)
            ]
            position_str = ",".join(
                ["{0!s}".format(x) for x in random_positions])
            attributes["random_positions"] = random_positions

            db_challenge = Challenge(self.token.serial,
                                     transaction_id=transactionid,
                                     challenge=options.get("challenge"),
                                     data=position_str,
                                     session=options.get("session"),
                                     validitytime=validity)
            db_challenge.save()
            transactionid = transactionid or db_challenge.transaction_id
            return_message = return_message.format(position_str)

        expiry_date = datetime.datetime.now() + \
                                    datetime.timedelta(seconds=validity)
        attributes['valid_until'] = "{0!s}".format(expiry_date)

        return True, return_message, transactionid, attributes
示例#21
0
    def create_challenge(self, transactionid=None, options=None):
        """
        This method creates a challenge, which is submitted to the user.
        The submitted challenge will be preserved in the challenge
        database.

        If no transaction id is given, the system will create a transaction
        id and return it, so that the response can refer to this transaction.

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :type options: dict
        :return: tuple of (bool, message, transactionid, attributes)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional ``attributes``, which are displayed in the JSON response.
        """
        options = options or {}
        message = get_action_values_from_options(
            SCOPE.AUTH, "{0!s}_{1!s}".format(
                self.get_class_type(), ACTION.CHALLENGETEXT), options) or _(
                    u'Please confirm with your U2F token ({0!s})').format(
                        self.token.description)

        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        challenge = geturandom(32)
        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=transactionid,
                                 challenge=hexlify_and_unicode(challenge),
                                 data=None,
                                 session=options.get("session"),
                                 validitytime=validity)
        db_challenge.save()
        sec_object = self.token.get_otpkey()
        key_handle_hex = sec_object.getKey()
        key_handle_bin = binascii.unhexlify(key_handle_hex)
        key_handle_url = url_encode(key_handle_bin)
        challenge_url = url_encode(challenge)
        u2f_sign_request = {
            "appId": self.get_tokeninfo("appId"),
            "version": U2F_Version,
            "challenge": challenge_url,
            "keyHandle": key_handle_url
        }

        image_url = IMAGES.get(self.token.description.lower().split()[0], "")
        response_details = {
            "u2fSignRequest": u2f_sign_request,
            "hideResponseInput": True,
            "img": image_url
        }

        return True, message, db_challenge.transaction_id, response_details
示例#22
0
    def create_challenge(self, transactionid=None, options=None):
        """
        This method creates a challenge, which is submitted to the user.
        The submitted challenge will be preserved in the challenge
        database.

        If no transaction id is given, the system will create a transaction
        id and return it, so that the response can refer to this transaction.

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :type options: dict
        :return: tuple of (bool, message, transactionid, attributes)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional ``attributes``, which are displayed in the JSON response.
        """
        options = options or {}
        message = 'Please scan the QR Code'

        # Get ValidityTime=120s. Maybe there is a TIQRChallengeValidityTime...
        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        # We need to set the user ID
        user_identifier, user_displayname = self.get_user_displayname()

        service_identifier = get_from_config("tiqr.serviceIdentifier") or \
                             "org.privacyidea"

        # Get the OCRASUITE from the token information
        ocrasuite = self.get_tokeninfo("ocrasuite") or OCRA_DEFAULT_SUITE
        # Depending on the OCRA-SUITE we create the challenge
        os = OCRASuite(ocrasuite)
        challenge = os.create_challenge()

        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=None,
                                 challenge=challenge,
                                 data=None,
                                 session=options.get("session"),
                                 validitytime=validity)
        db_challenge.save()

        # Encode the user to UTF-8 and quote the result
        encoded_user_identifier = urllib.quote_plus(user_identifier.encode('utf-8'))
        authurl = u"tiqrauth://{0!s}@{1!s}/{2!s}/{3!s}".format(
                                              encoded_user_identifier,
                                              service_identifier,
                                              db_challenge.transaction_id,
                                              challenge)
        attributes = {"img": create_img(authurl, width=250),
                      "value": authurl,
                      "poll": True,
                      "hideResponseInput": True}

        return True, message, db_challenge.transaction_id, attributes