Java 12)でSSLベースの(非HTTP)サーバーを開発しているときに、サーバーとクライアントが一緒に通信するために予期しない困難に遭遇しました。接続は常にhandshake_failureエラーで中断されました。努力の結果(最初は自分の証明書を無差別にしていました)、ソケットレベルでのHelloWorld SSLクライアント/サーバーペアのおかげで問題を特定することができました。ハンドシェイクの失敗は、デフォルトで有効になっているプロトコル間のTLSv1.3の存在が原因でしたJDKで。
これまでのところ、ハンドシェイクエラーを回避するための私の解決策は、TLSv1.3を完全に無効にすること(サーバー側)ですが、この解決策には満足していません。何が起こっているのかを理解したいと思います(JDKまたはシステムのバグではなく、アプリケーションまたは環境のバグであると想定しています)。
以下にいくつかのコードを示します。誰かが私が間違っていることを確認して教えてもらえますか?よろしくお願いします。
これが私の設定です:OSX 10.14(Mojave)以下のバージョンのJavaでテストしました:
編集:テストにJava 11を追加すると、このバージョンでも問題が発生します
// HelloSSLServer.Java
import Java.net.*;
import javax.net.*;
import javax.net.ssl.*;
import Java.io.*;
public class HelloSSLServer {
public static void main(String args[]) throws Exception {
int port = 1234;
boolean needClientAuth = false;
ServerSocketFactory factory = SSLServerSocketFactory.getDefault();
try (ServerSocket ss = factory.createServerSocket(port)) {
SSLServerSocket ssl = (SSLServerSocket) ss;
ssl.setNeedClientAuth(needClientAuth);
ssl.setEnabledProtocols(new String[] { "TLSv1.2" }); // fails with TLSv1.3 (or with the default, i.e. no call)
while (true) {
try (Socket socket = ss.accept()) {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello World!");
}
}
}
}
}
// HelloSSLClient.Java
import Java.net.*;
import javax.net.*;
import javax.net.ssl.*;
import Java.io.*;
public class HelloSSLClient {
public static void main(String args[]) throws Exception {
String Host = "localhost";
int port = 1234;
SocketFactory factory = SSLSocketFactory.getDefault();
try (Socket connection = factory.createSocket(Host, port)) {
SSLSocket ssl = (SSLSocket) connection;
SSLParameters sslParams = new SSLParameters();
sslParams.setEndpointIdentificationAlgorithm("HTTPS");
ssl.setSSLParameters(sslParams);
BufferedReader input =
new BufferedReader(new InputStreamReader(connection.getInputStream()));
String message = input.readLine();
System.out.println("Got the message: " + message);
}
}
}
(サーバーの証明書は、SSLアプリケーションをテストするために、サーバーのキーストアとクライアントのトラストストアにも必要です)
結果は次のとおりです(エラーが発生した場合)。
$ Java -Djavax.net.ssl.keyStore=serverkeystore.ks -Djavax.net.ssl.keyStorePassword=xxxxxxx HelloSSLServer
$ Java -Djavax.net.ssl.trustStore=clienttruststore.ks -Djavax.net.ssl.trustStorePassword=xxxxxx HelloSSLClient
Exception in thread "main" javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
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:307)
at Java.base/Sun.security.ssl.Alert$AlertConsumer.consume(Alert.Java:285)
at Java.base/Sun.security.ssl.TransportContext.dispatch(TransportContext.Java:180)
at Java.base/Sun.security.ssl.SSLTransport.decode(SSLTransport.Java:164)
at Java.base/Sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.Java:1180)
at Java.base/Sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.Java:1091)
at Java.base/Sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.Java:402)
at Java.base/Sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.Java:721)
at Java.base/Sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.Java:804)
at Java.base/Sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.Java:284)
at Java.base/Sun.nio.cs.StreamDecoder.implRead(StreamDecoder.Java:326)
at Java.base/Sun.nio.cs.StreamDecoder.read(StreamDecoder.Java:178)
at Java.base/Java.io.InputStreamReader.read(InputStreamReader.Java:185)
at Java.base/Java.io.BufferedReader.fill(BufferedReader.Java:161)
at Java.base/Java.io.BufferedReader.readLine(BufferedReader.Java:326)
at Java.base/Java.io.BufferedReader.readLine(BufferedReader.Java:392)
at HelloSSLClient.main(HelloSSLClient.Java:21)
@ SvetlinZarevと@ user207421で提案されているように、-Djavax.net.debug = ssl:handshakeを有効にした出力の一部を次に示します。このコードはTLSv1.2プロトコルを強制したときに機能するので、デフォルトを使用するだけで何が起こっているのかを確認することはより興味深いです(ssl.setEnabledProtocolsへの呼び出しはコメント化されています):
注:プロジェクトは個人的で実験的であり、サーバーの証明書は偽造され、自己署名され、クライアントのトラストストアに追加され(キーストアとトラストストアもダミーです)、とにかく公開されるため、私はしません機密情報を公開することへの恐怖
クライアント側から:
Java -Djavax.net.debug=ssl:handshake -Djavax.net.ssl.trustStore=clienttruststore.ks -Djavax.net.ssl.trustStorePassword=xxxxxxxxx HelloSSLClient
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.229 CEST|SSLCipher.Java:463|jdk.tls.keyLimits: entry = AES/GCM/NoPadding KeyUpdate 2^37. AES/GCM/NOPADDING:KEYUPDATE = 137438953472
javax.net.ssl|WARNING|01|main|2019-08-22 09:54:22.451 CEST|ServerNameExtension.Java:261|Unable to indicate server name
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.451 CEST|SSLExtensions.Java:257|Ignore, context unavailable extension: server_name
javax.net.ssl|WARNING|01|main|2019-08-22 09:54:22.455 CEST|SignatureScheme.Java:282|Signature algorithm, ed25519, is not supported by the underlying providers
javax.net.ssl|WARNING|01|main|2019-08-22 09:54:22.455 CEST|SignatureScheme.Java:282|Signature algorithm, ed448, is not supported by the underlying providers
javax.net.ssl|INFO|01|main|2019-08-22 09:54:22.459 CEST|AlpnExtension.Java:161|No available application protocols
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.460 CEST|SSLExtensions.Java:257|Ignore, context unavailable extension: application_layer_protocol_negotiation
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.460 CEST|SSLExtensions.Java:257|Ignore, context unavailable extension: cookie
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.477 CEST|SSLExtensions.Java:257|Ignore, context unavailable extension: renegotiation_info
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.477 CEST|PreSharedKeyExtension.Java:633|No session to resume.
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.478 CEST|SSLExtensions.Java:257|Ignore, context unavailable extension: pre_shared_key
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.482 CEST|ClientHello.Java:653|Produced ClientHello handshake message (
"ClientHello": {
"client version" : "TLSv1.2",
"random" : "8D 95 FA BE D8 F4 BC AC E6 15 36 FE FE A2 57 C4 DD EF F6 53 B8 54 1D 4A ED AE C6 0A CD 92 E0 A4",
"session id" : "CC EE 0F 29 F8 9A 3B 72 61 61 99 46 AA 69 CF 23 4F E9 05 13 2A 52 B8 1D 34 18 FA DF 26 1B 46 87",
"cipher suites" : "[TLS_AES_128_GCM_SHA256(0x1301), TLS_AES_256_GCM_SHA384(0x1302), TLS_CHACHA20_POLY1305_SHA256(0x1303), TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B), TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(0xCCA9), TLS_DHE_DSS_WITH_AES_128_CBC_SHA256(0x0040),
[...]
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA(0xC00E), TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x0033), TLS_DHE_DSS_WITH_AES_128_CBC_SHA(0x0032), TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00FF)]",
[...]
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.528 CEST|ServerHello.Java:871|Consuming ServerHello handshake message (
"ServerHello": {
"server version" : "TLSv1.2",
"random" : "B5 27 FE 28 29 85 AC 1A C4 62 57 28 45 12 63 BA 4D CC 4B E0 02 A4 A9 7A ED 9F A3 8D A6 98 85 BE",
"session id" : "CC EE 0F 29 F8 9A 3B 72 61 61 99 46 AA 69 CF 23 4F E9 05 13 2A 52 B8 1D 34 18 FA DF 26 1B 46 87",
"cipher suite" : "TLS_AES_128_GCM_SHA256(0x1301)",
"compression methods" : "00",
"extensions" : [
"supported_versions (43)": {
"selected version": [TLSv1.3]
},
"key_share (51)": {
"server_share": {
[...]
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.528 CEST|SSLExtensions.Java:189|Consumed extension: supported_versions
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.529 CEST|ServerHello.Java:967|Negotiated protocol version: TLSv1.3
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.530 CEST|SSLExtensions.Java:160|Ignore unsupported extension: server_name
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.530 CEST|SSLExtensions.Java:160|Ignore unsupported extension: max_fragment_length
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.530 CEST|SSLExtensions.Java:160|Ignore unsupported extension: status_request
[...]
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.582 CEST|SSLExtensions.Java:170|Ignore unavailable extension: server_name
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.582 CEST|SSLExtensions.Java:170|Ignore unavailable extension: max_fragment_length
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.583 CEST|SSLExtensions.Java:189|Consumed extension: supported_groups
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.583 CEST|SSLExtensions.Java:204|Ignore unavailable extension: server_name
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.583 CEST|SSLExtensions.Java:204|Ignore unavailable extension: max_fragment_length
javax.net.ssl|WARNING|01|main|2019-08-22 09:54:22.583 CEST|SSLExtensions.Java:212|Ignore impact of unsupported extension: supported_groups
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.583 CEST|SSLExtensions.Java:204|Ignore unavailable extension: application_layer_protocol_negotiation
javax.net.ssl|DEBUG|01|main|2019-08-22 09:54:22.585 CEST|Alert.Java:238|Received alert message (
"Alert": {
"level" : "fatal",
"description": "handshake_failure"
}
)
javax.net.ssl|ERROR|01|main|2019-08-22 09:54:22.586 CEST|TransportContext.Java:312|Fatal (HANDSHAKE_FAILURE): Received fatal alert: handshake_failure (
"throwable" : {
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at Java.base/Sun.security.ssl.Alert.createSSLException(Alert.Java:131)
そしてこれがサーバー側です:
Java -Djavax.net.debug=ssl,handshake -Djavax.net.ssl.keyStore=serverkeystore.ks -Djavax.net.ssl.keyStorePassword=xxxxxxxxx HelloSSLServer
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:55.869 CEST|SSLCipher.Java:463|jdk.tls.keyLimits: entry = AES/GCM/NoPadding KeyUpdate 2^37. AES/GCM/NOPADDING:KEYUPDATE = 137438953472
javax.net.ssl|WARNING|01|main|2019-08-22 12:23:59.697 CEST|SignatureScheme.Java:282|Signature algorithm, ed25519, is not supported by the underlying providers
javax.net.ssl|WARNING|01|main|2019-08-22 12:23:59.698 CEST|SignatureScheme.Java:282|Signature algorithm, ed448, is not supported by the underlying providers
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.704 CEST|ClientHello.Java:809|Consuming ClientHello handshake message (
"ClientHello": {
"client version" : "TLSv1.2",
"random" : "0D E6 53 8A B0 E4 E7 9A 80 93 49 84 AD 88 0A 5F D5 7F 29 37 C3 86 A7 28 A7 D9 C6 7D EB DF 6A 3D",
"session id" : "5B 9A 18 25 31 65 8C 8F E8 E6 93 DA F5 AA 50 45 A8 C9 20 D1 9D 67 35 9B 7B D3 46 D5 CA C0 FC 85",
"cipher suites" : "[TLS_AES_128_GCM_SHA256(0x1301), TLS_AES_256_GCM_SHA384(0x1302), TLS_CHACHA20_POLY1305_SHA256(0x1303), TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B),
[...]
TLS_DHE_DSS_WITH_AES_128_CBC_SHA(0x0032), TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00FF)]",
"compression methods" : "00",
"extensions" : [
"status_request (5)": {
"certificate status type": ocsp
"OCSP status request": {
"responder_id": <empty>
"request extensions": {
<empty>
}
}
},
"supported_groups (10)": {
"versions": [secp256r1, secp384r1, secp521r1, sect283k1, sect283r1, sect409k1, sect409r1, sect571k1, sect571r1, secp256k1, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
},
"ec_point_formats (11)": {
"formats": [uncompressed]
},
"signature_algorithms (13)": {
"signature schemes": [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha224, rsa_sha224, dsa_sha224, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]
},
"signature_algorithms_cert (50)": {
"signature schemes": [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha224, rsa_sha224, dsa_sha224, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]
},
[...]
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.704 CEST|SSLExtensions.Java:189|Consumed extension: supported_versions
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.705 CEST|ClientHello.Java:839|Negotiated protocol version: TLSv1.3
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.705 CEST|SSLExtensions.Java:189|Consumed extension: psk_key_exchange_modes
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.706 CEST|PreSharedKeyExtension.Java:805|Handling pre_shared_key absence.
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.706 CEST|SSLExtensions.Java:170|Ignore unavailable extension: server_name
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.706 CEST|SSLExtensions.Java:170|Ignore unavailable extension: max_fragment_length
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.706 CEST|SSLExtensions.Java:189|Consumed extension: status_request
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.707 CEST|SSLExtensions.Java:189|Consumed extension: supported_groups
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.707 CEST|SSLExtensions.Java:160|Ignore unsupported extension: ec_point_formats
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.707 CEST|SSLExtensions.Java:189|Consumed extension: signature_algorithms
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.707 CEST|SSLExtensions.Java:189|Consumed extension: signature_algorithms_cert
[...]
javax.net.ssl|DEBUG|01|main|2019-08-22 12:23:59.739 CEST|ServerHello.Java:576|Produced ServerHello handshake message (
"ServerHello": {
"server version" : "TLSv1.2",
"random" : "FD FD 39 0C 3A D8 F0 E8 38 F8 08 D8 19 94 7A FA 4A 68 71 F8 4C 32 EB 7A D0 53 96 E5 9F E8 0A 3B",
"session id" : "5B 9A 18 25 31 65 8C 8F E8 E6 93 DA F5 AA 50 45 A8 C9 20 D1 9D 67 35 9B 7B D3 46 D5 CA C0 FC 85",
"cipher suite" : "TLS_AES_128_GCM_SHA256(0x1301)",
"compression methods" : "00",
"extensions" : [
"supported_versions (43)": {
"selected version": [TLSv1.3]
},
"key_share (51)": {
"server_share": {
[...]
javax.net.ssl|ALL|01|main|2019-08-22 12:23:59.785 CEST|X509Authentication.Java:243|No X.509 cert selected for EC
javax.net.ssl|WARNING|01|main|2019-08-22 12:23:59.785 CEST|CertificateMessage.Java:1055|Unavailable authentication scheme: ecdsa_sha1
javax.net.ssl|ALL|01|main|2019-08-22 12:23:59.785 CEST|X509Authentication.Java:243|No X.509 cert selected for RSA
javax.net.ssl|WARNING|01|main|2019-08-22 12:23:59.785 CEST|CertificateMessage.Java:1055|Unavailable authentication scheme: rsa_pkcs1_sha1
javax.net.ssl|WARNING|01|main|2019-08-22 12:23:59.785 CEST|CertificateMessage.Java:1065|No available authentication scheme
javax.net.ssl|ERROR|01|main|2019-08-22 12:23:59.788 CEST|TransportContext.Java:312|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)
[...]
これが解決策です。証明書を生成するには、keytoolで-keyalgフラグが必要です。それ以外の場合、鍵は古いデフォルトDSAで暗号化され、TLS1.3ではもう許可されません。 RSAで動作します。
結論:
したがって、予期しない事態を避けるために、常に明示的な-keyalg RSA(またはサポートされているその他のTLS1.3アルゴリズム)を使用して証明書を生成することをお勧めします。