def make_fragment(message_id, fragment_number, last_fragment, payload, payload_limit): if fragment_number > 0b0011_1111: raise ValueError("Too many fragments needed for this payload.") if not payload and message_id != DUMMY_FRAG_ID: raise ValueError("No more fragments left to generate.") frag_byte = fragment_number if last_fragment: frag_byte |= FragmentGenerator.LAST_FRAG_FLAG if len(payload) < payload_limit: frag_byte |= FragmentGenerator.PADDING_FLAG padding_bytes, padding_len = padding_length_to_bytes(payload_limit - len(payload)) else: padding_len = 0 padding_bytes = bytes() fragment = i2b(message_id, FRAG_ID_SIZE) + i2b(frag_byte, FRAG_FLAG_SIZE) fragment += padding_bytes fragment += payload[:payload_limit] + get_random_bytes(padding_len) return fragment
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 __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 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 __init__(self, csv_row, port): self.packet_id = int(csv_row[PACKET_ID_INDEX]) self.timestamp = float(csv_row[TIMESTAMP_INDEX]) self.port = i2b(port, PORT_LEN) self.payload_len = int(csv_row[LENGTH_INDEX]) self.payload_len -= ETHERNET_STACK_OVERHEAD
def test_replay_detection_too_small(): encryptor = LinkEncryptor(link_key) decryptor = LinkDecryptor(link_key) first_encrypted = encryptor.encrypt(msg_type + i2b(chan_id, CHAN_ID_SIZE) + msg_ctr + payload) first_link_counter = encryptor.counter for _ in range(REPLAY_WINDOW_SIZE + 1): encrypted = encryptor.encrypt(msg_type + i2b(chan_id, CHAN_ID_SIZE) + msg_ctr + payload) decryptor.decrypt(encrypted) assert first_link_counter not in decryptor.replay_detector.replay_window try: decryptor.decrypt(first_encrypted) except ReplayDetectedError: assert True return assert False
def test_link_encryption(): encryptor = LinkEncryptor(link_key) decryptor = LinkDecryptor(link_key) encrypted = encryptor.encrypt(msg_type + i2b(chan_id, CHAN_ID_SIZE) + msg_ctr + payload) chan_id2, msg_ctr2, payload2, msg_type2 = decryptor.decrypt(encrypted) assert int(encryptor.counter) in decryptor.replay_detector assert chan_id == chan_id2 assert msg_ctr == msg_ctr2 assert payload == payload2 assert msg_type == msg_type2
def recv_response(self, response): self.last_interaction = time() """Turns the response into a MixMessage and saves its fragments for later sending. """ frag_gen = FragmentGenerator(response) while frag_gen: print(self, "Data", "<-", len(frag_gen.udp_payload)) fragment = frag_gen.get_data_fragment() packet = fragment + get_random_bytes(MIX_COUNT * CTR_PREFIX_LEN) ChannelExit.to_mix.append(DATA_MSG_FLAG + i2b(self.in_chan_id, CHAN_ID_SIZE) + packet)
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 test_replay_detection_already_seen(): encryptor = LinkEncryptor(link_key) decryptor = LinkDecryptor(link_key) encrypted = encryptor.encrypt(msg_type + i2b(chan_id, CHAN_ID_SIZE) + msg_ctr + payload) decryptor.decrypt(encrypted) try: decryptor.decrypt(encrypted) except ReplayDetectedError: assert True return assert False
def test_encrypt_fragment(): channel = ChannelEntry(src_addr, dest_addr, public_keys) sym_key = gen_sym_key() channel.sym_keys = [sym_key] * MIX_COUNT fragment = FragmentGenerator(bytes(100)).get_data_fragment() packet1 = channel._encrypt_fragment(fragment) packet2 = fragment counter = 1 for _ in range(MIX_COUNT): cipher = ctr_cipher(sym_key, counter) packet2 = i2b(counter, CTR_PREFIX_LEN) + cipher.encrypt(packet2) assert packet1 == packet2
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]
ip, port = "127.0.0.1", 45000 entry_addr = ("127.0.0.1", 20000) if len(argv) > 1: to_send = int(argv[1]) else: to_send = -1 sock = socket(AF_INET, UDP) start = time() packets = 1 while to_send != 0: msg_size = randrange(MIN_MSG_SIZE, MAX_MSG_SIZE) pack_id = i2b(packets, 4) try: sock.sendto( ip2b(ip) + i2b(port, 2) + pack_id + bytes(msg_size - 4), entry_addr) except OSError: continue to_send -= 1 sleep(1 / 20.0) print("Sent", packets, "packets in", time() - start, "seconds.")
def send_chan_confirm(self): print(self, "Init", "<-", "len:", DATA_PACKET_SIZE) ChannelExit.to_mix.append(CHAN_CONFIRM_MSG_FLAG + i2b(self.in_chan_id, CHAN_ID_SIZE) + bytes(CTR_PREFIX_LEN) + get_random_bytes(DATA_PACKET_SIZE))
def header(ip, port): return ip2b(ip) + i2b(port, 2)
def __bytes__(self): return i2b(self.current_value, CTR_PREFIX_LEN)
def get_bytes(self): return Packet.ip + self.port + i2b(self.packet_id, 4) + bytes( self.payload_len)