Exemplo n.º 1
0
    def _handle_client(self, client_reader, client_writer, address):
        """
        This method actually does the work to handle the requests for a specific client. The protocol is byte AND line
        oriented, the command is read first (8 bytes) and matched against available commands, the complete line (up to
        64k) is read into memory and must end with newline character. The received data is verified against the received
        user id's public key. Once verified, the matched command is executed.

        @param client_reader: StreamReader object
        @param client_writer: StreamWriter object
        """
        while True:
            # recevied incoming data time stamp
            stamp = int(time())
            # read command first, only take first 8 bytes
            cmd = yield from client_reader.readexactly(COMMAND_LENGTH)

            if not cmd:
                # nothing received from client
                break

            ################
            # Data checking
            ################

            # check received command is valid
            try:
                command = Commands[int(cmd)]
            except ValueError:
                logging.info("\t".join(("Invalid Command Provided By Client",
                                        "IP: {!r}".format(address),
                                        "Command: {!r}".format(cmd))))
                yield from self._close_connection(client_writer, INVALID_COMMAND)
                return

            try:
                # use of encoded data allows for direct readline()
                data = yield from client_reader.readline()

            except asyncio.futures.TimeoutError:
                yield from self._close_connection(client_writer, TIMEOUT)
                return

            # clear up data before proceeding (i.e newline char)
            data = data.strip()

            ################
            # Authorisation
            ################
            if command != friendAcceptance:
                try:
                    data = a85decode(data, foldspaces=True)
                except ValueError:
                    logging.info("Invalid data received, unable to decode as ASCII85")
                    yield from self._close_connection(client_writer, INVALID_COMMAND)
                    return

                # verify data integrity
                try:
                    # using getAuthority hits the disk db
                    data = VerifyKey(getAuthority(self.safe, self.profileId, self.friendMasks[data[-36:]])[0],
                                     encoder=HexEncoder).verify(data)
                except (BadSignatureError, TypeError):
                    # signed data does not match stored public key for provided user id
                    logging.warning('\t'.join(("Unable to verify sent data",
                                               "IP: {!r}".format(address),
                                               "Data: {!r}".format(data),
                                               "Sent ID: {!r}".format(data[-36:]))))
                    yield from self._close_connection(client_writer, INVALID_DATA)
                    return

                # data received as: timestamp, hash chain hex, destination user id, data, sender user id
                tstamp, chain, dest, origin = data[:10], data[10:50], data[50:86], data[-36:]
                data = data[86:-36]

                ###########################
                # Message integrity checks
                ###########################
                integrity = True
                # validation checks
                if dest != self.uid:
                    logging.warning("Message Integrity Failure: User id destination {!r} differs from logged in user".format(dest))
                    integrity = False

                try:

                    if integrity and (stamp + LIMIT_MESSAGE_TIME) < int(tstamp) < (stamp - LIMIT_MESSAGE_TIME):
                        logging.warning("Message Integrity Failure: Message is either too old or in the future, current time '{}' received stamp '{}'".format(stamp, int(tstamp)))
                        integrity = False
                except ValueError:
                        logging.warning("Message Integrity Failure: Message time {!r} is invalid".format(tstamp))

                if integrity and isValidUUID(origin):
                    hchain = bytes(sha1(b''.join((self.hashchain[address], data))).hexdigest(), encoding='ascii')
                    if hchain != chain:
                        logging.warning("Message Integrity Failure: Provided hash chain {!r} does not match local {!r}".format(chain, hchain))
                        integrity = False
                else:
                    logging.warning("Message Integrity Failure: Invalid UUID provided by {!r}".format(origin))
                    integrity = False

                if not integrity:
                    yield from self._close_connection(client_writer, INVALID_COMMAND)
                    return

                try:
                    # ensure command integrity for authorised commands
                    assert Commands[int(data[-COMMAND_LENGTH:])] == command
                except (ValueError, AssertionError):
                    logging.info('\t'.join(("Client unsigned CMD data does not equal signed CMD",
                                            "IP: {!r}".format(address),
                                            "Unsigned Command: {!r}".format(cmd),
                                            "Signed Command: {!r}".format(data[-COMMAND_LENGTH:]))))
                    yield from self._close_connection(client_writer, INVALID_DATA)
                    return

                # all checks cleared, update hash chain
                self.hashchain[address] = hchain

                # data verified, execute command with data and origin user id
                returnData = yield from self._command_dispatch(client_reader, client_writer, command, b''.join((data, origin)))
            else:
                returnData, authToken = yield from friendAcceptance(client_reader, client_writer, self.safe, self.profileId, data)
                if authToken is not None:
                    self.auth.append(authToken)

            # send command result to client
            client_writer.write(returnData)

            # Flush buffer
            yield from client_writer.drain()
Exemplo n.º 2
0
def friendAcceptance(reader, writer, safe, profileId, data, requests=None):
    """
    Handle incoming friend request acceptance (P2P)

    Once a request has been made, and the destination user accepts, the destination user contacts the request user
    who runs this coroutine to complete the friendship.

    Requester->Server (quip client, friendRequest)
    Server->Destination (Heartbeat token)
    Destination->Server (quip client, getRequests)
    Destination->Requester (p2p client, friendCompletion) to (p2p server, this coroutine)

    @param reader: StreamReader object
    @param writer: StreamWriter objet
    @param safe: crypto box
    @param profileId: profile ID of logged in user
    @param data: uid followed by hash of message
    @param requests: (Optional) Recent outgoing friend requests {uid: message hash}
    @return: Auth token
    """
    if not requests:
        requests = {}

    # auth token
    auth = None
    try:
        # verify required input data length
        assert len(data) == 76
        # user id, message hash
        mhash, uid = data[:-36], data[-36:]
        # valid UUID
        assert isValidUUID(uid) is True
    except AssertionError:
        logging.info("\t".join(("Invalid friend completion data received", "Data: {!r}".format(data))))
        return b''.join((BFALSE, WRITE_END)), auth

    if uid not in requests:
        # check db for older requests
        requests.update(getFriendRequests(safe, profileId))

    # obtain request information for this user (uid)
    try:
        msg, timestamp, _, rowid = requests[uid]
    except KeyError:
        logging.warning("\t".join(("Friend Request Failure",
                                   "No friend request found for given user ID", "UID: {!r}".format(uid))))
        return b''.join((BFALSE, WRITE_END)), auth

    # ensure our potential friend has the correct hash value for the friend request
    try:
        assert mhash.decode('ascii') == sha1(b''.join((uid, msg))).hexdigest()
    except (UnicodeDecodeError, AssertionError):
        logging.warning("\t".join(("Friend Request Failure", "Hash values do not match",
                                   "Sent: {!r}".format(mhash),
                                   "Local: {!r}".format(sha1(b''.join((uid, msg))).hexdigest()))))
        return b''.join((BFALSE, WRITE_END)), auth

    # hash value has matched, get public key
    spub = getSigningKeys(safe, profileId)[1]
    mpub = getMessageKeys(safe, profileId)[1]

    # auth token sent to friend
    token = bytes(str(uuid4()), encoding='ascii')
    # create our auth token to be sent to server
    auth = bytes(sha384(b''.join((uid, token))).hexdigest(), encoding='ascii')


    # work out length of data
    data = b''.join((token, spub, mpub))
    # send length to read and auth token and public keys
    writer.write(b''.join((bytes(str(len(data)), encoding='ascii'), WRITE_END, data)))
    yield from writer.drain()

    # recv back success to confirm storage of sent data by friend
    success = yield from reader.readline()
    try:
        assert int(success[0]) == 49
        int(success)
    except (KeyError, ValueError):
        logging.warning("\t".join(("Friend Request Warning",
                                   "Friendship completion failed. Storage confirmation: {!r}".format(success))))
        return b''.join((BFALSE, WRITE_END)), None

    port = success[1:-1]
    # receive length to read
    data = yield from reader.readline()
    try:
        length = int(data)
    except ValueError:
        return b''.join((BFALSE, WRITE_END)), None

    data = yield from reader.read(length)

    fauth, spub, mpub = data[:36], data[36:100], data[100:]

    try:
        assert len(data) > 115
        assert isValidUUID(fauth) is True
    except AssertionError:
        logging.error("\t".join(("Friend Request Failure",
                                 "Invalid mask or public key provided", "Data: {!r}".format(data))))
        return b''.join((BFALSE, WRITE_END)), None

    # created and store localised mask of friend's true ID
    fmask = setUidMask(safe, profileId, uid)

    # store friend's auth mask
    # (the mask we use when submitting authorised requests to the hub server regarding this friend)
    setFriendAuth(safe, profileId, fmask, fauth, auth)

    # store public key for friend
    storeAuthority(safe, profileId, fmask, spub, mpub)

    # store address locally
    setAddress(safe, profileId, fmask,
               b':'.join((bytes(writer.transport.get_extra_info('peername')[0], encoding='ascii'), port)))

    # delete local friend request storage
    delFriendRequests(rowid)

    # True for success of all required friendship steps, hash of auth token we sent to friend (must be sent to hub server)
    return BTRUE, auth