diff --git a/.gitignore b/.gitignore index 5b13d1c..95fe6f5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ venv /SyncPlay.egg-info /build +/cert /dist /syncplay v* /syncplay_v* @@ -13,4 +14,4 @@ dist.7z .* !.travis.yml !.appveyor.yml -__pycache__ \ No newline at end of file +__pycache__ diff --git a/server.crt b/server.crt deleted file mode 100644 index d93e114..0000000 --- a/server.crt +++ /dev/null @@ -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----- diff --git a/server.pem b/server.pem deleted file mode 100644 index 2b12284..0000000 --- a/server.pem +++ /dev/null @@ -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----- diff --git a/syncplay/client.py b/syncplay/client.py index 99cbd18..6f5aa9a 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -108,6 +108,8 @@ class SyncplayClient(object): self._warnings = self._WarningManager(self._player, self.userlist, self.ui, self) self.fileSwitch = FileSwitchManager(self) self.playlist = SyncplayPlaylist(self) + + self._serverSupportsTLS = True if constants.LIST_RELATIVE_CONFIGS and 'loadedRelativePaths' in self._config and self._config['loadedRelativePaths']: paths = "; ".join(self._config['loadedRelativePaths']) @@ -704,11 +706,11 @@ class SyncplayClient(object): if '[' in host: host = host.strip('[]') 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()) - self._wrapped = HostnameEndpoint(reactor, host, port) - self._contextFactory = optionsForClientTLS(hostname=host, trustRoot=trust_root) - self._endpoint = wrapClientTLS(self._contextFactory, self._wrapped) + self._endpoint = HostnameEndpoint(reactor, host, port) + self.protocolFactory.options = optionsForClientTLS(hostname=host, trustRoot = trust_root) + def retry(retries): self._lastGlobalUpdate = None diff --git a/syncplay/protocols.py b/syncplay/protocols.py index c4394ac..938a174 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -27,6 +27,8 @@ class JSONCommandProtocol(LineReceiver): self.handleError(message[1]) elif command == "Chat": self.handleChat(message[1]) + elif command == "TLS": + self.handleTLS(message[1]) else: self.dropWithError(getMessage("unknown-command-server-error").format(message[1])) # TODO: log, not drop @@ -72,7 +74,11 @@ class SyncClientProtocol(JSONCommandProtocol): def connectionMade(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): self._client.destroyProtocol() @@ -296,11 +302,24 @@ class SyncClientProtocol(JSONCommandProtocol): }) 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): 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): def __init__(self, factory): @@ -602,6 +621,15 @@ class SyncServerProtocol(JSONCommandProtocol): def sendError(self, 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): diff --git a/syncplayServer.py b/syncplayServer.py index c8b9b36..0be1618 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -15,14 +15,14 @@ except AttributeError: from OpenSSL import crypto 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 -with open('server.pem') as f: +with open('cert/server.pem') as f: certData = f.read() -certificate = ssl.PrivateCertificate.loadPEM(certData).options() +cert = ssl.PrivateCertificate.loadPEM(certData).options() if __name__ == '__main__': argsGetter = ConfigurationGetter() @@ -39,8 +39,9 @@ if __name__ == '__main__': args.max_username_length, 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) - endpoint6 = SSL4ServerEndpoint(reactor, int(args.port), certificate, interface='::') + endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) endpoint6.listen(factory) reactor.run()