我正在使用Java开发简单的SSL服务器/客户端。请忽略未使用的trustStore。当服务器设置serverSock.setNeedClientAuth(false)时,它可以正常工作。但是,设置serverSock.setNeedClientAuth(true)时,出现错误提示
*** ServerHello, TLSv1
....
***
*** ECDH ServerKeyExchange
Server key: Sun EC public key, 256 bits
public x coord: 61670393751189389356366022463080915345182339021857366784148461923453434926203
public y coord: 11927389709535675731950695034443898307097761611191306989959806723983291216258
parameters: secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)
**main, handling exception: java.lang.NullPointerException
%% Invalidated: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]**
main, SEND TLSv1 ALERT: fatal, description = internal_error
main, WRITE: TLSv1 Alert, length = 2
main, called closeSocket()
Exception in thread "main" javax.net.ssl.SSLException: java.lang.NullPointerException
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
at sun.security.ssl.SSLSocketImpl.handleException(Unknown Source)
at sun.security.ssl.SSLSocketImpl.handleException(Unknown Source)
at sun.security.ssl.AppInputStream.read(Unknown Source)
at java.io.InputStream.read(Unknown Source)
at cn.secure.CAServer.start(SecureServer.java:100)
at cn.secure.SecureServer.main(SecureServer.java:33)
Caused by: java.lang.NullPointerException
at sun.security.ssl.HandshakeMessage$CertificateRequest.<init>(Unknown Source)
at sun.security.ssl.ServerHandshaker.clientHello(Unknown Source)
at sun.security.ssl.ServerHandshaker.processMessage(Unknown Source)
at sun.security.ssl.Handshaker.processLoop(Unknown Source)
at sun.security.ssl.Handshaker.process_record(Unknown Source)
at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.readDataRecord(Unknown Source)
... 4 more
看来ServerHello尚未完成。
以下是我的代码。请让我知道如何解决。
// Server code
class CAServer
{
private SSLContext ctx;
private KeyManagerFactory kmf;
private TrustManagerFactory tmf;
private SSLServerSocket serverSock;
public void init() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException
{
ctx=SSLContext.getInstance("TLS");
kmf=KeyManagerFactory.getInstance("SunX509");
tmf=TrustManagerFactory.getInstance("SunX509");
char[] pwd="111".toCharArray();
KeyStore ks=KeyStore.getInstance("JKS");
KeyStore ts=KeyStore.getInstance("JKS");
ks.load(new FileInputStream("C:/Users/Jim/ca.keystore"), pwd);
ts.load(new FileInputStream("C:/Users/Jim/ca.keystore"), pwd); // unused
kmf.init(ks,pwd);
tmf.init(ts);
TrustManager[] trustClientCerts = new TrustManager[] { new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs,String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs,String authType) {
}
}
};
ctx.init(kmf.getKeyManagers(),trustClientCerts, null);
//init server
serverSock=(SSLServerSocket)ctx.getServerSocketFactory().createServerSocket(13000);
serverSock.setNeedClientAuth(true);
}
public void start() throws IOException
{
System.out.println("My Secure server start");
while(true)
{
Socket s=serverSock.accept();
InputStream input=s.getInputStream();
byte[] c=new byte[256];
input.read(c); **// error(NullPointer) occurs here**
System.out.println(new String(c));
}
}
}
// Client code
class MyClient
{
private SSLContext ctx;
KeyManagerFactory kmf;
TrustManagerFactory tmf;
private SSLSocket clientSock;
public void init() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, FileNotFoundException, IOException, KeyManagementException, UnrecoverableKeyException
{
ctx=SSLContext.getInstance("TLS");
kmf=KeyManagerFactory.getInstance("SunX509");
tmf=TrustManagerFactory.getInstance("SunX509");
char[] pwd="111".toCharArray();
KeyStore ks=KeyStore.getInstance("JKS");
KeyStore ts=KeyStore.getInstance("JKS");
ks.load(new FileInputStream("C:/Users/jim/alice.keystore"), pwd);
ts.load(new FileInputStream("C:/Users/jim/alice.keystore"), pwd); //unused
TrustManager[] trustServerCerts = new TrustManager[]{new X509TrustManager()
{
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType)
{
for(X509Certificate c :certs){
System.out.println(c.getSubjectDN().getName());
}
}
}
};
kmf.init(ks, pwd);
tmf.init(ts);
ctx.init(kmf.getKeyManagers(), trustServerCerts, null);
clientSock=(SSLSocket)ctx.getSocketFactory().createSocket("127.0.0.1", 13000);
clientSock.setUseClientMode(true);
}
public void run() throws IOException
{
InputStream input = null;
OutputStream output = null;
output = clientSock.getOutputStream();
BufferedOutputStream bufferedOutput = new BufferedOutputStream(output);
bufferedOutput.write("Alice: is running".getBytes());
bufferedOutput.flush();
}
}
顺便说一句,Windows仅根据日志支持TLSv1,这令人惊讶。
最佳答案
从 X509TrustManager.getAcceptedIssuers()
文档中:
如果在那里返回空值(就像您一样),随后在某处导致NullPointerException
,也就不足为奇了。
确实是 constructor
of sun.security.ssl.HandshakeMessage$CertificateRequest
makes use of this array of CA certificates assuming it's not null。
另外,如果您想更实际地尝试双向身份验证,则可以创建自己的测试CA并拥有适当的信任库。您正在使用的行为(一个空列表)有效,但仅在TLS 1.1规范中指定为可接受。 (无论如何,通过所有信任检查很少是一个好主意。)
根据您的评论之一:
getAcceptedIssuers()
仅用于构建在Certificate Request
TLS消息中发送的可接受的CA证书列表。尽管它是证书的数组,但实际上仅使用这些证书的主题DN。
这与 SSLCADNRequestFile
and SSLCADNRequestPath
directives in Apache Httpd非常相似。使它与您的信任库中的列表不同的情况很少有用。
验证实际使用的是checkServerTrusted()
。
如果需要,您可以以编程方式加载您的信任库,而不是使用this answer中所述的默认属性或系统属性。然后,使用初始化的SSLContext
创建服务器套接字。
如果您每次需要更改信任库时都可以关闭并重新打开服务器套接字,那么在实现自己的TrustManager
(将其调用委派给另一个由KeyStore
(您的信任库)和TrustManagerFactory
初始化的另一个trustmanager)中,还可能会有些复杂。 。每当您的信任库更改时,您都将更改委托(delegate)的TrustManager
(您可能需要考虑可能的并发问题)。
关于java - Java SSL双向握手错误,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/30540871/