startTLS: enabled on server and client, TCP stays as default

This commit is contained in:
Alberto Sottile 2019-02-04 15:34:35 +01:00
parent 0890db8364
commit 58ccca5766
6 changed files with 44 additions and 77 deletions

3
.gitignore vendored
View File

@ -5,6 +5,7 @@ venv
/SyncPlay.egg-info /SyncPlay.egg-info
/build /build
/cert
/dist /dist
/syncplay v* /syncplay v*
/syncplay_v* /syncplay_v*
@ -13,4 +14,4 @@ dist.7z
.* .*
!.travis.yml !.travis.yml
!.appveyor.yml !.appveyor.yml
__pycache__ __pycache__

View File

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDDjCCAfYCCQCi9L0SyIknmTANBgkqhkiG9w0BAQsFADBJMQswCQYDVQQGEwJQ
TDETMBEGA1UECAwKU29tZS1TdGF0ZTERMA8GA1UECgwIU3luY3BsYXkxEjAQBgNV
BAMMCWxvY2FsaG9zdDAeFw0xOTAyMDMxOTA0MTFaFw0yMTExMjMxOTA0MTFaMEkx
CzAJBgNVBAYTAlBMMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQKDAhTeW5j
cGxheTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxdnxzQ2ddPWLBHzHRlc2uGCML6MtPdTW5mOzQbj+jxHqhcJszIo4
5/ZoqCX11tgQ69cJphTmg0Pjd89xTiqQBOf/qD3kSycds6j26H4oiIsuvOCaa5LN
lE5jAGZQWWRrnAqXJgbnQZgW+2a8bhJGCospRRIK+h48FDazOwEoNHjmPC7DHWrt
HlU/BbuzGPLhekKzR7LTD8/32+4g1e2LMMEv22LYrN2cRpZqb8wXYgjsMRc7aqAA
NS7x0tspBhBfCigDLd4i+SuKPGkyI118uss7eKx7MDgmQp1vUiTOkKphgT1S/a7m
4EJ3xO+75WjIQ4bJPmLbdLWMKOXi2t7PVQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB
AQCADrdRY64VpPeM8c9MCn7jXDR0B7xjwoQkiyFvISCRiWZwX8QE2atjZ6jGnuB3
LBattjmjHcCNwLEvc5dZT0ioeiAvNdEbcMitYS7d2x3QIQ2n2zpSMp3speAv7mdG
YkC/oE7bbORBksjsxLCAOPOrDYijyTwDN0oTkDcuhkdztbO5Frp/5vA/i/U29Sxv
ebbJ0JXl8LJKzJqslyRv6sVxsNFH0foX7rwbXzciO4TscHHrFDZwNBhjWYPITJ7J
BBgr8Cs9ZbKFQ7+o1bUob7B8n2tKtVxAHfTQfBe68ZlcdTHfFririLjhRDVXSAFw
8ZZzQoma7VJ/1l8jcoWhdfOe
-----END CERTIFICATE-----

View File

@ -1,46 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDDjCCAfYCCQCi9L0SyIknmTANBgkqhkiG9w0BAQsFADBJMQswCQYDVQQGEwJQ
TDETMBEGA1UECAwKU29tZS1TdGF0ZTERMA8GA1UECgwIU3luY3BsYXkxEjAQBgNV
BAMMCWxvY2FsaG9zdDAeFw0xOTAyMDMxOTA0MTFaFw0yMTExMjMxOTA0MTFaMEkx
CzAJBgNVBAYTAlBMMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQKDAhTeW5j
cGxheTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxdnxzQ2ddPWLBHzHRlc2uGCML6MtPdTW5mOzQbj+jxHqhcJszIo4
5/ZoqCX11tgQ69cJphTmg0Pjd89xTiqQBOf/qD3kSycds6j26H4oiIsuvOCaa5LN
lE5jAGZQWWRrnAqXJgbnQZgW+2a8bhJGCospRRIK+h48FDazOwEoNHjmPC7DHWrt
HlU/BbuzGPLhekKzR7LTD8/32+4g1e2LMMEv22LYrN2cRpZqb8wXYgjsMRc7aqAA
NS7x0tspBhBfCigDLd4i+SuKPGkyI118uss7eKx7MDgmQp1vUiTOkKphgT1S/a7m
4EJ3xO+75WjIQ4bJPmLbdLWMKOXi2t7PVQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB
AQCADrdRY64VpPeM8c9MCn7jXDR0B7xjwoQkiyFvISCRiWZwX8QE2atjZ6jGnuB3
LBattjmjHcCNwLEvc5dZT0ioeiAvNdEbcMitYS7d2x3QIQ2n2zpSMp3speAv7mdG
YkC/oE7bbORBksjsxLCAOPOrDYijyTwDN0oTkDcuhkdztbO5Frp/5vA/i/U29Sxv
ebbJ0JXl8LJKzJqslyRv6sVxsNFH0foX7rwbXzciO4TscHHrFDZwNBhjWYPITJ7J
BBgr8Cs9ZbKFQ7+o1bUob7B8n2tKtVxAHfTQfBe68ZlcdTHfFririLjhRDVXSAFw
8ZZzQoma7VJ/1l8jcoWhdfOe
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxdnxzQ2ddPWLBHzHRlc2uGCML6MtPdTW5mOzQbj+jxHqhcJs
zIo45/ZoqCX11tgQ69cJphTmg0Pjd89xTiqQBOf/qD3kSycds6j26H4oiIsuvOCa
a5LNlE5jAGZQWWRrnAqXJgbnQZgW+2a8bhJGCospRRIK+h48FDazOwEoNHjmPC7D
HWrtHlU/BbuzGPLhekKzR7LTD8/32+4g1e2LMMEv22LYrN2cRpZqb8wXYgjsMRc7
aqAANS7x0tspBhBfCigDLd4i+SuKPGkyI118uss7eKx7MDgmQp1vUiTOkKphgT1S
/a7m4EJ3xO+75WjIQ4bJPmLbdLWMKOXi2t7PVQIDAQABAoIBAFz8ZlE58eOzNyff
wQRFHvmenqQQ68Vgj7Nt7iSYXkM9Z1yAGQQ0fjQ+scc9OAJGQAWnZeiBcCkHMhPw
Ec9r343+v0AB/pZ3htUWNxzjlgc+arPoV4rxTt9By/O3IlIxCQYoUAtWOT+xzDNR
gIO24OY5qybEKRaOOSxC3Q+BJrUpvIMEf93w7YQQ5SqulcmSYsIK25t+ACdXAlkX
KpvszojDU+qfUH7Uz8/yvFcbZ8LeDdrv1Wcedx15VUIcrU+D9DBYK1NOFW2vuPJT
DJZOQFXMTxg6kSED0O8a4Z3VhaPEBiGdN4KOIkC1tUj8i+BM441Jg4nme8OLX/Vm
NGftpm0CgYEA93w8P6gp1wnO7R56FdRoL9nhfgoMQroqNMqHdoGlWPFZNDqtvbFW
vjhg1v98T8mBvQMsfLruUuDDykacOdDyHRAbPQ+gICUjRXDFgu+GhHcIRn3dcZli
cSRka/JsuqCuTFnIoa981IYEllAQTZ+3w+qR8d+BkoR7K55v5aRxNCsCgYEAzKiM
8u1W3d6/E6EgaiSVOuCwOB85zbQH1t1s6wQoD34u+CEKyW3/WCkZuNMlE6J8luwt
HfXilFq9ZfAdyxN/DhHIygulbIbGwtzYFI6rEmU3zL1bX27ZWStjuDUyWf3zX4T2
9vlBf9CwJWeotaKl+Or2aeGAiNP5830WIpikyn8CgYEA8AjjNqqXyiWNOZaxurKF
SsP8XQ7JzX5aqVE2Cc683INZjbrMAIwcIer0ohKyM4CyAO0vHNsBhAjUXUAXDkyG
R4HzqUmaeRMMHrG+H7zJr3jz4cr6GNA4FpzBeaFrq6dk5lC+s3NNk6NYl6GX7nHW
/oJogzvQpJcyD6Bfz0+rLHkCgYBr0uFvm1uIyTIiRWGuileVDYvKBamOlqsKqN4Z
c7cncnOMhtwIA8vjxsOmfJesII9DdGrQvhsBzky6yCbqNvtZjkUbLceZxegyAehV
7FR0/J7JX3okbWJVeGaxRlWg1ArE6Gi09d1sWaZ0Doj0KR0IZ8IrRoNRk1y8y8o9
r+4iQQKBgDyuv6nz4xV3GrW6ohVcCRg8R4yZmb65A4guxZIwMh3nbf+rHWO3RTxd
LMiCLSW3Py2xsxiMa5ICEm75Hke8+KHwRBL7SK1eqaFrdhzvTALQp0IfBu1/t7bR
5bJVa6EL55eNA0LcOZqX36rDYzpzZjaf46XNzshZ/p0X7NryEhNl
-----END RSA PRIVATE KEY-----

View File

@ -108,6 +108,8 @@ class SyncplayClient(object):
self._warnings = self._WarningManager(self._player, self.userlist, self.ui, self) self._warnings = self._WarningManager(self._player, self.userlist, self.ui, self)
self.fileSwitch = FileSwitchManager(self) self.fileSwitch = FileSwitchManager(self)
self.playlist = SyncplayPlaylist(self) self.playlist = SyncplayPlaylist(self)
self._serverSupportsTLS = True
if constants.LIST_RELATIVE_CONFIGS and 'loadedRelativePaths' in self._config and self._config['loadedRelativePaths']: if constants.LIST_RELATIVE_CONFIGS and 'loadedRelativePaths' in self._config and self._config['loadedRelativePaths']:
paths = "; ".join(self._config['loadedRelativePaths']) paths = "; ".join(self._config['loadedRelativePaths'])
@ -704,11 +706,11 @@ class SyncplayClient(object):
if '[' in host: if '[' in host:
host = host.strip('[]') host = host.strip('[]')
port = int(port) port = int(port)
with open('server.crt') as cert_file: with open('cert/server.crt') as cert_file:
trust_root = Certificate.loadPEM(cert_file.read()) trust_root = Certificate.loadPEM(cert_file.read())
self._wrapped = HostnameEndpoint(reactor, host, port) self._endpoint = HostnameEndpoint(reactor, host, port)
self._contextFactory = optionsForClientTLS(hostname=host, trustRoot=trust_root) self.protocolFactory.options = optionsForClientTLS(hostname=host, trustRoot = trust_root)
self._endpoint = wrapClientTLS(self._contextFactory, self._wrapped)
def retry(retries): def retry(retries):
self._lastGlobalUpdate = None self._lastGlobalUpdate = None

View File

@ -27,6 +27,8 @@ class JSONCommandProtocol(LineReceiver):
self.handleError(message[1]) self.handleError(message[1])
elif command == "Chat": elif command == "Chat":
self.handleChat(message[1]) self.handleChat(message[1])
elif command == "TLS":
self.handleTLS(message[1])
else: else:
self.dropWithError(getMessage("unknown-command-server-error").format(message[1])) # TODO: log, not drop self.dropWithError(getMessage("unknown-command-server-error").format(message[1])) # TODO: log, not drop
@ -72,7 +74,11 @@ class SyncClientProtocol(JSONCommandProtocol):
def connectionMade(self): def connectionMade(self):
self._client.initProtocol(self) self._client.initProtocol(self)
self.sendHello() if self._client._serverSupportsTLS:
self.sendTLS({"startTLS": "send"})
self._client.ui.showMessage("Attempting secure connection")
else:
self.sendHello()
def connectionLost(self, reason): def connectionLost(self, reason):
self._client.destroyProtocol() self._client.destroyProtocol()
@ -296,11 +302,24 @@ class SyncClientProtocol(JSONCommandProtocol):
}) })
def handleError(self, error): def handleError(self, error):
self.dropWithError(error["message"]) if "startTLS" in error["message"] and not self.logged:
self._client.ui.showErrorMessage("This server does not support TLS")
self._client._serverSupportsTLS = False
else:
self.dropWithError(error["message"])
def sendError(self, message): def sendError(self, message):
self.sendMessage({"Error": {"message": message}}) self.sendMessage({"Error": {"message": message}})
def sendTLS(self, message):
self.sendMessage({"TLS": message})
def handleTLS(self, message):
answer = message["startTLS"] if "startTLS" in message else None
if "true" in answer and not self.logged:
self.transport.startTLS(self._client.protocolFactory.options)
self._client.ui.showMessage("Secure connection established")
self.sendHello()
class SyncServerProtocol(JSONCommandProtocol): class SyncServerProtocol(JSONCommandProtocol):
def __init__(self, factory): def __init__(self, factory):
@ -602,6 +621,15 @@ class SyncServerProtocol(JSONCommandProtocol):
def sendError(self, message): def sendError(self, message):
self.sendMessage({"Error": {"message": message}}) self.sendMessage({"Error": {"message": message}})
def sendTLS(self, message):
self.sendMessage({"TLS": message})
def handleTLS(self, message):
inquiry = message["startTLS"] if "startTLS" in message else None
if "send" in inquiry and not self.isLogged():
self.sendTLS({"startTLS": "true"})
self.transport.startTLS(self._factory.options)
class PingService(object): class PingService(object):

View File

@ -15,14 +15,14 @@ except AttributeError:
from OpenSSL import crypto from OpenSSL import crypto
from twisted.internet import reactor, ssl from twisted.internet import reactor, ssl
from twisted.internet.endpoints import SSL4ServerEndpoint from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint
from syncplay.server import SyncFactory, ConfigurationGetter from syncplay.server import SyncFactory, ConfigurationGetter
with open('server.pem') as f: with open('cert/server.pem') as f:
certData = f.read() certData = f.read()
certificate = ssl.PrivateCertificate.loadPEM(certData).options() cert = ssl.PrivateCertificate.loadPEM(certData).options()
if __name__ == '__main__': if __name__ == '__main__':
argsGetter = ConfigurationGetter() argsGetter = ConfigurationGetter()
@ -39,8 +39,9 @@ if __name__ == '__main__':
args.max_username_length, args.max_username_length,
args.stats_db_file args.stats_db_file
) )
endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), certificate, interface='0.0.0.0') factory.options = cert
endpoint4 = TCP4ServerEndpoint(reactor, int(args.port))
endpoint4.listen(factory) endpoint4.listen(factory)
endpoint6 = SSL4ServerEndpoint(reactor, int(args.port), certificate, interface='::') endpoint6 = TCP6ServerEndpoint(reactor, int(args.port))
endpoint6.listen(factory) endpoint6.listen(factory)
reactor.run() reactor.run()