HTTPS, HSTS und HPKP auf einem Uberspace
Ganz bestimmt total sicher!
Ich habe in letzter Zeit mal ein bisschen herum experimentiert, was Webseitenverschlüsselung angeht.
So ein Uberspace ist ja schon toll. Man kann da sogar eigene Domains aufschalten. Aber Seiten nur über HTTP aufzurufen, ist ja schon ziemlich unsicher. Jeder Angreifer, der auch nur passiv den Datenverkehr mitschneiden kann, sieht alles.
Was kann man dagegen tun?
Die Lösung (oder jedenfalls ein Teil davon) lautet:
HTTPS
Ja, genau. Laut Wiki von Uberspace funktioniert HTTPS ja standardmäßig per https://benutzername.host.uberspace.de/
- aber diese URL sieht ja schon irgendwie blöd aus.
Für alles andere braucht man ein eigenes Zertifikat. Aber das ist nicht schwierig oder kompliziert. Am einfachsten (und kostenlos!) sind Zertifikate von Let‘s Encrypt.
Zum Einrichten muss man einmal Folgendes ausführen:
ytvwld@betelgeuse ~> uberspace-letsencrypt We have now created the following config file for you: /home/ytvwld/.config/letsencrypt/cli.ini Please review especially the domain list we have generated for you and adapt the 'domains' setting if you want to make some changes: asteroid.ytvwld.de www.ytvwld.de ytvwld.de [...] When you're done you can use the official Let's Encrypt client by executing: letsencrypt certonly You can safely ignore all 'Root (sudo) is required to run most of letsencrypt functionality' warnings.
Evtl. wichtig hierbei: Die erste Domain hier wird als Hauptdomain für das neue Zertifikat genommen.
Wenn man da was ändern möchte, also einfach den entsprechenden Abschnitt ~/.config/letsencrypt/cli.ini
bearbeiten:
[...] Beware that Let's Encrypt does NOT support wildcard hostnames. # If you're using wildcards you have to add each subdomain explicitly. domains = asteroid.ytvwld.de,[...],www.ytvwld.de,ytvwld.de [...]
Dann kann man auch schon das Zertifikat erstellen lassen:
ytvwld@betelgeuse ~> letsencrypt certonly [...] IMPORTANT NOTES: - If you lose your account credentials, you can recover through e-mails sent to [...]. - Congratulations! Your certificate and chain have been saved at /home/ytvwld/.config/letsencrypt/live/asteroid.ytvwld.de/fullchain.pem. Your cert will expire on 2016-05-03. To obtain a new version of the certificate in the future, simply run Let's Encrypt again. - Your account credentials have been saved in your Let's Encrypt configuration directory at /home/ytvwld/.config/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Let's Encrypt so making regular backups of this folder is ideal.
Dabei auftretende Fragen einfach so beantworten, wie man es gerne hätte. Nun hat man u.a. einen privaten Schlüssel und ein Zertifikat:
ytvwld@betelgeuse ~> ls -l ~/.config/letsencrypt/live/asteroid.ytvwld.de/ insgesamt 4 lrwxrwxrwx. 1 ytvwld ytvwld 42 3. Feb 20:18 cert.pem -> ../../archive/asteroid.ytvwld.de/cert5.pem lrwxrwxrwx. 1 ytvwld ytvwld 43 3. Feb 20:18 chain.pem -> ../../archive/asteroid.ytvwld.de/chain5.pem lrwxrwxrwx. 1 ytvwld ytvwld 47 3. Feb 20:18 fullchain.pem -> ../../archive/asteroid.ytvwld.de/fullchain5.pem lrwxrwxrwx. 1 ytvwld ytvwld 45 3. Feb 20:18 privkey.pem -> ../../archive/asteroid.ytvwld.de/privkey5.pem
Jetzt muss man noch den Webserver so einrichten, dass der die auch benutzt:
ytvwld@betelgeuse ~> uberspace-add-certificate -k ~/.config/letsencrypt/live/asteroid.ytvwld.de/privkey.pem -c ~/.config/letsencrypt/live/asteroid.ytvwld.de/cert.pem 🔑 Found key... 📝 Found certificate... 🔑 Key seems valid, moving on... 📝 Certificate seems valid, moving on... (step by step) 🔐 Certificate matches key, moving on... (we're getting there!) 📜 Magically getting intermediate certificate(s) if there are any needed... (hold on tight) 🔐 Checking for asteroid.ytvwld.de. 🌍 temporary webserver started... ✅ certificate is valid. 🌍 killed temporary webserver... 🚀 All good! Your new certificate will be live within the next five minutes.
Wenn man noch mal nachgucken möchte, ob das tatsächlich geklappt hat:
ytvwld@betelgeuse ~> uberspace-list-certificates common name: asteroid.ytvwld.de issuer: Let's Encrypt Authority X1 valid until: 2016-05-03 20:17:00 CEST will be removed in 89 days. alternative name: www.ytvwld.de alternative name: ytvwld.de alternative name: asteroid.ytvwld.de [...]
So, jetzt sollte die Seite auch über die eigene Domain per HTTPS aufrufen lassen.
Aber was bringt uns das jetzt eigentlich?
Wenn Leute die HTTPS-Version der Seite aufrufen, können Angreifer, die einfach nur passiv den Datenverkehr mitschneiden, nicht mehr sehen, was tatsächlich übertragen wird.
Was fehlt noch?
Die wenigsten Leute werden absichtlich die HTTPS-Version aufrufen.
HTTPS erzwingen
Dazu kann man sie aber ganz einfach zwingen. Man muss nur die .htaccess
(für die entsprechende Seite - oder für alle Seiten: /var/www/virtual/benutzername/.htaccess
) anpassen:
[...] RewriteEngine On RewriteCond %{HTTPS} !=on RewriteCond %{ENV:HTTPS} !=on RewriteRule .* https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L] [...]
Was hat das jetzt gebracht?
Besucher können die Seite nun nicht mehr unverschlüsselt aufrufen. Passives Mitschneiden des Traffics ist nun also zwecklos.
Was fehlt noch?
Angreifer, die aktiv die Daten verändern können, können z.B. mit SSLstrip eingreifen. Wenn ich da jetzt mal das Diagramm klaue:
Benutzer <== HTTP ==> Angreifer <== HTTPS ==> Website
Wir haben nun erzwungen, dass die rechte Seite HTTPS sein muss. Toll, aber das reicht nicht. Der Browser des Clients muss wissen, dass unsere Seite nur über HTTPS aufgerufen werden kann / soll / darf / muss.
HSTS
HTTP Strict Transport Security bietet dies teilweise.
Dazu einfach noch mal die .htaccess
bearbeiten und Folgendes einfügen:
[...] header always set Strict-Transport-Security "max-age=31556926 ; includeSubDomains" [...]
Das bedeutet: Diese Seite (und alle ihre Subdomains) soll ein Jahr lang (356,25 Tage) nur noch über HTTPS aufgerufen werden.
Was hat das jetzt gebracht?
Wenn ein Client im letzten Jahr einmal eine erfolgreiche verschlüsselte Verbindung zu unserer Seite aufgebaut hatte, dann wird er ein Jahr lang auch nur solche akzeptieren.
Was fehlt noch?
Für den ersten Verbindungsaufbau bringt uns das nichts.
HSTS preloaden
Aber wir können unsere Seite einer Liste mit vielen HSTS-Seiten hinzufügen, die mehrere Browser verwenden.
Dazu einfach noch mal die .htaccess
bearbeiten und das preload
ergänzen:
[...] header always set Strict-Transport-Security "max-age=31556926 ; includeSubDomains ; preload" [...]
Nun einfach noch unsere Domain auf dieser Seite eingeben.
Was hat das jetzt gebracht?
Alle großen Browser wissen nun, dass unsere Seite nur via HTTPS aufrufbar ist. Dies schützt uns und Besucher vor Angreifern, die den Traffic beliebig manipulieren und mitschneiden können, aber kein gültiges Zertifikat für unsere Domain haben.
Was fehlt noch?
Angreifer, die über ein gültiges Zertifikat für unsere Domain verfügen (was eigentlich nie passieren sollte!), können sich gegenüber Besuchern als wir und gegenüber uns als Besucher ausgeben - also:
Benutzer <== HTTPS ==> Angreifer <== HTTPS ==> Website
HPKP
Mit HTTP Public Key Pinning können wir festlegen, welche Zertifikate gültig für unsere Seite sind. Eine ausführlich Anleitung findet sich hier.
Ein Problem dabei ist, dass unsere Zertifikate von Let’s Encrypt nur drei Monate gültig sind und jedesmal den privaten Schlüssel wechseln. Die zu pinnen, wird also schwierig.
Aber wir können ja einfach Let’s Encrypt als CA pinnen. Deren Hash ist YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=
.
(Dies kann man hier herausfinden oder, indem man das Zertifikat speichert und dann openssl x509 -in Let\'sEncryptAuthorityX1.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
ausführt.)
Zusätzlich brauchen wir noch Backups. Hierzu bietet es sich an, auf mehreren Rechnern (mindestens zwei) Folgendes zu tun:
- Einen privaten Schlüssel erstellen:
openssl genrsa -out backup.key 4096
- Einen CSR erstellen:
openssl req -new -key backup.key -config openssl.conf -batch -out backup.csr
- Hashen:
openssl req -pubkey < backup.csr | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64
Meine verwendete openssl.conf
ist diese (sie ist allerdings nicht für Let’s Encrypt geeignet wegen den Wildcards):
#Quellen: https://svn.cacert.org/CAcert/Software/CSRGenerator/csr und http://apetec.com/support/GenerateSAN-CSR.htm [ req ] distinguished_name = req_distinguished_name encrypt_key = no req_extensions = v3_req # Extensions to add to certificate request [ req_distinguished_name ] commonName = Common Name (eg, YOUR name) commonName_default = asteroid.ytvwld.de commonName_max = 64 [ v3_req ] subjectAltName=@alt_names [ alt_names ] DNS.1 = *.asteroid.ytvwld.de DNS.2 = *.ytvwld.de DNS.3 = ytvwld.de
Jetzt haben wir drei (oder mehr) Hashes.
pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; pin-sha256="3UrfkYs2Fysa5tb+I/pe6cjOqPphiKM2G0U0GaXNcYY="; pin-sha256="0Iq1O1lr5s01CpdFH9DNucq5U3KUpa3L5j3x+3CahgU=";
HPKP soll auch für Subdomains gelten:
includeSubdomains;
Wie viele Sekunden soll das alles gelten?
max-age=31536000;
Es wäre auch schön, wenn es Benachrichtigungen gäbe, wenn die Verbindung manipuliert wird - report-uri.io bietet so etwas an:
report-uri="https://ytvwld.report-uri.io/r/default/hpkp/enforce"
Und damit haben wir unseren HPKP-Header auch schon fertig:
pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; pin-sha256="3UrfkYs2Fysa5tb+I/pe6cjOqPphiKM2G0U0GaXNcYY="; pin-sha256="0Iq1O1lr5s01CpdFH9DNucq5U3KUpa3L5j3x+3CahgU="; includeSubdomains; max-age=31536000; report-uri="https://ytvwld.report-uri.io/r/default/hpkp/enforce"
Den müssen wir jetzt noch Apache beibringen. Also die .htaccess
bearbeiten und Folgendes einfügen:
[...] header always set Public-Key-Pins 'pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; pin-sha256="3UrfkYs2Fysa5tb+I/pe6cjOqPphiKM2G0U0GaXNcYY="; pin-sha256="0Iq1O1lr5s01CpdFH9DNucq5U3KUpa3L5j3x+3CahgU="; includeSubdomains; max-age=31536000; report-uri="https://ytvwld.report-uri.io/r/default/hpkp/enforce"' [...]
Was hat das jetzt gebracht?
Nur Zertifikate von Let’s Encrypt oder mit unseren Backup-Keys sind für die Domain noch gültig.
Was fehlt noch?
Automatische Verlängerung.
Automatische Verlängerung
Das folgende Skript sollte das regeln:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #!/usr/bin/env python3 from time import sleep from random import randint from subprocess import Popen, PIPE import os import sys #sleep for a random time sleep(randint(0, 6000)) #get the configuration folder letsencrypt_folder = os.path.join(os.environ["HOME"], os.path.join(".config", "letsencrypt")) hostname = "asteroid.ytvwld.de" print ("Frage neues Zertifikat an...") Popen(["letsencrypt", "--verbose", "--non-interactive", "--renew-by-default", "--agree-tos", "--expand", "certonly"]).wait() print ("Tausche Zertifikat aus...") folder = os.path.join(os.path.join(letsencrypt_folder, "live"), hostname) print ("(importiere aus {}...)".format(folder)) Popen(["uberspace-add-certificate", "-k", os.path.join(folder, "privkey.pem"), "-c", os.path.join(folder, "cert.pem")]).wait() |
Dies kann man als ~/bin/certificate-renewal.py
speichern, ausführbar machen und monatlich in cron einstellen.
Was hat das jetzt gebracht?
Unsere Zertifikate und Schlüssel werden regelmäßig erneuert.
Was fehlt noch?
Natürlich hilft das alles nichts, wenn jemand Zugriff auf den Server bekommt.
Das Ergebnis
… findet man im SSL-Test.