diff --git a/README.md b/README.md index e646f65..6a56bb3 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,4 @@ -# TLS power measure benchmark - -Goal: measure the power overhead of adding TLS to a client or a server, on realistic loads. - -Realistic load implies using a real-world client (such as a web browser on a low-end device) and server (such as a video streaming platform on a typical server). - -Problem: realistic clients have complex behaviors that go beyond a simple OpenSSL example code, and modern browsers and website won't work without HTTPS. - -Assumption: most of the load added by the security layers is decoupled from the application (in both web browsers and web servers). - -``` -Experiments: -1: (Client) <=[TLS]=> (Proxy 1) <-[plain]-> (Proxy 2) <-[plain]-> (Proxy 3) <=[TLS]=> (Server) -2: (Client) <=[TLS]=> (Proxy 1) <==[TLS]==> (Proxy 2) <-[plain]-> (Proxy 3) <=[TLS]=> (Server) -3: (Client) <=[TLS]=> (Proxy 1) <-[plain]-> (Proxy 2) <==[TLS]==> (Proxy 3) <=[TLS]=> (Server) - ^measure^ -``` - -Client and server are identical in all experiments. The only modification made to the client is to add a trusted certificate owned by Proxy 1. - -Only Proxy 2 must be a dedicated machine. The other parties may be placed on the same machine. - -Call E1, E2, E3 the energy measured for Proxy 2 in experiments 1, 2, 3. - -E2-E1 is the energy used to operate the server's half of a TLS connection. - -E3-E1 is the energy used to operate the client's half of a TLS connection. - -Client: -* Web browser trusting a certificate owned by Proxy 1 - -## Things to experiment - -* Implementations - * OpenSSL 1.0.2 - * OpenSSL 1.1.1 - * OpenSSL 3.0 - * OpenSSL 3.2 - * OpenSSL 3.3 - * OpenSSL 3.4 - * OpenSSL 3.5 - * OpenSSL 3.6 - * [WolfSSL](https://github.com/wolfSSL/wolfssl) - * GnuTLS - * [NSS](https://github.com/nss-dev/nss) (used by Firefox) (?) - * opencryptoki (?) - * AWS-LC - * [BoringSSL](https://github.com/google/boringssl) (Chrome, Android) - * LibreSSL - * [SymCrypt](https://github.com/microsoft/SymCrypt) (used by Windows) ([install manually](https://github.com/microsoft/SymCrypt/releases)) -* Versions - * TLS 1.2 - * TLS 1.3 -* Features - * TLS - * Ciphers - * TLS_AES_128_GCM_SHA256 - * TLS_AES_256_GCM_SHA384 - * TLS_CHACHA20_POLY1305_SHA256 - * TLS_AES_128_CCM_SHA256 - * TLS_AES_128_CCM_8_SHA256 - * Key exchange groups - * secp256r1 - * secp384r1 - * secp521r1 - * x25519 - * x448 - * ffdhe2048 - * ffdhe3072 - * ffdhe4096 - * ffdhe6144 - * ffdhe8192 - * Signatures - * rsa_pkcs1_sha256 - * rsa_pkcs1_sha384 - * rsa_pkcs1_sha512 - * ecdsa_secp256r1_sha256 - * ecdsa_secp384r1_sha384 - * ecdsa_secp521r1_sha512 - * rsa_pss_rsae_sha256 - * rsa_pss_rsae_sha384 - * rsa_pss_rsae_sha512 - * ed25519 - * ed448 - * rsa_pss_pss_sha256 - * rsa_pss_pss_sha384 - * rsa_pss_pss_sha512 - * rsa_pkcs1_sha1 - * ecdsa_sha1 - * X.509 - * Signature algorithm - * RSA2048 - * RSA3072 - * RSA4096 - * EC 256 - * EC 384 - * SCT - * TODO !!! -* Usages - * Video streaming - * Full-speed download - * Real-time video call - * Mostly text browsing - * With or without ads - -Most of the implementations can be used through RusTLS. - -However RusTLS clients won't enable to force TLS1.2 if 1.3 is available. - -* séparer conso crypto / trafic -* mesure avec debugger : quelles fonctions consomment -* mesure mémoire -* mesure overhead +# TLS ### WolfSSL @@ -123,89 +11,16 @@ make sudo make install ``` -## State of the art - -* https://pub.h-brs.de/frontdoor/deliver/index/docId/4771/file/2019-ESP32-TLS-Power.pdf - * 2019 - * TLS 1.2, 1.3 - * ESP32 - * WolfSSL -* https://www.semanticscholar.org/paper/Energy-Consumption-Framework-and-Analysis-of-on-Patterson-Buchanan/706736a29cef777e5dc50ba22b4788b2bfb4c6ef - * 2025 - * RaspberryPi - * OpenSSL - * ML-KEM -* https://www.semanticscholar.org/paper/Energy-Profiling-and-Comparison-of-TLS-Protocols-Gatram-Reddy/9c061fe57a0008574b85919bc70fc803c6e66f06 - * Energy Profiling and Comparison of TLS Protocols for Embedded Devices: Experimental Study - * 2024 - * RaspberryPi - * TLS PQ, TLS KEM, TLS -* https://www.semanticscholar.org/paper/Energy-Consumption-Evaluation-of-Post-Quantum-TLS-Tasopoulos-Dimopoulos/2ffc6d13349e2fa5f89aaf18e69ce2044ecef4fe - * 2023 - * STM Nucleo - * WolfSSL - * TLS 1.3 PQ -* https://arxiv.org/pdf/2508.04583v2 - * 2025 - * TLS 1.3 - * Nginx + Python requests - * https://github.com/MarcT0K/privacy-carbon-experiments -* https://davidtnaylor.com/CostOfTheS.pdf - * 2014 -* https://www.researchgate.net/publication/359906722_Performance_Analysis_of_SSLTLS_Crypto_Libraries_Based_on_Operating_Platform - * 2022 - * throughput & cpu - * OpenSSL, GnuTLS, Boring, S2n, NSS, Cryptlib -* https://www.haproxy.com/blog/state-of-ssl-stacks - * 2025 - * OpenSSL -* https://ieeexplore.ieee.org/abstract/document/5983970 - * TLS and energy consumption on a mobile device: A measurement study - * 2011 - * TLS - * Nokia phone -* https://dspace.mit.edu/handle/1721.1/111861 - * Energy-efficient protocols and hardware architectures for transport layer security - * 2017 -* https://dl.acm.org/doi/abs/10.1145/871506.871518 - * Analyzing the energy consumption of security protocols - * 2003 - * SSL -* https://ieeexplore.ieee.org/abstract/document/1563998 - * A study of the energy consumption characteristics of cryptographic algorithms and security protocols - * 2003 - * SSL -* https://ieeexplore.ieee.org/abstract/document/8598334 - * Energy and Processing Demand Analysis of TLS Protocol in Internet of Things Applications - * 2016 - * TLS -* https://dl.acm.org/doi/10.1145/3345768.3355924 - * A Comprehensive Empirical Analysis of TLS Handshake and Record Layer on IoT Platforms - * 2019 - * IoT, TLS -* https://hal.science/hal-04197885/document - * Empreinte carbone de la transmission de données sur le backbone RENATER - * 2021 -* https://ieeexplore.ieee.org/document/10971851/ - * Optimizing TLS/SSL for IoT Devices: Performance Enhancements and Security Considerations - * 2024 -* https://ieeexplore.ieee.org/document/10060762/ - * Performance Evaluation of Quantum-Resistant TLS for Consumer IoT Devices - * 2023 -* - * Evaluating the Energy Profile of Tasks Managed by Build Automation Tools in Continuous Integration Workflows: The Case of Apache Maven and Gradle - * 2025 - -## Sources - -* [RFC8446 (TLS 1.3)](https://datatracker.ietf.org/doc/html/rfc8446) - ## Reproduce ### Record +On the controler machine. + #### Installation +Build netreplay and copy it in this folder. + Authorize netreplay to bind to ports 80 and 443: ```bash sudo setcap CAP_NET_BIND_SERVICE=+eip netreplay @@ -221,11 +36,10 @@ In settings, disable DNS security. In `about:config`, set `network.dns.forceResolve` to `127.0.0.1`. -Run the shell commands: +Run the shell command: ```bash python exp.py make debug -c -python exp.py update-certs debug ``` In Firefox, go to security settings, Certificates, import `/dev/shm/exp/certs/prime256v1/ca.crt` and trust it for identifying websites. @@ -240,85 +54,40 @@ Start the record proxy: ./netreplay records/mynewrecord record ``` -Just browse. Any traffic to and from the selected names will be recorded. Terminate netplayer with CTRL+C when finished. - -#### Incomplete records - -The last HTTP payload of a record may be incomplete. If that happens, the run command will hang and display the faulty record number after a timeout. -Re-run with `-d --record ` and note the missing packet number. -Run `netreplay remove ` to remove the packet. +Just browse. Any traffic will be recorded. Terminate netplayer with CTRL+C when finished. ### Measure -Measure resource cost on a different machine. +Copy the tlsbench folder on the target. +Build netreplay with the wanted cryptographic backends on the target (one build per backend), and place each version in tlsbench, named `netreplay-aws-lc`, `netreplay-boring`, etc. -Add p2 the `/etc/hosts`: - -``` -192.168.3.14 p2 -``` - -Install things on p2: +Install dependencies on the target: ```bash sudo apt install acct dtach sudo chmod +s /sbin/sa ``` +Modify experiment parameters at the top of `exp.py` to suit your setup and the wanted experiments. + Run: ```bash -python exp.py make pi -c -python exp.py send pi -python exp.py update-certs pi # also do this command on p2 -python exp.py run pi --idle +python exp.py make pi3 -c +python exp.py send pi3 +python exp.py run pi3 --idle ``` -On Debian, update-certs says 0 certs added even if it has actually updated some certs. This step is still needed. +It may be needed to keep an SSH connection to the target open during the experiment, and to set `RemoveIPC=no` in `/etc/systemd/logind.conf` (then `systemctl restart systemd-logind`). ### Profile Profile code execution on the local machine. -Add the domains the `/etc/hosts`: - -``` -127.0.0.1 youtube.com.localhost -127.0.0.1 www.youtube.com.localhost -127.0.0.1 i.ytimg.com.localhost -127.0.0.1 fonts.gstatic.com.localhost -127.0.0.1 www.gstatic.com.localhost -127.0.0.1 www.google.com.localhost -127.0.0.1 accounts.google.com.localhost -127.0.0.1 yt3.ggpht.com.localhost -127.0.0.1 rr1---sn-gxo5uxg-jqbl.googlevideo.com.localhost -127.0.0.1 rr2---sn-gxo5uxg-jqbl.googlevideo.com.localhost -127.0.0.1 rr4---sn-q4fl6nds.googlevideo.com.localhost -127.0.0.1 fr.wikipedia.org.localhost -127.0.0.1 upload.wikimedia.org.localhost -127.0.0.1 apple.com.localhost -127.0.0.1 www.apple.com.localhost -192.168.3.1 youtube.com -192.168.3.1 www.youtube.com -192.168.3.1 i.ytimg.com -192.168.3.1 fonts.gstatic.com -192.168.3.1 www.gstatic.com -192.168.3.1 www.google.com -192.168.3.1 accounts.google.com -192.168.3.1 yt3.ggpht.com -192.168.3.1 rr1---sn-gxo5uxg-jqbl.googlevideo.com -192.168.3.1 rr2---sn-gxo5uxg-jqbl.googlevideo.com -192.168.3.1 rr4---sn-q4fl6nds.googlevideo.com -192.168.3.1 fr.wikipedia.org -192.168.3.1 upload.wikimedia.org -192.168.3.1 apple.com -192.168.3.1 www.apple.com -``` - Install sa: ```bash -sudo apt install acct python3-invoke python3-fabric +sudo apt install acct dtach python3-invoke python3-fabric sudo chmod +s /sbin/sa ``` @@ -336,12 +105,9 @@ Install OpenSSL with debug symbols: To build netreplay with this openssl: ```bash -OPENSSL_LIB_DIR=/home/pi/reps/openssl-openssl-3.6.0/ OPENSSL_DIR=/home/pi/reps/openssl-openssl-3.6.0/ cargo build --release +OPENSSL_LIB_DIR=/home/pi/reps/openssl-openssl-3.6.1/ OPENSSL_DIR=/home/pi/reps/openssl-openssl-3.6.1/ cargo build --release ``` -Or: Backup your system's `libcrypto.so` and `libssl.so` and replace them with the new ones. -It would be simpler with `LD_PRELOAD` but Rust loads dynamic libraries in a particuliar way so it doesn't work. - Authorize non-root users to use perf: ```bash @@ -352,8 +118,6 @@ Run: ```bash python exp.py make local -c -python exp.py send local -python exp.py update-certs local python exp.py run local python plots.py prof ``` @@ -381,10 +145,9 @@ python exp.py make g5k -c python exp.py send g5k # Notes -scp /lib/x86_64-linux-gnu/libssl.so.3.6 lyon.g5k:~/ -scp /lib/x86_64-linux-gnu/libcrypto.so.3.6 lyon.g5k:~/ +scp /lib/x86_64-linux-gnu/libssl.so.3.6 nancy.g5k:~/ +scp /lib/x86_64-linux-gnu/libcrypto.so.3.6 nancy.g5k:~/ scp records/wikipedia nancy.g5k:tlsbench/records/ -LD_PRELOAD="/home/pengelib/tlsbench/libssl.so.3.6 /home/pengelib/tlsbench/libcrypto.so.3.6" ./netreplay --help ``` #### Wattmetre @@ -392,49 +155,3 @@ LD_PRELOAD="/home/pengelib/tlsbench/libssl.so.3.6 /home/pengelib/tlsbench/libcry API timestamp at Paris time. https://api.grid5000.fr/stable/sites/nancy/metrics?nodes=gros-69&metrics=bmc_node_power_watt&start_time=2026-02-26T14:00&end_time=2026-02-26T14:40 - -## Problems - -### Youtube - -Youtube utilise des trucs aléatoires en `RANDOM.googlevideo.com` pour la vidéo. Cependant il y a quelques domaines utilisés qui ne changent pas, du moins sur un même navigateur avec la même vidéo et sur une courte période. -Avant d'enregistrer le trafic, il faut observer les domaines utilisés puis générer les certificats et les redirections en fonction. - -## Notes - -### CertVerify - -CertVerify est l'extension dans le ServerHello qui signe la discussion passée avec la clé secrète du certificat. - -Il a fallu désactiver la réutilisation de session, qui en TLS1.3 passe par le PSK, pour pouvoir mesurer le CertVerify. - -### Size overhead and usage survey - -```bash -openssl s_server -port 8000 -cert /dev/shm/exp/certs/prime256v1/wikipedia.org.crt -key /dev/shm/exp/certs/prime256v1/wikipedia.org.key -curl https://wikipedia.org --tlsv1.3 --curves x25519 --connect-to wikipedia.org:443:127.0.0.1:8000 -k -``` - -Get the most used domains here https://www.akamai.com/fr/security-research/akarank - -```bash -python crawler.py crawl /dev/shm/top1K.csv -python crawler.py stat /dev/shm/crawl.json -``` - -### 0-RTT - -```bash -echo "hello world" > /dev/shm/ed -openssl s_server -port 8000 -cert /dev/shm/exp/certs/prime256v1/wikipedia.org.crt -key /dev/shm/exp/certs/prime256v1/wikipedia.org.key -early_data -# First req, without early data -echo | openssl s_client -no-interactive -keylogfile /dev/shm/client.txt -sess_out sessions 127.0.0.1:8000 -# Second req, using 0-RTT for early data -echo | openssl s_client -no-interactive -early_data /dev/shm/ed -keylogfile /dev/shm/client.txt -sess_in sessions 127.0.0.1:8000 -``` - -### Re-issuing certificates - -```bash -openssl x509 -in oldcert -CA cacertfile -CAkey capkeyfile -out newcert -days 365 -``` diff --git a/exp.py b/exp.py index f0125a0..6661712 100644 --- a/exp.py +++ b/exp.py @@ -260,7 +260,9 @@ CONFIGS = { True, ], "records": [ - { "filename": "wikipedia", "repeat": 12000, "time": 60 }, + # Can't repeat more than 8000 times here + # TODO check if netreplay client frees ports correctly, or try to reuse ports + { "filename": "wikipedia", "repeat": 8000, "time": 45 }, ], "repo_dir": "/home/pengelib/tlsbench", "exp_dir": "/dev/shm/exp", @@ -275,7 +277,7 @@ CONFIGS = { "rapl": False, "sa": False, "listen_port": 8080, - "idle": "idle - - - - - - - - - 1772205368.593937 1772206568.6941307 1200.1001937389374 0 298843 2217803 16.6 0 -", + "idle": "idle - - - - - - - - - 1772205368.593937 1772206568.6941307 1200.1001937389374 0 298843 2217803 5.53333333333333 0 -", "notify_listen": ("0.0.0.0", 8090), "notify_addr": "TODO:8090", "ld_preload": { @@ -326,6 +328,8 @@ DOMAINS_ = [ "en.wikipedia.org", "auth.wikimedia.org", "upload.wikimedia.org", "meta.wikimedia.org", "intake-analytics.wikimedia.org", # Google search "www.google.com", "www.googleadservices.com", "www.gstatic.com", "csp.withgoogle.com", "ogads-pa.clients6.google.com", "play.google.com", "ssl.gstatic.com", "fonts.gstatic.com", "ogs.google.com", + # Firefox extensions + "addons.mozilla.org", ] CERT_SIGN_ALGS = [ @@ -929,7 +933,7 @@ def run_exp(config, only_record=None, idle=False, shutdown=False, debug=False): session_count = 0 try: while True: - rec_count = int.from_bytes(notify_socket.recv(4)) + rec_count = int.from_bytes(notify_socket.recv(4), "big") if rec_count == 0xffffffff: break if rec_count > session_count: diff --git a/exp_proxies.py b/exp_proxies.py deleted file mode 100644 index 4099a97..0000000 --- a/exp_proxies.py +++ /dev/null @@ -1,535 +0,0 @@ -#!/usr/bin/python3 -# OLD VERSION -# using 3 proxies between actual browser and actual server - -import os, sys, subprocess - -REPODIR = "/home/tuxmain/Documents/doc/tlsbench" -P2_SSH = "exp@p2" -P2_PSW = "exp" -P2_REPODIR = "/home/exp/exp" -EXPDIR = "/dev/shm/exp" -DOMAINS_ = [ - # Apple - "apple.com", "www.apple.com", "graffiti-tags.apple.com", - "store.storeimages.cdn-apple.com", - "mzstatic.com", "is1-ssl.mzstatic.com" - # Youtube video - "youtube.com", "www.youtube.com", "i.ytimg.com", "fonts.gstatic.com", "www.google.com", "accounts.google.com", "yt3.ggpht.com", "www.gstatic.com", "fonts.googleapis.com", "www.googleadservices.com", - # W3C - "w3.org", "www.w3.org", - # Amazon - "amazon.com", "www.amazon.com", - # JA3 fingerprint test - "www.edgecomputing.live", - # JA4 fingerprint test - "ja4db.com", - # Local test - "foo.home", - # Peertube - "flim.txmn.tk", -] - -#URL = "https://www.youtube.com/watch?v=IBWezPN4Ep8" -#URL = "https://www.edgecompute.live/tls/ja3" -#URL = "https://ja4db.com/id/ja4/" -#URL = "https://www.w3.org/" -#URL = "http://foo.home:8080/?hello=hi" -#URL = "https://www.apple.com/" -#URL = "https://www.amazon.com/" -URL = "https://flim.txmn.tk/w/4zpfvGB72oTL4hcSqdAwx8" - -URLS = [ - # Heavy showcase website - "https://www.apple.com/", - # Light showcase website - "https://librezo.fr/", - # Long Wikipedia article - "https://fr.wikipedia.org/wiki/Sp%C3%A9cial:Recherche?search=Victor+Hugo&sourceid=Mozilla-search", - # Youtube video - "https://www.youtube.com/watch?v=IBWezPN4Ep8", - # Peertube video - "https://videos.domainepublic.net/videos/watch/eaee7866-d209-4e5c-b7b0-443395b79c82", - # Google search - "https://www.google.com/search?q=where+do+birds+go+when+it+rains" -] - -CERT_SIGN_ALGS = [ - "prime256v1", # widely used - "secp384r1", # rarely used but supported by browsers because it's NIST standard - #"secp521r1", # not supported by browsers because NIST said it was not needed - "rsa2048", "rsa3072", "rsa4096", # widely used -] -IMPLS = [ - #"aws_lc", # Amazon's Rust crypto widely used in Rust stuff - #"boring", # Google's fork of OpenSSL used in Chrome and Android - #"openssl", # widely used - "ring", # used in most Rust stuff - #"symcrypt", # Microsoft's crypto - #"wolfcrypt" # used in embedded (won't build with rpxy for now) -] -# Symmetric ciphers -# They also allow to choose the TLS version. -CIPHERS = [ - # TLS 1.3 - "AES_256_GCM_SHA384", - "AES_128_GCM_SHA256", - "CHACHA20_POLY1305_SHA256", - # TLS 1.2 - # ECDSA vs RSA refers to the certificate signature algorithm. - # DH is EC is aither case, using the group specified below. - "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", -] -KEXES = [ - "X25519", - "SECP256R1", - "SECP384R1", -] - -# Testing all combinations would be too much. Instead we isolate independent parts. -EXPERIMENTS = { - # Compare ciphers among implementations and TLS versions - "impl-cipher-ver": { - "impls": IMPLS, - "urls": [URL], - "ciphers": [ - "AES_128_GCM_SHA256", - "AES_256_GCM_SHA384", - "CHACHA20_POLY1305_SHA256", - "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - ], - "kexes": ["X25519"], - "cert": ["prime256v1"], - }, - # Compare signatures among implementations and TLS versions - "impl-cert-ver": { - "impls": IMPLS, - "urls": [URL], - "ciphers": [ - "AES_128_GCM_SHA256", - "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256", - ], - "kexes": ["X25519"], - "cert": ["prime256v1", "secp384r1", "rsa2048", "rsa3072", "rsa4096"], - }, - # Compare key exchange groups among implementations and TLS versions - "impl-kex-ver": { - "impls": IMPLS, - "urls": [URL], - "ciphers": [ - "AES_128_GCM_SHA256", - "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256", - ], - "kexes": ["X25519", "SECP256R1", "SECP384R1"], - "cert": ["prime256v1"], - }, -} - -DOMAINS = [] -for domain in DOMAINS_: - if not domain in DOMAINS: - DOMAINS.append(domain) - -# JS to redirect the target domains to local (bypass DNS without altering system's config or webpages or packets) -SCRIPT_FIREFOX_HOSTS = """const gOverride = Cc["@mozilla.org/network/native-dns-override;1"].getService(Ci.nsINativeDNSResolverOverride); -gOverride.clearOverrides(); -var names = """+str(DOMAINS)+"""; -for(var i in names) { - gOverride.addIPOverride(names[i], "127.0.0.1"); -} -""" - -def getargv(arg:str, default:str="", n:int=1, args:list=sys.argv) -> str: - if arg in args and len(args) > args.index(arg)+n: - return args[args.index(arg)+n] - else: - return default - -def sh(cmds): - if type(cmds) == list: - for cmd in cmds: - os.system(cmd) - elif type(cmds) == str: - os.system(cmds) - else: - raise TypeError - -def make_sk(outpath, alg): - sh({ - "ed25519": [ - f"openssl genpkey -out {outpath}.sec1 -algorithm ed25519", - f"openssl pkcs8 -topk8 -nocrypt -in {outpath}.sec1 -out {outpath} -outform PEM" - ], - "prime256v1": [ - f"openssl ecparam -genkey -name prime256v1 -noout -out {outpath}.sec1", - f"openssl pkcs8 -topk8 -nocrypt -in {outpath}.sec1 -out {outpath} -outform PEM" - ], - "rsa2048": [ - f"openssl genrsa -out {outpath} 2048" - ], - "rsa3072": [ - f"openssl genrsa -out {outpath} 3072" - ], - "rsa4096": [ - f"openssl genrsa -out {outpath} 4096" - ], - "secp384r1": [ - f"openssl ecparam -genkey -name secp384r1 -noout -out {outpath}.sec1", - f"openssl pkcs8 -topk8 -nocrypt -in {outpath}.sec1 -out {outpath} -outform PEM" - ], - }[alg]) - -def make_ca_cert(outpath, skpath): - sh(f"openssl req -x509 -new -key {skpath} -sha256 -days 730 -out {outpath} -subj '/CN=Foobar Root CA/C=AT/ST=Lyon/L=Lyon/O=Foobar'") - -def make_cert(outpath, skpath, capath, caskpath, name, domains): - sh(f"openssl req -new -key {skpath} -sha256 -out /tmp/tmp.csr -subj '/CN={name}/C=AT/ST=Lyon/L=Lyon/O={name}'") - ext = open("/tmp/tmp.v3.ext", "w") - ext.write("""authorityKeyIdentifier=keyid,issuer -basicConstraints=CA:FALSE -keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment -subjectAltName = @alt_names -[alt_names] -""") - i = 1 - for domain in domains: - ext.write(f"DNS.{i} = {domain}\n") - i += 1 - ext.write(f"DNS.{i} = {domain}.localhost\n") - i += 1 - ext.write(f"DNS.{i} = {domain}.p2\n") - i += 1 - ext.write(f"DNS.{i} = {domain}.p3\n") - i += 1 - ext.close() - - sh(f"openssl x509 -req -in /tmp/tmp.csr -CA {capath} -CAkey {caskpath} -CAcreateserial -out {outpath} -days 365 -sha256 -extfile /tmp/tmp.v3.ext") - -def get_domain_root(domain): - last_dot = domain.rfind(".") - penultimate_dot = domain.rfind(".", 0, last_dot) - return domain[penultimate_dot+1:] - -# Issue secret keys, CA cert and signed certs for given domains -# All using the same algorithm -def make_certs(outdir, domains, alg): - if outdir[-1] != "/": - outdir += "/" - - make_sk(outdir+"ca.key", alg) - make_ca_cert(outdir+"ca.crt", outdir+"ca.key") - - # Only make certs for root domains, and include subdomains - roots = {} - for domain in domains: - root = domain - if domain.count(".") > 1: - root = get_domain_root(domain) - if root in roots: - roots[root].append(domain) - else: - roots[root] = [domain] - for root in roots: - make_sk(outdir+root+".key", alg) - make_cert(outdir+root+".crt", outdir+root+".key", outdir+"ca.crt", outdir+"ca.key", root, roots[root]) - -RPXY_CONFIGS = { - "p1_plain": { - "listen_http": 80, - "listen_https": 443, - "app": """[apps.{app}] -server_name = "{domain}" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}.p2:42002" }}]}}] -[apps.{app}_localhost] -server_name = "{domain}.localhost" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}.p2:42002" }}]}}] -""" - }, - "p1_tls": { - "listen_http": 80, - "listen_https": 443, - "app": """[apps.{app}] -server_name = "{domain}" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}.p2:43002", tls = true }}], set_host = "{domain}.p2"}}] -[apps.{app}_localhost] -server_name = "{domain}.localhost" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}.p2:43002", tls = true }}], set_host = "{domain}.p2"}}] -""" - }, - "p2_plain": { - "listen_http": 42002, - "listen_https": 43002, - "app": """[apps.{app}] -server_name = "{domain}" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}.p3:42003" }}], set_host = "{domain}.p3"}}] -[apps.{app}_localhost] -server_name = "{domain}.localhost" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}.p3:42003" }}], set_host = "{domain}.p3"}}] -[apps.{app}_p2] -server_name = "{domain}.p2" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}.p3:42003" }}], set_host = "{domain}.p3"}}] -""" - }, - "p2_tls": { - "listen_http": 42002, - "listen_https": 43002, - "app": """[apps.{app}] -server_name = "{domain}" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}.p3:43003", tls = true }}], set_host = "{domain}.p3"}}] -[apps.{app}_localhost] -server_name = "{domain}.localhost" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}.p3:43003", tls = true }}], set_host = "{domain}.p3"}}] -[apps.{app}_p2] -server_name = "{domain}.p2" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}.p3:43003", tls = true }}], set_host = "{domain}.p3"}}] -""" - }, - "p3": { - "listen_http": 42003, - "listen_https": 43003, - "app": """[apps.{app}] -server_name = "{domain}" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}", tls = true }}], set_host = "{domain}"}}] -[apps.{app}_localhost] -server_name = "{domain}.localhost" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}", tls = true }}], set_host = "{domain}"}}] -[apps.{app}_p3] -server_name = "{domain}.p3" -tls = {{ tls_cert_path = "{cert}", tls_cert_key_path = "{key}", https_redirection = false }} -reverse_proxy = [{{ upstream = [{{ location = "{domain}", tls = true }}], set_host = "{domain}"}}] -""" - }, -} - -SETUPS = { - #"none": { - # "rpxy_configs": ["p1_plain", "p2_plain", "p3"], - #}, - #"client": { - # "rpxy_configs": ["p1_plain", "p2_tls", "p3"], - #}, - "server": { - "rpxy_configs": ["p1_tls", "p2_plain", "p3"], - }, -} - -def make_rpxy_config(outdir, domains, cryptodir): - if outdir[-1] != "/": - outdir += "/" - if cryptodir[-1] != "/": - cryptodir += "/" - - for config_name in RPXY_CONFIGS: - config = RPXY_CONFIGS[config_name] - f = open(outdir+config_name+".toml", "w") - f.write("listen_port = {}\nlisten_port_tls = {}\n".format(config["listen_http"], config["listen_https"])) - for domain in domains: - app = domain.replace(".", "_") - root = get_domain_root(domain) - f.write(config["app"].format( - app=app, - domain=domain, - cert=cryptodir+root+".crt", - key=cryptodir+root+".key", - )) - f.close() - -def make_everything(expdir, domains): - os.makedirs(expdir, exist_ok=True) - if expdir[-1] != "/": - expdir += "/" - for alg in CERT_SIGN_ALGS: - algdir = expdir+"certs/"+alg - os.makedirs(algdir, exist_ok=True) - make_certs(algdir, domains, alg) - # this will be a symbolic link to the chosen certs directory - cryptodir = expdir+"current_certs" - for setup in SETUPS: - setupdir = expdir+"setups/"+setup - os.makedirs(setupdir, exist_ok=True) - make_rpxy_config(setupdir, domains, cryptodir) - -def choose_cert_alg(expdir, alg): - if expdir[-1] != "/": - expdir += "/" - try: - os.unlink(expdir+"current_certs") - except FileNotFoundError as e: - pass - # WHY is dst pointing to src????? - os.symlink(expdir+"certs/"+alg, expdir+"current_certs", True) - -def choose_impl(expdir, p, impl): - if expdir[-1] != "/": - expdir += "/" - os.symlink(os.getcwd()+"/rpxy_rustls_"+impl, expdir+str(p)+"_rpxy", False) - -def run_rpxy(expdir, repodir, setup, p, impl, ciphers=None, kexes=None): - if expdir[-1] != "/": - expdir += "/" - repodir = repodir.removesuffix("/") - env = {"RUST_LOG": "debug"} - if ciphers: - env["CIPHERS"] = ",".join(ciphers) - if kexes: - env["KEXES"] = ",".join(KEXES) - print("RUN", [repodir+"/rpxy_rustls_"+impl, "--config", expdir+"setups/"+setup+"/"+p+".toml"]) - return subprocess.Popen([repodir+"/rpxy_rustls_"+impl, "--config", expdir+"setups/"+setup+"/"+p+".toml"], env=env) - -# https://stackoverflow.com/questions/8775598/how-to-start-a-background-process-with-nohup-using-fabric -def runbg(ssh, cmd, vars={}): - strvars = "" - for var in vars: - strvars += f"export {var}="+vars[var]+" && " - return ssh.run(f"{strvars}dtach -n `mktemp -u /tmp/dtach.XXXX` {cmd}") - -def run_exp(ssh, expdir, p2_path, exps): - from selenium import webdriver - import time - import invoke - - logfile = open(expdir+"/log-"+str(int(time.time())), "w") - - driver = webdriver.Firefox() - driver.get("about:config") - driver.execute_script(SCRIPT_FIREFOX_HOSTS) - time.sleep(1) - for expname in exps: - exp = exps[expname] - for impl in exp["impls"]: - sh(f"killall rpxy_rustls_{impl}") - try: - ssh.run(f"killall rpxy_rustls_{impl}") - except invoke.exceptions.UnexpectedExit as e: - pass - for impl in exp["impls"]: - for alg in exp["cert"]: - for kex in exp["kexes"]: - for cipher in exp["ciphers"]: - choose_cert_alg(expdir, alg) - ssh.run(f"python {p2_path}/exp.py cert {alg}") - for setup in SETUPS: - print(f"EXPERIMENT {expname}: {impl} {alg} {kex} {cipher} {setup}") - setupdir = expdir+"setups/"+setup - p1 = run_rpxy(expdir, REPODIR, setup, SETUPS[setup]["rpxy_configs"][0], impl) - p3 = run_rpxy(expdir, REPODIR, setup, SETUPS[setup]["rpxy_configs"][2], impl) - p2_rpxy_config = SETUPS[setup]["rpxy_configs"][1] - vars = {"CIPHERS": cipher, "KEXES": kex} - - runbg(ssh, f"{p2_path}/rpxy_rustls_{impl} --config {expdir}/setups/{setup}/{p2_rpxy_config}.toml --log-dir /dev/shm", vars) - - start = time.time() - for url in exp["urls"]: - driver.get(url) - #time.sleep(20) - input("???") - end = time.time() - logfile.write(f"{expname} {impl} {alg} {kex} {cipher} {setup} {start} {end}\n") - - # DO the experiment - - p1.kill() - p3.kill() - ssh.run(f"killall rpxy_rustls_{impl}") - ssh.run(f"killall dtach") - logfile.flush() - driver.quit() - -def update_certs(): - import platform - dist = platform.freedesktop_os_release()["ID"] - if dist == "debian": - for alg in CERT_SIGN_ALGS: - sh([ - f"sudo cp {EXPDIR}/certs/{alg}/ca.crt /usr/local/share/ca-certificates/ca-{alg}.crt", - f"sudo chown root:root /usr/local/share/ca-certificates/ca-{alg}.crt" - ]) - sh("sudo update-ca-certificates") - elif dist == "arch": - for alg in CERT_SIGN_ALGS: - sh([ - f"sudo cp {EXPDIR}/certs/{alg}/ca.crt /etc/ca-certificates/trust-source/anchors/ca-{alg}.crt", - f"sudo chown root:root /etc/ca-certificates/trust-source/anchors/ca-{alg}.crt" - ]) - sh("sudo update-ca-trust extract") - -# copy local dir src to remote parent dir dst (src's copy will be a subdir of dst) -def upload_dir(ssh, src, dst): - src = src.removesuffix("/") - src_name = src.split("/")[-1] - os.chdir(f"{src}/..") - os.system(f"tar -czf {src}/../tmp.tar.gz {src_name}") - print(ssh.put(src+"/../tmp.tar.gz", dst)) - print(ssh.run(f"cd {dst} && tar -xf tmp.tar.gz")) - -def connect_ssh(): - import fabric - ssh_passphrase = "--passphrase" in sys.argv - connect_kwargs = {} - if ssh_passphrase: - import getpass - connect_kwargs["passphrase"] = getpass.getpass("Enter passphrase for SSH key: ") - if P2_PSW != None: - connect_kwargs["password"] = P2_PSW - ssh = fabric.Connection(P2_SSH, connect_kwargs=connect_kwargs) - return ssh - -if __name__ == "__main__": - if len(sys.argv) < 2 or sys.argv[1] in ["h", "help", "?", "-h", "-help", "--help", "/?"]: - print("""Options: - make Create everything - cert Select cert signature algorithm - send - update-certs - run Run experiment - -Signature algorithms: - {sig_algs} - -Implementations: - {impls} - -Run options: - --passphrase Prompt SSH key decryption passphrase (when using pubkey login) -""".format( - sig_algs=" ".join(CERT_SIGN_ALGS), - impls=" ".join(IMPLS), -)) - exit() - - opt = sys.argv[1] - if opt == "make": - make_everything(EXPDIR, DOMAINS) - elif opt == "cert": - alg = sys.argv[2] - if not alg in CERT_SIGN_ALGS: - print("Error: alg must be in", CERT_SIGN_ALGS) - exit(1) - choose_cert_alg(EXPDIR, alg) - elif opt == "send": - ssh = connect_ssh() - upload_dir(ssh, EXPDIR, "/dev/shm") - elif opt == "update-certs": - update_certs() - elif opt == "run": - ssh = connect_ssh() - run_exp(ssh, EXPDIR, P2_REPODIR, EXPERIMENTS) - else: - print("Unknown command, use help for help") - exit(1)