【发布时间】:2012-12-26 05:45:11
【问题描述】:
我编写了一个 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());
我在想也许我需要使用 SSLSocket 等较低级别的 API 并利用 HandshakeCompletedListener?
我还想知道 Apache SSLVerifyDepth 指令是否与重新协商发生的原因有关。我在每个目录上下文(只有一个上传目录)中得到了值为 2 的指令,Apache 手册对此进行了说明:
在每个目录的上下文中,它会强制与 读取 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();
}
【问题讨论】:
-
如果我使用 UNIX 实用程序“curl”,我可以毫无意外地传输大文件,所以我想知道它有什么不同...
-
这是一个讨论该问题的错误报告:bugzilla.redhat.com/show_bug.cgi?id=491763。在使用 HttpsUrlConnections 时,在 PUT 之前在上传目录上发出 HTTP GET(或 HEAD 或 OPTIONS)似乎不起作用 - 我猜 keep-alive 没有得到尊重?
-
使用 -Djavax.net.debug=ssl,handshake 运行您的客户端并将输出发布到您的答案中。
-
调试输出过于冗长,无法粘贴到评论中,但基本上看起来应用程序数据在服务器甚至请求重新协商之前就已发送,到那时为时已晚,因为套接字关闭。有没有办法可靠地预测重新谈判? curl 有什么作用?
-
将其编辑到您的问题中。在您这样做之前,没有人可以提供帮助。