Java - SocketException:客户端发送请求时连接重置

时间:2015-06-26 04:24:05

标签: java ssl jsse

Java Network Programming 4th Edition,第10章关于安全套接字,有一个构建安全服务器的例子。代码可以找到here

我正在尝试制作更简单的代码版本。这是我的代码:

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

唯一的不同之处在于我的代码中我创建了一个Reader,因此服务器可以读取字符。 我尝试使用简单的客户端发送文本服务器。这是客户:

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){

    }

我首先运行Server,然后运行Cient。但是当服务器接受来自客户端的输入时,它会抛出此异常:

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

更新客户 根据dave答案,我添加了两行代码flush()close()

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

但又有一个例外:

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

1 个答案:

答案 0 :(得分:2)

套接字流上的OutputStreamWriter显然是缓冲区,而您的客户端没有.flush().close() ,因此您的数据实际上并未发送。

如果您的Java程序(或更确切地说是JVM)退出而没有在套接字流上执行.close() (包括关闭传递给Stream的Writer)处理取决于平台;在Windows上,它会发送一个RST,导致您在对等端看到的&#34; Connection reset&#34; 异常。 Unix通常在TCP级别关闭连接,这对于SSL / TLS实际上并不完全正常,但是&#34;足够接近&#34; (因为它)Java将其视为EOF。

跟随问题

编辑

服务器获取SSLHandshakeException &#34;收到警告 bad_certificate certificate_unknown&#34; 哪些理论上可能意味着一些事情,但几乎总是意味着服务器正在使用的证书(来自您加载的密钥库以及匹配的私钥)未由客户端信任的CA(证书颁发机构)签名。

您为客户显示的代码没有做任何事情来设置或更改其信任库;如果其他地方没有代码,或者java命令行选项-Dx=y等外部设置来设置系统属性,则客户端将使用JSSE 默认信任库 ,如果存在,则为文件JRE/lib/security/jssecacerts,否则为文件JRE/lib/security/cacerts(其中JRE是安装JRE的目录;如果使用JDK,则JRE是JDK目录的子目录)。如果您(以及系统中的其他任何人)在安装JRE后仍无法修改这些文件,jssecacerts不存在且cacerts包含一组&# 34;着名的根&#34; CA确定CA ,如Verisign和Equifax等。

因此,你需要:

  • 使用知名CA 颁发的证书;如果您还没有这样的证书,您必须通过证明(至少)您对经过认证的域名的控制权并取决于CA可能支付的费用从CA获得该证书;如果您拥有或获得此类证书,请将其安装在您的密钥库中, in 私钥条目,使用任何链证书(对于知名CA,几乎总是在至少一个连锁证书。)

  • 使用由任何其他CA 颁发的证书,包括您组成的adhoc CA,以及包括作为限制案例自签名证书,它隐含着自己的CA,例如keytool -genkeypair自动生成的证书;并将该CA(或自签名证书)的CA证书放入客户端使用的信任库。为此,有两种方式:

    • 将服务器的CA证书(或自签名证书)放在客户端使用的JRE的默认信任库文件中。这确实会影响共享该默认信任库的任何其他程序,这可能是使用该JRE的所有其他程序。如果你使用jssecacerts它只影响JSSE,如果你使用cacerts它也会影响签名代码的验证(如果有的话),如果你升级你的JRE,它会被删除,通常是在Windows上自动。

    • 创建(或重用)另一个信任库,将服务器的CA证书放在那里,让客户端使用该非默认信任库。有几种选择:在外部设置默认信任库的系统属性,在程序中明确设置它们(在首次使用JSSE之前!),显式加载&#34;密钥库&#34;文件(实际上包含证书)并在非默认SSLSocketFactory中使用其信任管理器,就像您的服务器代码为keymanager一样,或者甚至用您喜欢的任何商店(s?)编写自己的信任管理器并使用它们。

编辑#2简单示例

详细介绍所有这些选项会太长,但一个简单选项如下。

  1. 生成密钥对和(默认)自签名证书:
  2. keytool -genkeypair -keystore ksfile -keyalg RSA

    提示&#34;名字和姓氏&#34; (实际上是cert中的CommonName属性)输入服务器的名称,特别是客户端用于连接服务器的名称;在这个问题中,这是&#34; localhost&#34;。其他名称字段并不重要;根据需要填写或省略它们,但如果使用的国家/地区必须是2个字母,如提示所示。您可以添加命令行-dname "CN=common_name_value",而不是回答提示。如果服务器有多个名称,则此处省略了一些选项。 对于某些其他应用程序,您可能需要使用-alias name指定条目名称;对于这个问题,我们并不需要。

    1. 获取证书副本:
    2. keytool -exportcert -rfc -keystore ksfile [-alias name] -file certfile

      在此示例中,客户端与服务器位于同一台计算机上。在另一种情况下,有必要将该文件的内容从服务器复制到客户端;这通常通过复制文件来最方便地完成。

      1. 将cert放在客户端信任库中。如上所述,有许多选项,但您可能会看到最常见的选项,因为通常最快的方法是将它放在JRE默认文件中,cacerts:
      2. keytool -importcert -keystore JRE/lib/security/cacerts -file certfile

        其中JRE是安装JRE(Java运行时环境)的目录。这取决于您的操作系统以及如何安装JRE(或JDK,包括JRE),例如是否包装管理器。如果安装了多个JRE和/或JDK,则取决于您使用的是哪一个。

        在Unix上如果你在没有指定路径的情况下调用javawhich java(或者在bash和其他shell中,type java)会告诉你运行的完整路径名。但请注意,这通常是指向真实位置的符号链接,其形式应为/somewhere/java[version]/bin/java;将bin/java部分更改为lib/security/cacerts

        在Windows上,如果您在系统范围内安装正常的&#34;&#34; Java,您运行的程序是%windir%\system32\java.exe,其中windir通常为c:\windows,但JRE的实际代码和文件位于c:\program files\java\jreNc:\program files (x86)\java\jreN,具体取决于您的体系结构(32位或64位)和jreN目前适用jre7jre8,但将来可能会扩展。或者运行Java控制面板;在Java选项卡中,View按钮显示已安装JRE的位置。