当前位置: 首页 > 面试题库 >

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

章光华
2023-03-14
问题内容

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

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手册对此进行了说明:

在按目录的上下文中,它会在读取HTTP请求之后但在发送HTTP响应之前,以重新配置的客户端验证深度强制进行SSL重新协商。

以下是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源(从PEMhtml" target="_blank">文件获取证书和密钥):

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();
}

问题答案:

似乎Sun
Java内置的HttpsUrlConnection工具无法以服务器友好的方式(即没有溢出服务器SSL重新协商缓冲区)来处理带有客户端证书的大型HTTP
PUT。

我检查了curl所做的事情,以了解“服务器友好”的含义,结果发现有一个名为“ Expect”的HTTP 1.1标头,curl发送的值为“
100-continue”(请参阅​​规格http://www.w3 .org / Protocols / rfc2616 /
rfc2616-sec14.html#sec14.20)。该标头本质上说:“我有很大的负载,但是在发送之前,请告诉我是否可以处理”。这使端点有时间在发送有效负载之前重新协商客户端证书。

在Sun
HttpUrlConnection实现中,似乎不允许使用此标头,而实际上是在受限标头列表中。这意味着即使您使用HttpUrlConnection.setRequestProperty方法进行设置,标题也不会实际发送到服务器。您可以使用系统属性sun.net.http.allowRestrictedHeaders覆盖受限制的标头,但是由于Sun实现不知道如何处理协议的这一部分,因此客户端只会因套接字异常而崩溃。

有趣的是,似乎Java的OpenJDK实现确实支持此标头。另外,Apache HTTP
Client库支持此标头(http://hc.apache.org/);我已经用Apache
HTTP客户端库实现了一个测试程序,它可以使用客户端证书和Expect标头成功执行大文件的HTTP PUT请求。

概括地说,解决方案是:

  1. 将Apache SSLRenegBufferSize指令设置为一个很大的数字(例如64MB)。默认值为128K。此解决方案可能会导致拒绝服务风险
  2. 配置一台始终需要客户端证书的主机,而不是只需要几个目录的主机。这样可以避免重新谈判。在我的情况下,这不是一个好选择,因为大多数用户都是匿名的,或者已验证用户名/密码。只有一个上载目录用于程序上载文件。我们只需要为此一个目录创建一个具有自己的SSL证书的新虚拟主机。
  3. 使用支持HTTP 1.1 Expect标头的客户端。不幸的是,Sun Java不支持此功能。必须使用第三方(例如Apache HTTP Component Client库)或使用Java套接字API推出自己的解决方案。
  4. 通过最初发出没有大有效负载但导致重新协商的HTTP请求来利用HTTP 1.1持久连接(使用keep-alive进行流水线化),然后将连接重用于HTTP PUT。从理论上讲,客户端应该能够在上传目录上发出HTTP HEAD或OPTIONS,然后重用相同的连接来执行PUT。为了使它起作用,持久连接池可能只需要包含一个连接,以避免“启动”一个连接,然后为PUT发出另一个连接。但是,由于我一直无法使该解决方案正常工作,因此HttpUrlConnection类似乎不会保留/重用涉及客户端证书或SSL的持久连接。


 类似资料:
  • 问题内容: 我有一个具有高 缓冲区I / O 的客户端SQL Server 。 我检查了长时间运行的查询,添加了新索引,并且服务器上没有锁。是磁盘问题吗? 问题答案: 首先尝试检查您的磁盘延迟时间是否在阈值内。 您可以检查此链接以配置Perfmon:SQL Server磁盘性能指标-第1部分- 最重要的磁盘性能指标 该链接还提供了一些工具,可以对您的IO进行压力测试并获得报告。 如何使用SQLIO

  • 验证 SSH 服务器证书(客户端) 和 SSH 用户证书 类似,使用客户端机构认证来验证主机。需要一个 SSH 服务器数字证书认证机构签发服务器证书,客户端只需要保存 CA 的公钥。 使用一台处于气隙系统下的计算机来生成服务器数字证书认证机构的根证书: ❯ ssh-keygen -C "SSH Server Certificate Authority" -f sshserver.root.ca 使

  • 我正在Linux上编写一个C应用程序。我的应用程序有一个 UDP 服务器,它在某些事件上向客户端发送数据。UDP 服务器还会收到来自客户端的一些反馈/确认。 为了实现这个应用程序,我使用了一个UDP套接字(例如< code>int fdSocket)来发送和接收来自所有客户端的数据。我将这个socked绑定到端口8080,并将套接字设置为非阻塞模式。 我创建了两个线程。在一个线程中,我等待某个事件

  • 问题内容: 我正在连接到以前成功使用过的Web服务,但是现在他们已经更改了主机名并向我发送了两个.pem文件。一个是CA,另一个是我的新客户证书。 (我将Java 1.5,Spring + Spring Web Services与Apache httpclient一起使用,但是我怀疑我的问题是证书,密钥和SSL本身。) 我已经导入了.pem文件以及从Firefox导出到cacerts中的主机的.c

  • 问题内容: 它看起来像一个标准问题,但是我在任何地方都找不到清晰的方向。 我有Java代码试图连接到可能带有自签名(或过期)证书的服务器。该代码报告以下错误: 据我了解,我必须使用keytool并告诉java允许这种连接是可以的。 解决此问题的所有说明均假定我完全精通keytool,例如 生成服务器的私钥并将其导入密钥库 是否有人可以发布详细说明? 我正在运行Unix,所以bash脚本是最好的。