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

Java-SocketException:客户端发送请求时重置连接,java,ssl,jsse,Java,Ssl,Jsse,在关于安全套接字的第10章中,有一个构建安全服务器的示例。代码可以找到 我正在尝试使代码更简单。这是我的密码: try { SSLContext context = SSLContext.getInstance(algorithm); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); KeyStore ks = KeyStore.getInstance("JKS")

在关于安全套接字的第10章中,有一个构建安全服务器的示例。代码可以找到

我正在尝试使代码更简单。这是我的密码:

 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();
    }
我先运行服务器,然后运行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()

但另一个例外是:

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

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

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

编辑以下问题的

服务器获取SSLHandshakeException“已接收警报错误\u证书证书证书\u未知”这在理论上可能意味着一些事情,但几乎总是意味着服务器正在使用的证书(来自您加载的密钥库,以及匹配的私钥)没有由CA(证书颁发机构)签名受客户信任

为客户机显示的代码不会设置或更改其信任库;如果其他地方没有这样做的代码,或者像
java
commandline选项
-Dx=y
这样的外部设置来设置系统属性,客户端将使用JSSE默认信任库,其中是文件
JRE/lib/security/jssecacerts
,如果存在,则是文件
JRE/lib/security/cacerts
(其中JRE是安装JRE的目录;如果使用JDK,则JRE是JDK目录的子目录)。如果您(以及您系统上的任何其他人)在安装JRE后没有修改这些文件,
jssecacerts
不存在,
cacerts
包含一组由Oracle确定的“已知根”CA,如Verisign和Equifax等

因此,您需要:

  • 使用由著名CA颁发的证书;如果您尚未拥有此类证书,您必须通过证明(至少)您对已认证域名的控制权,并根据CA可能支付的费用,从CA处获得证书;如果您确实拥有或获得了这样的证书,请将其安装在您的密钥库中的privatekey条目中,并带有任何链证书(对于著名的CA,几乎总是至少有一个链证书)

  • 使用由任何其他CA颁发的证书,包括您组成的临时CA,以及包括作为限制情况的自签名的证书,该证书隐式地是其自己的CA,例如自动生成的
    密钥工具-genkeypair
    ;并将该CA(或自签名证书)的CA证书放入客户端使用的信任库中。为此,有两种方法:

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

    • 创建(或重用)另一个信任库,将服务器的CA证书放入其中,并让客户端使用该非默认信任库。有几个选项可供选择:从外部设置默认信任库的系统属性,在程序中显式设置它们(在首次使用JSSE之前!),显式加载“密钥库”文件(实际上包含证书),并在非默认SSLSocketFactory中使用其trustmanager,就像服务器代码对keymanager所做的那样,或者甚至用你喜欢的商店编写你自己的trustmanager,并以类似的方式使用

编辑#2个简单示例

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

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

    对于提示“first and last name”(实际上是cert中的CommonName属性),输入服务器的名称,特别是客户端将用于连接到服务器的名称;在问题中,这是“localhost”。其他名称字段并不重要;根据您的喜好填写或省略它们,但如提示所示,该国家/地区(如果使用)必须为2个字母。您可以在命令行
    -dname“CN=common\u name\u value”
    上添加,而不是回答提示。如果您有多个服务器名称,这里会省略一些选项。 对于某些其他应用程序,您可能需要使用
    -别名
    指定条目名称;对于这个问题,它是不需要的

  • 获取证书的副本:
  • keytool-exportcert-rfc-keystore ksfile[-别名]-文件certfile

    在本例中,客户机与服务器位于同一台机器上。在另一种情况下,需要将此文件的内容从服务器复制到客户端;这通常通过复制文件来实现

  • 将证书放入客户端信任库。如上所述,有很多选择,但你可能会看到的一个建议最经常,因为它通常是快速
    ...
    out.write("Hello");
    out.flush();
    socket.close();
    ...
    
    javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown