Neuspjesi SSL rukovanja

Java Top

Upravo sam najavio novo Uči proljeće tečaj, usredotočen na osnove Spring 5 i Spring Boot 2:

>> PROVJERITE TEČAJ

1. Pregled

Secure Socket Layer (SSL) je kriptografski protokol koji pruža sigurnost u komunikaciji putem mreže. U ovom uputstvu razgovarat ćemo o različitim scenarijima koji mogu dovesti do neuspjeha SSL rukovanja i kako to učiniti.

Imajte na umu da naš Uvod u SSL pomoću JSSE detaljnije pokriva osnove SSL-a.

2. Terminologija

Važno je napomenuti da zbog sigurnosnih ranjivosti SSL kao standard zamjenjuje Transport Layer Security (TLS). Većina programskih jezika, uključujući Java, imaju knjižnice koje podržavaju i SSL i TLS.

Od nastanka SSL-a, mnogi proizvodi i jezici poput OpenSSL-a i Jave imali su reference na SSL koje su zadržali čak i nakon preuzimanja TLS-a. Iz tog razloga, u ostatku ovog vodiča, koristit ćemo izraz SSL da bismo se općenito odnosili na kriptografske protokole.

3. Postavljanje

U svrhu ovog vodiča stvorit ćemo jednostavne poslužiteljske i klijentske aplikacije koristeći Java Socket API za simulaciju mrežne veze.

3.1. Stvaranje klijenta i poslužitelja

U Javi možemo koristiti sockets za uspostavljanje komunikacijskog kanala između poslužitelja i klijenta preko mreže. Utičnice su dio Java Secure Socket Extension (JSSE) u Javi.

Počnimo s definiranjem jednostavnog poslužitelja:

int port = 8443; ServerSocketFactory factory = SSLServerSocketFactory.getDefault (); probajte (ServerSocket listener = factory.createServerSocket (port)) {SSLServerSocket sslListener = (SSLServerSocket) listener; sslListener.setNeedClientAuth (istina); sslListener.setEnabledCipherSuites (novi niz [] {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"}); sslListener.setEnabledProtocols (novi String [] {"TLSv1.2"}); while (true) {try (Socket socket = sslListener.accept ()) {PrintWriter out = new PrintWriter (socket.getOutputStream (), true); out.println ("Pozdrav svijetu!"); }}}

Gore definirani poslužitelj vraća poruku "Hello World!" povezanom klijentu.

Dalje, definirajmo osnovnog klijenta kojeg ćemo povezati s našim SimpleServer:

Niz hosta = "localhost"; int port = 8443; SocketFactory tvornica = SSLSocketFactory.getDefault (); probajte (Socket connection = factory.createSocket (host, port)) {((SSLSocket) connection) .setEnabledCipherSuites (new String [] {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"}); ((SSLSocket) veza) .setEnabledProtocols (novi String [] {"TLSv1.2"}); SSLParameters sslParams = novi SSLParameters (); sslParams.setEndpointIdentificationAlgorithm ("HTTPS"); (((SSLSocket) veza) .setSSLParameters (sslParams); BufferedReader input = novi BufferedReader (novi InputStreamReader (connection.getInputStream ())); vrati input.readLine (); }

Naš klijent ispisuje poruku koju je vratio poslužitelj.

3.2. Stvaranje certifikata u Javi

SSL pruža tajnost, integritet i autentičnost u mrežnim komunikacijama. Potvrde igraju važnu ulogu u utvrđivanju autentičnosti.

Ti certifikati obično kupuju i potpisuju tijela za izdavanje certifikata, ali za ovaj ćemo vodič koristiti samopotpisane certifikate.

Da bismo to postigli, možemo koristiti alat za ključeve, koji se isporučuje s JDK:

$ keytool -genkey -keypass lozinka \ -storepass lozinka \ -keystore serverkeystore.jks

Gornja naredba pokreće interaktivnu ljusku za prikupljanje podataka za certifikat kao što su Common Name (CN) i Distinguished Name (DN). Kad pružimo sve relevantne detalje, ona generira datoteku serverkeystore.jks, koji sadrži privatni ključ poslužitelja i njegovu javnu potvrdu.

Imajte na umu da serverkeystore.jks pohranjen je u formatu Java Key Store (JKS), koji je zaštićen od Java. Ovih dana, alat za ključeve podsjetit će nas da bismo trebali razmisliti o korištenju PKCS # 12, koji također podržava.

Možemo dalje koristiti alat za ključeve za izdvajanje javnog certifikata iz generirane datoteke pohrane ključeva:

$ keytool -export -storepass password \ -file server.cer \ -keystore serverkeystore.jks

Gornja naredba izvozi javni certifikat iz pohrane ključeva kao datoteku poslužitelj.cer. Upotrijebimo izvezeni certifikat za klijenta dodavanjem u njegovu trgovinu povjerenja:

$ keytool -import -v -trustcacerts \ -file server.cer \ -keypass lozinka \ -storepass lozinka \ -keystore clienttruststore.jks

Sada smo generirali trgovinu ključeva za poslužitelj i odgovarajuću trgovinu povjerenja za klijenta. Razmotrit ćemo upotrebu ovih generiranih datoteka kada razgovaramo o mogućim neuspjesima rukovanja.

A više detalja o korištenju Java-ove pohrane ključeva možete pronaći u našem prethodnom vodiču.

4. SSL rukovanje

SSL rukovanje je mehanizam kojim klijent i poslužitelj uspostavljaju povjerenje i logistiku potrebnu za osiguravanje svoje veze putem mreže.

Ovo je vrlo orkestriran postupak i razumijevanje njegovih detalja može vam pomoći razumjeti zašto često ne uspije, a što namjeravamo pokriti u sljedećem odjeljku.

Tipični koraci u SSL rukovanju su:

  1. Klijent pruža popis mogućih SSL verzija i paketa šifri koje treba koristiti
  2. Poslužitelj se slaže oko određene SSL verzije i paketa šifri, uzvraćajući svojim certifikatom
  3. Klijent izvlači javni ključ iz certifikata uzvraća šifriranim "predmaster ključem"
  4. Poslužitelj dešifrira "pred-glavni ključ" koristeći svoj privatni ključ
  5. Klijent i poslužitelj izračunavaju "zajedničku tajnu" pomoću razmijenjenog "pre-master ključa"
  6. Klijent i poslužitelj razmjenjuju poruke koje potvrđuju uspješno šifriranje i dešifriranje pomoću "zajedničke tajne"

Iako je većina koraka jednaka za bilo koje SSL rukovanje, postoji suptilna razlika između jednosmjernog i dvosmjernog SSL-a. Pogledajmo brzo ove razlike.

4.1. Rukovanje u jednosmjernom SSL-u

Ako se pozivamo na gore spomenute korake, drugi korak spominje razmjenu certifikata. Jednosmjerni SSL zahtijeva da klijent može vjerovati poslužitelju putem svog javnog certifikata. Ovaj ostavlja poslužitelju da vjeruje svim klijentima koji traže vezu. Poslužitelj ne može zatražiti i potvrditi javni certifikat od klijenata koji može predstavljati sigurnosni rizik.

4.2. Rukovanje u dvosmjernom SSL-u

S jednosmjernim SSL-om, poslužitelj mora vjerovati svim klijentima. No, dvosmjerni SSL dodaje mogućnost poslužitelju da može uspostaviti i pouzdane klijente. Tijekom dvosmjernog rukovanja, i klijent i poslužitelj moraju međusobno predočiti i prihvatiti javne certifikate prije nego što se uspostavi uspješna veza.

5. Scenariji neuspjeha rukovanja

Nakon brzog pregleda, scenarije neuspjeha možemo sagledati s većom jasnoćom.

SSL rukovanje, u jednosmjernoj ili dvosmjernoj komunikaciji, može propasti iz više razloga. Proći ćemo kroz svaki od ovih razloga, simulirati neuspjeh i shvatiti kako možemo izbjeći takve scenarije.

U svakom od ovih scenarija koristit ćemo SimpleClient i SimpleServer stvorili smo ranije.

5.1. Nedostaje certifikat poslužitelja

Pokušajmo pokrenuti SimpleServer i spojite ga kroz SimpleClient. Iako očekujemo da ćemo vidjeti poruku „Pozdrav svijetu!“, Predstavljena nam je iznimka:

Iznimka u niti "main" javax.net.ssl.SSLHandshakeException: Primljeno fatalno upozorenje: handshake_failure

To znači da je nešto pošlo po zlu. The SSLHandshakeException gore, na apstraktan način, izjavljuje da klijent prilikom spajanja na poslužitelj nije primio nijedan certifikat.

Da bismo riješili ovaj problem, poslužit ćemo se pohranom ključeva koju smo ranije generirali prosljeđujući ih kao svojstva sustava na poslužitelj:

-Djavax.net.ssl.keyStore = clientkeystore.jks -Djavax.net.ssl.keyStorePassword = lozinka

Važno je napomenuti da sistemsko svojstvo za stazu datoteke pohrane ključeva treba biti apsolutna staza ili datoteka spremišta ključeva treba biti smještena u isti direktorij odakle se poziva Java naredba za pokretanje poslužitelja. Svojstvo Java sustava za pohranu ključeva ne podržava relativne staze.

Pomaže li nam ovo u ishodi koju očekujemo? Saznajmo u sljedećem pododjeljku.

5.2. Potvrda nepouzdanog poslužitelja

Dok vodimo SimpleServer i SimpleClient opet s promjenama u prethodnom pododjeljku, što dobivamo kao izlaz:

Iznimka u niti "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX izgradnja puta nije uspjela: sun.security.provider.certpath.SunCertPathBuilderException: nije moguće pronaći valjani put certifikacije do traženog cilja

Pa, nije djelovalo točno onako kako smo očekivali, ali izgleda da je propalo iz drugog razloga.

Ovaj posebni neuspjeh uzrokovan je činjenicom da naš poslužitelj koristi samopotpisani potvrda koju nije potpisalo tijelo za ovjeravanje (CA).

Doista, svaki put kad certifikat potpiše nešto drugo osim onoga što je u zadanoj trgovini povjerenja, vidjet ćemo ovu pogrešku. Zadana skladišta povjerenja u JDK obično se isporučuju s informacijama o uobičajenim CA-ima koji se koriste.

Da bismo ovdje riješili ovaj problem, morat ćemo prisiliti SimpleClient vjerovati potvrdi koju je predočio SimpleServer. Upotrijebimo trgovinu povjerenja koju smo ranije generirali predajući ih klijentu kao svojstva sustava:

-Djavax.net.ssl.trustStore = clienttruststore.jks -Djavax.net.ssl.trustStorePassword = lozinka

Napominjemo da ovo nije idealno rješenje. U idealnom scenariju ne bismo trebali koristiti samopotpisanu potvrdu, već potvrdu koju je ovjerilo tijelo za ovjere (CA) kojem klijenti mogu zadati povjerenje.

Idemo na sljedeći pododjeljak kako bismo saznali dobivamo li sada očekivani rezultat.

5.3. Nedostaje certifikat klijenta

Pokušajmo još jednom pokrenuti SimpleServer i SimpleClient, primijenivši promjene iz prethodnih pododjeljaka:

Iznimka u niti "main" java.net.SocketException: Softver uzrokovao prekid veze: recv nije uspio

Opet, ne nešto što smo očekivali. The SocketException ovdje nam govori da poslužitelj nije mogao vjerovati klijentu. To je zato što smo postavili dvosmjerni SSL. U našem SimpleServer imamo:

((SSLServerSocket) slušatelj) .setNeedClientAuth (true);

Gornji kod označava SSLServerSocket je potreban za provjeru autentičnosti klijenta putem njegove javne potvrde.

Možemo stvoriti trgovinu ključeva za klijenta i odgovarajuću trgovinu povjerenja za poslužitelj na način sličan onome koji smo koristili prilikom stvaranja prethodne trgovine ključeva i trgovine povjerenja.

Ponovno ćemo pokrenuti poslužitelj i proslijediti mu sljedeća svojstva sustava:

-Djavax.net.ssl.keyStore = serverkeystore.jks \ -Djavax.net.ssl.keyStorePassword = lozinka \ -Djavax.net.ssl.trustStore = servertruststore.jks \ -Djavax.net.ssl.trustStorePassword = lozinka

Zatim ćemo ponovno pokrenuti klijenta prosljeđivanjem ovih svojstava sustava:

-Djavax.net.ssl.keyStore = clientkeystore.jks \ -Djavax.net.ssl.keyStorePassword = lozinka \ -Djavax.net.ssl.trustStore = clienttruststore.jks \ -Djavax.net.ssl.trustStorePassword = lozinka

Konačno, imamo izlaz koji smo željeli:

Pozdrav svijete!

5.4. Pogrešne potvrde

Osim gore navedenih pogrešaka, rukovanje može propasti iz različitih razloga povezanih s načinom na koji smo stvorili certifikate. Jedna česta pogreška povezana je s netočnim CN-om. Istražimo detalje pohrane ključeva poslužitelja koju smo prethodno stvorili:

keytool -v -list -keystore serverkeystore.jks

Kada pokrenemo gornju naredbu, možemo vidjeti detalje pohrane ključeva, posebno vlasnika:

... Vlasnik: CN = localhost, OU = tehnologija, O = baeldung, L = grad, ST = država, C = xx ...

CN vlasnika ove potvrde postavljen je na localhost. CN vlasnika mora se točno podudarati s hostom poslužitelja. Ako postoji bilo kakva neusklađenost, rezultirat će datotekom SSLHandshakeException.

Pokušajmo regenerirati certifikat poslužitelja s CN-om kao bilo što drugo osim kao localhost. Kada sada koristimo regenerirani certifikat za pokretanje SimpleServer i SimpleClient odmah ne uspije:

Iznimka u niti "main" javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: Nije pronađeno ime koje se podudara s localhost-om

Gornji trag iznimke jasno ukazuje da je klijent očekivao certifikat s imenom kao localhost koji nije pronašao.

Imajte na umu da JSSE prema zadanim postavkama ne nalaže provjeru imena hosta. Omogućili smo provjeru imena hosta u SimpleClient eksplicitnom uporabom HTTPS-a:

SSLParameters sslParams = novi SSLParameters (); sslParams.setEndpointIdentificationAlgorithm ("HTTPS"); (((SSLSocket) veza) .setSSLParameters (sslParams);

Provjera imena hosta čest je uzrok neuspjeha i općenito i uvijek bi se trebala provoditi radi veće sigurnosti. Pojedinosti o provjeri imena hosta i njegovoj važnosti za sigurnost s TLS-om potražite u ovom članku.

5.5. Nekompatibilna SSL verzija

Trenutno postoje različiti kriptografski protokoli, uključujući različite verzije SSL-a i TLS-a.

Kao što je ranije spomenuto, SSL je općenito zamijenjen TLS-om zbog njegove kriptografske snage. Kriptografski protokol i verzija dodatni su element oko kojeg se klijent i poslužitelj moraju dogovoriti tijekom rukovanja.

Na primjer, ako poslužitelj koristi kriptografski protokol SSL3, a klijent koristi TLS1.3, ne može se dogovoriti o kriptografskom protokolu i SSLHandshakeException generirat će se.

U našem SimpleClient promijenimo protokol u nešto što nije kompatibilno s protokolom postavljenim za poslužitelj:

(((SSLSocket) veza) .setEnabledProtocols (novi String [] {"TLSv1.1"});

Kad ponovno pokrenemo svog klijenta, dobit ćemo SSLHandshakeException:

Iznimka u niti "main" javax.net.ssl.SSLHandshakeException: Nema odgovarajućeg protokola (protokol je onemogućen ili su paketi šifri neprikladni)

Trag iznimke u takvim je slučajevima apstraktan i ne govori nam o točnom problemu. Da biste riješili ove vrste problema, potrebno je provjeriti koriste li i klijent i poslužitelj isti ili kompatibilni kriptografski protokol.

5.6. Nekompatibilni Cipher Suite

Klijent i poslužitelj moraju se također dogovoriti oko paketa šifara koji će koristiti za šifriranje poruka.

Tijekom rukovanja klijent će predstaviti popis mogućih šifri koje će koristiti, a poslužitelj će odgovoriti odabranom šifrom s popisa. Poslužitelj će generirati SSLHandshakeException ako ne može odabrati prikladnu šifru.

U našem SimpleClient promijenimo paket šifri u nešto što nije kompatibilno sa paketom šifri koji koristi naš poslužitelj:

(((SSLSocket) veza) .setEnabledCipherSuites (novi String [] {"TLS_RSA_WITH_AES_128_GCM_SHA256"});

Kad ponovno pokrenimo klijenta, dobit ćemo SSLHandshakeException:

Iznimka u niti "main" javax.net.ssl.SSLHandshakeException: Primljeno fatalno upozorenje: handshake_failure

Opet, trag iznimke prilično je apstraktan i ne govori nam o točnom problemu. Rješenje takve pogreške je provjera omogućenih paketa šifri koje koriste i klijent i poslužitelj te osiguravanje da postoji barem jedan zajednički paket.

Klijenti i poslužitelji obično su konfigurirani da koriste širok spektar šifriranih paketa, tako da je manja vjerojatnost da se dogodi ova pogreška. Ako naiđemo na ovu pogrešku, to je obično zato što je poslužitelj konfiguriran za upotrebu vrlo selektivne šifre. Poslužitelj se može odlučiti za provođenje selektivnog skupa šifri iz sigurnosnih razloga.

6. Zaključak

U ovom uputstvu naučili smo o postavljanju SSL-a pomoću Java utičnica. Zatim smo razgovarali o SSL rukovanju s jednosmjernim i dvosmjernim SSL-om. Konačno, prošli smo popis mogućih razloga zbog kojih SSL rukovanje može zakazati i razgovarali o rješenjima.

Kao i uvijek, kod za primjere dostupan je na GitHub-u.

Dno Java

Upravo sam najavio novo Uči proljeće tečaj, usredotočen na osnove Spring 5 i Spring Boot 2:

>> PROVJERITE TEČAJ

$config[zx-auto] not found$config[zx-overlay] not found