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 __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
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
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
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
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
def __init__(self, link_key): self.key = link_key self.replay_detector = ReplayDetector()