与客户端证书的SSL重新协商导致服务器缓冲区溢出(SSL Renegotiation with Cl

2019-07-18 03:08发布

我编写它使用客户端证书通过HTTPS连接到Apache Web服务器,并执行一个文件到服务器的HTTP PUT一个Java客户端应用程序。 它正常工作与小文件,但路数崩溃。

Apache服务器日志显示以下内容:

...
OpenSSL: Handshake: done
...
Changed client verification type will force renegotiation
...
filling buffer, max size 131072 bytes
...
request body exceeds maximum size (131072) for SSL buffer
could not buffer message body to allow SSL renegotiation to proceed
...    
OpenSSL: I/O error, 5 bytes expected to read on BIO
(104)Connection reset by peer: SSL input filter read failed.
(32)Broken pipe: core_output_filter: writing data to the network
Connection closed to child 20 with standard shutdown

客户端上的回应是:

java.io.IOException: Server returned HTTP response code: 401 for URL

我不熟悉这个过程,所以我不知道,如果再谈判是必要的还是在这里,如果有什么我可以做,以防止它。 或许我可以让客户端等待,直到重协商是发送应用程序数据之前完成? 下面是客户端代码的摘录(处理除去错误):

        URL url = new URL("my url goes here");
        con = (HttpsURLConnection) url.openConnection();
        con.setSSLSocketFactory(getMyCustomClientCertSocketFactory());
        con.setRequestMethod("PUT");
        con.setDoOutput(true);
        con.connect();
        writer = new OutputStreamWriter(con.getOutputStream());
        writer.write(xml);
        writer.close();

        parseServerResponse(con.getInputStream());

我想也许我需要使用较低级别的API一样的SSLSocket,并充分利用HandshakeCompletedListener任何?

我也想知道,如果在Apache SSLVerifyDepth指令有什么关系,为什么重新协商发生。 我和值2得到了指令,在每个目录上下文(只有一个上传目录)和Apache手册说,这一下吧:

在每个目录上下文它迫使与重新配置的客户端验证深度的SSL的重新协商的HTTP请求被读取但发送的HTTP响应之前之后。

由于这里要求是Java调试输出:

keyStore is : 
keyStore type is : jks
keyStore provider is : 
init keystore
init keymanager of type SunX509
trustStore is: C:\Program Files\Java\jdk1.6.0_35\jre\lib\security\cacerts
trustStore type is : jks
trustStore provider is : 
init truststore
adding as trusted cert:
 ...
trigger seeding of SecureRandom
done seeding SecureRandom
***
found key for : key-alias
chain [0] = [
[
...
]
***
trigger seeding of SecureRandom
done seeding SecureRandom
Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
%% No cached client session
*** ClientHello, TLSv1
RandomCookie:  ...
Session ID:  {}
Cipher Suites: [SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }
***
main, WRITE: TLSv1 Handshake, length = 75
main, WRITE: SSLv2 client hello message, length = 101
main, READ: TLSv1 Handshake, length = 81
*** ServerHello, TLSv1
RandomCookie:  ...
Session ID:  ...
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA
Compression Method: 0
Extension renegotiation_info, renegotiated_connection: <empty>
***
%% Created:  [Session-1, TLS_RSA_WITH_AES_128_CBC_SHA]
** TLS_RSA_WITH_AES_128_CBC_SHA
main, READ: TLSv1 Handshake, length = 4392
*** Certificate chain
chain [0] = [
[
...
Certificate Extensions: 8
[1]: ObjectId: 1.3.6.1.5.5.7.1.1 Criticality=false
AuthorityInfoAccess [
  [
   accessMethod: ...
   accessLocation: URIName: ...
   accessMethod: ...
   accessLocation: URIName: ...
]

[2]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
...
]
]
[3]: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
  CA:false
  PathLen: undefined
]
[4]: ObjectId: 2.5.29.31 Criticality=false
CRLDistributionPoints [
  [DistributionPoint:
     [URIName: ...
]]
[5]: ObjectId: 2.5.29.32 Criticality=false
CertificatePolicies [
  [CertificatePolicyId: ...
[PolicyQualifierInfo: [
  qualifierID: ...
  qualifier: ...
]]  ]
]
[6]: ObjectId: 2.5.29.37 Criticality=false
ExtendedKeyUsages [
  serverAuth
  clientAuth
]
[7]: ObjectId: 2.5.29.15 Criticality=true
KeyUsage [
  DigitalSignature
  Key_Encipherment
]
[8]: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: ...
]
]
  Algorithm: [SHA1withRSA]
  Signature:
...
]
...
***
main, READ: TLSv1 Handshake, length = 4
*** ServerHelloDone
*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
main, WRITE: TLSv1 Handshake, length = 518
SESSION KEYGEN:
PreMaster Secret:
...
CONNECTION KEYGEN:
Client Nonce:
...
Server Nonce:
...
Master Secret:
...
Client MAC write Secret:
...
Server MAC write Secret:
...
Client write key:
...
Server write key:
...
Client write IV:
...
Server write IV:
...
main, WRITE: TLSv1 Change Cipher Spec, length = 1
*** Finished
verify_data:  { 18, 162, 18, 251, 82, 111, 87, 133, 53, 240, 114, 155 }
***
main, WRITE: TLSv1 Handshake, length = 48
main, READ: TLSv1 Change Cipher Spec, length = 1
main, READ: TLSv1 Handshake, length = 48
*** Finished
verify_data:  { 46, 206, 8, 40, 63, 252, 99, 190, 251, 183, 110, 201 }
***
%% Cached client session: [Session-1, TLS_RSA_WITH_AES_128_CBC_SHA]
main, WRITE: TLSv1 Application Data, length = 256
main, WRITE: TLSv1 Application Data, length = 32
main, WRITE: TLSv1 Application Data, length = 16416
main, WRITE: TLSv1 Application Data, length = 16416
...
main, WRITE: TLSv1 Application Data, length = 16416
main, WRITE: TLSv1 Application Data, length = 16416
main, WRITE: TLSv1 Application Data, length = 512
main, READ: TLSv1 Application Data, length = 304 

由于这里要求的是getMyCustomClientCertSocketFactory源(获得证书和密钥从PEM文件):

public static SSLSocketFactory getMyCustomClientCertSocketFactory(String pemPath,
        boolean verifyPeer)
        throws NoSuchAlgorithmException, FileNotFoundException, IOException,
        KeyStoreException, CertificateException, UnrecoverableKeyException,
        KeyManagementException, InvalidKeySpecException {
    SSLContext context = SSLContext.getInstance("TLS");

    byte[] certAndKey = IOUtil.fileToBytes(new File(pemPath));
    byte[] certBytes = parseDERFromPEM(certAndKey,
            "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
    byte[] keyBytes = parseDERFromPEM(certAndKey,
            "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");

    X509Certificate cert = generateX509CertificateFromDER(certBytes);
    RSAPrivateKey key = generateRSAPrivateKeyFromDER(keyBytes);

    KeyStore keystore = KeyStore.getInstance("JKS");
    keystore.load(null);
    keystore.setCertificateEntry("cert-alias", cert);
    keystore.setKeyEntry("key-alias", key, "changeit".toCharArray(),
            new Certificate[]{cert});

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(keystore, "changeit".toCharArray());

    KeyManager[] km = kmf.getKeyManagers();

    TrustManager[] tm = null;

    if (!verifyPeer) {
        tm = new TrustManager[]{new TrustyTrustManager()};
    }

    context.init(km, tm, null);

    return context.getSocketFactory();
}

Answer 1:

这似乎是内置的Sun Java的HttpsURLConnection的设备无法处理在服务器友好的方式与客户端证书的场景大HTTP PUT(即不会溢出服务器SSL重新协商缓冲)。

我要检查什么卷曲在做,看看有什么“服务器友好的意思”,它原来有一个HTTP 1.1标名为“期待”,这与卷曲值“100继续”(见规格表发送HTTP://www.w3 .ORG /协议/ RFC2616 / RFC2616-sec14.html#sec14.20 )。 这个头实际上是说“我有一个巨大的有效载荷,但在此之前我把它请让我知道如果你能处理它。” 这使端点时间重新协商客户端证书发送有效载荷之前。

在太阳HttpURLConnection的实现看来这头是不允许的,而实际上是在限制标题列表; 这意味着即使你与HttpUrlConnection.setRequestProperty方法标题实际上没有发送到服务器设置。 您可以使用系统属性sun.net.http.allowRestrictedHeaders覆盖受限的头,但随后的客户端只是一个插座异常崩溃,因为Sun实现不知道如何处理这部分协议。

有趣的是,似乎在OpenJDK的Java实现不支持这个头。 另外,在Apache HTTP客户端库支持该头( http://hc.apache.org/ ); 我实现了与Apache HTTP客户端库的测试程序,它可以成功地执行和使用客户端证书和期待头大文件的HTTP PUT请求。

总括来说,该解决方案是:

  1. 设置Apache的SSLRenegBufferSize指令数量巨大(如64MB)。 默认为128K。 该解决方案可以创建的服务风险拒绝
  2. 配置总是需要客户端证书,而不是一个,其中只有极少的几个目录需要它的主机。 这将避免重新谈判。 这不是我的方案一个很好的选择,因为大多数用户都匿名或用户名/密码认证。 只有对文件的程序上传单个上传目录。 我们必须创建自己的SSL证书一个新的虚拟主机只为这一个目录。
  3. 使用哪个支持HTTP 1.1期望报头中的客户端。 不幸的是在Sun Java不支持这个开箱即用。 必须使用第三方如Apache HTTP组件客户端库或使用Java套接字API推出自己的解决方案。
  4. 杠杆HTTP 1.1持久连接(流水线与保活)通过最初发出不具有大的有效载荷的HTTP请求,而导致重新协商发生,然后再用该HTTP PUT的连接。 从理论上讲,客户端应该能够发出的上传目录的HTTP头或选项,然后重复使用相同的连接做PUT。 为了使这项工作持久连接池很可能只需要包含一个连接,以避免“吸”一个连接,然后被发布另一个用于PUT。 然而,它似乎并不像HttpURLConnection的类将保持/重用涉及客户端证书或SSL,因为我一直无法得到实现此解决办法持久连接。 见( HttpsURLConnection的,并保持有效 )。


Answer 2:

java.io.IOException异常:服务器返回的HTTP响应代码:401网址

这是一个应用程序错误。 它不是由SSL层所致。 我不知道为什么你得到401 Unauthorized的更大的文件,但你也忽略了什么是getMyCustomClientCertSocketFactory()
另外你尝试另一种方法,例如POST ? 你有同样的问题?



Answer 3:

基于所有的额外信息,现在提供的,我想你应该写在多个数据块,而不仅仅是一个XML。 目前你正在写一个大块,这将通过SSL进行分块成块16K,这是窒息阿帕奇由于某种原因(它不应该)。 我会尝试块大小不超过4K大。 调整块大小,直到它的工作原理。

你可能会发现客户端证书的问题,一旦你过去的这一个。 不要气馁,这是证明你已经至少解决了这个问题。



文章来源: SSL Renegotiation with Client Certificate causes Server Buffer Overflow