【问题标题】:Java SSL two-way handshake errorJava SSL 双向握手错误
【发布时间】:2015-05-30 00:16:19
【问题描述】:

我正在使用 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,这很令人惊讶。

【问题讨论】:

  • 如果getAcceptedIssuers()返回一个空数组,而不是null呢?
  • 另外,您可以使用 A 的密钥库作为 B 的信任库,而不是使用全部信任的方法,反之亦然。
  • 谢谢,我只是尝试在客户端代码的 getAcceptedIssuers() 中添加一个空数组: public X509Certificate[] getAcceptedIssuers() { return newX509Certificate[]{};但它仍然提示同样的错误。如果在运行时添加/删除证书,使用 TrustManager[] trustClientCerts 可以动态检查证书。我在原始消息中添加了错误堆栈跟踪。
  • 服务器代码中的getAcceptedIssuers 怎么样?
  • 哇!在服务器中添加新的证书数组有效! X509Certificate[] getAcceptedIssuers() 返回一组证书颁发机构证书,这些证书可用于验证对等方的信任。我不知道 getAcceptedIssuers 的用途。你能解释一下吗?谢谢。

标签: java sockets ssl


【解决方案1】:

来自X509TrustManager.getAcceptedIssuers() 文档:

返回:一个非空(可能为空)可接受的 CA 颁发者证书数组。

如果在此处返回空值(如您所做的那样)随后会在某处导致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 规范中被指定为可接受的。 (无论如何,绕过所有信任检查都不是一个好主意。)


来自您的一位 cmets:

我不知道 getAcceptedIssuers 的用途。能稍微解释一下吗?

getAcceptedIssuers() 仅用于构建在Certificate Request TLS 消息中发送的可接受 CA 证书列表。虽然是证书数组,但真正使用的只是这些证书的主题 DN。

这与SSLCADNRequestFile and SSLCADNRequestPath directives in Apache Httpd 非常相似。使它与您的信任库中的列表不同是非常有用的。

实际用于验证的是checkServerTrusted()


Truststore 包含受信任方的证书,在程序启动时加载。如果我们要在运行时添加/删除 Truststore 中的条目,有没有办法做到这一点?

如果需要,您可以以编程方式加载您的信任库,而不是使用默认或系统属性,如 this answer 中所述。然后,从您初始化的SSLContext 创建您的服务器套接字。

如果您可以在每次需要更改信任库时关闭并重新打开服务器套接字您还可以有一些更复杂的东西,您可以实现自己的 TrustManager,将其调用委托给从 @987654337 初始化的另一个信任管理器@(您的信任库)和 TrustManagerFactory。每当您的信任库更改时,您将更改委派的TrustManager(您可能需要考虑可能的并发问题)。

【讨论】:

  • Truststore 包含受信任方的证书,在程序启动时加载。如果我们要在运行时添加/删除 Truststore 中的条目,有没有办法做到这一点?欣赏。
  • 证书在我的代码中都是自签名的。 getAcceptedIssuers() 返回可接受的 CA 颁发者证书的非空(可能为空)数组。如何理解“可接受”?
  • the TLS specification, certificate_authorities in Certificate Request。从本质上讲,它是服务器宣传它可能接受的内容(尽管如果它在客户端提供证书时出于任何原因不喜欢证书,它仍然可能拒绝)。实际上,空列表会使大多数客户发送他们找到的第一个。然后由checkServerTrusted 以一种或另一种方式验证它们(例如,如果在您的情况下它们都是自签名的,则针对已知证书的固定列表)。
  • 感谢您的详细解释。由于信任库是一组 .crt 证书文件,在运行时使用 checkServerTrusted/checkClientTrusted 检查证书(.crt 文件)在功能上是否可行?通过这样做,我们可以避免重新加载信任库和重新打开套接字。
  • 如果您没有太多,可能值得将它们放在您保存在内存中的并发集合中(例如,CopyOnWriteArrayListConcurrentHashMap 使用 DN 作为提高效率的关键)并使用它与您在checkClientTrusted 中获得的链中的第一个证书进行比较。否则,您确实也可以每次都访问文件系统(即使它不一定有效)。
【解决方案2】:

getAcceptedIssuers() 方法可能不会返回 null。请参阅 Javadoc。

但不要使用这种不安全的信任管理器代码,尤其是当您拥有自己的信任库时。

呃,不,你没有。您的密钥库应该与您的信任库不同。它们的功能完全不同。

【讨论】:

  • 谢谢。我看到信任库和密钥库完全不同。 Java 有相当不错的加密技术,但与 Openssl 相比,安全概念(如密钥库、信任库)有些混乱。
  • 主要是因为他们决定对两者使用相同的格式。
猜你喜欢
  • 1970-01-01
  • 2017-02-11
  • 2016-11-19
  • 1970-01-01
  • 2015-02-03
  • 2016-08-08
  • 2016-04-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多