JDK-7148699 : Handshaker throws NPE if TrustManager returns null from getAcceptedIssuers
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.net.ssl
  • Affected Version: 7
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: linux
  • CPU: x86
  • Submitted: 2012-02-24
  • Updated: 2012-03-20
  • Resolved: 2012-02-25
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
jdk@ssl-jdk:~$ java -version
openjdk version "1.7.0-internal-fastdebug"
OpenJDK Runtime Environment (build 1.7.0-internal-fastdebug-mhall_2012_01_26_11_27-b00)
OpenJDK 64-Bit Server VM (build 21.0-b17-fastdebug, mixed mode)
jdk@ssl-jdk:~$


ADDITIONAL OS VERSION INFORMATION :
jdk@ssl-jdk:~$ uname -a
Linux ssl-jdk 3.0.0-14-server #23-Ubuntu SMP Mon Nov 21 20:49:05 UTC 2011 x86_64 x86_64 x86_64 GNU/Linux
jdk@ssl-jdk:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=11.10
DISTRIB_CODENAME=oneiric
DISTRIB_DESCRIPTION="Ubuntu 11.10"
jdk@ssl-jdk:~$


A DESCRIPTION OF THE PROBLEM :
If one creates a TrustManager (in this case used for creating a TLSv1.2 compliant SSL handshake testing tool using Java 7 for interoperability testing of a TLSv1.2 compliant SSL security tester), which returns null from its "getAcceptedIssuers()" method instead of the empty Certificate array seen below:

        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCertificates = new TrustManager[] {
            new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    System.err.println("*** entering useless getAcceptedIssuers ***");
                    // XXX: cannot return null; causes an NPE because this goes into the CertificateRequest
                    return new X509Certificate[0];
                }
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    System.err.println("*** entering useless checkClientTrusted ***");
                }
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    System.err.println("*** entering useless checkServerTrusted ***");
                }
            }
        };

Then the following uncaught exception is thrown from the JSSE implementation:

*** entering useless getAcceptedIssuers ***
main, handling exception: java.lang.NullPointerException
%% Invalidated:  [Session-1, SSL_RSA_WITH_NULL_SHA]
main, SEND TLSv1.2 ALERT:  fatal, description = internal_error
main, WRITE: TLSv1.2 Alert, length = 2
[Raw write]: length = 7
0000: 15 03 03 00 02 02 50                               ......P
main, called closeSocket()
javax.net.ssl.SSLException: java.lang.NullPointerException
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1836)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1794)
        at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1777)
        at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1703)
        at sun.security.ssl.AppInputStream.read(AppInputStream.java:113)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:154)
        at java.io.BufferedReader.readLine(BufferedReader.java:317)
        at java.io.BufferedReader.readLine(BufferedReader.java:382)
        at com.mudynamics.security.ssl.SslServer.main(SslServer.java:60)
Caused by: java.lang.NullPointerException
        at sun.security.ssl.HandshakeMessage$CertificateRequest.<init>(HandshakeMessage.java:1269)
        at sun.security.ssl.ServerHandshaker.clientHello(ServerHandshaker.java:839)
        at sun.security.ssl.ServerHandshaker.processMessage(ServerHandshaker.java:167)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:966)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1262)
        at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:816)
        at sun.security.ssl.AppInputStream.read(AppInputStream.java:102)
        ... 8 more

This happens because the CertificateRequest message is assuming that the value returned from the TrustManager is always defined. Something should be checking the validity of values returned from the TrustManager before putting them into Handshake messages.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached Java Class with the following command line options:

-Djavax.net.debug=all
-Djavax.net.ssl.keyStore=<keystore>
-Djavax.net.ssl.keyStorePassword=<password>
-XX:SuppressErrorAt=/stackValue.hpp:64 (needed if using fastdebug due to some assertion failure which crashes the OpenJDK)

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The Handshaker should send the client a CertificateRequest containing an empty certificate_authorities field indicating any CA is accepted.
ACTUAL -
The Handshaker throws an uncaught NPE which could be avoided.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
javax.net.ssl.SSLException: java.lang.NullPointerException
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1836)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1794)
        at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1777)
        at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1703)
        at sun.security.ssl.AppInputStream.read(AppInputStream.java:113)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:154)
        at java.io.BufferedReader.readLine(BufferedReader.java:317)
        at java.io.BufferedReader.readLine(BufferedReader.java:382)
        at com.mudynamics.security.ssl.SslServer.main(SslServer.java:60)
Caused by: java.lang.NullPointerException
        at sun.security.ssl.HandshakeMessage$CertificateRequest.<init>(HandshakeMessage.java:1269)
        at sun.security.ssl.ServerHandshaker.clientHello(ServerHandshaker.java:839)
        at sun.security.ssl.ServerHandshaker.processMessage(ServerHandshaker.java:167)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:966)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1262)
        at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:816)
        at sun.security.ssl.AppInputStream.read(AppInputStream.java:102)
        ... 8 more


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package com.mudynamics.security.ssl;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class SslServer {
    public static void main(String[] args) throws Throwable {
        String[] supportedSuites;
        
        System.setOut(System.err);
        
        SSLContext sslContext = SslServer.createSslContext();
        SSLParameters sslParameters = sslContext.getDefaultSSLParameters();
        
        sslParameters.setAlgorithmConstraints(null);
        sslParameters.setEndpointIdentificationAlgorithm(null);
        sslParameters.setWantClientAuth(true);
        sslParameters.setNeedClientAuth(true);
        
        supportedSuites = sslParameters.getCipherSuites();
        sslParameters.setCipherSuites(supportedSuites);
        
        SSLServerSocketFactory ssf = sslContext.getServerSocketFactory();
        
        SSLServerSocket serverSocket =
            (SSLServerSocket) ssf.createServerSocket(31337);
        
        serverSocket.setWantClientAuth(true);
        serverSocket.setNeedClientAuth(true);
        
        supportedSuites = serverSocket.getSupportedCipherSuites();
        serverSocket.setEnabledCipherSuites(supportedSuites);
        
        while (true) {
            try {
                SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
                System.out.println("--------------------------------------------------------------------------------");
                InputStream is = clientSocket.getInputStream();
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                
                String input = null;
                while ((input = br.readLine()) != null) {
                    System.out.println(input);
                    System.out.flush();
                }
            }
            catch (Throwable t) {
                t.printStackTrace(System.out);
            }
        }
    }
    
    public static SSLContext createSslContext() throws Throwable {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCertificates = new TrustManager[] {
            new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    System.err.println("*** entering useless getAcceptedIssuers ***");
                    // XXX: cannot return null; causes an NPE because this goes into the CertificateRequest
//                    return new X509Certificate[0];
                    return null;
                }
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    System.err.println("*** entering useless checkClientTrusted ***");
                }
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    System.err.println("*** entering useless checkServerTrusted ***");
                }
            }
        };
        
        String keyStoreFile = "./etc/ssl-server.jks";
        String keyStorePassword = "ssl-server";
        
        KeyStore keyStore = KeyStore.getInstance("jks");
        keyStore.load(new FileInputStream(keyStoreFile), keyStorePassword.toCharArray());
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
        
        // must use TLSv1.2 or some ciphers fail to load
        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(keyManagerFactory.getKeyManagers(), trustAllCertificates, new SecureRandom());
        
        // KeyStore trustStore = KeyStore.getInstance("jks");
        // trustStore.load(new FileInputStream(), "ebxmlrr".toCharArray());
        // TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
        // trustManagerFactory.init(trustStore);
        
        return sslContext;
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Returning a zero length array of X509Certificate produces the expected result of a CertificateRequest with zero length to allow any Client Cert CA.

Comments
EVALUATION Please note that getAcceptedIssuers() API does not allow a null entry to be returned. It must be non-null, possibly empty. /** * Return an array of certificate authority certificates * which are trusted for authenticating peers. * * @return a non-null (possibly empty) array of acceptable * CA issuer certificates. */ public X509Certificate[] getAcceptedIssuers(); Close as "not a defect".
25-02-2012