def handle_file_request(self, request_client_cid, file): """Checks availability of the requested file and creates a bridge if available. """ if file not in self.files: return ("file not found in network files: " + file, 404) # 404 Not Found request_client_ip = self.peers[request_client_cid] # Find the CID and IP of the first client owning the requested file owning_client_cid = choice(self.files[file]) owning_client_ip = self.peers[owning_client_cid] # Create the CID and FSID for this bridge bridge_cid = generate_bytes(cid_size).hex() fsid = self.fsid_counter self.fsid_counter += 1 # Send a message to the node at the start of the bridge make_bridge_message = { "type": "make_bridge", "bridge_CID": bridge_cid, "to": request_client_ip, "FSID": fsid } # Send on the control channel of the node requests.post(get_url(owning_client_ip) + "/control", json=make_bridge_message) # Send a message to the node at the end of the bridge receive_bridge_message = { "type": "receive_bridge", "CID": request_client_cid, "bridge_CID": bridge_cid } # Send on the control channel of the node requests.post(get_url(request_client_ip) + "/control", json=receive_bridge_message) # Send a message to the client, asking he/she to send the file request_message = { "CID": owning_client_cid, "payload": json_to_bytes({ "type": "request", "file": file, "FSID": fsid }).hex() } requests.post(get_url(owning_client_ip), json=request_message) return "ok"
def forward_upstream(self, message, colour): """Sends message to next node, bridge or tracker. """ up_cid = self.down_relay[message["CID"]]["UpCID"] up_ip = self.down_relay[message["CID"]]["UpIP"] sess_key = self.down_relay[message["CID"]]["SessKey"] # Decrypt the payload (peel one layer of the onion) payload = aes_decrypt(bytes.fromhex(message["payload"]), sess_key) # Try to decode the payload try: decoded_payload = bytes_to_json(payload) # No decoding exception, the payload was a valid JSON once # decoded. # Two possibilities here: the payload is for a bridge or # for the tracker if "FSID" in decoded_payload: return self.transmit_to_bridge(decoded_payload, colour) # If the message is a teardown message elif "type" in decoded_payload and decoded_payload[ "type"] == "teardown": self.teardown(message["CID"]) # If we pass here, then we should just forward upstream except (UnicodeDecodeError, json.decoder.JSONDecodeError) as e: # A decoding exception occurred, just forward upstream pass self.cprint([message["CID"], "upstream", up_ip], "forward", colour) new_message = {"CID": up_cid, "payload": payload.hex()} requests.post(get_url(up_ip), json=new_message) return "ok"
def receive_from_bridge(self, message, colour): """Receives messaged from bridge. """ # Disabled check for now, it may cause problems if not self.bridgeCID_matches_existing_downCID(message["CID"]): return "Bridge CID does not matches with a down CID", 400 # 400 Bad Request down_cid = self.down_file_transfer[message["CID"]] down_ip = self.down_relay[down_cid]["DownIP"] sess_key = self.down_relay[down_cid]["SessKey"] self.cprint([message["CID"], down_ip], "receive_from_bridge", colour) payload = bytes.fromhex(message["payload"]) signatures = [self.sign(payload).hex()] new_message = { "CID": down_cid, # Encrypt the message when sending downstream, we # received it as encoded plaintext "payload": aes_encrypt(payload, sess_key).hex(), "signatures": signatures } requests.post(get_url(down_ip), json=new_message) return "ok"
def transmit_to_bridge(self, payload, colour): """Sends payload through bridge. """ fsid = payload["FSID"] # Disabled for now, this test causes problems if fsid not in self.up_file_transfer: return "FSID not found for file sharing", 404 # 404 Not Found bridge_ip = self.up_file_transfer[fsid]["IP"] bridge_cid = self.up_file_transfer[fsid]["CID"] self.cprint([fsid, bridge_ip], "transmit_to_bridge", colour) new_message = { "CID": bridge_cid, # The payload is not encrypted, just encoded "payload": json_to_bytes({ "type": "file", "file": payload["file"], "data": payload["data"] }).hex() } requests.post(get_url(bridge_ip), json=new_message) return "ok"
def conn(self): """Connects to the torrent network, uploading the list of files this client has. """ self.tunnel_nodes = self.select_nodes(node_pool) self.sesskeys = [ generate_bytes(aes_common.key_size) for _ in self.tunnel_nodes ] self.cid = generate_bytes(cid_size).hex() tracker_payload = { "type": "ls", "files": list(self.owned_files.keys()) } payloadZ = { "to": tracker, # The payload is plaintext between Z and the tracker, but encoded "relay": json_to_bytes(tracker_payload).hex() } payloadY = { "to": self.tunnel_nodes[2], # Encrypt the AES session key for Z with RSA, it will be copied by # the node "aes_key": rsa_encrypt(self.sesskeys[2], public_keys[self.tunnel_nodes[2]]).hex(), # Encrypt the payload for Z with the AES session key "relay": aes_encrypt(json_to_bytes(payloadZ), self.sesskeys[2]).hex() } payloadX = { "to": self.tunnel_nodes[1], "aes_key": rsa_encrypt(self.sesskeys[1], public_keys[self.tunnel_nodes[1]]).hex(), "relay": aes_encrypt(json_to_bytes(payloadY), self.sesskeys[1]).hex() } message = { "CID": self.cid, "aes_key": rsa_encrypt(self.sesskeys[0], public_keys[self.tunnel_nodes[0]]).hex(), "payload": aes_encrypt(json_to_bytes(payloadX), self.sesskeys[0]).hex() } requests.post(get_url(self.tunnel_nodes[0]), json=message) self.connected = True return redirect("/")
def create_tunnel(self, message, colour): """Creates tunnel using three nodes. Read more on /docs/index.md """ # Decrypt the AES key sess_key = rsa_decrypt(bytes.fromhex(message["aes_key"]), self.private_key) sess_key = sess_key[ -aes_common.key_size:] # Discard the right padding created by RSA # Decrypt the payload with the AES key # It should be a JSON string once decoded payload = aes_decrypt(bytes.fromhex(message["payload"]), sess_key) payload = bytes_to_json(payload) if "relay" not in payload or "to" not in payload: # All these fields should be present return "relay and to are needed in payload when creating the tunnel", 400 # 400 Bad Request # Generate a CID for the upstream link up_cid = generate_bytes(cid_size).hex() self.cprint([message["CID"]], "unknownCID", colour) # Add info to the relay tables self.down_relay[message["CID"]] = { "DownIP": request.remote_addr, "SessKey": sess_key, "UpCID": up_cid, "UpIP": payload["to"] } self.up_relay[up_cid] = { "DownCID": message["CID"], "DownIP": request.remote_addr, "SessKey": sess_key, "UpIP": payload["to"] } # Forward the payload to the next node upstream new_message = { "CID": up_cid, # Copy verbatim the encrypted key and payload for the next # node (not our business) "payload": payload["relay"] } if "aes_key" in payload: new_message["aes_key"] = payload["aes_key"] self.cprint([up_cid, payload["to"]], "add_to_relay", colour) requests.post(get_url(payload["to"]), json=new_message) return "ok"
def send_payload(self, payload): """Encrypts three times a message and send it to the tunnel. The tunnel has to be established beforehand. """ payload = json_to_bytes(payload) # Encrypt in the reverse order, the closest node (first in the # list) decrypts first for node, sesskey in reversed( list(zip(self.tunnel_nodes, self.sesskeys))): payload = aes_encrypt(payload, sesskey) message = {"CID": self.cid, "payload": payload.hex()} requests.post(get_url(self.tunnel_nodes[0]), json=message)
def forward_downstream(self, message, colour): """Sends message to next node. """ down_cid = self.up_relay[message["CID"]]["DownCID"] down_ip = self.up_relay[message["CID"]]["DownIP"] sess_key = self.up_relay[message["CID"]]["SessKey"] self.cprint([message["CID"], "downstream", down_ip], "forward", colour) signatures = [] if "signatures" in message: signatures = message["signatures"] payload = bytes.fromhex(message["payload"]) signature = self.sign(payload).hex() signatures.append(signature) new_message = { "CID": down_cid, # Encrypt the payload (add a layer to the onion) "payload": aes_encrypt(payload, sess_key).hex(), "signatures": signatures } print(new_message) requests.post(get_url(down_ip), json=new_message) return "ok"
def handle_new_client(self, cid, ip, files): """Registers a new client by remembering the CID and IP of the exit node, and send back the list of available files. """ self.peers[cid] = ip for file in files: # Create a list of owning clients for the file if there is none, # and put the cid of the client in it self.files.setdefault(file, []).append(cid) # Send a new list of files to all peers for peer_cid, peer_ip in self.peers.items(): response = { "CID": peer_cid, "payload": json_to_bytes({ "type": "ls", "files": list(self.files.keys()) }).hex() } requests.post(get_url(peer_ip), json=response) return "ok"