From 9fcd4823b53bf4beacf5f31a4c44ea0d5fc7ba42 Mon Sep 17 00:00:00 2001 From: ed Date: Tue, 25 Jun 2019 21:01:00 +0000 Subject: [PATCH] add cursed TLS to enable crypto.subtle, thx webkit --- copyparty/__init__.py | 6 +++++ copyparty/__main__.py | 37 ++++++++++++++++++++++++++++- copyparty/httpconn.py | 38 ++++++++++++++++++++++++++---- copyparty/httpsrv.py | 11 ++++++++- copyparty/res/insecure.pem | 48 ++++++++++++++++++++++++++++++++++++++ copyparty/web/browser.html | 4 ++-- copyparty/web/upload.css | 4 ++-- docs/up2k.txt | 18 ++++++++++++++ 8 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 copyparty/res/insecure.pem diff --git a/copyparty/__init__.py b/copyparty/__init__.py index 046fc070..c351fad3 100644 --- a/copyparty/__init__.py +++ b/copyparty/__init__.py @@ -25,5 +25,11 @@ class EnvParams(object): + "/copyparty" ) + try: + os.mkdir(self.cfg) + except: + if not os.path.isdir(self.cfg): + raise + E = EnvParams() diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 17018e99..43604534 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -8,10 +8,14 @@ __copyright__ = 2019 __license__ = "MIT" __url__ = "https://github.com/9001/copyparty/" +import os +import shutil +import filecmp import locale import argparse from textwrap import dedent +from .__init__ import E from .__version__ import S_VERSION, S_BUILD_DT from .svchub import SvcHub @@ -35,7 +39,7 @@ class RiceFormatter(argparse.HelpFormatter): return "".join(indent + line + "\n" for line in text.splitlines()) -def main(): +def ensure_locale(): for x in [ "en_US.UTF-8", "English_United States.UTF8", @@ -48,6 +52,37 @@ def main(): except: continue + +def ensure_cert(): + """ + the default cert (and the entire TLS support) is only here to enable the + crypto.subtle javascript API, which is necessary due to the webkit guys + being massive memers (https://www.chromium.org/blink/webcrypto) + + i feel awful about this and so should they + """ + cert_insec = os.path.join(E.mod, "res/insecure.pem") + cert_cfg = os.path.join(E.cfg, "cert.pem") + if not os.path.exists(cert_cfg): + shutil.copy2(cert_insec, cert_cfg) + + try: + if filecmp.cmp(cert_cfg, cert_insec): + print( + "\033[33m\n using default TLS certificate; https will be insecure." + + "\033[36m\n certificate location: {}\033[0m\n".format(cert_cfg) + ) + except: + pass + + # speaking of the default cert, + # printf 'NO\n.\n.\n.\n.\ncopyparty-insecure\n.\n' | faketime '2000-01-01 00:00:00' openssl req -x509 -sha256 -newkey rsa:2048 -keyout insecure.pem -out insecure.pem -days $((($(printf %d 0x7fffffff)-$(date +%s --date=2000-01-01T00:00:00Z))/(60*60*24))) -nodes && ls -al insecure.pem && openssl x509 -in insecure.pem -text -noout + + +def main(): + ensure_locale() + ensure_cert() + ap = argparse.ArgumentParser( formatter_class=RiceFormatter, prog="copyparty", diff --git a/copyparty/httpconn.py b/copyparty/httpconn.py index 0f8c5dcd..449135af 100644 --- a/copyparty/httpconn.py +++ b/copyparty/httpconn.py @@ -3,6 +3,8 @@ from __future__ import print_function, unicode_literals import os +import ssl +import socket import jinja2 from .__init__ import E @@ -16,15 +18,14 @@ class HttpConn(object): creates an HttpCli for each request (Connection: Keep-Alive) """ - def __init__(self, sck, addr, args, auth, log_func): + def __init__(self, sck, addr, args, auth, log_func, cert_path): self.s = sck self.addr = addr self.args = args self.auth = auth + self.cert_path = cert_path - self.sr = Unrecv(sck) self.workload = 0 - self.log_func = log_func self.log_src = "{} \033[36m{}".format(addr[0], addr[1]).ljust(26) @@ -37,9 +38,38 @@ class HttpConn(object): def respath(self, res_name): return os.path.join(E.mod, "web", res_name) + def log(self, msg): + self.log_func(self.log_src, msg) + def run(self): + method = None + if self.cert_path: + method = self.s.recv(4, socket.MSG_PEEK) + if len(method) != 4: + err = b"need at least 4 bytes in the first packet; got {}".format( + len(method) + ) + self.log(err) + self.s.send(b"HTTP/1.1 400 Bad Request\r\n\r\n" + err) + return + + if method not in [None, b"GET ", b"HEAD", b"POST"]: + self.log_src = self.log_src.replace("[36m", "[35m") + try: + self.s = ssl.wrap_socket( + self.s, server_side=True, certfile=self.cert_path + ) + except OSError as ex: + if "ALERT_BAD_CERTIFICATE" in ex.strerror: + self.log("client rejected our certificate (nice)") + else: + raise + + return + + self.sr = Unrecv(self.s) + while True: cli = HttpCli(self) if not cli.run(): - self.s.close() return diff --git a/copyparty/httpsrv.py b/copyparty/httpsrv.py index 4058f12c..7d1b976d 100644 --- a/copyparty/httpsrv.py +++ b/copyparty/httpsrv.py @@ -2,9 +2,11 @@ # coding: utf-8 from __future__ import print_function, unicode_literals +import os import time import threading +from .__init__ import E from .httpconn import HttpConn from .authsrv import AuthSrv @@ -27,6 +29,12 @@ class HttpSrv(object): self.workload_thr_alive = False self.auth = AuthSrv(args, log_func) + cert_path = os.path.join(E.cfg, "cert.pem") + if os.path.exists(cert_path): + self.cert_path = cert_path + else: + self.cert_path = None + def accept(self, sck, addr): """takes an incoming tcp connection and creates a thread to handle it""" thr = threading.Thread(target=self.thr_client, args=(sck, addr, self.log)) @@ -42,7 +50,7 @@ class HttpSrv(object): def thr_client(self, sck, addr, log): """thread managing one tcp client""" - cli = HttpConn(sck, addr, self.args, self.auth, log) + cli = HttpConn(sck, addr, self.args, self.auth, log, self.cert_path) with self.mutex: self.clients[cli] = 0 self.workload += 50 @@ -57,6 +65,7 @@ class HttpSrv(object): cli.run() finally: + sck.close() with self.mutex: del self.clients[cli] diff --git a/copyparty/res/insecure.pem b/copyparty/res/insecure.pem new file mode 100644 index 00000000..1bb66aef --- /dev/null +++ b/copyparty/res/insecure.pem @@ -0,0 +1,48 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDD84b31Brvmfcc +GTwXuf5n5NW6KxIhLWVZgsCJq+lwGTGl1Cqm9vnzZqD7MDLNzztWk51FDXxeJy9Z +Vos1F+n3qfDi4zdd8zWIJxLTsIEkqZjSWiQqqmFijXoaEPBsQ32qRZprh/FfNw3P +TagroIgpgzd196eoWdDdggenYBG8/PbktaBKgOo6jz6Q5/UzNTmvcbtlxlsxPos8 +aTqzezCgA/cArg8mxmfvRPrw8umC8ATiVLvGrXeMiSvcr8Els5air/idhYC8u5zO +zGZCePxoKUptQvh/06jsye1S3JOf+RsfAWvu7DgWvSJU2dHfVmtb4ZMSa6cjqTPP +cW7S1WyLAgMBAAECggEAAwabqvAHinOiMTjiiKtClnAeLMXFfeWpjvxJ5NZWwHhj +H+Bq2DEwIuYOzlIsNqlgjTGyWAKhTQLl5EdF1wgLgNuK8LX5gOXkibmwvLwZAmvs +BDOII3CGGHN+0zA3xjQ0mJCCle5/d6zt9amJU0MjVyDDlnrAiAT7CLCdVaRSIczv +SETM90NBVHNMraB+Tz9kR1FhyDDlzqpRn/19Hm4BG6/iSmPYpL3jgCRWNweXSXdd +x9EitJCz6Yo9+0qPD8DayrH5sKviZw0QDXUezWCcQvgXMsv5MJa7+UaawLk6bq5B +Ou1TFGuFdNeNmWofAwPE8UOSIXoKhBK84z5lpTs44QKBgQDg9bRrn3a2u8vpx3r7 +B+v4dlVq5I3lbVwLwEo6vnw2UiIOtQaQMiWX8HoHR3rmzCVMZq8xNNhEYSboFgtB +bbQaxoApdqVTF700c5Le82tj3rBXN0vBIUm6Nzpz0IKQw8HWGz1q/sHd8uZdmtAO +m4JrZIJqMItcLhyjYCp8hRUkvwKBgQDe/SZVCBMocHASyB4RgeAkNKSjFEYe2yAl +23W9Me3koJqTvHEZnv3Xk5O3HF0dxElktJW5slyrA2BcrzuusO+pge/29OV4ypFa +kI51ebMgO3hm2BKKMwxR3mT4KVAbKveLi7hdlnbHVCcZelygkw+11tzIfaikeswR +c4FoyPpvNQKBgDEA1OBsyCteFTlDnuJ4A0sIW+sBBnfnrplQtdq+C8i5c3nIrTlT +8yR52dskEv2bkrRl2dvaKxIaJ6N+yczi3MzIWLqvgavsC+cVFfVDCS2kIL2e6f2U +Br9tsGnyDb8DJYJCRMq92/VBKDVTt+a2sV47cr02/eSClvJvzFF7m/N5AoGAJYzD +k7YUY87rUH5aceBI+k/TGZMka7XCqB1Yqk9qHAHfhdlJwmK/pDm5ujAQjh6rrUWr +oOWkLTgYVgM8LaKl+Qlke1Wp/rk92N5W3vlrbJYXJFpmZNdLz81/ezqZvrlxjhIt +LbVUsyQ8oVG1n2SkVJ6l9y0R5QC4tIea1yZg5bECgYEAhF2EOtBCJ3EDEpHvnlK7 +OuLRVjce4Vy+Mj3nFbHQMJf+A1B7+B43Ce1fn94OfGJldLDjvaZb8O91JAkHAwJQ +GCpOVj9go0ffg/2hurtXW3bMLXIXqcn1538MB5NdlcL22+T0CCprAeuRQm4saMhz +mkt5VszEuF/c88usk0ZMm4Y= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUWWDVQNmrPPo2HlyhYuSYY2uKPbEwDQYJKoZIhvcNAQEL +BQAwKjELMAkGA1UEBhMCTk8xGzAZBgNVBAMMEmNvcHlwYXJ0eS1pbnNlY3VyZTAe +Fw0wMDAxMDEwMDAwMDBaFw0zODAxMTkwMDAwMDBaMCoxCzAJBgNVBAYTAk5PMRsw +GQYDVQQDDBJjb3B5cGFydHktaW5zZWN1cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDD84b31BrvmfccGTwXuf5n5NW6KxIhLWVZgsCJq+lwGTGl1Cqm +9vnzZqD7MDLNzztWk51FDXxeJy9ZVos1F+n3qfDi4zdd8zWIJxLTsIEkqZjSWiQq +qmFijXoaEPBsQ32qRZprh/FfNw3PTagroIgpgzd196eoWdDdggenYBG8/PbktaBK +gOo6jz6Q5/UzNTmvcbtlxlsxPos8aTqzezCgA/cArg8mxmfvRPrw8umC8ATiVLvG +rXeMiSvcr8Els5air/idhYC8u5zOzGZCePxoKUptQvh/06jsye1S3JOf+RsfAWvu +7DgWvSJU2dHfVmtb4ZMSa6cjqTPPcW7S1WyLAgMBAAGjUzBRMB0GA1UdDgQWBBQC +uhBjOit55cEz7jMIiJq2BljqxDAfBgNVHSMEGDAWgBQCuhBjOit55cEz7jMIiJq2 +BljqxDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCu7VE/Pt4H +IOHAacQFJN9rdUtI1lcvbJEMdpPglHgFa546pgzDjS8QGvULmppb4mkMbezZ2u+v +ZsWMfLxATYgfqEPO/fnT/UqFX2TI2pmmZhqSV+eW8AGRPD5LYyfXpvU4ciSz4Fyl +FlrcCOStbeOjHwpeF+n92sF6/LL3n4yy1jmLnTaZDdd59PcWpQkX8omBWlXPdCPR +ot0ZI2OpK5mDUG2FglCXl29k1q/zLsKPyfmG7T6VOMB8HO7EylzgSTke4WH9QLR/ +bkn7AZU+JEIdC6SgR0WSQAntqM+b/OuYSBzXDX0XLwnyY2Dx8SshKNF9v6GwTq7j +yQev95xStUTw +-----END CERTIFICATE----- diff --git a/copyparty/web/browser.html b/copyparty/web/browser.html index b76aa10a..067a0cc7 100644 --- a/copyparty/web/browser.html +++ b/copyparty/web/browser.html @@ -6,9 +6,9 @@ copyparty - + {%- if can_upload %} - + {%- endif %} diff --git a/copyparty/web/upload.css b/copyparty/web/upload.css index 6751df21..e5c86efc 100644 --- a/copyparty/web/upload.css +++ b/copyparty/web/upload.css @@ -46,7 +46,7 @@ font-size: 2em; margin: 1em auto; padding: 1em 0; - width: 15em; + width: 12em; cursor: pointer; box-shadow: .4em .4em 0 #111; } @@ -71,7 +71,7 @@ } #u2conf { margin: 1em auto; - width: 30em; + width: 26em; } #u2conf * { text-align: center; diff --git a/docs/up2k.txt b/docs/up2k.txt index 5a56256a..5c07b4e4 100644 --- a/docs/up2k.txt +++ b/docs/up2k.txt @@ -39,6 +39,10 @@ combines parts into final file if all-ok use sha512 instead of sha256 everywhere write directly to .$wark.tmp instead of parts, then move to destination +may have to forego the predictable wark and instead use random key: + webkit is doing the https-only meme for crypto.subtle.* + so native sha512 is unavailable on LAN (oh no) + so having the client hash everything before first byte is NG @@ -52,6 +56,20 @@ registry moves file into place when all chunks are verified +## +## in case we can't rely on sha512 of entire file + +handshake +client gets wark (random session-key) +localstorage[filename+size] = wark +thread 1: sha512 chunks +thread 2: upload chunks +server renames wark to filename on last chunk finished +if conflict with another wark during upload: all files are renamed +if conflict with existing filename: new file is renamed + + + ## ## required capabilities