def update(self, param, reset_failcount=True):
        """
        update - process initialization parameters

        :param param: dict of initialization parameters
        :type param: dict

        :return: nothing
        """
        if is_true(getParam(param, 'genkey', optional)):
            raise ParameterError("Generating OTP keys is not supported")

        upd_param = param.copy()

        # If the OTP key is given, it is given as a 496-character hex string which
        # encodes a 248-byte blob. As we want to set a 248-byte OTPKey (= Blob),
        # we unhexlify the OTP key
        if 'otpkey' in param:
            if len(param['otpkey']) != 496:
                raise ParameterError(
                    'Expected OTP key as 496-character hex string, but length is {!s}'
                    .format(len(param['otpkey'])))
            try:
                upd_param['otpkey'] = binascii.unhexlify(upd_param['otpkey'])
            except (binascii.Error, TypeError):
                raise ParameterError(
                    'Expected OTP key as 496-character hex string, but it is malformed'
                )

        TokenClass.update(self, upd_param, reset_failcount)
Exemple #2
0
def decode_base32check(encoded_data, always_upper=True):
    """
    Decode arbitrary data which is given in the following format::

        strip_padding(base32(sha1(payload)[:4] + payload))

    Raise a ParameterError if the encoded payload is malformed.
    :param encoded_data: The base32 encoded data.
    :type encoded_data: basestring
    :param always_upper: If we should convert lowercase to uppercase
    :type always_upper: bool
    :return: hex-encoded payload
    """
    # First, add the padding to have a multiple of 8 bytes
    if always_upper:
        encoded_data = encoded_data.upper()
    encoded_length = len(encoded_data)
    if encoded_length % 8 != 0:
        encoded_data += "=" * (8 - (encoded_length % 8))
    assert len(encoded_data) % 8 == 0
    # Decode as base32
    try:
        decoded_data = base64.b32decode(encoded_data)
    except TypeError:
        raise ParameterError("Malformed base32check data: Invalid base32")
    # Extract checksum and payload
    if len(decoded_data) < 4:
        raise ParameterError("Malformed base32check data: Too short")
    checksum, payload = decoded_data[:4], decoded_data[4:]
    payload_hash = hashlib.sha1(payload).digest()
    if payload_hash[:4] != checksum:
        raise ParameterError("Malformed base32check data: Incorrect checksum")
    return binascii.hexlify(payload)
Exemple #3
0
    def update(self, param, reset_failcount=True):
        """
        process the initialization parameters

        We need to distinguish the first authentication step
        and the second authentication step.

        1. step:
            ``param`` contains:

            - ``type``
            - ``genkey``

        2. step:
            ``param`` contains:

            - ``serial``
            - ``fbtoken``
            - ``pubkey``

        :param param: dict of initialization parameters
        :type param: dict

        :return: nothing
        """
        upd_param = {}
        for k, v in param.items():
            upd_param[k] = v

        if "serial" in upd_param and "fbtoken" in upd_param and "pubkey" in upd_param:
            # We are in step 2:
            if self.token.rollout_state != "clientwait":
                raise ParameterError("Invalid state! The token you want to enroll is not in the state 'clientwait'.")
            enrollment_credential = getParam(upd_param, "enrollment_credential", optional=False)
            if enrollment_credential != self.get_tokeninfo("enrollment_credential"):
                raise ParameterError("Invalid enrollment credential. You are not authorized to finalize this token.")
            self.del_tokeninfo("enrollment_credential")
            self.token.rollout_state = "enrolled"
            self.token.active = True
            self.add_tokeninfo(PUBLIC_KEY_SMARTPHONE, upd_param.get("pubkey"))
            self.add_tokeninfo("firebase_token", upd_param.get("fbtoken"))
            # create a keypair for the server side.
            pub_key, priv_key = generate_keypair(4096)
            self.add_tokeninfo(PUBLIC_KEY_SERVER, pub_key)
            self.add_tokeninfo(PRIVATE_KEY_SERVER, priv_key, "password")

        elif "genkey" in upd_param:
            # We are in step 1:
            upd_param["2stepinit"] = 1
            self.add_tokeninfo("enrollment_credential", geturandom(20, hex=True))
            # We also store the firebase config, that was used during the enrollment.
            self.add_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG, param.get(PUSH_ACTION.FIREBASE_CONFIG))
        else:
            raise ParameterError("Invalid Parameters. Either provide (genkey) or (serial, fbtoken, pubkey).")

        TokenClass.update(self, upd_param, reset_failcount)
Exemple #4
0
        def check_user_or_serial_in_request_wrapper(*args, **kwds):
            user = self.request.all_data.get("user", "").strip()
            serial = self.request.all_data.get("serial", "").strip()
            if not serial and not user:
                raise ParameterError(_("You need to specify a serial or a user."))
            if "*" in serial:
                raise ParameterError(_("Invalid serial number."))
            if "%" in user:
                raise ParameterError(_("Invalid user."))

            f_result = func(*args, **kwds)
            return f_result
def set_periodic_task(name=None,
                      interval=None,
                      nodes=None,
                      taskmodule=None,
                      ordering=0,
                      options=None,
                      active=True,
                      id=None,
                      retry_if_failed=True):
    """
    Set a periodic task configuration. If ``id`` is None, this creates a new database entry.
    Otherwise, an existing entry is overwritten. We actually ensure that such
    an entry exists and throw a ``ParameterError`` otherwise.

    This also checks if ``interval`` is a valid cron expression, and throws
    a ``ParameterError`` if it is not.

    :param name: Unique name of the periodic task
    :type name: unicode
    :param interval: Periodicity as a string in crontab format
    :type interval: unicode
    :param nodes: List of nodes on which this task should be run
    :type nodes: list of unicode
    :param taskmodule: Name of the task module
    :type taskmodule: unicode
    :param ordering: Ordering of the periodic task (>= 0). Lower numbers are executed first.
    :type ordering: int
    :param options: Additional options for the task module
    :type options: Dictionary mapping unicodes to values that can be converted to unicode or None
    :param active: Flag determining whether the periodic task is active
    :type active: bool
    :param retry_if_failed: true if privacyidea should retry to execute this periodic task if it fails
                            false if privacyidea should just try onetime regardless the failing of the task
    :type retry_if_failed: bool
    :param id: ID of the existing entry, or None
    :type id: int or None
    :return: ID of the entry
    """
    try:
        croniter(interval)
    except ValueError as e:
        raise ParameterError("Invalid interval: {!s}".format(e))
    if ordering < 0:
        raise ParameterError("Invalid ordering: {!s}".format(ordering))
    if id is not None:
        # This will throw a ParameterError if there is no such entry
        get_periodic_task_by_id(id)
    periodic_task = PeriodicTask(name, active, interval, nodes, taskmodule,
                                 ordering, options, id, retry_if_failed)
    return periodic_task.id
Exemple #6
0
    def get_offline_otps(token_obj, otppin, amount, rounds=ROUNDS):
        """
        Retrieve the desired number of passwords (= PIN + OTP), hash them
        and return them in a dictionary. Increase the token counter.
        :param token_obj: token in question
        :param otppin: The OTP PIN to prepend in the passwords. The PIN is not validated!
        :param amount: Number of OTP values (non-negative!)
        :param rounds: Number of PBKDF2 rounds
        :return: dictionary
        """
        if amount < 0:
            raise ParameterError("Invalid refill amount: {!r}".format(amount))
        (res, err, otp_dict) = token_obj.get_multi_otp(count=amount,
                                                       counter_index=True)
        otps = otp_dict.get("otp")
        for key in otps.keys():
            # Return the hash of OTP PIN and OTP values
            otps[key] = pbkdf2_sha512.using(
                rounds=rounds, salt_size=10).hash(otppin + otps.get(key))
        # We do not disable the token, so if all offline OTP values
        # are used, the token can be used the authenticate online again.
        # token_obj.enable(False)
        # increase the counter by the consumed values and
        # also store it in tokeninfo.
        token_obj.inc_otp_counter(increment=amount)

        return otps
Exemple #7
0
    def get_refill(token_obj, password, options=None):
        """
        Returns new authentication OTPs to refill the client

        To do so we also verify the password, which may consist of PIN + OTP.

        :param token_obj: Token object
        :param password: PIN + OTP
        :param options: dict that might contain "count" and "rounds"
        :return: a dictionary of auth items
        """
        options = options or {}
        count = int(options.get("count", 100))
        rounds = int(options.get("rounds", ROUNDS))
        _r, otppin, otpval = token_obj.split_pin_pass(password)
        if not _r:
            raise ParameterError("Could not split password")
        current_token_counter = token_obj.token.count
        first_offline_counter = current_token_counter - count
        if first_offline_counter < 0:
            first_offline_counter = 0
        # find the value in the offline OTP values! This resets the token.count!
        matching_count = token_obj.check_otp(otpval, first_offline_counter, count)
        token_obj.set_otp_count(current_token_counter)
        # Raise an exception *after* we reset the token counter
        if matching_count < 0:
            raise ValidateError("You provided a wrong OTP value.")
        # We have to add 1 here: Assume *first_offline_counter* is the counter value of the first offline OTP
        # we sent to the client. Assume the client then requests a refill with that exact OTP value.
        # Then, we need to respond with a refill of one OTP value, as the client has consumed one OTP value.
        counter_diff = matching_count - first_offline_counter + 1
        otps = MachineApplication.get_offline_otps(token_obj, otppin, counter_diff, rounds)
        token_obj.add_tokeninfo(key="offline_counter",
                                value=count)
        return otps
Exemple #8
0
    def get_init_detail(self, params=None, user=None):
        """
        This returns the init details during enrollment.

        In the 1st step the QR Code is returned.
        """
        response_detail = TokenClass.get_init_detail(self, params, user)
        if "otpkey" in response_detail:
            del response_detail["otpkey"]
        params = params or {}
        user = user or User()
        tokenlabel = params.get("tokenlabel", "<s>")
        tokenissuer = params.get("tokenissuer", "privacyIDEA")
        sslverify = getParam(params, PUSH_ACTION.SSL_VERIFY, allowed_values=["0", "1"], default="1")
        # Add rollout state the response
        response_detail['rollout_state'] = self.token.rollout_state

        extra_data = {"enrollment_credential": self.get_tokeninfo("enrollment_credential")}
        imageurl = params.get("appimageurl")
        if imageurl:
            extra_data.update({"image": imageurl})
        if self.token.rollout_state == "clientwait":
            # Get the values from the configured PUSH config
            fb_identifier = params.get(PUSH_ACTION.FIREBASE_CONFIG)
            firebase_configs = get_smsgateway(identifier=fb_identifier, gwtype=GWTYPE)
            if len(firebase_configs) != 1:
                raise ParameterError("Unknown Firebase configuration!")
            fb_options = firebase_configs[0].option_dict
            for k in [FIREBASE_CONFIG.PROJECT_NUMBER, FIREBASE_CONFIG.PROJECT_ID,
                      FIREBASE_CONFIG.APP_ID, FIREBASE_CONFIG.API_KEY,
                      FIREBASE_CONFIG.APP_ID_IOS, FIREBASE_CONFIG.API_KEY_IOS]:
                extra_data[k] = fb_options.get(k)
            # this allows to upgrade our crypto
            extra_data["v"] = 1
            extra_data["serial"] = self.get_serial()
            extra_data["sslverify"] = sslverify
            # We display this during the first enrollment step!
            qr_url = create_push_token_url(url=fb_options.get(FIREBASE_CONFIG.REGISTRATION_URL),
                                           user=user.login,
                                           realm=user.realm,
                                           serial=self.get_serial(),
                                           tokenlabel=tokenlabel,
                                           issuer=tokenissuer,
                                           user_obj=user,
                                           extra_data=extra_data,
                                           ttl=fb_options.get(FIREBASE_CONFIG.TTL))
            response_detail["pushurl"] = {"description": _("URL for privacyIDEA Push Token"),
                                          "value": qr_url,
                                          "img": create_img(qr_url, width=250)
                                          }
            self.add_tokeninfo(FIREBASE_CONFIG.PROJECT_ID, fb_options.get(FIREBASE_CONFIG.PROJECT_ID))

            response_detail["enrollment_credential"] = self.get_tokeninfo("enrollment_credential")

        elif self.token.rollout_state == "enrolled":
            # in the second enrollment step we return the public key of the server to the smartphone.
            pubkey = strip_key(self.get_tokeninfo(PUBLIC_KEY_SERVER))
            response_detail["public_key"] = pubkey

        return response_detail
Exemple #9
0
    def api_endpoint(cls, request, g):
        """
        This provides a function to be plugged into the API endpoint
        /ttype/u2f

        The u2f token can return the facet list at this URL.

        :param request: The Flask request
        :param g: The Flask global object g
        :return: Flask Response or text
        """
        configured_app_id = get_from_config("u2f.appId")
        if configured_app_id is None:
            raise ParameterError("u2f is not configured")
        app_id = configured_app_id.strip("/")

        # Read the facets from the policies
        pol_facets = Match.action_only(
            g, scope=SCOPE.AUTH,
            action=U2FACTION.FACETS).action_values(unique=False)
        facet_list = ["https://{0!s}".format(x) for x in pol_facets]
        facet_list.append(app_id)

        log.debug("Sending facets lists for appId {0!s}: {1!s}".format(
            app_id, facet_list))
        res = {
            "trustedFacets": [{
                "version": {
                    "major": 1,
                    "minor": 0
                },
                "ids": facet_list
            }]
        }
        return "fido.trusted-apps+json", res
def get_scheduled_periodic_tasks(node, current_timestamp=None, interval_tzinfo=None):
    """
    Collect all periodic tasks that should be run on a specific node, ordered by
    their ordering.

    This function is usually called by the local cron runner which is aware of the
    current local node name.

    :param node: Node name
    :type node: unicode
    :param current_timestamp: The current timestamp, defaults to the current time
    :type current_timestamp: timezone-aware datetime
    :param interval_tzinfo: timezone in which the crontab expression should be interpreted
    :type interval_tzinfo: tzinfo, defaults to local time
    :return: List of periodic task dictionaries
    """
    active_ptasks = get_periodic_tasks(node=node, active=True)
    if current_timestamp is None:
        current_timestamp = datetime.now(tzutc())
    if current_timestamp.tzinfo is None:
        raise ParameterError(u"expected timezone-aware datetime, got {!r}".format(current_timestamp))
    scheduled_ptasks = []
    log.debug(u"Collecting periodic tasks to run at {!s}".format(current_timestamp.isoformat()))
    for ptask in active_ptasks:
        try:
            next_timestamp = calculate_next_timestamp(ptask, node, interval_tzinfo)
            log.debug(u"Next scheduled run of {!r}: {!s}".format(ptask["name"], next_timestamp.isoformat()))
            if next_timestamp <= current_timestamp:
                log.debug(u"Scheduling periodic task {!r}".format(ptask["name"]))
                scheduled_ptasks.append(ptask)
        except Exception as e:
            log.warning(u"Ignoring periodic task {!r}: {!r}".format(ptask["name"], e))
    return scheduled_ptasks
    def generate_symmetric_key(self, server_component, client_component,
                               options=None):
        """
        Generate a composite key from a server and client component
        using a PBKDF2-based scheme.

        :param server_component: The component usually generated by privacyIDEA
        :type server_component: hex string
        :param client_component: The component usually generated by the
            client (e.g. smartphone)
        :type client_component: hex string
        :param options:
        :return: the new generated key as hex string
        """
        # As /token/init has already been called before, self.hashlib
        # is already set.
        keysize = keylen[self.hashlib]
        rounds = int(self.get_tokeninfo('2step_difficulty'))
        decoded_client_component = binascii.unhexlify(client_component)
        expected_client_size = int(self.get_tokeninfo('2step_clientsize'))
        if expected_client_size != len(decoded_client_component):
            raise ParameterError('Client Secret Size is expected to be {}, but is {}'.format(
                expected_client_size, len(decoded_client_component)
            ))
        # Based on the two components, we generate a symmetric key using PBKDF2
        # We pass the hex-encoded server component as the password and the
        # client component as the salt.
        secret = pbkdf2(server_component.lower(),
                        decoded_client_component,
                        rounds,
                        keysize)
        return binascii.hexlify(secret)
Exemple #12
0
 def check_user_or_serial_in_request_wrapper(*args, **kwds):
     user = request.all_data.get("user")
     serial = request.all_data.get("serial")
     if not serial and not user:
         raise ParameterError(_("You need to specify a serial or a user."))
     f_result = func(*args, **kwds)
     return f_result
Exemple #13
0
def offlinerefill():
    """
    This endpoint allows to fetch new offline OTP values for a token,
    that is already offline.
    According to the definition it will send the missing OTP values, so that
    the client will have as much otp values as defined.

    :param serial: The serial number of the token, that should be refilled.
    :param refilltoken: The authorization token, that allows refilling.
    :param pass: the last password (maybe password+OTP) entered by the user
    :return:
    """
    serial = getParam(request.all_data, "serial", required)
    refilltoken = getParam(request.all_data, "refilltoken", required)
    password = getParam(request.all_data, "pass", required)
    tokenobj_list = get_tokens(serial=serial)
    if len(tokenobj_list) != 1:
        raise ParameterError("The token does not exist")
    else:
        tokenobj = tokenobj_list[0]
        tokenattachments = list_machine_tokens(serial=serial,
                                               application="offline")
        if tokenattachments:
            # TODO: Currently we do not distinguish, if a token had more than one offline attachment
            # We need the options to pass the count and the rounds for the next offline OTP values,
            # which could have changed in the meantime.
            options = tokenattachments[0].get("options")
            # check refill token:
            if tokenobj.get_tokeninfo("refilltoken") == refilltoken:
                # refill
                otps = MachineApplication.get_refill(tokenobj, password,
                                                     options)
                refilltoken = MachineApplication.generate_new_refilltoken(
                    tokenobj)
                response = send_result(True)
                content = response.json
                content["auth_items"] = {
                    "offline": [{
                        "refilltoken": refilltoken,
                        "response": otps
                    }]
                }
                response.set_data(json.dumps(content))
                return response
        raise ParameterError(
            "Token is not an offline token or refill token is incorrect")
Exemple #14
0
def offlinerefill():
    """
    This endpoint allows to fetch new offline OTP values for a token,
    that is already offline.
    According to the definition it will send the missing OTP values, so that
    the client will have as much otp values as defined.

    :param serial: The serial number of the token, that should be refilled.
    :param refilltoken: The authorization token, that allows refilling.
    :param pass: the last password (maybe password+OTP) entered by the user
    :return:
    """
    result = False
    otps = {}
    serial = getParam(request.all_data, "serial", required)
    refilltoken = getParam(request.all_data, "refilltoken", required)
    password = getParam(request.all_data, "pass", required)
    tokenobj_list = get_tokens(serial=serial)
    if len(tokenobj_list) != 1:
        raise ParameterError("The token does not exist")
    else:
        tokenobj = tokenobj_list[0]
        machine_defs = list_token_machines(serial)
        # check if is still an offline token:
        for mdef in machine_defs:
            if mdef.get("application") == "offline":
                # check refill token:
                if tokenobj.get_tokeninfo("refilltoken") == refilltoken:
                    # refill
                    otps = MachineApplication.get_refill(
                        tokenobj, password, mdef.get("options"))
                    refilltoken = MachineApplication.generate_new_refilltoken(
                        tokenobj)
                    response = send_result(True)
                    content = json.loads(response.data)
                    content["auth_items"] = {
                        "offline": [{
                            "refilltoken": refilltoken,
                            "response": otps
                        }]
                    }
                    response.data = json.dumps(content)
                    return response
        raise ParameterError(
            "Token is not an offline token or refill token is incorrect")
Exemple #15
0
def set_periodic_task_api():
    """
    Create or replace an existing periodic task definition.

    :param id: ID of an existing periodic task definition that should be updated
    :param name: Name of the periodic task
    :param active: true if the periodic task should be active
    :param retry_if_failed: privacyIDEA will retry to execute the task if failed
    :param interval: Interval at which the periodic task should run (in cron syntax)
    :param nodes: Comma-separated list of nodes on which the periodic task should run
    :param taskmodule: Task module name of the task
    :param ordering: Ordering of the task, must be a number >= 0.
    :param options: A dictionary (possibly JSON) of periodic task options, mapping unicodes to unicodes
    :return: ID of the periodic task
    """
    param = request.all_data
    ptask_id = getParam(param, "id", optional=True)
    if ptask_id is not None:
        ptask_id = int(ptask_id)
    name = getParam(param, "name", optional=False)
    active = is_true(getParam(param, "active", default=True))
    retry_if_failed = is_true(getParam(param, "retry_if_failed", default=True))
    interval = getParam(param, "interval", optional=False)
    node_string = getParam(param, "nodes", optional=False)
    if node_string.strip():
        node_list = [node.strip() for node in node_string.split(",")]
    else:
        raise ParameterError(u"nodes: expected at least one node")
    taskmodule = getParam(param, "taskmodule", optional=False)
    if taskmodule not in get_available_taskmodules():
        raise ParameterError("Unknown task module: {!r}".format(taskmodule))
    ordering = int(getParam(param, "ordering", optional=False))
    options = getParam(param, "options", optional=True)
    if options is None:
        options = {}
    elif not isinstance(options, dict):
        options = json.loads(options)
        if not isinstance(options, dict):
            raise ParameterError(u"options: expected dictionary, got {!r}".format(options))
    result = set_periodic_task(name, interval, node_list, taskmodule, ordering, options, active, ptask_id,
                               retry_if_failed)
    g.audit_object.log({"success": True, "info": result})
    return send_result(result)
def get_taskmodule(identifier):
    """
    Return an instance of the given task module. Raise ParameterError if it does not exist.
    :param identifier: identifier of the task module
    :return: instance of a BaseTask subclass
    """
    if identifier not in TASK_MODULES:
        raise ParameterError(u"Unknown task module: {!r}".format(identifier))
    else:
        return TASK_MODULES[identifier]()
def get_periodic_task_by_name(name):
    """
    Get a periodic task by name. Raise ParameterError if the task could not be found.
    :param name: task name, unicode
    :return: dictionary
    """
    periodic_tasks = get_periodic_tasks(name)
    if len(periodic_tasks) != 1:
        raise ParameterError("The periodic task with unique name {!r} does not exist".format(name))
    return periodic_tasks[0]
Exemple #18
0
def vasco_deserialize(tokendata):
    '''
    Convert the given bytestring to a ``TDigipassBlob`` object and return it

    :param tokendata: A string of 248 bytes
    :return: The Vasco data blob
    '''
    if len(tokendata) != 248:
        raise ParameterError("Data blob has incorrect size")
    return TDigipassBlob.from_buffer_copy(tokendata)
def _get_periodic_task_entry(ptask_id):
    """
    Get a periodic task entry by ID. Raise ParameterError if the task could not be found.
    This is only for internal use.
    :param id: task ID as integer
    :return: PeriodicTask object
    """
    periodic_task = PeriodicTask.query.filter_by(id=ptask_id).first()
    if periodic_task is None:
        raise ParameterError("The periodic task with id {!r} does not exist".format(ptask_id))
    return periodic_task
Exemple #20
0
def check_serial_valid(serial):
    """
    This function checks the given serial number for allowed values.
    Raises an exception if the format of the serial number is not allowed

    :param serial:
    :return: True or Exception
    """
    if not re.match(ALLOWED_SERIAL, serial):
        raise ParameterError(
            "Invalid serial number. Must comply to {0!s}.".format(
                ALLOWED_SERIAL))
    return True
Exemple #21
0
    def user_or_serial_wrapper(*args, **kwds):
        # If there is no user and serial keyword parameter and if
        # there is no normal argument, we do not have enough information
        serial = kwds.get("serial")
        user = kwds.get("user")
        # We have no serial! The serial would be the first arg
        if (serial is None and (len(args) == 0 or args[0] is None)
                and (user is None or (user is not None and user.is_empty()))):
            # We either have an empty User object or None
            raise ParameterError(ParameterError.USER_OR_SERIAL)

        f_result = func(*args, **kwds)
        return f_result
Exemple #22
0
    def update(self, param, reset_failcount=True):
        """
        This method is called during the initialization process.

        :param param: parameters from the token init
        :type param: dict
        :return: None
        """
        TokenClass.update(self, param)
        reg_data = getParam(param, "regdata")
        verify_cert = is_true(getParam(param, "u2f.verify_cert", default=True))
        if not reg_data:
            self.token.rollout_state = ROLLOUTSTATE.CLIENTWAIT
            # Set the description in the first enrollment step
            if "description" in param:
                self.set_description(getParam(param, "description",
                                              default=""))
        elif reg_data and self.token.rollout_state == ROLLOUTSTATE.CLIENTWAIT:
            attestation_cert, user_pub_key, key_handle, \
                signature, automatic_description = parse_registration_data(reg_data,
                                                                 verify_cert=verify_cert)
            client_data = getParam(param, "clientdata", required)
            client_data_str = url_decode(client_data)
            app_id = self.get_tokeninfo("appId", "")
            # Verify the registration data
            # In case of any crypto error, check_data raises an exception
            check_registration_data(attestation_cert, app_id, client_data_str,
                                    user_pub_key, key_handle, signature)
            self.set_otpkey(key_handle)
            self.add_tokeninfo("pubKey", user_pub_key)
            # add attestation certificate info
            issuer = x509name_to_string(attestation_cert.get_issuer())
            serial = "{!s}".format(attestation_cert.get_serial_number())
            subject = x509name_to_string(attestation_cert.get_subject())

            self.add_tokeninfo("attestation_issuer", issuer)
            self.add_tokeninfo("attestation_serial", serial)
            self.add_tokeninfo("attestation_subject", subject)
            # Reset rollout state
            self.token.rollout_state = ""
            # If no description has already been set, set the automatic description or the
            # description given in the 2nd request
            if not self.token.description:
                self.set_description(
                    getParam(param,
                             "description",
                             default=automatic_description))
        else:
            raise ParameterError(
                "regdata provided but token not in clientwait rollout_state.")
Exemple #23
0
    def get_authentication_item(token_type,
                                serial,
                                challenge=None,
                                options=None,
                                filter_param=None):
        """
        :param token_type: the type of the token. At the moment
                           we only support "HOTP" token. Supporting time
                           based tokens is difficult, since we would have to
                           return a looooong list of OTP values.
                           Supporting "yubikey" token (AES) would be
                           possible, too.
        :param serial:     the serial number of the token.
        :param challenge:  This can contain the password (otp pin + otp
        value) so that we can put the OTP PIN into the hashed response.
        :type challenge: basestring
        :return auth_item: A list of hashed OTP values
        """
        ret = {}
        options = options or {}
        password = challenge
        if token_type.lower() == "hotp":
            tokens = get_tokens(serial=serial)
            if len(tokens) == 1:
                token_obj = tokens[0]
                if password:
                    _r, otppin, _ = token_obj.split_pin_pass(password)
                    if not _r:
                        raise ParameterError("Could not split password")
                else:
                    otppin = ""
                otps = MachineApplication.get_offline_otps(
                    token_obj, otppin, int(options.get("count", 100)),
                    int(options.get("rounds", ROUNDS)))
                refilltoken = MachineApplication.generate_new_refilltoken(
                    token_obj)
                ret["response"] = otps
                ret["refilltoken"] = refilltoken
                user_object = token_obj.user
                if user_object:
                    uInfo = user_object.info
                    if "username" in uInfo:
                        ret["username"] = uInfo.get("username")

        else:
            log.info("Token %r, type %r is not supported by "
                     "OFFLINE application module" % (serial, token_type))

        return ret
Exemple #24
0
def enable_event(event_id, enable=True):
    """
    Enable or disable the and event
    :param event_id: ID of the event
    :return:
    """
    ev = EventHandler.query.filter_by(id=event_id).first()
    if not ev:
        raise ParameterError("The event with id '{0!s}' does not "
                             "exist".format(event_id))

    # Update the event
    ev.active = enable
    r = ev.save()
    return r
    def do(self, action, options=None):
        """
        This method executes the defined action in the given event.

        :param action:
        :param environment:
        :param options:
        :return:
        """
        ret = True
        g = options.get("g")
        request = options.get("request")
        logged_in_user = g.logged_in_user
        user = get_user_from_param(request.all_data)
        if action.lower() == "sendmail" and logged_in_user.get("role") == \
                ROLE.ADMIN and not user.is_empty() and user.login:
            emailconfig = options.get("emailconfig")
            if not emailconfig:
                log.error("Missing parameter 'emailconfig'")
                raise ParameterError("Missing parameter 'emailconfig'")
            useremail = user.info.get("email")
            subject = options.get("subject") or "An action was performed on " \
                                                "your token."
            body = options.get("body") or DEFAULT_BODY
            body = body.format(
                admin=logged_in_user.get("username"),
                realm=logged_in_user.get("realm"),
                action=request.path,
                serial=g.audit_object.audit_data.get("serial"),
                url=request.url_root,
                user=user.info.get("givenname")
                )
            try:
                ret = send_email_identifier(emailconfig,
                                            recipient=useremail,
                                            subject=subject, body=body)
            except Exception as exx:
                log.error("Failed to send email: {0!s}".format(exx))
                ret = False
            if ret:
                log.info("Sent a notification email to user {0}".format(user))
            else:
                log.warning("Failed to send a notification email to user "
                            "{0}".format(user))

        return ret
Exemple #26
0
    def update(self, param):
        """
        This method is called during the initialization process.
        :param param: parameters from the token init
        :type param: dict
        :return: None
        """
        TokenClass.update(self, param)

        realms = getParam(param, "4eyes", required)
        separator = getParam(param, "separator", optional, default=" ")
        if len(separator) > 1:
            raise ParameterError("The separator must only be one single "
                                 "character")
        realms = self.realms_dict_to_string(realms)
        self.convert_realms(realms)
        self.add_tokeninfo("separator", separator)
        self.add_tokeninfo("4eyes", realms)
Exemple #27
0
    def update(self, param):
        """
        This method is called during the initialization process.

        :param param: parameters from the token init
        :type param: dict
        :return: None
        """
        # We should only initialize such a token, when the user is
        # immediately given in the init process, since the token on the
        # smartphone needs to contain a userId.
        if not self.user:
            # The user and realms should have already been set in init_token()
            raise ParameterError("Missing parameter: {0!r}".format("user"), id=905)

        ocrasuite = get_from_config("tiqr.ocrasuite") or OCRA_DEFAULT_SUITE
        OCRASuite(ocrasuite)
        self.add_tokeninfo("ocrasuite", ocrasuite)
        TokenClass.update(self, param)
Exemple #28
0
    def _api_endpoint_post(cls, request_data):
        """ Handle all POST requests to the api endpoint

        :param request_data: Dictionary containing the parameters of the request
        :type request_data: dict
        :returns: The result of handling the request and a dictionary containing
                  the details of the request handling
        :rtype: (bool, dict)
        """
        details = {}
        result = False

        serial = getParam(request_data, "serial", optional=False)
        if all(k in request_data for k in ("fbtoken", "pubkey")):
            log.debug("Do the 2nd step of the enrollment.")
            try:
                token_obj = get_one_token(
                    serial=serial,
                    tokentype="push",
                    rollout_state=ROLLOUTSTATE.CLIENTWAIT)
                token_obj.update(request_data)
            except ResourceNotFoundError:
                raise ResourceNotFoundError(
                    "No token with this serial number "
                    "in the rollout state 'clientwait'.")
            init_detail_dict = request_data

            details = token_obj.get_init_detail(init_detail_dict)
            result = True
        elif all(k in request_data for k in ("nonce", "signature")):
            log.debug(
                "Handling the authentication response from the smartphone.")
            challenge = getParam(request_data, "nonce")
            signature = getParam(request_data, "signature")

            # get the token_obj for the given serial:
            token_obj = get_one_token(serial=serial, tokentype="push")
            pubkey_obj = _build_verify_object(
                token_obj.get_tokeninfo(PUBLIC_KEY_SMARTPHONE))
            # Do the 2nd step of the authentication
            # Find valid challenges
            challengeobject_list = get_challenges(serial=serial,
                                                  challenge=challenge)

            if challengeobject_list:
                # There are valid challenges, so we check this signature
                for chal in challengeobject_list:
                    # verify the signature of the nonce
                    sign_data = u"{0!s}|{1!s}".format(challenge, serial)
                    try:
                        pubkey_obj.verify(b32decode(signature),
                                          sign_data.encode("utf8"),
                                          padding.PKCS1v15(), hashes.SHA256())
                        # The signature was valid
                        log.debug(
                            "Found matching challenge {0!s}.".format(chal))
                        chal.set_otp_status(True)
                        chal.save()
                        result = True
                    except InvalidSignature as _e:
                        pass
        elif all(k in request_data
                 for k in ('new_fb_token', 'timestamp', 'signature')):
            timestamp = getParam(request_data, 'timestamp', optional=False)
            signature = getParam(request_data, 'signature', optional=False)
            # first check if the timestamp is in the required span
            cls._check_timestamp_in_range(timestamp, UPDATE_FB_TOKEN_WINDOW)
            try:
                tok = get_one_token(serial=serial,
                                    tokentype=cls.get_class_type())
                pubkey_obj = _build_verify_object(
                    tok.get_tokeninfo(PUBLIC_KEY_SMARTPHONE))
                sign_data = u"{new_fb_token}|{serial}|{timestamp}".format(
                    **request_data)
                pubkey_obj.verify(b32decode(signature),
                                  sign_data.encode("utf8"), padding.PKCS1v15(),
                                  hashes.SHA256())
                # If the timestamp and signature are valid we update the token
                tok.add_tokeninfo('firebase_token',
                                  request_data['new_fb_token'])
                result = True
            except (ResourceNotFoundError, ParameterError, TypeError,
                    InvalidSignature, ConfigAdminError, BinasciiError) as e:
                # to avoid disclosing information we always fail with an invalid
                # signature error even if the token with the serial could not be found
                log.debug('{0!s}'.format(traceback.format_exc()))
                log.info('The following error occurred during the signature '
                         'check: "{0!r}"'.format(e))
                raise privacyIDEAError('Could not verify signature!')
        else:
            raise ParameterError("Missing parameters!")

        return result, details
    def api_endpoint(cls, request, g):
        """
        This provides a function which is called by the API endpoint
        ``/ttype/push`` which is defined in :doc:`../../api/ttype`

        The method returns a tuple ``("json", {})``

        This endpoint provides several functionalities:

        - It is used for the 2nd enrollment step of the smartphone.
          It accepts the following parameters:

            .. sourcecode:: http

              POST /ttype/push HTTP/1.1
              Host: https://yourprivacyideaserver

              serial=<token serial>
              fbtoken=<firebase token>
              pubkey=<public key>

        - It is also used when the smartphone sends the signed response
          to the challenge during authentication. The following parameters ar accepted:

            .. sourcecode:: http

              POST /ttype/push HTTP/1.1
              Host: https://yourprivacyideaserver

              serial=<token serial>
              nonce=<the actual challenge>
              signature=<the signed nonce>

        - And it also acts as an endpoint for polling challenges:

            .. sourcecode:: http

              GET /ttype/push HTTP/1.1
              Host: https://yourprivacyideaserver

              serial=<tokenserial>
              timestamp=<timestamp>
              signature=SIGNATURE(<tokenserial>|<timestamp>)

          More on polling can be found here: https://github.com/privacyidea/privacyidea/wiki/concept%3A-pushtoken-poll

        :param request: The Flask request
        :param g: The Flask global object g
        :return: The json string representing the result dictionary
        :rtype: tuple("json", str)
        """
        details = {}
        result = False

        if request.method == 'POST':
            serial = getParam(request.all_data, "serial", optional=False)
            if serial and "fbtoken" in request.all_data and "pubkey" in request.all_data:
                log.debug("Do the 2nd step of the enrollment.")
                try:
                    token_obj = get_one_token(serial=serial,
                                              tokentype="push",
                                              rollout_state="clientwait")
                    token_obj.update(request.all_data)
                except ResourceNotFoundError:
                    raise ResourceNotFoundError(
                        "No token with this serial number "
                        "in the rollout state 'clientwait'.")
                init_detail_dict = request.all_data

                details = token_obj.get_init_detail(init_detail_dict)
                result = True
            elif serial and "nonce" in request.all_data and "signature" in request.all_data:
                log.debug(
                    "Handling the authentication response from the smartphone."
                )
                challenge = getParam(request.all_data, "nonce")
                serial = getParam(request.all_data, "serial")
                signature = getParam(request.all_data, "signature")

                # get the token_obj for the given serial:
                token_obj = get_one_token(serial=serial, tokentype="push")
                pubkey_obj = _build_verify_object(
                    token_obj.get_tokeninfo(PUBLIC_KEY_SMARTPHONE))
                # Do the 2nd step of the authentication
                # Find valid challenges
                challengeobject_list = get_challenges(serial=serial,
                                                      challenge=challenge)

                if challengeobject_list:
                    # There are valid challenges, so we check this signature
                    for chal in challengeobject_list:
                        # verify the signature of the nonce
                        sign_data = u"{0!s}|{1!s}".format(challenge, serial)
                        try:
                            pubkey_obj.verify(b32decode(signature),
                                              sign_data.encode("utf8"),
                                              padding.PKCS1v15(),
                                              hashes.SHA256())
                            # The signature was valid
                            log.debug(
                                "Found matching challenge {0!s}.".format(chal))
                            chal.set_otp_status(True)
                            chal.save()
                            result = True
                        except InvalidSignature as _e:
                            pass

            else:
                raise ParameterError("Missing parameters!")
        elif request.method == 'GET':
            # This is only used for polling
            # By default we allow polling if the policy is not set.
            allow_polling = get_action_values_from_options(
                SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING,
                options={'g': g}) or PushAllowPolling.ALLOW
            if allow_polling == PushAllowPolling.DENY:
                raise PolicyError('Polling not allowed!')
            serial = getParam(request.all_data, "serial", optional=False)
            timestamp = getParam(request.all_data, 'timestamp', optional=False)
            signature = getParam(request.all_data, 'signature', optional=False)
            # first check if the timestamp is in the required span
            try:
                ts = isoparse(timestamp)
            except (ValueError, TypeError) as _e:
                log.debug('{0!s}'.format(traceback.format_exc()))
                raise privacyIDEAError(
                    'Could not parse timestamp {0!s}. '
                    'ISO-Format required.'.format(timestamp))
            # TODO: make time delta configurable
            td = timedelta(minutes=POLL_TIME_WINDOW)
            # We don't know if the passed timestamp is timezone aware. If no
            # timezone is passed, we assume UTC
            if ts.tzinfo:
                now = datetime.now(utc)
            else:
                now = datetime.utcnow()
            if not (now - td <= ts <= now + td):
                raise privacyIDEAError(
                    'Timestamp {0!s} not in valid range.'.format(timestamp))
            # now check the signature
            # first get the token
            try:
                tok = get_one_token(serial=serial,
                                    tokentype=cls.get_class_type())
                # If the push_allow_polling policy is set to "token" we also
                # need to check the POLLING_ALLOWED tokeninfo. If it evaluated
                # to 'False', polling is not allowed for this token. If the
                # tokeninfo value evaluates to 'True' or is not set at all,
                # polling is allowed for this token.
                if allow_polling == PushAllowPolling.TOKEN:
                    if not is_true(
                            tok.get_tokeninfo(POLLING_ALLOWED,
                                              default='True')):
                        log.debug(
                            'Polling not allowed for pushtoken {0!s} due to '
                            'tokeninfo.'.format(serial))
                        raise PolicyError('Polling not allowed!')

                pubkey_obj = _build_verify_object(
                    tok.get_tokeninfo(PUBLIC_KEY_SMARTPHONE))
                sign_data = u"{serial}|{timestamp}".format(**request.all_data)
                pubkey_obj.verify(b32decode(signature),
                                  sign_data.encode("utf8"), padding.PKCS1v15(),
                                  hashes.SHA256())
                # The signature was valid now check for an open challenge
                # we need the private server key to sign the smartphone data
                pem_privkey = tok.get_tokeninfo(PRIVATE_KEY_SERVER)
                # we also need the FirebaseGateway for this token
                fb_identifier = tok.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
                if not fb_identifier:
                    raise ResourceNotFoundError(
                        'The pushtoken {0!s} has no Firebase configuration '
                        'assigned.'.format(serial))
                fb_gateway = create_sms_instance(fb_identifier)
                options = {'g': g}
                challenges = []
                challengeobject_list = get_challenges(serial=serial)
                for chal in challengeobject_list:
                    # check if the challenge is active and not already answered
                    _cnt, answered = chal.get_otp_status()
                    if not answered and chal.is_valid():
                        # then return the necessary smartphone data to answer
                        # the challenge
                        sp_data = _build_smartphone_data(
                            serial, chal.challenge, fb_gateway, pem_privkey,
                            options)
                        challenges.append(sp_data)
                # return the challenges as a list in the result value
                result = challenges
            except (ResourceNotFoundError, ParameterError, InvalidSignature,
                    ConfigAdminError, BinasciiError) as e:
                # to avoid disclosing information we always fail with an invalid
                # signature error even if the token with the serial could not be found
                log.debug('{0!s}'.format(traceback.format_exc()))
                log.info('The following error occurred during the signature '
                         'check: "{0!r}"'.format(e))
                raise privacyIDEAError('Could not verify signature!')

        else:
            raise privacyIDEAError(
                'Method {0!s} not allowed in \'api_endpoint\' '
                'for push token.'.format(request.method))

        return "json", prepare_result(result, details=details)
Exemple #30
0
    def api_endpoint(cls, request, g):
        """
        This provides a function which is called by the API endpoint
        /ttype/push which is defined in api/ttype.py

        The method returns
            return "json", {}

        This endpoint is used for the 2nd enrollment step of the smartphone.
        Parameters sent:
            * serial
            * fbtoken
            * pubkey

        This endpoint is also used, if the smartphone sends the signed response
        to the challenge during authentication
        Parameters sent:
            * serial
            * nonce (which is the challenge)
            * signature (which is the signed nonce)


        :param request: The Flask request
        :param g: The Flask global object g
        :return: dictionary
        """
        details = {}
        result = False
        serial = getParam(request.all_data, "serial", optional=False)

        if serial and "fbtoken" in request.all_data and "pubkey" in request.all_data:
            # Do the 2nd step of the enrollment
            try:
                token_obj = get_one_token(serial=serial,
                                          tokentype="push",
                                          rollout_state="clientwait")
                token_obj.update(request.all_data)
            except ResourceNotFoundError:
                raise ResourceNotFoundError(
                    "No token with this serial number in the rollout state 'clientwait'."
                )
            init_detail_dict = request.all_data

            details = token_obj.get_init_detail(init_detail_dict)
            result = True
        elif serial and "nonce" in request.all_data and "signature" in request.all_data:
            challenge = getParam(request.all_data, "nonce")
            serial = getParam(request.all_data, "serial")
            signature = getParam(request.all_data, "signature")

            # get the token_obj for the given serial:
            token_obj = get_one_token(serial=serial, tokentype="push")
            pubkey_pem = token_obj.get_tokeninfo(PUBLIC_KEY_SMARTPHONE)
            # The public key of the smartphone was probably sent as urlsafe:
            pubkey_pem = pubkey_pem.replace("-", "+").replace("_", "/")
            # The public key was sent without any header
            pubkey_pem = "-----BEGIN PUBLIC KEY-----\n{0!s}\n-----END PUBLIC KEY-----".format(
                pubkey_pem.strip().replace(" ", "+"))
            # Do the 2nd step of the authentication
            # Find valid challenges
            challengeobject_list = get_challenges(serial=serial,
                                                  challenge=challenge)

            if challengeobject_list:
                # There are valid challenges, so we check this signature
                for chal in challengeobject_list:
                    # verify the signature of the nonce
                    pubkey_obj = serialization.load_pem_public_key(
                        to_bytes(pubkey_pem), default_backend())
                    sign_data = u"{0!s}|{1!s}".format(challenge, serial)
                    try:
                        pubkey_obj.verify(b32decode(signature),
                                          sign_data.encode("utf8"),
                                          padding.PKCS1v15(), hashes.SHA256())
                        # The signature was valid
                        chal.set_otp_status(True)
                        result = True
                    except InvalidSignature as e:
                        pass

        else:
            raise ParameterError("Missing parameters!")

        return "json", prepare_result(result, details=details)