带有 Java 11 和自签名证书的 TLS 1.3 服务器套接字

TLS 1.3 server socket with Java 11 and self-signed certificates

我想创建一个简单的 TLS 1.3 服务器套接字侦听器,它使用自签名证书。

首先我创建了一个 RSA public/私钥对:

openssl req -x509 -newkey rsa:2048 -sha256 -days 9125 -nodes -keyout test.key -out test.crt

我在一个简单的 Java 程序中添加了 public 和私钥作为字符串

public class TlsServer
{

    private static final String TEST_CRT = "-----BEGIN CERTIFICATE-----\n"
        // ...
        + "-----END CERTIFICATE-----";

    private static final String TEST_KEY
        = "-----BEGIN PRIVATE KEY-----\n"
        // ...
        + "-----END PRIVATE KEY-----";

    private static final int PORT = 32333;

    private static final String TLS_PROTOCOL = "TLSv1.3";

    private static final String[] CIPHER_SUITES = new String[]
    {
        "TLS_AES_128_GCM_SHA256",
        "TLS_AES_256_GCM_SHA384"
    };

    public static void main(String[] args) throws Exception
    {
        new TlsServer().start();
    }

    private void start() throws Exception
    {
        //System.setProperty("javax.net.debug", "all");

        SSLContext ctx = SSLContext.getInstance(TLS_PROTOCOL);

        // Init Key Store.
        char[] password = "changeit".toCharArray();
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(null, password);
        Certificate cert = convertCertificate(TEST_CRT);
        ks.setCertificateEntry("test_crt", cert);
        Key pk = new SecretKeySpec(TEST_KEY.getBytes(StandardCharsets.ISO_8859_1), "RSA");
        ks.setKeyEntry("test_key", pk, password, new Certificate[]
        {
            cert
        });

        // Init Key Manager Factory.
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, password);

        // Init Trust Manager Factory.
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);

        ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        SSLServerSocket serverSocket = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(PORT);
        serverSocket.setNeedClientAuth(false);
        serverSocket.setEnabledProtocols(new String[]
        {
            TLS_PROTOCOL
        });
        serverSocket.setEnabledCipherSuites(CIPHER_SUITES);

        System.out.printf("Server started on port %d%n", PORT);
        while (true)
        {
            try (SSLSocket socket = (SSLSocket) serverSocket.accept())
            {
                System.out.println("Accept new connection: " + socket.getRemoteSocketAddress());
                InputStream is = new BufferedInputStream(socket.getInputStream());
                OutputStream os = new BufferedOutputStream(socket.getOutputStream());
                byte[] data = new byte[2048];
                int len = is.read(data);
                if (len <= 0)
                {
                    throw new IOException("No data received");
                }
                System.out.printf("Server received %d bytes: %s%n", len, new String(data, 0, len));
                os.write(data, 0, len);
                os.flush();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    private Certificate convertCertificate(String cert) throws CertificateException
    {
        InputStream in = new ByteArrayInputStream(cert.getBytes(StandardCharsets.ISO_8859_1));
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        return certFactory.generateCertificate(in);
    }
}

Java版本

openjdk version "11.0.11" 2021-04-20
OpenJDK Runtime Environment AdoptOpenJDK-11.0.11+9 (build 11.0.11+9)
OpenJDK 64-Bit Server VM AdoptOpenJDK-11.0.11+9 (build 11.0.11+9, mixed mode)

服务器启动并侦听连接,但如果我尝试使用客户端(例如 curl、Chrome 或简单的 Java-Client)访问服务器,我会得到以下信息错误:

Server started on port 32333
Accept new connection: /0:0:0:0:0:0:0:1:49966
javax.net.ssl.SSLHandshakeException: No available authentication scheme
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:336)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:292)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:283)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateProducer.onProduceCertificate(CertificateMessage.java:972)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateProducer.produce(CertificateMessage.java:961)
    at java.base/sun.security.ssl.SSLHandshake.produce(SSLHandshake.java:436)
    at java.base/sun.security.ssl.ClientHello$T13ClientHelloConsumer.goServerHello(ClientHello.java:1234)
    at java.base/sun.security.ssl.ClientHello$T13ClientHelloConsumer.consume(ClientHello.java:1170)
    at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.onClientHello(ClientHello.java:852)
    at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.consume(ClientHello.java:813)
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:182)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:171)
    at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1418)
    at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1324)
    at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:440)
    at java.base/sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:829)
    at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:920)
    at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:252)
    at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:292)
    at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:351)
    at java.base/java.io.FilterInputStream.read(FilterInputStream.java:107)
    at TlsServer.start(TlsServer.java:143)
    at TlsServer.main(TlsServer.java:95)

调试输出如下所示:

javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.010 CEST|ServerHello.java:577|Produced ServerHello handshake message (
"ServerHello": {
  "server version"      : "TLSv1.2",
  "random"              : "78 B9 53 93 89 F9 8F 8C 2B 03 33 72 5D 21 4D A5 DC 42 59 CE 54 24 CD 75 9F 26 15 09 FB 53 91 00",
  "session id"          : "EC 09 75 BD 7F 00 40 CB 9F DC F4 BA FD 03 57 82 6C D0 6E 4C 2D FD 79 AA 07 91 86 A8 1C A8 99 38",
  "cipher suite"        : "TLS_AES_128_GCM_SHA256(0x1301)",
  "compression methods" : "00",
  "extensions"          : [
    "supported_versions (43)": {
      "selected version": [TLSv1.3]
    },
    "key_share (51)": {
      "server_share": {
        "named group": x25519
        "key_exchange": {
          0000: B3 C2 7D 1D 77 D1 CE 3F   1D C2 15 0A 3A 21 B5 3B  ....w..?....:!.;
          0010: ED D0 BB 79 D1 C0 88 46   98 71 DD 2D 62 40 E1 1E  ...y...F.q.-b@..
        }
      },
    }
  ]
}
)
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.010 CEST|SSLSocketOutputRecord.java:241|WRITE: TLS13 handshake, length = 122
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.011 CEST|SSLSocketOutputRecord.java:255|Raw write (
  0000: 16 03 03 00 7A 02 00 00   76 03 03 78 B9 53 93 89  ....z...v..x.S..
  0010: F9 8F 8C 2B 03 33 72 5D   21 4D A5 DC 42 59 CE 54  ...+.3r]!M..BY.T
  0020: 24 CD 75 9F 26 15 09 FB   53 91 00 20 EC 09 75 BD  $.u.&...S.. ..u.
  0030: 7F 00 40 CB 9F DC F4 BA   FD 03 57 82 6C D0 6E 4C  ..@.......W.l.nL
  0040: 2D FD 79 AA 07 91 86 A8   1C A8 99 38 13 01 00 00  -.y........8....
  0050: 2E 00 2B 00 02 03 04 00   33 00 24 00 1D 00 20 B3  ..+.....3.$... .
  0060: C2 7D 1D 77 D1 CE 3F 1D   C2 15 0A 3A 21 B5 3B ED  ...w..?....:!.;.
  0070: D0 BB 79 D1 C0 88 46 98   71 DD 2D 62 40 E1 1E     ..y...F.q.-b@..
)
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.017 CEST|SSLCipher.java:1840|KeyLimit read side: algorithm = AES/GCM/NOPADDING:KEYUPDATE
countdown value = 137438953472
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.018 CEST|SSLCipher.java:1994|KeyLimit write side: algorithm = AES/GCM/NOPADDING:KEYUPDATE
countdown value = 137438953472
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.018 CEST|SSLSocketOutputRecord.java:225|Raw write (
  0000: 14 03 03 00 01 01                                  ......
)
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.019 CEST|ServerNameExtension.java:527|Ignore unavailable extension: server_name
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.019 CEST|SSLExtensions.java:260|Ignore, context unavailable extension: server_name
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.019 CEST|MaxFragExtension.java:469|Ignore unavailable max_fragment_length extension
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.019 CEST|SSLExtensions.java:260|Ignore, context unavailable extension: max_fragment_length
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.020 CEST|AlpnExtension.java:365|Ignore unavailable extension: application_layer_protocol_negotiation
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.020 CEST|SSLExtensions.java:260|Ignore, context unavailable extension: application_layer_protocol_negotiation
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.020 CEST|EncryptedExtensions.java:137|Produced EncryptedExtensions message (
"EncryptedExtensions": [
  "supported_groups (10)": {
    "versions": [x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
  }
]
)
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.020 CEST|SSLSocketOutputRecord.java:241|WRITE: TLS13 handshake, length = 32
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.028 CEST|SSLCipher.java:2036|Plaintext before ENCRYPTION (
  0000: 08 00 00 1C 00 1A 00 0A   00 16 00 14 00 1D 00 17  ................
  0010: 00 18 00 19 00 1E 01 00   01 01 01 02 01 03 01 04  ................
  0020: 16 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00  ................
  0030: 00                                                 .
)
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.028 CEST|SSLSocketOutputRecord.java:255|Raw write (
  0000: 17 03 03 00 41 7D 95 DC   2E 14 CB 2C B5 B3 D4 79  ....A......,...y
  0010: 67 4C D6 01 7E 7C EE 31   58 A3 63 33 E1 30 0E 3C  gL.....1X.c3.0.<
  0020: FB 73 DD 85 57 95 36 B5   93 17 73 3A E6 2E 6C A9  .s..W.6...s:..l.
  0030: A1 F0 49 15 93 28 39 A8   E3 ED D5 02 85 05 09 37  ..I..(9........7
  0040: 28 3F B3 52 62 94                                  (?.Rb.
)
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.028 CEST|X509Authentication.java:295|No X.509 cert selected for EC
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.029 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: ecdsa_secp256r1_sha256
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.029 CEST|X509Authentication.java:295|No X.509 cert selected for EC
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.029 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: ecdsa_secp384r1_sha384
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.029 CEST|X509Authentication.java:295|No X.509 cert selected for EC
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.029 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: ecdsa_secp521r1_sha512
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.029 CEST|X509Authentication.java:295|No X.509 cert selected for RSA
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.029 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: rsa_pss_rsae_sha256
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.029 CEST|X509Authentication.java:295|No X.509 cert selected for RSA
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.029 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: rsa_pss_rsae_sha384
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.029 CEST|X509Authentication.java:295|No X.509 cert selected for RSA
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.029 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: rsa_pss_rsae_sha512
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.029 CEST|X509Authentication.java:295|No X.509 cert selected for RSASSA-PSS
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.029 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: rsa_pss_pss_sha256
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.029 CEST|X509Authentication.java:295|No X.509 cert selected for RSASSA-PSS
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.029 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: rsa_pss_pss_sha384
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.030 CEST|X509Authentication.java:295|No X.509 cert selected for RSASSA-PSS
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.030 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: rsa_pss_pss_sha512
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.030 CEST|X509Authentication.java:295|No X.509 cert selected for RSA
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.030 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: rsa_pkcs1_sha256
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.030 CEST|X509Authentication.java:295|No X.509 cert selected for RSA
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.030 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: rsa_pkcs1_sha384
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.030 CEST|X509Authentication.java:295|No X.509 cert selected for RSA
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.030 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: rsa_pkcs1_sha512
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.030 CEST|X509Authentication.java:295|No X.509 cert selected for EC
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.030 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: ecdsa_sha1
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.030 CEST|X509Authentication.java:295|No X.509 cert selected for RSA
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.030 CEST|CertificateMessage.java:1083|Unavailable authentication scheme: rsa_pkcs1_sha1
javax.net.ssl|WARNING|01|main|2021-05-27 12:02:13.030 CEST|CertificateMessage.java:1093|No available authentication scheme
javax.net.ssl|ERROR|01|main|2021-05-27 12:02:13.031 CEST|TransportContext.java:341|Fatal (HANDSHAKE_FAILURE): No available authentication scheme (
"throwable" : {
  javax.net.ssl.SSLHandshakeException: No available authentication scheme
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:336)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:292)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:283)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateProducer.onProduceCertificate(CertificateMessage.java:972)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateProducer.produce(CertificateMessage.java:961)
    at java.base/sun.security.ssl.SSLHandshake.produce(SSLHandshake.java:436)
    at java.base/sun.security.ssl.ClientHello$T13ClientHelloConsumer.goServerHello(ClientHello.java:1234)
    at java.base/sun.security.ssl.ClientHello$T13ClientHelloConsumer.consume(ClientHello.java:1170)
    at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.onClientHello(ClientHello.java:852)
    at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.consume(ClientHello.java:813)
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:182)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:171)
    at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1418)
    at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1324)
    at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:440)
    at java.base/sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:829)
    at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:920)
    at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:252)
    at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:292)
    at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:351)
    at java.base/java.io.FilterInputStream.read(FilterInputStream.java:107)
    at TlsServer.start(TlsServer.java:143)
    at TlsServer.main(TlsServer.java:95)}

)
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.031 CEST|SSLSessionImpl.java:784|Invalidated session:  Session(1622109721099|SSL_NULL_WITH_NULL_NULL)
javax.net.ssl|ALL|01|main|2021-05-27 12:02:13.031 CEST|SSLSessionImpl.java:784|Invalidated session:  Session(1622109733001|TLS_AES_128_GCM_SHA256)
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.031 CEST|SSLSocketOutputRecord.java:71|WRITE: TLS13 alert(handshake_failure), length = 2
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.031 CEST|SSLCipher.java:2036|Plaintext before ENCRYPTION (
  0000: 02 28 15 00 00 00 00 00   00 00 00 00 00 00 00 00  .(..............
  0010: 00 00 00                                           ...
)
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.032 CEST|SSLSocketOutputRecord.java:85|Raw write (
  0000: 17 03 03 00 23 96 86 5D   95 12 6A 81 E7 77 F0 B5  ....#..]..j..w..
  0010: 45 7F F3 A4 98 D9 1B E6   FF C7 C1 BC 5F 1B B4 55  E..........._..U
  0020: DD 5A FE B9 B1 98 47 CF                            .Z....G.
)
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.032 CEST|SSLSocketImpl.java:1638|close the underlying socket
javax.net.ssl|DEBUG|01|main|2021-05-27 12:02:13.032 CEST|SSLSocketImpl.java:1657|close the SSL connection (initiative)
javax.net.ssl.SSLHandshakeException: No available authentication scheme
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:336)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:292)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:283)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateProducer.onProduceCertificate(CertificateMessage.java:972)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateProducer.produce(CertificateMessage.java:961)
    at java.base/sun.security.ssl.SSLHandshake.produce(SSLHandshake.java:436)
    at java.base/sun.security.ssl.ClientHello$T13ClientHelloConsumer.goServerHello(ClientHello.java:1234)
    at java.base/sun.security.ssl.ClientHello$T13ClientHelloConsumer.consume(ClientHello.java:1170)
    at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.onClientHello(ClientHello.java:852)
    at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.consume(ClientHello.java:813)
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:182)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:171)
    at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1418)
    at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1324)
    at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:440)
    at java.base/sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:829)
    at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:920)
    at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:252)
    at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:292)
    at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:351)
    at java.base/java.io.FilterInputStream.read(FilterInputStream.java:107)
    at TlsServer.start(TlsServer.java:143)
    at TlsServer.main(TlsServer.java:95)

有谁知道,如果我在配置中遗漏了什么?

这可能不是答案,但为了更好地阅读,我正在使用它。下面是我使用 1.3 版的 TLS 服务器示例代码(源代码来自 https://blog.gypsyengineer.com/en/security/an-example-of-tls-13-client-and-server-on-java.html)。

重要的是在服务器启动时有一个密钥库 一个可用的信任库并使用(我使用“System.setProperty”而不是绑定它们"-Djavax.net...").

也许这可以帮助您自己找到解决方案:-)

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;

/*
 * Don't forget to set the following system properties when you run the class:
 *
 *     javax.net.ssl.keyStore
 *     javax.net.ssl.keyStorePassword
 *     javax.net.ssl.trustStore
 *     javax.net.ssl.trustStorePassword
 *
 * More details can be found in JSSE docs.
 *
 * For example:
 *
 *     java -cp classes \
 *         -Djavax.net.ssl.keyStore=keystore \
 *         -Djavax.net.ssl.keyStorePassword=passphrase \
 *         -Djavax.net.ssl.trustStore=keystore \
 *         -Djavax.net.ssl.trustStorePassword=passphrase \
 *             com.gypsyengineer.tlsbunny.jsse.TLSv13Test
 *
 * For testing purposes, you can download the keystore file from
 *
 *     https://github.com/openjdk/jdk/tree/master/test/jdk/javax/net/ssl/etc
 */

public class TLSv13Test {
    // source: https://blog.gypsyengineer.com/en/security/an-example-of-tls-13-client-and-server-on-java.html
    private static final int delay = 1000; // in millis
    private static final String[] protocols = new String[] {"TLSv1.3"};
    private static final String[] cipher_suites = new String[] {"TLS_AES_128_GCM_SHA256"};
    private static final String message =
            "Like most of life's problems, this one can be solved with bending!";

    public static void main(String[] args) throws Exception {
        // system settings for keystores
        System.setProperty("javax.net.ssl.keyStore", "keystores\tls\keystore");
        System.setProperty("javax.net.ssl.keyStorePassword", "passphrase");
        System.setProperty("javax.net.ssl.trustStore", "keystores\tls\truststore");
        System.setProperty("javax.net.ssl.trustStorePassword", "passphrase");
        System.setProperty("javax.net.debug", "all");

        try (EchoServer server = EchoServer.create()) {
            new Thread(server).start();
            Thread.sleep(delay);

            try (SSLSocket socket = createSocket("localhost", server.port())) {
                InputStream is = new BufferedInputStream(socket.getInputStream());
                OutputStream os = new BufferedOutputStream(socket.getOutputStream());
                os.write(message.getBytes());
                os.flush();
                byte[] data = new byte[2048];
                int len = is.read(data);
                if (len <= 0) {
                    throw new IOException("no data received");
                }
                System.out.printf("client received %d bytes: %s%n",
                        len, new String(data, 0, len));
            }
        }
    }

    public static SSLSocket createSocket(String host, int port) throws IOException {
        SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault()
                .createSocket(host, port);
        socket.setEnabledProtocols(protocols);
        socket.setEnabledCipherSuites(cipher_suites);
        return socket;
    }

    public static class EchoServer implements Runnable, AutoCloseable {
        private static final int FREE_PORT = 0;
        private final SSLServerSocket sslServerSocket;
        private EchoServer(SSLServerSocket sslServerSocket) {
            this.sslServerSocket = sslServerSocket;
        }

        public int port() {
            return sslServerSocket.getLocalPort();
        }

        @Override
        public void close() throws IOException {
            if (sslServerSocket != null && !sslServerSocket.isClosed()) {
                sslServerSocket.close();
            }
        }

        @Override
        public void run() {
            System.out.printf("server started on port %d%n", port());

            try (SSLSocket socket = (SSLSocket) sslServerSocket.accept()) {
                System.out.println("accepted");
                InputStream is = new BufferedInputStream(socket.getInputStream());
                OutputStream os = new BufferedOutputStream(socket.getOutputStream());
                byte[] data = new byte[2048];
                int len = is.read(data);
                if (len <= 0) {
                    throw new IOException("no data received");
                }
                System.out.printf("server received %d bytes: %s%n",
                        len, new String(data, 0, len));
                os.write(data, 0, len);
                os.flush();
            } catch (Exception e) {
                System.out.printf("exception: %s%n", e.getMessage());
            }

            System.out.println("server stopped");
        }

        public static EchoServer create() throws IOException {
            return create(FREE_PORT);
        }

        public static EchoServer create(int port) throws IOException {
            SSLServerSocket socket = (SSLServerSocket)
                    SSLServerSocketFactory.getDefault().createServerSocket(port);
            socket.setEnabledProtocols(protocols);
            socket.setEnabledCipherSuites(cipher_suites);
            return new EchoServer(socket);
        }
    }
}

我正在使用第二个答案,因为代码太长,无法将其粘贴到第一个答案中。

是的,可以 运行 使用 临时 信任库和密钥库的 TLS 服务器。我无法判断它带来的任何安全问题 - 所以在使用此代码时要小心。

为了让您的代码 运行 开箱即用,我使用了 RSA 私钥和自签名证书 - 我去掉了“--- BEGIN ... END ---”并离开了只有(Base64 编码)部分才能得到 key/certificate。别担心,这是一个 示例密钥:

public static X509Certificate getcertificate() throws CertificateException {
    String certificate = "MIIDSzCCAjMCBFc5m4cwDQYJKoZIhvcNAQELBQAwajELMAkGA1UEBhMCVVMxCzAJ" +
            "BgNVBAgMAkNBMRIwEAYDVQQHDAlDdXBlcnRpbm8xDjAMBgNVBAoMBUR1bW15MQ4w" +
            "DAYDVQQLDAVEdW1teTEaMBgGA1UEAwwRZHVtbXkuZXhhbXBsZS5jb20wHhcNMTYw" +
            "NTE2MTAwNjM4WhcNMjYwNTE2MTAwNjM4WjBqMQswCQYDVQQGEwJVUzELMAkGA1UE" +
            "CAwCQ0ExEjAQBgNVBAcMCUN1cGVydGlubzEOMAwGA1UECgwFRHVtbXkxDjAMBgNV" +
            "BAsMBUR1bW15MRowGAYDVQQDDBFkdW1teS5leGFtcGxlLmNvbTCCASIwDQYJKoZI" +
            "hvcNAQEBBQADggEPADCCAQoCggEBAMkbQD5byG7xnyOWVzeIwbtHVPem/LJlXAHc" +
            "WJwz8pZ75rGBnZmAtgunXU++yPTZZOgYTZpM+qQhrFy2Z1Di/vVzhlVpcOZBw1RB" +
            "lI3s2uQYaWVtBtKtNELYWXEbhfxVJFBs5lLmsYJufVasMT+3XzBERuIEUV43JTKS" +
            "5ttwkoVgYRX6KDrIj0hYA7ZmOB/y5E1BG2mLsSkX0SoTCpuhgYwlWO4kvYoGu9vH" +
            "SfugUzQBqSiE1yN+Qfl3+76U23Pmbm/by9n3M3AoULxk2XnQmgBYtzViV0f1E3rr" +
            "/77XvIydWLHB0kotpND+ZN4IvTwpr3v7Iy6wbofg+Ounvwf7MKMCAwEAATANBgkq" +
            "hkiG9w0BAQsFAAOCAQEAWRWiibTf9UZAlXr+2ZmBMMMONpBOUaSbexLNs6MahBmj" +
            "cdEwOQ+l5xAdV4xVEDoDhu0m1JeDXy6SXo7mYkM2l9u/uz7tRStuEiM+tbCDaclb" +
            "W/H5QOdpiIx9uh86cEgBpk4xUO5vSaLI/9uSKgGkTNAFrjy3ME3wfc0cd5ntS8ca" +
            "8xobcmQ59SYZb9Gi0u/YN9yro9RwGDL8O1Bmp9+9rn5hor/oOVGHlbz2MN43moSZ" +
            "EiIfegW9OWiaWqIGqBOXvoEGp0exN+lhhmonI3zdYCB0jss2QYm3E1IWqZUkNOg3" +
            "yulIxph3FYvF+cA/vTVlohq5VB3DsCrnkDmqbU2mlw==";
    byte[] encodedCertificate = Base64.getDecoder().decode(certificate);
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(encodedCertificate));
}

// don't worry, it is a sample rsa private key
public static PrivateKey getPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
    String privateKeyPem = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDJG0A+W8hu8Z8j" +
            "llc3iMG7R1T3pvyyZVwB3FicM/KWe+axgZ2ZgLYLp11Pvsj02WToGE2aTPqkIaxc" +
            "tmdQ4v71c4ZVaXDmQcNUQZSN7NrkGGllbQbSrTRC2FlxG4X8VSRQbOZS5rGCbn1W" +
            "rDE/t18wREbiBFFeNyUykubbcJKFYGEV+ig6yI9IWAO2Zjgf8uRNQRtpi7EpF9Eq" +
            "EwqboYGMJVjuJL2KBrvbx0n7oFM0AakohNcjfkH5d/u+lNtz5m5v28vZ9zNwKFC8" +
            "ZNl50JoAWLc1YldH9RN66/++17yMnVixwdJKLaTQ/mTeCL08Ka97+yMusG6H4Pjr" +
            "p78H+zCjAgMBAAECggEAMh7yEHiUiB39AQQPoZ4aVoANK5m5IgcD+sy9YtTJkXq4" +
            "wKWirya2eEoShfTxJaDmtreT47BqySxBRmwJbM3eKDNOGAxq4GAke+PKT+LnnPB+" +
            "mBInoKsdOsmr5PYsmvpnTgoODzxColTCNS8+KPidJyzlE6Bq3RXWVffpxGgWhFnT" +
            "JM1MTpJJ/tKeZAUKOXbSU9T079gduPHTkrFeUnAt179ruSLov2Nbt8XhqDiY+pXm" +
            "EjMb+Q8JlBJ1F82i43BR6w6fjdSReGUNs8NDbmjpMasuk2U1/t28Idnt3M07jDUP" +
            "LGPrAeTZd/PLNpbflZMJoDT5SoCFycXNE3rHrsr8YQKBgQDveZzFcZctuYVd9iDd" +
            "SNLS6a2MuSwLrEPtUXRC7QR8BO2OkEvqMD5tVv3+a0ZPQpNg+4KzaKdzbsTNifpY" +
            "qjNzfydZ3CZzKlRVt/88icFxicWvI+fKiDdcGUt7agS4o9Ce+EkkqBySvVJB5Blz" +
            "vKLNyMQsJb2y6jRTekl/iO2XIQKBgQDW+9j128OmtPBERMw0RKxS5KZEYIhvZ+kw" +
            "lRe7D21o1d7MQx0zxKw58AWDW/epiVyno/FDuWmj2pQQ/oLpZl3ERg+hf3yUp48A" +
            "MziyuI3+INni/+uMWR3RN6sp4IbebXb6itGAIOm7ljgYwFV9crpLd7GZz3wi3/iZ" +
            "+zOVLl9DQwKBgQDXbYOGayUg0SAU4vG1n2loqyagzYO+DH4e44O/IRFDr/s0oMJq" +
            "LnQ6UGO1mDNr4exK9nchhif9Q8xvSoyXbqVSZTS1NcKxH4c2hYtqnlITHWlkoNxH" +
            "6jpC885fe4Q7xcJK//hsrX7m0sFI3TW4VB3xGYbAYENCzEW+QugTfs6dgQKBgQCi" +
            "nB5QYOkNWID/8lXPFz6M+Jv2zlmEgsF0WOF5QUMNb++06vLUrGdk73MMF+0tlFO8" +
            "DZo5Eq6gHH2wmQImTqKQCjpaepadzluw2A2DyWrFlM2aEN926hVOod/arhT1ezDq" +
            "c0PhuYNxuz81IY3IdJYK7T8tyy3nJyfgOIycw1WVBwKBgQDQ+iZpSMp1wIqnIeNb" +
            "zdlaVYXBu7d0tBlG/JGnPRlNVJkVpKd4woWtMwkqMGuAmmSkdjzhFYca9SWgzV1R" +
            "gzQzf1GuClF8KO7drdyvKGNqDFhyuCgE9mXI34ovjNcva9u+aZGHxuEYlCTKA0cV" +
            "UT/0leMkegvP/kFKOvzfYRBNFg==";
    byte[] encodedPrivateKey = Base64.getDecoder().decode(privateKeyPem);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
    return (PrivateKey) keyFactory.generatePrivate(pkcs8EncodedKeySpec);
}

现在是代码 - 将其粘贴到“//System.setProperty("javax.net.debug", "all"); 之间和“SSLServerSocket serverSocket = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(PORT);”并删除之间的代码行。粘贴此代码行:

    //Create a temp truststore with the server certificate
    KeyStore ksTemp = KeyStore.getInstance("JKS");
    ksTemp.load(null, null); //Initialize it
    ksTemp.setCertificateEntry("Alias", getcertificate());
    ByteArrayOutputStream bOut = new ByteArrayOutputStream();
    // save the temp keystore
    ksTemp.store(bOut, "passphrase".toCharArray());
    //Now create the keystore to be used by jsse
    KeyStore keyStoreTs = KeyStore.getInstance("JKS");
    keyStoreTs.load(new ByteArrayInputStream(bOut.toByteArray()), "passphrase".toCharArray());

    // now lets do the same with the keystore
    KeyStore ksTemp2 = KeyStore.getInstance("JKS");
    ksTemp2.load(null, null); //Initialize it
    ksTemp2.setCertificateEntry("Alias", getcertificate());
    ByteArrayOutputStream bOut2 = new ByteArrayOutputStream();
    // save the temp keystore
    ksTemp2.store(bOut2, "passphrase".toCharArray());
    //Now create the keystore to be used by jsse
    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(new ByteArrayInputStream(bOut2.toByteArray()), "passphrase".toCharArray());
    X509Certificate[] chain = new X509Certificate[1];
    chain[0] = getcertificate();
    keyStore.setKeyEntry("privateCert", getPrivateKey(), "passphrase".toCharArray(), chain);

    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStoreTs);

    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, "passphrase".toCharArray());

    // create SSLContext to establish the secure connection
    SSLContext ctx = SSLContext.getInstance(TLS_PROTOCOL);
    ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

再次警告:这是粗略的代码 - 检查自己以确保安全!

运行 服务器并请求与 https://localhost:32333/ 的连接,服务器协议显示:

Server started on port 32333
Accept new connection: /127.0.0.1:58640
Server received 375 bytes: GET / HTTP/1.1
Host: localhost:32333
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0


Accept new connection: /127.0.0.1:58642
Server received 331 bytes: GET /favicon.ico HTTP/1.1
Host: localhost:32333
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: image/webp,*/*
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: https://localhost:32333/
Cache-Control: max-age=0

我的浏览器 (Firefox) 显示此数据:

GET / HTTP/1.1
Host: localhost:32333
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

嗯,这应该可以解决您的问题。周末愉快。

Meta:我不敢相信这不是骗子,但我找不到。

SSL/TLS 身份验证使用 public-key or asymmetric cryptography 非密钥或对称。而不是这个

    Key pk = new SecretKeySpec(TEST_KEY.getBytes(StandardCharsets.ISO_8859_1), "RSA");
    ks.setKeyEntry("test_key", pk, password, new Certificate[]
    {
        cert
    });

这样做

    byte[] der = Base64.getDecoder().decode(TEST_KEY.replaceAll("-----(BEGIN|END) PRIVATE KEY-----\r?\n","").replaceAll("\r?\n",""));
    // or make these changes already in the value coded as Michael Fehr did
    // or leave the internal breaks and use .getMimeDecoder() (but remove the BEGIN/END lines)
    PrivateKey pk = KeyFactory.getInstance("RSA") .generatePrivate(new PKCS8EncodedKeySpec(der));
    ks.setKeyEntry("test_key", pk, password, new Certificate[]{cert} );

然后像现在一样将其用于 KeyManagerFactory.init。您不需要将服务器自己的证书放在其 TrustManager 中,您可以只使用 null 作为 SSLContext.init 的第一个参数,特别是因为您不要求客户端身份验证。但是,连接到您的服务器的客户端需要将此证书添加到他们的信任库;对于 curl,您可以在命令行上使用 --cacert $pemfile,但其他客户端会有所不同并且可能更复杂。

PS:密钥和证书,以及KeyManager和TrustManager(如果有的话)对于1.3可以与早期协议相同,除了DSA不能在1.3中使用;只有版本和密码套件不同。然而,in 1.3 you have the option of using a cert restricted to RSA-PSS 而不是普通的 RSA,当从 OpenSSL 创建时,密钥文件也表明了这一点(尽管实际上不需要限制密钥)。

为了完整起见,添加到@Michael Fehr 的回答中,下面是与之配套的工作客户端部分。

但是,如果有人能列出 keytool 或 openssl 的步骤来重新创建链式证书,请回复我的 post,谢谢并享受! 这是我的第一个 post 感谢任何代表。

在 TlsClient.java 中输入:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

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

public class TlsClient {

private static final String[] CIPHER_SUITES = new String[] {
        "TLS_AES_128_GCM_SHA256",
        "TLS_AES_256_GCM_SHA384"
};
private static final int PORT = 32333;
private static final String TLS_PROTOCOL = "TLSv1.3";

public static X509Certificate getcertificate() throws CertificateException {
    String certificate = "MIIDSzCCAjMCBFc5m4cwDQYJKoZIhvcNAQELBQAwajELMAkGA1UEBhMCVVMxCzAJ" +
            "BgNVBAgMAkNBMRIwEAYDVQQHDAlDdXBlcnRpbm8xDjAMBgNVBAoMBUR1bW15MQ4w" +
            "DAYDVQQLDAVEdW1teTEaMBgGA1UEAwwRZHVtbXkuZXhhbXBsZS5jb20wHhcNMTYw" +
            "NTE2MTAwNjM4WhcNMjYwNTE2MTAwNjM4WjBqMQswCQYDVQQGEwJVUzELMAkGA1UE" +
            "CAwCQ0ExEjAQBgNVBAcMCUN1cGVydGlubzEOMAwGA1UECgwFRHVtbXkxDjAMBgNV" +
            "BAsMBUR1bW15MRowGAYDVQQDDBFkdW1teS5leGFtcGxlLmNvbTCCASIwDQYJKoZI" +
            "hvcNAQEBBQADggEPADCCAQoCggEBAMkbQD5byG7xnyOWVzeIwbtHVPem/LJlXAHc" +
            "WJwz8pZ75rGBnZmAtgunXU++yPTZZOgYTZpM+qQhrFy2Z1Di/vVzhlVpcOZBw1RB" +
            "lI3s2uQYaWVtBtKtNELYWXEbhfxVJFBs5lLmsYJufVasMT+3XzBERuIEUV43JTKS" +
            "5ttwkoVgYRX6KDrIj0hYA7ZmOB/y5E1BG2mLsSkX0SoTCpuhgYwlWO4kvYoGu9vH" +
            "SfugUzQBqSiE1yN+Qfl3+76U23Pmbm/by9n3M3AoULxk2XnQmgBYtzViV0f1E3rr" +
            "/77XvIydWLHB0kotpND+ZN4IvTwpr3v7Iy6wbofg+Ounvwf7MKMCAwEAATANBgkq" +
            "hkiG9w0BAQsFAAOCAQEAWRWiibTf9UZAlXr+2ZmBMMMONpBOUaSbexLNs6MahBmj" +
            "cdEwOQ+l5xAdV4xVEDoDhu0m1JeDXy6SXo7mYkM2l9u/uz7tRStuEiM+tbCDaclb" +
            "W/H5QOdpiIx9uh86cEgBpk4xUO5vSaLI/9uSKgGkTNAFrjy3ME3wfc0cd5ntS8ca" +
            "8xobcmQ59SYZb9Gi0u/YN9yro9RwGDL8O1Bmp9+9rn5hor/oOVGHlbz2MN43moSZ" +
            "EiIfegW9OWiaWqIGqBOXvoEGp0exN+lhhmonI3zdYCB0jss2QYm3E1IWqZUkNOg3" +
            "yulIxph3FYvF+cA/vTVlohq5VB3DsCrnkDmqbU2mlw==";
    byte[] encodedCertificate = Base64.getDecoder().decode(certificate);
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(encodedCertificate));
}

public static PrivateKey getPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
    String privateKeyPem = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDJG0A+W8hu8Z8j" +
            "llc3iMG7R1T3pvyyZVwB3FicM/KWe+axgZ2ZgLYLp11Pvsj02WToGE2aTPqkIaxc" +
            "tmdQ4v71c4ZVaXDmQcNUQZSN7NrkGGllbQbSrTRC2FlxG4X8VSRQbOZS5rGCbn1W" +
            "rDE/t18wREbiBFFeNyUykubbcJKFYGEV+ig6yI9IWAO2Zjgf8uRNQRtpi7EpF9Eq" +
            "EwqboYGMJVjuJL2KBrvbx0n7oFM0AakohNcjfkH5d/u+lNtz5m5v28vZ9zNwKFC8" +
            "ZNl50JoAWLc1YldH9RN66/++17yMnVixwdJKLaTQ/mTeCL08Ka97+yMusG6H4Pjr" +
            "p78H+zCjAgMBAAECggEAMh7yEHiUiB39AQQPoZ4aVoANK5m5IgcD+sy9YtTJkXq4" +
            "wKWirya2eEoShfTxJaDmtreT47BqySxBRmwJbM3eKDNOGAxq4GAke+PKT+LnnPB+" +
            "mBInoKsdOsmr5PYsmvpnTgoODzxColTCNS8+KPidJyzlE6Bq3RXWVffpxGgWhFnT" +
            "JM1MTpJJ/tKeZAUKOXbSU9T079gduPHTkrFeUnAt179ruSLov2Nbt8XhqDiY+pXm" +
            "EjMb+Q8JlBJ1F82i43BR6w6fjdSReGUNs8NDbmjpMasuk2U1/t28Idnt3M07jDUP" +
            "LGPrAeTZd/PLNpbflZMJoDT5SoCFycXNE3rHrsr8YQKBgQDveZzFcZctuYVd9iDd" +
            "SNLS6a2MuSwLrEPtUXRC7QR8BO2OkEvqMD5tVv3+a0ZPQpNg+4KzaKdzbsTNifpY" +
            "qjNzfydZ3CZzKlRVt/88icFxicWvI+fKiDdcGUt7agS4o9Ce+EkkqBySvVJB5Blz" +
            "vKLNyMQsJb2y6jRTekl/iO2XIQKBgQDW+9j128OmtPBERMw0RKxS5KZEYIhvZ+kw" +
            "lRe7D21o1d7MQx0zxKw58AWDW/epiVyno/FDuWmj2pQQ/oLpZl3ERg+hf3yUp48A" +
            "MziyuI3+INni/+uMWR3RN6sp4IbebXb6itGAIOm7ljgYwFV9crpLd7GZz3wi3/iZ" +
            "+zOVLl9DQwKBgQDXbYOGayUg0SAU4vG1n2loqyagzYO+DH4e44O/IRFDr/s0oMJq" +
            "LnQ6UGO1mDNr4exK9nchhif9Q8xvSoyXbqVSZTS1NcKxH4c2hYtqnlITHWlkoNxH" +
            "6jpC885fe4Q7xcJK//hsrX7m0sFI3TW4VB3xGYbAYENCzEW+QugTfs6dgQKBgQCi" +
            "nB5QYOkNWID/8lXPFz6M+Jv2zlmEgsF0WOF5QUMNb++06vLUrGdk73MMF+0tlFO8" +
            "DZo5Eq6gHH2wmQImTqKQCjpaepadzluw2A2DyWrFlM2aEN926hVOod/arhT1ezDq" +
            "c0PhuYNxuz81IY3IdJYK7T8tyy3nJyfgOIycw1WVBwKBgQDQ+iZpSMp1wIqnIeNb" +
            "zdlaVYXBu7d0tBlG/JGnPRlNVJkVpKd4woWtMwkqMGuAmmSkdjzhFYca9SWgzV1R" +
            "gzQzf1GuClF8KO7drdyvKGNqDFhyuCgE9mXI34ovjNcva9u+aZGHxuEYlCTKA0cV" +
            "UT/0leMkegvP/kFKOvzfYRBNFg==";
    byte[] encodedPrivateKey = Base64.getDecoder().decode(privateKeyPem);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
    return (PrivateKey) keyFactory.generatePrivate(pkcs8EncodedKeySpec);
}

public static void main(String[] args) throws Exception {
    new TlsClient().start();
}

private void start() throws Exception {
    System.setProperty("javax.net.debug", "all");

    // String ksPw = "canEdit";

    // Create a temp truststore with the server certificate
    KeyStore ksTemp = KeyStore.getInstance("JKS");
    ksTemp.load(null, null); // Initialize it
    ksTemp.setCertificateEntry("Alias", getcertificate());
    ByteArrayOutputStream bOut = new ByteArrayOutputStream();
    // save the temp keystore
    ksTemp.store(bOut, "passphrase".toCharArray());
    // Now create the keystore to be used by jsse
    KeyStore keyStoreTs = KeyStore.getInstance("JKS");
    keyStoreTs.load(new ByteArrayInputStream(bOut.toByteArray()), "passphrase".toCharArray());

    // now lets do the same with the keystore
    KeyStore ksTemp2 = KeyStore.getInstance("JKS");
    ksTemp2.load(null, null); // Initialize it
    ksTemp2.setCertificateEntry("Alias", getcertificate());
    ByteArrayOutputStream bOut2 = new ByteArrayOutputStream();
    // save the temp keystore
    ksTemp2.store(bOut2, "passphrase".toCharArray());
    // Now create the keystore to be used by jsse
    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(new ByteArrayInputStream(bOut2.toByteArray()), "passphrase".toCharArray());
    X509Certificate[] chain = new X509Certificate[1];
    chain[0] = getcertificate();
    keyStore.setKeyEntry("privateCert", getPrivateKey(), "passphrase".toCharArray(), chain);

    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStoreTs);

    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, "passphrase".toCharArray());

    // create SSLContext to establish the secure connection
    SSLContext ctx = SSLContext.getInstance(TLS_PROTOCOL);
    ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

    // Socket connection.
    SSLSocket socket = (SSLSocket) ctx.getSocketFactory().createSocket("localhost", PORT);
    socket.setEnabledCipherSuites(CIPHER_SUITES);
    socket.setEnabledProtocols(new String[] {
            TLS_PROTOCOL });

    System.out.println("Connected: " + socket.isConnected());
    Thread.sleep(1000);
    InputStream is = new BufferedInputStream(socket.getInputStream());
    OutputStream os = new BufferedOutputStream(socket.getOutputStream());
    os.write("Hello World".getBytes());
    os.flush();
    byte[] data = new byte[2048];
    int len = is.read(data);
    if (len <= 0) {
        throw new IOException("No data received.");
    }
        System.out.printf("Client received %d bytes: %s%n", len, new String(data, 0, len));
    }
}