Initial commit

This commit is contained in:
Pascal Engélibert 2025-11-05 14:28:26 +01:00
commit a71491c068
13 changed files with 1470 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
netreplay
rpxy_*

270
README.md Normal file
View file

@ -0,0 +1,270 @@
# 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.
### rpxy
Reverse-proxy utilisant RusTLS.
### WolfSSL
```bash
git clone https://github.com/wolfSSL/wolfssl --depth 1
cd wolfssl
sh autogen.sh
./configure --enable-all --enable-all-crypto --disable-shared --prefix=/opt/wolfssl-rs/
make
sudo make install
```
### self-signed cert
```bash
openssl req -x509 -newkey rsa:2048 -keyout /tmp/foo.home.key -subj "/CN=foo.home/C=AT/ST=Lyon/L=Lyon/O=MyOrg" -out /tmp/foo.home.crt -nodes -sha256 -addext "subjectAltName=DNS:foo.home"
```
### Client
Automatize experiments using [Selenium](https://www.selenium.dev/documentation/webdriver/getting_started/)
#### Experiment management
* Manager tells P2 what shared libs and rpxy binary to load.
* Tell P1, P2, P3 what rpxy config to load.
* Start measures.
* Start Yocto (USB).
* Start
#### Ad-hoc proxy?
Features:
* Use RusTLS and any backend easily
* Listen to plain HTTP or TLS (1.2 or 1.3)
*
## 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
* 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
*
## Sources
* [RFC8446 (TLS 1.3)](https://datatracker.ietf.org/doc/html/rfc8446)
## Notes
Install sa on p2:
```bash
sudo apt install acct
sudo chmod +s /sbin/sa
```
Override DNS in browser:
```bash
firefox -P tlsbench
```
In `about:config`, set `devtools.chrome.enabled` to `true`.
Set default DNS in settings.
In console:
```js
const gOverride = Cc["@mozilla.org/network/native-dns-override;1"].getService(Ci.nsINativeDNSResolverOverride);
gOverride.clearOverrides();
var names = ["apple.com", "www.apple.com", "mzstatic.com", "youtube.com", "www.youtube.com", "i.ytimg.com", "fonts.gstatic.com", "www.google.com", "accounts.google.com", "yt3.ggpht.com", "www.gstatic.com"];
for(var i in names) {
gOverride.addIPOverride(names[i], "127.0.0.1");
}
```
Authorize rpxy to bind to ports 80 and 443:
```bash
sudo setcap CAP_NET_BIND_SERVICE=+eip rpxy_rustls_ring
```
Add CA certificate on ArchLinux:
```bash
sudo cp /dev/shm/exp/certs/rsa2048/ca.crt /etc/ca-certificates/trust-source/anchors/ca-rsa2048.crt
sudo cp /dev/shm/exp/certs/rsa3072/ca.crt /etc/ca-certificates/trust-source/anchors/ca-rsa3072.crt
sudo cp /dev/shm/exp/certs/rsa4096/ca.crt /etc/ca-certificates/trust-source/anchors/ca-rsa4096.crt
sudo cp /dev/shm/exp/certs/secp384r1/ca.crt /etc/ca-certificates/trust-source/anchors/ca-secp384r1.crt
sudo chown root:root /etc/ca-certificates/trust-source/anchors/ca-*.crt
sudo update-ca-trust extract
```
```bash
python exp.py make
python exp.py send-setups
python exp.py send-certs
python exp.py run
```
On Debian, update-certs says 0 certs added even if it has actually updated some certs. This step is still needed.
## Problems
### Youtube gives 502 bad gateway.
* Works with bare curl. (not hiding we're a bot)
* We have same JA3 fingerprint as Firefox.
* HTTP request is intact.
* JA4 fingerprint different from Firefox but existing for some browsers.
### Modèle d'expérience à revoir
* Le relai d'une vidéo streaming avec TLS prend max 3% d'un cœur sur le Pi3, soit pas beaucoup plus que le bruit.
* Il faudrait spammer avec plusieurs connexions pour voir un effet significatif.
* On peut spammer le streaming vidéo, mais pas le reste (antibots).
* On ne devrait pas spammer sur Renater...
* Il faut donc tout faire en local.
Solutions :
* Copier les sites en statique et les servir avec Apache. => OK pour des sites propres genre Wikipedia, WordPress (à voir pour la pub)
* Installer des instances => Peertube, WordPress
* Copier le trafic et le rejouer => risque de demander beaucoup de dev
### 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.
## TODO
* Partie serveur sans TLS de netreplay -> sans SNI, il faut parser le HTTP >.<
* exp.py: détecter la fin du replay
* yoctowatt
* mesures CPU, mémoire, bande passante sur p2
* CPU: paquet acct, commande `sa -m`

604
exp.py Normal file
View file

@ -0,0 +1,604 @@
#!/usr/bin/python3
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"
P2_ADDR = "192.168.3.14"
DOMAINS_ = [
# Apple
"apple.com", "www.apple.com", "graffiti-tags.apple.com", "securemetrics.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.gstatic.com",
"www.google.com", "accounts.google.com",
"yt3.ggpht.com",
"rr1---sn-gxo5uxg-jqbl.googlevideo.com",
"rr2---sn-gxo5uxg-jqbl.googlevideo.com",
"rr4---sn-q4fl6nds.googlevideo.com",
# Amazon
"amazon.com", "www.amazon.com",
# Wikipedia article
"fr.wikipedia.org", "upload.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",
# Peertube video
"videos.domainepublic.net",
]
WATTMETER = True
RECORDS = [
{ "filename": "youtube", "repeat": 10 },
#{ "filename": "peertube", "repeat": 10 },
#{ "filename": "wikipedia", "repeat": 10 },
#{ "filename": "apple", "repeat": 10 },
#{ "filename": "google", "repeat": 10 },
]
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_rs", # 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 in either 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,
# "records": RECORDS,
# "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,
# "records": RECORDS,
# "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,
# "records": RECORDS,
# "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"],
# },
"debug": {
"impls": IMPLS,
"records": RECORDS,
"ciphers": [
"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,ECDHE_RSA_WITH_AES_128_GCM_SHA256",
],
"kexes": ["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="", n:int=1, args:list=sys.argv):
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:
print(cmd)
os.system(cmd)
elif type(cmds) == str:
print(cmds)
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, make_ca):
if outdir[-1] != "/":
outdir += "/"
if make_ca:
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])
# Make a cert for all domains because choosing a certificate as a proxy is a real pain
make_sk(outdir+"all.key", alg)
make_cert(outdir+"all.crt", outdir+"all.key", outdir+"ca.crt", outdir+"ca.key", "wikipedia.org", domains)
RPXY_CONFIGS = {
"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}" }}], 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}" }}], set_host = "{domain}"}}]
[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}" }}], set_host = "{domain}"}}]
"""
},
"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}", 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}_p2]
server_name = "{domain}.p2"
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_config": "plain",
"netreplay_tls_mode": "none",
"p2_port": 80,
"listen_port": 80,
},
"client": {
"rpxy_config": "tls",
"netreplay_tls_mode": "server",
"p2_port": 80,
"listen_port": 443,
},
"server": {
"rpxy_config": "plain",
"netreplay_tls_mode": "client",
"p2_port": 443,
"listen_port": 80,
},
#"both": {
# "rpxy_config": "tls",
# "netreplay_tls_mode": "both",
# "p2_port": 443,
# "listen_port": 443,
#},
}
def make_rpxy_config(outdir, domains, cryptodir, config_name):
if outdir[-1] != "/":
outdir += "/"
if cryptodir[-1] != "/":
cryptodir += "/"
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, make_ca):
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, make_ca)
# this will be a symbolic link to the chosen certs directory
cryptodir = expdir+"current_certs"
configdir = expdir+"configs/"
os.makedirs(configdir, exist_ok=True)
for config_name in RPXY_CONFIGS:
make_rpxy_config(configdir, domains, cryptodir, config_name)
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, config_name, 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)
return subprocess.Popen([repodir+"/rpxy_rustls_"+impl, "--config", expdir+"configs/"+config_name+".toml"], env=env)
def run_netreplay(expdir, repodir, record, p2_addr, p2_port, listen_port, tls_mode, only_record=None):
if expdir[-1] != "/":
expdir += "/"
repodir = repodir.removesuffix("/")
env = {"RUST_LOG": "debug"}
cmd = [repodir+"/netreplay", repodir+"/records/"+record["filename"], "play", p2_addr, str(p2_port), str(listen_port), expdir+"current_certs", tls_mode, "-r", str(record["repeat"])]
if only_record != None:
cmd += ["--record", only_record]
print(" ".join(cmd))
return subprocess.Popen(cmd, env=env)
# https://stackoverflow.com/questions/8775598/how-to-start-a-background-process-with-nohup-using-fabric
def runbg(ssh, cmd, vars={}):
print("[SSH]", cmd)
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 get_cpu_stat(ssh):
res = ssh.run("/sbin/sa --list-all-names", hide=True)
for line in res.stdout.split("\n"):
if "rpxy" in line:
return float(re.finditer("\\s(\\d+\\.\\d+)cp\\s", line).__next__().group(1))
return 0.0
def get_net_stat(ssh):
res = ssh.run("cat /proc/net/netstat", hide=True)
items = res.stdout.split("\n")[3].split(" ")
bytes_in = int(items[7])
bytes_out = int(items[8])
return (bytes_in, bytes_out)
def run_exp(ssh, expdir, p2_path, exps, only_record=None):
wattmeter = None
if WATTMETER:
errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
sys.exit("init error" + errmsg.value)
wattmeter = YPower.FirstPower()
if wattmeter is None or not wattmeter.isOnline():
print("No YoctoWatt connected")
exit(1)
sh("killall netreplay")
for expname in exps:
exp = exps[expname]
for impl in exp["impls"]:
try:
ssh.run(f"killall rpxy_rustls_{impl}")
except invoke.exceptions.UnexpectedExit as e:
pass
rpxy_cpu = get_cpu_stat(ssh)
logfile = open(expdir+"/log-"+str(int(time.time())), "w")
logfile.write("exp impl alg kex cipher setup record t_start t_end cpu bytes_in bytes_out Wh\n")
for expname in exps:
exp = exps[expname]
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:
setupdir = expdir+"setups/"+setup
for record in exp["records"]:
print(f"EXPERIMENT {expname}: {impl} {alg} {kex} {cipher} {setup}")
p2_rpxy_config = SETUPS[setup]["rpxy_config"]
vars = {"CIPHERS": cipher, "KEXES": kex}
runbg(ssh, f"{p2_path}/rpxy_rustls_{impl} --config {expdir}/configs/{p2_rpxy_config}.toml --log-dir /dev/shm", vars)
time.sleep(1)
p2_bytes_in, p2_bytes_out = get_net_stat(ssh)
energy = 0
if WATTMETER:
energy = wattmeter.get_meter()
start = time.time()
netreplay = run_netreplay(expdir, REPODIR, record, P2_ADDR, SETUPS[setup]["p2_port"], SETUPS[setup]["listen_port"], SETUPS[setup]["netreplay_tls_mode"], only_record=only_record)
# TODO detect when netreplay has finished
try:
netreplay.wait()
except KeyboardInterrupt:
netreplay.kill()
try:
ssh.run(f"killall rpxy_rustls_{impl}")
except invoke.exceptions.UnexpectedExit as e:
pass
try:
ssh.run(f"killall dtach")
except invoke.exceptions.UnexpectedExit as e:
pass
exit(0)
#time.sleep(30)
#sh("killall netreplay")
try:
ssh.run(f"killall rpxy_rustls_{impl}")
except invoke.exceptions.UnexpectedExit as e:
pass
try:
ssh.run(f"killall dtach")
except invoke.exceptions.UnexpectedExit as e:
pass
end = time.time()
new_energy = 0
if WATTMETER:
new_energy = wattmeter.get_meter()
new_p2_bytes_in, new_p2_bytes_out = get_net_stat(ssh)
new_rpxy_cpu = get_cpu_stat(ssh)
record_filename = record["filename"]
rpxy_cpu_diff = new_rpxy_cpu - rpxy_cpu
p2_bytes_in_diff = new_p2_bytes_in - p2_bytes_in
p2_bytes_out_diff = new_p2_bytes_out - p2_bytes_out
energy_diff = new_energy - energy
rpxy_cpu = new_rpxy_cpu
logfile.write(f"{expname} {impl} {alg} {kex} {cipher} {setup} {record_filename} {start} {end} {rpxy_cpu_diff} {p2_bytes_in_diff} {p2_bytes_out_diff} {energy}\n")
logfile.flush()
if WATTMETER:
YAPI.FreeAPI()
def update_certs():
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():
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 [-c] Create everything
cert <alg> Select cert signature algorithm
send Send configs and certs to p2
update-certs Update system's certs
run Run experiment
script Print Firefox script to override DNS
Signature algorithms:
{sig_algs}
Implementations:
{impls}
Make options:
-c Make CA cert (otherwise use already existing one)
Run options:
--passphrase Prompt SSH key decryption passphrase (when using pubkey login)
--count Do not run experiments but display number of experiments
--record <id> Only play this record
""".format(
sig_algs=" ".join(CERT_SIGN_ALGS),
impls=" ".join(IMPLS),
))
exit()
opt = sys.argv[1]
if opt == "make":
make_ca = "-c" in sys.argv
make_everything(EXPDIR, DOMAINS, make_ca)
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":
import fabric
ssh = connect_ssh()
upload_dir(ssh, EXPDIR, "/dev/shm")
elif opt == "update-certs":
import platform
update_certs()
elif opt == "run":
if "--count" in sys.argv:
exps = 0
for expname in EXPERIMENTS:
exp = EXPERIMENTS[expname]
exps += len(exp["impls"]) * len(exp["cert"]) * len(exp["kexes"]) * len(exp["ciphers"]) * len(exp["records"])
print("Experiments to make:", exps * len(SETUPS))
exit(0)
import time
import invoke
import re
import fabric
if WATTMETER:
import yoctopuce
from yoctopuce.yocto_api import *
from yoctopuce.yocto_power import *
ssh = connect_ssh()
run_exp(ssh, EXPDIR, P2_REPODIR, EXPERIMENTS, only_record=getargv("--record", None))
elif opt == "script":
print(SCRIPT_FIREFOX_HOSTS)
else:
print("Unknown command, use help for help")
exit(1)

535
exp_proxies.py Normal file
View file

@ -0,0 +1,535 @@
#!/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_rs", # 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)

6
install-certs-arch.sh Normal file
View file

@ -0,0 +1,6 @@
sudo cp /dev/shm/exp/certs/rsa2048/ca.crt /etc/ca-certificates/trust-source/anchors/ca-rsa2048.crt
sudo cp /dev/shm/exp/certs/rsa3072/ca.crt /etc/ca-certificates/trust-source/anchors/ca-rsa3072.crt
sudo cp /dev/shm/exp/certs/rsa4096/ca.crt /etc/ca-certificates/trust-source/anchors/ca-rsa4096.crt
sudo cp /dev/shm/exp/certs/secp384r1/ca.crt /etc/ca-certificates/trust-source/anchors/ca-secp384r1.crt
sudo chown root:root /etc/ca-certificates/trust-source/anchors/ca-*.crt
sudo update-ca-trust extract

6
install-certs-debian.sh Normal file
View file

@ -0,0 +1,6 @@
sudo cp /dev/shm/exp/certs/rsa2048/ca.crt /usr/local/share/ca-certificates/ca-rsa2048.crt
sudo cp /dev/shm/exp/certs/rsa3072/ca.crt /usr/local/share/ca-certificates/ca-rsa3072.crt
sudo cp /dev/shm/exp/certs/rsa4096/ca.crt /usr/local/share/ca-certificates/ca-rsa4096.crt
sudo cp /dev/shm/exp/certs/secp384r1/ca.crt /usr/local/share/ca-certificates/ca-secp384r1.crt
sudo chown root:root /usr/local/share/ca-certificates/ca-*.crt
sudo update-ca-certificates

7
mkcacert.sh Normal file
View file

@ -0,0 +1,7 @@
#!/bin/bash
# Generate CA secret key
openssl genrsa -aes256 -out ca.key -passout pass:foo 4096
# Generate CA certificate
openssl req -x509 -new -nodes -key certs/ca.key -sha256 -days 1826 -out certs/ca.crt -passin pass:foo -subj '/CN=MyOrg Root CA/C=AT/ST=Vienna/L=Vienna/O=MyOrg'

29
mkcert.sh Normal file
View file

@ -0,0 +1,29 @@
#!/bin/bash
# $1 is the first script argument, that will be the certificate's name.
# Other arguments are other domain names to be added to the certificate.
# Generate secret key
openssl req -new -nodes -out certs/$1.csr -newkey rsa:2048 -keyout certs/$1.key -subj "/CN=$1/C=AT/ST=Vienna/L=Vienna/O=MyOrg"
# Write certificate information
cat > certs/$1.v3.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
EOF
# Iterate script arguments
let i=1
for name in "$@"
do
echo "DNS.$i = $name" >> certs/$1.v3.ext
let i++
echo "DNS.$i = $name.localhost" >> certs/$1.v3.ext
let i++
done
# Sign certificate with CA
openssl x509 -req -in certs/$1.csr -CA certs/ca.crt -CAkey certs/ca.key -CAcreateserial -out certs/$1.crt -days 730 -sha256 -extfile certs/$1.v3.ext -passin pass:foo

3
mkcerts.sh Normal file
View file

@ -0,0 +1,3 @@
#!/bin/bash
sh mkcert.sh apple.com www.apple.com securemetrics.apple.com
sh mkcert.sh mzstatic.com www.mzstatic.com is1-ssl.mzstatic.com

BIN
records/apple Normal file

Binary file not shown.

BIN
records/google Normal file

Binary file not shown.

BIN
records/youtube Normal file

Binary file not shown.

8
run.sh Normal file
View file

@ -0,0 +1,8 @@
#kissdns kissdns.json 5532
RUST_LOG=debug ./rpxy_rustls_ring --config experiments/p1_tls.toml &
RUST_LOG=debug ./rpxy_rustls_ring --config experiments/p2_tls.toml &
RUST_LOG=debug ./rpxy_rustls_ring --config experiments/p3.toml &
# By default, ^C does not kill subprocesses. This fixes it.
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
wait