带有 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));
}
}
我想创建一个简单的 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));
}
}