From d131688794c53ee8de2a8a44862147f9a7277516 Mon Sep 17 00:00:00 2001 From: Davte Date: Fri, 17 Apr 2020 21:39:36 +0200 Subject: [PATCH] Handle SSL exceptions --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++ filebridging/__init__.py | 2 +- filebridging/client.py | 36 ++++++++++++++++++++----------- filebridging/server.py | 6 ++++-- 4 files changed, 75 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index fff1643..0eb0e15 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Share files via a bridge server using TCP over SSL and end-to-end encryption. ## Requirements Python3.8+ is needed for this package. +You may find it [here](https://www.python.org/downloads/). + +OpenSSL 1.1.1+ is required as well to handle SSL connection and end-to-end cryptography. +On Windows, installing [git for Windows](https://gitforwindows.org/) will install OpenSSL as well. ## Usage If you need a virtual environment, create it. @@ -29,6 +33,7 @@ python -m filebridging.client --help python -m filebridging.client r --host localhost --port 5000 --certificate ~/.ssh/server.crt --token 12345678 --password supersecretpasswordhere --path ~/Downloads ``` + * Client-client example ```bash # 2 distinct tabs @@ -37,6 +42,7 @@ python -m filebridging.client --help ``` The receiver client may be standalone as well: just add the `--key` parameter (for SSL-secured sessions) and the `--standalone` flag. + * Configuration file example ```python #!/bin/python @@ -51,3 +57,43 @@ python -m filebridging.client --help token = 'sessiontok' file_path = '.' ``` + +## Generating SSL certificates + + +Store configuration in file `mycert.csr.cnf` and run the following command to generate a self-signed SSL certificate. +```bash +openssl req -newkey rsa:2048 -nodes -keyout ./mycert.key \ + -x509 -days 365 -out ./mycert.crt \ + -config <( cat mycert.csr.cnf ) +``` + + +**mycert.csr.cnf** +```text +[req] +default_bits = 2048 +prompt = no +default_md = sha256 +distinguished_name = dn +req_extensions = v3_req +subjectAltName = @alt_names + +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[dn] +C=US +ST=YourState +L=YourTown +O=FileBridging +OU=filebridging +emailAddress=filebridging@yourdomain.com +CN = yourdomain.com + +[ alt_names ] +DNS.1 = yourdomain.com +DNS.2 = 1.111.111.11 +``` \ No newline at end of file diff --git a/filebridging/__init__.py b/filebridging/__init__.py index 0d888dd..b69174c 100644 --- a/filebridging/__init__.py +++ b/filebridging/__init__.py @@ -13,6 +13,6 @@ __author__ = "Davide Testa" __email__ = "davide@davte.it" __credits__ = [] __license__ = "GNU General Public License v3.0" -__version__ = "0.0.5" +__version__ = "0.0.6" __maintainer__ = "Davide Testa" __contact__ = "t.me/davte" diff --git a/filebridging/client.py b/filebridging/client.py index 3e29a21..23c3284 100644 --- a/filebridging/client.py +++ b/filebridging/client.py @@ -40,25 +40,27 @@ class Client: buffer_length_limit=10 ** 4, file_path=None, password=None, - token=None): + token=None, + ssl_handshake_timeout=None): self._host = host self._port = port self._ssl_context = ssl_context self._action = action self._standalone = standalone - self._stopping = False - self._reader = None - self._writer = None - # Shared queue of bytes - self.buffer = collections.deque() + self._file_path = file_path + self._password = password + self._token = token + self._ssl_handshake_timeout = ssl_handshake_timeout # How many bytes per chunk self._buffer_chunk_size = buffer_chunk_size # How many chunks in buffer self._buffer_length_limit = buffer_length_limit - self._file_path = file_path + # Shared queue of bytes + self.buffer = collections.deque() self._working = False - self._token = token - self._password = password + self._stopping = False + self._reader = None + self._writer = None self._encryption_complete = False self._file_name = None self._file_size = None @@ -138,6 +140,16 @@ class Client: def set_ssl_context(self, ssl_context: ssl.SSLContext): self._ssl_context = ssl_context + @property + def ssl_handshake_timeout(self) -> Union[int, None]: + """Return SSL handshake timeout. + + If SSL context is not set, return None. + Otherwise, return seconds to wait before considering handshake failed. + """ + if self.ssl_context: + return self._ssl_handshake_timeout + @property def token(self): """Session token. @@ -199,13 +211,13 @@ class Client: host=self.host, port=self.port, ssl=self.ssl_context, - ssl_handshake_timeout=5 + ssl_handshake_timeout=self.ssl_handshake_timeout ) except (ConnectionRefusedError, ConnectionResetError, ConnectionAbortedError) as exception: logging.error(f"Connection error: {exception}") return - except ssl.SSLCertVerificationError as exception: + except ssl.SSLError as exception: logging.error(f"SSL error: {exception}") return await self.connect(reader=reader, writer=writer) @@ -276,7 +288,7 @@ class Client: while 1: server_hello = await self.reader.readline() if not server_hello: - logging.error("Server disconnected.") + logging.error("Server refused connection.") return server_hello = server_hello.decode('utf-8').strip('\n').split('|') if self.action == 'receive' and server_hello[0] == 's': diff --git a/filebridging/server.py b/filebridging/server.py index dc34244..fc10c64 100644 --- a/filebridging/server.py +++ b/filebridging/server.py @@ -237,8 +237,10 @@ class Server: return def disconnect(self, connection_token: str) -> None: - del self.buffers[connection_token] - del self.connections[connection_token] + if connection_token in self.buffers: + del self.buffers[connection_token] + if connection_token in self.connections: + del self.connections[connection_token] def run(self): loop = asyncio.get_event_loop()