SSL-håndtrykfejl

Java Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

1. Oversigt

Secured Socket Layer (SSL) er en kryptografisk protokol, der giver sikkerhed i kommunikation over netværket. I denne vejledning diskuterer vi forskellige scenarier, der kan resultere i en SSL-håndtryksfejl, og hvordan man gør det.

Bemærk, at vores introduktion til SSL ved hjælp af JSSE dækker det grundlæggende i SSL mere detaljeret.

2. Terminologi

Det er vigtigt at bemærke, at SSL som standard på grund af sikkerhedssårbarheder erstattes af Transport Layer Security (TLS). De fleste programmeringssprog, inklusive Java, har biblioteker, der understøtter både SSL og TLS.

Siden starten af ​​SSL havde mange produkter og sprog som OpenSSL og Java henvisninger til SSL, som de opbevarede, selv efter at TLS overtog. Af den grund bruger vi i resten af ​​denne vejledning udtrykket SSL til generelt at henvise til kryptografiske protokoller.

3. Opsætning

Med henblik på denne vejledning opretter vi en simpel server og klientapplikationer ved hjælp af Java Socket API til at simulere en netværksforbindelse.

3.1. Oprettelse af en klient og en server

I Java kan vi bruge sockets at etablere en kommunikationskanal mellem en server og klient over netværket. Stik er en del af Java Secure Socket Extension (JSSE) i Java.

Lad os begynde med at definere en simpel server:

int-port = 8443; ServerSocketFactory fabrik = SSLServerSocketFactory.getDefault (); prøv (ServerSocket lytter = fabrik.createServerSocket (port)) {SSLServerSocket sslListener = (SSLServerSocket) lytter; sslListener.setNeedClientAuth (sand); sslListener.setEnabledCipherSuites (ny streng [] {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"}); sslListener.setEnabledProtocols (ny streng [] {"TLSv1.2"}); while (true) {try (Socket socket = sslListener.accept ()) {PrintWriter out = new PrintWriter (socket.getOutputStream (), true); out.println ("Hej verden!"); }}}

Serveren defineret ovenfor returnerer meddelelsen "Hello World!" til en tilsluttet klient.

Lad os derefter definere en grundlæggende klient, som vi opretter forbindelse til vores SimpleServer:

String vært = "localhost"; int-port = 8443; SocketFactory fabrik = SSLSocketFactory.getDefault (); prøv (Socket forbindelse = fabrik.createSocket (vært, port)) {((SSLSocket) forbindelse) .setEnabledCipherSuites (ny streng [] {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"}); ((SSLSocket) forbindelse) .setEnabledProtocols (ny streng [] {"TLSv1.2"}); SSLParameters sslParams = nye SSLParameters (); sslParams.setEndpointIdentificationAlgorithm ("HTTPS"); ((SSLSocket) -forbindelse) .setSSLParameters (sslParams); BufferedReader input = new BufferedReader (new InputStreamReader (connection.getInputStream ())); returner input.readLine (); }

Vores klient udskriver den besked, der er returneret af serveren.

3.2. Oprettelse af certifikater i Java

SSL giver hemmeligholdelse, integritet og ægthed i netværkskommunikation. Certifikater spiller en vigtig rolle for så vidt angår etablering af ægthed.

Disse certifikater købes typisk og underskrives af en certifikatmyndighed, men til denne tutorial bruger vi selvsignerede certifikater.

For at opnå dette kan vi bruge nøgleværktøj, som leveres med JDK:

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

Ovenstående kommando starter en interaktiv skal til at indsamle information til certifikatet som Common Name (CN) og Distinguished Name (DN). Når vi giver alle relevante detaljer, genererer den filen serverkeystore.jks, som indeholder serverens private nøgle og dens offentlige certifikat.

Noter det serverkeystore.jks er gemt i Java Key Store (JKS) format, som er proprietært til Java. Disse dage, nøgleværktøj vil minde os om, at vi burde overveje at bruge PKCS # 12, som den også understøtter.

Vi kan bruge yderligere nøgleværktøj for at udtrække det offentlige certifikat fra den genererede keystore-fil:

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

Ovenstående kommando eksporterer det offentlige certifikat fra keystore som en fil server.cer. Lad os bruge det eksporterede certifikat til klienten ved at føje det til dets tillidsbutik:

$ keytool -import -v -trustcacerts \ -fil server.cer \ -keypass password \ -storepass password \ -keystore clienttruststore.jks

Vi har nu genereret en keystore til serveren og tilsvarende truststore til klienten. Vi gennemgår brugen af ​​disse genererede filer, når vi diskuterer mulige fejl i håndtryk.

Og flere detaljer omkring brugen af ​​Java's keystore kan findes i vores tidligere tutorial.

4. SSL-håndtryk

SSL-håndtryk er en mekanisme, hvorved en klient og server etablerer den tillid og logistik, der kræves for at sikre deres forbindelse over netværket.

Dette er en meget orkestreret procedure, og forståelse af detaljerne i dette kan hjælpe med at forstå, hvorfor det ofte mislykkes, hvilket vi har til hensigt at dække i det næste afsnit.

Typiske trin i et SSL-håndtryk er:

  1. Klienten giver en liste over mulige SSL-versioner og krypteringspakker, der kan bruges
  2. Server accepterer en bestemt SSL-version og cipher-suite og svarer tilbage med sit certifikat
  3. Klient udtrækker den offentlige nøgle fra certifikatet reagerer tilbage med en krypteret "pre-master nøgle"
  4. Server dekrypterer "pre-master nøglen" ved hjælp af sin private nøgle
  5. Klient og server beregner en "delt hemmelighed" ved hjælp af den udskiftede "pre-master key"
  6. Klient- og serverudvekslingsmeddelelser, der bekræfter den vellykkede kryptering og dekryptering ved hjælp af den "delte hemmelighed"

Mens de fleste trin er de samme for ethvert SSL-håndtryk, er der en subtil forskel mellem envejs- og tovejs-SSL. Lad os hurtigt gennemgå disse forskelle.

4.1. Håndtrykket i envejs SSL

Hvis vi henviser til de ovennævnte trin, nævner trin to certifikatudvekslingen. Envejs SSL kræver, at en klient kan stole på serveren gennem sit offentlige certifikat. Det her forlader serveren til at stole på alle klienter der anmoder om en forbindelse. Der er ingen måde for en server at anmode om og validere det offentlige certifikat fra klienter, der kan udgøre en sikkerhedsrisiko.

4.2. Håndtrykket i tovejs SSL

Med envejs SSL skal serveren have tillid til alle klienter. Men tovejs SSL tilføjer muligheden for, at serveren også kan etablere betroede klienter. Under et tovejs håndtryk, både klienten og serveren skal præsentere og acceptere hinandens offentlige certifikater inden en vellykket forbindelse kan etableres.

5. Scenarier med fejl i håndtryk

Efter at have foretaget den hurtige gennemgang kan vi se på fejlscenarier med større klarhed.

Et SSL-håndtryk i en- eller tovejskommunikation kan mislykkes af flere grunde. Vi gennemgår hver af disse grunde, simulerer fejlen og forstår, hvordan vi kan undgå sådanne scenarier.

I hvert af disse scenarier bruger vi SimpleClient og SimpleServer vi skabte tidligere.

5.1. Manglende servercertifikat

Lad os prøve at køre SimpleServer og forbinde det gennem SimpleClient. Mens vi forventer at se meddelelsen "Hello World!", Får vi en undtagelse:

Undtagelse i tråden "main" javax.net.ssl.SSLHandshakeException: Modtaget dødelig alarm: handshake_failure

Dette indikerer, at noget gik galt. Det SSLHandshakeException ovenfor på en abstrakt måde angiver, at klienten ved tilslutning til serveren ikke modtog noget certifikat.

For at løse dette problem bruger vi den keystore, vi genererede tidligere, ved at sende dem som systemegenskaber til serveren:

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

Det er vigtigt at bemærke, at systemegenskaben for keystore-filstien enten skal være en absolut sti, eller at keystore-filen skal placeres i samme bibliotek, hvorfra Java-kommandoen påberåbes for at starte serveren. Java-systemegenskab til keystore understøtter ikke relative stier.

Hjælper dette os med at få det output, vi forventer? Lad os finde ud af det i det næste underafsnit.

5.2. Utrovert servercertifikat

Når vi kører SimpleServer og SimpleClient igen med ændringerne i det forrige underafsnit, hvad får vi som output:

Undtagelse i tråden "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: kunne ikke finde en gyldig certificeringssti til det ønskede mål

Nå, det fungerede ikke nøjagtigt som vi forventede, men ser ud til at det har mislykkedes af en anden grund.

Denne særlige fejl skyldes, at vores server bruger en selvsigneret certifikat, som ikke er underskrevet af en Certificate Authority (CA).

Virkelig, når certifikatet er underskrevet af noget andet end det, der er i standard tillidsbutik, ser vi denne fejl. Standard truststore i JDK leveres typisk med oplysninger om almindelige CA'er, der er i brug.

For at løse dette problem her bliver vi nødt til at tvinge SimpleClient at stole på det certifikat, der er fremlagt af SimpleServer. Lad os bruge den truststore, vi genererede tidligere, ved at sende dem som systemegenskaber til klienten:

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

Bemærk, at dette ikke er en ideel løsning. I et ideelt scenario bør vi ikke bruge et selvsigneret certifikat, men et certifikat, der er certificeret af en Certificate Authority (CA), som klienter som standard kan stole på.

Lad os gå til næste underafsnit for at finde ud af, om vi får vores forventede output nu.

5.3. Manglende klientcertifikat

Lad os prøve endnu en gang at køre SimpleServer og SimpleClient efter at have anvendt ændringerne fra tidligere underafsnit:

Undtagelse i tråden "main" java.net.SocketException: Software forårsagede forbindelse afbrudt: recv mislykkedes

Igen, ikke noget vi forventede. Det SocketException her fortæller os, at serveren ikke kunne stole på klienten. Dette skyldes, at vi har oprettet en tovejs SSL. I vores SimpleServer vi har:

((SSLServerSocket) lytter) .setNeedClientAuth (sand);

Ovenstående kode angiver en SSLServerSocket kræves til klientgodkendelse gennem deres offentlige certifikat.

Vi kan oprette en keystore til klienten og en tilsvarende truststore til serveren på en måde, der ligner den, vi brugte, da vi oprettede den forrige keystore og truststore.

Vi genstarter serveren og sender den følgende systemegenskaber:

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

Derefter genstarter vi klienten ved at videregive disse systemegenskaber:

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

Endelig har vi den ønskede output:

Hej Verden!

5.4. Forkerte certifikater

Bortset fra ovenstående fejl kan et håndtryk mislykkes på grund af forskellige årsager relateret til, hvordan vi har oprettet certifikaterne. En almindelig fejl er relateret til en forkert CN. Lad os udforske detaljerne i server-keystore, vi oprettede tidligere:

keytool -v -list -keystore serverkeystore.jks

Når vi kører ovenstående kommando, kan vi se detaljerne i keystore, specifikt ejeren:

... Ejer: CN = localhost, OU = teknologi, O = baeldung, L = by, ST = stat, C = xx ...

CN for ejeren af ​​dette certifikat er indstillet til localhost. Ejerens CN skal nøjagtigt matche værten på serveren. Hvis der er nogen uoverensstemmelse, vil det resultere i en SSLHandshakeException.

Lad os prøve at regenerere servercertifikatet med CN som noget andet end localhost. Når vi nu bruger det regenererede certifikat til at køre SimpleServer og SimpleClient det fejler straks:

Undtagelse i tråden "main" javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: Intet navn matchende localhost fundet

Ovenstående undtagelsesspor viser tydeligt, at klienten forventede et certifikat med navnet som localhost, som det ikke fandt.

Bemærk, at JSSE pålægger ikke værtsnavnsbekræftelse som standard. Vi har aktiveret værtsnavnbekræftelse i SimpleClient gennem eksplicit brug af HTTPS:

SSLParameters sslParams = nye SSLParameters (); sslParams.setEndpointIdentificationAlgorithm ("HTTPS"); ((SSLSocket) forbindelse) .setSSLParameters (sslParams);

Verifikation af værtsnavn er en almindelig årsag til fiasko og generelt og bør altid håndhæves for bedre sikkerhed. For yderligere oplysninger om værtsnavnsbekræftelse og dens betydning for sikkerhed med TLS henvises til denne artikel.

5.5. Inkompatibel SSL-version

I øjeblikket er der forskellige kryptografiske protokoller, herunder forskellige versioner af SSL og TLS i drift.

Som tidligere nævnt er SSL generelt erstattet af TLS for sin kryptografiske styrke. Den kryptografiske protokol og version er et yderligere element, som en klient og en server skal være enige om under et håndtryk.

For eksempel, hvis serveren bruger en kryptografisk protokol af SSL3, og klienten bruger TLS1.3, kan de ikke blive enige om en kryptografisk protokol og en SSLHandshakeException vil blive genereret.

I vores SimpleClient lad os ændre protokollen til noget, der ikke er kompatibelt med det protokolsæt, der er sat til serveren:

((SSLSocket) forbindelse) .setEnabledProtocols (ny streng [] {"TLSv1.1"});

Når vi kører vores klient igen, får vi en SSLHandshakeException:

Undtagelse i tråden "main" javax.net.ssl.SSLHandshakeException: Ingen passende protokol (protokol er deaktiveret eller krypteringspakker er upassende)

Undtagelsesspor i sådanne tilfælde er abstrakt og fortæller os ikke det nøjagtige problem. For at løse disse typer problemer er det nødvendigt at kontrollere, at både klienten og serveren bruger enten de samme eller kompatible kryptografiske protokoller.

5.6. Inkompatibel krypteringssuite

Klienten og serveren skal også blive enige om den krypteringspakke, de vil bruge til at kryptere beskeder.

Under et håndtryk vil klienten præsentere en liste over mulige cifre, der skal bruges, og serveren vil svare med en valgt chiffer fra listen. Serveren genererer en SSLHandshakeException hvis det ikke kan vælge en passende kryptering.

I vores SimpleClient lad os ændre krypteringspakken til noget, der ikke er kompatibelt med krypteringspakken, der bruges af vores server:

((SSLSocket) forbindelse) .setEnabledCipherSuites (ny streng [] {"TLS_RSA_WITH_AES_128_GCM_SHA256"});

Når vi genstarter vores klient, får vi en SSLHandshakeException:

Undtagelse i tråden "main" javax.net.ssl.SSLHandshakeException: Modtaget dødelig alarm: handshake_failure

Igen er sporene med undtagelser ret abstrakte og fortæller os ikke det nøjagtige problem. Løsningen på en sådan fejl er at verificere de aktiverede chifferpakker, der bruges af både klienten og serveren, og sikre, at der er mindst en fælles suite tilgængelig.

Normalt er klienter og servere konfigureret til at bruge en lang række krypteringspakker, så det er mindre sandsynligt, at denne fejl opstår. Hvis vi støder på denne fejl, skyldes det typisk, at serveren er konfigureret til at bruge en meget selektiv chiffer. En server kan vælge at håndhæve et selektivt sæt cifre af sikkerhedsmæssige årsager.

6. Konklusion

I denne vejledning lærte vi om opsætning af SSL ved hjælp af Java-sockets. Derefter diskuterede vi SSL-håndtryk med envejs- og tovejs-SSL. Endelig gennemgik vi en liste over mulige årsager til, at SSL-håndtryk måske mislykkedes, og diskuterede løsningerne.

Som altid er koden til eksemplerne tilgængelig på GitHub.

Java bund

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN