From aaf9d53be9b329344d7330964281819839ad4e4a Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 12 Feb 2021 00:31:28 +0000 Subject: [PATCH] more ssl options --- .vscode/settings.json | 4 +++ copyparty/__main__.py | 64 +++++++++++++++++++++++++++++++++++-------- copyparty/httpconn.py | 30 ++++++++++++++++++-- copyparty/util.py | 22 +++++++++++++-- 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index cb779e0b..214453da 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -56,5 +56,9 @@ // things you may wanna edit: // "python.pythonPath": "/usr/bin/python3", + "python.formatting.blackArgs": [ + "-t", + "py27" + ], //"python.linting.enabled": true, } \ No newline at end of file diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 71b393ee..e8f9965d 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -10,6 +10,7 @@ __url__ = "https://github.com/9001/copyparty/" import re import os +import sys import time import shutil import filecmp @@ -20,7 +21,13 @@ from textwrap import dedent from .__init__ import E, WINDOWS, VT100 from .__version__ import S_VERSION, S_BUILD_DT, CODENAME from .svchub import SvcHub -from .util import py_desc +from .util import py_desc, align_tab + +HAVE_SSL = True +try: + import ssl +except: + HAVE_SSL = False class RiceFormatter(argparse.HelpFormatter): @@ -86,9 +93,7 @@ def ensure_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 configure_ssl(al): - import ssl - +def configure_ssl_ver(al): def terse_sslver(txt): txt = txt.lower() for c in ["_", "v", "."]: @@ -107,7 +112,7 @@ def configure_ssl(al): avail = [terse_sslver(x[6:]) for x in flags] avail = " ".join(sorted(avail) + ["all"]) print("\navailable ssl/tls versions:\n " + avail) - return + sys.exit(0) al.ssl_flags_en = 0 al.ssl_flags_de = 0 @@ -131,6 +136,31 @@ def configure_ssl(al): # think i need that beer now +def configure_ssl_ciphers(al): + ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + if al.ssl_ver: + ctx.options &= ~al.ssl_flags_en + ctx.options |= al.ssl_flags_de + + is_help = al.ciphers == "help" + + if al.ciphers: + try: + ctx.set_ciphers(al.ciphers) + except: + if not is_help: + print("\n\033[1;31mfailed to set ciphers\033[0m\n") + + if not hasattr(ctx, "get_ciphers"): + print("cannot read cipher list: openssl or python too old") + else: + ciphers = [x["description"] for x in ctx.get_ciphers()] + print("\n ".join(["\nenabled ciphers:"] + align_tab(ciphers) + [""])) + + if is_help: + sys.exit(0) + + def main(): time.strptime("19970815", "%Y%m%d") # python#7980 if WINDOWS: @@ -142,7 +172,8 @@ def main(): print(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc)) ensure_locale() - ensure_cert() + if HAVE_SSL: + ensure_cert() ap = argparse.ArgumentParser( formatter_class=RiceFormatter, @@ -204,9 +235,14 @@ def main(): ap.add_argument("-nid", action="store_true", help="no info disk-usage") ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile") ap.add_argument("--urlform", type=str, default="print,get", help="how to handle url-forms") - ap.add_argument("--ssl-ver", type=str, help="ssl/tls versions to allow") - ap.add_argument("--https-only", action="store_true", help="disable plaintext") - ap.add_argument("--http-only", action="store_true", help="disable ssl/tls") + + ap2 = ap.add_argument_group('SSL/TLS options') + ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls") + ap2.add_argument("--https-only", action="store_true", help="disable plaintext") + ap2.add_argument("--ssl-ver", type=str, help="ssl/tls versions to allow") + ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ciphers") + ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info") + ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets") al = ap.parse_args() # fmt: on @@ -220,8 +256,14 @@ def main(): except: raise Exception("invalid value for -p") - if al.ssl_ver: - configure_ssl(al) + if HAVE_SSL: + if al.ssl_ver: + configure_ssl_ver(al) + + if al.ciphers: + configure_ssl_ciphers(al) + else: + print("\033[33m ssl module does not exist; cannot enable https\033[0m\n") SvcHub(al).run() diff --git a/copyparty/httpconn.py b/copyparty/httpconn.py index bbc7264c..0e158415 100644 --- a/copyparty/httpconn.py +++ b/copyparty/httpconn.py @@ -3,10 +3,15 @@ from __future__ import print_function, unicode_literals import os import sys -import ssl import time import socket +HAVE_SSL = True +try: + import ssl +except: + HAVE_SSL = False + try: import jinja2 except ImportError: @@ -107,7 +112,7 @@ class HttpConn(object): self.sr = None if self.args.https_only: is_https = True - elif self.args.http_only: + elif self.args.http_only or not HAVE_SSL: is_https = False else: is_https = self._detect_https() @@ -125,7 +130,28 @@ class HttpConn(object): ctx.options &= ~self.args.ssl_flags_en ctx.options |= self.args.ssl_flags_de # print(repr(ctx.options)) + + if self.args.ssl_log: + try: + ctx.keylog_filename = self.args.ssl_log + except: + self.log("keylog failed; openssl or python too old") + + if self.args.ciphers: + ctx.set_ciphers(self.args.ciphers) + self.s = ctx.wrap_socket(self.s, server_side=True) + if self.args.ssl_dbg and hasattr(self.s, "shared_ciphers"): + overlap = [y[::-1] for y in self.s.shared_ciphers()] + lines = [str(x) for x in (["TLS cipher overlap:"] + overlap)] + self.log("\n".join(lines)) + for k, v in [ + ["compression", self.s.compression()], + ["ALPN proto", self.s.selected_alpn_protocol()], + ["NPN proto", self.s.selected_npn_protocol()], + ]: + self.log("TLS {}: {}".format(k, v or "nah")) + except Exception as ex: em = str(ex) diff --git a/copyparty/util.py b/copyparty/util.py index 45e0c50f..b5b37090 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -108,7 +108,7 @@ def ren_open(fname, *args, **kwargs): with open(fname, *args, **kwargs) as f: yield {"orz": [f, fname]} return - + orig_name = fname bname = fname ext = "" @@ -632,10 +632,10 @@ def sendfile_kern(lower, upper, f, s): except Exception as ex: # print("sendfile: " + repr(ex)) n = 0 - + if n <= 0: return upper - ofs - + ofs += n # print("sendfile: ok, sent {} now, {} total, {} remains".format(n, ofs - lower, upper - ofs)) @@ -718,6 +718,22 @@ def py_desc(): ) +def align_tab(lines): + rows = [] + ncols = 0 + for ln in lines: + row = [x for x in ln.split(" ") if x] + ncols = max(ncols, len(row)) + rows.append(row) + + lens = [0] * ncols + for row in rows: + for n, col in enumerate(row): + lens[n] = max(lens[n], len(col)) + + return ["".join(x.ljust(y + 2) for x, y in zip(row, lens)) for row in rows] + + class Pebkac(Exception): def __init__(self, code, msg=None): super(Pebkac, self).__init__(msg or HTTPCODE[code])