当前位置: 首页 > 面试题库 >

SSLServerSocket和证书设置

刘绍晖
2023-03-14
问题内容

我使用ServerSocketSocket对象编写了一个客户端/服务器Java程序。然后,我修改了要使用的代码SSLServerSocket和“
SSLSocket”,但是抛出了不同的异常,包括:

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

我希望能以编程方式做更多的事情。我也可以使用自签名证书。

我遵循的一个教程建议使用keytoolJava应用程序创建证书,然后将该文件移入Java项目。我已经用terminal命令完成了keytool -genkey -alias zastore -keyalg RSA -keystore za.store。我将密码指定为password

然后我调用该函数System.setProperty,希望SSLSockets可以正常工作,但仍然不能。

这是我的服务器代码

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());
        }
    }

}

然后,当按下按钮时,我的客户端代码的片段将连接:

@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();
        }   
    }
}

一篇Stackoverflow文章建议我的服务器“没有证书”。我不知道这意味着什么,或者不知道如何获得一个并将其放置在正确的位置。


问题答案:

由于各种原因,可能会出现以下错误:

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

调试时要检查的要点:

  1. 密钥库和信任库已正确加载并用于创建套接字连接
  2. 证书与启用的密码套件兼容
  3. 客户端和服务器中至少应启用一个 通用 密码套件,并且该密码套件还应与证书兼容

例如,在以下示例中,我将Java
8与默认的密码套件一起使用。我生成的证书使用的是ECDSA和SHA384,因此在服务器和客户端之间建立TLS连接时,我可以TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384通过启用debug(System.setProperty("javax.net.debug", "ssl");)来查看协商的密码套件。

以下是一个工作示例:

第一步,需要创建密钥对和证书。为了进行测试,让我们创建一个自签名证书,并为服务器和客户端使用相同的证书:

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

现在创建服务器:

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();
        }
      }
    }
  }
}

现在创建客户端:

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();
    }
  }
}

最后,这是一个测试连接的JUnit测试:

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;
    }
  }
}

注意:证书(在此示例中为servercert.p12)应位于类路径中。 在此示例中,我将其保留在Maven文件夹结构的test /
resources文件夹中,以便JUnit测试可以在类路径中获取它。

密码套件背景

使用TLS /
SSL时,要使用的加密算法由密码套件确定。服务器支持一组密码套件(您可以根据需要和所需的安全级别启用或禁用某些套件)。客户端还支持一组密码套件。在建立连接期间,将在客户端和服务器之间协商要使用的密码套件。鉴于服务器支持该特定密码套件,将优先考虑客户端的使用偏好。

您可以在此处找到Sun
Providers直至Java 8支持的密码套件的列表。

典型的密码套件名称如下所示:

TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256

这里,

ECDHE 代表“椭圆曲线Diffie
Hellman短暂”。这是一个密钥交换算法。椭圆变体(第一个E)用于表演,而临时变体(最后一个E)用于向前保密。前向保密性意味着,如果攻击者继续通过TLS记录所有通信,并且在以后的某个时间以某种方式获得了私钥,则他/她将无法解密过去记录的通信。

ECDSA
是一种用于对密钥进行签名的数字签名算法,用于对共享密钥进行身份验证(验证完整性)。ECDSA比其他认证算法(例如HMAC)更弱和更慢。但是它用于共享密钥身份验证,因为它不需要验证者知道用于创建身份验证标签的秘密密钥。服务器可以很好地使用其私钥来验证消息的完整性。

AES_128_GCM-
双方(通常是浏览器和Web服务器)之间共享一个公共密钥后,将使用对称块密码算法来加密双方之间的消息交换。在这种特定情况下,将使用具有128位密钥和GCM身份验证模式的分组密码AES。

SHA256 -PRF的散列算法



 类似资料:
  • 我希望尽可能多的编程。我也可以使用自签名证书。 我所学的一个教程建议使用java应用程序创建一个证书,然后将该文件移到java项目中。我已经使用终端命令完成了这项工作。我将密码指定为。 然后,我调用函数,希望SSLSockets能够工作,但它仍然不能工作。

  • 问题内容: 我正在尝试使用安全套接字层(HTTPS)与Java中的PHP脚本建立连接,但是我发现为了确保最大的安全性/有效性,我必须将网站使用的SSL证书导入到我的应用程序中…我不知道该怎么做。 如果有帮助,我的SSL证书不是自签名的,而是由StartSSL提供的,并且我正在使用Eclipse IDE。 有人能指出我正确的方向吗?即,我需要什么文件,应该在哪里导入它们,以及在Java中需要什么代码

  • 问题内容: 在我们的iOS项目中,我们将用于生成AdHoc和AppStore构建的签名证书和供应配置文件都提交给版本控制存储库。这样,每当新开发人员下载该应用程序的新副本时,他就拥有了为测试人员创建AdHoc构建所需的一切。 我们正在使用Jenkins进行持续集成,我希望有一个脚本可以对提交的文件进行完整性检查。特别是,我想检查提交的配置文件是否确实是使用在存储库中提交的签名证书生成的。 有谁知道

  • 我有一个DER格式的证书文件和一个DER格式的密钥文件,密钥是加密的。 我想创建一个包含PEM格式的cert和加密密钥的cer文件,如下所示: 我可以使用以下命令获得PEM格式的证书: 我还可以使用以下命令获取PEM格式的未加密密钥: 这将创建一个以以下开头的文件: -----开始RSA私钥----- 然而,我找不到一个方法让它开始 开始加密私钥 这样我就可以连接两个文件并获得证书。 你知道我能做

  • 问题内容: 我试图分几个步骤处理X509证书,并遇到了两个问题。我是JCE的新手,所以我还没有完全了解最新信息。 我们希望能够基于不同的编码(PEM,DER和PCKS7)解析几个不同的X509证书。我已经使用FireFox 从https://belgium.be以PEM和PCKS7格式导出了相同的证书(证书包括链)。我已经省略了几行不需要的问题 只要我使用FileInputStream而不是PCK