Co TLS naprawdę chroni w połączeniu HTTPS
Otwórz stronę przez zwykłe http:// w sieci, której nie kontrolujesz, na lotnisku albo na routerze w kawiarni, a wszystko, co wysyłasz, leci jako czytelny tekst. Ścieżka URL, ciasteczka, pola formularza, hasło w POST-cie logowania: każdy w tej sieci ze snifferem pakietów odczyta to w locie. TLS, Transport Layer Security, to protokół, który to blokuje. Siedzi między TCP a protokołem aplikacji, więc zanim HTTP zacznie w ogóle mówić, połączenie jest już zaszyfrowane i powiązane ze zweryfikowaną tożsamością serwera. HTTPS to po prostu HTTP puszczony przez takie połączenie zamiast przez zwykłe.
TLS łączy trzy gwarancje, i działają tylko razem. Szyfrowanie oznacza, że obserwator w sieci widzi wyłącznie ciąg zaszyfrowany, nic więcej. Integralność oznacza, że zmieniony w trakcie transmisji bit zostaje wykryty, a połączenie pada zamiast dostarczyć zmienione dane. Uwierzytelnianie oznacza, że klient może potwierdzić, że dotarł do example.com, a nie do kogoś, kto pierwszy odpowiedział pod tym adresem IP. Stracisz jedną z tych trzech rzeczy, a pozostałe dwie przestają wiele znaczyć: zaszyfrowany kanał do oszusta nie jest bezpiecznym kanałem.
Jak handshake TLS buduje zaszyfrowany kanał
Zanim wyjdzie jakiekolwiek żądanie HTTP, klient i serwer przeprowadzają handshake, który ustala wspólny tajny klucz bez wysyłania tego klucza przez sieć. TLS 1.3, obecna wersja i ta, której powinieneś używać, robi to w jednej rundzie, podczas gdy TLS 1.2 potrzebował dwóch.
| |
Klient wysyła ClientHello: obsługiwane wersje TLS i zestawy szyfrów, losowy nonce, oraz zgadnięcie, którą grupę do wymiany kluczy wybierze serwer, wysłane jako key_share w tej samej wiadomości. To zgadywanie to skrót TLS 1.3. Serwer odpowiada własnym key_share w ServerHello, a obie strony wykonują wymianę Diffiego-Hellmana na tych shares, żeby niezależnie wyprowadzić ten sam symetryczny klucz sesji. Sam klucz nigdy nie przechodzi przez sieć.
Od tego momentu wszystko inne jest już zaszyfrowane tym kluczem sesji: certyfikat serwera, dowód posiadania pasującego klucza prywatnego, wiadomości Finished, które liczą sumę kontrolną całego handshake’u. Jedna runda, i połączenie działa.
TLS 1.2 potrzebował drugiej rundy, bo wymiana kluczy i negocjacja szyfru szły jako osobne kroki, zamiast się nakładać. Ta dodatkowa runda to czysta latencja. Na łączu z 150ms RTT dokłada to 150ms do każdego nowego połączenia HTTPS. To różnica widoczna wprost w czasie ładowania strony, nie tylko w statystykach bezpieczeństwa.
| TLS 1.2 | TLS 1.3 | |
|---|---|---|
| Rundy do pierwszego zaszyfrowanego bajtu | 2 | 1 |
| Wymiana kluczy | Negocjowana po zestawie szyfrów | Zgadnięta i wysłana z ClientHello |
| Renegocjacja w trakcie sesji | Dozwolona | Usunięta |
| Statyczna wymiana kluczy RSA | Dozwolona | Usunięta (forward secrecy obowiązkowe) |
| Słabe szyfry (RC4, przestarzały CBC) | Dozwolone | Usunięte |
Co udowadnia certyfikat i jak działa łańcuch zaufania
Wymiana kluczy zajmuje się szyfrowaniem. Udowodnienie, że serwer to naprawdę example.com, to zadanie certyfikatu. Certyfikat wiąże klucz publiczny z nazwą domeny i niesie podpis Certificate Authority (CA) — organizacji, której własny klucz publiczny jest już wbudowany w magazyny zaufania twojego systemu i przeglądarki.
W praktyce łańcuch ma trzy poziomy. Certyfikat root CA, samopodpisany i preinstalowany wszędzie, podpisuje certyfikat intermediate CA. Pośredni podpisuje certyfikat liścia — ten dla twojej domeny. Serwery prawie nigdy nie pokazują certyfikatu podpisanego bezpośrednio przez root: serwują liść plus pośredni, a klient odbudowuje łańcuch aż do rootu, któremu już ufa.
Pomiń pośredni certyfikat, a dostaniesz awarię, która wygląda jak duch. Przeglądarki, które mają w cache kopię tego pośredniego certyfikatu, łączą się bez problemu, więc strona działa na twoim laptopie; wszyscy inni dostają błąd zaufania. To jeden z najczęstszych błędów konfiguracji TLS w produkcji i nigdy nie powtarza się na maszynie, z której testowałeś.
Certyfikat niesie też okno ważności (notBefore / notAfter) i hostname, które pokrywa, wymienione w rozszerzeniu Subject Alternative Name (SAN). SAN to pole, które klienty naprawdę dziś czytają; stare pole Common Name jest szczątkowe. Certyfikat dla example.com nie pokrywa api.example.com, chyba że ta nazwa też jest na liście SAN, albo certyfikat jest wildcardem dla *.example.com.
Jak sprawdzić certyfikat poleceniem openssl s_client
Nie potrzebujesz przeglądarki, żeby to zobaczyć. openssl s_client otwiera surowe połączenie TLS i wypisuje, co zostało wynegocjowane:
| |
-servername ustawia SNI (Server Name Indication) — hostname wysyłany bez szyfrowania w ClientHello, żeby serwer hostujący wiele domen na jednym IP wiedział, który certyfikat pokazać. Pomiń tę flagę, a serwer multi-tenant nie ma jak wybrać.
Żeby dostać tylko datę wygaśnięcia zamiast całego zrzutu handshake’u, przekieruj do openssl x509:
| |
To wypisuje notBefore, notAfter, subject (domenę, dla której wystawiono certyfikat) i issuer (CA, która go podpisała). Dla skryptu monitorującego, który potrzebuje tylko pass/fail, -checkend przyjmuje okno w sekundach i ustawia kod wyjścia:
| |
curl -v jest szybszy do szybkiego sprawdzenia, bo wypisuje wynegocjowaną wersję TLS i łańcuch certyfikatów w ramach szczegółowego śladu handshake’u:
| |
Jak uruchomić HTTPS na localhost z mkcert
Certyfikat samopodpisany, czyli wystawiony i podpisany przez ciebie zamiast przez CA, daje szyfrowanie, ale nie uwierzytelnianie. Nikt za niego nie ręczy, więc każda przeglądarka pokazuje ostrzeżenie. W porządku do szybkiego testu z openssl. Uciążliwe przy lokalnym developmencie, gdzie chcesz, żeby https://myapp.local ładowało się bez klikania przez ostrzeżenie.
mkcert rozwiązuje to, generując lokalne CA, instalując je w magazynach zaufania systemu i przeglądarki, i wystawiając certyfikaty podpisane przez to CA:
| |
Dostajesz parę certyfikat/klucz .pem w bieżącym katalogu, której ufa każda przeglądarka na maszynie, bo teraz ufa CA zainstalowanemu przez mkcert. Skieruj konfigurację TLS serwera deweloperskiego na te dwa pliki, a ostrzeżenie znika.
Trzymaj rootCA-key.pem z dala od systemu kontroli wersji. Każdy, kto ma prywatny klucz lokalnego CA z mkcert, może wygenerować certyfikat dla dowolnej domeny, której zaufa twoja maszyna — to ta sama klasa zagrożenia, co ta opisana w sekcji o secrets w zmiennych środowiskowych: wyciekły klucz prywatny to wyciekły sekret, nic mniej.
Jak zdobyć certyfikat produkcyjny z Let’s Encrypt i certbot
Na produkcji potrzebujesz certyfikatu podpisanego przez CA, któremu przeglądarka każdego odwiedzającego już ufa, a Let’s Encrypt to darmowa, zautomatyzowana opcja, z której korzysta większość stron. certbot mówi protokołem ACME do serwerów Let’s Encrypt i z wtyczką do serwera WWW konfiguruje za ciebie Nginx albo Apache:
| |
To dowodzi, że kontrolujesz domenę (serwując token, który umieszcza certbot, albo przez rekord DNS, zależnie od typu wyzwania), pobiera certyfikat i zapisuje dyrektywy ssl_certificate oraz ssl_certificate_key w twojej konfiguracji Nginx.
Certyfikaty Let’s Encrypt trwają 90 dni. Krótkie okno jest celowe: sprawia, że zautomatyzowane odnawianie staje się jedyną sensowną drogą. Certbot instaluje timer systemd, albo cron job na starszych systemach, który uruchamia się dwa razy dziennie i odnawia wszystko, co jest w ciągu 30 dni od wygaśnięcia:
| |
--deploy-hook odpala się tylko przy faktycznym odnowieniu, nie przy każdym uruchomieniu timera, więc Nginx nie jest przeładowywany dwa razy dziennie bez powodu. Przetestuj tę ścieżkę, zanim zaufasz jej bez nadzoru:
| |
Jak działa terminacja TLS na reverse proxy albo CDN
Większość usług nigdy nie terminuje TLS wewnątrz procesu aplikacji. Reverse proxy przed twoją aplikacją to zwykłe miejsce na to: Nginx albo Caddy trzyma certyfikat, deszyfruje przychodzące połączenie HTTPS i przekazuje żądanie do backendu zwykłym HTTP w sieci wewnętrznej. Kod twojej aplikacji nigdy nie widzi certyfikatu.
| |
fullchain.pem to certyfikat liścia z już dołączonym pośrednim, plik z łańcuchem zaufania gotowy do serwowania w takiej postaci, jaka jest. Używaj tego zamiast cert.pem, który jest samym liściem i wraca do awarii brakującego pośredniego opisanej wyżej.
CDN robi to samo o jeden skok dalej. Terminuje TLS w punkcie brzegowym (edge PoP) najbliższym odwiedzającemu, a potem otwiera drugie połączenie TLS z powrotem do twojego origin. Dwa połączenia, dwa handshake’i, a certyfikat, który waliduje przeglądarka odwiedzającego, to certyfikat brzegowy CDN. Twój certyfikat na origin musi zadowolić tylko CDN.
Ten podział decyduje, gdzie debugować. Jeśli curl prosto na origin pokazuje ważny certyfikat, a publiczna domena nie, problem siedzi na poziomie proxy albo CDN, i żadna ilość czytania logów aplikacji tego nie znajdzie.
Co HSTS dodaje ponad TLS
TLS zabezpiecza połączenie, gdy jest już HTTPS. Nie robi nic z pierwszym żądaniem, które nadal może wyjść zwykłym http://, gdy użytkownik wpisze samą domenę albo kliknie stary link, a to właśnie to pierwsze żądanie jest miejscem, gdzie atakujący na trasie może przechwycić i wymusić downgrade. Strict-Transport-Security zamyka tę lukę: po jednej udanej wizycie przez HTTPS przeglądarka przepisuje każde kolejne żądanie do tej domeny na HTTPS, zanim opuści maszynę.
| |
max-age jest w sekundach, a 63072000 to dwa lata, wygodnie powyżej minimum jednego roku wymaganego do zgłoszenia na preload list. includeSubDomains rozszerza politykę na każdą subdomenę. preload oznacza domenę jako kwalifikującą się do listy wbudowanej w Chrome, Firefoksa i Safari, których wpisy dostają przepisanie tylko-na-HTTPS od samego pierwszego żądania, bez potrzeby wcześniejszej wizyty.
Dodawaj preload dopiero wtedy, gdy naprawdę każda subdomena obsługuje HTTPS. Zejście z preload list zajmuje miesiące, zanim rozejdzie się z powrotem przez wydania przeglądarek, a do tego czasu każda subdomena, która nie potrafi HTTPS, jest nieosiągalna.
Kiedy TLS nie wystarcza i co sprawdzić najpierw
TLS weryfikuje serwer wobec klienta. Nie mówi nic o tym, co ten serwer robi później z twoim żądaniem, i nie powstrzyma użytkownika, który padł ofiarą phishingu, przed wpisaniem hasła na przekonującej fałszywej domenie, która ma zupełnie ważny certyfikat. Let’s Encrypt wystawi certyfikat dla examp1e.com równie chętnie jak dla example.com. Kłódka oznacza, że połączenie jest prywatne, nie że druga strona jest uczciwa.
Trzy awarie tłumaczą większość zgłoszeń “strona nie działa”, które okazują się złą konfiguracją TLS: wygasły certyfikat, brakujący pośredni i lista SAN, która nie pokrywa żądanego hostname. Wszystkie trzy widać w powyższym wyniku openssl s_client, zanim jeszcze otworzysz choć jeden log aplikacji.
Jeśli sam terminujesz TLS, wrzuć to polecenie -checkend do cron joba albo alertu monitoringu już dziś. Certyfikat, który wywraca stronę, to zawsze ten, o którym nikt nie pamiętał, że istnieje.