From e35f34d3a91009bb7440bd1a776349b77681ad0e Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sun, 3 Feb 2019 16:30:20 +0100 Subject: [PATCH 01/62] Restore TCP4 and TCP6 server endpoints --- syncplayServer.py | 46 ++++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/syncplayServer.py b/syncplayServer.py index eaa9f34..0b27c47 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -13,38 +13,28 @@ except AttributeError: import warnings warnings.warn("You must run Syncplay with Python 3.4 or newer!") -from twisted.internet import reactor, tcp +from twisted.internet import reactor +from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint from syncplay.server import SyncFactory, ConfigurationGetter -class DualStackPort(tcp.Port): - - def __init__(self, port, factory, backlog=50, interface='', reactor=None): - tcp.Port.__init__(self, port, factory, backlog, interface, reactor) - - def createInternetSocket(self): - s = tcp.Port.createInternetSocket(self) - try: - s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) - except: - pass - return s - if __name__ == '__main__': argsGetter = ConfigurationGetter() args = argsGetter.getConfiguration() - dsp = DualStackPort(int(args.port), - SyncFactory( - args.port, - args.password, - args.motd_file, - args.isolate_rooms, - args.salt, - args.disable_ready, - args.disable_chat, - args.max_chat_message_length, - args.max_username_length, - args.stats_db_file), - interface='::') - dsp.startListening() + factory = SyncFactory( + args.port, + args.password, + args.motd_file, + args.isolate_rooms, + args.salt, + args.disable_ready, + args.disable_chat, + args.max_chat_message_length, + args.max_username_length, + args.stats_db_file + ) + endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) + endpoint4.listen(factory) + endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) + endpoint6.listen(factory) reactor.run() From 894f9ddee685d1cc114c5613531ec51fd1f01bb6 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sun, 3 Feb 2019 16:40:50 +0100 Subject: [PATCH 02/62] TLS: server listens on TLS via IPv4 --- server.crt | 26 ++++++++++++++++++++++++++ server.key | 27 +++++++++++++++++++++++++++ syncplayServer.py | 25 ++++++++++++++++++++----- 3 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 server.crt create mode 100644 server.key diff --git a/server.crt b/server.crt new file mode 100644 index 0000000..b302fd6 --- /dev/null +++ b/server.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEcjCCA1qgAwIBAgIJAJe1HOCC73ylMA0GCSqGSIb3DQEBCwUAMHExCzAJBgNV +BAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYDVQQKDAhT +eW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEWD2RldkBz +eW5jcGxheS5wbDAeFw0xOTAyMDMxNTM2MDdaFw0yMDAyMDMxNTM2MDdaMHExCzAJ +BgNVBAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYDVQQK +DAhTeW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEWD2Rl +dkBzeW5jcGxheS5wbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7S +L3L0Mhf+5pMnSRUGQnxN89o+7aKtQ7Lf/rIGrFFjLNOOcTeWuod/x/MjDw5sP7HP +xQFb0/pAdm7VS5L/GJpWBQiHvB3VndyZ3CazQRs2NOus9CLBtoMh480dz/oiqed+ +XxkhD3hVyRDSkqz+QPNwj5b64FhYo9ocnOZwKJ3KvWBPDPV1wzIiCBh7YB2V/78f +K4qHaX6QVUvruxwpHp/Iw+JMHACBTRND5gXGo7vb/6g8AnKmypy+nS0iFbNasq5p +G5UFFb6T9jcQ7OfHvPOa9b3wFD/R3bi+dOyboST/BUd5j1+sJhrvEGI+fK6zIA11 +uwOasVEekv5I/z2jtG8CAwEAAaOCAQswggEHMIGNBgNVHSMEgYUwgYKhdaRzMHEx +CzAJBgNVBAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYD +VQQKDAhTeW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEW +D2RldkBzeW5jcGxheS5wbIIJAJe1HOCC73ylMAkGA1UdEwQCMAAwCwYDVR0PBAQD +AgTwMF0GA1UdEQRWMFSCUjxzcGVjaWZ5LXRoZS1zYW1lLWNvbW1vbi1uYW1lLXRo +YXQteW91LXVzZWQtd2hpbGUtZ2VuZXJhdGluZy1jc3ItaW4tdGhlLWxhc3Qtc3Rl +cD4wDQYJKoZIhvcNAQELBQADggEBAI+jWXb3nRYS1aOUduiF7VAVIaouYRPgkdb8 +p/W0S36KZ7jJXIQurXxx2znjtdC60qwBjJSqLnI1D6lWP6SdX8LMCNmJjtO/dZaX +zoTDqBEcjXp7Up17Zi2liCtYNYAFDdEX9wWMhrg26gY/IccBwm2wKD+B6MfOBTJc +/DRrp4cf/XcUCoNRCmU6JDFABKc63fK2Tr3ClTrXq2qYTaR4xIVu6EUAJGE+zMja +rOT4vXzvAPPHCf03uYCm8jagoxX9Qi7NyAWnyJOgO0DNnhKyxe1MZEkQV1X8m1ko +BVdikLM5hUHIzI2+5Q7o4jr2CHb5oL5OF4PUypwfeRe+PzoI9qs= +-----END CERTIFICATE----- diff --git a/server.key b/server.key new file mode 100644 index 0000000..4b08fd3 --- /dev/null +++ b/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEAztIvcvQyF/7mkydJFQZCfE3z2j7toq1Dst/+sgasUWMs045x +N5a6h3/H8yMPDmw/sc/FAVvT+kB2btVLkv8YmlYFCIe8HdWd3JncJrNBGzY066z0 +IsG2gyHjzR3P+iKp535fGSEPeFXJENKSrP5A83CPlvrgWFij2hyc5nAoncq9YE8M +9XXDMiIIGHtgHZX/vx8riodpfpBVS+u7HCken8jD4kwcAIFNE0PmBcaju9v/qDwC +cqbKnL6dLSIVs1qyrmkblQUVvpP2NxDs58e885r1vfAUP9HduL507JuhJP8FR3mP +X6wmGu8QYj58rrMgDXW7A5qxUR6S/kj/PaO0bwIDAQABAoIBAQCdeVQ/hseNlbwi +V0tCAt4kn4YIqtltf9eIHx1il8okvgqnmlGCLfYgrZ6k9hDEvo9Q6A3sluq0JNkI +UseUST+GY/C5KgCGBtyH127U2K+99Gwe9jV9MVmyRt+YkhaFMleDt6jZDV8mFMtf +T7X2mjECKrZqEuM24WcA78BXhRan7RMl77ZQFrFNn+TccFItNUoeP75vD+5aLeH+ +ovWbMM3EaSFMPKoOZ3yJ78IEtD1B61DLOjZNlYsli7phmJR0veb8GHhllDovZ6ru +d5k56r5AqFLSV5Xp29Z0+8mbVOjlgDZ5EYYdyyo0KKX3sADtR2f2/ktIZuFPQFsC +nXbKnRVhAoGBAPeI9ft1B5htUdukb7IUcrdLoPv2cl9uw7e/kUemTO51Uk3QSnex +LUYe5JHoQDBdG7tN88xJLzp/ybH8+SublSrwnF7lqs+TRE3NGI05SIprpMIV2dAo +A+/6icg2jqeGt7ZZv2Ge9VzcxuxwmO5hkx51OlulBuEzdzGOfr/KdZ1jAoGBANXk +y4iQ145V1EkelwZZtMfZH8PZXp7ZVdF2vWG4HDV5frOwIanVtm5VEOW683gJsDnk +9+emRtzd0bm5wjy0K784VWzY/JODzxTgkaBgb1qxoHJv8xdzyo/F3n5WjalOnDQ5 +Y2uBDRj9tMKcZGX9yfnDik4ostT9KRUfy+xhXlCFAoGBAJhw2EvLfqn4aPkDA2aA +AUWwZavF0hnDdPSsHSQq3iXcSptxMusTSfZtAAZ7KUyfinyHPSUJzPQRzny2dhzM +68hN9X7boZ3D68SeEgimxm24bPa5zjHK8uHPQRrzvladOHYsPxg3dJODw13I2eW0 +YY3YU+AseVgnAbibMKOcY2JRAoGBAM4m91iHqYpBBRr4Rb1UJp5bkXcVAVJ+zGzX +Uc5mQaH0mH6dasSi+3K5wFB68IAoblZObfUx2Ki1jpbACAqz6Foj9Im1VTLkXmmT +zCfT6l9yRd8mMRjQfFI39qqzx1OANaNMEJujxTjqBhdv3oBCrDFzGtEb/sizu63l +eIk6i0l1AoGBAPRicQrL3fQ0Ig7O6Oc59Rs2I2FCioGTm4m6PMJzhTQQIkaBHxco +Vd8wWQCbZDdP/8o0uh/qvBey0fuGp9ff0k4rOUJtHTI0YlzdJzAEVyuPt+Z+heXc +KSsqIL/+WFL8pNO0wX0dycVEwSZnZ45wBskTGFZjv9c4Q0rdQBru9QWd +-----END RSA PRIVATE KEY----- diff --git a/syncplayServer.py b/syncplayServer.py index 0b27c47..e3eca96 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -13,11 +13,25 @@ except AttributeError: import warnings warnings.warn("You must run Syncplay with Python 3.4 or newer!") -from twisted.internet import reactor -from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint +from OpenSSL import crypto +from twisted.internet import reactor, ssl +from twisted.internet.endpoints import TCP4ServerEndpoint, SSL4ServerEndpoint, TCP6ServerEndpoint from syncplay.server import SyncFactory, ConfigurationGetter +with open('server.crt', 'r') as f: + cert_data = f.read() +with open('server.key', 'r') as f: + key_data = f.read() + +cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) +key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_data) +options = ssl.CertificateOptions( + privateKey=key, + certificate=cert, + acceptableProtocols=[b'h2'], +) + if __name__ == '__main__': argsGetter = ConfigurationGetter() args = argsGetter.getConfiguration() @@ -33,8 +47,9 @@ if __name__ == '__main__': args.max_username_length, args.stats_db_file ) - endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) + #endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) + endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), options) endpoint4.listen(factory) - endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) - endpoint6.listen(factory) + #endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) + #endpoint6.listen(factory) reactor.run() From 13fb63b12fc92eb5230a8d200844cb3e4755d531 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sun, 3 Feb 2019 21:19:10 +0100 Subject: [PATCH 03/62] TLS: client-server implementation with self-issued certs on IPv4 --- server.crt | 41 +++++++++++++++++------------------------ server.key | 27 --------------------------- server.pem | 46 ++++++++++++++++++++++++++++++++++++++++++++++ syncplay/client.py | 11 ++++++++--- syncplayServer.py | 19 +++++-------------- 5 files changed, 76 insertions(+), 68 deletions(-) delete mode 100644 server.key create mode 100644 server.pem diff --git a/server.crt b/server.crt index b302fd6..d93e114 100644 --- a/server.crt +++ b/server.crt @@ -1,26 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIEcjCCA1qgAwIBAgIJAJe1HOCC73ylMA0GCSqGSIb3DQEBCwUAMHExCzAJBgNV -BAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYDVQQKDAhT -eW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEWD2RldkBz -eW5jcGxheS5wbDAeFw0xOTAyMDMxNTM2MDdaFw0yMDAyMDMxNTM2MDdaMHExCzAJ -BgNVBAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYDVQQK -DAhTeW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEWD2Rl -dkBzeW5jcGxheS5wbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7S -L3L0Mhf+5pMnSRUGQnxN89o+7aKtQ7Lf/rIGrFFjLNOOcTeWuod/x/MjDw5sP7HP -xQFb0/pAdm7VS5L/GJpWBQiHvB3VndyZ3CazQRs2NOus9CLBtoMh480dz/oiqed+ -XxkhD3hVyRDSkqz+QPNwj5b64FhYo9ocnOZwKJ3KvWBPDPV1wzIiCBh7YB2V/78f -K4qHaX6QVUvruxwpHp/Iw+JMHACBTRND5gXGo7vb/6g8AnKmypy+nS0iFbNasq5p -G5UFFb6T9jcQ7OfHvPOa9b3wFD/R3bi+dOyboST/BUd5j1+sJhrvEGI+fK6zIA11 -uwOasVEekv5I/z2jtG8CAwEAAaOCAQswggEHMIGNBgNVHSMEgYUwgYKhdaRzMHEx -CzAJBgNVBAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYD -VQQKDAhTeW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEW -D2RldkBzeW5jcGxheS5wbIIJAJe1HOCC73ylMAkGA1UdEwQCMAAwCwYDVR0PBAQD -AgTwMF0GA1UdEQRWMFSCUjxzcGVjaWZ5LXRoZS1zYW1lLWNvbW1vbi1uYW1lLXRo -YXQteW91LXVzZWQtd2hpbGUtZ2VuZXJhdGluZy1jc3ItaW4tdGhlLWxhc3Qtc3Rl -cD4wDQYJKoZIhvcNAQELBQADggEBAI+jWXb3nRYS1aOUduiF7VAVIaouYRPgkdb8 -p/W0S36KZ7jJXIQurXxx2znjtdC60qwBjJSqLnI1D6lWP6SdX8LMCNmJjtO/dZaX -zoTDqBEcjXp7Up17Zi2liCtYNYAFDdEX9wWMhrg26gY/IccBwm2wKD+B6MfOBTJc -/DRrp4cf/XcUCoNRCmU6JDFABKc63fK2Tr3ClTrXq2qYTaR4xIVu6EUAJGE+zMja -rOT4vXzvAPPHCf03uYCm8jagoxX9Qi7NyAWnyJOgO0DNnhKyxe1MZEkQV1X8m1ko -BVdikLM5hUHIzI2+5Q7o4jr2CHb5oL5OF4PUypwfeRe+PzoI9qs= +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.key b/server.key deleted file mode 100644 index 4b08fd3..0000000 --- a/server.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpgIBAAKCAQEAztIvcvQyF/7mkydJFQZCfE3z2j7toq1Dst/+sgasUWMs045x -N5a6h3/H8yMPDmw/sc/FAVvT+kB2btVLkv8YmlYFCIe8HdWd3JncJrNBGzY066z0 -IsG2gyHjzR3P+iKp535fGSEPeFXJENKSrP5A83CPlvrgWFij2hyc5nAoncq9YE8M -9XXDMiIIGHtgHZX/vx8riodpfpBVS+u7HCken8jD4kwcAIFNE0PmBcaju9v/qDwC -cqbKnL6dLSIVs1qyrmkblQUVvpP2NxDs58e885r1vfAUP9HduL507JuhJP8FR3mP -X6wmGu8QYj58rrMgDXW7A5qxUR6S/kj/PaO0bwIDAQABAoIBAQCdeVQ/hseNlbwi -V0tCAt4kn4YIqtltf9eIHx1il8okvgqnmlGCLfYgrZ6k9hDEvo9Q6A3sluq0JNkI -UseUST+GY/C5KgCGBtyH127U2K+99Gwe9jV9MVmyRt+YkhaFMleDt6jZDV8mFMtf -T7X2mjECKrZqEuM24WcA78BXhRan7RMl77ZQFrFNn+TccFItNUoeP75vD+5aLeH+ -ovWbMM3EaSFMPKoOZ3yJ78IEtD1B61DLOjZNlYsli7phmJR0veb8GHhllDovZ6ru -d5k56r5AqFLSV5Xp29Z0+8mbVOjlgDZ5EYYdyyo0KKX3sADtR2f2/ktIZuFPQFsC -nXbKnRVhAoGBAPeI9ft1B5htUdukb7IUcrdLoPv2cl9uw7e/kUemTO51Uk3QSnex -LUYe5JHoQDBdG7tN88xJLzp/ybH8+SublSrwnF7lqs+TRE3NGI05SIprpMIV2dAo -A+/6icg2jqeGt7ZZv2Ge9VzcxuxwmO5hkx51OlulBuEzdzGOfr/KdZ1jAoGBANXk -y4iQ145V1EkelwZZtMfZH8PZXp7ZVdF2vWG4HDV5frOwIanVtm5VEOW683gJsDnk -9+emRtzd0bm5wjy0K784VWzY/JODzxTgkaBgb1qxoHJv8xdzyo/F3n5WjalOnDQ5 -Y2uBDRj9tMKcZGX9yfnDik4ostT9KRUfy+xhXlCFAoGBAJhw2EvLfqn4aPkDA2aA -AUWwZavF0hnDdPSsHSQq3iXcSptxMusTSfZtAAZ7KUyfinyHPSUJzPQRzny2dhzM -68hN9X7boZ3D68SeEgimxm24bPa5zjHK8uHPQRrzvladOHYsPxg3dJODw13I2eW0 -YY3YU+AseVgnAbibMKOcY2JRAoGBAM4m91iHqYpBBRr4Rb1UJp5bkXcVAVJ+zGzX -Uc5mQaH0mH6dasSi+3K5wFB68IAoblZObfUx2Ki1jpbACAqz6Foj9Im1VTLkXmmT -zCfT6l9yRd8mMRjQfFI39qqzx1OANaNMEJujxTjqBhdv3oBCrDFzGtEb/sizu63l -eIk6i0l1AoGBAPRicQrL3fQ0Ig7O6Oc59Rs2I2FCioGTm4m6PMJzhTQQIkaBHxco -Vd8wWQCbZDdP/8o0uh/qvBey0fuGp9ff0k4rOUJtHTI0YlzdJzAEVyuPt+Z+heXc -KSsqIL/+WFL8pNO0wX0dycVEwSZnZ45wBskTGFZjv9c4Q0rdQBru9QWd ------END RSA PRIVATE KEY----- diff --git a/server.pem b/server.pem new file mode 100644 index 0000000..2b12284 --- /dev/null +++ b/server.pem @@ -0,0 +1,46 @@ +-----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 0758e2e..99cbd18 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -11,8 +11,9 @@ import time from copy import deepcopy from functools import wraps -from twisted.internet.endpoints import HostnameEndpoint +from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS from twisted.internet.protocol import ClientFactory +from twisted.internet.ssl import Certificate, optionsForClientTLS from twisted.internet import reactor, task, defer, threads from twisted.application.internet import ClientService @@ -703,7 +704,11 @@ class SyncplayClient(object): if '[' in host: host = host.strip('[]') port = int(port) - self._endpoint = HostnameEndpoint(reactor, host, port) + with open('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) def retry(retries): self._lastGlobalUpdate = None @@ -719,7 +724,7 @@ class SyncplayClient(object): self.reconnecting = True return(0.1 * (2 ** min(retries, 5))) - self._reconnectingService = ClientService(self._endpoint, self.protocolFactory , retryPolicy=retry) + self._reconnectingService = ClientService(self._endpoint, self.protocolFactory, retryPolicy=retry) waitForConnection = self._reconnectingService.whenConnected(failAfterFailures=1) self._reconnectingService.startService() diff --git a/syncplayServer.py b/syncplayServer.py index e3eca96..053c93b 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -15,22 +15,14 @@ except AttributeError: from OpenSSL import crypto from twisted.internet import reactor, ssl -from twisted.internet.endpoints import TCP4ServerEndpoint, SSL4ServerEndpoint, TCP6ServerEndpoint +from twisted.internet.endpoints import SSL4ServerEndpoint, TCP6ServerEndpoint from syncplay.server import SyncFactory, ConfigurationGetter -with open('server.crt', 'r') as f: - cert_data = f.read() -with open('server.key', 'r') as f: - key_data = f.read() +with open('server.pem') as f: + certData = f.read() -cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) -key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_data) -options = ssl.CertificateOptions( - privateKey=key, - certificate=cert, - acceptableProtocols=[b'h2'], -) +certificate = ssl.PrivateCertificate.loadPEM(certData).options() if __name__ == '__main__': argsGetter = ConfigurationGetter() @@ -47,8 +39,7 @@ if __name__ == '__main__': args.max_username_length, args.stats_db_file ) - #endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) - endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), options) + endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), certificate) endpoint4.listen(factory) #endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) #endpoint6.listen(factory) From 0890db8364d72f6c78254244d1fb810e3bc2907e Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sun, 3 Feb 2019 23:11:27 +0100 Subject: [PATCH 04/62] TLS: server listens on both IPv4 and IPv6 --- syncplayServer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/syncplayServer.py b/syncplayServer.py index 053c93b..c8b9b36 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -15,7 +15,7 @@ except AttributeError: from OpenSSL import crypto from twisted.internet import reactor, ssl -from twisted.internet.endpoints import SSL4ServerEndpoint, TCP6ServerEndpoint +from twisted.internet.endpoints import SSL4ServerEndpoint from syncplay.server import SyncFactory, ConfigurationGetter @@ -39,8 +39,8 @@ if __name__ == '__main__': args.max_username_length, args.stats_db_file ) - endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), certificate) + endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), certificate, interface='0.0.0.0') endpoint4.listen(factory) - #endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) - #endpoint6.listen(factory) + endpoint6 = SSL4ServerEndpoint(reactor, int(args.port), certificate, interface='::') + endpoint6.listen(factory) reactor.run() From 58ccca576675070004161c7fea98d9cd5cd54c45 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 4 Feb 2019 15:34:35 +0100 Subject: [PATCH 05/62] startTLS: enabled on server and client, TCP stays as default --- .gitignore | 3 ++- server.crt | 19 ------------------ server.pem | 46 ------------------------------------------- syncplay/client.py | 10 ++++++---- syncplay/protocols.py | 32 ++++++++++++++++++++++++++++-- syncplayServer.py | 11 ++++++----- 6 files changed, 44 insertions(+), 77 deletions(-) delete mode 100644 server.crt delete mode 100644 server.pem 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() From cecd992fa874ce07f443bf636632cc97d24adccb Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 4 Feb 2019 16:01:27 +0100 Subject: [PATCH 06/62] startTLS: added --tls [file] argument in server --- syncplay/client.py | 1 - syncplay/messages_en.py | 1 + syncplay/protocols.py | 15 ++++++++++----- syncplay/server.py | 15 +++++++++++++-- syncplayServer.py | 11 +++-------- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 6f5aa9a..32dfca2 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -710,7 +710,6 @@ class SyncplayClient(object): trust_root = Certificate.loadPEM(cert_file.read()) 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/messages_en.py b/syncplay/messages_en.py index 8c869ee..82c55e7 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -439,6 +439,7 @@ en = { "server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters "server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", "server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", + "server-tls-argument": "Enable TLS connections using the certificate file provided", "server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).", "server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 938a174..df960ba 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -316,10 +316,12 @@ class SyncClientProtocol(JSONCommandProtocol): def handleTLS(self, message): answer = message["startTLS"] if "startTLS" in message else None - if "true" in answer and not self.logged: + if "true" in answer and not self.logged and self._client.protocolFactory.options is not None: self.transport.startTLS(self._client.protocolFactory.options) self._client.ui.showMessage("Secure connection established") - self.sendHello() + elif "false" in answer: + self._client.ui.showErrorMessage("This server does not support TLS") + self.sendHello() class SyncServerProtocol(JSONCommandProtocol): def __init__(self, factory): @@ -626,9 +628,12 @@ class SyncServerProtocol(JSONCommandProtocol): 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) + if "send" in inquiry: + if not self.isLogged() and self._factory.options is not None: + self.sendTLS({"startTLS": "true"}) + self.transport.startTLS(self._factory.options) + else: + self.sendTLS({"startTLS": "false"}) class PingService(object): diff --git a/syncplay/server.py b/syncplay/server.py index d62d8d5..e9d7136 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -7,7 +7,7 @@ import time from string import Template from twisted.enterprise import adbapi -from twisted.internet import task, reactor +from twisted.internet import task, reactor, ssl from twisted.internet.protocol import Factory import syncplay @@ -20,7 +20,7 @@ from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomString class SyncFactory(Factory): def __init__(self, port='', password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH, - maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None): + maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCert=None): self.isolateRooms = isolateRooms print(getMessage("welcome-server-notification").format(syncplay.version)) self.port = port @@ -48,6 +48,16 @@ class SyncFactory(Factory): self._statsRecorder.startRecorder(statsDelay) else: self._statsDbHandle = None + self.options = None + if tlsCert is not None: + try: + with open(tlsCert) as f: + certData = f.read() + cert = ssl.PrivateCertificate.loadPEM(certData).options() + self.options = cert + except Exception as e: + print(e) + print("Cannot import certificate. TLS support not enabled.") def buildProtocol(self, addr): return SyncServerProtocol(self) @@ -624,3 +634,4 @@ class ConfigurationGetter(object): self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH)) self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH)) self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument")) + self._argparser.add_argument('--tls', metavar='file', type=str, nargs='?', help=getMessage("server-tls-argument")) diff --git a/syncplayServer.py b/syncplayServer.py index 0be1618..679e6c4 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -14,16 +14,11 @@ except AttributeError: warnings.warn("You must run Syncplay with Python 3.4 or newer!") from OpenSSL import crypto -from twisted.internet import reactor, ssl +from twisted.internet import reactor from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint from syncplay.server import SyncFactory, ConfigurationGetter -with open('cert/server.pem') as f: - certData = f.read() - -cert = ssl.PrivateCertificate.loadPEM(certData).options() - if __name__ == '__main__': argsGetter = ConfigurationGetter() args = argsGetter.getConfiguration() @@ -37,9 +32,9 @@ if __name__ == '__main__': args.disable_chat, args.max_chat_message_length, args.max_username_length, - args.stats_db_file + args.stats_db_file, + args.tls ) - factory.options = cert endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) endpoint4.listen(factory) endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) From 09b035e57d2f5b8efaba7a5575f6942181b46856 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 4 Feb 2019 17:50:07 +0100 Subject: [PATCH 07/62] startTLS: avoid retrying TLS connection if hostname is an IP address --- syncplay/client.py | 10 +++++++--- syncplay/protocols.py | 7 ++++++- syncplayServer.py | 1 - 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 32dfca2..8b8fb67 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -706,10 +706,14 @@ class SyncplayClient(object): if '[' in host: host = host.strip('[]') port = int(port) - with open('cert/server.crt') as cert_file: - trust_root = Certificate.loadPEM(cert_file.read()) self._endpoint = HostnameEndpoint(reactor, host, port) - self.protocolFactory.options = optionsForClientTLS(hostname=host, trustRoot = trust_root) + try: + with open('cert/server.crt') as cert_file: + trust_root = Certificate.loadPEM(cert_file.read()) + self.protocolFactory.options = optionsForClientTLS(hostname=host, trustRoot = trust_root) + except Exception as e: + self.protocolFactory.options = None + self._serverSupportsTLS = False def retry(retries): self._lastGlobalUpdate = None diff --git a/syncplay/protocols.py b/syncplay/protocols.py index df960ba..1b63774 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -78,9 +78,15 @@ class SyncClientProtocol(JSONCommandProtocol): self.sendTLS({"startTLS": "send"}) self._client.ui.showMessage("Attempting secure connection") else: + self._client.ui.showErrorMessage("This server does not support TLS") self.sendHello() def connectionLost(self, reason): + try: + if "Invalid DNS-ID" in str(reason.value): + self._client._serverSupportsTLS = False + except: + pass self._client.destroyProtocol() def dropWithError(self, error): @@ -303,7 +309,6 @@ class SyncClientProtocol(JSONCommandProtocol): def handleError(self, error): 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"]) diff --git a/syncplayServer.py b/syncplayServer.py index 679e6c4..39d23cc 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -13,7 +13,6 @@ except AttributeError: import warnings warnings.warn("You must run Syncplay with Python 3.4 or newer!") -from OpenSSL import crypto from twisted.internet import reactor from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint From fe11e66b293008545734bc0c08fb6d3a71ed99d0 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 4 Feb 2019 18:07:18 +0100 Subject: [PATCH 08/62] startTLS: support Let's Encrypt certificates --- syncplay/server.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index e9d7136..ae0fb82 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -5,6 +5,7 @@ import os import random import time from string import Template +from OpenSSL import crypto from twisted.enterprise import adbapi from twisted.internet import task, reactor, ssl @@ -20,7 +21,7 @@ from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomString class SyncFactory(Factory): def __init__(self, port='', password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH, - maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCert=None): + maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCertPath=None): self.isolateRooms = isolateRooms print(getMessage("welcome-server-notification").format(syncplay.version)) self.port = port @@ -49,12 +50,18 @@ class SyncFactory(Factory): else: self._statsDbHandle = None self.options = None - if tlsCert is not None: + if tlsCertPath is not None: try: - with open(tlsCert) as f: - certData = f.read() - cert = ssl.PrivateCertificate.loadPEM(certData).options() - self.options = cert + privkey=open(tlsCertPath+'/privkey.pem', 'rt').read() + certif=open(tlsCertPath+'/cert.pem', 'rt').read() + chain=open(tlsCertPath+'/chain.pem', 'rt').read() + + privkeypyssl=crypto.load_privatekey(crypto.FILETYPE_PEM,privkey) + certifpyssl=crypto.load_certificate(crypto.FILETYPE_PEM,certif) + chainpyssl=[crypto.load_certificate(crypto.FILETYPE_PEM,chain)] + + contextFactory=ssl.CertificateOptions(privateKey=privkeypyssl,certificate=certifpyssl,extraCertChain=chainpyssl) + self.options = contextFactory except Exception as e: print(e) print("Cannot import certificate. TLS support not enabled.") From 063a191e99337ed1a1be40cd809dcf0e24ac7f8b Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 4 Feb 2019 19:50:09 +0100 Subject: [PATCH 09/62] startTLS: fall back to TCP if server certificate is not validated --- syncplay/protocols.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 1b63774..e52bb15 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -85,6 +85,8 @@ class SyncClientProtocol(JSONCommandProtocol): try: if "Invalid DNS-ID" in str(reason.value): self._client._serverSupportsTLS = False + elif "certificate verify failed" in str(reason.value): + self._client._serverSupportsTLS = False except: pass self._client.destroyProtocol() From a055e3b881f8e9f22e54e967e7b6882e6760f0a1 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 16:13:47 +0100 Subject: [PATCH 10/62] startTLS: correct certificate loading and validation --- syncplay/client.py | 8 +++++--- syncplay/server.py | 31 +++++++++++++++++-------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 8b8fb67..47234fc 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1,7 +1,9 @@ import ast +import certifi import collections import hashlib +import os import os.path import random import re @@ -24,6 +26,8 @@ from syncplay.messages import getMissingStrings, getMessage from syncplay.protocols import SyncClientProtocol from syncplay.utils import isMacOS +os.environ['SSL_CERT_FILE'] = certifi.where() + class SyncClientFactory(ClientFactory): def __init__(self, client, retry=constants.RECONNECT_RETRIES): @@ -708,9 +712,7 @@ class SyncplayClient(object): port = int(port) self._endpoint = HostnameEndpoint(reactor, host, port) try: - with open('cert/server.crt') as cert_file: - trust_root = Certificate.loadPEM(cert_file.read()) - self.protocolFactory.options = optionsForClientTLS(hostname=host, trustRoot = trust_root) + self.protocolFactory.options = optionsForClientTLS(hostname=host) except Exception as e: self.protocolFactory.options = None self._serverSupportsTLS = False diff --git a/syncplay/server.py b/syncplay/server.py index ae0fb82..722951b 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -51,20 +51,7 @@ class SyncFactory(Factory): self._statsDbHandle = None self.options = None if tlsCertPath is not None: - try: - privkey=open(tlsCertPath+'/privkey.pem', 'rt').read() - certif=open(tlsCertPath+'/cert.pem', 'rt').read() - chain=open(tlsCertPath+'/chain.pem', 'rt').read() - - privkeypyssl=crypto.load_privatekey(crypto.FILETYPE_PEM,privkey) - certifpyssl=crypto.load_certificate(crypto.FILETYPE_PEM,certif) - chainpyssl=[crypto.load_certificate(crypto.FILETYPE_PEM,chain)] - - contextFactory=ssl.CertificateOptions(privateKey=privkeypyssl,certificate=certifpyssl,extraCertChain=chainpyssl) - self.options = contextFactory - except Exception as e: - print(e) - print("Cannot import certificate. TLS support not enabled.") + self._allowTLSconnections(tlsCertPath) def buildProtocol(self, addr): return SyncServerProtocol(self) @@ -211,6 +198,22 @@ class SyncFactory(Factory): else: watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex()) + def _allowTLSconnections(path): + try: + privkey = open(path+'/privkey.pem', 'rt').read() + certif = open(path+'/cert.pem', 'rt').read() + chain = open(path+'/chain.pem', 'rt').read() + + privkeypyssl = crypto.load_privatekey(crypto.FILETYPE_PEM, privkey) + certifpyssl = crypto.load_certificate(crypto.FILETYPE_PEM, certif) + chainpyssl = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)] + + contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, extraCertChain=chainpyssl) + self.options = contextFactory + except Exception as e: + print(e) + print("Cannot import certificates. TLS support not enabled.") + class StatsRecorder(object): def __init__(self, dbHandle, roomManager): From 7dfe9d2bdbfb0767da0fbafd6bb497853c56a6b3 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 16:16:50 +0100 Subject: [PATCH 11/62] startTLS: fix server cert loading function --- syncplay/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index 722951b..dc5feac 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -198,7 +198,7 @@ class SyncFactory(Factory): else: watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex()) - def _allowTLSconnections(path): + def _allowTLSconnections(self, path): try: privkey = open(path+'/privkey.pem', 'rt').read() certif = open(path+'/cert.pem', 'rt').read() @@ -644,4 +644,4 @@ class ConfigurationGetter(object): self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH)) self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH)) self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument")) - self._argparser.add_argument('--tls', metavar='file', type=str, nargs='?', help=getMessage("server-tls-argument")) + self._argparser.add_argument('--tls', metavar='path', type=str, nargs='?', help=getMessage("server-tls-argument")) From 18e39b4aaea3f6cf95a18a5d87adcfbaaa422d70 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 16:28:48 +0100 Subject: [PATCH 12/62] startTLS: making TLS support (and dependencies) optional --- syncplay/client.py | 11 +++++++---- syncplay/protocols.py | 2 +- syncplay/server.py | 9 +++++++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 47234fc..7a2b64d 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1,6 +1,5 @@ import ast -import certifi import collections import hashlib import os @@ -15,10 +14,16 @@ from functools import wraps from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS from twisted.internet.protocol import ClientFactory -from twisted.internet.ssl import Certificate, optionsForClientTLS from twisted.internet import reactor, task, defer, threads from twisted.application.internet import ClientService +try: + import certifi + from twisted.internet.ssl import optionsForClientTLS + os.environ['SSL_CERT_FILE'] = certifi.where() +except: + pass + from syncplay import utils, constants, version from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \ PRIVACY_HIDDENFILENAME @@ -26,8 +31,6 @@ from syncplay.messages import getMissingStrings, getMessage from syncplay.protocols import SyncClientProtocol from syncplay.utils import isMacOS -os.environ['SSL_CERT_FILE'] = certifi.where() - class SyncClientFactory(ClientFactory): def __init__(self, client, retry=constants.RECONNECT_RETRIES): diff --git a/syncplay/protocols.py b/syncplay/protocols.py index e52bb15..340bb92 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -78,7 +78,7 @@ class SyncClientProtocol(JSONCommandProtocol): self.sendTLS({"startTLS": "send"}) self._client.ui.showMessage("Attempting secure connection") else: - self._client.ui.showErrorMessage("This server does not support TLS") + self._client.ui.showErrorMessage("TLS is not supported") self.sendHello() def connectionLost(self, reason): diff --git a/syncplay/server.py b/syncplay/server.py index dc5feac..a76c0a5 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -5,12 +5,17 @@ import os import random import time from string import Template -from OpenSSL import crypto from twisted.enterprise import adbapi from twisted.internet import task, reactor, ssl from twisted.internet.protocol import Factory +try: + from OpenSSL import crypto + from twisted.internet import ssl +except: + pass + import syncplay from syncplay import constants from syncplay.messages import getMessage @@ -212,7 +217,7 @@ class SyncFactory(Factory): self.options = contextFactory except Exception as e: print(e) - print("Cannot import certificates. TLS support not enabled.") + print("TLS support is not enabled.") class StatsRecorder(object): From 014483b5467069447e078092f5d38cb586cf8b57 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 16:48:23 +0100 Subject: [PATCH 13/62] startTLS: handle ui messages --- syncplay/messages_de.py | 7 +++++++ syncplay/messages_en.py | 7 ++++++- syncplay/messages_it.py | 6 ++++++ syncplay/messages_ru.py | 7 +++++++ syncplay/protocols.py | 8 ++++---- syncplay/server.py | 2 +- 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index fc09489..e67cbf8 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -310,6 +310,12 @@ de = { "userguide-menu-label": "&Benutzerhandbuch öffnen", "update-menu-label": "auf &Aktualisierung prüfen", + # startTLS messages - TODO: Translate + "startTLS-initiated": "Attempting secure connection", + "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-not-supported-client": "TLS is not supported", + "startTLS-not-supported-server": "This server does not support TLS", + # About dialog - TODO: Translate "about-menu-label": "&About Syncplay", "about-dialog-title": "About Syncplay", @@ -438,6 +444,7 @@ de = { "server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # TODO: Translate "server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", # TODO: Translate "server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", # TODO: Translate + "server-startTLS-argument": "Enable TLS connections using the certificate files in the path provided", # TODO: Translate "server-messed-up-motd-unescaped-placeholders": "Die Nachricht des Tages hat unmaskierte Platzhalter. Alle $-Zeichen sollten verdoppelt werden ($$).", "server-messed-up-motd-too-long": "Die Nachricht des Tages ist zu lang - Maximal {} Zeichen, aktuell {}.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 82c55e7..f5971cd 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -312,6 +312,11 @@ en = { "userguide-menu-label": "Open user &guide", "update-menu-label": "Check for &update", + "startTLS-initiated": "Attempting secure connection", + "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-not-supported-client": "TLS is not supported", + "startTLS-not-supported-server": "This server does not support TLS", + # About dialog "about-menu-label": "&About Syncplay", "about-dialog-title": "About Syncplay", @@ -439,7 +444,7 @@ en = { "server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters "server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", "server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", - "server-tls-argument": "Enable TLS connections using the certificate file provided", + "server-startTLS-argument": "Enable TLS connections using the certificate files in the path provided", "server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).", "server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index a3d4c8c..33fcab9 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -312,6 +312,11 @@ it = { "userguide-menu-label": "Apri guida &utente", "update-menu-label": "Controlla la presenza di &aggiornamenti", + "startTLS-initiated": "Tentativo di connessione sicura in corso", + "startTLS-secure-connection-ok": "Connessione sicura stabilita", + "startTLS-not-supported-client": "TLS non è supportato", + "startTLS-not-supported-server": "Questo server non supporta TLS", + # About dialog "about-menu-label": "&Informazioni su Syncplay", "about-dialog-title": "Informazioni su Syncplay", @@ -439,6 +444,7 @@ it = { "server-chat-maxchars-argument": "Numero massimo di caratteri in un messaggio di chat (default è {})", # Default number of characters "server-maxusernamelength-argument": "Numero massimo di caratteri in un nome utente (default è {})", "server-stats-db-file-argument": "Abilita la raccolta dei dati statistici nel file SQLite indicato", + "server-startTLS-argument": "Abilita il protocollo TLS usando i certificati contenuti nel percorso indicato", "server-messed-up-motd-unescaped-placeholders": "Il messaggio del giorno ha dei caratteri non 'escaped'. Tutti i simboli $ devono essere doppi ($$).", "server-messed-up-motd-too-long": "Il messaggio del giorno è troppo lungo - numero massimo di caratteri è {}, {} trovati.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index f9156d2..7d258a2 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -313,6 +313,12 @@ ru = { "userguide-menu-label": "&Руководство пользователя", "update-menu-label": "Проверить &обновления", + # startTLS messages - TODO: Translate + "startTLS-initiated": "Attempting secure connection", + "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-not-supported-client": "TLS is not supported", + "startTLS-not-supported-server": "This server does not support TLS", + # About dialog - TODO: Translate "about-menu-label": "&About Syncplay", "about-dialog-title": "About Syncplay", @@ -439,6 +445,7 @@ ru = { "server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # TODO: Translate "server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", # TODO: Translate "server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", # TODO: Translate + "server-startTLS-argument": "Enable TLS connections using the certificate files in the path provided", # TODO: Translate "server-messed-up-motd-unescaped-placeholders" : "MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).", "server-messed-up-motd-too-long" : "MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 340bb92..cc7ad9d 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -76,9 +76,9 @@ class SyncClientProtocol(JSONCommandProtocol): self._client.initProtocol(self) if self._client._serverSupportsTLS: self.sendTLS({"startTLS": "send"}) - self._client.ui.showMessage("Attempting secure connection") + self._client.ui.showMessage(getMessage("startTLS-initiated")) else: - self._client.ui.showErrorMessage("TLS is not supported") + self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-client")) self.sendHello() def connectionLost(self, reason): @@ -325,9 +325,9 @@ class SyncClientProtocol(JSONCommandProtocol): answer = message["startTLS"] if "startTLS" in message else None if "true" in answer and not self.logged and self._client.protocolFactory.options is not None: self.transport.startTLS(self._client.protocolFactory.options) - self._client.ui.showMessage("Secure connection established") + self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok")) elif "false" in answer: - self._client.ui.showErrorMessage("This server does not support TLS") + self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server")) self.sendHello() class SyncServerProtocol(JSONCommandProtocol): diff --git a/syncplay/server.py b/syncplay/server.py index a76c0a5..4a0c142 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -649,4 +649,4 @@ class ConfigurationGetter(object): self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH)) self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH)) self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument")) - self._argparser.add_argument('--tls', metavar='path', type=str, nargs='?', help=getMessage("server-tls-argument")) + self._argparser.add_argument('--tls', metavar='path', type=str, nargs='?', help=getMessage("server-startTLS-argument")) From 6d5a618767cbd88e4a594f4591eab4614cf26bd4 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 20:11:59 +0100 Subject: [PATCH 14/62] startTLS: show negotiated TLS version --- syncplay/messages_de.py | 2 +- syncplay/messages_en.py | 2 +- syncplay/messages_it.py | 2 +- syncplay/messages_ru.py | 2 +- syncplay/protocols.py | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index e67cbf8..6c8b3d0 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -312,7 +312,7 @@ de = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", - "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-secure-connection-ok": "Secure connection established ({})", "startTLS-not-supported-client": "TLS is not supported", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index f5971cd..b907614 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -313,7 +313,7 @@ en = { "update-menu-label": "Check for &update", "startTLS-initiated": "Attempting secure connection", - "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-secure-connection-ok": "Secure connection established ({})", "startTLS-not-supported-client": "TLS is not supported", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 33fcab9..cf31785 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -313,7 +313,7 @@ it = { "update-menu-label": "Controlla la presenza di &aggiornamenti", "startTLS-initiated": "Tentativo di connessione sicura in corso", - "startTLS-secure-connection-ok": "Connessione sicura stabilita", + "startTLS-secure-connection-ok": "Connessione sicura stabilita ({})", "startTLS-not-supported-client": "TLS non è supportato", "startTLS-not-supported-server": "Questo server non supporta TLS", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 7d258a2..01e9e1b 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -315,7 +315,7 @@ ru = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", - "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-secure-connection-ok": "Secure connection established ({})", "startTLS-not-supported-client": "TLS is not supported", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index cc7ad9d..3ec83d8 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -325,7 +325,8 @@ class SyncClientProtocol(JSONCommandProtocol): answer = message["startTLS"] if "startTLS" in message else None if "true" in answer and not self.logged and self._client.protocolFactory.options is not None: self.transport.startTLS(self._client.protocolFactory.options) - self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok")) + TLSConnVersion = self.transport.protocol._tlsConnection.get_protocol_version_name() + self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(TLSConnVersion)) elif "false" in answer: self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server")) self.sendHello() From 93052b4b9596419beeaae6fd7cc26018b8c740fa Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 20:26:44 +0100 Subject: [PATCH 15/62] startTLS: separate not-supported messages for client and server --- syncplay/client.py | 3 ++- syncplay/messages_de.py | 2 +- syncplay/messages_en.py | 2 +- syncplay/messages_it.py | 2 +- syncplay/messages_ru.py | 2 +- syncplay/protocols.py | 12 ++++++++---- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 7a2b64d..37bd2b3 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -716,9 +716,10 @@ class SyncplayClient(object): self._endpoint = HostnameEndpoint(reactor, host, port) try: self.protocolFactory.options = optionsForClientTLS(hostname=host) + self._clientSupportsTLS = True except Exception as e: self.protocolFactory.options = None - self._serverSupportsTLS = False + self._clientSupportsTLS = False def retry(retries): self._lastGlobalUpdate = None diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 6c8b3d0..1adbfb9 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -313,7 +313,7 @@ de = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-not-supported-client": "TLS is not supported", + "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", # About dialog - TODO: Translate diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index b907614..2c8f84f 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -314,7 +314,7 @@ en = { "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-not-supported-client": "TLS is not supported", + "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", # About dialog diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index cf31785..99fd305 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -314,7 +314,7 @@ it = { "startTLS-initiated": "Tentativo di connessione sicura in corso", "startTLS-secure-connection-ok": "Connessione sicura stabilita ({})", - "startTLS-not-supported-client": "TLS non è supportato", + "startTLS-not-supported-client": "Questo client non supporta TLS", "startTLS-not-supported-server": "Questo server non supporta TLS", # About dialog diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 01e9e1b..b4f6332 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -316,7 +316,7 @@ ru = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-not-supported-client": "TLS is not supported", + "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", # About dialog - TODO: Translate diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 3ec83d8..41bf0d0 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -74,11 +74,15 @@ class SyncClientProtocol(JSONCommandProtocol): def connectionMade(self): self._client.initProtocol(self) - if self._client._serverSupportsTLS: - self.sendTLS({"startTLS": "send"}) - self._client.ui.showMessage(getMessage("startTLS-initiated")) + if self._client._clientSupportsTLS: + if self._client._serverSupportsTLS: + self.sendTLS({"startTLS": "send"}) + self._client.ui.showMessage(getMessage("startTLS-initiated")) + else: + self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server")) + self.sendHello() else: - self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-client")) + self._client.ui.showMessage(getMessage("startTLS-not-supported-client")) self.sendHello() def connectionLost(self, reason): From cecb8c9b7b38c90184b732646c5830d120433174 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Thu, 7 Feb 2019 12:46:42 +0100 Subject: [PATCH 16/62] startTLS: get server certificate only after handshake --- syncplay/protocols.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 41bf0d0..11d3e91 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -4,6 +4,8 @@ import time from functools import wraps from twisted.protocols.basic import LineReceiver +from twisted.internet.interfaces import IHandshakeListener +from zope.interface.declarations import implementer import syncplay from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION @@ -61,6 +63,7 @@ class JSONCommandProtocol(LineReceiver): raise NotImplementedError() +@implementer(IHandshakeListener) class SyncClientProtocol(JSONCommandProtocol): def __init__(self, client): self._client = client @@ -91,6 +94,8 @@ class SyncClientProtocol(JSONCommandProtocol): self._client._serverSupportsTLS = False elif "certificate verify failed" in str(reason.value): self._client._serverSupportsTLS = False + elif "tlsv1 alert protocol version" in str(reason.value): + self._client._clientSupportsTLS = False except: pass self._client.destroyProtocol() @@ -329,12 +334,20 @@ class SyncClientProtocol(JSONCommandProtocol): answer = message["startTLS"] if "startTLS" in message else None if "true" in answer and not self.logged and self._client.protocolFactory.options is not None: self.transport.startTLS(self._client.protocolFactory.options) - TLSConnVersion = self.transport.protocol._tlsConnection.get_protocol_version_name() - self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(TLSConnVersion)) elif "false" in answer: self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server")) self.sendHello() + def handshakeCompleted(self): + self._serverCertificateTLS = self.transport.getPeerCertificate() + self._subjectTLS = self._serverCertificateTLS.get_subject().CN + self._issuerTLS = self._serverCertificateTLS.get_issuer().CN + self._expiredTLS =self._serverCertificateTLS.has_expired() + self._expireDateTLS = self._serverCertificateTLS.get_notAfter() + self._connVersionTLS = self.transport.protocol._tlsConnection.get_protocol_version_name() + self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(self._connVersionTLS)) + + class SyncServerProtocol(JSONCommandProtocol): def __init__(self, factory): self._factory = factory From 51902c8f8c21956864ea145aafc8b2c7ed761a79 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Thu, 7 Feb 2019 12:55:52 +0100 Subject: [PATCH 17/62] startTLS: get encrypted connection information after handshake --- syncplay/protocols.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 11d3e91..56c0a22 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -344,7 +344,11 @@ class SyncClientProtocol(JSONCommandProtocol): self._issuerTLS = self._serverCertificateTLS.get_issuer().CN self._expiredTLS =self._serverCertificateTLS.has_expired() self._expireDateTLS = self._serverCertificateTLS.get_notAfter() - self._connVersionTLS = self.transport.protocol._tlsConnection.get_protocol_version_name() + + self._encryptedConnectionTLS = self.transport.protocol._tlsConnection + self._connVersionTLS = self._encryptedConnectionTLS.get_protocol_version_name() + self._cipherNameTLS = self._encryptedConnectionTLS.get_cipher_name() + self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(self._connVersionTLS)) From 810a791332e863c7dd1debc2f60503c2535aa92c Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Thu, 7 Feb 2019 15:31:35 +0100 Subject: [PATCH 18/62] startTLS: set minimum protocol version to TLS 1.2 --- syncplay/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/syncplay/server.py b/syncplay/server.py index 4a0c142..86d9c97 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -213,7 +213,8 @@ class SyncFactory(Factory): certifpyssl = crypto.load_certificate(crypto.FILETYPE_PEM, certif) chainpyssl = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)] - contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, extraCertChain=chainpyssl) + contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, + extraCertChain=chainpyssl, raiseMinimumTo=ssl.TLSVersion.TLSv1_2) self.options = contextFactory except Exception as e: print(e) From 70feed33050de42045f6288b19d612d473abe70e Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Thu, 7 Feb 2019 15:34:43 +0100 Subject: [PATCH 19/62] startTLS: enable Diffie-Hellman based key exchange on server Following the guidelines reported in the Twisted documentation here https://twistedmatrix.com/documents/current/core/howto/ssl.html this commit enables Diffie-Hellman based key exchange on the server. Before launching the server, a parameters .pem file must be generated as detailed in https://twistedmatrix.com/documents/18.9.0/api/twisted.internet.ssl.DiffieHellmanParameters.html by running `openssl dhparam -out dh_param_1024.pem -2 1024` on the server machine. This parameters file must be placed in the same path that contains the server certificates. --- syncplay/server.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/syncplay/server.py b/syncplay/server.py index 86d9c97..cdf24c8 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -13,6 +13,7 @@ from twisted.internet.protocol import Factory try: from OpenSSL import crypto from twisted.internet import ssl + from twisted.python.filepath import FilePath except: pass @@ -213,8 +214,12 @@ class SyncFactory(Factory): certifpyssl = crypto.load_certificate(crypto.FILETYPE_PEM, certif) chainpyssl = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)] + dhFilePath = FilePath(path+'/dh_param_1024.pem') + dhParams = ssl.DiffieHellmanParameters.fromFile(dhFilePath) + contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, - extraCertChain=chainpyssl, raiseMinimumTo=ssl.TLSVersion.TLSv1_2) + extraCertChain=chainpyssl, dhParameters=dhParams, + raiseMinimumTo=ssl.TLSVersion.TLSv1_2) self.options = contextFactory except Exception as e: print(e) From 8e1f40341cd6a53a06412ec2a99037f1d745e9e1 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Thu, 7 Feb 2019 23:26:20 +0100 Subject: [PATCH 20/62] startTLS: change DH key parameter filename --- syncplay/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/server.py b/syncplay/server.py index cdf24c8..563822e 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -214,7 +214,7 @@ class SyncFactory(Factory): certifpyssl = crypto.load_certificate(crypto.FILETYPE_PEM, certif) chainpyssl = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)] - dhFilePath = FilePath(path+'/dh_param_1024.pem') + dhFilePath = FilePath(path+'/dh_param.pem') dhParams = ssl.DiffieHellmanParameters.fromFile(dhFilePath) contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, From 00799faf1cc9bca5876c147902f1e0e6edf45e88 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sun, 3 Feb 2019 16:30:20 +0100 Subject: [PATCH 21/62] Restore TCP4 and TCP6 server endpoints --- syncplayServer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/syncplayServer.py b/syncplayServer.py index 39d23cc..0b27c47 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -31,8 +31,7 @@ if __name__ == '__main__': args.disable_chat, args.max_chat_message_length, args.max_username_length, - args.stats_db_file, - args.tls + args.stats_db_file ) endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) endpoint4.listen(factory) From 7f9e694b04006467358f9086d708eac790ce4653 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sun, 3 Feb 2019 16:40:50 +0100 Subject: [PATCH 22/62] TLS: server listens on TLS via IPv4 --- server.crt | 26 ++++++++++++++++++++++++++ server.key | 27 +++++++++++++++++++++++++++ syncplayServer.py | 25 ++++++++++++++++++++----- 3 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 server.crt create mode 100644 server.key diff --git a/server.crt b/server.crt new file mode 100644 index 0000000..b302fd6 --- /dev/null +++ b/server.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEcjCCA1qgAwIBAgIJAJe1HOCC73ylMA0GCSqGSIb3DQEBCwUAMHExCzAJBgNV +BAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYDVQQKDAhT +eW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEWD2RldkBz +eW5jcGxheS5wbDAeFw0xOTAyMDMxNTM2MDdaFw0yMDAyMDMxNTM2MDdaMHExCzAJ +BgNVBAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYDVQQK +DAhTeW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEWD2Rl +dkBzeW5jcGxheS5wbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7S +L3L0Mhf+5pMnSRUGQnxN89o+7aKtQ7Lf/rIGrFFjLNOOcTeWuod/x/MjDw5sP7HP +xQFb0/pAdm7VS5L/GJpWBQiHvB3VndyZ3CazQRs2NOus9CLBtoMh480dz/oiqed+ +XxkhD3hVyRDSkqz+QPNwj5b64FhYo9ocnOZwKJ3KvWBPDPV1wzIiCBh7YB2V/78f +K4qHaX6QVUvruxwpHp/Iw+JMHACBTRND5gXGo7vb/6g8AnKmypy+nS0iFbNasq5p +G5UFFb6T9jcQ7OfHvPOa9b3wFD/R3bi+dOyboST/BUd5j1+sJhrvEGI+fK6zIA11 +uwOasVEekv5I/z2jtG8CAwEAAaOCAQswggEHMIGNBgNVHSMEgYUwgYKhdaRzMHEx +CzAJBgNVBAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYD +VQQKDAhTeW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEW +D2RldkBzeW5jcGxheS5wbIIJAJe1HOCC73ylMAkGA1UdEwQCMAAwCwYDVR0PBAQD +AgTwMF0GA1UdEQRWMFSCUjxzcGVjaWZ5LXRoZS1zYW1lLWNvbW1vbi1uYW1lLXRo +YXQteW91LXVzZWQtd2hpbGUtZ2VuZXJhdGluZy1jc3ItaW4tdGhlLWxhc3Qtc3Rl +cD4wDQYJKoZIhvcNAQELBQADggEBAI+jWXb3nRYS1aOUduiF7VAVIaouYRPgkdb8 +p/W0S36KZ7jJXIQurXxx2znjtdC60qwBjJSqLnI1D6lWP6SdX8LMCNmJjtO/dZaX +zoTDqBEcjXp7Up17Zi2liCtYNYAFDdEX9wWMhrg26gY/IccBwm2wKD+B6MfOBTJc +/DRrp4cf/XcUCoNRCmU6JDFABKc63fK2Tr3ClTrXq2qYTaR4xIVu6EUAJGE+zMja +rOT4vXzvAPPHCf03uYCm8jagoxX9Qi7NyAWnyJOgO0DNnhKyxe1MZEkQV1X8m1ko +BVdikLM5hUHIzI2+5Q7o4jr2CHb5oL5OF4PUypwfeRe+PzoI9qs= +-----END CERTIFICATE----- diff --git a/server.key b/server.key new file mode 100644 index 0000000..4b08fd3 --- /dev/null +++ b/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEAztIvcvQyF/7mkydJFQZCfE3z2j7toq1Dst/+sgasUWMs045x +N5a6h3/H8yMPDmw/sc/FAVvT+kB2btVLkv8YmlYFCIe8HdWd3JncJrNBGzY066z0 +IsG2gyHjzR3P+iKp535fGSEPeFXJENKSrP5A83CPlvrgWFij2hyc5nAoncq9YE8M +9XXDMiIIGHtgHZX/vx8riodpfpBVS+u7HCken8jD4kwcAIFNE0PmBcaju9v/qDwC +cqbKnL6dLSIVs1qyrmkblQUVvpP2NxDs58e885r1vfAUP9HduL507JuhJP8FR3mP +X6wmGu8QYj58rrMgDXW7A5qxUR6S/kj/PaO0bwIDAQABAoIBAQCdeVQ/hseNlbwi +V0tCAt4kn4YIqtltf9eIHx1il8okvgqnmlGCLfYgrZ6k9hDEvo9Q6A3sluq0JNkI +UseUST+GY/C5KgCGBtyH127U2K+99Gwe9jV9MVmyRt+YkhaFMleDt6jZDV8mFMtf +T7X2mjECKrZqEuM24WcA78BXhRan7RMl77ZQFrFNn+TccFItNUoeP75vD+5aLeH+ +ovWbMM3EaSFMPKoOZ3yJ78IEtD1B61DLOjZNlYsli7phmJR0veb8GHhllDovZ6ru +d5k56r5AqFLSV5Xp29Z0+8mbVOjlgDZ5EYYdyyo0KKX3sADtR2f2/ktIZuFPQFsC +nXbKnRVhAoGBAPeI9ft1B5htUdukb7IUcrdLoPv2cl9uw7e/kUemTO51Uk3QSnex +LUYe5JHoQDBdG7tN88xJLzp/ybH8+SublSrwnF7lqs+TRE3NGI05SIprpMIV2dAo +A+/6icg2jqeGt7ZZv2Ge9VzcxuxwmO5hkx51OlulBuEzdzGOfr/KdZ1jAoGBANXk +y4iQ145V1EkelwZZtMfZH8PZXp7ZVdF2vWG4HDV5frOwIanVtm5VEOW683gJsDnk +9+emRtzd0bm5wjy0K784VWzY/JODzxTgkaBgb1qxoHJv8xdzyo/F3n5WjalOnDQ5 +Y2uBDRj9tMKcZGX9yfnDik4ostT9KRUfy+xhXlCFAoGBAJhw2EvLfqn4aPkDA2aA +AUWwZavF0hnDdPSsHSQq3iXcSptxMusTSfZtAAZ7KUyfinyHPSUJzPQRzny2dhzM +68hN9X7boZ3D68SeEgimxm24bPa5zjHK8uHPQRrzvladOHYsPxg3dJODw13I2eW0 +YY3YU+AseVgnAbibMKOcY2JRAoGBAM4m91iHqYpBBRr4Rb1UJp5bkXcVAVJ+zGzX +Uc5mQaH0mH6dasSi+3K5wFB68IAoblZObfUx2Ki1jpbACAqz6Foj9Im1VTLkXmmT +zCfT6l9yRd8mMRjQfFI39qqzx1OANaNMEJujxTjqBhdv3oBCrDFzGtEb/sizu63l +eIk6i0l1AoGBAPRicQrL3fQ0Ig7O6Oc59Rs2I2FCioGTm4m6PMJzhTQQIkaBHxco +Vd8wWQCbZDdP/8o0uh/qvBey0fuGp9ff0k4rOUJtHTI0YlzdJzAEVyuPt+Z+heXc +KSsqIL/+WFL8pNO0wX0dycVEwSZnZ45wBskTGFZjv9c4Q0rdQBru9QWd +-----END RSA PRIVATE KEY----- diff --git a/syncplayServer.py b/syncplayServer.py index 0b27c47..e3eca96 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -13,11 +13,25 @@ except AttributeError: import warnings warnings.warn("You must run Syncplay with Python 3.4 or newer!") -from twisted.internet import reactor -from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint +from OpenSSL import crypto +from twisted.internet import reactor, ssl +from twisted.internet.endpoints import TCP4ServerEndpoint, SSL4ServerEndpoint, TCP6ServerEndpoint from syncplay.server import SyncFactory, ConfigurationGetter +with open('server.crt', 'r') as f: + cert_data = f.read() +with open('server.key', 'r') as f: + key_data = f.read() + +cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) +key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_data) +options = ssl.CertificateOptions( + privateKey=key, + certificate=cert, + acceptableProtocols=[b'h2'], +) + if __name__ == '__main__': argsGetter = ConfigurationGetter() args = argsGetter.getConfiguration() @@ -33,8 +47,9 @@ if __name__ == '__main__': args.max_username_length, args.stats_db_file ) - endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) + #endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) + endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), options) endpoint4.listen(factory) - endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) - endpoint6.listen(factory) + #endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) + #endpoint6.listen(factory) reactor.run() From 8bff90d8095bea2dfae94a389c07554edbfdd1c4 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sun, 3 Feb 2019 21:19:10 +0100 Subject: [PATCH 23/62] TLS: client-server implementation with self-issued certs on IPv4 --- server.crt | 41 +++++++++++++++++------------------------ server.key | 27 --------------------------- server.pem | 46 ++++++++++++++++++++++++++++++++++++++++++++++ syncplay/client.py | 13 ++++++------- syncplayServer.py | 19 +++++-------------- 5 files changed, 74 insertions(+), 72 deletions(-) delete mode 100644 server.key create mode 100644 server.pem diff --git a/server.crt b/server.crt index b302fd6..d93e114 100644 --- a/server.crt +++ b/server.crt @@ -1,26 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIEcjCCA1qgAwIBAgIJAJe1HOCC73ylMA0GCSqGSIb3DQEBCwUAMHExCzAJBgNV -BAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYDVQQKDAhT -eW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEWD2RldkBz -eW5jcGxheS5wbDAeFw0xOTAyMDMxNTM2MDdaFw0yMDAyMDMxNTM2MDdaMHExCzAJ -BgNVBAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYDVQQK -DAhTeW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEWD2Rl -dkBzeW5jcGxheS5wbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7S -L3L0Mhf+5pMnSRUGQnxN89o+7aKtQ7Lf/rIGrFFjLNOOcTeWuod/x/MjDw5sP7HP -xQFb0/pAdm7VS5L/GJpWBQiHvB3VndyZ3CazQRs2NOus9CLBtoMh480dz/oiqed+ -XxkhD3hVyRDSkqz+QPNwj5b64FhYo9ocnOZwKJ3KvWBPDPV1wzIiCBh7YB2V/78f -K4qHaX6QVUvruxwpHp/Iw+JMHACBTRND5gXGo7vb/6g8AnKmypy+nS0iFbNasq5p -G5UFFb6T9jcQ7OfHvPOa9b3wFD/R3bi+dOyboST/BUd5j1+sJhrvEGI+fK6zIA11 -uwOasVEekv5I/z2jtG8CAwEAAaOCAQswggEHMIGNBgNVHSMEgYUwgYKhdaRzMHEx -CzAJBgNVBAYTAlBMMQ0wCwYDVQQIDAROb25lMQ0wCwYDVQQHDAROb25lMREwDwYD -VQQKDAhTeW5jcGxheTERMA8GA1UEAwwIU3luY3BsYXkxHjAcBgkqhkiG9w0BCQEW -D2RldkBzeW5jcGxheS5wbIIJAJe1HOCC73ylMAkGA1UdEwQCMAAwCwYDVR0PBAQD -AgTwMF0GA1UdEQRWMFSCUjxzcGVjaWZ5LXRoZS1zYW1lLWNvbW1vbi1uYW1lLXRo -YXQteW91LXVzZWQtd2hpbGUtZ2VuZXJhdGluZy1jc3ItaW4tdGhlLWxhc3Qtc3Rl -cD4wDQYJKoZIhvcNAQELBQADggEBAI+jWXb3nRYS1aOUduiF7VAVIaouYRPgkdb8 -p/W0S36KZ7jJXIQurXxx2znjtdC60qwBjJSqLnI1D6lWP6SdX8LMCNmJjtO/dZaX -zoTDqBEcjXp7Up17Zi2liCtYNYAFDdEX9wWMhrg26gY/IccBwm2wKD+B6MfOBTJc -/DRrp4cf/XcUCoNRCmU6JDFABKc63fK2Tr3ClTrXq2qYTaR4xIVu6EUAJGE+zMja -rOT4vXzvAPPHCf03uYCm8jagoxX9Qi7NyAWnyJOgO0DNnhKyxe1MZEkQV1X8m1ko -BVdikLM5hUHIzI2+5Q7o4jr2CHb5oL5OF4PUypwfeRe+PzoI9qs= +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.key b/server.key deleted file mode 100644 index 4b08fd3..0000000 --- a/server.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpgIBAAKCAQEAztIvcvQyF/7mkydJFQZCfE3z2j7toq1Dst/+sgasUWMs045x -N5a6h3/H8yMPDmw/sc/FAVvT+kB2btVLkv8YmlYFCIe8HdWd3JncJrNBGzY066z0 -IsG2gyHjzR3P+iKp535fGSEPeFXJENKSrP5A83CPlvrgWFij2hyc5nAoncq9YE8M -9XXDMiIIGHtgHZX/vx8riodpfpBVS+u7HCken8jD4kwcAIFNE0PmBcaju9v/qDwC -cqbKnL6dLSIVs1qyrmkblQUVvpP2NxDs58e885r1vfAUP9HduL507JuhJP8FR3mP -X6wmGu8QYj58rrMgDXW7A5qxUR6S/kj/PaO0bwIDAQABAoIBAQCdeVQ/hseNlbwi -V0tCAt4kn4YIqtltf9eIHx1il8okvgqnmlGCLfYgrZ6k9hDEvo9Q6A3sluq0JNkI -UseUST+GY/C5KgCGBtyH127U2K+99Gwe9jV9MVmyRt+YkhaFMleDt6jZDV8mFMtf -T7X2mjECKrZqEuM24WcA78BXhRan7RMl77ZQFrFNn+TccFItNUoeP75vD+5aLeH+ -ovWbMM3EaSFMPKoOZ3yJ78IEtD1B61DLOjZNlYsli7phmJR0veb8GHhllDovZ6ru -d5k56r5AqFLSV5Xp29Z0+8mbVOjlgDZ5EYYdyyo0KKX3sADtR2f2/ktIZuFPQFsC -nXbKnRVhAoGBAPeI9ft1B5htUdukb7IUcrdLoPv2cl9uw7e/kUemTO51Uk3QSnex -LUYe5JHoQDBdG7tN88xJLzp/ybH8+SublSrwnF7lqs+TRE3NGI05SIprpMIV2dAo -A+/6icg2jqeGt7ZZv2Ge9VzcxuxwmO5hkx51OlulBuEzdzGOfr/KdZ1jAoGBANXk -y4iQ145V1EkelwZZtMfZH8PZXp7ZVdF2vWG4HDV5frOwIanVtm5VEOW683gJsDnk -9+emRtzd0bm5wjy0K784VWzY/JODzxTgkaBgb1qxoHJv8xdzyo/F3n5WjalOnDQ5 -Y2uBDRj9tMKcZGX9yfnDik4ostT9KRUfy+xhXlCFAoGBAJhw2EvLfqn4aPkDA2aA -AUWwZavF0hnDdPSsHSQq3iXcSptxMusTSfZtAAZ7KUyfinyHPSUJzPQRzny2dhzM -68hN9X7boZ3D68SeEgimxm24bPa5zjHK8uHPQRrzvladOHYsPxg3dJODw13I2eW0 -YY3YU+AseVgnAbibMKOcY2JRAoGBAM4m91iHqYpBBRr4Rb1UJp5bkXcVAVJ+zGzX -Uc5mQaH0mH6dasSi+3K5wFB68IAoblZObfUx2Ki1jpbACAqz6Foj9Im1VTLkXmmT -zCfT6l9yRd8mMRjQfFI39qqzx1OANaNMEJujxTjqBhdv3oBCrDFzGtEb/sizu63l -eIk6i0l1AoGBAPRicQrL3fQ0Ig7O6Oc59Rs2I2FCioGTm4m6PMJzhTQQIkaBHxco -Vd8wWQCbZDdP/8o0uh/qvBey0fuGp9ff0k4rOUJtHTI0YlzdJzAEVyuPt+Z+heXc -KSsqIL/+WFL8pNO0wX0dycVEwSZnZ45wBskTGFZjv9c4Q0rdQBru9QWd ------END RSA PRIVATE KEY----- diff --git a/server.pem b/server.pem new file mode 100644 index 0000000..2b12284 --- /dev/null +++ b/server.pem @@ -0,0 +1,46 @@ +-----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 37bd2b3..7d5d06c 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -14,6 +14,7 @@ from functools import wraps from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS from twisted.internet.protocol import ClientFactory +from twisted.internet.ssl import Certificate, optionsForClientTLS from twisted.internet import reactor, task, defer, threads from twisted.application.internet import ClientService @@ -713,13 +714,11 @@ class SyncplayClient(object): if '[' in host: host = host.strip('[]') port = int(port) - self._endpoint = HostnameEndpoint(reactor, host, port) - try: - self.protocolFactory.options = optionsForClientTLS(hostname=host) - self._clientSupportsTLS = True - except Exception as e: - self.protocolFactory.options = None - self._clientSupportsTLS = False + with open('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) def retry(retries): self._lastGlobalUpdate = None diff --git a/syncplayServer.py b/syncplayServer.py index e3eca96..053c93b 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -15,22 +15,14 @@ except AttributeError: from OpenSSL import crypto from twisted.internet import reactor, ssl -from twisted.internet.endpoints import TCP4ServerEndpoint, SSL4ServerEndpoint, TCP6ServerEndpoint +from twisted.internet.endpoints import SSL4ServerEndpoint, TCP6ServerEndpoint from syncplay.server import SyncFactory, ConfigurationGetter -with open('server.crt', 'r') as f: - cert_data = f.read() -with open('server.key', 'r') as f: - key_data = f.read() +with open('server.pem') as f: + certData = f.read() -cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) -key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_data) -options = ssl.CertificateOptions( - privateKey=key, - certificate=cert, - acceptableProtocols=[b'h2'], -) +certificate = ssl.PrivateCertificate.loadPEM(certData).options() if __name__ == '__main__': argsGetter = ConfigurationGetter() @@ -47,8 +39,7 @@ if __name__ == '__main__': args.max_username_length, args.stats_db_file ) - #endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) - endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), options) + endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), certificate) endpoint4.listen(factory) #endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) #endpoint6.listen(factory) From d7e577ef0496531f6096131efb25fa9ca698e85a Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sun, 3 Feb 2019 23:11:27 +0100 Subject: [PATCH 24/62] TLS: server listens on both IPv4 and IPv6 --- syncplayServer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/syncplayServer.py b/syncplayServer.py index 053c93b..c8b9b36 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -15,7 +15,7 @@ except AttributeError: from OpenSSL import crypto from twisted.internet import reactor, ssl -from twisted.internet.endpoints import SSL4ServerEndpoint, TCP6ServerEndpoint +from twisted.internet.endpoints import SSL4ServerEndpoint from syncplay.server import SyncFactory, ConfigurationGetter @@ -39,8 +39,8 @@ if __name__ == '__main__': args.max_username_length, args.stats_db_file ) - endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), certificate) + endpoint4 = SSL4ServerEndpoint(reactor, int(args.port), certificate, interface='0.0.0.0') endpoint4.listen(factory) - #endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) - #endpoint6.listen(factory) + endpoint6 = SSL4ServerEndpoint(reactor, int(args.port), certificate, interface='::') + endpoint6.listen(factory) reactor.run() From 3eeaeed6dd53d8d2fa628070e6d42ad92f653880 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 4 Feb 2019 15:34:35 +0100 Subject: [PATCH 25/62] startTLS: enabled on server and client, TCP stays as default --- server.crt | 19 ------------------ server.pem | 46 ------------------------------------------- syncplay/client.py | 8 ++++---- syncplay/protocols.py | 42 ++++++++++----------------------------- syncplayServer.py | 11 ++++++----- 5 files changed, 20 insertions(+), 106 deletions(-) delete mode 100644 server.crt delete mode 100644 server.pem 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 7d5d06c..25fef58 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -714,11 +714,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 56c0a22..87e626d 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -77,15 +77,10 @@ class SyncClientProtocol(JSONCommandProtocol): def connectionMade(self): self._client.initProtocol(self) - if self._client._clientSupportsTLS: - if self._client._serverSupportsTLS: - self.sendTLS({"startTLS": "send"}) - self._client.ui.showMessage(getMessage("startTLS-initiated")) - else: - self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server")) - self.sendHello() + if self._client._serverSupportsTLS: + self.sendTLS({"startTLS": "send"}) + self._client.ui.showMessage("Attempting secure connection") else: - self._client.ui.showMessage(getMessage("startTLS-not-supported-client")) self.sendHello() def connectionLost(self, reason): @@ -320,6 +315,7 @@ class SyncClientProtocol(JSONCommandProtocol): def handleError(self, error): 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"]) @@ -332,25 +328,10 @@ class SyncClientProtocol(JSONCommandProtocol): def handleTLS(self, message): answer = message["startTLS"] if "startTLS" in message else None - if "true" in answer and not self.logged and self._client.protocolFactory.options is not None: + if "true" in answer and not self.logged: self.transport.startTLS(self._client.protocolFactory.options) - elif "false" in answer: - self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server")) - self.sendHello() - - def handshakeCompleted(self): - self._serverCertificateTLS = self.transport.getPeerCertificate() - self._subjectTLS = self._serverCertificateTLS.get_subject().CN - self._issuerTLS = self._serverCertificateTLS.get_issuer().CN - self._expiredTLS =self._serverCertificateTLS.has_expired() - self._expireDateTLS = self._serverCertificateTLS.get_notAfter() - - self._encryptedConnectionTLS = self.transport.protocol._tlsConnection - self._connVersionTLS = self._encryptedConnectionTLS.get_protocol_version_name() - self._cipherNameTLS = self._encryptedConnectionTLS.get_cipher_name() - - self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(self._connVersionTLS)) - + self._client.ui.showMessage("Secure connection established") + self.sendHello() class SyncServerProtocol(JSONCommandProtocol): def __init__(self, factory): @@ -657,12 +638,9 @@ class SyncServerProtocol(JSONCommandProtocol): def handleTLS(self, message): inquiry = message["startTLS"] if "startTLS" in message else None - if "send" in inquiry: - if not self.isLogged() and self._factory.options is not None: - self.sendTLS({"startTLS": "true"}) - self.transport.startTLS(self._factory.options) - else: - self.sendTLS({"startTLS": "false"}) + 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() From 7910ddec15f18f116c1d226bc42d1d0c655b7b3c Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 4 Feb 2019 16:01:27 +0100 Subject: [PATCH 26/62] startTLS: added --tls [file] argument in server --- syncplay/client.py | 1 - syncplay/messages_en.py | 2 +- syncplay/protocols.py | 15 ++++++++++----- syncplay/server.py | 15 +++++++++++---- syncplayServer.py | 11 +++-------- 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 25fef58..16f4edd 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -718,7 +718,6 @@ class SyncplayClient(object): trust_root = Certificate.loadPEM(cert_file.read()) 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/messages_en.py b/syncplay/messages_en.py index 2c8f84f..e86e0db 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -444,7 +444,7 @@ en = { "server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters "server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", "server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", - "server-startTLS-argument": "Enable TLS connections using the certificate files in the path provided", + "server-tls-argument": "Enable TLS connections using the certificate file provided", "server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).", "server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 87e626d..b319ce3 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -328,10 +328,12 @@ class SyncClientProtocol(JSONCommandProtocol): def handleTLS(self, message): answer = message["startTLS"] if "startTLS" in message else None - if "true" in answer and not self.logged: + if "true" in answer and not self.logged and self._client.protocolFactory.options is not None: self.transport.startTLS(self._client.protocolFactory.options) self._client.ui.showMessage("Secure connection established") - self.sendHello() + elif "false" in answer: + self._client.ui.showErrorMessage("This server does not support TLS") + self.sendHello() class SyncServerProtocol(JSONCommandProtocol): def __init__(self, factory): @@ -638,9 +640,12 @@ class SyncServerProtocol(JSONCommandProtocol): 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) + if "send" in inquiry: + if not self.isLogged() and self._factory.options is not None: + self.sendTLS({"startTLS": "true"}) + self.transport.startTLS(self._factory.options) + else: + self.sendTLS({"startTLS": "false"}) class PingService(object): diff --git a/syncplay/server.py b/syncplay/server.py index 563822e..42bfd73 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -27,7 +27,7 @@ from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomString class SyncFactory(Factory): def __init__(self, port='', password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH, - maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCertPath=None): + maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCert=None): self.isolateRooms = isolateRooms print(getMessage("welcome-server-notification").format(syncplay.version)) self.port = port @@ -56,8 +56,15 @@ class SyncFactory(Factory): else: self._statsDbHandle = None self.options = None - if tlsCertPath is not None: - self._allowTLSconnections(tlsCertPath) + if tlsCert is not None: + try: + with open(tlsCert) as f: + certData = f.read() + cert = ssl.PrivateCertificate.loadPEM(certData).options() + self.options = cert + except Exception as e: + print(e) + print("Cannot import certificate. TLS support not enabled.") def buildProtocol(self, addr): return SyncServerProtocol(self) @@ -655,4 +662,4 @@ class ConfigurationGetter(object): self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH)) self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH)) self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument")) - self._argparser.add_argument('--tls', metavar='path', type=str, nargs='?', help=getMessage("server-startTLS-argument")) + self._argparser.add_argument('--tls', metavar='file', type=str, nargs='?', help=getMessage("server-tls-argument")) diff --git a/syncplayServer.py b/syncplayServer.py index 0be1618..679e6c4 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -14,16 +14,11 @@ except AttributeError: warnings.warn("You must run Syncplay with Python 3.4 or newer!") from OpenSSL import crypto -from twisted.internet import reactor, ssl +from twisted.internet import reactor from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint from syncplay.server import SyncFactory, ConfigurationGetter -with open('cert/server.pem') as f: - certData = f.read() - -cert = ssl.PrivateCertificate.loadPEM(certData).options() - if __name__ == '__main__': argsGetter = ConfigurationGetter() args = argsGetter.getConfiguration() @@ -37,9 +32,9 @@ if __name__ == '__main__': args.disable_chat, args.max_chat_message_length, args.max_username_length, - args.stats_db_file + args.stats_db_file, + args.tls ) - factory.options = cert endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) endpoint4.listen(factory) endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) From 81e0a2004745120c219d54526be6107b769e5c3c Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 4 Feb 2019 17:50:07 +0100 Subject: [PATCH 27/62] startTLS: avoid retrying TLS connection if hostname is an IP address --- syncplay/client.py | 10 +++++++--- syncplay/protocols.py | 6 +----- syncplayServer.py | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 16f4edd..65d6f36 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -714,10 +714,14 @@ class SyncplayClient(object): if '[' in host: host = host.strip('[]') port = int(port) - with open('cert/server.crt') as cert_file: - trust_root = Certificate.loadPEM(cert_file.read()) self._endpoint = HostnameEndpoint(reactor, host, port) - self.protocolFactory.options = optionsForClientTLS(hostname=host, trustRoot = trust_root) + try: + with open('cert/server.crt') as cert_file: + trust_root = Certificate.loadPEM(cert_file.read()) + self.protocolFactory.options = optionsForClientTLS(hostname=host, trustRoot = trust_root) + except Exception as e: + self.protocolFactory.options = None + self._serverSupportsTLS = False def retry(retries): self._lastGlobalUpdate = None diff --git a/syncplay/protocols.py b/syncplay/protocols.py index b319ce3..ed7590e 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -81,16 +81,13 @@ class SyncClientProtocol(JSONCommandProtocol): self.sendTLS({"startTLS": "send"}) self._client.ui.showMessage("Attempting secure connection") else: + self._client.ui.showErrorMessage("This server does not support TLS") self.sendHello() def connectionLost(self, reason): try: if "Invalid DNS-ID" in str(reason.value): self._client._serverSupportsTLS = False - elif "certificate verify failed" in str(reason.value): - self._client._serverSupportsTLS = False - elif "tlsv1 alert protocol version" in str(reason.value): - self._client._clientSupportsTLS = False except: pass self._client.destroyProtocol() @@ -315,7 +312,6 @@ class SyncClientProtocol(JSONCommandProtocol): def handleError(self, error): 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"]) diff --git a/syncplayServer.py b/syncplayServer.py index 679e6c4..39d23cc 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -13,7 +13,6 @@ except AttributeError: import warnings warnings.warn("You must run Syncplay with Python 3.4 or newer!") -from OpenSSL import crypto from twisted.internet import reactor from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint From f16ceed53981024d291b3a0ba3330f9e2fbbd013 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 4 Feb 2019 18:07:18 +0100 Subject: [PATCH 28/62] startTLS: support Let's Encrypt certificates --- syncplay/server.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index 42bfd73..eea2f87 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -5,6 +5,7 @@ import os import random import time from string import Template +from OpenSSL import crypto from twisted.enterprise import adbapi from twisted.internet import task, reactor, ssl @@ -27,7 +28,7 @@ from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomString class SyncFactory(Factory): def __init__(self, port='', password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH, - maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCert=None): + maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None, tlsCertPath=None): self.isolateRooms = isolateRooms print(getMessage("welcome-server-notification").format(syncplay.version)) self.port = port @@ -56,12 +57,18 @@ class SyncFactory(Factory): else: self._statsDbHandle = None self.options = None - if tlsCert is not None: + if tlsCertPath is not None: try: - with open(tlsCert) as f: - certData = f.read() - cert = ssl.PrivateCertificate.loadPEM(certData).options() - self.options = cert + privkey=open(tlsCertPath+'/privkey.pem', 'rt').read() + certif=open(tlsCertPath+'/cert.pem', 'rt').read() + chain=open(tlsCertPath+'/chain.pem', 'rt').read() + + privkeypyssl=crypto.load_privatekey(crypto.FILETYPE_PEM,privkey) + certifpyssl=crypto.load_certificate(crypto.FILETYPE_PEM,certif) + chainpyssl=[crypto.load_certificate(crypto.FILETYPE_PEM,chain)] + + contextFactory=ssl.CertificateOptions(privateKey=privkeypyssl,certificate=certifpyssl,extraCertChain=chainpyssl) + self.options = contextFactory except Exception as e: print(e) print("Cannot import certificate. TLS support not enabled.") From dc5c63a57c3463dd0c72ee466298aff428fa3e42 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 4 Feb 2019 19:50:09 +0100 Subject: [PATCH 29/62] startTLS: fall back to TCP if server certificate is not validated --- syncplay/protocols.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index ed7590e..a7f1a07 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -88,6 +88,8 @@ class SyncClientProtocol(JSONCommandProtocol): try: if "Invalid DNS-ID" in str(reason.value): self._client._serverSupportsTLS = False + elif "certificate verify failed" in str(reason.value): + self._client._serverSupportsTLS = False except: pass self._client.destroyProtocol() From 07fd1434ba990434c0862b3ed7a57d1ebdb849b3 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 16:13:47 +0100 Subject: [PATCH 30/62] startTLS: correct certificate loading and validation --- syncplay/client.py | 7 ++++--- syncplay/server.py | 26 ++++---------------------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 65d6f36..e31e77c 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1,5 +1,6 @@ import ast +import certifi import collections import hashlib import os @@ -32,6 +33,8 @@ from syncplay.messages import getMissingStrings, getMessage from syncplay.protocols import SyncClientProtocol from syncplay.utils import isMacOS +os.environ['SSL_CERT_FILE'] = certifi.where() + class SyncClientFactory(ClientFactory): def __init__(self, client, retry=constants.RECONNECT_RETRIES): @@ -716,9 +719,7 @@ class SyncplayClient(object): port = int(port) self._endpoint = HostnameEndpoint(reactor, host, port) try: - with open('cert/server.crt') as cert_file: - trust_root = Certificate.loadPEM(cert_file.read()) - self.protocolFactory.options = optionsForClientTLS(hostname=host, trustRoot = trust_root) + self.protocolFactory.options = optionsForClientTLS(hostname=host) except Exception as e: self.protocolFactory.options = None self._serverSupportsTLS = False diff --git a/syncplay/server.py b/syncplay/server.py index eea2f87..0816372 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -58,20 +58,7 @@ class SyncFactory(Factory): self._statsDbHandle = None self.options = None if tlsCertPath is not None: - try: - privkey=open(tlsCertPath+'/privkey.pem', 'rt').read() - certif=open(tlsCertPath+'/cert.pem', 'rt').read() - chain=open(tlsCertPath+'/chain.pem', 'rt').read() - - privkeypyssl=crypto.load_privatekey(crypto.FILETYPE_PEM,privkey) - certifpyssl=crypto.load_certificate(crypto.FILETYPE_PEM,certif) - chainpyssl=[crypto.load_certificate(crypto.FILETYPE_PEM,chain)] - - contextFactory=ssl.CertificateOptions(privateKey=privkeypyssl,certificate=certifpyssl,extraCertChain=chainpyssl) - self.options = contextFactory - except Exception as e: - print(e) - print("Cannot import certificate. TLS support not enabled.") + self._allowTLSconnections(tlsCertPath) def buildProtocol(self, addr): return SyncServerProtocol(self) @@ -218,7 +205,7 @@ class SyncFactory(Factory): else: watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex()) - def _allowTLSconnections(self, path): + def _allowTLSconnections(path): try: privkey = open(path+'/privkey.pem', 'rt').read() certif = open(path+'/cert.pem', 'rt').read() @@ -228,16 +215,11 @@ class SyncFactory(Factory): certifpyssl = crypto.load_certificate(crypto.FILETYPE_PEM, certif) chainpyssl = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)] - dhFilePath = FilePath(path+'/dh_param.pem') - dhParams = ssl.DiffieHellmanParameters.fromFile(dhFilePath) - - contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, - extraCertChain=chainpyssl, dhParameters=dhParams, - raiseMinimumTo=ssl.TLSVersion.TLSv1_2) + contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, extraCertChain=chainpyssl) self.options = contextFactory except Exception as e: print(e) - print("TLS support is not enabled.") + print("Cannot import certificates. TLS support not enabled.") class StatsRecorder(object): From 2219bcf4dc56fd1c0b7907415d12dd47e61013c6 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 16:16:50 +0100 Subject: [PATCH 31/62] startTLS: fix server cert loading function --- syncplay/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index 0816372..4d62016 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -205,7 +205,7 @@ class SyncFactory(Factory): else: watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex()) - def _allowTLSconnections(path): + def _allowTLSconnections(self, path): try: privkey = open(path+'/privkey.pem', 'rt').read() certif = open(path+'/cert.pem', 'rt').read() @@ -651,4 +651,4 @@ class ConfigurationGetter(object): self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH)) self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH)) self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument")) - self._argparser.add_argument('--tls', metavar='file', type=str, nargs='?', help=getMessage("server-tls-argument")) + self._argparser.add_argument('--tls', metavar='path', type=str, nargs='?', help=getMessage("server-tls-argument")) From 0c46f545105fe369c9d5cb6b6dfe6a59296e6b9b Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 16:28:48 +0100 Subject: [PATCH 32/62] startTLS: making TLS support (and dependencies) optional --- syncplay/client.py | 4 ---- syncplay/protocols.py | 2 +- syncplay/server.py | 4 +--- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index e31e77c..7a2b64d 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -1,6 +1,5 @@ import ast -import certifi import collections import hashlib import os @@ -15,7 +14,6 @@ from functools import wraps from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS from twisted.internet.protocol import ClientFactory -from twisted.internet.ssl import Certificate, optionsForClientTLS from twisted.internet import reactor, task, defer, threads from twisted.application.internet import ClientService @@ -33,8 +31,6 @@ from syncplay.messages import getMissingStrings, getMessage from syncplay.protocols import SyncClientProtocol from syncplay.utils import isMacOS -os.environ['SSL_CERT_FILE'] = certifi.where() - class SyncClientFactory(ClientFactory): def __init__(self, client, retry=constants.RECONNECT_RETRIES): diff --git a/syncplay/protocols.py b/syncplay/protocols.py index a7f1a07..9545dce 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -81,7 +81,7 @@ class SyncClientProtocol(JSONCommandProtocol): self.sendTLS({"startTLS": "send"}) self._client.ui.showMessage("Attempting secure connection") else: - self._client.ui.showErrorMessage("This server does not support TLS") + self._client.ui.showErrorMessage("TLS is not supported") self.sendHello() def connectionLost(self, reason): diff --git a/syncplay/server.py b/syncplay/server.py index 4d62016..a76c0a5 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -5,7 +5,6 @@ import os import random import time from string import Template -from OpenSSL import crypto from twisted.enterprise import adbapi from twisted.internet import task, reactor, ssl @@ -14,7 +13,6 @@ from twisted.internet.protocol import Factory try: from OpenSSL import crypto from twisted.internet import ssl - from twisted.python.filepath import FilePath except: pass @@ -219,7 +217,7 @@ class SyncFactory(Factory): self.options = contextFactory except Exception as e: print(e) - print("Cannot import certificates. TLS support not enabled.") + print("TLS support is not enabled.") class StatsRecorder(object): From 3fc9dcf0af97d49e7ea75f84ee8e6c99127c505b Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 16:48:23 +0100 Subject: [PATCH 33/62] startTLS: handle ui messages --- syncplay/messages_de.py | 4 ++-- syncplay/messages_en.py | 6 +++--- syncplay/messages_it.py | 4 ++-- syncplay/messages_ru.py | 4 ++-- syncplay/protocols.py | 8 ++++---- syncplay/server.py | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 1adbfb9..e67cbf8 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -312,8 +312,8 @@ de = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", - "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-not-supported-client": "This client does not support TLS", + "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-not-supported-client": "TLS is not supported", "startTLS-not-supported-server": "This server does not support TLS", # About dialog - TODO: Translate diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index e86e0db..f5971cd 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -313,8 +313,8 @@ en = { "update-menu-label": "Check for &update", "startTLS-initiated": "Attempting secure connection", - "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-not-supported-client": "This client does not support TLS", + "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-not-supported-client": "TLS is not supported", "startTLS-not-supported-server": "This server does not support TLS", # About dialog @@ -444,7 +444,7 @@ en = { "server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters "server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", "server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", - "server-tls-argument": "Enable TLS connections using the certificate file provided", + "server-startTLS-argument": "Enable TLS connections using the certificate files in the path provided", "server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).", "server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 99fd305..33fcab9 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -313,8 +313,8 @@ it = { "update-menu-label": "Controlla la presenza di &aggiornamenti", "startTLS-initiated": "Tentativo di connessione sicura in corso", - "startTLS-secure-connection-ok": "Connessione sicura stabilita ({})", - "startTLS-not-supported-client": "Questo client non supporta TLS", + "startTLS-secure-connection-ok": "Connessione sicura stabilita", + "startTLS-not-supported-client": "TLS non è supportato", "startTLS-not-supported-server": "Questo server non supporta TLS", # About dialog diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index b4f6332..7d258a2 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -315,8 +315,8 @@ ru = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", - "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-not-supported-client": "This client does not support TLS", + "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-not-supported-client": "TLS is not supported", "startTLS-not-supported-server": "This server does not support TLS", # About dialog - TODO: Translate diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 9545dce..c06ba2f 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -79,9 +79,9 @@ class SyncClientProtocol(JSONCommandProtocol): self._client.initProtocol(self) if self._client._serverSupportsTLS: self.sendTLS({"startTLS": "send"}) - self._client.ui.showMessage("Attempting secure connection") + self._client.ui.showMessage(getMessage("startTLS-initiated")) else: - self._client.ui.showErrorMessage("TLS is not supported") + self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-client")) self.sendHello() def connectionLost(self, reason): @@ -328,9 +328,9 @@ class SyncClientProtocol(JSONCommandProtocol): answer = message["startTLS"] if "startTLS" in message else None if "true" in answer and not self.logged and self._client.protocolFactory.options is not None: self.transport.startTLS(self._client.protocolFactory.options) - self._client.ui.showMessage("Secure connection established") + self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok")) elif "false" in answer: - self._client.ui.showErrorMessage("This server does not support TLS") + self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server")) self.sendHello() class SyncServerProtocol(JSONCommandProtocol): diff --git a/syncplay/server.py b/syncplay/server.py index a76c0a5..4a0c142 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -649,4 +649,4 @@ class ConfigurationGetter(object): self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH)) self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH)) self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument")) - self._argparser.add_argument('--tls', metavar='path', type=str, nargs='?', help=getMessage("server-tls-argument")) + self._argparser.add_argument('--tls', metavar='path', type=str, nargs='?', help=getMessage("server-startTLS-argument")) From ff3e49b87d559574772feb8dddb90c90f414b49a Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 20:11:59 +0100 Subject: [PATCH 34/62] startTLS: show negotiated TLS version --- syncplay/messages_de.py | 2 +- syncplay/messages_en.py | 2 +- syncplay/messages_it.py | 2 +- syncplay/messages_ru.py | 2 +- syncplay/protocols.py | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index e67cbf8..6c8b3d0 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -312,7 +312,7 @@ de = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", - "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-secure-connection-ok": "Secure connection established ({})", "startTLS-not-supported-client": "TLS is not supported", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index f5971cd..b907614 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -313,7 +313,7 @@ en = { "update-menu-label": "Check for &update", "startTLS-initiated": "Attempting secure connection", - "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-secure-connection-ok": "Secure connection established ({})", "startTLS-not-supported-client": "TLS is not supported", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 33fcab9..cf31785 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -313,7 +313,7 @@ it = { "update-menu-label": "Controlla la presenza di &aggiornamenti", "startTLS-initiated": "Tentativo di connessione sicura in corso", - "startTLS-secure-connection-ok": "Connessione sicura stabilita", + "startTLS-secure-connection-ok": "Connessione sicura stabilita ({})", "startTLS-not-supported-client": "TLS non è supportato", "startTLS-not-supported-server": "Questo server non supporta TLS", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 7d258a2..01e9e1b 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -315,7 +315,7 @@ ru = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", - "startTLS-secure-connection-ok": "Secure connection established", + "startTLS-secure-connection-ok": "Secure connection established ({})", "startTLS-not-supported-client": "TLS is not supported", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index c06ba2f..d2a2522 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -328,7 +328,8 @@ class SyncClientProtocol(JSONCommandProtocol): answer = message["startTLS"] if "startTLS" in message else None if "true" in answer and not self.logged and self._client.protocolFactory.options is not None: self.transport.startTLS(self._client.protocolFactory.options) - self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok")) + TLSConnVersion = self.transport.protocol._tlsConnection.get_protocol_version_name() + self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(TLSConnVersion)) elif "false" in answer: self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server")) self.sendHello() From e6912dc6594cc6d80e669aaaafca31814f2d2cd0 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Tue, 5 Feb 2019 20:26:44 +0100 Subject: [PATCH 35/62] startTLS: separate not-supported messages for client and server --- syncplay/client.py | 3 ++- syncplay/messages_de.py | 2 +- syncplay/messages_en.py | 2 +- syncplay/messages_it.py | 2 +- syncplay/messages_ru.py | 2 +- syncplay/protocols.py | 12 ++++++++---- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 7a2b64d..37bd2b3 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -716,9 +716,10 @@ class SyncplayClient(object): self._endpoint = HostnameEndpoint(reactor, host, port) try: self.protocolFactory.options = optionsForClientTLS(hostname=host) + self._clientSupportsTLS = True except Exception as e: self.protocolFactory.options = None - self._serverSupportsTLS = False + self._clientSupportsTLS = False def retry(retries): self._lastGlobalUpdate = None diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 6c8b3d0..1adbfb9 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -313,7 +313,7 @@ de = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-not-supported-client": "TLS is not supported", + "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", # About dialog - TODO: Translate diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index b907614..2c8f84f 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -314,7 +314,7 @@ en = { "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-not-supported-client": "TLS is not supported", + "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", # About dialog diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index cf31785..99fd305 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -314,7 +314,7 @@ it = { "startTLS-initiated": "Tentativo di connessione sicura in corso", "startTLS-secure-connection-ok": "Connessione sicura stabilita ({})", - "startTLS-not-supported-client": "TLS non è supportato", + "startTLS-not-supported-client": "Questo client non supporta TLS", "startTLS-not-supported-server": "Questo server non supporta TLS", # About dialog diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 01e9e1b..b4f6332 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -316,7 +316,7 @@ ru = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-not-supported-client": "TLS is not supported", + "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", # About dialog - TODO: Translate diff --git a/syncplay/protocols.py b/syncplay/protocols.py index d2a2522..0c65e6c 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -77,11 +77,15 @@ class SyncClientProtocol(JSONCommandProtocol): def connectionMade(self): self._client.initProtocol(self) - if self._client._serverSupportsTLS: - self.sendTLS({"startTLS": "send"}) - self._client.ui.showMessage(getMessage("startTLS-initiated")) + if self._client._clientSupportsTLS: + if self._client._serverSupportsTLS: + self.sendTLS({"startTLS": "send"}) + self._client.ui.showMessage(getMessage("startTLS-initiated")) + else: + self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server")) + self.sendHello() else: - self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-client")) + self._client.ui.showMessage(getMessage("startTLS-not-supported-client")) self.sendHello() def connectionLost(self, reason): From 9e59a8500bd8f168d554e5cc0fb31be2dc8e3cdc Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Thu, 7 Feb 2019 12:46:42 +0100 Subject: [PATCH 36/62] startTLS: get server certificate only after handshake --- syncplay/protocols.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 0c65e6c..11d3e91 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -94,6 +94,8 @@ class SyncClientProtocol(JSONCommandProtocol): self._client._serverSupportsTLS = False elif "certificate verify failed" in str(reason.value): self._client._serverSupportsTLS = False + elif "tlsv1 alert protocol version" in str(reason.value): + self._client._clientSupportsTLS = False except: pass self._client.destroyProtocol() @@ -332,12 +334,20 @@ class SyncClientProtocol(JSONCommandProtocol): answer = message["startTLS"] if "startTLS" in message else None if "true" in answer and not self.logged and self._client.protocolFactory.options is not None: self.transport.startTLS(self._client.protocolFactory.options) - TLSConnVersion = self.transport.protocol._tlsConnection.get_protocol_version_name() - self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(TLSConnVersion)) elif "false" in answer: self._client.ui.showErrorMessage(getMessage("startTLS-not-supported-server")) self.sendHello() + def handshakeCompleted(self): + self._serverCertificateTLS = self.transport.getPeerCertificate() + self._subjectTLS = self._serverCertificateTLS.get_subject().CN + self._issuerTLS = self._serverCertificateTLS.get_issuer().CN + self._expiredTLS =self._serverCertificateTLS.has_expired() + self._expireDateTLS = self._serverCertificateTLS.get_notAfter() + self._connVersionTLS = self.transport.protocol._tlsConnection.get_protocol_version_name() + self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(self._connVersionTLS)) + + class SyncServerProtocol(JSONCommandProtocol): def __init__(self, factory): self._factory = factory From abaf178fa6a7b6522d99c59533c7f40f11d2b022 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Thu, 7 Feb 2019 12:55:52 +0100 Subject: [PATCH 37/62] startTLS: get encrypted connection information after handshake --- syncplay/protocols.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 11d3e91..56c0a22 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -344,7 +344,11 @@ class SyncClientProtocol(JSONCommandProtocol): self._issuerTLS = self._serverCertificateTLS.get_issuer().CN self._expiredTLS =self._serverCertificateTLS.has_expired() self._expireDateTLS = self._serverCertificateTLS.get_notAfter() - self._connVersionTLS = self.transport.protocol._tlsConnection.get_protocol_version_name() + + self._encryptedConnectionTLS = self.transport.protocol._tlsConnection + self._connVersionTLS = self._encryptedConnectionTLS.get_protocol_version_name() + self._cipherNameTLS = self._encryptedConnectionTLS.get_cipher_name() + self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(self._connVersionTLS)) From b19f2eaaaccdfbe0385e564668af09ca7b6ad085 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Thu, 7 Feb 2019 15:31:35 +0100 Subject: [PATCH 38/62] startTLS: set minimum protocol version to TLS 1.2 --- syncplay/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/syncplay/server.py b/syncplay/server.py index 4a0c142..86d9c97 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -213,7 +213,8 @@ class SyncFactory(Factory): certifpyssl = crypto.load_certificate(crypto.FILETYPE_PEM, certif) chainpyssl = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)] - contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, extraCertChain=chainpyssl) + contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, + extraCertChain=chainpyssl, raiseMinimumTo=ssl.TLSVersion.TLSv1_2) self.options = contextFactory except Exception as e: print(e) From 9b4338146ed82b4b1ae3f69050e5b67cdd3f084e Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Thu, 7 Feb 2019 15:34:43 +0100 Subject: [PATCH 39/62] startTLS: enable Diffie-Hellman based key exchange on server Following the guidelines reported in the Twisted documentation here https://twistedmatrix.com/documents/current/core/howto/ssl.html this commit enables Diffie-Hellman based key exchange on the server. Before launching the server, a parameters .pem file must be generated as detailed in https://twistedmatrix.com/documents/18.9.0/api/twisted.internet.ssl.DiffieHellmanParameters.html by running `openssl dhparam -out dh_param_1024.pem -2 1024` on the server machine. This parameters file must be placed in the same path that contains the server certificates. --- syncplay/server.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/syncplay/server.py b/syncplay/server.py index 86d9c97..cdf24c8 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -13,6 +13,7 @@ from twisted.internet.protocol import Factory try: from OpenSSL import crypto from twisted.internet import ssl + from twisted.python.filepath import FilePath except: pass @@ -213,8 +214,12 @@ class SyncFactory(Factory): certifpyssl = crypto.load_certificate(crypto.FILETYPE_PEM, certif) chainpyssl = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)] + dhFilePath = FilePath(path+'/dh_param_1024.pem') + dhParams = ssl.DiffieHellmanParameters.fromFile(dhFilePath) + contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, - extraCertChain=chainpyssl, raiseMinimumTo=ssl.TLSVersion.TLSv1_2) + extraCertChain=chainpyssl, dhParameters=dhParams, + raiseMinimumTo=ssl.TLSVersion.TLSv1_2) self.options = contextFactory except Exception as e: print(e) From 9721cf32e43a2a1d3a7737678122361fe97b4bb5 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Thu, 7 Feb 2019 23:26:20 +0100 Subject: [PATCH 40/62] startTLS: change DH key parameter filename --- syncplay/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/server.py b/syncplay/server.py index cdf24c8..563822e 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -214,7 +214,7 @@ class SyncFactory(Factory): certifpyssl = crypto.load_certificate(crypto.FILETYPE_PEM, certif) chainpyssl = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)] - dhFilePath = FilePath(path+'/dh_param_1024.pem') + dhFilePath = FilePath(path+'/dh_param.pem') dhParams = ssl.DiffieHellmanParameters.fromFile(dhFilePath) contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, From dbb2b1c9ff9a994f742791153a713b067f5a16ca Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 11 Feb 2019 14:10:21 +0100 Subject: [PATCH 41/62] startTLS: add accepted ciphers list and remove DH params --- syncplay/server.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/syncplay/server.py b/syncplay/server.py index 563822e..0769c21 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -13,7 +13,6 @@ from twisted.internet.protocol import Factory try: from OpenSSL import crypto from twisted.internet import ssl - from twisted.python.filepath import FilePath except: pass @@ -206,19 +205,21 @@ class SyncFactory(Factory): def _allowTLSconnections(self, path): try: - privkey = open(path+'/privkey.pem', 'rt').read() + privKey = open(path+'/privkey.pem', 'rt').read() certif = open(path+'/cert.pem', 'rt').read() chain = open(path+'/chain.pem', 'rt').read() - privkeypyssl = crypto.load_privatekey(crypto.FILETYPE_PEM, privkey) - certifpyssl = crypto.load_certificate(crypto.FILETYPE_PEM, certif) - chainpyssl = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)] + privKeyPySSL = crypto.load_privatekey(crypto.FILETYPE_PEM, privKey) + certifPySSL = crypto.load_certificate(crypto.FILETYPE_PEM, certif) + chainPySSL = [crypto.load_certificate(crypto.FILETYPE_PEM, chain)] - dhFilePath = FilePath(path+'/dh_param.pem') - dhParams = ssl.DiffieHellmanParameters.fromFile(dhFilePath) + cipherListString = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:"\ + "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:"\ + "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384" + accCiphers = ssl.AcceptableCiphers.fromOpenSSLCipherString(cipherListString) - contextFactory = ssl.CertificateOptions(privateKey=privkeypyssl, certificate=certifpyssl, - extraCertChain=chainpyssl, dhParameters=dhParams, + contextFactory = ssl.CertificateOptions(privateKey=privKeyPySSL, certificate=certifPySSL, + extraCertChain=chainPySSL, acceptableCiphers=accCiphers, raiseMinimumTo=ssl.TLSVersion.TLSv1_2) self.options = contextFactory except Exception as e: From 6dcaf218911699388f2ae359c44387915ab5d5d8 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 11 Feb 2019 15:54:16 +0100 Subject: [PATCH 42/62] startTLS: drop connection if server certificate is not valid --- syncplay/messages_de.py | 1 + syncplay/messages_en.py | 1 + syncplay/messages_it.py | 1 + syncplay/messages_ru.py | 1 + syncplay/protocols.py | 4 ++-- 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 1adbfb9..c693d2f 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -313,6 +313,7 @@ de = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", + "startTLS-server-certificate-invalid": "Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party.", "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 2c8f84f..4bfa882 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -314,6 +314,7 @@ en = { "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", + "startTLS-server-certificate-invalid": "Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party.", "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 99fd305..339863d 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -314,6 +314,7 @@ it = { "startTLS-initiated": "Tentativo di connessione sicura in corso", "startTLS-secure-connection-ok": "Connessione sicura stabilita ({})", + "startTLS-server-certificate-invalid": "Connessione sicura non riuscita. Il certificato di sicurezza di questo server non è valido. La comunicazione potrebbe essere intercettata da una terza parte.", "startTLS-not-supported-client": "Questo client non supporta TLS", "startTLS-not-supported-server": "Questo server non supporta TLS", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index b4f6332..17bdd00 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -316,6 +316,7 @@ ru = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", + "startTLS-server-certificate-invalid": "Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party.", "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 56c0a22..b1dbef2 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -92,10 +92,10 @@ class SyncClientProtocol(JSONCommandProtocol): try: if "Invalid DNS-ID" in str(reason.value): self._client._serverSupportsTLS = False - elif "certificate verify failed" in str(reason.value): - self._client._serverSupportsTLS = False elif "tlsv1 alert protocol version" in str(reason.value): self._client._clientSupportsTLS = False + elif "certificate verify failed" in str(reason.value): + self.dropWithError(getMessage("startTLS-server-certificate-invalid")) except: pass self._client.destroyProtocol() From 24cb681acd2ff53880014c6a93cf7013bcd12c5a Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 11 Feb 2019 16:25:26 +0100 Subject: [PATCH 43/62] startTLS: add link to 'startTLS-server-certificate-invalid' error message --- syncplay/messages_de.py | 2 +- syncplay/messages_en.py | 2 +- syncplay/messages_it.py | 2 +- syncplay/messages_ru.py | 2 +- syncplay/ui/gui.py | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index c693d2f..1c2e149 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -313,7 +313,7 @@ de = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-server-certificate-invalid": "Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party.", + "startTLS-server-certificate-invalid": 'Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party. For further details and troubleshooting see here.', "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 4bfa882..eb7a71c 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -314,7 +314,7 @@ en = { "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-server-certificate-invalid": "Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party.", + "startTLS-server-certificate-invalid": 'Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party. For further details and troubleshooting see here.', "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 339863d..6415810 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -314,7 +314,7 @@ it = { "startTLS-initiated": "Tentativo di connessione sicura in corso", "startTLS-secure-connection-ok": "Connessione sicura stabilita ({})", - "startTLS-server-certificate-invalid": "Connessione sicura non riuscita. Il certificato di sicurezza di questo server non è valido. La comunicazione potrebbe essere intercettata da una terza parte.", + "startTLS-server-certificate-invalid": 'Connessione sicura non riuscita. Il certificato di sicurezza di questo server non è valido. La comunicazione potrebbe essere intercettata da una terza parte. Per ulteriori dettagli e informazioni sulla risoluzione del problema, clicca qui.', "startTLS-not-supported-client": "Questo client non supporta TLS", "startTLS-not-supported-server": "Questo server non supporta TLS", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 17bdd00..038daa1 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -316,7 +316,7 @@ ru = { # startTLS messages - TODO: Translate "startTLS-initiated": "Attempting secure connection", "startTLS-secure-connection-ok": "Secure connection established ({})", - "startTLS-server-certificate-invalid": "Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party.", + "startTLS-server-certificate-invalid": 'Secure connection failed. The server uses an invalid security certificate. This communication could be intercepted by a third party. For further details and troubleshooting see here.', "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index ed12463..cfaf161 100755 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -779,6 +779,7 @@ class MainWindow(QtWidgets.QMainWindow): if criticalerror: QtWidgets.QMessageBox.critical(self, "Syncplay", message) message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">") + message = message.replace("<a href="https://syncplay.pl/trouble">", '').replace("</a>", "") message = message.replace("\n", "
") message = "".format(constants.STYLE_ERRORNOTIFICATION) + message + "" self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "
") From 23f4d686f23d811355dd76c96c21928847575c32 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 11 Feb 2019 16:37:30 +0100 Subject: [PATCH 44/62] startTLS: client-side verification of the certifi bundle --- syncplay/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index 37bd2b3..aa35f00 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -19,7 +19,7 @@ from twisted.application.internet import ClientService try: import certifi - from twisted.internet.ssl import optionsForClientTLS + from twisted.internet.ssl import Certificate, optionsForClientTLS os.environ['SSL_CERT_FILE'] = certifi.where() except: pass @@ -715,9 +715,13 @@ class SyncplayClient(object): port = int(port) self._endpoint = HostnameEndpoint(reactor, host, port) try: + caCertFP = open(certifi.where()) + caCertTwisted = Certificate.loadPEM(caCertFP.read()) + caCertFP.close() self.protocolFactory.options = optionsForClientTLS(hostname=host) self._clientSupportsTLS = True except Exception as e: + self.ui.showDebugMessage(str(e)) self.protocolFactory.options = None self._clientSupportsTLS = False From cb661803288a3cb8fdf2a38ae7654838c7d140f2 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 11 Feb 2019 17:19:37 +0100 Subject: [PATCH 45/62] startTLS: import and cosmetic fixes --- syncplay/client.py | 2 +- syncplay/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index aa35f00..4d91049 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -115,7 +115,7 @@ 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']: diff --git a/syncplay/server.py b/syncplay/server.py index 0769c21..5ecf951 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -7,7 +7,7 @@ import time from string import Template from twisted.enterprise import adbapi -from twisted.internet import task, reactor, ssl +from twisted.internet import task, reactor from twisted.internet.protocol import Factory try: From 285428fb0373c48aad675ce07966e715633b3608 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 11 Feb 2019 19:15:43 +0100 Subject: [PATCH 46/62] Check if the server is listening on either IPv4 or IPv6, avoid errors happening in dual stack configurations --- syncplayServer.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/syncplayServer.py b/syncplayServer.py index 39d23cc..ccb8537 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -18,6 +18,28 @@ from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint from syncplay.server import SyncFactory, ConfigurationGetter +class ServerStatus: pass + +def isListening6(f): + ServerStatus.listening6 = True + +def isListening4(f): + ServerStatus.listening4 = True + +def failed6(f): + ServerStatus.listening6 = False + print(f.value) + print("IPv6 listening failed.") + +def failed4(f): + ServerStatus.listening4 = False + if ServerStatus.listening6 and "Address already in use" in str(f.value): + pass + else: + print(f.value) + print("IPv4 listening failed.") + + if __name__ == '__main__': argsGetter = ConfigurationGetter() args = argsGetter.getConfiguration() @@ -34,8 +56,12 @@ if __name__ == '__main__': args.stats_db_file, args.tls ) - endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) - endpoint4.listen(factory) endpoint6 = TCP6ServerEndpoint(reactor, int(args.port)) - endpoint6.listen(factory) - reactor.run() + endpoint6.listen(factory).addCallbacks(isListening6, failed6) + endpoint4 = TCP4ServerEndpoint(reactor, int(args.port)) + endpoint4.listen(factory).addCallbacks(isListening4, failed4) + if ServerStatus.listening6 or ServerStatus.listening4: + reactor.run() + else: + print("Unable to listen using either IPv4 and IPv6 protocols. Quitting the server now.") + sys.exit() From 8d52acbd88b66fbb276a1da81a4a93ecaa0a5ac4 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 11 Feb 2019 19:55:30 +0100 Subject: [PATCH 47/62] Get exact error value from Twisted if IPv6 listener is dual stack --- syncplayServer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/syncplayServer.py b/syncplayServer.py index ccb8537..b0538d3 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -15,6 +15,7 @@ except AttributeError: from twisted.internet import reactor from twisted.internet.endpoints import TCP4ServerEndpoint, TCP6ServerEndpoint +from twisted.internet.error import CannotListenError from syncplay.server import SyncFactory, ConfigurationGetter @@ -33,7 +34,7 @@ def failed6(f): def failed4(f): ServerStatus.listening4 = False - if ServerStatus.listening6 and "Address already in use" in str(f.value): + if f.type is CannotListenError and ServerStatus.listening6: pass else: print(f.value) From 466e8533d625d22d37b2da43faa0df55764d41cf Mon Sep 17 00:00:00 2001 From: Etoh Date: Thu, 14 Feb 2019 23:03:53 +0000 Subject: [PATCH 48/62] Add secure connection icon to press for cert details --- buildPy2exe.py | 1 + resources/green_lock.png | Bin 0 -> 791 bytes syncplay/__init__.py | 2 +- syncplay/client.py | 5 +++++ syncplay/messages_de.py | 5 +++++ syncplay/messages_en.py | 7 ++++++- syncplay/messages_it.py | 7 ++++++- syncplay/messages_ru.py | 7 ++++++- syncplay/protocols.py | 7 ++++++- syncplay/ui/consoleUI.py | 3 +++ syncplay/ui/gui.py | 35 ++++++++++++++++++++++++++++++++--- 11 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 resources/green_lock.png diff --git a/buildPy2exe.py b/buildPy2exe.py index 0dfe4af..975484f 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -658,6 +658,7 @@ guiIcons = [ 'resources/mpv.png', 'resources/vlc.png', 'resources/house.png', 'resources/film_link.png', 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/chevrons_right.png', 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', + 'resources/green_lock.png', 'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png', 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png', 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png', diff --git a/resources/green_lock.png b/resources/green_lock.png new file mode 100644 index 0000000000000000000000000000000000000000..61197400c24ecc9bb8cf268cb243eeeef4d1ac45 GIT binary patch literal 791 zcmV+y1L*vTP)N2bZe?^J zG%hhNHDpIvQUCw~l}SWFR5(v%QtL~TaTGpO)+D1Sx)XLUE6C_VMECksbiXHRSx`iM zh|)5cu`!s_;nc}CryIKo8*8he0_WUv&RJ{HA0YXyzvJoo`BJ{-hr@Z__nh-Q&w0)( z0OrKNz(7Sf95w|4ft1hZD|x-%lH2W0_4M?ZIy*Zn^u4nNRYW3@>QE>&l}@KoEEZ8J zm8NqpmqQ|vfZcANve|6afBXL*t_TK$llgoenM?+JhRfxG)9C~iAel@~hs9!d2kTYZ~*dn59IBt8FUHmf_%TtxC1HG zGU_13ddNhB7C-Jm#_vM%Hz2udTD)1I1Mrhk0SxYkWT+&23Nm~aGIpLr=OF1bkPocI zcJN2p0vWkVrOR{xeJqUMf_&Kr>DvzJ=So=u*j7VYH$jF@Xlt~tg@l;DUV%Jcq62Vq zR|W8CFPjgsfdeCCwQ$cD^kV23nz3Aq%m#je!LgMSH%nF_RsoTCH%gd#j%g<=klXA$TMVjOWvewUs)N;5+@CT>x VKThU?(8>S+002ovPDHLkV1l4xWRU;> literal 0 HcmV?d00001 diff --git a/syncplay/__init__.py b/syncplay/__init__.py index 7eefcf6..3932bf1 100755 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,5 +1,5 @@ version = '1.6.3' revision = ' beta' milestone = 'Yoitsu' -release_number = '72' +release_number = '73' projectURL = 'https://syncplay.pl/' diff --git a/syncplay/client.py b/syncplay/client.py index 4d91049..33a1d90 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -398,6 +398,7 @@ class SyncplayClient(object): self.ui.showMessage(getMessage("current-offset-notification").format(self._userOffset)) def onDisconnect(self): + self.ui.setSSLMode(False) if self._config['pauseOnLeave']: self.setPaused(True) self.lastPausedOnLeaveTime = time.time() @@ -727,6 +728,7 @@ class SyncplayClient(object): def retry(retries): self._lastGlobalUpdate = None + self.ui.setSSLMode(False) if retries == 0: self.onDisconnect() if retries > constants.RECONNECT_RETRIES: @@ -1476,6 +1478,9 @@ class UiManager(object): self.showOSDMessage(messageString, duration=constants.OSD_DURATION) self.__ui.showMessage(messageString) + def setSSLMode(self, sslMode, sslInformation=""): + self.__ui.setSSLMode(sslMode, sslInformation) + def showMessage(self, message, noPlayer=False, noTimestamp=False, OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL): if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, OSDType=OSDType, mood=mood) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 1c2e149..78e2f68 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -71,6 +71,9 @@ de = { "syncplay-uptodate-notification": "Syncplay ist aktuell", "syncplay-updateavailable-notification": "Eine neuere Version von Syncplay ist verfügbar. Soll die Download-Seite geöffnet werden?", + "ssl-information-title": "SSL Certificate Details", # TODO: Translate + "ssl-information-message": "Subject: {}\nIssuer: {}\nExpiration Date: {}\nConnection Version: {}\nCipher: {}", # TODO: Translate + "mplayer-file-required-notification": "Syncplay für mplayer benötigt eine Dateiangabe beim Start", "mplayer-file-required-notification/example": "Anwendungsbeispiel: syncplay [optionen] [url|pfad/]Dateiname", "mplayer2-required": "Syncplay ist inkompatibel zu MPlayer 1.x, bitte nutze MPlayer2 oder mpv", @@ -410,6 +413,8 @@ de = { "reset-tooltip": "Alle Einstellungen auf Standardwerte zurücksetzen.", "update-server-list-tooltip": "Mit syncplay.pl verbinden um die Liste öffentlicher Server zu aktualisieren.", + "sslconnection-tooltip": "Securely connected to server. Press button for certificate details", # TODO: Translate + "joinroom-tooltip": "Den aktuellen Raum verlassen und stattdessen den angegebenen betreten.", "seektime-msgbox-label": "Springe zur angegebenen Zeit (in Sekunden oder min:sek). Verwende +/- zum relativen Springen.", "ready-tooltip": "Zeigt an, ob du bereit zum anschauen bist", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index eb7a71c..b7bc8d4 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -71,6 +71,9 @@ en = { "syncplay-uptodate-notification": "Syncplay is up to date", "syncplay-updateavailable-notification": "A new version of Syncplay is available. Do you want to visit the release page?", + "ssl-information-title": "SSL Certificate Details", + "ssl-information-message": "Subject: {}\nIssuer: {}\nExpiration Date: {}\nConnection Version: {}\nCipher: {}", + "mplayer-file-required-notification": "Syncplay using mplayer requires you to provide file when starting", "mplayer-file-required-notification/example": "Usage example: syncplay [options] [url|path/]filename", "mplayer2-required": "Syncplay is incompatible with MPlayer 1.x, please use mplayer2 or mpv", @@ -267,7 +270,7 @@ en = { "run-label": "Run Syncplay", "storeandrun-label": "Store configuration and run Syncplay", - "contact-label": "Feel free to e-mail dev@syncplay.pl, chat via the #Syncplay IRC channel on irc.freenode.net, raise an issue via GitHub, like us on Facebook, follow us on Twitter, or visit https://syncplay.pl/. NOTE: Chat messages are not encrypted so do not use Syncplay to send sensitive information.", + "contact-label": "Feel free to e-mail dev@syncplay.pl, chat via the #Syncplay IRC channel on irc.freenode.net, raise an issue via GitHub, like us on Facebook, follow us on Twitter, or visit https://syncplay.pl/. Do not use Syncplay to send sensitive information.", "joinroom-label": "Join room", "joinroom-menu-label": "Join room {}", @@ -409,6 +412,8 @@ en = { "reset-tooltip": "Reset all settings to the default configuration.", "update-server-list-tooltip": "Connect to syncplay.pl to update list of public servers.", + "sslconnection-tooltip": "Securely connected to server. Press button for certificate details", + "joinroom-tooltip": "Leave current room and joins specified room.", "seektime-msgbox-label": "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.", "ready-tooltip": "Indicates whether you are ready to watch.", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 6415810..d94db60 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -71,6 +71,9 @@ it = { "syncplay-uptodate-notification": "Syncplay è aggiornato", "syncplay-updateavailable-notification": "Una nuova versione di Syncplay è disponibile. Vuoi visitare la pagina delle release?", + "ssl-information-title": "SSL Certificate Details", # TODO: Translate + "ssl-information-message": "Subject: {}\nIssuer: {}\nExpiration Date: {}\nConnection Version: {}\nCipher: {}", # TODO: Translate + "mplayer-file-required-notification": "Utilizzare Syncplay con mplayer di selezionare il file all'avvio", "mplayer-file-required-notification/example": "Esempio di utilizzo: syncplay [opzioni] [url|percorso/]nomefile", "mplayer2-required": "Syncplay non è compatibile con MPlayer 1.x, per favore utilizza mplayer2 or mpv", @@ -267,7 +270,7 @@ it = { "run-label": "Avvia Syncplay", "storeandrun-label": "Salva la configurazione e avvia Syncplay", - "contact-label": "Sentiti libero di inviare un'e-mail a dev@syncplay.pl, chattare tramite il canale IRC #Syncplay su irc.freenode.net, segnalare un problema su GitHub, lasciare un like sulla nostra pagina Facebook, seguirci su Twitter, o visitare https://syncplay.pl/. NOTA: i messaggi di chat non sono cifrati, quindi non usare Syncplay per inviare dati sensibili.", + "contact-label": "Sentiti libero di inviare un'e-mail a dev@syncplay.pl, chattare tramite il canale IRC #Syncplay su irc.freenode.net, segnalare un problema su GitHub, lasciare un like sulla nostra pagina Facebook, seguirci su Twitter, o visitare https://syncplay.pl/. Non usare Syncplay per inviare dati sensibili.", # TODO: Check translation "joinroom-label": "Entra nella stanza", "joinroom-menu-label": "Entra nella stanza {}", @@ -409,6 +412,8 @@ it = { "reset-tooltip": "Ripristina le impostazioni iniziali di Syncplay.", "update-server-list-tooltip": "Scarica la lista dei server pubblici da syncplay.pl.", + "sslconnection-tooltip": "Securely connected to server. Press button for certificate details", # TODO: Translate + "joinroom-tooltip": "Lascia la stanza attuale e entra in quella specificata.", "seektime-msgbox-label": "Salta all'istante di tempo specificato (in secondi / min:sec). Usa +/- per una ricerca relativa.", "ready-tooltip": "Indica quando sei pronto a guardare.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 038daa1..4ab13bf 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -71,6 +71,9 @@ ru = { "syncplay-uptodate-notification": "У вас последняя версия Syncplay", "syncplay-updateavailable-notification": "Доступна новая версия Syncplay. Хотите открыть страницу релиза?", + "ssl-information-title": "SSL Certificate Details", # TODO: Translate + "ssl-information-message": "Subject: {}\nIssuer: {}\nExpiration Date: {}\nConnection Version: {}\nCipher: {}", # TODO: Translate + "mplayer-file-required-notification": "Для использования Syncplay с mplayer необходимо передать файл в качестве параметра", "mplayer-file-required-notification/example": "Пример использования: syncplay [options] [url|path/]filename", "mplayer2-required": "Syncplay не совместим с MPlayer 1.x, пожалуйста, используйте mplayer2 или mpv", @@ -269,7 +272,7 @@ ru = { "run-label": "Запустить", "storeandrun-label": "Сохранить и запустить", - "contact-label": "Есть идея, нашли ошибку или хотите оставить отзыв? Пишите на dev@syncplay.pl, в IRC канал #Syncplay на irc.freenode.net или задавайте вопросы через GitHub. Кроме того, заходите на www.syncplay.pl за инорфмацией, помощью и обновлениями! NOTE: Chat messages are not encrypted so do not use Syncplay to send sensitive information.", # TODO: Translate last sentence + "contact-label": "Есть идея, нашли ошибку или хотите оставить отзыв? Пишите на dev@syncplay.pl, в IRC канал #Syncplay на irc.freenode.net или задавайте вопросы через GitHub. Кроме того, заходите на www.syncplay.pl за инорфмацией, помощью и обновлениями! Do not use Syncplay to send sensitive information.", # TODO: Translate last sentence "joinroom-label": "Зайти в комнату", "joinroom-menu-label": "Зайти в комнату {}", @@ -411,6 +414,8 @@ ru = { "reset-tooltip": "Сбрасывает все настройки Syncplay в начальное состояние.", "update-server-list-tooltip": "Обновить список публичных серверов от syncplay.pl.", + "sslconnection-tooltip": "Securely connected to server. Press button for certificate details", # TODO: Translate + "joinroom-tooltip": "Покинуть комнату и зайти в другую, указанную комнату.", "seektime-msgbox-label": "Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.", "ready-tooltip": "Показывает, готовы ли Вы к просмотру или нет.", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index b1dbef2..0d75de0 100755 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -339,17 +339,22 @@ class SyncClientProtocol(JSONCommandProtocol): self.sendHello() def handshakeCompleted(self): + from datetime import datetime self._serverCertificateTLS = self.transport.getPeerCertificate() self._subjectTLS = self._serverCertificateTLS.get_subject().CN self._issuerTLS = self._serverCertificateTLS.get_issuer().CN self._expiredTLS =self._serverCertificateTLS.has_expired() - self._expireDateTLS = self._serverCertificateTLS.get_notAfter() + self._expireDateTLS = datetime.strptime(self._serverCertificateTLS.get_notAfter().decode('ascii'), '%Y%m%d%H%M%SZ') self._encryptedConnectionTLS = self.transport.protocol._tlsConnection self._connVersionTLS = self._encryptedConnectionTLS.get_protocol_version_name() self._cipherNameTLS = self._encryptedConnectionTLS.get_cipher_name() self._client.ui.showMessage(getMessage("startTLS-secure-connection-ok").format(self._connVersionTLS)) + self._client.ui.setSSLMode( + True, + getMessage("ssl-information-message") + .format(self._subjectTLS,self._issuerTLS, self._expireDateTLS, self._connVersionTLS, self._cipherNameTLS)) class SyncServerProtocol(JSONCommandProtocol): diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 38f10ff..3063ff3 100755 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -108,6 +108,9 @@ class ConsoleUI(threading.Thread): def showErrorMessage(self, message, criticalerror=False): print("ERROR:\t" + message) + def setSSLMode(self, sslMode, sslInformation): + pass + def _extractSign(self, m): if m: if m == "-": diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index cfaf161..814fcd9 100755 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -120,6 +120,7 @@ class AboutDialog(QtWidgets.QDialog): self.setWindowTitle(getMessage("about-dialog-title")) if isWindows(): self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) + self.setWindowIcon(QtGui.QPixmap(resourcespath + 'syncplay.png')) nameLabel = QtWidgets.QLabel("
Syncplay
") nameLabel.setFont(QtGui.QFont("Helvetica", 18)) linkLabel = QtWidgets.QLabel("
syncplay.pl
") @@ -172,6 +173,8 @@ class MainWindow(QtWidgets.QMainWindow): playlistState = [] updatingPlaylist = False playlistIndex = None + sslInformation = "N/A" + sslMode = False def setPlaylistInsertPosition(self, newPosition): if not self.playlist.isEnabled(): @@ -427,6 +430,14 @@ class MainWindow(QtWidgets.QMainWindow): self.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH) self.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH) + def setSSLMode(self, sslMode, sslInformation): + self.sslMode = sslMode + self.sslInformation = sslInformation + self.sslButton.setVisible(sslMode) + + def getSSLInformation(self): + return self.sslInformation + def showMessage(self, message, noTimestamp=False): message = str(message) username = None @@ -1201,6 +1212,7 @@ class MainWindow(QtWidgets.QMainWindow): window.outputbox.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) window.outputlabel = QtWidgets.QLabel(getMessage("notifications-heading-label")) + window.outputlabel.setMinimumHeight(27) window.chatInput = QtWidgets.QLineEdit() window.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH) window.chatInput.returnPressed.connect(self.sendChatMessage) @@ -1237,21 +1249,29 @@ class MainWindow(QtWidgets.QMainWindow): self.listTreeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.listTreeView.customContextMenuRequested.connect(self.openRoomMenu) window.listlabel = QtWidgets.QLabel(getMessage("userlist-heading-label")) + window.listlabel.setMinimumHeight(27) + window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'green_lock.png'),"") + window.sslButton.setVisible(False) + window.sslButton.setFixedHeight(27) + window.sslButton.setFixedWidth(27) + window.sslButton.pressed.connect(self.openSSLDetails) + window.sslButton.setToolTip(getMessage("sslconnection-tooltip")) window.listFrame = QtWidgets.QFrame() window.listFrame.setLineWidth(0) window.listFrame.setMidLineWidth(0) window.listFrame.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) window.listLayout.setContentsMargins(0, 0, 0, 0) - window.userlistLayout = QtWidgets.QVBoxLayout() + window.userlistLayout = QtWidgets.QGridLayout() window.userlistFrame = QtWidgets.QFrame() window.userlistFrame.setLineWidth(0) window.userlistFrame.setMidLineWidth(0) window.userlistFrame.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) window.userlistLayout.setContentsMargins(0, 0, 0, 0) window.userlistFrame.setLayout(window.userlistLayout) - window.userlistLayout.addWidget(window.listlabel) - window.userlistLayout.addWidget(window.listTreeView) + window.userlistLayout.addWidget(window.listlabel, 0, 0, Qt.AlignLeft) + window.userlistLayout.addWidget(window.sslButton, 0, 2, Qt.AlignRight) + window.userlistLayout.addWidget(window.listTreeView, 1, 0, 1, 3) window.listSplit = QtWidgets.QSplitter(Qt.Vertical, self) window.listSplit.addWidget(window.userlistFrame) @@ -1510,6 +1530,15 @@ class MainWindow(QtWidgets.QMainWindow): if not isMacOS(): window.mainLayout.setMenuBar(window.menuBar) + @needsClient + def openSSLDetails(self): + QtWidgets.QMessageBox.information( + self, + getMessage("ssl-information-title"), + "[{}]\n{}".format(getMessage("ssl-information-title"),self.getSSLInformation()) + ) + + def openAbout(self): aboutMsgBox = AboutDialog() aboutMsgBox.exec_() From 3695c31ec757bd00942d8d894d40afc7120e588b Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Fri, 15 Feb 2019 15:17:57 +0100 Subject: [PATCH 49/62] startTLS: set lock button appearance on macOS --- syncplay/ui/gui.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 814fcd9..a3830b8 100755 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1250,10 +1250,18 @@ class MainWindow(QtWidgets.QMainWindow): self.listTreeView.customContextMenuRequested.connect(self.openRoomMenu) window.listlabel = QtWidgets.QLabel(getMessage("userlist-heading-label")) window.listlabel.setMinimumHeight(27) - window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'green_lock.png'),"") - window.sslButton.setVisible(False) - window.sslButton.setFixedHeight(27) - window.sslButton.setFixedWidth(27) + if isMacOS: + window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'green_lock.png').scaled(14, 14),"") + window.sslButton.setVisible(False) + window.sslButton.setFixedHeight(21) + window.sslButton.setFixedWidth(21) + window.sslButton.setMinimumSize(21, 21) + window.sslButton.setStyleSheet("QPushButton:!hover{border: 1px solid gray;} QPushButton:hover{border:2px solid black;}") + else: + window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'green_lock.png'),"") + window.sslButton.setVisible(False) + window.sslButton.setFixedHeight(27) + window.sslButton.setFixedWidth(27) window.sslButton.pressed.connect(self.openSSLDetails) window.sslButton.setToolTip(getMessage("sslconnection-tooltip")) window.listFrame = QtWidgets.QFrame() @@ -1532,11 +1540,12 @@ class MainWindow(QtWidgets.QMainWindow): @needsClient def openSSLDetails(self): - QtWidgets.QMessageBox.information( - self, - getMessage("ssl-information-title"), - "[{}]\n{}".format(getMessage("ssl-information-title"),self.getSSLInformation()) - ) + boxReturn = QtWidgets.QMessageBox.information( + self, + getMessage("ssl-information-title"), + "[{}]\n{}".format(getMessage("ssl-information-title"),self.getSSLInformation()) + ) + if(boxReturn): self.sslButton.setDown(False) def openAbout(self): From b35a4fec9deb1ccb23f5d9c5c943f956909267a1 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Fri, 15 Feb 2019 16:32:50 +0100 Subject: [PATCH 50/62] startTLS: add custom certificate info dialog --- resources/{green_lock.png => lock_green.png} | Bin resources/lock_green_dialog.png | Bin 0 -> 862 bytes syncplay/protocols.py | 18 ++++-- syncplay/ui/gui.py | 56 ++++++++++++++++--- 4 files changed, 59 insertions(+), 15 deletions(-) rename resources/{green_lock.png => lock_green.png} (100%) create mode 100644 resources/lock_green_dialog.png diff --git a/resources/green_lock.png b/resources/lock_green.png similarity index 100% rename from resources/green_lock.png rename to resources/lock_green.png diff --git a/resources/lock_green_dialog.png b/resources/lock_green_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..fc62b0514b6b4f766e983783ecfbd1d89fc40290 GIT binary patch literal 862 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!n2Vh}LpV4%Za?&Y0OWEOctjR6 zFfeI=Fyo0kzl4B-k|nMYCBgY=CFO}lsSM@i<$9TU*~Q6;1*v-ZMd`EO*+>Bu{R!|1 zasB`QKf`~9hW`zrt3uPxq$TW6C_Dya_?GO9&DtNDbR;QlcWA->gv`TH*?Z!EB58+W za*jk~AC1b`ACY@BJpEu)&i=@(gW(zb!!r*>W*-XAJQAIAI3nvnRQAEB%!3ixK=z@C z?1Mn5sI0?4k;tq=QJIHtcG!}?}44?uK<1mB` zCc)xBEkKP>HDD2-jUe@eko7K51Ovx;)sI6;gY3rCVcfpe7Yqo6Lv1|9f1BXwXJb&Thm7BL; zy?OWk^OqmL{`~!S$-_pOfq}8q)5S3);_%yPnf`|y1lp!Mv*>Fqk|{dD%cWs1Ca9sn zv6S`O|Nr8L{ZPHiqRK){dj6rh5hyU z<$K=kYMsIDwQk0S)q7v)KMu^Cdivb@MVocBpR(`$+1*-{@bLZD4MiK|tN1_i)E3{j z`*60=X5({P?~K!bTqG5|`GY64tmEq~ZBU)rr1nzKQtGh_)9f>{hXl)BZt;F_=%7GB z!Sqn8wM(DT%y~k`osF*u8HT(uRUW8D(PM{DbtZN;^YSyncplay is using an encrypted connection to {}.".format(tlsData["subject"])) + descLabel = QtWidgets.QLabel("Encryption with a digital certificate keeps information private as it's sent to or from the
server {}.".format(tlsData["subject"])) + connDataLabel = QtWidgets.QLabel("Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.".format(tlsData["protocolVersion"], tlsData["cipher"])) + certDataLabel = QtWidgets.QLabel("Certificate issued by {} valid until {}.".format(tlsData["issuer"], tlsData["expires"])) + statusLabel.setFont(QtGui.QFont("Helvetica", 12)) + descLabel.setFont(QtGui.QFont("Helvetica", 12)) + connDataLabel.setFont(QtGui.QFont("Helvetica", 12)) + certDataLabel.setFont(QtGui.QFont("Helvetica", 12)) + lockIconPixmap = QtGui.QPixmap(resourcespath + "lock_green_dialog.png") + lockIconLabel = QtWidgets.QLabel() + lockIconLabel.setPixmap(lockIconPixmap.scaled(64, 64, Qt.KeepAspectRatio)) + certLayout = QtWidgets.QGridLayout() + certLayout.addWidget(lockIconLabel, 1, 0, 3, 1, Qt.AlignLeft | Qt.AlignTop) + certLayout.addWidget(statusLabel, 0, 1, 1, 3) + certLayout.addWidget(descLabel, 1, 1, 1, 3) + certLayout.addWidget(connDataLabel, 2, 1, 1, 3) + certLayout.addWidget(certDataLabel, 3, 1, 1, 3) + closeButton = QtWidgets.QPushButton("Close") + closeButton.setFixedWidth(100) + closeButton.setAutoDefault(False) + closeButton.clicked.connect(self.closeDialog) + certLayout.addWidget(closeButton, 4, 3, 1, 1) + certLayout.setVerticalSpacing(10) + certLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.setSizeGripEnabled(False) + self.setLayout(certLayout) + + def closeDialog(self): + self.close() + + class MainWindow(QtWidgets.QMainWindow): insertPosition = None playlistState = [] @@ -1251,14 +1293,14 @@ class MainWindow(QtWidgets.QMainWindow): window.listlabel = QtWidgets.QLabel(getMessage("userlist-heading-label")) window.listlabel.setMinimumHeight(27) if isMacOS: - window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'green_lock.png').scaled(14, 14),"") + window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'lock_green.png').scaled(14, 14),"") window.sslButton.setVisible(False) window.sslButton.setFixedHeight(21) window.sslButton.setFixedWidth(21) window.sslButton.setMinimumSize(21, 21) window.sslButton.setStyleSheet("QPushButton:!hover{border: 1px solid gray;} QPushButton:hover{border:2px solid black;}") else: - window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'green_lock.png'),"") + window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'lock_green.png'),"") window.sslButton.setVisible(False) window.sslButton.setFixedHeight(27) window.sslButton.setFixedWidth(27) @@ -1540,13 +1582,9 @@ class MainWindow(QtWidgets.QMainWindow): @needsClient def openSSLDetails(self): - boxReturn = QtWidgets.QMessageBox.information( - self, - getMessage("ssl-information-title"), - "[{}]\n{}".format(getMessage("ssl-information-title"),self.getSSLInformation()) - ) - if(boxReturn): self.sslButton.setDown(False) - + sslDetailsBox = CertificateDialog(self.getSSLInformation()) + sslDetailsBox.exec_() + self.sslButton.setDown(False) def openAbout(self): aboutMsgBox = AboutDialog() From b5f191bb68062ba2153e2fceb5b18bfa0c89c198 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Fri, 15 Feb 2019 16:55:47 +0100 Subject: [PATCH 51/62] startTLS: multilanguage strings in certificate dialog --- syncplay/messages_de.py | 12 ++++++++---- syncplay/messages_en.py | 12 ++++++++---- syncplay/messages_it.py | 12 ++++++++---- syncplay/messages_ru.py | 12 ++++++++---- syncplay/ui/gui.py | 12 ++++++------ 5 files changed, 38 insertions(+), 22 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 78e2f68..3849b8d 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -71,9 +71,6 @@ de = { "syncplay-uptodate-notification": "Syncplay ist aktuell", "syncplay-updateavailable-notification": "Eine neuere Version von Syncplay ist verfügbar. Soll die Download-Seite geöffnet werden?", - "ssl-information-title": "SSL Certificate Details", # TODO: Translate - "ssl-information-message": "Subject: {}\nIssuer: {}\nExpiration Date: {}\nConnection Version: {}\nCipher: {}", # TODO: Translate - "mplayer-file-required-notification": "Syncplay für mplayer benötigt eine Dateiangabe beim Start", "mplayer-file-required-notification/example": "Anwendungsbeispiel: syncplay [optionen] [url|pfad/]Dateiname", "mplayer2-required": "Syncplay ist inkompatibel zu MPlayer 1.x, bitte nutze MPlayer2 oder mpv", @@ -320,6 +317,13 @@ de = { "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", + # TLS certificate dialog - TODO: Translate + "tls-information-title": "Certificate Details", + "tls-dialog-status-label": "Syncplay is using an encrypted connection to {}.", + "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it's sent to or from the
server {}.", + "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.", + "tls-dialog-certificate-label": "Certificate issued by {} valid until {}.", + # About dialog - TODO: Translate "about-menu-label": "&About Syncplay", "about-dialog-title": "About Syncplay", @@ -413,7 +417,7 @@ de = { "reset-tooltip": "Alle Einstellungen auf Standardwerte zurücksetzen.", "update-server-list-tooltip": "Mit syncplay.pl verbinden um die Liste öffentlicher Server zu aktualisieren.", - "sslconnection-tooltip": "Securely connected to server. Press button for certificate details", # TODO: Translate + "sslconnection-tooltip": "Securely connected to server. Click for certificate details", # TODO: Translate "joinroom-tooltip": "Den aktuellen Raum verlassen und stattdessen den angegebenen betreten.", "seektime-msgbox-label": "Springe zur angegebenen Zeit (in Sekunden oder min:sek). Verwende +/- zum relativen Springen.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index b7bc8d4..6090e8c 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -71,9 +71,6 @@ en = { "syncplay-uptodate-notification": "Syncplay is up to date", "syncplay-updateavailable-notification": "A new version of Syncplay is available. Do you want to visit the release page?", - "ssl-information-title": "SSL Certificate Details", - "ssl-information-message": "Subject: {}\nIssuer: {}\nExpiration Date: {}\nConnection Version: {}\nCipher: {}", - "mplayer-file-required-notification": "Syncplay using mplayer requires you to provide file when starting", "mplayer-file-required-notification/example": "Usage example: syncplay [options] [url|path/]filename", "mplayer2-required": "Syncplay is incompatible with MPlayer 1.x, please use mplayer2 or mpv", @@ -321,6 +318,13 @@ en = { "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", + # TLS certificate dialog + "tls-information-title": "Certificate Details", + "tls-dialog-status-label": "Syncplay is using an encrypted connection to {}.", + "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it's sent to or from the
server {}.", + "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.", + "tls-dialog-certificate-label": "Certificate issued by {} valid until {}.", + # About dialog "about-menu-label": "&About Syncplay", "about-dialog-title": "About Syncplay", @@ -412,7 +416,7 @@ en = { "reset-tooltip": "Reset all settings to the default configuration.", "update-server-list-tooltip": "Connect to syncplay.pl to update list of public servers.", - "sslconnection-tooltip": "Securely connected to server. Press button for certificate details", + "sslconnection-tooltip": "Securely connected to server. Click for certificate details", "joinroom-tooltip": "Leave current room and joins specified room.", "seektime-msgbox-label": "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index d94db60..d8e0b23 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -71,9 +71,6 @@ it = { "syncplay-uptodate-notification": "Syncplay è aggiornato", "syncplay-updateavailable-notification": "Una nuova versione di Syncplay è disponibile. Vuoi visitare la pagina delle release?", - "ssl-information-title": "SSL Certificate Details", # TODO: Translate - "ssl-information-message": "Subject: {}\nIssuer: {}\nExpiration Date: {}\nConnection Version: {}\nCipher: {}", # TODO: Translate - "mplayer-file-required-notification": "Utilizzare Syncplay con mplayer di selezionare il file all'avvio", "mplayer-file-required-notification/example": "Esempio di utilizzo: syncplay [opzioni] [url|percorso/]nomefile", "mplayer2-required": "Syncplay non è compatibile con MPlayer 1.x, per favore utilizza mplayer2 or mpv", @@ -321,6 +318,13 @@ it = { "startTLS-not-supported-client": "Questo client non supporta TLS", "startTLS-not-supported-server": "Questo server non supporta TLS", + # TLS certificate dialog + "tls-information-title": "Informazioni sul certificato", + "tls-dialog-status-label": "Syncplay è connesso a {} tramite una connessione codificata.", + "tls-dialog-desc-label": "La codifica con un certificato digitale mantiene private le informazioni quando vengono
inviate dal/al server {}.", + "tls-dialog-connection-label": "Informazioni codificate usando Transport Layer Security (TLS), versione {} usando gli
algoritmi di cifratura:{}.", + "tls-dialog-certificate-label": "Certificato rilasciato da {} valido fino al {}.", + # About dialog "about-menu-label": "&Informazioni su Syncplay", "about-dialog-title": "Informazioni su Syncplay", @@ -412,7 +416,7 @@ it = { "reset-tooltip": "Ripristina le impostazioni iniziali di Syncplay.", "update-server-list-tooltip": "Scarica la lista dei server pubblici da syncplay.pl.", - "sslconnection-tooltip": "Securely connected to server. Press button for certificate details", # TODO: Translate + "sslconnection-tooltip": "Securely connected to server. Click for certificate details", # TODO: Translate "joinroom-tooltip": "Lascia la stanza attuale e entra in quella specificata.", "seektime-msgbox-label": "Salta all'istante di tempo specificato (in secondi / min:sec). Usa +/- per una ricerca relativa.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 4ab13bf..f2f0433 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -71,9 +71,6 @@ ru = { "syncplay-uptodate-notification": "У вас последняя версия Syncplay", "syncplay-updateavailable-notification": "Доступна новая версия Syncplay. Хотите открыть страницу релиза?", - "ssl-information-title": "SSL Certificate Details", # TODO: Translate - "ssl-information-message": "Subject: {}\nIssuer: {}\nExpiration Date: {}\nConnection Version: {}\nCipher: {}", # TODO: Translate - "mplayer-file-required-notification": "Для использования Syncplay с mplayer необходимо передать файл в качестве параметра", "mplayer-file-required-notification/example": "Пример использования: syncplay [options] [url|path/]filename", "mplayer2-required": "Syncplay не совместим с MPlayer 1.x, пожалуйста, используйте mplayer2 или mpv", @@ -323,6 +320,13 @@ ru = { "startTLS-not-supported-client": "This client does not support TLS", "startTLS-not-supported-server": "This server does not support TLS", + # TLS certificate dialog - TODO: Translate + "tls-information-title": "Certificate Details", + "tls-dialog-status-label": "Syncplay is using an encrypted connection to {}.", + "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it's sent to or from the
server {}.", + "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.", + "tls-dialog-certificate-label": "Certificate issued by {} valid until {}.", + # About dialog - TODO: Translate "about-menu-label": "&About Syncplay", "about-dialog-title": "About Syncplay", @@ -414,7 +418,7 @@ ru = { "reset-tooltip": "Сбрасывает все настройки Syncplay в начальное состояние.", "update-server-list-tooltip": "Обновить список публичных серверов от syncplay.pl.", - "sslconnection-tooltip": "Securely connected to server. Press button for certificate details", # TODO: Translate + "sslconnection-tooltip": "Securely connected to server. Click for certificate details", # TODO: Translate "joinroom-tooltip": "Покинуть комнату и зайти в другую, указанную комнату.", "seektime-msgbox-label": "Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.", diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index cb44ebe..dd5dfc3 100755 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -175,14 +175,14 @@ class CertificateDialog(QtWidgets.QDialog): self.setWindowTitle("") self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | Qt.WindowCloseButtonHint | Qt.CustomizeWindowHint) else: - self.setWindowTitle(getMessage("about-dialog-title")) + self.setWindowTitle(getMessage("tls-information-title")) if isWindows(): self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) - self.setWindowIcon(QtGui.QPixmap(resourcespath + 'lock_green.png')) - statusLabel = QtWidgets.QLabel("Syncplay is using an encrypted connection to {}.".format(tlsData["subject"])) - descLabel = QtWidgets.QLabel("Encryption with a digital certificate keeps information private as it's sent to or from the
server {}.".format(tlsData["subject"])) - connDataLabel = QtWidgets.QLabel("Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.".format(tlsData["protocolVersion"], tlsData["cipher"])) - certDataLabel = QtWidgets.QLabel("Certificate issued by {} valid until {}.".format(tlsData["issuer"], tlsData["expires"])) + self.setWindowIcon(QtGui.QPixmap(resourcespath + 'syncplay.png')) + statusLabel = QtWidgets.QLabel(getMessage("tls-dialog-status-label").format(tlsData["subject"])) + descLabel = QtWidgets.QLabel(getMessage("tls-dialog-desc-label").format(tlsData["subject"])) + connDataLabel = QtWidgets.QLabel(getMessage("tls-dialog-connection-label").format(tlsData["protocolVersion"], tlsData["cipher"])) + certDataLabel = QtWidgets.QLabel(getMessage("tls-dialog-certificate-label").format(tlsData["issuer"], tlsData["expires"])) statusLabel.setFont(QtGui.QFont("Helvetica", 12)) descLabel.setFont(QtGui.QFont("Helvetica", 12)) connDataLabel.setFont(QtGui.QFont("Helvetica", 12)) From 0add282db67fb5eea9747e496bfd5ce3935294b8 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Fri, 15 Feb 2019 17:07:05 +0100 Subject: [PATCH 52/62] startTLS: set font size in dialog only on macOS --- syncplay/ui/gui.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index dd5dfc3..8c8c088 100755 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -183,10 +183,11 @@ class CertificateDialog(QtWidgets.QDialog): descLabel = QtWidgets.QLabel(getMessage("tls-dialog-desc-label").format(tlsData["subject"])) connDataLabel = QtWidgets.QLabel(getMessage("tls-dialog-connection-label").format(tlsData["protocolVersion"], tlsData["cipher"])) certDataLabel = QtWidgets.QLabel(getMessage("tls-dialog-certificate-label").format(tlsData["issuer"], tlsData["expires"])) - statusLabel.setFont(QtGui.QFont("Helvetica", 12)) - descLabel.setFont(QtGui.QFont("Helvetica", 12)) - connDataLabel.setFont(QtGui.QFont("Helvetica", 12)) - certDataLabel.setFont(QtGui.QFont("Helvetica", 12)) + if isMacOS(): + statusLabel.setFont(QtGui.QFont("Helvetica", 12)) + descLabel.setFont(QtGui.QFont("Helvetica", 12)) + connDataLabel.setFont(QtGui.QFont("Helvetica", 12)) + certDataLabel.setFont(QtGui.QFont("Helvetica", 12)) lockIconPixmap = QtGui.QPixmap(resourcespath + "lock_green_dialog.png") lockIconLabel = QtWidgets.QLabel() lockIconLabel.setPixmap(lockIconPixmap.scaled(64, 64, Qt.KeepAspectRatio)) From 8882c9badefe8d76954e4014114360935829beb6 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Fri, 15 Feb 2019 18:42:51 +0100 Subject: [PATCH 53/62] startTLS: amend messages --- syncplay/messages_de.py | 4 ++-- syncplay/messages_en.py | 4 ++-- syncplay/messages_it.py | 2 +- syncplay/messages_ru.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 3849b8d..b09883e 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -320,7 +320,7 @@ de = { # TLS certificate dialog - TODO: Translate "tls-information-title": "Certificate Details", "tls-dialog-status-label": "Syncplay is using an encrypted connection to {}.", - "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it's sent to or from the
server {}.", + "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it is sent to or from the
server {}.", "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.", "tls-dialog-certificate-label": "Certificate issued by {} valid until {}.", @@ -417,7 +417,7 @@ de = { "reset-tooltip": "Alle Einstellungen auf Standardwerte zurücksetzen.", "update-server-list-tooltip": "Mit syncplay.pl verbinden um die Liste öffentlicher Server zu aktualisieren.", - "sslconnection-tooltip": "Securely connected to server. Click for certificate details", # TODO: Translate + "sslconnection-tooltip": "Securely connected to server. Click for certificate details.", # TODO: Translate "joinroom-tooltip": "Den aktuellen Raum verlassen und stattdessen den angegebenen betreten.", "seektime-msgbox-label": "Springe zur angegebenen Zeit (in Sekunden oder min:sek). Verwende +/- zum relativen Springen.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 6090e8c..80fb020 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -321,7 +321,7 @@ en = { # TLS certificate dialog "tls-information-title": "Certificate Details", "tls-dialog-status-label": "Syncplay is using an encrypted connection to {}.", - "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it's sent to or from the
server {}.", + "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it is sent to or from the
server {}.", "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.", "tls-dialog-certificate-label": "Certificate issued by {} valid until {}.", @@ -416,7 +416,7 @@ en = { "reset-tooltip": "Reset all settings to the default configuration.", "update-server-list-tooltip": "Connect to syncplay.pl to update list of public servers.", - "sslconnection-tooltip": "Securely connected to server. Click for certificate details", + "sslconnection-tooltip": "Securely connected to server. Click for certificate details.", "joinroom-tooltip": "Leave current room and joins specified room.", "seektime-msgbox-label": "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index d8e0b23..022e236 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -416,7 +416,7 @@ it = { "reset-tooltip": "Ripristina le impostazioni iniziali di Syncplay.", "update-server-list-tooltip": "Scarica la lista dei server pubblici da syncplay.pl.", - "sslconnection-tooltip": "Securely connected to server. Click for certificate details", # TODO: Translate + "sslconnection-tooltip": "Connessione sicura al server. Clicca per informazioni sul certificato.", "joinroom-tooltip": "Lascia la stanza attuale e entra in quella specificata.", "seektime-msgbox-label": "Salta all'istante di tempo specificato (in secondi / min:sec). Usa +/- per una ricerca relativa.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index f2f0433..148402b 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -323,7 +323,7 @@ ru = { # TLS certificate dialog - TODO: Translate "tls-information-title": "Certificate Details", "tls-dialog-status-label": "Syncplay is using an encrypted connection to {}.", - "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it's sent to or from the
server {}.", + "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it is sent to or from the
server {}.", "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.", "tls-dialog-certificate-label": "Certificate issued by {} valid until {}.", @@ -418,7 +418,7 @@ ru = { "reset-tooltip": "Сбрасывает все настройки Syncplay в начальное состояние.", "update-server-list-tooltip": "Обновить список публичных серверов от syncplay.pl.", - "sslconnection-tooltip": "Securely connected to server. Click for certificate details", # TODO: Translate + "sslconnection-tooltip": "Securely connected to server. Click for certificate details.", # TODO: Translate "joinroom-tooltip": "Покинуть комнату и зайти в другую, указанную комнату.", "seektime-msgbox-label": "Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.", From 303032b9137d875e1728f3f85d7a5d997db2f4bb Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Fri, 15 Feb 2019 19:00:45 +0100 Subject: [PATCH 54/62] Amend green lock icon filenames --- buildPy2exe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index 975484f..0daf7d0 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -658,7 +658,7 @@ guiIcons = [ 'resources/mpv.png', 'resources/vlc.png', 'resources/house.png', 'resources/film_link.png', 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/chevrons_right.png', 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', - 'resources/green_lock.png', + 'resources/lock_green.png', 'resources/lock_green_dialog.png', 'resources/tick.png', 'resources/lock_open.png', 'resources/empty_checkbox.png', 'resources/tick_checkbox.png', 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png', 'resources/arrow_switch.png', 'resources/film_go.png', 'resources/world_go.png', 'resources/arrow_refresh.png', 'resources/bullet_right_grey.png', From 6fa2eb01c16cef43c1ce482a34e6370395c9235e Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Fri, 15 Feb 2019 19:14:29 +0100 Subject: [PATCH 55/62] Travis: add twisted[tls] and certifi modules --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5acd051..a1d02bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ before_install: - python3 -c "from py2app.recipes import pyside2" install: -- pip3 install twisted appnope requests +- pip3 install twisted[tls] appnope requests certifi before_deploy: - pip3 install dmgbuild From 8d2472bc68da673521352723f66d50dbcb9d13b2 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Fri, 15 Feb 2019 19:37:22 +0100 Subject: [PATCH 56/62] buildpy2app: include cffi in the app bundle --- buildPy2app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildPy2app.py b/buildPy2app.py index 8824f67..96f5db0 100755 --- a/buildPy2app.py +++ b/buildPy2app.py @@ -16,7 +16,7 @@ DATA_FILES = [ OPTIONS = { 'iconfile': 'resources/icon.icns', 'extra_scripts': 'syncplayServer.py', - 'includes': {'PySide2.QtCore', 'PySide2.QtUiTools', 'PySide2.QtGui', 'PySide2.QtWidgets', 'certifi'}, + 'includes': {'PySide2.QtCore', 'PySide2.QtUiTools', 'PySide2.QtGui', 'PySide2.QtWidgets', 'certifi', 'cffi'}, 'excludes': {'PySide', 'PySide.QtCore', 'PySide.QtUiTools', 'PySide.QtGui'}, 'qt_plugins': [ 'platforms/libqcocoa.dylib', From 2ac6417c505d0a1dd14f3625765e10fae3fa987f Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Fri, 15 Feb 2019 19:46:37 +0100 Subject: [PATCH 57/62] buildpy2app: include lua subfolder in the app bundle --- buildPy2app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/buildPy2app.py b/buildPy2app.py index 96f5db0..4e921c9 100755 --- a/buildPy2app.py +++ b/buildPy2app.py @@ -12,6 +12,7 @@ import syncplay APP = ['syncplayClient.py'] DATA_FILES = [ ('resources', glob('resources/*.png') + glob('resources/*.rtf') + glob('resources/*.lua')), + ('resources/lua/intf', glob('resources/lua/intf/*.lua')) ] OPTIONS = { 'iconfile': 'resources/icon.icns', From 9132a9ae56dfc61e2c26461c26d56cb93bfc24e4 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Fri, 15 Feb 2019 23:26:59 +0100 Subject: [PATCH 58/62] startTLS: find certifi.where() if embedded in a zip file --- syncplay/client.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 33a1d90..f107a06 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -20,7 +20,18 @@ from twisted.application.internet import ClientService try: import certifi from twisted.internet.ssl import Certificate, optionsForClientTLS - os.environ['SSL_CERT_FILE'] = certifi.where() + certPath = certifi.where() + if os.path.exists(certPath): + os.environ['SSL_CERT_FILE'] = certPath + elif 'zip' in certPath: + import tempfile + import zipfile + zipPath, memberPath = certPath.split('.zip/') + zipPath += '.zip' + archive = zipfile.ZipFile(zipPath, 'r') + tmpDir = tempfile.gettempdir() + extractedPath = archive.extract(memberPath, tmpDir) + os.environ['SSL_CERT_FILE'] = extractedPath except: pass @@ -716,7 +727,7 @@ class SyncplayClient(object): port = int(port) self._endpoint = HostnameEndpoint(reactor, host, port) try: - caCertFP = open(certifi.where()) + caCertFP = open(os.environ['SSL_CERT_FILE']) caCertTwisted = Certificate.loadPEM(caCertFP.read()) caCertFP.close() self.protocolFactory.options = optionsForClientTLS(hostname=host) From 0f10161d862b07d329554ec9bc027afa7dea1c1e Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sat, 16 Feb 2019 00:27:01 +0100 Subject: [PATCH 59/62] Remove unneeded wrapClientTLS import --- syncplay/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/client.py b/syncplay/client.py index f107a06..4834775 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -12,7 +12,7 @@ import time from copy import deepcopy from functools import wraps -from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS +from twisted.internet.endpoints import HostnameEndpoint from twisted.internet.protocol import ClientFactory from twisted.internet import reactor, task, defer, threads from twisted.application.internet import ClientService From e16775c33bfd5e787684d8dc160d60ceaf11ff6d Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sat, 16 Feb 2019 11:55:03 +0100 Subject: [PATCH 60/62] Explicitely set TLS options to None when server config is not valid --- syncplay/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/syncplay/server.py b/syncplay/server.py index 5ecf951..8bb9a9e 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -223,6 +223,7 @@ class SyncFactory(Factory): raiseMinimumTo=ssl.TLSVersion.TLSv1_2) self.options = contextFactory except Exception as e: + self.options = None print(e) print("TLS support is not enabled.") From b655085f596d685fd6b7a3ea04430c23b3185809 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sat, 16 Feb 2019 12:24:15 +0100 Subject: [PATCH 61/62] Update third-party-notices --- resources/third-party-notices.rtf | 163 ++++++++++++++++++++++++++---- 1 file changed, 142 insertions(+), 21 deletions(-) diff --git a/resources/third-party-notices.rtf b/resources/third-party-notices.rtf index 9d02349..13d3355 100644 --- a/resources/third-party-notices.rtf +++ b/resources/third-party-notices.rtf @@ -1,7 +1,8 @@ -{\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf830 +{\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf600 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} {\*\expandedcolortbl;;} +\vieww13920\viewh8980\viewkind0 \deftab529 \pard\tx529\pardeftab529\pardirnatural\partightenfactor0 @@ -24,25 +25,6 @@ The above copyright notice and this permission notice shall be included in all\ copies or substantial portions of the Software.\ \ -\b PySide\ - -\b0 \ -Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).\ -Contact: PySide team \ -\ -This library is free software; you can redistribute it and/or\ -modify it under the terms of the GNU Lesser General Public\ -License as published by the Free Software Foundation; either\ -version 2.1 of the License, or (at your option) any later version.\ -This library is distributed in the hope that it will be useful,\ -but WITHOUT ANY WARRANTY; without even the implied warranty of\ -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\ -Lesser General Public License for more details.\ -\ -You should have received a copy of the GNU Lesser General Public License\ -along with this program. If not, see \ -\ - \b Qt for Python\ \b0 \ @@ -318,4 +300,143 @@ http://www.apache.org/licenses/LICENSE-2.0\ Unless required by applicable law or agreed to in writing, software distributed under the \ License is distributed on an \'93AS IS\'94 BASIS, WITHOUT WARRANTIES OR CONDI-\ TIONS OF ANY KIND, either express or implied. See the License for the specific lang-\ -uage governing permissions and limitations under the License.} \ No newline at end of file +uage governing permissions and limitations under the License.\ +\ + +\b python-certifi +\b0 \ +\ +This Source Code Form is subject to the terms of the Mozilla Public License,\ +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain\ +one at http://mozilla.org/MPL/2.0/.\ +\ + +\b cffi +\b0 \ +\ +\pard\pardeftab720\partightenfactor0 +\cf0 This package has been mostly done by Armin Rigo with help from\ +Maciej Fija\uc0\u322 kowski. The idea is heavily based (although not directly\ +copied) from LuaJIT ffi by Mike Pall.\ +\ +Other contributors:\ +\ + Google Inc.\ +\pard\tx529\pardeftab529\pardirnatural\partightenfactor0 +\cf0 \ +The MIT License\ +\ +Permission is hereby granted, free of charge, to any person \ +obtaining a copy of this software and associated documentation \ +files (the "Software"), to deal in the Software without \ +restriction, including without limitation the rights to use, \ +copy, modify, merge, publish, distribute, sublicense, and/or \ +sell copies of the Software, and to permit persons to whom the \ +Software is furnished to do so, subject to the following conditions:\ +\ +The above copyright notice and this permission notice shall be included \ +in all copies or substantial portions of the Software.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS \ +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \ +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL \ +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \ +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \ +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER \ +DEALINGS IN THE SOFTWARE.\ +\ + +\b service-identity +\b0 \ +\ +Copyright (c) 2014 Hynek Schlawack\ +\ +Permission is hereby granted, free of charge, to any person obtaining a copy of\ +this software and associated documentation files (the "Software"), to deal in\ +the Software without restriction, including without limitation the rights to\ +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\ +of the Software, and to permit persons to whom the Software is furnished to do\ +so, subject to the following conditions:\ +\ +The above copyright notice and this permission notice shall be included in all\ +copies or substantial portions of the Software.\ +\ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\ +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\ +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\ +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\ +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\ +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\ +SOFTWARE.\ +\ + +\b pyopenssl +\b0 \ +\ +Licensed under the Apache License, Version 2.0 (the \'93License\'94); you may not use this file\ +except in compliance with the License. You may obtain a copy of the License at\ +\ +http://www.apache.org/licenses/LICENSE-2.0\ +\ +Unless required by applicable law or agreed to in writing, software distributed under the \ +License is distributed on an \'93AS IS\'94 BASIS, WITHOUT WARRANTIES OR CONDI-\ +TIONS OF ANY KIND, either express or implied. See the License for the specific lang-\ +uage governing permissions and limitations under the License.\ +\ + +\b cryptography +\b0 \ +\ +Authors listed here: https://github.com/pyca/cryptography/blob/master/AUTHORS.rst\ +\ +Licensed under the Apache License, Version 2.0 (the \'93License\'94); you may not use this file\ +except in compliance with the License. You may obtain a copy of the License at\ +\ +http://www.apache.org/licenses/LICENSE-2.0\ +\ +Unless required by applicable law or agreed to in writing, software distributed under the \ +License is distributed on an \'93AS IS\'94 BASIS, WITHOUT WARRANTIES OR CONDI-\ +TIONS OF ANY KIND, either express or implied. See the License for the specific lang-\ +uage governing permissions and limitations under the License.\ +\ + +\b Icons\ +\ + +\b0 Syncplay uses the following icons and images:\ +\ +- Silk icon set 1.3\ +_________________________________________\ +Mark James\ +http://www.famfamfam.com/lab/icons/silk/\ +_________________________________________\ +\ +This work is licensed under a\ +Creative Commons Attribution 2.5 License.\ +[ http://creativecommons.org/licenses/by/2.5/ ]\ +\ +This means you may use it for any purpose,\ +and make any changes you like.\ +All I ask is that you include a link back\ +to this page in your credits.\ +\ +Are you using this icon set? Send me an email\ +(including a link or picture if available) to\ +mjames@gmail.com\ +\ +Any other questions about this icon set please\ +contact mjames@gmail.com\ +\ +- Silk Companion 1\ +\ +\pard\pardeftab720\partightenfactor0 +\cf0 Copyright Damien Guard - CC-BY 3.0\ +https://damieng.com/creative/icons/silk-companion-1-icons\ +\ +- Padlock free icon\ +CC-BY 3.0\ +Icon made by Maxim Basinski from https://www.flaticon.com/free-icon/padlock_291248\ +\ +\pard\tx529\pardeftab529\pardirnatural\partightenfactor0 +\cf0 \ +} \ No newline at end of file From 9e19fcccfa6ae3611f09e439081f655968334a03 Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Sun, 17 Feb 2019 12:45:20 +0100 Subject: [PATCH 62/62] Add missing space in TLS cipher messages --- syncplay/messages_de.py | 2 +- syncplay/messages_en.py | 2 +- syncplay/messages_it.py | 2 +- syncplay/messages_ru.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index b09883e..efefc19 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -321,7 +321,7 @@ de = { "tls-information-title": "Certificate Details", "tls-dialog-status-label": "Syncplay is using an encrypted connection to {}.", "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it is sent to or from the
server {}.", - "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.", + "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite: {}.", "tls-dialog-certificate-label": "Certificate issued by {} valid until {}.", # About dialog - TODO: Translate diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 80fb020..e88161c 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -322,7 +322,7 @@ en = { "tls-information-title": "Certificate Details", "tls-dialog-status-label": "Syncplay is using an encrypted connection to {}.", "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it is sent to or from the
server {}.", - "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.", + "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite: {}.", "tls-dialog-certificate-label": "Certificate issued by {} valid until {}.", # About dialog diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 022e236..bdb210e 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -322,7 +322,7 @@ it = { "tls-information-title": "Informazioni sul certificato", "tls-dialog-status-label": "Syncplay è connesso a {} tramite una connessione codificata.", "tls-dialog-desc-label": "La codifica con un certificato digitale mantiene private le informazioni quando vengono
inviate dal/al server {}.", - "tls-dialog-connection-label": "Informazioni codificate usando Transport Layer Security (TLS), versione {} usando gli
algoritmi di cifratura:{}.", + "tls-dialog-connection-label": "Informazioni codificate usando Transport Layer Security (TLS), versione {} usando gli
algoritmi di cifratura: {}.", "tls-dialog-certificate-label": "Certificato rilasciato da {} valido fino al {}.", # About dialog diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 148402b..95f6904 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -324,7 +324,7 @@ ru = { "tls-information-title": "Certificate Details", "tls-dialog-status-label": "Syncplay is using an encrypted connection to {}.", "tls-dialog-desc-label": "Encryption with a digital certificate keeps information private as it is sent to or from the
server {}.", - "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite:{}.", + "tls-dialog-connection-label": "Information encrypted using Transport Layer Security (TLS), version {} with the cipher
suite: {}.", "tls-dialog-certificate-label": "Certificate issued by {} valid until {}.", # About dialog - TODO: Translate