如何解决官方的Oracle SSLSocketClient.java演示代码是否不安全?
从此link,给出SSLSocketClient.java的演示:
import java.net.*;
import java.io.*;
import javax.net.ssl.*;
/*
* This example demostrates how to use a SSLSocket as client to
* send a HTTP request and get response from an HTTPS server.
* It assumes that the client is not behind a firewall
*/
public class SSLSocketClient {
public static void main(String[] args) throws Exception {
try {
SSLSocketFactory factory =
(SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket =
(SSLSocket)factory.createSocket("www.verisign.com",443);
/*
* send http request
*
* Before any application data is sent or received,the
* SSL socket will do SSL handshaking first to set up
* the security attributes.
*
* SSL handshaking can be initiated by either flushing data
* down the pipe,or by starting the handshaking by hand.
*
* Handshaking is started manually in this example because
* PrintWriter catches all IOExceptions (including
* SSLExceptions),sets an internal error flag,and then
* returns without rethrowing the exception.
*
* Unfortunately,this means any error messages are lost,* which caused lots of confusion for others using this
* code. The only way to tell there was an error is to call
* PrintWriter.checkerror().
*/
socket.startHandshake();
PrintWriter out = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getoutputStream())));
out.println("GET / HTTP/1.0");
out.println();
out.flush();
/*
* Make sure there were no surprises
*/
if (out.checkerror())
System.out.println(
"SSLSocketClient: java.io.PrintWriter error");
/* read response */
BufferedReader in = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
out.close();
socket.close();
} catch (Exception e) {
e.printstacktrace();
}
}
}
我有两个问题:
- 根据this official document,如果我们使用的是原始SSLSocketFactory而不是HttpsURLConnection,则握手过程中不会强制执行主机名验证。因此,主机名验证应手动完成。
使用原始SSLSocket和SSLEngine类时,应始终在发送任何数据之前检查对等方的凭据。 SSLSocket和SSLEngine类不会自动验证URL中的主机名是否与对等方凭据中的主机名匹配。如果未验证主机名,则可以通过URL欺骗利用应用程序。从JDK 7开始,可以在SSL / TLS握手期间处理端点标识/验证过程。请参阅SSLParameters.getEndpointIdentificationAlgorithm方法。
这是否意味着演示不安全?
-
我看到了在Java 7中添加主机名验证的解决方案,如下所示:
SSLParameters sslParams = new SSLParameters(); sslParams.setEndpointIdentificationAlgorithm(“ HTTPS”); sslSocket.setSSLParameters(sslParams);
当算法指定为“ HTTPS”时,握手将验证主机名。否则(仅使用原始SSLSockeFactory的算法为空),根本未调用主机名验证。 我很好奇我可以解决如下问题:
SSLSocketFactory factory =
(SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket =
(SSLSocket)factory.createSocket("www.verisign.com",443);
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
if(!hv.verify(socket.getSession().getPeerHost(),socket.getSession())){
threw CertificateException("Hostname does not match!")
}
我看到HttpsURLConnection.getDefaultHostnameVerifier()
可以返回默认的HostnameVerifier,我可以用它来进行验证吗?我看到很多人在谈论使用自定义HostnameVerifier。我不知道是否存在默认值,为什么我们需要自定义它?
解决方法
边界线作为答案,但是评论太长了。
(1)是,对于HTTPS (如您所引用的内容后面的段落中所述),这是一个安全漏洞;此示例可能是在Java 7之前编写的,此后未进行更新。您可以提交错误报告,让他们进行更新。 (当然,有些使用SSL / TLS应用程序不验证主机名,例如SNMPS和LDAPS,甚至没有 URL,但仍可以实现使用Java JSSE。)
(2)HTTP也错误或不正确:
-
PrintWriter使用的JVM的lineSeparator随平台而异,但是HTTP标准(RFC 2068、2616、7230)在所有平台上均要求CRLF作为请求标头,尽管某些服务器(可能包括google)仅接受-LF遵循传统的Postel格言:“在发送邮件时保持保守,在接收邮件时保持自由”;
-
读取端假定所有数据都是面向行的,并且不会因规范化EOL而损坏,对于HTTP header 和 some 主体(例如当请求没有接受(或接受编码)但不能保证时,您将从大多数 Web服务器获取的文本/ html;
-
读取端还假设可以安全地从JVM默认“字符集”解码所有数据并将其重新编码为;这对于HTTP标头(实际上是7位ASCII)是正确的,但不是很多/大多数主体:特别是处理8859或类似UTF8的东西会破坏掉其中的大部分,而处理UTF8像8859或CP1252则会把它弄乱。
(3)尽管HTTP / 1.0仍然得到广泛支持并且使演示变得更加简单,但它正式被淘汰了,所以让我放一张幻灯片。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。