Esempio n. 1
0
def test_replay_detection():
    detector = ReplayDetector()

    replay_start = 20

    for i in range(1, replay_start + REPLAY_WINDOW_SIZE):
        detector.check_replay_window(i)

    try:
        detector.check_replay_window(1)
        assert False
    except ReplayDetectedError:
        pass

    try:
        detector.check_replay_window(replay_start)
        assert False
    except ReplayDetectedError:
        pass

    try:
        detector.check_replay_window(replay_start + REPLAY_WINDOW_SIZE // 2)
        assert False
    except ReplayDetectedError:
        pass

    try:
        detector.check_replay_window(replay_start + REPLAY_WINDOW_SIZE + 1)
        assert True
    except ReplayDetectedError:
        assert False
Esempio n. 2
0
class LinkDecryptor:
    def __init__(self, link_key):
        self.key = link_key
        self.replay_detector = ReplayDetector()

    def decrypt(self, cipher_text):
        b_link_ctr, header, mac, fragment = cut(cipher_text, CTR_PREFIX_LEN,
                                                LINK_HEADER_LEN, GCM_MAC_LEN)

        link_ctr = b2i(b_link_ctr)

        cipher = gcm_cipher(self.key, link_ctr)

        plain_header = cipher.decrypt_and_verify(header, mac)

        chan_id, msg_ctr, msg_type, _ = cut(plain_header, CHAN_ID_SIZE,
                                            CTR_PREFIX_LEN, MSG_TYPE_FLAG_LEN)

        self.replay_detector.check_replay_window(link_ctr)

        return b2i(chan_id), msg_ctr, fragment, msg_type
Esempio n. 3
0
class ChannelEntry:
    out_chan_list = []
    to_mix = []
    to_client = []
    table = dict()

    def __init__(self, src_addr, dest_addr, pub_comps):
        self.src_addr = src_addr
        self.dest_addr = dest_addr
        self.chan_id = ChannelEntry.random_channel()
        self.b_chan_id = i2b(self.chan_id, CHAN_ID_SIZE)

        self.pub_comps = pub_comps

        print(self, "New Channel", self.dest_addr)

        ChannelEntry.table[self.chan_id] = self

        self.sym_keys = []
        self.request_counter = Counter(CHANNEL_CTR_START)
        self.replay_detector = ReplayDetector(start=CHANNEL_CTR_START)

        for _ in self.pub_comps:
            self.sym_keys.append(gen_sym_key())

        self.packets = []
        self.mix_msg_store = MixMessageStore()

        self.last_interaction = time()

        self.allowed_to_send = False

    def can_send(self):
        return self.packets

    def request(self, request):
        self.last_interaction = time()

        self._make_request_fragments(request)

    def response(self, response):
        self.last_interaction = time()

        msg_type, response = cut(response, MSG_TYPE_FLAG_LEN)

        if msg_type == CHAN_CONFIRM_MSG_FLAG:
            self._chan_confirm_msg()
        elif msg_type == DATA_MSG_FLAG:
            self._receive_response_fragment(response)

    def get_message(self):
        if self.allowed_to_send:
            ret = self._get_data_message()
        else:
            ret = self._get_init_message()

        self._clean_generator_list()

        return ret

    def get_completed_responses(self):
        packets = self.mix_msg_store.completed()

        self.mix_msg_store.remove_completed()

        for packet in packets:
            print(self, "Data", "<-", len(packet.payload))

        return packets

    def _get_init_message(self):
        self.request_counter.next()

        ip, port = self.dest_addr

        destination = ip2b(ip) + i2b(port, PORT_LEN)

        fragment = self._get_init_fragment()

        channel_init = gen_init_msg(self.pub_comps, self.sym_keys, destination + fragment)

        print(self, "Init", "->", len(channel_init))

        # we send a counter value with init messages for channel replay detection only
        return CHAN_INIT_MSG_FLAG + self.b_chan_id + bytes(self.request_counter) + channel_init

    def _get_data_message(self):
        # todo make into generator
        fragment = self._get_data_fragment()

        print(self, "Data", "->", len(fragment) - CTR_PREFIX_LEN)

        return DATA_MSG_FLAG + self.b_chan_id + fragment

    def _get_init_fragment(self):
        # todo make into generator
        if self.packets:
            init_fragment = self.packets[0].get_init_fragment()
        else:
            init_fragment = make_dummy_init_fragment()

        return init_fragment

    def _get_data_fragment(self):
        # todo make into generator
        if self.packets:
            fragment = self.packets[0].get_data_fragment()
        else:
            fragment = make_dummy_data_fragment()

        return self._encrypt_fragment(fragment)

    def _clean_generator_list(self):
        delete = []
        for i in range(len(self.packets)):
            if not self.packets[i]:
                delete.append(i)

        for generator in reversed(delete):
            del self.packets[generator]

    def _chan_confirm_msg(self):
        if not self.allowed_to_send:
            print(self, "Received channel confirmation")

        self.allowed_to_send = True

    def _make_request_fragments(self, request):
        generator = FragmentGenerator(request)

        self.packets.append(generator)

        timed_out = check_for_timed_out_channels(ChannelEntry.table, timeout=25)

        for channel_id in timed_out:
            del ChannelEntry.table[channel_id]

    def _receive_response_fragment(self, response):
        fragment = self._decrypt_fragment(response)

        try:
            self.mix_msg_store.parse_fragment(fragment)
        except ValueError:
            print(self, "Dummy Response received")
            return

    def _encrypt_fragment(self, fragment):
        self.request_counter.next()

        for key in reversed(self.sym_keys):
            counter = self.request_counter

            cipher = ctr_cipher(key, int(counter))

            fragment = bytes(counter) + cipher.encrypt(fragment)

        return fragment

    def _decrypt_fragment(self, fragment):
        ctr = 0

        for key in self.sym_keys:
            ctr, cipher_text = cut(fragment, CTR_PREFIX_LEN)

            ctr = b2i(ctr)

            cipher = ctr_cipher(key, ctr)

            fragment = cipher.decrypt(cipher_text)

        self.replay_detector.check_replay_window(ctr)

        return fragment

    def __str__(self):
        return "ChannelEntry {}:{} - {}:".format(*self.src_addr, self.chan_id)

    @staticmethod
    def random_channel():
        rand_id = random_channel_id()

        while rand_id in ChannelEntry.out_chan_list:
            rand_id = random_channel_id()

        ChannelEntry.out_chan_list.append(rand_id)

        return rand_id
Esempio n. 4
0
class ChannelMid:
    out_chan_list = []
    requests = []
    responses = []

    table_out = dict()
    table_in = dict()

    def __init__(self, in_chan_id):
        self.in_chan_id = in_chan_id
        self.out_chan_id = ChannelMid.random_channel()

        print(self, "New Channel")

        ChannelMid.table_out[self.out_chan_id] = self
        ChannelMid.table_in[self.in_chan_id] = self

        self.key = None
        self.request_replay_detector = ReplayDetector(start=CHANNEL_CTR_START)
        self.response_replay_detector = ReplayDetector(start=CHANNEL_CTR_START)

        self.response_counter = Counter(CHANNEL_CTR_START)

        self.last_interaction = time()

        self.initialized = False

    def forward_request(self, request):
        """Takes a mix fragment, already stripped of the channel id."""
        self.last_interaction = time()

        ctr, cipher_text = cut(request, CTR_PREFIX_LEN)
        ctr = b2i(ctr)

        self.request_replay_detector.check_replay_window(ctr)

        cipher = ctr_cipher(self.key, ctr)

        forward_msg = cipher.decrypt(cipher_text) + get_random_bytes(CTR_MODE_PADDING)

        print(self, "Data", "->", len(forward_msg) - CTR_PREFIX_LEN)

        ChannelMid.requests.append(DATA_MSG_FLAG + i2b(self.out_chan_id, CHAN_ID_SIZE) + forward_msg)

        timed_out = check_for_timed_out_channels(ChannelMid.table_in)

        for in_id in timed_out:
            out_id = ChannelMid.table_in[in_id].out_chan_id

            del ChannelMid.table_in[in_id]
            del ChannelMid.table_out[out_id]

    def forward_response(self, response):
        self.last_interaction = time()

        # cut the padding off
        response, _ = cut(response, -CTR_MODE_PADDING)

        msg_type, response = cut(response, MSG_TYPE_FLAG_LEN)

        msg_ctr, _ = cut(response, CTR_PREFIX_LEN)

        # todo find better way
        if msg_ctr != bytes(CTR_PREFIX_LEN):
            self.response_replay_detector.check_replay_window(b2i(msg_ctr))

        self.response_counter.next()

        cipher = ctr_cipher(self.key, int(self.response_counter))

        forward_msg = cipher.encrypt(response)

        print(self, "Data", "<-", len(forward_msg))

        response = msg_type + i2b(self.in_chan_id, CHAN_ID_SIZE) + bytes(self.response_counter) + forward_msg

        ChannelMid.responses.append(response)

    def parse_channel_init(self, channel_init, priv_comp):
        """Takes an already decrypted channel init message and reads the key.
        """
        self.last_interaction = time()

        msg_ctr, channel_init = cut(channel_init, CTR_PREFIX_LEN)

        self.request_replay_detector.check_replay_window(b2i(msg_ctr))

        sym_key, _, channel_init = process(priv_comp, channel_init)

        if self.key is not None:
            assert self.key == sym_key
            self.initialized = True
        else:
            self.key = sym_key

        print(self, "Init", "->", len(channel_init))

        channel_init = msg_ctr + channel_init

        # we add an empty ctr prefix, because the link encryption expects there
        # to be one, even though the channel init wasn't sym encrypted

        # todo look at this one again
        packet = CHAN_INIT_MSG_FLAG + i2b(self.out_chan_id, CHAN_ID_SIZE) + channel_init

        ChannelMid.requests.append(packet)

    def __str__(self):
        return "ChannelMid {} - {}:".format(self.in_chan_id, self.out_chan_id)

    @staticmethod
    def random_channel():
        rand_id = random_channel_id()

        while rand_id in ChannelMid.table_out.keys():
            rand_id = random_channel_id()

        ChannelMid.out_chan_list.append(rand_id)

        return rand_id