Example #1
0
    def on_challenge(self, session, challenge):
        assert challenge.method == u"scram"
        assert self._client_nonce is not None
        required_args = ['nonce', 'kdf', 'salt', 'iterations']
        optional_args = ['memory', 'channel_binding']
        for k in required_args:
            if k not in challenge.extra:
                raise RuntimeError("WAMP-SCRAM challenge option '{}' is "
                                   " required but not specified".format(k))
        for k in challenge.extra:
            if k not in optional_args + required_args:
                raise RuntimeError(
                    "WAMP-SCRAM challenge has unknown attribute '{}'".format(
                        k))

        channel_binding = challenge.extra.get(u'channel_binding', u'')
        server_nonce = challenge.extra[u'nonce'].encode('ascii')  # base64
        salt = challenge.extra[u'salt']  # base64
        iterations = int(challenge.extra[u'iterations'])
        memory = int(challenge.extra.get(u'memory', -1))
        password = self._args['password'].encode('utf8')  # supplied by user
        authid = saslprep(self._args['authid'])
        algorithm = challenge.extra[u'kdf']
        client_nonce = self._client_nonce

        self._auth_message = (
            u"{client_first_bare},{server_first},{client_final_no_proof}".
            format(
                client_first_bare=u"n={},r={}".format(authid, client_nonce),
                server_first=u"r={},s={},i={}".format(
                    server_nonce.decode('ascii'), salt.decode('ascii'),
                    iterations),
                client_final_no_proof=u"c={},r={}".format(
                    channel_binding, server_nonce.decode('ascii')),
            ).encode('ascii'))

        if algorithm == u'argon2id-13':
            if memory == -1:
                raise ValueError(
                    "WAMP-SCRAM 'argon2id-13' challenge requires 'memory' parameter"
                )
            self._salted_password = _hash_argon2id13_secret(
                password, salt, iterations, memory)
        elif algorithm == u'pbkdf2':
            self._salted_password = _hash_pbkdf2_secret(
                password, salt, iterations)
        else:
            raise RuntimeError(
                "WAMP-SCRAM specified unknown KDF '{}'".format(algorithm))

        client_key = hmac.new(self._salted_password, b"Client Key",
                              hashlib.sha256).digest()
        stored_key = hashlib.new('sha256', client_key).digest()

        client_signature = hmac.new(stored_key, self._auth_message,
                                    hashlib.sha256).digest()
        client_proof = xor_array(client_key, client_signature)

        return base64.b64encode(client_proof)
    def on_challenge(self, session, challenge):
        assert challenge.method == u"scram"
        assert self._client_nonce is not None
        required_args = ['nonce', 'kdf', 'salt', 'iterations']
        optional_args = ['memory', 'channel_binding']
        for k in required_args:
            if k not in challenge.extra:
                raise RuntimeError(
                    "WAMP-SCRAM challenge option '{}' is "
                    " required but not specified".format(k)
                )
        for k in challenge.extra:
            if k not in optional_args + required_args:
                raise RuntimeError(
                    "WAMP-SCRAM challenge has unknown attribute '{}'".format(k)
                )

        channel_binding = challenge.extra.get(u'channel_binding', u'')
        server_nonce = challenge.extra[u'nonce'].encode('ascii')  # base64
        salt = challenge.extra[u'salt']  # base64
        iterations = int(challenge.extra[u'iterations'])
        memory = int(challenge.extra.get(u'memory', -1))
        password = self._args['password'].encode('utf8')  # supplied by user
        authid = saslprep(self._args['authid'])
        algorithm = challenge.extra[u'kdf']
        client_nonce = self._client_nonce

        self._auth_message = (
            u"{client_first_bare},{server_first},{client_final_no_proof}".format(
                client_first_bare=u"n={},r={}".format(authid, client_nonce),
                server_first=u"r={},s={},i={}".format(server_nonce.decode('ascii'), salt.decode('ascii'), iterations),
                client_final_no_proof=u"c={},r={}".format(channel_binding, server_nonce.decode('ascii')),
            ).encode('ascii')
        )

        if algorithm == u'argon2id-13':
            if memory == -1:
                raise ValueError(
                    "WAMP-SCRAM 'argon2id-13' challenge requires 'memory' parameter"
                )
            self._salted_password = _hash_argon2id13_secret(password, salt, iterations, memory)
        elif algorithm == u'pbkdf2':
            self._salted_password = _hash_pbkdf2_secret(password, salt, iterations)
        else:
            raise RuntimeError(
                "WAMP-SCRAM specified unknown KDF '{}'".format(algorithm)
            )

        client_key = hmac.new(self._salted_password, b"Client Key", hashlib.sha256).digest()
        stored_key = hashlib.new('sha256', client_key).digest()

        client_signature = hmac.new(stored_key, self._auth_message, hashlib.sha256).digest()
        client_proof = xor_array(client_key, client_signature)

        return base64.b64encode(client_proof)
Example #3
0
    def on_challenge(self, session, challenge):
        assert challenge.method == u"scram"
        assert self._client_nonce is not None
        required_args = ['nonce', 'salt', 'cost']
        optional_args = ['memory', 'parallel', 'channel_binding']
        # probably want "algorithm" too, with either "argon2id-19" or
        # "pbkdf2" as values
        for k in required_args:
            if k not in challenge.extra:
                raise RuntimeError(
                    "WAMP-SCRAM challenge option '{}' is "
                    " required but not specified".format(k)
                )
        for k in challenge.extra:
            if k not in optional_args + required_args:
                raise RuntimeError(
                    "WAMP-SCRAM challenge has unknown attribute '{}'".format(k)
                )

        channel_binding = challenge.extra.get(u'channel_binding', u'')
        server_nonce = challenge.extra[u'nonce']  # base64
        salt = challenge.extra[u'salt']  # base64
        cost = int(challenge.extra[u'cost'])
        memory = int(challenge.extra.get(u'memory', 512))
        parallel = int(challenge.extra.get(u'parallel', 2))
        password = self._args['password'].encode('utf8')  # supplied by user
        authid = saslprep(self._args['authid'])
        client_nonce = self._client_nonce

        auth_message = (
            "{client_first_bare},{server_first},{client_final_no_proof}".format(
                client_first_bare="n={},r={}".format(authid, client_nonce),
                server_first="r={},s={},i={}".format(server_nonce, salt, cost),
                client_final_no_proof="c={},r={}".format(channel_binding, server_nonce),
            )
        )

        rawhash = hash_secret(
            secret=password,
            salt=base64.b64decode(salt),
            time_cost=cost,
            memory_cost=memory,
            parallelism=parallel,
            hash_len=16,  # another knob?
            type=Type.ID,
            version=19,
        )
        # spits out stuff like:
        # '$argon2i$v=19$m=512,t=2,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ'

        _, tag, ver, options, salt_data, hash_data = rawhash.split(b'$')
        salted_password = hash_data
        client_key = hmac.new(salted_password, b"Client Key", hashlib.sha256).digest()
        stored_key = hashlib.new('sha256', client_key).digest()

        client_signature = hmac.new(stored_key, auth_message.encode('ascii'), hashlib.sha256).digest()
        client_proof = xor_array(client_key, client_signature)

        def confirm_server_signature(session, details):
            """
            When the server is satisfied, it sends a 'WELCOME' message.
            This will cause the session to be set up and 'join' gets
            notified. Here, we check the server-signature thus
            authorizing the server -- if it fails we drop the
            connection.
            """
            alleged_server_sig = base64.b64decode(details.authextra['scram_server_signature'])
            server_key = hmac.new(salted_password, b"Server Key", hashlib.sha256).digest()
            server_signature = hmac.new(server_key, auth_message.encode('ascii'), hashlib.sha256).digest()
            if not hmac.compare_digest(server_signature, alleged_server_sig):
                session.log.error("Verification of server SCRAM signature failed")
                session.leave(
                    u"wamp.error.cannot_authenticate",
                    u"Verification of server signature failed",
                )
            else:
                session.log.info(
                    "Verification of server SCRAM signature successful"
                )
        session.on('join', confirm_server_signature)

        return base64.b64encode(client_proof)