コード例 #1
0
    def get_response(self, request: _ProxyRequest):
        """Constructs a response packet to a given Radius request

        If the request is an 'initial' request (i.e. has no 'State'
        attribute, so it did not arrive in response to an
        AccessChallenge), then we simply call get_initial_response()

        If the request is a response to an AccessChallenge, then
        lookup the challenge state, perform some consistency checks
        (username, source ip), then we call self.get_challenge_response()

        Returns:
            packet.Packet
        """
        try:
            challenge_state = self._find_challenge(request)
            if challenge_state:
                # sanity check on src ip
                expected_source = challenge_state.source
                if expected_source[0] != request.source[0]:
                    raise BadStateError("Response to AccessChallenge from "
                                        "wrong source ip: %r" %
                                        request.source[0])

                # sanity check on username
                if challenge_state.initial_request.username != request.username:
                    raise BadStateError("Response to AccessChallenge with "
                                        "incorrect username: %r" %
                                        request.username)

                # everything checks out OK so far
                self.log_request(
                    request,
                    "Valid response to challenge issued at id %r" %
                    challenge_state.initial_request.id,
                )

                # clear challenge state (it has been consumed)
                self._cleanup_challenge(challenge_state)

                # build response
                ret = yield self.get_challenge_response(
                    request, challenge_state.state)
            else:
                ret = yield self.get_initial_response(request)
        except BadStateError as e:
            msg = str(e)
            self.log_request(request, msg)
            log.auth_standard(
                msg=msg,
                username=request.username,
                auth_stage="Unknown",
                status=log.AUTH_REJECT,
                server_section=self.server_section_name,
                client_ip=request.client_ip,
                server_section_ikey=self.server_section_ikey,
            )
            ret = yield self.create_reject_packet(request, "Unknown Challenge")

        defer.returnValue(ret)
コード例 #2
0
    def duo_auth(self, request, primary_res, factor=None, preauth_res=None):
        if not preauth_res:
            # Must call preauth even if the factor is known.
            response_packet, preauth_res = yield self.preauth(
                request, primary_res)
            if response_packet is not None:
                # E.g. enroll policy of deny or allow.
                defer.returnValue(response_packet)
        if factor is None:
            factor = util.factor_for_request(self.factors, preauth_res)
            if factor is None:
                msg = 'User has no Duo factors usable with this configuration'
                log.auth_standard(msg=msg,
                                  username=request.username,
                                  auth_stage=log.AUTH_SECONDARY,
                                  status=log.AUTH_ERROR,
                                  client_ip=request.client_ip,
                                  server_section=self.server_section_name,
                                  server_section_ikey=self.server_section_ikey)
                self.log_request(request, msg)
                defer.returnValue(self.create_reject_packet(request, msg))

        # Factor was either passed in or found above auto-push
        response_packet = yield super(DuoEAPRadiusServer, self).duo_auth(
            request,
            primary_res,
            factor,
        )
        defer.returnValue(response_packet)
コード例 #3
0
ファイル: eap.py プロジェクト: slayer/duoauthproxy-freebsd
    def add_incoming(self, msg: bytes):
        # Add a message to the EAP-TLS connection
        self.ssl_con.bio_write(msg)
        if not self.handshake:
            try:
                self.ssl_con.set_accept_state()
                self.ssl_con.do_handshake()
            except SSL.WantReadError:
                self.handshake = True
        try:
            # If there is a decrypted message ready, push it to inner eap
            decrypted_msg = self.ssl_con.recv(9999)  # bytes

            # Inner eap lacks code, id, len
            # add them on so the packet is parsed the same
            header = (chr(2).encode() + msg[1:2] +
                      struct.pack(">H",
                                  len(decrypted_msg) + 4))  # bytes

            if (decrypted_msg[0] == 2):
                # For some reason the MS-Auth-TLV has a header already
                header = b""
            elif decrypted_msg[0] == 6:
                # GTC response
                gtc = yield self.gtc_received(self, decrypted_msg[1:],
                                              self.gtc_message)
                defer.returnValue(gtc)

            add_response = yield self.innerEAP.add_message(header +
                                                           decrypted_msg)
            if add_response:
                error = yield self.errback(self, add_response.message)
                defer.returnValue(error)

        except SSL.WantReadError:
            # No decrypted message ready yet
            pass
        except SSL.Error as e:
            # Possibly retransmission or some other unknown error
            ssl_error = "SSL Error: {0}".format(e)
            log.auth_standard(
                msg=ssl_error,
                username=self.current_request.username,
                auth_stage=log.AUTH_UNKNOWN,
                status=log.AUTH_ERROR,
                client_ip=self.current_request.client_ip,
                server_section=self.server.server_section_name,
                server_section_ikey=self.server.server_section_ikey,
            )
            error = yield self.errback(self, ssl_error)
            defer.returnValue(error)

        defer.returnValue(False)
コード例 #4
0
    def get_challenge_response(self, request, state):
        # Do not have access to factor - request.password is users cookie
        success = False
        self.log_request(request, 'Challenge Response: %r' % request.password)

        if state and 'primary_res' in state:
            radius_attrs = state['primary_res'].radius_attrs
        else:
            radius_attrs = {}

        auth_cookie = urllib.parse.unquote(self._get_authcookie(request))
        try:
            finish_res = yield self.client.proxy_finish(auth_cookie)
        except duo_async.DuoAPIError as e:
            log.err(None, 'Duo proxy_finish call failed')
            response_packet = self.response_for_api_error(
                request,
                state['primary_res'],
                e,
                state['primary_res'].radius_attrs,
            )
            defer.returnValue(response_packet)

        self.log_request(request,
                         'Authcookie validation result: %r' % finish_res)
        if (finish_res['valid_cookie']
                and (finish_res['user'] == request.username)):
            success = True

        if success:
            log.auth_standard(msg='Valid login from iframe',
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_ALLOW,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey,
                              client_ip=request.client_ip)
            defer.returnValue(
                self.create_accept_packet(
                    request,
                    radius_attrs=radius_attrs,
                ))
        else:
            log.auth_standard(msg='Invalid login from iframe',
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey,
                              client_ip=request.client_ip)
            defer.returnValue(self.create_reject_packet(request))
コード例 #5
0
    def response_for_api_error(self,
                               request,
                               primary_res,
                               e,
                               radius_reject_attrs=None):
        """Build and return a response packet for the given request, primary response,
        and api error.  Assumes the primary response is success.  Include the
        passed-in radius attributes when building a rejection packet.

        Returns a response ready to send to the client.
        """
        if duo_async.should_server_fail_open(self.failmode, e.fail_open):
            msg = duo_async.get_fail_open_msg()
            self.log_request(request, msg)

            log.auth_standard(
                msg=msg,
                username=request.username,
                auth_stage=log.AUTH_SECONDARY,
                status=log.AUTH_ALLOW,
                client_ip=request.client_ip,
                server_section=self.server_section_name,
                server_section_ikey=self.server_section_ikey,
            )

            return self.create_accept_packet(
                request,
                msg,
                primary_res.radius_attrs,
            )

        msg = duo_async.FAILMODE_SECURE_MSG
        self.log_request(request, msg)

        log.auth_standard(
            msg=msg,
            username=request.username,
            auth_stage=log.AUTH_SECONDARY,
            status=log.AUTH_REJECT,
            client_ip=request.client_ip,
            server_section=self.server_section_name,
            server_section_ikey=self.server_section_ikey,
        )

        if radius_reject_attrs is None:
            radius_reject_attrs = {}

        return self.create_reject_packet(request,
                                         msg,
                                         radius_attrs=radius_reject_attrs)
コード例 #6
0
    def get_challenge_response(self, request, state, session=None):
        """Overriden from abstract method in ChallengeResponseRadiusServer."""
        if 'EAP-Message' in request.packet:
            request_message = b''.join(request.packet[79])
            if session is None:
                session = state
            session.current_request = request

            add_response = yield session.add_message(request_message)
            if add_response:
                defer.returnValue(add_response)

            response_message = yield session.next_message()

            if isinstance(response_message, bytes):
                # Next EAP message
                response_packet = self.create_challenge(request, '', session)
                response_packet[79] = self.fragment(response_message)
            else:
                # response is a packet from kill_session or accept_session
                response_packet = response_message

            # EAP-Message (79) RADIUS packets MUST include a Message-Authenticator.
            response_packet.add_message_authenticator()
            defer.returnValue(response_packet)

        else:
            # If no EAP, treat it like a normal radius request
            try:
                password = request.password
            except Exception:
                password = None
            if not password:
                # Either PAP but blank or un-decryptable. (Not PAP?
                # Wrong shared secret?). Not EAP, either, or
                # EAP-Message would've been found.
                msg = 'Missing or improperly-formatted password'
                self.log_request(request, msg)
                log.auth_standard(msg=msg,
                                  username=request.username,
                                  auth_stage=log.AUTH_PRIMARY,
                                  status=log.AUTH_ERROR,
                                  client_ip=request.client_ip,
                                  server_section=self.server_section_name,
                                  server_section_ikey=self.server_section_ikey)
                defer.returnValue(self.create_reject_packet(request, msg))
            else:
                result = yield self.auto_auth(request, password)
                defer.returnValue(result)
コード例 #7
0
    def duo_auth_only(self, request, factor):
        """
        duo_auth and return its result.  On failure, generate an appropriate
        response based on the configured failmode.
        """
        try:
            client_ip = request.client_ip
            if not ip_util.is_valid_ip(client_ip):
                client_ip = None
            auth_res = yield self.client.auth(request.username, factor,
                                              client_ip)
        except duo_async.DuoAPIError as e:
            log.err(None, 'Duo auth call failed')
            if duo_async.should_server_fail_open(self.failmode, e.fail_open):
                msg = duo_async.get_fail_open_msg()
                self.log_request(request, msg)
                log.auth_standard(msg=msg,
                                  username=request.username,
                                  auth_stage=log.AUTH_SECONDARY,
                                  status=log.AUTH_ALLOW,
                                  client_ip=request.client_ip,
                                  server_section=self.server_section_name,
                                  server_section_ikey=self.server_section_ikey)
                ret = {'result': duo_async.API_RESULT_ALLOW, 'status': msg}
                defer.returnValue(ret)
            else:
                msg = duo_async.FAILMODE_SECURE_MSG
                self.log_request(request, msg)
                log.auth_standard(msg=msg,
                                  username=request.username,
                                  auth_stage=log.AUTH_SECONDARY,
                                  status=log.AUTH_ERROR,
                                  client_ip=request.client_ip,
                                  server_section=self.server_section_name,
                                  server_section_ikey=self.server_section_ikey)
                ret = {'result': duo_async.API_RESULT_DENY, 'status': msg}
                defer.returnValue(ret)

        if auth_res['result'] == duo_async.API_RESULT_ALLOW:
            log.auth_standard(msg=auth_res['status'],
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_ALLOW,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
        else:
            log.auth_standard(msg=auth_res['status'],
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)

        defer.returnValue(auth_res)
コード例 #8
0
ファイル: server.py プロジェクト: slayer/duoauthproxy-freebsd
    def primary_auth(self, request: _ProxyRequest, password: str = None):
        """
        Perform primary authentication.

        request -- Radius Access Request message
        password -- Password included in the radius request, if any

        Returns:
            AuthResult: the RADIUS response to the primary authentication request.
        """
        if self.pass_through_all:
            radius_attrs = dict(request.packet)
        else:
            radius_attrs = dict((attr, request[attr])
                                for attr in self._pass_through_attr_names +
                                base.MS_CHAP2_REQUEST_ATTRS if attr in request)
        try:
            primary_res = yield self.primary_ator.authenticate(
                request.username, password, request.client_ip, radius_attrs)
        except AuthError as err:
            msg = "Error performing primary authentication: %s" % err
            self.log_request(request, msg)
            log.auth_standard(
                msg=msg,
                username=request.username,
                auth_stage=log.AUTH_PRIMARY,
                status=log.AUTH_ERROR,
                client_ip=request.client_ip,
                server_section=self.server_section_name,
                server_section_ikey=self.server_section_ikey,
            )
            primary_res = AuthResult(False, msg)
        else:
            if not primary_res.success:
                primary_res_msg = "Primary credentials rejected - {0}".format(
                    primary_res.msg)
                self.log_request(request, primary_res_msg)
                log.auth_standard(
                    msg=primary_res_msg,
                    username=request.username,
                    auth_stage=log.AUTH_PRIMARY,
                    status=log.AUTH_REJECT,
                    client_ip=request.client_ip,
                    server_section=self.server_section_name,
                    server_section_ikey=self.server_section_ikey,
                )
            else:
                log.auth_standard(
                    msg="Primary authentication successful - {0}".format(
                        primary_res.msg),
                    username=request.username,
                    auth_stage=log.AUTH_PRIMARY,
                    status=log.AUTH_ALLOW,
                    client_ip=request.client_ip,
                    server_section=self.server_section_name,
                    server_section_ikey=self.server_section_ikey,
                )

        defer.returnValue(primary_res)
コード例 #9
0
    def duo_preauth(self, request):
        """
        Get the user's duo auth status, and return it.  On API error,
        this builds an appropriate failure return.

        request -- The user's RADIUS request

        Returns the preauth JSON result.
        """
        try:
            if request.username in self.exempt_usernames:
                preauth_message = 'User exempted from 2FA'
                preauth_res = {
                    'result': duo_async.API_RESULT_ALLOW,
                    'status': preauth_message
                }
            else:
                preauth_res = yield self.client.preauth(
                    request.username, request.client_ip, self.failmode)
        except duo_async.DuoAPIError as e:
            log.err(None, 'Duo preauth call failed')
            if duo_async.should_server_fail_open(self.failmode, e.fail_open):
                preauth_message = duo_async.get_fail_open_msg()
                preauth_res = {
                    'result': duo_async.API_RESULT_ALLOW,
                    'status': 'API call failed'
                }
            else:
                preauth_message = duo_async.FAILMODE_SECURE_MSG
                preauth_res = {
                    'result': duo_async.API_RESULT_DENY,
                    'status': 'API call failed'
                }
        else:
            if 'status' in preauth_res:
                preauth_message = 'Duo preauth returned \'%s\': \'%s\'' % (
                    preauth_res['result'], preauth_res['status'])
                self.log_request(request, preauth_message)
            else:
                preauth_message = 'Duo preauth returned \'%s\'' % preauth_res[
                    'result']
                self.log_request(request, preauth_message)

        if preauth_res['result'] == duo_async.API_RESULT_ALLOW:
            log.auth_standard(msg=preauth_message,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_ALLOW,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
        elif preauth_res['result'] == duo_async.API_RESULT_DENY:
            log.auth_standard(msg=preauth_message,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
        elif preauth_res['result'] == duo_async.API_RESULT_ENROLL:
            log.auth_standard(msg=log.AUTH_ENROLL_MSG,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)

        defer.returnValue(preauth_res)
コード例 #10
0
    def passcode_received(self, session, passcode: str, prompt):
        request = session.current_request
        if self.allow_concat:
            # In concat mode, attempt auth on first password
            result = yield self.auto_auth(request, passcode)
            if result.code == packet.AccessAccept:
                session.state = eap.EAP_SESSION_PEAP_ACCEPT
            else:
                session.state = eap.EAP_SESSION_DENY
        else:
            # If we didn't get any input, just resend the last prompt
            if passcode == '':
                return

            if not session.password:
                # otherwise, if this is the first credential we got
                # from the user, then do primary auth
                session.password = passcode
                session.primary_res = yield self.primary_auth(
                    request, session.password)

                # if primary auth fails, send back a reject
                if not session.primary_res.success:
                    session.state = eap.EAP_SESSION_DENY
                    return

                # otherwise, do preauth and proceed according to the response
                resp, preauth_res = yield self.preauth(request,
                                                       session.primary_res)
                session.state = eap.EAP_SESSION_PEAP_GTC

                if preauth_res is None:
                    if resp.code == packet.AccessAccept:
                        session.state = eap.EAP_SESSION_PEAP_ACCEPT
                    else:
                        session.state = eap.EAP_SESSION_DENY
                else:
                    if preauth_res['result'] == duo_async.API_RESULT_ENROLL:
                        session.enrolling = True
                        gtc_msg = (preauth_res['status'] +
                                   ' then try again.').encode()
                        session.innerEAP.gtc_message = gtc_msg
                        session.state = eap.EAP_SESSION_PEAP_GTC
                    elif preauth_res['result'] == duo_async.API_RESULT_AUTH:
                        session.innerEAP.gtc_message = _format_short(
                            preauth_res)
                        session.gtc_message = session.innerEAP.gtc_message
                    elif preauth_res['result'] == duo_async.API_RESULT_DENY:
                        session.state = eap.EAP_SESSION_DENY
                    elif preauth_res['result'] == duo_async.API_RESULT_ALLOW:
                        session.state = eap.EAP_SESSION_PEAP_ACCEPT

            elif session.enrolling:
                # Always deny an auth attempt after displaying enrollment link
                session.state = eap.EAP_SESSION_DENY
            else:
                result = yield self.auto_auth(request,
                                              session.password,
                                              factor=passcode,
                                              primary_res=session.primary_res)
                if result.code == packet.AccessAccept:
                    session.state = eap.EAP_SESSION_PEAP_ACCEPT
                elif passcode.rstrip('0123456789') == 'sms':
                    # Let them enter an SMSed passcode
                    if 'Reply-Message' in result:
                        session.innerEAP.gtc_message = (
                            result['Reply-Message'][0] + '\n' +
                            session.innerEAP.gtc_message).encode()
                    else:
                        # Reject result and no reply message means primary auth failed, no sms sent
                        log.auth_standard(
                            msg="Duo authentication failed",
                            username=request.username,
                            auth_stage=log.AUTH_SECONDARY,
                            status=log.AUTH_REJECT,
                            client_ip=request.client_ip,
                            server_section=self.server_section_name,
                            server_section_ikey=self.server_section_ikey)
                        session.state = eap.EAP_SESSION_DENY
                else:
                    log.auth_standard(
                        msg="Unrecognized passcode",
                        username=request.username,
                        auth_stage=log.AUTH_SECONDARY,
                        status=log.AUTH_REJECT,
                        client_ip=request.client_ip,
                        server_section=self.server_section_name,
                        server_section_ikey=self.server_section_ikey)
                    session.state = eap.EAP_SESSION_DENY
コード例 #11
0
ファイル: eap.py プロジェクト: slayer/duoauthproxy-freebsd
    def add_message(self, msg: bytes):
        """Add a message to the EAP session.
        Returns False on success, returns errback on error, or gtc_received
        on receiving a token card response.
        """
        code, identifier, length, eap_type = struct.unpack('>BBHB', msg[:5])
        if not self.inner and self.id != 0 and identifier != self.id:
            # Silently discard response if ID does not match
            log.msg("Response ID %s does not match request ID %s" %
                    (identifier, self.id))
            defer.returnValue(False)
        if len(msg) != length:
            message = 'Malformed EAP packet: length ({0}) does not match actual length ({1})'.format(
                length, len(msg))
            log.auth_standard(
                msg=message,
                username=self.current_request.username,
                auth_stage=log.AUTH_UNKNOWN,
                status=log.AUTH_ERROR,
                client_ip=self.current_request.client_ip,
                server_section=self.server.server_section_name,
                server_section_ikey=self.server.server_section_ikey)
            error = yield self.errback(self, message)
            defer.returnValue(error)

        if code == EAP_CODE_REQUEST:
            # EAP request, we are talking to RADIUS, this should not happen
            message = "EAP request received."
            log.auth_standard(
                msg=message,
                username=self.current_request.username,
                auth_stage=log.AUTH_UNKNOWN,
                status=log.AUTH_ERROR,
                client_ip=self.current_request.client_ip,
                server_section=self.server.server_section_name,
                server_section_ikey=self.server.server_section_ikey)
            error = yield self.errback(self, message)
            defer.returnValue(error)
        elif code == EAP_CODE_RESPONSE:
            # EAP response, we are talking to NetMotion
            self.id += 1
            if eap_type == EAP_TYPE_IDENTITY:  # Identity
                self.state = EAP_SESSION_REQUEST_GTC
            elif eap_type == EAP_TYPE_NAK:  # Legacy Nak
                # Data element (element 5) will indicate the desired type
                desired_type = msg[5]
                if desired_type == EAP_TYPE_PEAP:  # PEAP request
                    self.state = EAP_SESSION_PEAP_REQUEST
                else:
                    message = "Unknown Nak request %s." % desired_type
                    log.auth_standard(
                        msg=message,
                        username=self.current_request.username,
                        auth_stage=log.AUTH_UNKNOWN,
                        status=log.AUTH_ERROR,
                        client_ip=self.current_request.client_ip,
                        server_section=self.server.server_section_name,
                        server_section_ikey=self.server.server_section_ikey)
                    error = yield self.errback(self, message)
                    defer.returnValue(error)
            elif eap_type == EAP_TYPE_TLV:
                if msg[5:7] == MS_RESULT_HEADER and len(
                        msg) == 11:  # MS-Result-TLV
                    if msg[-1] == 1 and self.state == EAP_SESSION_ACCEPT:  # Success
                        self.state = EAP_SESSION_SUCCESS
                    else:  # Failure
                        self.state = EAP_SESSION_FAILURE
                else:
                    # No support for cryptobinding or SoH TLV
                    # At least exit with some relevant info
                    message = "Unsupported TLV: {0!r}.".format(msg)
                    log.auth_standard(
                        msg=message,
                        username=self.current_request.username,
                        auth_stage=log.AUTH_UNKNOWN,
                        status=log.AUTH_ERROR,
                        client_ip=self.current_request.client_ip,
                        server_section=self.server.server_section_name,
                        server_section_ikey=self.server.server_section_ikey)
                    error = yield self.errback(self, message)
                    defer.returnValue(error)

            elif eap_type == EAP_TYPE_PEAP:  # PEAP
                tls_flags = msg[5]
                if tls_flags & 0x80:  # Length included
                    self.tls_len = struct.unpack('>I', msg[6:10])
                    tls_msg: bytes = msg[10:]
                else:
                    tls_msg = msg[6:]

                if self.state == EAP_SESSION_PEAP_MORE:
                    self.tls_msg += tls_msg
                else:
                    self.tls_msg = tls_msg

                if tls_flags & 0x40:  # More fragments
                    self.state = EAP_SESSION_PEAP_MORE
                else:
                    self.state = EAP_SESSION_PEAP
                    if self.tls_msg:
                        if hasattr(self, 'ssl_con'):
                            add_response = yield self.add_incoming(tls_msg)
                            if add_response:
                                # Errback or gtc callback occured, pass it through
                                defer.returnValue(add_response)
                        else:
                            # We reset the EAP session but the peer is trying to continue
                            self.state = EAP_SESSION_PEAP_REQUEST
                    else:
                        # Empty PEAP message, probably handshake over
                        self.state = EAP_SESSION_PEAP_ID
        defer.returnValue(False)
コード例 #12
0
ファイル: eap.py プロジェクト: slayer/duoauthproxy-freebsd
    def next_message(self) -> Iterator[bytes]:
        """Get the next outgoing message from the EAP session.

        Returns:
            str: contains message, if any
            pyrad.Packet: if authentication was successful. Result of calling this
                class' self.success() method.
            errback: if authentication was not successful
        """

        if self.state == EAP_SESSION_START:
            # Request Identity
            packet = EAPPacket(EAP_CODE_REQUEST, self.id, EAP_TYPE_IDENTITY,
                               chr(0).encode())

        elif self.state == EAP_SESSION_REQUEST_PEAP:
            # Send a Nak PEAP request
            packet = EAPPacket(EAP_CODE_REQUEST, self.id, EAP_TYPE_NAK,
                               EAP_TYPE_PEAP)

        elif self.state == EAP_SESSION_PEAP_REQUEST:
            # Start a TLS session and send TLS-start packet
            self.start_tls(self.pkey, self.certs, self.cipher_list,
                           self.minimum_tls_version)
            packet = EAPPacket(EAP_CODE_REQUEST,
                               self.id,
                               EAP_TYPE_PEAP,
                               b'',
                               start=True)

        elif self.state == EAP_SESSION_PEAP:
            # Currently in a PEAP session

            # Check to see if the TLS connection has outgoing messages
            outgoing_msg = self.get_outgoing()
            if outgoing_msg:
                packet = EAPPacket(EAP_CODE_REQUEST, self.id, EAP_TYPE_PEAP,
                                   outgoing_msg)

            else:
                # The tls connection has nothing to send
                # Get a message from inner EAP and feed it to tls
                # Connection then retry to find something to send
                inner_msg = yield self.innerEAP.next_message()

                # Upon error or success, next_message will yield
                #  an exception or a boolean respectively
                if isinstance(inner_msg, bytes):
                    self.ssl_con.send(inner_msg)
                    msg = yield self.next_message()
                    defer.returnValue(msg)
                elif isinstance(inner_msg, Exception):
                    log.auth_standard(
                        msg=repr(inner_msg),
                        username=self.current_request.username,
                        auth_stage=log.AUTH_UNKNOWN,
                        status=log.AUTH_ERROR,
                        client_ip=self.current_request.client_ip,
                        server_section=self.server.server_section_name,
                        server_section_ikey=self.server.server_section_ikey)
                    error = yield self.errback(self, repr(inner_msg))
                    defer.returnValue(error)
                elif inner_msg is True:
                    success = yield self.success(self)
                    defer.returnValue(success)
                else:
                    peap_error_message = "Error in PEAP session"
                    log.auth_standard(
                        msg=peap_error_message,
                        username=self.current_request.username,
                        auth_stage=log.AUTH_UNKNOWN,
                        status=log.AUTH_ERROR,
                        client_ip=self.current_request.client_ip,
                        server_section=self.server.server_section_name,
                        server_section_ikey=self.server.server_section_ikey)
                    error = self.errback(self, peap_error_message)
                    defer.returnValue(error)

        elif self.state == EAP_SESSION_REQUEST_GTC:
            # Send a password (GTC) prompt
            packet = EAPPacket(EAP_CODE_REQUEST, self.id, EAP_TYPE_GTC,
                               self.gtc_message)

        elif self.state == EAP_SESSION_PEAP_GTC:
            # Have inner EAP send GTC prompt
            self.innerEAP.state = EAP_SESSION_REQUEST_GTC
            self.state = EAP_SESSION_PEAP
            msg = yield self.next_message()
            defer.returnValue(msg)

        elif self.state == EAP_SESSION_PEAP_ID:
            # Have inner EAP request ID
            self.innerEAP.state = EAP_SESSION_START
            self.state = EAP_SESSION_PEAP
            msg = yield self.next_message()
            defer.returnValue(msg)

        elif self.state == EAP_SESSION_PEAP_ACCEPT:
            # Save the TLS key/randoms for generating MPPE keys
            self.mk = self.ssl_con.master_key()
            self.cr = self.ssl_con.client_random()
            self.sr = self.ssl_con.server_random()

            # Have inner EAP send accept message
            self.innerEAP.state = EAP_SESSION_ACCEPT
            self.state = EAP_SESSION_PEAP
            msg = yield self.next_message()
            defer.returnValue(msg)

        elif self.state == EAP_SESSION_PEAP_DENY:
            # Have inner EAP send reject message
            self.innerEAP.state = EAP_SESSION_DENY
            self.state = EAP_SESSION_PEAP
            msg = yield self.next_message()
            defer.returnValue(msg)

        elif self.state == EAP_SESSION_ACCEPT:
            # Send accept packet
            packet = EAPPacket(EAP_CODE_REQUEST, self.id, EAP_TYPE_TLV, True)

        elif self.state == EAP_SESSION_DENY:
            error = yield self.errback(self, 'Authentication denied.')
            defer.returnValue(error)

        elif self.state == EAP_SESSION_FAILURE:
            error = yield self.errback(self, 'EAP session failed.')
            defer.returnValue(error)

        elif self.state == EAP_SESSION_SUCCESS:
            success = yield self.success(self)
            defer.returnValue(success)

        if self.inner:
            defer.returnValue(
                packet.render()[4:])  # tunneled EAP skips code, id, length
        defer.returnValue(packet.render())
コード例 #13
0
ファイル: server.py プロジェクト: slayer/duoauthproxy-freebsd
    def _handle_request(self, datagram, host_port):
        host, port = host_port
        request_pkt = packet.AuthPacket(packet=datagram,
                                        dict=base.radius_dictionary())
        request_pkt.source = (host, port)

        # make sure it's an AccessRequest
        if request_pkt.code != packet.AccessRequest:
            raise packet.PacketError("non-AccessRequest packet received")

        # lookup secret
        secret_for_host = self.secret_for_host(host)
        if secret_for_host is not None:
            request_pkt.secret = secret_for_host.encode()
        else:
            raise packet.PacketError("Unknown Client: %s" % host)

        # Validate Message-Authenticator, if any
        if request_pkt.message_authenticator:
            if not request_pkt.verify_message_authenticator():
                raise packet.PacketError(
                    "Invalid Message-Authenticator from {0}".format(host))

        # check to see if it's a resend (i.e. client retry)
        old_request = self.requests.get((request_pkt.source, request_pkt.id))
        if old_request:
            old_request_pkt = old_request.packet
            if _match_request_packets(request_pkt, old_request_pkt):
                # enough things (src, id, authenticator, username,
                # password) match that it's probably safe to assume
                # it's a resend. so send our result back (if we have
                # one); otherwise ignore
                self.log_request(old_request, "Received duplicate request")
                self._resend_response(old_request)
                defer.returnValue(old_request)
            else:
                self._cleanup_request(old_request)

        # create request state
        log.msg("Received new request id %r from %r" %
                (request_pkt.id, request_pkt.source))
        request = _ProxyRequest(request_pkt, self.pw_codec,
                                self.client_ip_attr)
        self.requests[(request.source, request.id)] = request

        try:
            # Check if password property can decrypt using the current secret.
            if self._can_decode_password(request):
                # authenticate the user
                request.response = yield self._get_response(request)
            else:
                self.log_request(
                    request,
                    "Cannot decode password using the configured"
                    " radius_secret. Please ensure the client and"
                    " Authentication Proxy use the same shared"
                    " secret.",
                )
                msg = "Cannot decode password"
                log.auth_standard(
                    msg=msg,
                    username=request.username,
                    auth_stage=log.AUTH_UNKNOWN,
                    status=log.AUTH_ERROR,
                    server_section=self.server_section_name,
                    client_ip=request.client_ip,
                    server_section_ikey=self.server_section_ikey,
                )
                response = self.create_reject_packet(request, msg)
                request.response = response.ReplyPacket()

            self._send_response(request)
            defer.returnValue(request)
        except Exception as e:
            # Something went wrong. Clean up the request and raise.
            self._cleanup_request(request)
            raise e
コード例 #14
0
    def get_initial_response(self, request):
        # make sure username, password were provided
        if request.username is None:
            msg = 'No username provided'
            self.log_request(request, msg)
            log.auth_standard(msg=msg,
                              username=request.username,
                              auth_stage="Unknown",
                              status=log.AUTH_ERROR,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey,
                              client_ip=request.client_ip)
            defer.returnValue(self.create_reject_packet(request, msg))

        self.log_request(request,
                         'login attempt for username %r' % request.username)

        if request.password is None:
            self.log_request(
                request, 'Only the PAP with a Shared Secret format is'
                ' supported. Is the system communicating with'
                ' the Authentication Proxy using CHAP or'
                ' MSCHAPv2 instead?')
            msg = 'No password provided'
            self.log_request(request, msg)
            log.auth_standard(msg=msg,
                              username=request.username,
                              auth_stage=log.AUTH_PRIMARY,
                              status=log.AUTH_ERROR,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey,
                              client_ip=request.client_ip)
            defer.returnValue(self.create_reject_packet(request, msg))

        # perform primary authentication
        primary_res = yield self.primary_auth(request, request.password)
        if not primary_res.success:
            defer.returnValue(
                self.create_reject_packet(request, primary_res.msg))

        if request.username in self.exempt_usernames:
            msg = 'User exempted from 2FA'
            log.auth_standard(msg=msg,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_ALLOW,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey,
                              client_ip=request.client_ip)
            defer.returnValue(
                self.create_accept_packet(
                    request, msg, radius_attrs=primary_res.radius_attrs))

        # get a txid from duo service
        try:
            init_res = yield self.client.proxy_init(request.username)
        except duo_async.DuoAPIError as e:
            log.err(None, 'Duo proxy_init call failed')
            response_packet = self.response_for_api_error(
                request,
                primary_res,
                e,
                primary_res.radius_attrs,
            )
            defer.returnValue(response_packet)

        # Note: MUST NOT YIELD between calling _create_challenge_id
        # and calling create_challenge()
        challenge_id = self._create_challenge_id()

        # build script injection with the txid
        params = {
            'script_uri': self.script_uri,
            'proxy_txid': init_res['proxy_txid'],
            'api_host': self.client.host,
            'state': challenge_id
        }
        challenge_msg = self.script_inject % params
        if len(challenge_msg) > 253:
            raise ValueError(
                'response string is %d chars long, but cannot exceed 253 '
                'chars. If you specified a custom iframe_script_uri, you '
                'may need to shorten it by at least %d chars' %
                (len(challenge_msg), len(challenge_msg) - 253))

        self.log_request(request, 'Sending authentication challenge packet')
        state = {
            'primary_res': primary_res,
        }
        challenge_packet = self.create_challenge(request,
                                                 challenge_msg,
                                                 state=state,
                                                 challenge_id=challenge_id)
        defer.returnValue(challenge_packet)
コード例 #15
0
    def preauth(self, request, primary_res, radius_reject_attrs=None):
        # perform preauth request
        if radius_reject_attrs is None:
            radius_reject_attrs = {}
        try:
            if request.username in self.exempt_usernames:
                preauth_res = {
                    'result': duo_async.API_RESULT_ALLOW,
                    'status': 'User exempted from 2FA'
                }
            else:
                preauth_res = yield self.client.preauth(
                    request.username, request.client_ip, self.failmode)
        except duo_async.DuoAPIError as e:
            log.err(None, 'Duo preauth call failed')
            response = self.response_for_api_error(request, primary_res, e,
                                                   radius_reject_attrs)
            defer.returnValue((response, None))

        self.log_request(
            request, 'Got preauth result for: %r' % (preauth_res['result'], ))

        if preauth_res['result'] == duo_async.API_RESULT_ALLOW:
            msg = preauth_res['status']
            log.auth_standard(msg=msg,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_ALLOW,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
            response = self.create_accept_packet(
                request,
                msg,
                primary_res.radius_attrs,
            )
        elif preauth_res['result'] == duo_async.API_RESULT_DENY:
            msg = preauth_res['status']
            log.auth_standard(msg=msg,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
            response = self.create_reject_packet(
                request, msg, radius_attrs=radius_reject_attrs)
        elif preauth_res['result'] == duo_async.API_RESULT_ENROLL:
            msg = preauth_res['status']
            log.auth_standard(msg=log.AUTH_ENROLL_MSG,
                              username=request.username,
                              auth_stage=log.AUTH_SECONDARY,
                              status=log.AUTH_REJECT,
                              client_ip=request.client_ip,
                              server_section=self.server_section_name,
                              server_section_ikey=self.server_section_ikey)
            response = self.create_reject_packet(
                request, msg, radius_attrs=radius_reject_attrs)
        else:
            # duo_async.API_RESULT_AUTH - return a sentinel value
            # saying we should continue
            response = None
        defer.returnValue((response, preauth_res))
コード例 #16
0
    def get_challenge_response(self, request: _ProxyRequest, state: Dict[str,
                                                                         Any]):
        """
        Gets a challenge response for the given request

        Returns:
            packet.Packet
        """
        # Do not have access to factor - request.password is users cookie
        success = False
        self.log_request(request, "Challenge Response: %r" % request.password)

        if state and "primary_res" in state:
            radius_attrs = state["primary_res"].radius_attrs
        else:
            radius_attrs = {}

        auth_cookie = urllib.parse.unquote(self._get_authcookie(request))
        try:
            finish_res = yield self.client.proxy_finish(auth_cookie)
        except duo_async.DuoAPIError as e:
            log.failure("Duo proxy_finish call failed")
            response_packet = self.response_for_api_error(
                request,
                state["primary_res"],
                e,
                state["primary_res"].radius_attrs,
            )
            defer.returnValue(response_packet)

        self.log_request(request,
                         "Authcookie validation result: %r" % finish_res)
        if finish_res["valid_cookie"] and (finish_res["user"]
                                           == request.username):
            success = True

        if success:
            log.auth_standard(
                msg="Valid login from iframe",
                username=request.username,
                auth_stage=log.AUTH_SECONDARY,
                status=log.AUTH_ALLOW,
                server_section=self.server_section_name,
                server_section_ikey=self.server_section_ikey,
                client_ip=request.client_ip,
            )
            defer.returnValue(
                self.create_accept_packet(
                    request,
                    radius_attrs=radius_attrs,
                ))
        else:
            log.auth_standard(
                msg="Invalid login from iframe",
                username=request.username,
                auth_stage=log.AUTH_SECONDARY,
                status=log.AUTH_REJECT,
                server_section=self.server_section_name,
                server_section_ikey=self.server_section_ikey,
                client_ip=request.client_ip,
            )
            defer.returnValue(self.create_reject_packet(request))
コード例 #17
0
    def get_initial_response(self, request: _ProxyRequest):
        """
        Gets a response for the first request over a connection from an appliance

        Returns:
            packet.Packet
        """
        # make sure username, password were provided
        if request.username is None:
            msg = "No username provided"
            self.log_request(request, msg)
            log.auth_standard(
                msg=msg,
                username=request.username,
                auth_stage="Unknown",
                status=log.AUTH_ERROR,
                server_section=self.server_section_name,
                server_section_ikey=self.server_section_ikey,
                client_ip=request.client_ip,
            )
            defer.returnValue(self.create_reject_packet(request, msg))

        self.log_request(request,
                         "login attempt for username %r" % request.username)

        if request.password is None:
            self.log_request(
                request,
                "Only the PAP with a Shared Secret format is"
                " supported. Is the system communicating with"
                " the Authentication Proxy using CHAP or"
                " MSCHAPv2 instead?",
            )
            msg = "No password provided"
            self.log_request(request, msg)
            log.auth_standard(
                msg=msg,
                username=request.username,
                auth_stage=log.AUTH_PRIMARY,
                status=log.AUTH_ERROR,
                server_section=self.server_section_name,
                server_section_ikey=self.server_section_ikey,
                client_ip=request.client_ip,
            )
            defer.returnValue(self.create_reject_packet(request, msg))

        # perform primary authentication
        primary_res = yield self.primary_auth(request, request.password)
        if not primary_res.success:
            defer.returnValue(
                self.create_reject_packet(request, primary_res.msg))

        if request.username in self.exempt_usernames:
            msg = "User exempted from 2FA"
            log.auth_standard(
                msg=msg,
                username=request.username,
                auth_stage=log.AUTH_SECONDARY,
                status=log.AUTH_ALLOW,
                server_section=self.server_section_name,
                server_section_ikey=self.server_section_ikey,
                client_ip=request.client_ip,
            )
            defer.returnValue(
                self.create_accept_packet(
                    request, msg, radius_attrs=primary_res.radius_attrs))

        # get a txid from duo service
        try:
            init_res = yield self.client.proxy_init(request.username)
        except duo_async.DuoAPIError as e:
            log.failure("Duo proxy_init call failed")
            response_packet = self.response_for_api_error(
                request,
                primary_res,
                e,
                primary_res.radius_attrs,
            )
            defer.returnValue(response_packet)

        # Note: MUST NOT YIELD between calling _create_challenge_id
        # and calling create_challenge()
        challenge_id = self._create_challenge_id()

        # build script injection with the txid
        params = {
            "script_uri": self.script_uri,
            "proxy_txid": init_res["proxy_txid"],
            "api_host": self.client.host,
            "state": challenge_id,
        }
        challenge_msg = self.script_inject % params
        if len(challenge_msg) > 253:
            raise ValueError(
                "response string is %d chars long, but cannot exceed 253 "
                "chars. If you specified a custom iframe_script_uri, you "
                "may need to shorten it by at least %d chars" %
                (len(challenge_msg), len(challenge_msg) - 253))

        self.log_request(request, "Sending authentication challenge packet")
        state = {
            "primary_res": primary_res,
        }
        challenge_packet = self.create_challenge(request,
                                                 challenge_msg,
                                                 state=state,
                                                 challenge_id=challenge_id)
        defer.returnValue(challenge_packet)