Bezpieczeństwo dzięki protokołom sieciowym

Interakcje z szyfrowaniem klient-serwer używają protokołu Transport Layer Security (TLS) do ochrony danych aplikacji.

W tym artykule omawiamy sprawdzone metody dotyczące bezpiecznych protokołów sieciowych i koncepcji infrastruktury klucza publicznego (PKI). Więcej informacji znajdziesz w artykule Omówienie zabezpieczeń Androida oraz Omówienie uprawnień.

Pojęcia

Serwer z certyfikatem TLS ma klucz publiczny i odpowiadający mu klucz prywatny. Serwer używa kryptografii klucza publicznego do podpisywania certyfikatu podczas uzgadniania połączenia TLS.

Proste uzgadnianie połączenia potwierdza tylko, że serwer zna klucz prywatny certyfikatu. Aby rozwiązać ten problem, zezwól klientowi na zaufanie wielu certyfikatom. Serwer nie jest godny zaufania, jeśli jego certyfikat nie znajduje się w zestawie zaufanych certyfikatów po stronie klienta.

Serwery mogą jednak używać rotacji kluczy, aby zastąpić klucz publiczny certyfikatu nowym. Zmiana konfiguracji serwera wymaga zaktualizowania aplikacji klienta. Jeśli serwer to zewnętrzna usługa internetowa, np. przeglądarka internetowa lub aplikacja pocztowa, trudno określić, kiedy należy zaktualizować aplikację klienta.

Serwery zwykle korzystają z certyfikatów urzędów certyfikacji (CA), co zapewnia stabilność konfiguracji po stronie klienta. Urząd certyfikacji podpisuje certyfikat serwera za pomocą klucza prywatnego. Klient może wtedy sprawdzić, czy serwer ma certyfikat CA znany na platformie.

Zaufane urzędy certyfikacji są zwykle wymienione na platformie hosta. Android 8.0 (poziom interfejsu API 26) zawiera ponad 100 certyfikatów CA, które są aktualizowane w każdej wersji i nie zmieniają się na różnych urządzeniach.

Aplikacje klienckie potrzebują mechanizmu weryfikacji serwera, ponieważ urząd certyfikacji oferuje certyfikaty dla wielu serwerów. Certyfikat urzędu certyfikacji identyfikuje serwer za pomocą nazwy domeny, np. gmail.com, lub za pomocą znaku zastępczego, np. *.google.com.

Aby wyświetlić informacje o certyfikacie serwera witryny, użyj polecenia s_client narzędzia openssl, podając numer portu. Domyślnie HTTPS używa portu 443.

Polecenie przesyła dane wyjściowe openssl s_client do openssl x509, które formatuje informacje o certyfikacie zgodnie ze standardem X.509. Polecenie wysyła żądanie dotyczące tematu (nazwa serwera) i wystawcy (CA).

openssl s_client -connect WEBSITE-URL:443 | \
  openssl x509 -noout -subject -issuer

Przykład HTTPS

Zakładając, że masz serwer WWW z certyfikatem wydanym przez znany urząd certyfikacji, możesz wysłać bezpieczne żądanie, jak pokazano w tym kodzie:

Kotlin

val url = URL("https://wikipedia.org")
val urlConnection: URLConnection = url.openConnection()
val inputStream: InputStream = urlConnection.getInputStream()
copyInputStreamToOutputStream(inputStream, System.out)

Java

URL url = new URL("https://wikipedia.org");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);

Aby dostosować żądania HTTP, prześlij je do HttpURLConnection. Dokumentacja Androida HttpURLConnection zawiera przykłady obsługi nagłówków żądań i odpowiedzi, publikowania treści, zarządzania plikami cookie, używania serwerów proxy, przechowywania w pamięci podręcznej odpowiedzi itp. Platforma Androida weryfikuje certyfikaty i nazwy hostów za pomocą tych interfejsów API.

W miarę możliwości używaj tych interfejsów API. W tej sekcji omawiamy typowe problemy, które wymagają różnych rozwiązań.

Typowe problemy z weryfikacją certyfikatów serwera

Załóżmy, że zamiast zwracania treści funkcja getInputStream() wyrzuca wyjątek:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
        at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
        at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
        at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
        at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
        at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
        at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
        at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)

Może się tak zdarzyć z kilku powodów, m.in.:

  1. Nieznany urząd certyfikacji, który wydał certyfikat serwera.
  2. Certyfikat serwera nie został podpisany przez urząd certyfikacji, ale został podpisany samodzielnie.
  3. W konfiguracji serwera brakuje pośredniego urzędu certyfikacji.

W następnych sekcjach znajdziesz informacje o tym, jak rozwiązać te problemy, zachowując przy tym bezpieczeństwo połączenia z serwerem.

Nieznany urząd certyfikacji

Błąd SSLHandshakeExceptionwynika z tego, że system nie ufa urzędowi certyfikacji. Może to być spowodowane tym, że masz certyfikat nowej jednostki certyfikacji, której Android nie ufa, lub aplikacja działa w wersji wcześniejszej bez jednostki certyfikacji. Ponieważ jest on prywatny, CA jest rzadko znany. Większość nieznanych urzędów certyfikacji to nie urzędy certyfikacji publiczne, ale urzędy certyfikacji prywatne wydane przez organizację, taką jak rząd, korporacja czy instytucja edukacyjna na własny użytek.

Aby zaufać niestandardowym urzędom certyfikacji bez konieczności zmiany kodu aplikacji, zmień konfigurację zabezpieczeń sieci.

Uwaga: wiele witryn internetowych opisuje nieskuteczne rozwiązanie alternatywne, które polega na zainstalowaniu TrustManager, który nic nie robi. W przypadku korzystania z publicznego hotspota Wi-Fi użytkownicy są wtedy narażeni na ataki, ponieważ atakujący może wykorzystać sztuczki DNS, aby kierować ruch użytkowników przez serwer proxy, który udaje ich serwer. Atakujący może wtedy rejestrować hasła i inne dane osobowe. To działa, ponieważ atakujący może wygenerować certyfikat, a bez TrustManager weryfikującego, że certyfikat pochodzi z zaufanego źródła, nie można zablokować tego rodzaju ataku. Nie rób tego nawet tymczasowo. Zamiast tego sprawdź, czy aplikacja ufa wystawcy certyfikatu serwera.

Podpisany samodzielnie certyfikat serwera

Po drugie, SSLHandshakeExceptionmoże wystąpić z powodu certyfikatu podpisanego samodzielnie, który czyni serwer jego własnym urzędem certyfikacji. Jest to podobne do nieznanego urzędu certyfikacji, dlatego zmodyfikuj ustawienia zabezpieczeń sieci aplikacji, aby ufać swoim certyfikatom samopodpisanym.

Brak urzędu certyfikacji pośredniego

Po trzecie, SSLHandshakeExceptionwystępuje z powodu braku pośredniego urzędu certyfikacji. Publiczne urzędy certyfikacji rzadko podpisują certyfikaty serwera. Zamiast tego główny urząd certyfikacji podpisuje pośrednie urzędy certyfikacji.

Aby zmniejszyć ryzyko związane z ujawnieniem, urzędy certyfikacji przechowują główny urząd certyfikacji w trybie offline. Jednak systemy operacyjne, takie jak Android, zazwyczaj ufają tylko głównym urzędom certyfikacji, pozostawiając krótką lukę w zaufaniu między certyfikatem serwera podpisanym przez pośredni urząd certyfikacji a weryfikatorem certyfikatu, który rozpoznaje główny urząd certyfikacji.

Aby zlikwidować tę lukę w zaufaniu, serwer wysyła łańcuch certyfikatów z urzędu certyfikacji serwera przez wszystkie pośrednie urzędy do zaufanego głównego urzędu certyfikacji podczas uzgadniania połączenia TLS.

Oto np. łańcuch certyfikatów mail.google.com wyświetlany przez polecenie openssl s_client:

$ openssl s_client -connect mail.google.com:443
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=mail.google.com
   i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
   i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
---

Pokazuje to, że serwer wysyła certyfikat dla adresu mail.google.com wydany przez pośredni urząd certyfikacji Thawte SGC oraz drugi certyfikat dla urzędu certyfikacji Thawte SGC wydanego przez urząd certyfikacji Verisign, który jest głównym urzędem certyfikacji zaufanym przez Androida.

Serwer może jednak nie być skonfigurowany tak, aby zawierać wymagany pośredni urząd certyfikacji. Oto przykład serwera, który może powodować błąd w przeglądarkach na Androida i wyjątki w aplikacjach na Androida:

$ openssl s_client -connect egov.uscis.gov:443
---
Certificate chain
 0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov
   i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3
---

W przeciwieństwie do nieznanego urzędu certyfikacji lub samodzielnie podpisanego certyfikatu serwera większość przeglądarek komputerowych nie generuje błędów podczas komunikacji z tym serwerem. Przeglądarki na komputerach przechowują w pamięci podręcznej zaufane pośrednie urzędy certyfikacji. Gdy przeglądarka uzyska informacje o pośrednim urzędzie certyfikacji z jednej witryny, nie będzie już musiała ponownie szukać go w łańcuchu certyfikatów.

Niektóre witryny celowo używają dodatkowych serwerów internetowych do obsługi zasobów. Aby oszczędzać przepustowość, mogą wyświetlać główną stronę HTML z serwera z pełnym łańcuchem certyfikatów, ale obrazy, pliki CSS i skrypty JavaScript bez urzędu certyfikacji. Czasami te serwery mogą udostępniać usługę internetową, do której próbujesz uzyskać dostęp z aplikacji na Androida, która nie jest tak tolerancyjna.

Aby rozwiązać ten problem, skonfiguruj serwer tak, aby zawierał pośredni urząd certyfikacji w łańcuchu serwerów. Większość urzędów certyfikacji udostępnia instrukcje dotyczące typowych serwerów internetowych.

Ostrzeżenia dotyczące bezpośredniego używania SSLSocket

Do tej pory przykłady koncentrowały się na HTTPS z użyciem HttpsURLConnection. Czasami aplikacje muszą używać TLS oddzielnie od HTTPS. Na przykład aplikacja poczty e-mail może używać wariantów TLS SMTP, POP3 lub IMAP. W takich przypadkach aplikacja może bezpośrednio korzystać z SSLSocket, podobnie jak HttpsURLConnection wewnątrz.

Techniki opisane do tej pory, które pomagają rozwiązać problemy z weryfikacją certyfikatów, są również przydatne w przypadku SSLSocket. Gdy używasz niestandardowej wartości TrustManager, do parametru HttpsURLConnection przekazywana jest wartość SSLSocketFactory. Jeśli chcesz użyć niestandardowego TrustManager w przypadku SSLSocket, wykonaj te same czynności i użyj tego SSLSocketFactory do utworzenia SSLSocket.

Uwaga: SSLSocket nie przeprowadza weryfikacji nazwy hosta. Aplikacja musi samodzielnie zweryfikować nazwę hosta, najlepiej przez wywołanie funkcji getDefaultHostnameVerifier() z oczekiwaną nazwą hosta. Pamiętaj też, że funkcja HostnameVerifier.verify() nie wyrzuca wyjątku w przypadku błędu. Zamiast tego zwraca wartość logiczną, którą musisz sprawdzić.

Weryfikacja certyfikatu

TLS polega na urzędach certyfikacji, które wystawiają certyfikaty tylko zweryfikowanym właścicielom serwerów i domen. W rzadkich przypadkach urzędy certyfikacji są oszukiwane lub w przypadku Comodo lub DigiNotar są łamane, co powoduje, że certyfikaty nazwy hosta są wystawiane komuś innemu niż właściciel serwera lub domeny.

Aby zmniejszyć to ryzyko, Android obsługuje odwołania certyfikatów w całym systemie, korzystając z kombinacji listy blokowania i przezroczystości certyfikatów bez polegania na weryfikacji certyfikatów online. Dodatkowo Android będzie weryfikować odpowiedzi OCSP dołączone do protokołu TLS.

Aby włączyć przejrzystość certyfikatów w aplikacji, zapoznaj się z sekcją Włączanie przejrzystości certyfikatów w dokumentacji dotyczącej konfiguracji zabezpieczeń sieci.

Ograniczenie dostępu aplikacji do określonych certyfikatów

Uwaga: przypinanie certyfikatów, czyli ograniczanie liczby certyfikatów uznawanych za ważne w aplikacji do tych, które zostały wcześniej autoryzowane, nie jest zalecane w przypadku aplikacji na Androida. Przyszłe zmiany konfiguracji serwera, takie jak zmiana na inny CA, spowodują, że aplikacje z przypiętymi certyfikatami nie będą mogły połączyć się z serwerem bez otrzymania aktualizacji oprogramowania klienta.

Jeśli chcesz ograniczyć aplikację do akceptowania tylko określonych certyfikatów, musisz uwzględnić kilka zapasowych kodów PIN, w tym co najmniej 1 klucz, nad którym masz pełną kontrolę, oraz odpowiednio krótki okres ważności, aby uniknąć problemów ze zgodnością. Konfiguracja zabezpieczeń sieci zapewnia przypinanie z tymi funkcjami.

Certyfikaty klienta

W tym artykule skupiamy się na używaniu protokołu TLS do zabezpieczania komunikacji z serwerami. Protokół TLS obsługuje też pojęcie certyfikatów klienta, które umożliwiają serwerowi weryfikację tożsamości klienta. Chociaż wykrywanie niestandardowych TrustManager wykracza poza zakres tego artykułu, techniki stosowane w tym celu są podobne do tych używanych w przypadku niestandardowych TrustManager.

Nogotofail: narzędzie do testowania bezpieczeństwa ruchu sieciowego

Nogotofail to narzędzie, które ułatwia sprawdzanie, czy aplikacje są bezpieczne przed znanymi lukami w zabezpieczeniach protokołu TLS/SSL i nieprawidłowej konfiguracji. Jest to automatyczne, wydajne i skalowalne narzędzie do testowania problemów z bezpieczeństwem sieci na dowolnym urządzeniu, przez które może przechodzić ruch sieciowy.

Funkcja Nogotofail jest przydatna w 3 głównych przypadkach użycia:

  • znajdowanie błędów i luk w zabezpieczeniach;
  • weryfikowanie poprawek i sprawdzanie, czy nie występują regresje;
  • dowiedzieć się, które aplikacje i urządzenia generują jaki ruch;

Nogotofail działa na urządzeniach z systemem Android, iOS, Linux, Windows, ChromeOS, macOS oraz na każdym urządzeniu, którego używasz do łączenia się z internetem. Do konfigurowania ustawień i otrzymywania powiadomień na Androidzie i Linuxie dostępny jest klient. Sam mechanizm ataku może być wdrażany jako router, serwer VPN lub serwer proxy.

Narzędzie jest dostępne w projekcie open source Nogotofail.

Aktualizacje SSL i TLS

Android 10

Niektóre przeglądarki, takie jak Google Chrome, umożliwiają użytkownikom wybranie certyfikatu, gdy serwer TLS wysyła wiadomość z prośbą o certyfikat w ramach uzgadniania połączenia TLS. Od Androida 10 obiekty KeyChain uwzględniają parametry wydawców i specyfikacji kluczy podczas wywoływania metody KeyChain.choosePrivateKeyAlias(), aby wyświetlić użytkownikom prośbę o wybór certyfikatu. W szczególności nie zawiera on opcji, które nie są zgodne ze specyfikacją serwera.

Jeśli nie ma certyfikatów dostępnych do wyboru przez użytkownika, np. gdy żaden z nich nie pasuje do specyfikacji serwera lub na urządzeniu nie ma zainstalowanych żadnych certyfikatów, prośba o wybór certyfikatu w ogóle się nie wyświetli.

Ponadto w Androidzie 10 lub nowszym nie trzeba blokować ekranu, aby zaimportować klucze lub certyfikaty CA do obiektu KeyChain.

Protokół TLS 1.3 jest domyślnie włączony

W Androidzie 10 i nowszych protokół TLS 1.3 jest domyślnie włączony we wszystkich połączeniach TLS. Oto kilka ważnych informacji o implementacji TLS 1.3:

  • Zestawy szyfrów TLS 1.3 nie mogą być dostosowywane. Obsługiwane zestawy szyfrów TLS 1.3 są zawsze włączone, gdy włączony jest protokół TLS 1.3. Każda próba ich wyłączenia przez wywołanie funkcji setEnabledCipherSuites() zostanie zignorowana.
  • Podczas negocjowania TLS 1.3 obiekty HandshakeCompletedListener są wywoływane przed dodaniem sesji do pamięci podręcznej sesji. (w TLS 1.2 i w poprzednich wersjach te obiekty są wywoływane po dodaniu sesji do pamięci podręcznej sesji).
  • W niektórych sytuacjach, gdy instancje SSLEngine wywołują błąd SSLHandshakeException w poprzednich wersjach Androida, w Androidzie 10 i nowszych wywołują one zamiast tego błąd SSLProtocolException.
  • Tryb 0-RTT nie jest obsługiwany.

W razie potrzeby możesz uzyskać obiekt SSLContext z wyłączonym protokołem TLS 1.3, wywołując funkcję SSLContext.getInstance("TLSv1.2"). Wersje protokołów możesz też włączać i wyłączać w przypadku poszczególnych połączeń, wywołując setEnabledProtocols() na odpowiednim obiekcie.

Certyfikaty podpisane za pomocą algorytmu SHA-1 nie są zaufane w TLS

W Androidzie 10 certyfikaty, które korzystają z algorytmu szyfrowania SHA-1, nie są zaufane w przypadku połączeń TLS. Od 2016 r. główne urzędy certyfikacji nie wydają takich certyfikatów, a w Chrome i innych głównych przeglądarkach nie są one już uznawane za zaufane.

Każda próba połączenia kończy się niepowodzeniem, jeśli połączenie jest z witryną, która przedstawia certyfikat za pomocą SHA-1.

Zmiany i ulepszenia dotyczące KeyChain

Niektóre przeglądarki, np. Google Chrome, umożliwiają użytkownikom wybranie certyfikatu, gdy serwer TLS wysyła wiadomość z prośbą o certyfikat w ramach uzgadniania połączenia TLS. Od Androida 10 obiekty KeyChain uwzględniają parametry specyfikacji wydawców i kluczy podczas wywoływania funkcji KeyChain.choosePrivateKeyAlias(), aby wyświetlić użytkownikom prośbę o wybranie certyfikatu. W tym promptzie nie ma opcji, które nie są zgodne ze specyfikacją serwera.

Jeśli nie ma certyfikatów dostępnych do wybrania przez użytkownika, np. gdy żaden z nich nie pasuje do specyfikacji serwera lub na urządzeniu nie ma zainstalowanych żadnych certyfikatów, prośba o wybór certyfikatu w ogóle się nie wyświetli.

Ponadto w Androidzie 10 lub nowszym nie trzeba blokować ekranu urządzenia, aby zaimportować klucze lub certyfikaty CA do obiektu KeyChain.

Inne zmiany dotyczące TLS i kryptografii

W bibliotekach TLS i kryptografii wprowadzono kilka drobnych zmian, które mają zastosowanie w Androidzie 10:

  • Szyfry AES/GCM/NoPadding i ChaCha20/Poly1305/NoPadding zwracają bardziej dokładne rozmiary buforów z getOutputSize().
  • Mechanizm szyfrowania TLS_FALLBACK_SCSV jest pomijany w przypadku prób połączenia z maks. protokołem TLS 1.2 lub nowszym. Ze względu na ulepszenia w implementacjach serwerów TLS nie zalecamy korzystania z zewnętrznego mechanizmu zastępczego TLS. Zamiast tego zalecamy korzystanie z negocjacji wersji TLS.
  • ChaCha20-Poly1305 to alias ChaCha20/Poly1305/NoPadding.
  • Nazwy hostów z kropkami na końcu nie są uznawane za prawidłowe nazwy hostów SNI.
  • Rozszerzenie supported_signature_algorithms w CertificateRequest jest brane pod uwagę przy wyborze klucza podpisywania w odpowiedziach certyfikatu.
  • Nieprzezroczyste klucze podpisywania, takie jak te z Android Keystore, można używać z podpisami RSA-PSS w TLS.

Zmiany w połączeniu HTTPS

Jeśli aplikacja na Androidzie 10 przekaże wartość null do setSSLSocketFactory(), wystąpi błąd IllegalArgumentException. W poprzednich wersjach przekazanie wartości null do funkcji setSSLSocketFactory() miało taki sam efekt jak przekazanie bieżącej domyślnej fabryki.

Android 11

Domyślnie gniazda SSL używają mechanizmu SSL Conscrypt

Domyślna implementacja SSLSocket w Androidzie opiera się na Conscrypt. Od Androida 11 ta implementacja jest wewnętrznie zbudowana na podstawie modułu SSLEngine firmy Conscrypt.