在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
答案 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简单示例
详细介绍所有这些选项会太长,但一个简单选项如下。
keytool -genkeypair -keystore ksfile -keyalg RSA
提示&#34;名字和姓氏&#34; (实际上是cert中的CommonName属性)输入服务器的名称,特别是客户端用于连接服务器的名称;在这个问题中,这是&#34; localhost&#34;。其他名称字段并不重要;根据需要填写或省略它们,但如果使用的国家/地区必须是2个字母,如提示所示。您可以添加命令行-dname "CN=common_name_value"
,而不是回答提示。如果服务器有多个名称,则此处省略了一些选项。
对于某些其他应用程序,您可能需要使用-alias name
指定条目名称;对于这个问题,我们并不需要。
keytool -exportcert -rfc -keystore ksfile [-alias name] -file certfile
在此示例中,客户端与服务器位于同一台计算机上。在另一种情况下,有必要将该文件的内容从服务器复制到客户端;这通常通过复制文件来最方便地完成。
keytool -importcert -keystore JRE/lib/security/cacerts -file certfile
其中JRE是安装JRE(Java运行时环境)的目录。这取决于您的操作系统以及如何安装JRE(或JDK,包括JRE),例如是否包装管理器。如果安装了多个JRE和/或JDK,则取决于您使用的是哪一个。
在Unix上如果你在没有指定路径的情况下调用java
,which 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\jreN
或c:\program files (x86)\java\jreN
,具体取决于您的体系结构(32位或64位)和jreN目前适用jre7
或jre8
,但将来可能会扩展。或者运行Java控制面板;在Java选项卡中,View按钮显示已安装JRE的位置。