Java - SocketException: Connection Reset when Client Send Request

3.7k views Asked by At

In Java Network Programming 4th Edition, Chapter 10 about Secure Socket, there is an example of build Secure Server. The code can be find here.

I'm trying to make more simple version of the code. Here is my code:

 try {
        SSLContext context = SSLContext.getInstance(algorithm);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        KeyStore ks = KeyStore.getInstance("JKS");

        //char[] password = System.console().readPassword("Password: "); // open the .class with command line
        char[] password = {'t', 'h', 'i', 's', 'i', 's', 't', 'i', 'a', 'n'};

        ks.load(new FileInputStream("src/jnp4e.keys"), password);            
        kmf.init(ks, password);
        context.init(kmf.getKeyManagers(), null, null); // null = accept the default

        // wipe the password
        Arrays.fill(password, '0');

        SSLServerSocketFactory factory
                = context.getServerSocketFactory();
        SSLServerSocket server
                = (SSLServerSocket) factory.createServerSocket(PORT);

        // add anonymous (non-authenticated) cipher suites
        String[] supported = server.getSupportedCipherSuites();
        String[] anonCipherSuitesSupported = new String[supported.length];
        int numAnonCipherSuitesSupported = 0;
        for (int i = 0; i < supported.length; i++) {
            if (supported[i].indexOf("_anon_") > 0) {
                anonCipherSuitesSupported[numAnonCipherSuitesSupported++]
                        = supported[i];
            }
        }
        String[] oldEnabled = server.getEnabledCipherSuites();
        String[] newEnabled = new String[oldEnabled.length
                + numAnonCipherSuitesSupported];
        System.arraycopy(oldEnabled, 0, newEnabled, 0, oldEnabled.length);
        System.arraycopy(anonCipherSuitesSupported, 0, newEnabled,
                oldEnabled.length, numAnonCipherSuitesSupported);
        server.setEnabledCipherSuites(newEnabled);

        System.out.println("OK..");

        // Now all the set up is complete and we can focus
        // on the actual communication.
        while (true) {
            // This socket will be secure,
            // but there's no indication of that in the code!
            try (Socket theConnection = server.accept()) {
                InputStream in = theConnection.getInputStream();
                Reader r = new InputStreamReader(in, "UTF-8");
                int c;
                while ((c = r.read()) != -1) {
                    System.out.write(c);
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    } catch (IOException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException ex) {
        ex.printStackTrace();
    }

The only diffence is in my code I create a Reader so the server can read characters. I tried this server with simple client that send text. Here is the Client:

int port = 7000; // default https port
    String host = "localhost";
    SSLSocketFactory factory
            = (SSLSocketFactory) SSLSocketFactory.getDefault();


    SSLSocket socket = null;
    try {
        socket = (SSLSocket) factory.createSocket(host, port);

        // enable all the suites
        String[] supported = socket.getSupportedCipherSuites();
        socket.setEnabledCipherSuites(supported);

        Writer out = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
        out.write("Hello");

    }catch(Exception e){

    }

I run the Server first and then the Cient. But when the Server accept input from Client it throws this exception:

java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:189)
at java.net.SocketInputStream.read(SocketInputStream.java:121)
...

UPDATE CLIENT Based on dave answer, I add 2 lines of code flush() and close()

...
out.write("Hello");
out.flush();
socket.close();
...

But another exception arrive:

javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
1

There are 1 answers

9
dave_thompson_085 On BEST ANSWER

OutputStreamWriter on a socket stream apparently buffers and your client didn't .flush() or .close() so your data isn't actually sent.

If your Java program (or more exactly JVM) exits without doing .close() on a socket stream (including closing a Writer which passes through to the Stream) handling depends on the platform; on Windows it sends a RST which causes the "Connection reset" exception you see at the peer. Unix closes the connection normally at the TCP level, which is actually not fully normal for SSL/TLS, but "close enough" (as it were) that Java treats it as EOF.

Edit for followon question:

Server getting SSLHandshakeException "received alert bad_certificatecertificate_unknown" which theoretically could mean a few things but almost always means that the certificate the server is using (from the keystore you loaded, along with matching privatekey) is not signed by a CA (Certificate Authority) trusted by the client.

The code you show for the client doesn't do anything to set or alter its truststore; if there isn't code elsewhere that does so, or external settings like the java commandline option -Dx=y to set system properties, the client will use the JSSE default truststore, which is the file JRE/lib/security/jssecacerts if it exists otherwise the file JRE/lib/security/cacerts (where JRE is the directory where your JRE is installed; if you use a JDK, the JRE is a subdirectory of the JDK directory). If you (and anyone else on your system) haven't modified these files since the JRE was installed, jssecacerts doesn't exist and cacerts contains a set of "well-known root" CAs determined by Oracle, like Verisign and Equifax etc.

Thus, you need to either:

  • use a certificate issued by a well-known CA; if you don't already have such a cert you have to obtain it from the CA by proving (at least) your control of the domain name(s) certified and depending on the CA possibly paying a fee; if you do have or get such a cert, install it in your keystore, in the privatekey entry, with any chain certs (for well-known CAs there almost always is at least one chain cert).

  • use a certificate issued by any other CA, including an adhoc CA you make up, and including as the limit case a selfsigned certificate which is implicitly its own CA, such as the one keytool -genkeypair generates automatically; and put the CA certificate for that CA (or that selfsigned cert) into the truststore used by the client. For that there are two ways:

    • put the server's CA cert (or selfsigned cert) in the default truststore file of the JRE used by the client. This does affect any other programs sharing that default truststore, which is potentially all other programs using that JRE. If you use jssecacerts it only affects JSSE, if you use cacerts it also affects the verification of signed code (if any), plus it gets wiped out if you upgrade your JRE in place, as usually is automatic on Windows.

    • create (or reuse) another truststore, put the server's CA cert in there, and have the client use that nondefault truststore. There are several options for that: set the system properties for the default truststore externally, set them explicitly in your program (before first use of JSSE!), explicitly load a "keystore" file (actually containing the cert(s)) and use its trustmanager in a nondefault SSLSocketFactory much like your server code does for keymanager, or even write your own trustmanager with whatever store(s?) you like and use that similarly.

Edit#2 Simple Example

Covering all these options in detail would be much too long, but one simple option is as follows.

  1. Generate keypair and (default) selfsigned certificate:

keytool -genkeypair -keystore ksfile -keyalg RSA

For the prompt "first and last name" (which is actually the CommonName attribute in the cert) enter the name of the server, in particular the name the client(s) will use to connect to the server; in the question this is "localhost". The other name fields don't matter; fill or omit them as you like, except that Country if used must be 2 letters as the prompt says. Instead of answering the prompts, you can add on the command line -dname "CN=common_name_value". If you have more than one name for the server(s) there are some options omitted here. For some other applications you may need to specify the entry name with -alias name; for this question it isn't needed.

  1. Get a copy of the certificate:

keytool -exportcert -rfc -keystore ksfile [-alias name] -file certfile

In this example the client is on the same machine as the server. In another case it would be necessary to copy the contents of this file from the server to the client; this is often done most conveniently by copying the file.

  1. Put cert in client truststore. As above there are many options, but the one you will probably see suggested the most often because it is usually the quickest to start is to just put it in the JRE default file, cacerts:

keytool -importcert -keystore JRE/lib/security/cacerts -file certfile

where JRE is the directory where your JRE (Java Runtime Environment) is installed. This depends on your OS and how you installed your JRE (or JDK, which includes a JRE) such as with a package manager or not. If you have more than one JRE and/or JDK installed, it depends which one you are using.

On Unix if you invoke java without specifying a path, which java (or in bash and perhaps other shells, type java) will tell you the full pathname that is run. Note however this is often a symbolic link to the real location, which should be in the form /somewhere/java[version]/bin/java; change the bin/java part to lib/security/cacerts.

On Windows, if you install a normal "system-wide" Java, the program you run is %windir%\system32\java.exe where windir is usually c:\windows but the actual code and files for JRE are in c:\program files\java\jreN or c:\program files (x86)\java\jreN depending on your architecture (32-bit or 64-bit) and jreN is currently jre7 or jre8 as applicable, but likely to expand in the future. Or run the Java Control Panel; in the Java tab the View button shows location(s) of the installed JRE(s).