23 Commits

Author SHA1 Message Date
SG
00bf69ed34 check_tty=False 2023-05-12 21:05:20 +02:00
SG
7562395529 Some changes to progress bar 2023-05-12 20:54:36 +02:00
SG
603c3b7238 Fix chunk_id checks 2023-05-12 20:01:07 +02:00
SG
6db4849939 fixes 2023-05-12 13:43:13 +02:00
SG
260e29fbf6 Fix last chunk not received 2023-05-12 13:40:26 +02:00
SG
ef7ee52ad9 Some fixes and updates 2023-05-11 21:20:23 +02:00
SG
4e583e39c3 Change default server URL 2023-05-11 20:04:45 +02:00
SG
f36e78daf4 version bump 2023-05-08 19:27:23 +02:00
SG
8a1905a7b6 Fix progress bar handling 2023-05-08 19:18:55 +02:00
SG
71307f3bc7 updates 2023-05-08 19:03:16 +02:00
SG
99e9996a55 Remove xkcdpass requirement 2023-05-08 18:15:50 +02:00
SG
634c513c8c Switch from xkcdpass to a very simple diceware 2023-05-08 18:04:19 +02:00
SG
575568772a Reorganize files and folders 2023-05-08 17:44:34 +02:00
SG
d13b59b8e0 Reorganize files and folders 2023-05-08 17:44:16 +02:00
SG
9c385c8379 Update progress bar for smaller files 2023-05-03 18:19:53 +02:00
SG
ad17c935ef Minor fixes 2023-05-03 18:11:46 +02:00
SG
26a0840ad2 Some minor changes 2023-05-03 17:53:52 +02:00
SG
717abbc4b2 Fix some typos 2023-05-03 17:29:24 +02:00
SG
e634643141 Fix sender not exiting on last chunk 2023-05-03 17:25:59 +02:00
SG
4e0d53994c Readme 2023-05-01 17:07:50 +02:00
SG
d987dfd091 Readme 2023-05-01 17:07:09 +02:00
SG
962af5da89 Readme 2023-05-01 17:06:37 +02:00
SG
518929fe2d Readme 2023-05-01 17:04:43 +02:00
6 changed files with 65 additions and 36 deletions

View File

@@ -1,14 +1,14 @@
# Transphase
This is a collection of tools to copy files or proxy connections via WebSockets protocol.
It is done in a cryptographically safe(ish) way.
A rendevouz/relay server is used to help with data transfer.
This is a collection of tools to copy files or proxy connections via WebSockets protocol. \
It is done in a cryptographically safe(ish) way. \
A relay server is used to help with data transfer.
**Transphase** - the relay server \
**Transmat** - tool to copy files/directories \
**Transplace** - HTTP proxy/exit point
**transphase** - the relay server \
**transmat** - tool to copy files/directories \
**transplace** - HTTP proxy/exit point
### Copy files
Transphase server should be running and accessible by the parties.
### How to copy files using ```transmat```
Transphase relay server should be running and accessible by the parties.
On the sending party:
```
transmat --send <FILENAME>
@@ -18,4 +18,9 @@ This will prepare the sender and output the command to run on the receiving part
transmat --receive --password Space-Time-Continuum
```
The sending party will wait for the receiving party to connect to the relay server, and then it will start the transfer. \
With the exception of some service messages, all the data is encrypted. \
Encryption and decryption is done only client-side, and the relay server has no useful knowledge of the data it relays. \
Fernet module (AES128-CBC + HMAC-SHA256) is used to encrypt and authenticate the data.
See also ```transmat --help```

18
transmat/diceware.py Normal file

File diff suppressed because one or more lines are too long

View File

@@ -5,5 +5,4 @@ lz4==4.3.2
progress==1.6
pycparser==2.21
pyjson==1.3.0
websockets==11.0.2
xkcdpass==1.19.3
websockets==11.0.2

View File

@@ -1,16 +1,16 @@
#!/usr/bin/env python
import asyncio, websockets
import sys, os, base64, argparse, json, pickle
from xkcdpass import xkcd_password as xp
import sys, os, base64, argparse, json, pickle, math
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from progress.bar import Bar
import diceware
VERSION_NUMBER="0.1"
VERSION_NUMBER="0.1.3"
SCRIPT_BASENAME = os.path.basename(sys.argv[0])
async def read_chunks(filename, chunk_size = 1024):
with open(filename, "rb") as f:
@@ -94,26 +94,23 @@ async def send_encrypted_msg(ws, k, data):
await send_msg(ws, json.dumps(msg))
async def main():
WS_RELAY_SERVER = "wss://transmat.exocortex.ru"
WS_RELAY_SERVER = "wss://transmat.exocortex.ru/ws"
parser = argparse.ArgumentParser()
arg_group = parser.add_mutually_exclusive_group(required=True)
arg_group.add_argument('--receive', '--recv', action='store_true', help='Receive a file from the remote party (mutually exclusive with --send and --relay)')
arg_group.add_argument('--send', type=str, help='Send a file to the remote party (mutually exclusive with --receive and --relay)')
arg_group.add_argument('--version', action='store_true', help="Show version")
#arg_group.add_argument('--relay', action='store_true', help='Run as a Relay server (mutually exclusive with --receive and --send)')
parser.add_argument('--server', type=str, help="Specify the Relay server URL (ignored with --relay)")
parser.add_argument('--password', type=str, help="Specify the shared password (for --receive)")
args = parser.parse_args()
passwd_part = server_part = ""
send_part = f'{sys.argv[0]} --receive '
send_part = f'{SCRIPT_BASENAME} --receive'
if args.receive:
role = 'receive'
password = args.password
if args.send and args.password is None:
wordlist = xp.generate_wordlist(wordfile = xp.locate_wordfile(), min_length = 5, max_length = 9)
password = xp.generate_xkcdpassword(wordlist, numwords=4, delimiter = "-", case='capitalize')
password = diceware.generate_passphrase(4)
passwd_part = f"--password {password}"
if args.receive and args.password is None:
print("Error: --password required when receiving files.")
@@ -128,19 +125,19 @@ async def main():
WS_RELAY_SERVER = args.server
server_part = f'--server {WS_RELAY_SERVER}'
if args.version:
print(f"{sys.argv[0]} ver {VERSION_NUMBER}")
print(f"{SCRIPT_BASENAME} ver {VERSION_NUMBER}")
sys.exit(0)
k = derive_key_from_password(password)
peer_group_id = get_peer_group_id(password)
if role == 'send':
print('Run the following command on the remote party:\n\n', send_part, server_part, passwd_part, "\n")
print('Run the following command on the remote party:\n\n', send_part, passwd_part, server_part, "\n")
filename = os.path.basename(file_path)
file_size = os.path.getsize(file_path)
chunk_id = 0
chunk_size = 1024 * 512
number_of_chunks = round(file_size / chunk_size)
number_of_chunks = math.ceil(file_size / chunk_size)
WS_RELAY_SERVER = WS_RELAY_SERVER.replace('http', 'ws', 1)
async with websockets.connect(WS_RELAY_SERVER) as ws:
msgtype = "announce"
@@ -150,17 +147,19 @@ async def main():
message = json.loads(message)
if message["msgtype"] == "announce" and message["peer_group_id"] == peer_group_id:
break
bar = Bar('Transferring', max=number_of_chunks, suffix='%(percent).1f%% complete - %(eta_td)s remaining')
bar = Bar('Transferring', max=number_of_chunks, check_tty=False, suffix='%(percent).1f%% complete - %(eta_td)s remaining')
msgtype = "data"
async for chunk in read_chunks(file_path, chunk_size):
msg = (msgtype, peer_group_id, role, filename, chunk_size, chunk_id, number_of_chunks, chunk)
await send_encrypted_msg(ws, k, msg)
await asyncio.sleep(0.05)
chunk_id += 1
if chunk_id > number_of_chunks:
break
proceed = await ws.recv()
bar.next()
chunk_id += 1
bar.suffix = '%(percent).1f%% done'
print("\n")
bar.suffix = '%(percent).1f%% complete'
bar.update()
print("")
if role =='receive':
async with websockets.connect(WS_RELAY_SERVER) as ws:
@@ -168,16 +167,15 @@ async def main():
await send_encrypted_msg(ws, k, (msgtype, peer_group_id, role, "", "", "", "", ""))
bar = None
f = None
i = 1
while True:
message = await ws.recv()
message = json.loads(message)
payload = message["payload"]
msg = pickle.loads(decrypt_chunk(k, payload))
chunk_id = msg["chunk_id"]
number_of_chunks = msg["number_of_chunks"]
if bar is None:
filename = msg["filename"]
number_of_chunks = msg["number_of_chunks"]
bar = Bar('Receiving', max=number_of_chunks, suffix='%(percent).1f%% complete - %(eta_td)s remaining')
bar = Bar('Receiving', max=number_of_chunks, check_tty=False, suffix='%(percent).1f%% complete - %(eta_td)s remaining')
if f is None:
f = open(msg["filename"], "wb")
f.write(msg["chunk"])
@@ -185,14 +183,17 @@ async def main():
f.write(msg["chunk"])
msgtype = "proceed"
await send_encrypted_msg(ws, k, (msgtype, peer_group_id, role, "", "", "", "", "")) # request the next chunk from sender
i += 1
bar.suffix = "%(percent).1f%% complete - %(eta_td)s remaining"
bar.next()
if i > msg["number_of_chunks"]:
if chunk_id >= number_of_chunks-1:
# This is the last chunk, exit
f.close()
bar.suffix = '%(percent).1f%% complete'
bar.update()
f.close()
print("\n")
bar.finish()
break
asyncio.run(main())

View File

@@ -0,0 +1,6 @@
asyncio==3.4.3
cffi==1.15.1
cryptography==40.0.2
pycparser==2.21
pyjson==1.3.0
websockets==11.0.2