SSL-handshake-fouten

Java Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Overzicht

Secured Socket Layer (SSL) is een cryptografisch protocol dat beveiliging biedt bij communicatie via het netwerk. In deze zelfstudie bespreken we verschillende scenario's die kunnen resulteren in het mislukken van een SSL-handshake en hoe u dit kunt doen.

Merk op dat onze Inleiding tot SSL met JSSE de basisprincipes van SSL in meer detail behandelt.

2. Terminologie

Het is belangrijk op te merken dat vanwege beveiligingsproblemen SSL als standaard wordt vervangen door Transport Layer Security (TLS). De meeste programmeertalen, waaronder Java, hebben bibliotheken die zowel SSL als TLS ondersteunen.

Sinds de introductie van SSL hadden veel producten en talen, zoals OpenSSL en Java, verwijzingen naar SSL die ze bleven behouden, zelfs nadat TLS het overnam. Om deze reden zullen we in de rest van deze tutorial de term SSL gebruiken om in het algemeen naar cryptografische protocollen te verwijzen.

3. Installatie

Voor deze zelfstudie maken we eenvoudige server- en clienttoepassingen met behulp van de Java Socket API om een ​​netwerkverbinding te simuleren.

3.1. Een client en een server maken

In Java kunnen we sockets om een ​​communicatiekanaal tot stand te brengen tussen een server en client via het netwerk. Sockets zijn een onderdeel van de Java Secure Socket Extension (JSSE) in Java.

Laten we beginnen met het definiëren van een eenvoudige server:

int poort = 8443; ServerSocketFactory factory = SSLServerSocketFactory.getDefault (); probeer (ServerSocket listener = factory.createServerSocket (poort)) {SSLServerSocket sslListener = (SSLServerSocket) luisteraar; sslListener.setNeedClientAuth (true); sslListener.setEnabledCipherSuites (nieuwe String [] {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"}); sslListener.setEnabledProtocols (nieuwe String [] {"TLSv1.2"}); while (true) {try (Socket socket = sslListener.accept ()) {PrintWriter uit = nieuwe PrintWriter (socket.getOutputStream (), true); out.println ("Hallo wereld!"); }}}

De hierboven gedefinieerde server retourneert het bericht "Hallo wereld!" naar een verbonden klant.

Laten we vervolgens een basisclient definiëren, die we zullen verbinden met onze SimpleServer:

String host = "localhost"; int poort = 8443; SocketFactory factory = SSLSocketFactory.getDefault (); probeer (Socket-verbinding = factory.createSocket (host, poort)) {((SSLSocket) -verbinding) .setEnabledCipherSuites (nieuwe String [] {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"}); ((SSLSocket) verbinding) .setEnabledProtocols (nieuwe String [] {"TLSv1.2"}); SSLParameters sslParams = nieuwe SSLParameters (); sslParams.setEndpointIdentificationAlgorithm ("HTTPS"); ((SSLSocket) verbinding) .setSSLParameters (sslParams); BufferedReader input = nieuwe BufferedReader (nieuwe InputStreamReader (connection.getInputStream ())); return input.readLine (); }

Onze client drukt het bericht af dat door de server is geretourneerd.

3.2. Certificaten maken in Java

SSL biedt geheimhouding, integriteit en authenticiteit in netwerkcommunicatie. Certificaten spelen een belangrijke rol bij het vaststellen van authenticiteit.

Meestal worden deze certificaten gekocht en ondertekend door een certificeringsinstantie, maar voor deze zelfstudie gebruiken we zelfondertekende certificaten.

Om dit te bereiken kunnen we gebruik maken van belangrijk hulpmiddel, die wordt geleverd met de JDK:

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

De bovenstaande opdracht start een interactieve shell om informatie voor het certificaat te verzamelen, zoals Common Name (CN) en Distinguished Name (DN). Als we alle relevante details verstrekken, wordt het bestand gegenereerd serverkeystore.jks, die de persoonlijke sleutel van de server en het openbare certificaat bevat.

Let daar op serverkeystore.jks wordt opgeslagen in de Java Key Store (JKS) -indeling, die eigendom is van Java. De laatste tijd, belangrijk hulpmiddel zal ons eraan herinneren dat we zouden moeten overwegen om PKCS # 12 te gebruiken, dat ook wordt ondersteund.

We kunnen verder gebruiken belangrijk hulpmiddel om het openbare certificaat uit het gegenereerde sleutelarchiefbestand te halen:

$ keytool -export -storepass wachtwoord \ -bestand server.cer \ -keystore serverkeystore.jks

De bovenstaande opdracht exporteert het openbare certificaat vanuit de keystore als een bestand server.cer. Laten we het geëxporteerde certificaat voor de client gebruiken door het toe te voegen aan zijn truststore:

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

We hebben nu een keystore voor de server gegenereerd en een bijbehorende truststore voor de client. We zullen het gebruik van deze gegenereerde bestanden bespreken wanneer we mogelijke handshake-fouten bespreken.

En meer details over het gebruik van Java's keystore zijn te vinden in onze vorige tutorial.

4. SSL-handdruk

SSL-handdrukken zijn een mechanisme waarmee een client en server het vertrouwen en de logistiek tot stand brengen die nodig zijn om hun verbinding via het netwerk te beveiligen.

Dit is een zeer georkestreerde procedure en het begrijpen van de details hiervan kan helpen begrijpen waarom het vaak mislukt, wat we in de volgende sectie willen behandelen.

Typische stappen bij een SSL-handshake zijn:

  1. Client biedt een lijst met mogelijke SSL-versies en coderingssuites die kunnen worden gebruikt
  2. De server gaat akkoord met een bepaalde SSL-versie en coderingssuite en reageert hierop met zijn certificaat
  3. Client haalt de openbare sleutel uit het certificaat en antwoordt met een gecodeerde "pre-master key"
  4. De server decodeert de "pre-master key" met behulp van zijn privésleutel
  5. Client en server berekenen een "gedeeld geheim" met behulp van de uitgewisselde "pre-master key"
  6. Client en server wisselen berichten uit die de succesvolle codering en decodering bevestigen met behulp van het 'gedeelde geheim'

Hoewel de meeste stappen hetzelfde zijn voor elke SSL-handshake, is er een subtiel verschil tussen eenrichtings- en tweerichtings-SSL. Laten we deze verschillen snel bekijken.

4.1. De handdruk in eenrichtings-SSL

Als we verwijzen naar de bovenstaande stappen, vermeldt stap twee de uitwisseling van certificaten. Eenrichtings-SSL vereist dat een client de server kan vertrouwen via zijn openbare certificaat. Dit verlaat de server om alle clients te vertrouwen die een verbinding aanvragen. Er is geen manier voor een server om het openbare certificaat van clients op te vragen en te valideren, wat een beveiligingsrisico kan vormen.

4.2. De handdruk in tweerichtings-SSL

Met eenrichtings-SSL moet de server alle clients vertrouwen. Maar tweerichtings-SSL voegt de mogelijkheid toe voor de server om ook vertrouwde clients tot stand te brengen. Tijdens een tweezijdige handdruk, zowel de client als de server moeten elkaars openbare certificaten presenteren en accepteren voordat een succesvolle verbinding tot stand kan worden gebracht.

5. Handshake-foutscenario's

Na dat snelle overzicht kunnen we faalscenario's met meer duidelijkheid bekijken.

Een SSL-handshake, in eenrichtings- of tweerichtingscommunicatie, kan om meerdere redenen mislukken. We zullen elk van deze redenen doornemen, de mislukking simuleren en begrijpen hoe we dergelijke scenario's kunnen vermijden.

In elk van deze scenario's gebruiken we de SimpleClient en SimpleServer we hebben eerder gemaakt.

5.1. Ontbrekend servercertificaat

Laten we proberen het SimpleServer en verbind het via de SimpleClient. Hoewel we verwachten het bericht "Hallo wereld!" Te zien, krijgen we een uitzondering te zien:

Uitzondering in thread "main" javax.net.ssl.SSLHandshakeException: Fatale waarschuwing ontvangen: handshake_failure

Dit geeft aan dat er iets mis is gegaan. De SSLHandshakeException hierboven, op een abstracte manier, stelt dat de client bij het verbinden met de server geen certificaat heeft ontvangen.

Om dit probleem op te lossen, gebruiken we de keystore die we eerder hebben gegenereerd door deze als systeemeigenschappen door te geven aan de server:

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

Het is belangrijk op te merken dat de systeemeigenschap voor het keystore-bestandspad een absoluut pad moet zijn of dat het keystore-bestand in dezelfde directory moet worden geplaatst van waaruit de Java-opdracht wordt aangeroepen om de server te starten. Java-systeemeigenschap voor keystore ondersteunt geen relatieve paden.

Helpt dit ons om de output te krijgen die we verwachten? Laten we het in de volgende onderafdeling ontdekken.

5.2. Niet-vertrouwd servercertificaat

Terwijl we het SimpleServer en de SimpleClient nogmaals met de wijzigingen in de vorige subsectie, wat krijgen we als uitvoer:

Uitzondering in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX-pad bouwen mislukt: sun.security.provider.certpath.SunCertPathBuilderException: kan geen geldig certificeringspad naar het aangevraagde doel vinden

Nou, het werkte niet precies zoals we hadden verwacht, maar het lijkt erop dat het om een ​​andere reden is mislukt.

Deze specifieke fout wordt veroorzaakt door het feit dat onze server een zelf ondertekend certificaat dat niet is ondertekend door een certificeringsinstantie (CA).

Echt, elke keer dat het certificaat is ondertekend door iets anders dan wat in de standaard truststore staat, zullen we deze fout zien. De standaard truststore in JDK wordt doorgaans geleverd met informatie over veelgebruikte CA's die in gebruik zijn.

Om dit probleem hier op te lossen, zullen we moeten forceren SimpleClient om het certificaat van SimpleServer. Laten we de truststore gebruiken die we eerder hebben gegenereerd door ze als systeemeigenschappen door te geven aan de client:

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

Houd er rekening mee dat dit geen ideale oplossing is. In een ideaal scenario zouden we geen zelfondertekend certificaat moeten gebruiken, maar een certificaat dat is gecertificeerd door een certificeringsinstantie (CA) die klanten standaard kunnen vertrouwen.

Laten we naar de volgende subsectie gaan om erachter te komen of we nu onze verwachte uitvoer krijgen.

5.3. Klantcertificaat ontbreekt

Laten we nog een keer proberen de SimpleServer en de SimpleClient uit te voeren, nadat we de wijzigingen van de vorige subsecties hebben toegepast:

Uitzondering in thread "main" java.net.SocketException: Software veroorzaakte verbroken verbinding: recv mislukt

Nogmaals, niet iets dat we hadden verwacht. De SocketException hier vertelt ons dat de server de client niet kon vertrouwen. Dit komt doordat we een tweerichtings-SSL hebben opgezet. In onze SimpleServer we hebben:

((SSLServerSocket) luisteraar) .setNeedClientAuth (true);

De bovenstaande code geeft een SSLServerSocket is vereist voor clientauthenticatie via hun openbare certificaat.

We kunnen een keystore voor de client en een bijbehorende truststore voor de server maken op een manier die vergelijkbaar is met degene die we hebben gebruikt bij het maken van de vorige keystore en truststore.

We zullen de server opnieuw opstarten en de volgende systeemeigenschappen doorgeven:

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

Vervolgens zullen we de client herstarten door deze systeemeigenschappen door te geven:

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

Eindelijk hebben we de output die we wilden:

Hallo Wereld!

5.4. Onjuiste certificaten

Afgezien van de bovenstaande fouten, kan een handshake mislukken vanwege verschillende redenen die verband houden met de manier waarop we de certificaten hebben gemaakt. Een veel voorkomende fout houdt verband met een onjuiste CN. Laten we de details bekijken van de serversleutelarchief die we eerder hebben gemaakt:

keytool -v -list -keystore serverkeystore.jks

Wanneer we de bovenstaande opdracht uitvoeren, kunnen we de details van de keystore zien, met name de eigenaar:

... Eigenaar: CN = localhost, OU = technologie, O = baeldung, L = stad, ST = staat, C = xx ...

De CN van de eigenaar van dit certificaat is ingesteld op localhost. De CN van de eigenaar moet exact overeenkomen met de host van de server. Als er een mismatch is, resulteert dit in een SSLHandshakeException.

Laten we proberen het servercertificaat opnieuw te genereren met CN als iets anders dan localhost. Wanneer we het opnieuw gegenereerde certificaat nu gebruiken om het SimpleServer en SimpleClient het faalt prompt:

Uitzondering in thread "main" javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: geen naam die overeenkomt met localhost gevonden

De uitzonderingstracering hierboven geeft duidelijk aan dat de client een certificaat verwachtte met de naam localhost, dat deze niet kon vinden.

Houd er rekening mee dat JSSE verplicht standaard geen hostnaamverificatie. We hebben hostnaamverificatie ingeschakeld in het SimpleClient door expliciet gebruik van HTTPS:

SSLParameters sslParams = nieuwe SSLParameters (); sslParams.setEndpointIdentificationAlgorithm ("HTTPS"); ((SSLSocket) verbinding) .setSSLParameters (sslParams);

Hostnaamverificatie is een veelvoorkomende oorzaak van storingen en moet in het algemeen altijd worden afgedwongen voor een betere beveiliging. Raadpleeg dit artikel voor details over hostnaamverificatie en het belang ervan voor de beveiliging met TLS.

5.5. Incompatibele SSL-versie

Momenteel zijn er verschillende cryptografische protocollen, waaronder verschillende versies van SSL en TLS in gebruik.

Zoals eerder vermeld, is SSL in het algemeen vervangen door TLS vanwege zijn cryptografische kracht. Het cryptografische protocol en de versie zijn een bijkomend element waarover een client en een server het eens moeten worden tijdens een handshake.

Als de server bijvoorbeeld het cryptografisch protocol SSL3 gebruikt en de client TLS1.3 gebruikt, kunnen ze geen overeenstemming bereiken over een cryptografisch protocol en een SSLHandshakeException wordt gegenereerd.

In onze SimpleClient laten we het protocol veranderen in iets dat niet compatibel is met het protocol dat is ingesteld voor de server:

((SSLSocket) verbinding) .setEnabledProtocols (nieuwe String [] {"TLSv1.1"});

Als we onze client opnieuw runnen, krijgen we een SSLHandshakeException:

Uitzondering in thread "main" javax.net.ssl.SSLHandshakeException: geen geschikt protocol (protocol is uitgeschakeld of coderingssuites zijn ongepast)

Het uitzonderingsspoor in dergelijke gevallen is abstract en vertelt ons niet het exacte probleem. Om dit soort problemen op te lossen, is het nodig om te controleren of zowel de client als de server dezelfde of compatibele cryptografische protocollen gebruiken.

5.6. Incompatibele Cipher Suite

De client en server moeten het ook eens worden over de coderingssuite die ze zullen gebruiken om berichten te versleutelen.

Tijdens een handshake zal de client een lijst met mogelijke te gebruiken codes presenteren en de server zal reageren met een geselecteerd cijfer uit de lijst. De server genereert een SSLHandshakeException als het geen geschikt cijfer kan selecteren.

In onze SimpleClient laten we de coderingssuite veranderen in iets dat niet compatibel is met de coderingssuite die door onze server wordt gebruikt:

((SSLSocket) verbinding) .setEnabledCipherSuites (nieuwe String [] {"TLS_RSA_WITH_AES_128_GCM_SHA256"});

Wanneer we onze client opnieuw opstarten, krijgen we een SSLHandshakeException:

Uitzondering in thread "main" javax.net.ssl.SSLHandshakeException: fatale waarschuwing ontvangen: handshake_failure

Nogmaals, het uitzonderingsspoor is vrij abstract en vertelt ons niet het exacte probleem. De oplossing voor een dergelijke fout is om de ingeschakelde versleutelingssuites te verifiëren die door zowel de client als de server worden gebruikt en ervoor te zorgen dat er ten minste één gemeenschappelijke suite beschikbaar is.

Normaal gesproken zijn clients en servers geconfigureerd om een ​​breed scala aan coderingssuites te gebruiken, zodat de kans kleiner is dat deze fout optreedt. Als we deze fout tegenkomen, komt dat meestal doordat de server is geconfigureerd om een ​​zeer selectief cijfer te gebruiken. Een server kan ervoor kiezen om om veiligheidsredenen een selectieve set cijfers af te dwingen.

6. Conclusie

In deze zelfstudie hebben we geleerd over het instellen van SSL met Java-sockets. Vervolgens bespraken we SSL-handshakes met eenrichtings- en tweerichtings-SSL. Ten slotte hebben we een lijst met mogelijke redenen doorgenomen waarom SSL-handshakes kunnen mislukken en hebben we de oplossingen besproken.

Zoals altijd is de code voor de voorbeelden beschikbaar op GitHub.

Java onderkant

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS