示例#1
0
文件: scram.py 项目: LuckyDenis/xmpp
    def finish(self, data):
        """Process success indicator from the server.

        Process any addiitional data passed with the success.
        Fail if the server was not authenticated.

        :Parameters:
            - `data`: an optional additional data with success.
        :Types:
            - `data`: `bytes`

        :return: success or failure indicator.
        :returntype: `sasl.Success` or `sasl.Failure`"""
        if not self._server_first_message:
            logger.debug("Got success too early")
            return Failure("bad-success")
        if self._finished:
            return Success({
                "username": self.username,
                "authzid": self.authzid
            })
        else:
            ret = self._final_challenge(data)
            if isinstance(ret, Failure):
                return ret
            if self._finished:
                return Success({
                    "username": self.username,
                    "authzid": self.authzid
                })
            else:
                logger.debug("Something went wrong when processing additional"
                             " data with success?")
                return Failure("bad-success")
示例#2
0
    def _get_realm(self, realms, charset):
        """Choose a realm from the list specified by the server.

        :Parameters:
            - `realms`: the realm list.
            - `charset`: encoding of realms on the list.
        :Types:
            - `realms`: `list` of `bytes`
            - `charset`: `bytes`

        :return: the realm chosen or a failure indicator.
        :returntype: `bytes` or `Failure`"""
        if realms:
            realm = realms[0]
            ap_realms = self.in_properties.get("realms")
            if ap_realms is not None:
                realms = (unicode(r, charset) for r in realms)
                for ap_realm in ap_realms:
                    if ap_realm in realms:
                        realm = ap_realm
                        break
            realm = realm.decode(charset)
        else:
            realm = self.in_properties.get("realm")
        if realm is not None:
            self.realm = realm
            try:
                realm = realm.encode(charset)
            except UnicodeError:
                logger.debug(
                    "Couldn't encode realm from utf-8 to {0!r}".format(
                        charset))
                return Failure("incompatible-charset")
        return realm
示例#3
0
    def _check_params(self, username, realm, cnonce, digest_uri, response_val,
                      authzid, nonce_count):
        """Check parameters of a client reponse and pass them to further
        processing.

        :Parameters:
            - `username`: user name.
            - `realm`: realm.
            - `cnonce`: cnonce value.
            - `digest_uri`: digest-uri value.
            - `response_val`: response value computed by the client.
            - `authzid`: authorization id.
            - `nonce_count`: nonce count value.
        :Types:
            - `username`: `bytes`
            - `realm`: `bytes`
            - `cnonce`: `bytes`
            - `digest_uri`: `bytes`
            - `response_val`: `bytes`
            - `authzid`: `bytes`
            - `nonce_count`: `bytes`

        :return: a challenge, a success indicator or a failure indicator.
        :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`"""

        if not cnonce:
            logger.debug("Required 'cnonce' parameter not given")
            return Failure("not-authorized")
        if not response_val:
            logger.debug("Required 'response' parameter not given")
            return Failure("not-authorized")
        if not username:
            logger.debug("Required 'username' parameter not given")
            return Failure("not-authorized")
        if not digest_uri:
            logger.debug("Required 'digest_uri' parameter not given")
            return Failure("not-authorized")
        if not nonce_count:
            logger.debug("Required 'nc' parameter not given")
            return Failure("not-authorized")
        return self._make_final_challenge(username, realm, cnonce, digest_uri,
                                          response_val, authzid, nonce_count)
示例#4
0
    def challenge(self, challenge):
        """Process a challenge and return the response.

        :Parameters:
            - `challenge`: the challenge from server.
        :Types:
            - `challenge`: `bytes`

        :return: the response or a failure indicator.
        :returntype: `sasl.Response` or `sasl.Failure`"""

        if not challenge:
            logger.debug("Empty challenge")
            return Failure("bad-challenge")

        # workaround for some buggy implementations
        challenge = challenge.split(b'\x00')[0]

        if self.response_auth:
            return self._final_challenge(challenge)
        realms = []
        nonce = None
        charset = "iso-8859-1"
        while challenge:
            match = PARAM_RE.match(challenge)
            if not match:
                logger.debug("Challenge syntax error: {0!r}".format(challenge))
                return Failure("bad-challenge")
            challenge = match.group("rest")
            var = match.group("var")
            val = match.group("val")
            logger.debug("{0!r}: {1!r}".format(var, val))
            if var == b"realm":
                realms.append(_unquote(val))
            elif var == b"nonce":
                if nonce:
                    logger.debug("Duplicate nonce")
                    return Failure("bad-challenge")
                nonce = _unquote(val)
            elif var == b"qop":
                qopl = _unquote(val).split(b",")
                if b"auth" not in qopl:
                    logger.debug("auth not supported")
                    return Failure("not-implemented")
            elif var == b"charset":
                if val != b"utf-8":
                    logger.debug("charset given and not utf-8")
                    return Failure("bad-challenge")
                charset = "utf-8"
            elif var == b"algorithm":
                if val != b"md5-sess":
                    logger.debug("algorithm given and not md5-sess")
                    return Failure("bad-challenge")
        if not nonce:
            logger.debug("nonce not given")
            return Failure("bad-challenge")
        return self._make_response(charset, realms, nonce)
示例#5
0
文件: scram.py 项目: LuckyDenis/xmpp
    def _final_challenge(self, challenge):
        """Process the second challenge from the server and return the
        response.

        :Parameters:
            - `challenge`: the challenge from server.
        :Types:
            - `challenge`: `bytes`

        :return: the response or a failure indicator.
        :returntype: `sasl.Response` or `sasl.Failure`
        """
        if self._finished:
            return Failure("extra-challenge")

        match = SERVER_FINAL_MESSAGE_RE.match(challenge)
        if not match:
            logger.debug("Bad final message syntax: {0!r}".format(challenge))
            return Failure("bad-challenge")

        error = match.group("error")
        if error:
            logger.debug("Server returned SCRAM error: {0!r}".format(error))
            return Failure(u"scram-" + error.decode("utf-8"))

        verifier = match.group("verifier")
        if not verifier:
            logger.debug("No verifier value in the final message")
            return Failure("bad-succes")

        server_key = self.HMAC(self._salted_password, b"Server Key")
        server_signature = self.HMAC(server_key, self._auth_message)
        if server_signature != a2b_base64(verifier):
            logger.debug("Server verifier does not match")
            return Failure("bad-succes")

        self._finished = True
        return Response(None)
示例#6
0
    def response(self, response):
        """Process a client reponse.

        :Parameters:
            - `response`: the response from the client.
        :Types:
            - `response`: `bytes`

        :return: a challenge, a success indicator or a failure indicator.
        :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`"""
        if self.out_properties:
            return Success(self.out_properties)
        if not response:
            return Failure("not-authorized")
        return self._parse_response(response)
示例#7
0
    def _final_challenge(self, challenge):
        """Process the second challenge from the server and return the response.

        :Parameters:
            - `challenge`: the challenge from server.
        :Types:
            - `challenge`: `bytes`

        :return: the response or a failure indicator.
        :returntype: `sasl.Response` or `sasl.Failure`
        """
        if self.rspauth_checked:
            return Failure("extra-challenge")
        challenge = challenge.split(b'\x00')[0]
        rspauth = None
        while challenge:
            match = PARAM_RE.match(challenge)
            if not match:
                logger.debug("Challenge syntax error: {0!r}".format(challenge))
                return Failure("bad-challenge")
            challenge = match.group("rest")
            var = match.group("var")
            val = match.group("val")
            logger.debug("{0!r}: {1!r}".format(var, val))
            if var == b"rspauth":
                rspauth = val
        if not rspauth:
            logger.debug("Final challenge without rspauth")
            return Failure("bad-success")
        if rspauth == self.response_auth:
            self.rspauth_checked = True
            return Response(None)
        else:
            logger.debug("Wrong rspauth value - peer is cheating?")
            logger.debug("my rspauth: {0!r}".format(self.response_auth))
            return Failure("bad-success")
示例#8
0
文件: scram.py 项目: LuckyDenis/xmpp
    def challenge(self, challenge):
        """Process a challenge and return the response.

        :Parameters:
            - `challenge`: the challenge from server.
        :Types:
            - `challenge`: `bytes`

        :return: the response or a failure indicator.
        :returntype: `sasl.Response` or `sasl.Failure`
        """

        if not challenge:
            logger.debug("Empty challenge")
            return Failure("bad-challenge")

        if self._server_first_message:
            return self._final_challenge(challenge)

        match = SERVER_FIRST_MESSAGE_RE.match(challenge)
        if not match:
            logger.debug("Bad challenge syntax: {0!r}".format(challenge))
            return Failure("bad-challenge")

        self._server_first_message = challenge

        mext = match.group("mext")
        if mext:
            logger.debug("Unsupported extension received: {0!r}".format(mext))
            return Failure("bad-challenge")

        nonce = match.group("nonce")
        if not nonce.startswith(self._c_nonce):
            logger.debug("Nonce does not start with our nonce")
            return Failure("bad-challenge")

        salt = match.group("salt")
        try:
            salt = a2b_base64(salt)
        except ValueError:
            logger.debug("Bad base64 encoding for salt: {0!r}".format(salt))
            return Failure("bad-challenge")

        iteration_count = match.group("iteration_count")
        try:
            iteration_count = int(iteration_count)
        except ValueError:
            logger.debug("Bad iteration_count: {0!r}".format(iteration_count))
            return Failure("bad-challenge")

        return self._make_response(nonce, salt, iteration_count)
示例#9
0
文件: scram.py 项目: LuckyDenis/xmpp
    def _handle_final_response(self, response):
        match = CLIENT_FINAL_MESSAGE_RE.match(response)
        if not match:
            logger.debug("Bad response syntax: {0!r}".format(response))
            return Failure("not-authorized")
        if match.group("nonce") != self._nonce:
            logger.debug("Bad nonce in the final client response")
            return Failure("not-authorized")
        cb_input = a2b_base64(match.group("cb"))
        if not cb_input.startswith(self._gs2_header):
            logger.debug(
                "GS2 header in the final response ({0!r}) doesn't"
                " match the one sent in the first message ({1!r})".format(
                    cb_input, self._gs2_header))
            return Failure("not-authorized")
        if self._cb_name:
            cb_data = cb_input[len(self._gs2_header):]
            if cb_data != self.properties["channel-binding"][self._cb_name]:
                logger.debug("Channel binding data doesn't match")
                return Failure("not-authorized")

        proof = a2b_base64(match.group("proof"))

        auth_message = (self._client_first_message_bare + b"," +
                        self._server_first_message + b"," +
                        match.group("without_proof"))
        if self._stored_key is None:
            # compute something to prevent timing attack
            client_signature = self.HMAC(b"", auth_message)
            client_key = self.XOR(client_signature, proof)
            self.H(client_key)
            logger.debug("Authentication failed (bad username)")
            return Failure("not-authorized")

        client_signature = self.HMAC(self._stored_key, auth_message)
        client_key = self.XOR(client_signature, proof)
        if self.H(client_key) != self._stored_key:
            logger.debug("Authentication failed")
            return Failure("not-authorized")

        server_signature = self.HMAC(self._server_key, auth_message)
        server_final_message = b"v=" + standard_b64encode(server_signature)
        return Success(self.out_properties, server_final_message)
示例#10
0
    def _make_final_challenge(self, username, realm, cnonce, digest_uri,
                              response_val, authzid, nonce_count):
        """Send the second challenge in reply to the client response.

        :Parameters:
            - `username`: user name.
            - `realm`: realm.
            - `cnonce`: cnonce value.
            - `digest_uri`: digest-uri value.
            - `response_val`: response value computed by the client.
            - `authzid`: authorization id.
            - `nonce_count`: nonce count value.
        :Types:
            - `username`: `bytes`
            - `realm`: `bytes`
            - `cnonce`: `bytes`
            - `digest_uri`: `bytes`
            - `response_val`: `bytes`
            - `authzid`: `bytes`
            - `nonce_count`: `bytes`

        :return: a challenge, a success indicator or a failure indicator.
        :returntype: `sasl.Success` or `sasl.Failure`
        """

        username_uq = username.replace(b'\\', b'')
        if authzid:
            authzid_uq = authzid.replace(b'\\', b'')
        else:
            authzid_uq = None
        if realm:
            realm_uq = realm.replace(b'\\', b'')
        else:
            realm_uq = None
        digest_uri_uq = digest_uri.replace(b'\\', b'')
        props = dict(self.in_properties)
        props["realm"] = realm_uq.decode("utf-8")
        password, pformat = self.password_database.get_password(
            username_uq.decode("utf-8"), (u"plain", u"md5:user:realm:pass"),
            props)
        if pformat == u"md5:user:realm:pass":
            urp_hash = password.a2b_hex()
        elif pformat == u"plain":
            urp_hash = _make_urp_hash(username, realm,
                                      password.encode("utf-8"))
        else:
            logger.debug(u"Couldn't get password.")
            return Failure(u"not-authorized")
        valid_response = _compute_response(urp_hash, self.nonce, cnonce,
                                           nonce_count, authzid, digest_uri)
        if response_val != valid_response:
            logger.debug(u"Response mismatch: {0!r} != {1!r}".format(
                response_val, valid_response))
            return Failure(u"not-authorized")
        try:
            fields = digest_uri_uq.split(b"/")
            if len(fields) == 3:
                serv_type, host, serv_name = [
                    f.decode("utf-8") for f in fields
                ]
            elif len(fields) == 2:
                serv_type, host = [f.decode("utf-8") for f in fields]
                serv_name = None
            else:
                raise ValueError
        except (ValueError, UnicodeError):
            logger.debug("Bad digest_uri: {0!r}".format(digest_uri_uq))
            return Failure("not-authorized")
        if "service-type" in self.in_properties:
            if serv_type != self.in_properties["service-type"]:
                logger.debug(u"Bad serv-type: {0!r} != {1!r}".format(
                    serv_type, self.in_properties["service-type"]))
                return Failure("not-authorized")
        if "service-domain" in self.in_properties:
            if serv_name:
                if serv_name != self.in_properties["service-domain"]:
                    logger.debug(u"serv-name: {0!r} != {1!r}".format(
                        serv_name, self.in_properties["service-domain"]))
                return Failure("not-authorized")
            elif (host != self.in_properties["service-domain"]
                  and host != self.in_properties.get("service-hostname")):
                logger.debug(u"bad host: {0!r} != {1!r}"
                             u" & {0!r} != {2!r}".format(
                                 host, self.in_properties["service-domain"],
                                 self.in_properties.get("service-hostname")))
                return Failure("not-authorized")
        if "service-hostname" in self.in_properties:
            if host != self.in_properties["service-hostname"]:
                logger.debug(u"bad host: {0!r} != {1!r}".format(
                    host, self.in_properties["service-hostname"]))
                return Failure("not-authorized")
        rspauth = _compute_response_auth(urp_hash, self.nonce, cnonce,
                                         nonce_count, authzid, digest_uri)
        if authzid_uq is not None:
            authzid_uq = authzid_uq.decode("utf-8")
        self.out_properties = {
            "username": username.decode("utf-8"),
            "realm": realm.decode("utf-8"),
            "authzid": authzid_uq,
            "service-type": serv_type,
            "service-domain": serv_name if serv_name else host,
            "service-hostname": host
        }
        return Success(self.out_properties, b"rspauth=" + rspauth)
示例#11
0
    def _parse_response(self, response):
        """Parse a client reponse and pass to further processing.

        :Parameters:
            - `response`: the response from the client.
        :Types:
            - `response`: `bytes`

        :return: a challenge, a success indicator or a failure indicator.
        :returntype: `sasl.Challenge`, `sasl.Success` or `sasl.Failure`"""

        # workaround for some SASL implementations
        response = response.split(b'\x00')[0]

        if self.realm:
            realm = self.realm.encode("utf-8")
            realm = _quote(realm)
        else:
            realm = None

        username = None
        cnonce = None
        digest_uri = None
        response_val = None
        authzid = None
        nonce_count = None
        while response:
            match = PARAM_RE.match(response)
            if not match:
                logger.debug("Response syntax error: {0!r}".format(response))
                return Failure("not-authorized")
            response = match.group("rest")
            var = match.group("var")
            val = match.group("val")
            logger.debug("{0!r}: {1!r}".format(var, val))
            if var == b"realm":
                realm = val[1:-1]
            elif var == b"cnonce":
                if cnonce:
                    logger.debug("Duplicate cnonce")
                    return Failure("not-authorized")
                cnonce = val[1:-1]
            elif var == b"qop":
                if val != b'auth':
                    logger.debug("qop other then 'auth'")
                    return Failure("not-authorized")
            elif var == b"digest-uri":
                digest_uri = val[1:-1]
            elif var == b"authzid":
                authzid = val[1:-1]
            elif var == b"username":
                username = val[1:-1]
            elif var == b"response":
                response_val = val
            elif var == b"nc":
                nonce_count = val
                self.last_nonce_count += 1
                if int(nonce_count) != self.last_nonce_count:
                    logger.debug("bad nonce: {0!r} != {1!r}".format(
                        nonce_count, self.last_nonce_count))
                    return Failure("not-authorized")
        return self._check_params(username, realm, cnonce, digest_uri,
                                  response_val, authzid, nonce_count)
示例#12
0
    def _make_response(self, charset, realms, nonce):
        """Make a response for the first challenge from the server.

        :Parameters:
            - `charset`: charset name from the challenge.
            - `realms`: realms list from the challenge.
            - `nonce`: nonce value from the challenge.
        :Types:
            - `charset`: `bytes`
            - `realms`: `bytes`
            - `nonce`: `bytes`

        :return: the response or a failure indicator.
        :returntype: `sasl.Response` or `sasl.Failure`"""

        params = []
        realm = self._get_realm(realms, charset)
        if isinstance(realm, Failure):
            return realm
        elif realm:
            realm = _quote(realm)
            params.append(b'realm="' + realm + b'"')

        try:
            username = self.username.encode(charset)
        except UnicodeError:
            logger.debug("Couldn't encode username to {0!r}".format(charset))
            return Failure("incompatible-charset")

        username = _quote(username)
        params.append(b'username="******"')

        cnonce = self.in_properties.get("nonce_factory",
                                        default_nonce_factory)()
        cnonce = _quote(cnonce)
        params.append(b'cnonce="' + cnonce + b'"')

        params.append(b'nonce="' + nonce + b'"')

        self.nonce_count += 1

        nonce_count = "{0:08x}".format(self.nonce_count).encode("us-ascii")
        params.append(b'nc=' + nonce_count)

        params.append(b'qop=auth')

        serv_type = self.in_properties["service-type"]
        serv_type = serv_type.encode("us-ascii")
        serv_name = self.in_properties["service-domain"]
        host = self.in_properties.get("service-hostname", serv_name)
        serv_name = serv_name.encode("idna")
        host = host.encode("idna")

        if serv_name and serv_name != host:
            digest_uri = b"/".join((serv_type, host, serv_name))
        else:
            digest_uri = b"/".join((serv_type, host))

        digest_uri = _quote(digest_uri)
        params.append(b'digest-uri="' + digest_uri + b'"')

        if self.authzid:
            try:
                authzid = self.authzid.encode(charset)
            except UnicodeError:
                logger.debug(
                    "Couldn't encode authzid to {0!r}".format(charset))
                return Failure("incompatible-charset")
            authzid = _quote(authzid)
        else:
            authzid = b""

        try:
            epasswd = self.in_properties["password"].encode(charset)
        except UnicodeError:
            logger.debug("Couldn't encode password to {0!r}".format(charset))
            return Failure("incompatible-charset")
        logger.debug("Encoded password: {0!r}".format(epasswd))
        urp_hash = _make_urp_hash(username, realm, epasswd)

        response = _compute_response(urp_hash, nonce, cnonce, nonce_count,
                                     authzid, digest_uri)
        self.response_auth = _compute_response_auth(urp_hash, nonce, cnonce,
                                                    nonce_count, authzid,
                                                    digest_uri)
        params.append(b'response=' + response)
        if authzid:
            params.append(b'authzid="' + authzid + b'"')
        return Response(b",".join(params))
示例#13
0
文件: scram.py 项目: LuckyDenis/xmpp
    def _handle_first_response(self, response):
        match = CLIENT_FIRST_MESSAGE_RE.match(response)
        if not match:
            logger.debug("Bad response syntax: {0!r}".format(response))
            return Failure("not-authorized")

        mext = match.group("mext")
        if mext:
            logger.debug("Unsupported extension received: {0!r}".format(mext))
            return Failure("not-authorized")

        gs2_header = match.group("gs2_header")
        cb_name = match.group("cb_name")
        if self.channel_binding:
            if not cb_name:
                logger.debug("{0!r} used with no channel-binding".format(
                    self.name))
                return Failure("not-authorized")
            cb_name = cb_name.decode("utf-8")
            if cb_name not in self.properties["channel-binding"]:
                logger.debug(
                    "Channel binding data type {0!r} not available".format(
                        cb_name))
                return Failure("not-authorized")
        else:
            if gs2_header.startswith(b'y'):
                plus_name = self.name + "-PLUS"
                if plus_name in self.properties.get("enabled_mechanisms", []):
                    logger.warning("Channel binding downgrade attack detected")
                    return Failure("not-authorized")
            elif gs2_header.startswith(b'p'):
                # is this really an error?
                logger.debug("Channel binding requested for {0!r}".format(
                    self.name))
                return Failure("not-authorized")

        authzid = match.group("authzid")
        if authzid:
            self.out_properties['authzid'] = self.unescape(authzid).decode(
                "utf-8")
        else:
            self.out_properties['authzid'] = None
        username = self.unescape(match.group("username")).decode("utf-8")
        self.out_properties['username'] = username

        nonce_factory = self.properties.get("nonce_factory",
                                            default_nonce_factory)

        properties = dict(self.properties)
        properties.update(self.out_properties)

        s_pformat = "SCRAM-{0}-SaltedPassword".format(self.hash_function_name)
        k_pformat = "SCRAM-{0}-Keys".format(self.hash_function_name)
        password, pformat = self.password_database.get_password(
            username, (s_pformat, "plain"), properties)
        if pformat == s_pformat:
            if password is not None:
                salt, iteration_count, salted_password = password
            else:
                logger.debug("No password for user {0!r}".format(username))
        elif pformat != k_pformat:
            salt = self.properties.get("SCRAM-salt")
            if not salt:
                salt = nonce_factory()
            iteration_count = self.properties.get("SCRAM-iteration-count",
                                                  4096)
            if pformat == "plain" and password is not None:
                salted_password = self.Hi(self.Normalize(password), salt,
                                          iteration_count)
            else:
                logger.debug("No password for user {0!r}".format(username))
                password = None
                # to prevent timing attack, compute the key anyway
                salted_password = self.Hi(self.Normalize(""), salt,
                                          iteration_count)
        if pformat == k_pformat:
            salt, iteration_count, stored_key, server_key = password
        else:
            client_key = self.HMAC(salted_password, b"Client Key")
            stored_key = self.H(client_key)
            server_key = self.HMAC(salted_password, b"Server Key")

        if password is not None:
            self._stored_key = stored_key
            self._server_key = server_key
        else:
            self._stored_key = None
            self._server_key = None

        c_nonce = match.group("nonce")
        s_nonce = nonce_factory()
        if not VALUE_CHARS_RE.match(s_nonce):
            s_nonce = standard_b64encode(s_nonce)
        nonce = c_nonce + s_nonce
        server_first_message = (b"r=" + nonce + b",s=" +
                                standard_b64encode(salt) + b",i=" +
                                str(iteration_count).encode("utf-8"))
        self._nonce = nonce
        self._cb_name = cb_name
        self._gs2_header = gs2_header
        self._client_first_message_bare = match.group("client_first_bare")
        self._server_first_message = server_first_message
        return Challenge(server_first_message)