Skip to content

Commit

Permalink
Add FTPS support for server
Browse files Browse the repository at this point in the history
  • Loading branch information
oleksandr-kuzmenko committed Oct 11, 2018
1 parent a3f13b7 commit e3b5138
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 53 deletions.
10 changes: 9 additions & 1 deletion aioftp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ async def start(self, host=None, port=0, **kwargs):
host,
port,
loop=self.loop,
ssl=self.ssl,
**self._start_server_extra_arguments,
)
for sock in self.server.sockets:
Expand Down Expand Up @@ -776,6 +777,11 @@ class Server(AbstractServer):
:param encoding: encoding to use for convertion strings to bytes
:type encoding: :py:class:`str`
:param ssl: can be set to an :py:class:`ssl.SSLContext` instance
to enable TLS over the accepted connections.
Please look :py:meth:`asyncio.loop.create_server` docs.
:type ssl: :py:class:`ssl.SSLContext`
"""
path_facts = (
("st_size", "Size"),
Expand All @@ -799,7 +805,8 @@ def __init__(self,
read_speed_limit_per_connection=None,
write_speed_limit_per_connection=None,
data_ports=None,
encoding="utf-8"):
encoding="utf-8",
ssl=None):
self.loop = loop or asyncio.get_event_loop()
self.block_size = block_size
self.socket_timeout = socket_timeout
Expand Down Expand Up @@ -832,6 +839,7 @@ def __init__(self,
)
self.throttle_per_user = {}
self.encoding = encoding
self.ssl = ssl

async def dispatcher(self, reader, writer):
host, port, *_ = writer.transport.get_extra_info("peername", ("", ""))
Expand Down
32 changes: 32 additions & 0 deletions certificates/Certificate.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFijCCA3KgAwIBAgIJAOZ4cuVfwLdMMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV
BAYTAkpQMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQKDAhhaW8tbGliczEP
MA0GA1UECwwGYWlvZnRwMRIwEAYDVQQDDAkxMjcuMC4wLjEwHhcNMTgxMDExMjIz
MjEyWhcNNDYwMjI2MjIzMjEyWjBaMQswCQYDVQQGEwJKUDETMBEGA1UECAwKU29t
ZS1TdGF0ZTERMA8GA1UECgwIYWlvLWxpYnMxDzANBgNVBAsMBmFpb2Z0cDESMBAG
A1UEAwwJMTI3LjAuMC4xMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
wfjKC7kHdBLoCp2kTrk7GDg+4X2hF5wUH34fw9FqpuR2Z8O7GxX6AiTD8XYhFqUf
WOcjTgXI45SjpGT2enZlmqu5v37tnl/UQkui6EjLlieGZVU36Tf5KZyXzoHv+fQa
pkPBQ5KNJ7ikMX7Dbc8mCY1Nf+MjIMXcfGpZb1qNksx/I+d1xfQ46HX8I2ZeTWXi
tWXTAWeB46pyhGwufAKWbzyH17zRtJsrrWhcia2IND50rNWVIs8vNs9VWcfd9N3J
eXzNLx9w/CmrciboobqFcjSf+R+I+Aa/X/bKylG95u7Bb+bbaysFQE93CKoGSuuY
grLkqaK2ohyZrAXNmu/sFHrwTbZdb9Ppl3d9n7FjhnII5Mjbmc1EqvSASUXJxPRY
I++pNzhPUMRfeNbYvhRtOCZQneKz7JeMLdNMSFaqaMcRk5RrBRh7ry3zNCNOoi7d
oQey0u25/kW73Wxh8zSwdINKi72GjTRV4oj2PHqzurF3XTFKkuWfV402o5n2P4wc
8u1mEPbWfkoOI1D3TdJh61204dG1o/OwZd53fErKQb0Kn3/YL+AmU4tJVYZRjLm3
lOmypxiRxxP+7lQgdXIvx6EuQunL4ir9+wIu++d9Tv4qkLKtuRTWmcYs3yUw1jBt
ZwsMBQC1bQugxs/YExzlve206vmBJmr04Ou5IoozmxcCAwEAAaNTMFEwHQYDVR0O
BBYEFMtplRrnxT+G41SMSj91rjBdLKEnMB8GA1UdIwQYMBaAFMtplRrnxT+G41SM
Sj91rjBdLKEnMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAGtx
YnDm0DA73qjtSHjbpj9HAjj7i8/ZKrXa++b2/8KK7U26Ijipzi6N99NxkjhItiFY
o6juS5RKLLBJO1I+5iLtHNS01y4BSEUtQGzZCuhcxRyPGkn0QlQ/8nbrHKrds5tH
CDtpA13cBqBlpJgajbxYftKueFSod3PUGQVwFWwFahq9UwbIBqYCD1iySIlcTlA4
5SoUCWayibsK5JptbC1bs8AmBt2JsWesr2cpGsfzQ+tyz/2xMsmkL2JTtncxP41A
QTAb/d6Zwws5Zpe0OHiRVuE9OXqbTxdktHMyR+slvHSqwpUAYFvEyWdpW/fVZJLa
5vggyn0Roun3kC1TkQZbUJoJ2KUbz6egCJh7YjU/U5yEQO+iZdLYA7pwuigiQpGQ
5cq2rwAuswel2LLi1ZXZYB6juefTGr86XEsakkl/qlivYvy60u3Cp+GoUgF4wd3i
HbVDNlaen/zL4aol3hrfewytJuMMdjkt9U+vlsluL8fP9EnmHZyOGytf9oRx4bbr
CpKzGf2A35YIgXrLEkq9O+fOLNJ7sKVg7ycs77MaSDH1A24qDkLf9LhI9RtGj0zI
9YYmwyvQ/wXnABsu0SYq0/K+ATgMQxDvxHfUAyvVO+jnLjeIZp+0L3FyJ4JgW7W1
p7s748GJ119HBK6o6lz9W4Oj2++9un3/7DAr6CQF
-----END CERTIFICATE-----
52 changes: 52 additions & 0 deletions certificates/Key.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
u7OX/lMJSDyZlEkj+HpqrEv1Fq5wmw==
-----END PRIVATE KEY-----
30 changes: 26 additions & 4 deletions tests/common.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import asyncio
import functools
import pathlib
import logging
import pathlib
import shutil
import socket
import ssl
import sys

import nose

import aioftp


cert_path = str(pathlib.Path("certificates") / "Certificate.crt")
key_path = str(pathlib.Path("certificates") / "Key.key")

ssl_server = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_server.load_cert_chain(cert_path, key_path)

ssl_client = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ssl_client.load_verify_locations(cert_path)

PORT = 8888


Expand All @@ -23,16 +34,16 @@ def wrapper():
s_args, s_kwargs = server_args
c_args, c_kwargs = client_args

def run_in_loop(s_args, s_kwargs, c_args, c_kwargs):
def run_in_loop(s_args, s_kwargs, c_args, c_kwargs, s_ssl=None, c_ssl=None):
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)s] %(message)s",
datefmt="[%H:%M:%S]:",
)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)
server = aioftp.Server(*s_args, loop=loop, **s_kwargs)
client = aioftp.Client(*c_args, loop=loop, **c_kwargs)
server = aioftp.Server(*s_args, loop=loop, ssl=s_ssl, **s_kwargs)
client = aioftp.Client(*c_args, loop=loop, ssl=c_ssl, **c_kwargs)
try:
loop.run_until_complete(f(loop, client, server))
finally:
Expand All @@ -42,12 +53,23 @@ def run_in_loop(s_args, s_kwargs, c_args, c_kwargs):
client.close()
loop.close()

# SSL monkey patching for tests with self signed certificates.
# Works only with Python older than 3.7 version.
old_match_hostname = ssl.match_hostname
ssl.match_hostname = lambda cert, hostname: True

if "path_io_factory" not in s_kwargs:
for factory in (aioftp.PathIO, aioftp.AsyncPathIO):
s_kwargs["path_io_factory"] = factory
run_in_loop(s_args, s_kwargs, c_args, c_kwargs)
if sys.version_info.minor < 7:
run_in_loop(s_args, s_kwargs, c_args, c_kwargs, ssl_server, ssl_client)
else:
run_in_loop(s_args, s_kwargs, c_args, c_kwargs)
if sys.version_info.minor < 7:
run_in_loop(s_args, s_kwargs, c_args, c_kwargs, ssl_server, ssl_client)

ssl.match_hostname = old_match_hostname

return wrapper

Expand Down
4 changes: 2 additions & 2 deletions tests/test-connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ async def test_pasv_connection_ports_not_added(loop, client, server):
@with_connection
async def test_pasv_connection_ports(loop, client, server):

clients = [aioftp.Client(loop=loop) for _ in range(2)]
clients = [aioftp.Client(loop=loop, ssl=client.ssl) for _ in range(2)]
expected_data_ports = [30000, 30001]

for i, client in enumerate(clients):
Expand Down Expand Up @@ -128,7 +128,7 @@ async def test_data_ports_remains_empty(loop, client, server):
@with_connection
async def test_pasv_connection_port_reused(loop, client, server):

clients = [aioftp.Client(loop=loop) for _ in range(2)]
clients = [aioftp.Client(loop=loop, ssl=client.ssl) for _ in range(2)]

for client in clients:

Expand Down
12 changes: 6 additions & 6 deletions tests/test-maximum-connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@with_connection
async def test_multiply_connections_no_limits(loop, client, server):

clients = [aioftp.Client(loop=loop) for _ in range(4)]
clients = [aioftp.Client(loop=loop, ssl=client.ssl) for _ in range(4)]
for client in clients:

await client.connect("127.0.0.1", PORT)
Expand All @@ -23,7 +23,7 @@ async def test_multiply_connections_no_limits(loop, client, server):
@with_connection
async def test_multiply_connections_limited_error(loop, client, server):

clients = [aioftp.Client(loop=loop) for _ in range(5)]
clients = [aioftp.Client(loop=loop, ssl=client.ssl) for _ in range(5)]
for client in clients:

await client.connect("127.0.0.1", PORT)
Expand Down Expand Up @@ -53,7 +53,7 @@ async def test_multiply_user_commands(loop, client, server):
async def test_multiply_connections_with_user_limited_error(loop, client,
server):

clients = [aioftp.Client(loop=loop) for _ in range(5)]
clients = [aioftp.Client(loop=loop, ssl=client.ssl) for _ in range(5)]
for client in clients:

await client.connect("127.0.0.1", PORT)
Expand All @@ -69,7 +69,7 @@ async def test_multiply_connections_with_user_limited_error(loop, client,
@with_connection
async def test_multiply_connections_relogin_balanced(loop, client, server):

clients = [aioftp.Client(loop=loop) for _ in range(5)]
clients = [aioftp.Client(loop=loop, ssl=client.ssl) for _ in range(5)]
for client in clients[:-1]:

await client.connect("127.0.0.1", PORT)
Expand All @@ -90,7 +90,7 @@ async def test_multiply_connections_relogin_balanced(loop, client, server):
@expect_codes_in_exception("421")
async def test_multiply_connections_server_limit_error(loop, client, server):

clients = [aioftp.Client(loop=loop) for _ in range(5)]
clients = [aioftp.Client(loop=loop, ssl=client.ssl) for _ in range(5)]
for client in clients:

await client.connect("127.0.0.1", PORT)
Expand All @@ -107,7 +107,7 @@ async def test_multiply_connections_server_limit_error(loop, client, server):
async def test_multiply_connections_server_relogin_balanced(loop, client,
server):

clients = [aioftp.Client(loop=loop) for _ in range(5)]
clients = [aioftp.Client(loop=loop, ssl=client.ssl) for _ in range(5)]
for client in clients[:-1]:

await client.connect("127.0.0.1", PORT)
Expand Down
Loading

0 comments on commit e3b5138

Please sign in to comment.