Clean readme

This commit is contained in:
Pascal Engélibert 2026-03-17 15:25:27 +01:00
commit b8ad9c1c03
3 changed files with 27 additions and 841 deletions

323
README.md
View file

@ -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 <record-number>` and note the missing packet number.
Run `netreplay <record-file> remove <new-record-file> <record-number> <packet-number>` 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 <path/to/log>
```
@ -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
```

10
exp.py
View file

@ -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:

View file

@ -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 <alg> 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)