JavaRush /Blog Java /Random-PL /Z HTTP na HTTPS
Viacheslav
Poziom 3

Z HTTP na HTTPS

Opublikowano w grupie Random-PL
Z HTTP na HTTPS – 1
Treść:

Wstęp

We współczesnym świecie nie można żyć bez aplikacji internetowych. Zaczniemy od małego eksperymentu. Pamiętam, że jako dziecko na wszystkich straganach sprzedawano gazetę „Argumenty i Fakty”. Zapamiętałem je, ponieważ według mojego osobistego spostrzeżenia z dzieciństwa te gazety zawsze wyglądały dziwnie. I zdecydowałem, czy powinniśmy wejść na ich stronę internetową:
Z HTTP na HTTPS – 2
Jeżeli przejdziemy do pomocy przeglądarki Google Chrome, przeczytamy, że ta witryna nie korzysta z bezpiecznego połączenia i informacje wymieniane z witryną mogą być dostępne dla osób trzecich. Sprawdźmy inne wiadomości, na przykład wiadomości z Petersburga z Fontanki, mediów elektronicznych:
Z HTTP na HTTPS – 3
Jak widać, według tych danych, witryna Fontanka nie ma problemów z bezpieczeństwem. Okazuje się, że zasoby sieciowe mogą, ale nie muszą, być bezpieczne. Widzimy również, że dostęp do niechronionych zasobów następuje poprzez protokół HTTP. A jeśli zasób jest chroniony, to wymiana danych odbywa się za pomocą protokołu HTTPS, gdzie litera S na końcu oznacza „Bezpieczny”. Protokół HTTPS opisano w specyfikacji rfc2818: „ HTTP Over TLS ”. Spróbujmy stworzyć własną aplikację internetową i przekonajmy się jak to działa. Po drodze zrozumiemy warunki.
Z HTTP na HTTPS – 4

Aplikacja internetowa w Javie

Musimy więc stworzyć bardzo prostą aplikację internetową w Javie. Po pierwsze potrzebujemy samej aplikacji Java. W tym celu użyjemy systemu automatycznego budowania projektu Gradle. Dzięki temu nie będziemy musieli ręcznie tworzyć niezbędnej struktury katalogów + Gradle zajmie się za nas wszystkimi bibliotekami niezbędnymi do realizacji projektu i zadba o to, aby były one dostępne podczas wykonywania kodu. Więcej o Gradle możesz przeczytać w krótkiej recenzji: „ Krótkie wprowadzenie do Gradle ”. Użyjmy wtyczki Gradle Init i uruchommy polecenie:
gradle init --type java-application
Następnie otwórzmy skrypt kompilacji build.gradle, który opisuje z jakich bibliotek składa się nasz projekt, a jakie dostarczy nam Gradle. Dodajmy tam zależność od serwera WWW, na którym będziemy eksperymentować:
dependencies {
    // Web server
    implementation 'io.undertow:undertow-core:2.0.20.Final'
     // Use JUnit test framework
     testImplementation 'junit:junit:4.12'
}
Aby aplikacja internetowa działała, na pewno potrzebujemy serwera WWW, na którym będzie hostowana nasza aplikacja. Istnieje ogromna różnorodność serwerów internetowych, ale główne z nich to: Tomcat, Jetty, Undertow. Tym razem wybierzemy Undertow. Aby zrozumieć, jak możemy współpracować z naszym serwerem internetowym, przejdźmy do oficjalnej strony Undertow i przejdź do sekcji dokumentacji . Ty i ja połączyliśmy zależność od Undertow Core, dlatego interesuje nas rozdział dotyczący właśnie tego Core , czyli rdzenia, będącego podstawą serwera WWW. Najłatwiej jest użyć API Buildera dla Undertow:
public static void main(String[] args) {
	Undertow server = Undertow.builder()
            .addHttpListener(8080, "localhost")
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
    server.start();
}
Jeśli wykonamy kod, możemy przejść do następującego zasobu internetowego:
Z HTTP na HTTPS – 5
To działa po prostu. Dzięki API Undertow Builder dodajemy słuchacza HTTP do localhost i portu 8080. Ten słuchacz odbiera żądania z przeglądarki internetowej i w odpowiedzi zwraca ciąg znaków „Hello World”. Świetna aplikacja internetowa. Ale jak widzimy, używamy protokołu HTTP, tj. Ten rodzaj wymiany danych nie jest bezpieczny. Zastanówmy się, jak przeprowadzana jest wymiana za pomocą protokołu HTTPS.
Z HTTP na HTTPS – 6

Wymagania dla protokołu HTTPS

Aby zrozumieć, jak włączyć HTTPS, wróćmy do specyfikacji HTTPS: „ RFC-2818: HTTP Over TLS ”. Zgodnie ze specyfikacją dane w protokole HTTPS przesyłane są za pośrednictwem protokołów kryptograficznych SSL lub TLS. Koncepcje SSL i TLS często wprowadzają ludzi w błąd. W rzeczywistości protokół SSL ewoluował i zmienił swoje wersje. Później TLS stał się kolejnym krokiem w rozwoju protokołu SSL. Oznacza to, że TLS jest po prostu nową wersją protokołu SSL. Specyfikacja tak mówi: „SSL i jego następca TLS”. Dowiedzieliśmy się więc, że istnieją protokoły kryptograficzne SSL/TLS. SSL to skrót od Secure Sockets Layer i tłumaczy się jako „bezpieczna warstwa gniazd”. Gniazdo przetłumaczone z języka angielskiego to złącze. Uczestnicy transmisji danych w sieci wykorzystują gniazda jako interfejs programistyczny (czyli API) do komunikacji między sobą w sieci. Przeglądarka pełni rolę klienta i korzysta z gniazda klienta, natomiast serwer odbierający żądanie i wysyłający odpowiedź korzysta z gniazda serwera. I to właśnie pomiędzy tymi gniazdami następuje wymiana danych. Dlatego też protokół pierwotnie nosił nazwę SSL. Ale czas mijał, a protokół ewoluował. W pewnym momencie protokół SSL stał się protokołem TLS. TLS jest skrótem od Transport Layer Security. Z kolei protokół TLS bazuje na specyfikacji protokołu SSL w wersji 3.0. Protokół TLS jest tematem odrębnych artykułów i recenzji, dlatego po prostu wskażę materiały, które wydają mi się interesujące: Krótko mówiąc, podstawą HTTPS jest uzgadnianie TLS i sprawdzenie „Tożsamości serwera” (tj. identyfikacji serwera) przy użyciu jego certyfikatu cyfrowego. To jest ważne. Pamiętajmy o tym, bo... Do tego faktu powrócimy później. Dlatego wcześniej użyliśmy HttpListener, aby poinformować serwer, jak ma działać poprzez protokół HTTP. Jeśli w powyższym przykładzie dodaliśmy HttpListener do pracy przez HTTP, to aby pracować przez HTTPS musimy dodać HttpsListener:
Z HTTP na HTTPS – 7
Ale aby go dodać, potrzebujemy SSLContext. Co ciekawe, SSLContext nie jest klasą z Undertow, ale javax.net.ssl.SSLContext. Klasa SSLContext jest częścią tzw. „ Java Secure Socket Extension ” (JSSE) – rozszerzenia Java zapewniającego bezpieczeństwo połączenia internetowego. To rozszerzenie opisano w „ Przewodniku informacyjnym dotyczącym rozszerzenia Java Secure Socket Extension (JSSE) ”. Jak widać ze wstępnej części dokumentacji, JSSE zapewnia framework i implementację protokołów SSL i TLS w języku Java. Jak uzyskać SSLContext? Otwórz plik JavaDoc SSLContext i znajdź metodę getInstance . Jak widać, aby uzyskać SSLContext, musimy podać nazwę „Secure Socket Protocol”. Z opisu parametrów wynika, że ​​nazwy te można znaleźć w „ Dokumentacji nazw algorytmów standardu Java Cryptography Architecture Standard ”. Postępujmy więc zgodnie z instrukcją i przejdźmy do dokumentacji. I widzimy, że możemy wybierać pomiędzy SSL i TLS:
Z HTTP na HTTPS – 8
Teraz rozumiemy, że musimy utworzyć SSLContext w następujący sposób:
public SSLContext getSSLContext() {
	// 1. Получаем контекст, в рамках которого будем работать по TLS протоколу
	SSLContext context = null;
	try {
		context = SSLContext.getInstance("TLS");
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	}
	return context;
}
Po utworzeniu nowego kontekstu pamiętamy, że SSLContext został opisany w „ Przewodniku referencyjnym Java Secure Socket Extension (JSSE) ”. Czytamy i widzimy, że „Nowo utworzony SSLContext należy zainicjować wywołując metodę init”. Oznacza to, że stworzenie kontekstu nie wystarczy. Należy go zainicjować. I to jest logiczne, ponieważ jeśli chodzi o bezpieczeństwo, powiedzieliśmy tylko, że chcemy korzystać z protokołu TLS. Aby zainicjować SSLContext, musimy zapewnić trzy rzeczy: KeyManager, TrustManager, SecureRandom.
Z HTTP na HTTPS – 9

Menedżer kluczy

KeyManager to menedżer kluczy. Jest on odpowiedzialny za to, jakie „dane uwierzytelniające” należy przekazać osobie, która się z nami kontaktuje. Poświadczenie można przetłumaczyć jako tożsamość. Tożsamość jest potrzebna, aby klient miał pewność, że serwer jest tym, za kogo się podaje i można mu zaufać. Co będzie używane jako identyfikacja? Jak pamiętamy, tożsamość serwera weryfikowana jest za pomocą certyfikatu cyfrowego serwera. Proces ten można przedstawić następująco:
Z HTTP na HTTPS – 10
Dodatkowo w „ JSSE Reference Guide: How SSL Works ” podano, że SSL wykorzystuje „kryptografię asymetryczną”, co oznacza, że ​​potrzebujemy pary kluczy: klucza publicznego i klucza prywatnego. Ponieważ mówimy o kryptografii, w grę wchodzi „Java Cryptography Architecture” (JCA). Oracle udostępnia doskonały dokument na temat tej architektury: „ Przewodnik referencyjny architektury Java Cryptography (JCA) ”. Ponadto możesz przeczytać krótki przegląd JCA na JavaRush: „ Architektura kryptografii Java: pierwsza znajomość ”. Tak więc, aby zainicjować KeyManager, potrzebujemy KeyStore, w którym będzie przechowywany certyfikat naszego serwera. Najpopularniejszym sposobem tworzenia magazynu kluczy i certyfikatów jest narzędzie keytool dołączone do pakietu JDK. Przykład można zobaczyć w dokumentacji JSSE: „ Tworzenie magazynu kluczy do użycia z JSSE ”. Musimy więc użyć narzędzia KeyTool, aby utworzyć magazyn kluczy i zapisać tam certyfikat. Co ciekawe, generowanie kluczy było wcześniej określane za pomocą -genkey, ale teraz zaleca się użycie -genkeypair. Będziemy musieli zdefiniować następujące rzeczy:
  • alias : Alias ​​lub po prostu nazwa, pod którą wpis zostanie zapisany w Keystore
  • keyalg : Algorytm szyfrowania klucza. Wybierzmy algorytm RSA, który jest w zasadzie standardowym rozwiązaniem dla naszych celów.
  • keysize : Rozmiar klucza (w bitach). Minimalny zalecany rozmiar to 2048, ponieważ... mniejszy rozmiar został już pęknięty. Więcej możesz przeczytać tutaj: „ certyfikat ssl w 2048 bitach ”.
  • dname : Nazwa wyróżniająca, nazwa wyróżniająca.
Ważne jest, aby zrozumieć, że żądany zasób (na przykład https://localhost) zostanie z nim porównany. Nazywa się to „dopasowywaniem podmiotu”.
  • Ważność : Czas w dniach ważności wygenerowanego certyfikatu, tj. ważny.
  • ext : Rozszerzenie certyfikatu określone w „ Nazwanych rozszerzeniach ”.
W przypadku certyfikatów z podpisem własnym (tj. certyfikatów tworzonych niezależnie) należy określić następujące rozszerzenia:
  • -ext san:critical=dns:localhost,ip:127.0.0.1 >, aby wykonać dopasowanie tematu według alternatywnej nazwy podmiotu
  • -ext bc=ca:false >, aby wskazać, że ten certyfikat nie jest używany do podpisywania innych certyfikatów
Uruchommy polecenie (przykład dla systemu operacyjnego Windows):
keytool -genkeypair -alias ssl -keyalg RSA -keysize 2048 -dname "CN=localhost,OU=IT,O=Javarush,L=SaintPetersburg,C=RU,email=contact@email.com" -validity 90 -keystore C:/keystore.jks -storepass passw0rd -keypass passw0rd -ext san:critical=dns:localhost,ip:127.0.0.1 -ext bc=ca:false
Ponieważ plik zostanie utworzony, upewnij się, że masz wszystkie uprawnienia do utworzenia pliku. Prawdopodobnie zobaczysz także takie porady:
Z HTTP na HTTPS – 11
Tutaj powiedziano nam, że JKS jest formatem zastrzeżonym. Zastrzeżony oznacza, że ​​jest prywatną własnością autorów i jest przeznaczony do użytku wyłącznie w Javie. Podczas pracy z narzędziami stron trzecich może pojawić się konflikt, dlatego jesteśmy ostrzegani. Dodatkowo może pojawić się błąd: The destination pkcs12 keystore has different storepass and keypass. Ten błąd występuje, ponieważ hasła do wpisu w magazynie kluczy i do samego magazynu kluczy są różne. Jak mówi dokumentacja narzędzia keytool : „Na przykład większość narzędzi innych firm wymaga, aby hasło magazynu i hasło w magazynie kluczy PKCS #12 były takie same”. Klucz możemy określić sami (na przykład -destkeypass EntryPasw). Ale lepiej nie naruszać wymagań i ustawiać to samo hasło. Zatem import może wyglądać następująco:
keytool -importkeystore -srckeystore C:/keystore.jks -destkeystore C:/keystore.jks -deststoretype pkcs12
Przykład sukcesu:
Z HTTP na HTTPS – 12
Aby wyeksportować certyfikat do pliku, możesz uruchomić:
keytool -export -alias ssl -storepass passw0rd -file C:/server.cer -keystore C:/keystore.jks
Dodatkowo możemy uzyskać zawartość magazynu kluczy w następujący sposób:
keytool -list -v -keystore C:/keystore.jks -storepass passw0rd
Świetnie, teraz mamy magazyn kluczy zawierający certyfikat. Teraz możesz pobrać go z kodu:
public KeyStore getKeyStore() {
	// Согласно https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore
	try(FileInputStream fis = new FileInputStream("C:/keystore.jks")){
		KeyStore keyStore = KeyStore.getInstance("pkcs12");
		keyStore.load(fis, "passw0rd".toCharArray());
		return keyStore;
	} catch (IOException ioe) {
		throw new IllegalStateException(ioe);
	} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
		throw new IllegalStateException(e);
	}
}
Jeśli istnieje KeyStore, możemy zainicjować KeyManager:
public KeyManager[] getKeyManagers(KeyStore keyStore) {
	String keyManagerAlgo = KeyManagerFactory.getDefaultAlgorithm();
	KeyManagerFactory keyManagerFactory = null;
	try {
		keyManagerFactory = KeyManagerFactory.getInstance(keyManagerAlgo);
		keyManagerFactory.init(keyStore, "passw0rd".toCharArray());
		return keyManagerFactory.getKeyManagers();
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	} catch (UnrecoverableKeyException | KeyStoreException e) {
		throw new IllegalStateException(e);
	}
}
Nasz pierwszy cel został osiągnięty. Pozostaje dowiedzieć się, czym jest TrustManager. TrustManager jest opisany w dokumentacji JSSE w sekcji „ Interfejs TrustManager ”. Jest bardzo podobny do KeyManagera, ale jego celem jest sprawdzenie, czy osobie żądającej połączenia można zaufać. Mówiąc wprost, jest to KeyManager w odwrotnej kolejności =) Nie potrzebujemy TrustManagera, więc przekażemy wartość null. Zostanie wówczas utworzony domyślny TrustManager, który nie weryfikuje użytkownika końcowego wysyłającego żądania do naszego serwera. Dokumentacja tak mówi: „zostanie zastosowana domyślna implementacja”. To samo z SecureRandom. Jeśli określimy null, zostanie użyta domyślna implementacja. Pamiętajmy tylko, że SecureRandom jest klasą JCA i jest opisana w dokumentacji JCA w rozdziale „ Klasa SecureRandom ”. W sumie przygotowanie uwzględniające wszystkie opisane powyżej metody może wyglądać następująco:
public static void main(String[] args) {
	// 1. Подготавливаем приложение к работе по HTTPS
	App app = new App();
	SSLContext sslContext = app.getSSLContext();
	KeyStore keyStore = app.getKeyStore();
	KeyManager[] keyManagers = app.getKeyManagers(keyStore);
	try {
		sslContext.init(keyManagers, null, null);
	} catch (KeyManagementException e) {
		throw new IllegalStateException(e);
	}
Pozostaje tylko uruchomić serwer:
// 2. Поднимаем serwer
 	int httpsPort = 443;
	Undertow server = Undertow.builder()
            .addHttpsListener(httpsPort, "localhost", sslContext)
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
	server.start();
}
Tym razem nasz serwer będzie dostępny pod adresem https://localhost:443 Jednak nadal otrzymamy błąd, że temu zasobowi nie można ufać:
Z HTTP na HTTPS – 13
Zastanówmy się, co jest nie tak z certyfikatem i co z tym zrobić.
Z HTTP na HTTPS – 14

Zarządzanie certyfikatami

Zatem nasz serwer jest już gotowy do pracy poprzez HTTPS, ale klient mu nie ufa. Dlaczego? Przyjrzyjmy się:
Z HTTP na HTTPS – 15
Powodem jest to, że ten certyfikat jest certyfikatem z podpisem własnym. Certyfikat SSL z podpisem własnym oznacza certyfikat klucza publicznego wydany i podpisany przez tę samą osobę, którą identyfikuje. Oznacza to, że nie został wydany przez żaden szanowany urząd certyfikacji (CA, znany również jako urząd certyfikacji). Urząd Certyfikacji pełni funkcję powiernika i na co dzień przypomina notariusza. Zapewnia, że ​​wystawiane przez niego certyfikaty są rzetelne. Usługa wydawania certyfikatów przez takie urzędy certyfikacji jest płatna, więc nikt nie potrzebuje utraty zaufania i ryzyka reputacji. Domyślnie istnieje kilka zaufanych urzędów certyfikacji. Ta lista jest edytowalna. Każdy system operacyjny ma własne zarządzanie listą urzędów certyfikacji. Na przykład o zarządzaniu tą listą w systemie Windows można przeczytać tutaj: „ Zarządzaj zaufanymi certyfikatami głównymi w systemie Windows ”. Dodajmy certyfikat do zaufanych, jak wskazano w komunikacie o błędzie. Aby to zrobić, najpierw pobierz certyfikat:
Z HTTP na HTTPS – 16
W systemie operacyjnym Windows naciśnij Win + R i wykonaj, mmcaby wywołać konsolę sterowania. Następnie naciśnij Ctrl+M, aby dodać sekcję „Certyfikaty” do bieżącej konsoli. Następnie w podsekcji „Zaufane główne urzędy certyfikacji” wykonamy Действия / Все задачи / Импорт. Zaimportujmy wcześniej pobrany plik do pliku. Przeglądarka mogła zapamiętać poprzedni stan zaufania certyfikatu. Dlatego przed otwarciem strony należy ponownie uruchomić przeglądarkę. Na przykład w przeglądarce Google Chrome na pasku adresu musisz uruchomić chrome://restart. W systemie operacyjnym Windows można także użyć narzędzia do przeglądania certyfikatów certmgr.msc:
Z HTTP na HTTPS – 17
Jeśli wszystko zrobiliśmy poprawnie, zobaczymy pomyślne wywołanie naszego serwera poprzez HTTPS:
Z HTTP na HTTPS – 18
Jak widać, certyfikat jest teraz uważany za ważny, zasób jest dostępny i nie ma żadnych błędów.
Z HTTP na HTTPS – 19

Konkluzja

Ustaliliśmy więc, jak wygląda schemat włączania protokołu HTTPS na serwerze internetowym i co jest do tego potrzebne. Mam nadzieję, że w tym miejscu jest jasne, że wsparcie zapewnia interakcja Java Cryptography Architecture (JCA), która jest odpowiedzialna za kryptografię, i Java Secure Socket Extension (JSSE), które zapewnia implementację TLS po stronie Java. Widzieliśmy, jak narzędzie keytool zawarte w pakiecie JDK jest używane do pracy z magazynem kluczy i certyfikatów KeyStore. Dodatkowo zdaliśmy sobie sprawę, że HTTPS wykorzystuje protokoły SSL/TLS ze względów bezpieczeństwa. Aby to wzmocnić, radzę przeczytać doskonałe artykuły na ten temat: Mamy nadzieję, że po tej małej recenzji protokół HTTPS stanie się nieco bardziej przejrzysty. A jeśli chcesz włączyć protokół HTTPS, możesz łatwo zrozumieć warunki z dokumentacji serwerów aplikacji i struktur. #Wiaczesław
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION