SSLServerSocket et la configuration du certificat

Matthieu :

J'ai écrit un programme Java client / serveur à l' aide ServerSocketet des Socketobjets. Je me suis alors modifié le code à utiliser SSLServerSocketet « SSLSocket` mais j'obtiens différentes exceptions levées , y compris:

javax.net.ssl.SSLHandshakeException: no cipher suites in common

J'espère en faire autant par programme que je peux. Je suis également d'accord avec les certificats auto-signés.

Un tutoriel j'ai suivi a proposé la création d' un certificat avec l' keytoolapplication java, déplacer ensuite ce fichier dans votre projet java. Je l' ai fait avec la commande de terminal keytool -genkey -alias zastore -keyalg RSA -keystore za.store. Je le mot de passe affecté à être password.

J'appelle alors la fonction System.setPropertydans l' espoir des SSLSockets de travail , mais il ne fonctionne toujours pas.

Voici mon code serveur

public class Server implements Runnable
{
    private SSLServerSocket serverSocket;
    private int portNumber;
    private Thread acceptThread;

    private LinkedList<Connection> connections;

    private ConnectionListener connectionListener;

    public Server(int port, ConnectionListener connectionListener)
    {
        this.connectionListener = connectionListener;
        portNumber = port;
        connections = new LinkedList<Connection>();
        try 
        {
            System.setProperty("javax.net.ssl.trustStore", "za.store");
            System.setProperty("javax.net.ssl.keyStorePassword", "password");
            SSLServerSocketFactory sslssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

            serverSocket = (SSLServerSocket) sslssf.createServerSocket(portNumber,15);

        } 
        catch (IOException e) 
        {
            e.printStackTrace();
        }
    }

    public void startListening()
    {
        acceptThread = new Thread(this);
        acceptThread.start();
    }
    public void stopListening()
    {

        for(Connection c:connections)
        {
            c.stopListeningAndDisconnect();
        }

        try 
        {   
            serverSocket.close();
        } 
        catch (IOException e) 
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    @Override
    public void run() 
    {
        try 
        {
            while(true)
            {
                SSLSocket s = (SSLSocket) serverSocket.accept();
                Connection c = new Connection(s,connectionListener);
                connections.add(c);
                System.out.println("New Connection Established From"+s.getInetAddress().toString());
            }
        } 
        catch(java.net.SocketException e)
        {
            System.out.println("Listening thread terminated with exception.");
        }
        catch(IOException e) 
        {

            e.printStackTrace();
        }
    }

    public void removeConnection(Connection c)
    {
        connections.remove(c);
    }

    public void printConnections()
    {
        System.out.println("Number of connections "+connections.toString());

        for(int i=0; i<connections.size(); i++)
        {
            System.out.println(connections.toString());
        }
    }

}

Et puis un snipbit de mon code client qui se connecte quand un bouton est pressé:

@Override
    public void actionPerformed(ActionEvent e) 
    {
        if(e.getSource() == connect)
        {
            try 
            {
                System.setProperty("javax.net.ssl.trustStore", "za.store");
                System.setProperty("javax.net.ssl.keyStorePassword", "password");



                SSLSocketFactory sslsf = (SSLSocketFactory)SSLSocketFactory.getDefault();

                SSLSocket s = (SSLSocket)sslsf.createSocket(ipBox.getText(), Integer.parseInt(portBox.getText()));
                Connection c = new Connection(s,parent);
                parent.connectionSuccessful(c);
            } 
            catch (NumberFormatException e1) 
            {
                JOptionPane.showMessageDialog(this, "Error! Port number must be a number", "Error", JOptionPane.ERROR_MESSAGE);             
            } 
            catch (UnknownHostException e1) 
            {
                JOptionPane.showMessageDialog(this, "Error! Unable to find that host", "Error", JOptionPane.ERROR_MESSAGE);
            } 
            catch (IOException e1) 
            {


e1.printStackTrace();
        }   
    }
}

Un article Stackoverflow suggéré que mon serveur « ne dispose pas d'un certificat. » Je ne sais pas ce que cela signifie ou comment s'y prendre pour obtenir un et le localiser au bon endroit.

Saptarshi Basu:

L'erreur suivante peut être due à diverses raisons:

javax.net.ssl.SSLHandshakeException: no cipher suites in common

Les points à vérifier lors du débogage:

  1. Le fichier de clés et certifiées sont correctement chargé et utilisé pour créer les connexions socket
  2. Le certificat est compatible avec les suites de chiffrement permis
  3. Au moins une commune suite de chiffrement doit être activé dans le client et le serveur et que la suite de chiffrement doit être également compatible avec le certificat

Par exemple , dans l'exemple suivant, je suis en Java 8 avec le jeu par défaut de suites de chiffrement. Le certificat que j'ai généré utilise ECDSA et SHA384, et donc lorsque la connexion TLS est établie entre le serveur et le client, je peux voir la suite de chiffrement négocié est TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384en permettant la mise au point ( System.setProperty("javax.net.debug", "ssl");).

Voici un exemple de travail:

Dans un premier temps, une paire de clés et un besoin de certificat à créer. Pour des fins de test, nous allons créer un certificat auto-signé et nous allons utiliser le même certificat pour le serveur et le client:

keytool -genkeypair -alias server -keyalg EC \
-sigalg SHA384withECDSA -keysize 256 -keystore servercert.p12 \
-storetype pkcs12 -v -storepass abc123 -validity 10000 -ext san=ip:127.0.0.1

Créons maintenant le serveur:

package com.sapbasu.javastudy;

import java.io.InputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Objects;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManagerFactory;

/*
 * keytool -genkeypair -alias server -keyalg EC \
 * -sigalg SHA384withECDSA -keysize 256 -keystore servercert.p12 \
 * -storetype pkcs12 -v -storepass abc123 -validity 10000 -ext san=ip:127.0.0.1
 */

public class TLSServer {
  public void serve(int port, String tlsVersion, String trustStoreName,
      char[] trustStorePassword, String keyStoreName, char[] keyStorePassword)
      throws Exception {

    Objects.requireNonNull(tlsVersion, "TLS version is mandatory");

    if (port <= 0) {
      throw new IllegalArgumentException(
          "Port number cannot be less than or equal to 0");
    }

    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    InputStream tstore = TLSServer.class
        .getResourceAsStream("/" + trustStoreName);
    trustStore.load(tstore, trustStorePassword);
    tstore.close();
    TrustManagerFactory tmf = TrustManagerFactory
        .getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(trustStore);

    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    InputStream kstore = TLSServer.class
        .getResourceAsStream("/" + keyStoreName);
    keyStore.load(kstore, keyStorePassword);
    KeyManagerFactory kmf = KeyManagerFactory
        .getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, keyStorePassword);
    SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
        SecureRandom.getInstanceStrong());

    SSLServerSocketFactory factory = ctx.getServerSocketFactory();
    try (ServerSocket listener = factory.createServerSocket(port)) {
      SSLServerSocket sslListener = (SSLServerSocket) listener;

      sslListener.setNeedClientAuth(true);
      sslListener.setEnabledProtocols(new String[] {tlsVersion});
      // NIO to be implemented
      while (true) {
        try (Socket socket = sslListener.accept()) {
          PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
          out.println("Hello World!");
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
  }
}

Maintenant, créez le client:

package com.sapbasu.javastudy;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Objects;

import javax.net.SocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManagerFactory;

public class TLSClient {
  public String request(InetAddress serverHost, int serverPort,
      String tlsVersion, String trustStoreName, char[] trustStorePassword,
      String keyStoreName, char[] keyStorePassword) throws Exception {

    Objects.requireNonNull(tlsVersion, "TLS version is mandatory");

    Objects.requireNonNull(serverHost, "Server host cannot be null");

    if (serverPort <= 0) {
      throw new IllegalArgumentException(
          "Server port cannot be lesss than or equal to 0");
    }

    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    InputStream tstore = TLSClient.class
        .getResourceAsStream("/" + trustStoreName);
    trustStore.load(tstore, trustStorePassword);
    tstore.close();
    TrustManagerFactory tmf = TrustManagerFactory
        .getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(trustStore);

    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    InputStream kstore = TLSClient.class
        .getResourceAsStream("/" + keyStoreName);
    keyStore.load(kstore, keyStorePassword);
    KeyManagerFactory kmf = KeyManagerFactory
        .getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, keyStorePassword);
    SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
        SecureRandom.getInstanceStrong());

    SocketFactory factory = ctx.getSocketFactory();

    try (Socket connection = factory.createSocket(serverHost, serverPort)) {
      ((SSLSocket) connection).setEnabledProtocols(new String[] {tlsVersion});
      SSLParameters sslParams = new SSLParameters();
      sslParams.setEndpointIdentificationAlgorithm("HTTPS");
      ((SSLSocket) connection).setSSLParameters(sslParams);

      BufferedReader input = new BufferedReader(
          new InputStreamReader(connection.getInputStream()));
      return input.readLine();
    }
  }
}

Enfin, voici un test JUnit pour tester la connexion:

package com.sapbasu.javastudy;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.net.InetAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.jupiter.api.Test;

public class TLSServerClientTest {

  private static final int SERVER_PORT = 8444;
  private static final String TLS_VERSION = "TLSv1.2";
  private static final int SERVER_COUNT = 1;
  private static final String SERVER_HOST_NAME = "127.0.0.1";
  private static final String TRUST_STORE_NAME = "servercert.p12";
  private static final char[] TRUST_STORE_PWD = new char[] {'a', 'b', 'c', '1',
      '2', '3'};
  private static final String KEY_STORE_NAME = "servercert.p12";
  private static final char[] KEY_STORE_PWD = new char[] {'a', 'b', 'c', '1',
      '2', '3'};

  @Test
  public void whenClientSendsServerRequest_givenServerIsUp_returnsHelloWorld()
      throws Exception {
    TLSServer server = new TLSServer();
    TLSClient client = new TLSClient();

    System.setProperty("javax.net.debug", "ssl");

    ExecutorService serverExecutor = Executors.newFixedThreadPool(SERVER_COUNT);
    serverExecutor.submit(() -> {
      try {
        server.serve(SERVER_PORT, TLS_VERSION, TRUST_STORE_NAME,
            TRUST_STORE_PWD, KEY_STORE_NAME, KEY_STORE_PWD);
      } catch (Exception e) {
        e.printStackTrace();
      }
    });
    try {
      String returnedValue = client.request(
          InetAddress.getByName(SERVER_HOST_NAME), SERVER_PORT, TLS_VERSION,
          TRUST_STORE_NAME, TRUST_STORE_PWD, KEY_STORE_NAME, KEY_STORE_PWD);
      assertEquals("Hello World!", returnedValue);
    } catch (Exception e) {
      e.printStackTrace();
      throw e;
    }
  }
}

Note: Le certificat (servercert.p12 dans cet exemple) devraient être dans le classpath. Dans cet exemple, je l'ai gardé dans le test / ressources dossier de la structure du dossier Maven pour que le test JUnit peut obtenir dans le classpath.


Contexte Suite Cipher

Lorsque vous utilisez TLS / SSL, les algorithmes de chiffrement à utiliser sont déterminées par les suites de chiffrement. Le serveur prend en charge un ensemble de suites de chiffrement (vous pouvez activer ou désactiver certaines suites selon vos besoins et le niveau de sécurité que vous voulez). Le client prend également en charge un ensemble de suites de chiffrement. Lors de la configuration de la connexion, la suite de chiffrement à utiliser est négocié entre le client et le serveur. La préférence du client sera honoré, étant donné que les supports de serveur suite de chiffrement particulier.

Vous trouveriez la liste des suites de chiffrement pris en charge par les fournisseurs Sun Java JUSQU'A 8 ici .

Un nom de suite de chiffrement typique ressemble à ceci:

TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256

Ici,

ECDHE signifie Elliptic Curve Diffie Hellman Ephémère. Il est un algorithme d'échange de clés. La variante elliptique (le premier E) est utilisé pour l' exécution, alors que la variante éphémère (le dernier E) est secret avant. Des moyens de secret avant que si un attaquant continue d' enregistrer toutes les communications sur TLS et à un moment ultérieur obtient en quelque sorte la main sur la clé privée, il / elle ne peut pas déchiffrer les communications enregistrées passées.

ECDSA est un algorithme de signature numérique utilisé pour la signature de la clé et est utilisée pour l' authentification (vérification de l'intégrité) le secret partagé. ECDSA est plus faible et plus lent que les autres algorithmes d'authentification comme HMAC. Pourtant , il est utilisé pour l' authentification par clé partagée , car elle n'a pas besoin de connaître la clé secrète vérificateur utilisé pour créer l'étiquette d'authentification. Le serveur peut très bien utiliser sa clé privée pour vérifier l'intégrité du message.

AES_128_GCM - Une fois une clé secrète commune est partagée entre les deux parties (généralement un navigateur et un serveur web), un algorithme de chiffrement par bloc symétrique est utilisé pour chiffrer les échanges de messages entre les parties. Dans ce cas particulier, le chiffrement par bloc AES 128 bits clé et le mode d'authentification GCM est utilisé.

SHA256 - algorithme de hachage pour le PRF

Je suppose que tu aimes

Origine http://43.154.161.224:23101/article/api/json?id=199189&siteId=1
conseillé
Classement