SSLServerSocket和证书设置

SSLServerSocket和证书设置,第1张

SSLServerSocket和证书设置

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

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;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的散列算法



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4984434.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-14
下一篇 2022-11-14

发表评论

登录后才能评论

评论列表(0条)

保存