2

I'm using Java to develop a simple SSL server/client. Please ignore trustStore which is unused. When the server set serverSock.setNeedClientAuth(false), it works fine. However, when serverSock.setNeedClientAuth(true) is set, an error prompts that

*** 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

It seems ServerHello is not done.

Following is my code. Please let me know how to solve it.

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

    }

}

By the way, Windows only supports TLSv1 according to log which is surprising.

frogcd
  • 61
  • 1
  • 9
  • what if `getAcceptedIssuers()` returns an empty array, instead of `null`? – ZhongYu May 30 '15 at 00:25
  • also, instead of use a trust-all approach, you could use A's keystore as B's truststore, and vice versa. – ZhongYu May 30 '15 at 00:27
  • thanks, I just tried to add an empty array in getAcceptedIssuers() in client code: public X509Certificate[] getAcceptedIssuers() { return newX509Certificate[]{}; } But it still prompts the same error. Using TrustManager[] trustClientCerts can dynamically check certificates if certificates are added/removed at runtime. I have added error stack trace in my original message. – frogcd May 30 '15 at 00:31
  • what about `getAcceptedIssuers` in server code? – ZhongYu May 30 '15 at 00:46
  • Wow! adding new cert array in server works! X509Certificate[] getAcceptedIssuers() Return an array of certificate authority certificates which are trusted for authenticating peers. I don't figure out the use of getAcceptedIssuers. Can you explain a little bit? thk. – frogcd May 30 '15 at 00:50
  • I think, if client cert is required, server sends a list of acceptable CAs to client during handshake; empty list means all CAs are accepted. – ZhongYu May 30 '15 at 00:53
  • "*Windows only supports TLSv1*" Not sure where you got that from. It depends entirely on the client that is used, the libraries it uses (and possibly the version of Windows if it's using the native libraries). – Bruno May 30 '15 at 01:39

2 Answers2

2

From the X509TrustManager.getAcceptedIssuers() documentation:

Returns: a non-null (possibly empty) array of acceptable CA issuer certificates.

It should be no surprise if returning a null value there (like you do) causes a NullPointerException somewhere subsequently.

Indeed, the constructor of sun.security.ssl.HandshakeMessage$CertificateRequest makes use of this array of CA certificates assuming it's not null.

In addition, if you want a more realistic attempt at two-way authentication, you could create your own test CA and have a proper truststore. The behaviour you're using (an empty list) works, but it's only specified as acceptable in the TLS 1.1 specifications. (Bypassing all trust checks is rarely a good idea anyway.)


From one of your comments:

I don't figure out the use of getAcceptedIssuers. Can you explain a little bit?

getAcceptedIssuers() is only used to build the list of acceptable CA certificates sent in the Certificate Request TLS message. Although it's an array of certificates, only the subject DNs of these certificates are really used.

This is very similar to the SSLCADNRequestFile and SSLCADNRequestPath directives in Apache Httpd. It is extremely rarely useful to make this differ from the list in your truststore.

What is actually used for the verification is checkServerTrusted().


Truststore contains certificates of trusted parties, which is loaded at program startup. If we are going to add/remove entries in Truststore at runtime, is there a way to do that?

If needed you can load your truststore programmatically, instead of using the default or system properties, as described in this answer. Then, create your server socket from the SSLContext you initialised with it.

If you can close and re-open the server socket every time you need to change your truststoreYou could also have something a bit more complicated where you implement your own TrustManager that delegates its calls to another trustmanager initialised from a KeyStore (your truststore) and TrustManagerFactory. You would change the delegated TrustManager whenever your truststore changes (you might need to take into account possible concurrency issues).

Community
  • 1
  • 1
Bruno
  • 119,590
  • 31
  • 270
  • 376
  • Truststore contains certificates of trusted parties, which is loaded at program startup. If we are going to add/remove entries in Truststore at runtime, is there a way to do that? Appreciate. – frogcd May 30 '15 at 01:24
  • the certs are all self-signed in my code. getAcceptedIssuers() returns a non-null (possibly empty) array of acceptable CA issuer certificates. How to understand "acceptable"? – frogcd May 30 '15 at 01:34
  • 1
    See [the TLS specification, `certificate_authorities` in `Certificate Request`](http://tools.ietf.org/html/rfc5246#section-7.4.4). Essentially, it's what the server advertises it may accept (although it may still refuse if it doesn't like the cert for whatever reason when the client presents it). In practice, an empty list makes most clients send the first one they find. It's then up to `checkServerTrusted` to verify them one way or another (e.g. against a fixed list of known certs if they are all self-signed in your case). – Bruno May 30 '15 at 01:37
  • thanks for detailed explanations. Since truststore is a group of .crt certificate files, is it functionally okay to use checkServerTrusted/checkClientTrusted to check certificates(.crt files) at runtime? By doing that, we can avoid reloading truststore and re-opening socket. – frogcd May 30 '15 at 01:58
  • 1
    If you don't have too many of them, it might be worth putting them in a concurrent collection you keep in memory (e.g. `CopyOnWriteArrayList` or `ConcurrentHashMap` using the DN as key for efficiency) and use that to compare against the first cert in the chain you get in `checkClientTrusted`. Otherwise, you could also indeed access the file system every time (even though it's not necessarily efficient). – Bruno May 30 '15 at 02:03
  • @frogcd - who issues these client certs? the server could sign them; then the server only needs one CA (itself) in trust store. – ZhongYu May 30 '15 at 03:23
  • @frogcd **JSSE** calls `checkClientTrusted()` and `checkServerTrusted().` Not the application. – user207421 May 30 '15 at 05:11
0

The getAcceptedIssuers() method may not return null. See the Javadoc.

But don't use this insecure trust manager code, especially as you have your own truststore.

Err, no you don't. Your keystore should be distinct from your truststore. They have completely different functions.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Thanks. I see that truststore and keystore are quite different. Java has pretty nice crypto stuff but security concepts(like keystore, truststore) are somewhat confusing, compared with Openssl things. – frogcd May 30 '15 at 01:28
  • Mainly because they decided to use the same format for both. – user207421 May 30 '15 at 05:09