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() authurl = "tiqrauth://%s@%s/%s/%s" % (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
def test_18_challenges(self): db_token = Token.query.filter_by(serial=self.serial1).first() token = TokenClass(db_token) 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": "123456789"}) self.assertTrue(resp, resp) # test if challenge is valid C = Challenge("S123455", transaction_id="tid", challenge="Who are you?") C.save() self.assertTrue(C.is_valid())
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)
def test_20_check_challenge_response(self): db_token = Token.query.filter_by(serial=self.serial1).first() db_token.set_pin("test") token = TokenClass(db_token) r = token.check_challenge_response(user=None, passw="123454") # check that challenge does not match self.assertTrue(r == -1) # 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) # test the challenge janitor c1 = Challenge(self.serial1, transaction_id="t1", validitytime=0) c1.save() c2 = Challenge(self.serial1, transaction_id="t2", validitytime=0) c2.save() c3 = Challenge(self.serial1, transaction_id="t3", validitytime=0) c3.save() c4 = Challenge(self.serial1, transaction_id="t4", validitytime=0) c4.save() # Delete potentiall expired challenges token.challenge_janitor() # Check, that the challenge does not exist anymore r = Challenge.query.filter(Challenge.transaction_id == "t1").count() self.assertTrue(r == 0, r)
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 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) 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
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
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)
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
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=%r" % 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'] = "%s" % expiry_date return success, return_message, transactionid, 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 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
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
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) resp = token.is_challenge_response( User(login="******", realm=self.realm1), "test" + _digi2daplug("123456"), 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()
def test_20_check_challenge_response(self): db_token = Token.query.filter_by(serial=self.serial1).first() db_token.set_pin("test") token = DaplugTokenClass(db_token) r = token.check_challenge_response(user=None, passw=_digi2daplug("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=_digi2daplug("123454"), options={"state": "mytransaction"}) # The challenge matches, but the OTP does not match! self.assertTrue(r == -1, r)
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()
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 _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
def test_20_check_challenge_response(self): db_token = Token.query.filter_by(serial=self.serial1).first() db_token.set_pin("test") token = TotpTokenClass(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) # test the challenge janitor c1 = Challenge(self.serial1, transaction_id="t1", validitytime=0) c1.save() c2 = Challenge(self.serial1, transaction_id="t2", validitytime=0) c2.save() c3 = Challenge(self.serial1, transaction_id="t3", validitytime=100) c3.save() c4 = Challenge(self.serial1, transaction_id="t4", validitytime=100) c4.save() num = Challenge.query.filter(Challenge.serial==self.serial1).count() self.assertTrue(num >= 5, num) # We pass the third challenge as the valid challenge. # So 3 challenges will be deleted. token.challenge_janitor() # Now see if those challenges are deleted num1 = Challenge.query.filter(Challenge.transaction_id == "t1").count() num2 = Challenge.query.filter(Challenge.transaction_id == "t2").count() num3 = Challenge.query.filter(Challenge.transaction_id == "t3").count() num4 = Challenge.query.filter(Challenge.transaction_id == "t4").count() self.assertTrue(num1 == 0) self.assertTrue(num2 == 0) self.assertTrue(num3 == 1) self.assertTrue(num4 == 1)
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 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()
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())
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)
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
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
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
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
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 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)
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
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
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
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
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).get("result") detail = json.loads(res.data).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).get("result") detail = json.loads(res.data).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).get("result") self.assertEqual(result.get("status"), False) 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)
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
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
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 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 = binascii.hexlify(hashlib.sha256(challenge).digest()) elif options.get("hashchallenge", "").lower() == "sha512": challenge = binascii.hexlify(hashlib.sha512(challenge).digest()) elif options.get("hashchallenge"): challenge = binascii.hexlify(hashlib.sha1(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 return True, message, db_challenge.transaction_id, attributes