如何强制 java SSLContext 使用 TLSv1.1

Posted

技术标签:

【中文标题】如何强制 java SSLContext 使用 TLSv1.1【英文标题】:How to force java SSLContext to use TLSv1.1 【发布时间】:2021-11-22 07:30:50 【问题描述】:

如何让 jersey 客户端使用 TLSv1.1? 我正在尝试强制 com.sun.jersey.client.urlconnection.HTTPSProperties (球衣客户端代码最终进入此类)使用 TLSv1.1 (或 TLSv1)编写确认的测试代码服务器的协议。 在设置客户端时,我会执行以下操作:

SSLContext context = SSLContext.getInstance("TLSv1.1");
context.init(null, trustAll, new SecureRandom());
HTTPSProperties props = new HTTPSProperties(hv, context);
clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, props);

在 HTTPSProperties 内部(我下载了 1.x 源,所以这可能是错误的)我看到了:

public void setConnection(HttpsURLConnection connection) 
    if (hostnameVerifier != null)
        connection.setHostnameVerifier(hostnameVerifier);
    connection.setSSLSocketFactory(sslContext.getSocketFactory());

如果我改用SSLContext.getInstance("TLS");,代码可以正常工作。我试过添加System.setProperty("https.protocols", "TLSv1.1");,但还是不行。上面的代码给出了堆栈跟踪:

Caused by: javax.net.ssl.SSLException: Received fatal alert: protocol_version
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
        at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2020)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1127)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1580)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1508)
        at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:352)
        at com.sun.jersey.client.urlconnection.URLConnectionClientHandler._invoke(URLConnectionClientHandler.java:240)
        at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:147)
        ... 22 more

我的服务器(Jetty 9.4.41)认为它允许 TLSV1.1。当我转储 sslFactory 时,我得到:

I. 2021-09-29 16:37:59. [main] SSL context: Server@239105a8[provider=null,keyStore=file:***,trustStore=null] -
STOPPED
    +> trustAll=false
    +> Protocol Selections
    |  +> Enabled size=3
    |  |  +> TLSv1.1
    |  |  +> TLSv1.2
    |  |  +> TLSv1.3
    |  +> Disabled size=3
    |     +> SSLv2Hello - ConfigExcluded:'SSLv2Hello', ConfigIncluded:NotSelected
    |     +> SSLv3 - ConfigExcluded:'SSLv3', ConfigIncluded:NotSelected JVM:disabled
    |     +> TLSv1 - ConfigExcluded:'TLSv1', ConfigIncluded:NotSelected JVM:disabled
    +> Cipher Suite Selections
       +> Enabled size=39
       |  +> TLS_AES_128_GCM_SHA256
       |  +> TLS_AES_256_GCM_SHA384
       |  +> TLS_DHE_DSS_WITH_AES_128_CBC_SHA
       |  +> TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
       |  +> TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
       |  +> TLS_DHE_DSS_WITH_AES_256_CBC_SHA
       |  +> TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
       |  +> TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
       |  +> TLS_DHE_RSA_WITH_AES_128_CBC_SHA
       |  +> TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
       |  +> TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
       |  +> TLS_DHE_RSA_WITH_AES_256_CBC_SHA
       |  +> TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
       |  +> TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
       |  +> TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
       |  +> TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
       |  +> TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
       |  +> TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
       |  +> TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
       |  +> TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
       |  +> TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
       |  +> TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
       |  +> TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
       |  +> TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
       |  +> TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
       |  +> TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
       |  +> TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
       |  +> TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
       |  +> TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
       |  +> TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
       |  +> TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
       |  +> TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
       |  +> TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
       |  +> TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
       |  +> TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
       |  +> TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
       |  +> TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
       |  +> TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
       |  +> TLS_EMPTY_RENEGOTIATION_INFO_SCSV
       +> Disabled size=6
          +> TLS_RSA_WITH_AES_128_CBC_SHA - ConfigExcluded:'^TLS_RSA_.*$'
          +> TLS_RSA_WITH_AES_128_CBC_SHA256 - ConfigExcluded:'^TLS_RSA_.*$'
          +> TLS_RSA_WITH_AES_128_GCM_SHA256 - ConfigExcluded:'^TLS_RSA_.*$'
          +> TLS_RSA_WITH_AES_256_CBC_SHA - ConfigExcluded:'^TLS_RSA_.*$'
          +> TLS_RSA_WITH_AES_256_CBC_SHA256 - ConfigExcluded:'^TLS_RSA_.*$'
          +> TLS_RSA_WITH_AES_256_GCM_SHA384 - ConfigExcluded:'^TLS_RSA_.*$'

感谢任何帮助!提前致谢。

【问题讨论】:

我转储了 SSLContext:协议:TLSv1.1 提供者:SunJSSE 1.8 版支持的SSLParameters:Protocols="SSLv2Hello","SSLv3","TLSv1","TLSv1.1","TLSv1.2 " 现在找出服务器不响应 TLS 1.1 的原因。 【参考方案1】:

根据Jersey 3 official documentation,您可以使用SslConfigurator 并构建 SSL 上下文,如下所示:

SslConfigurator sslConfig = SslConfigurator.newInstance()
    //...
    .securityProtocol("TLSv1.1");

 SSLContext sslContext = sslConfig.createSSLContext();

列出了 SSLContext 支持的安全协议here。

如果问题仍然存在,您应该遵循标准diagnostics。请特别注意-Djdk.tls.client.protocols=TLSv1.1,TLSv1.2,它控制底层平台 TLS 实现,如here 所述。

最后,您可以诉诸 jdk.tls.disabledAlgorithms 并使用 -Djdk.tls.disabledAlgorithms=SSLv3,TLSv1 明确禁用 TLSv1。但是请注意,这将影响 所有 TLS 连接。

【讨论】:

不幸的是,我使用的是建立在 com.sun.jersey.client 之上的旧库,因此没有 SslConfigurator 类。【参考方案2】:

我将问题简化为如何强制客户端使用 TLSv1.1

我放弃了使用 jersey 客户端,只使用了 HttpsURLConnection。事实证明,使用 System.setProperties 并不理想,但 Tasos P. 的诊断链接给了我 SSL Labs 的 ViewMyClient。

下面的代码导致报告 SSL 3、TLS 1.0 和 1.1 可用,但 TLS 1.2、1.3 和 SSL 2 不可用。

// Using these breaks things!
//System.setProperty( "https.protocols", "TLSv1,TLSv1.1" );
//System.setProperty( "jdk.tls.disabledAlgorithms", "SSLv2Hello,SSLv3,TLSv1" );
String requestedProtocol = "TLSv1.1";
testOutput.reportHeader( "SSLLabs " + requestedProtocol );

SSLContext cx = SSLContext.getInstance( requestedProtocol );
cx.init( null, trustAll, new SecureRandom() );
testOutput.report( "Protocol: " + cx.getProtocol() );
testOutput.report( "Provider: " + cx.getProvider().toString() );
StringBuilder sb = new StringBuilder();
javax.net.ssl.SSLParameters p = cx.getSupportedSSLParameters();
sb.append( "Protocols=" );
for( String s : p.getProtocols() ) 
    sb.append( s ).append( ',' );

sb.append( '' );
testOutput.report( "SupportedSSLParameters: " + sb.toString() );
testOutput.report( "" );

int read;
byte[] bytes = new byte[1024];
final URL url = new URL(
    "https://clienttest.ssllabs.com:8443/ssltest/viewMyClient.html" );
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory( cx.getSocketFactory() );
conn.setHostnameVerifier( hv );
try 
    testOutput.report( "Response Message: " + conn.getResponseMessage() );
    InputStream in = conn.getInputStream();
    File file = new File( filename );
    FileOutputStream outputStream = new FileOutputStream( file );

    while( (read = in.read(bytes)) != -1 ) 
        outputStream.write( bytes, 0, read );
    
    outputStream.close();
    in.close();
    testOutput.report( "File " + filename + " written." );
 catch( Exception ex ) 
    testOutput.reportException( ex );

【讨论】:

以上是关于如何强制 java SSLContext 使用 TLSv1.1的主要内容,如果未能解决你的问题,请参考以下文章

httpsUrlConnection 如何设置的默认sslcontext和 hostnameverifier?

httpsUrlConnection 如何设置的默认sslcontext和 hostnameverifier?

是否可以构建使用SSL / TLS JAVA_OPTS作为其SSLContext的Java 11 HttpClient?

使用 SSLContext 创建 SocketChannel

Grpc Java:在服务器上设置 SSLContext

如何以编程方式设置 JAX-WS 客户端的 SSLContext?