From this link, a demo for SSLSocketClient.java is given:
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();
}
}
}
I have two questions:
- According to this official document, if we are using a raw SSLSocketFactory rather than the HttpsURLConnection, there is no hostname verification enforced in the handshake process. Therefore, hostname verification should be done manually.
When using raw SSLSocket and SSLEngine classes, you should always check the peer's credentials before sending any data. The SSLSocket and SSLEngine classes do not automatically verify that the host name in a URL matches the host name in the peer's credentials. An application could be exploited with URL spoofing if the host name is not verified. Since JDK 7, endpoint identification/verification procedures can be handled during SSL/TLS handshaking. See the SSLParameters.getEndpointIdentificationAlgorithm method.
Does it mean the demo is insecure?
I saw a solution to add hostname verification in Java 7 as:
SSLParameters sslParams = new SSLParameters(); sslParams.setEndpointIdentificationAlgorithm("HTTPS"); sslSocket.setSSLParameters(sslParams);
When the algorithm is specified as "HTTPS", the handshake will verify the hostname. Otherwise (the algorithm is empty only using raw SSLSockeFactory), the hostname verification has not been invoked at all. I curious about could I fix it as follows:
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!")
}
I saw the HttpsURLConnection.getDefaultHostnameVerifier() can return a default HostnameVerifier, can I use it to do verification? I saw many people talking about use a custom HostnameVerifier. I don't understand if there is a default one why we need to customize it?
Borderline as an answer but got much too long for comments.
(1) yes, for HTTPS (as noted in the paragraph after the one you quoted) this is a security flaw; probably this example was written before Java 7 and not updated since. You could file a bug report for them to update it. (Of course there are some using SSL/TLS applications that don't validate hostname, like SNMPS and LDAPS, and don't even have URLs, but can still be implemented using Java JSSE.)
(2) the HTTP is wrong or poor also:
PrintWriter uses the JVM's lineSeparator which varies by platform, but HTTP standards (RFCs 2068, 2616, 7230) require CRLF for request header(s) on all platforms, though some servers (probably including google) will accept just-LF following the traditional Postel maxim 'be conservative in what you send and liberal in what you receive';
the read side assumes all data is line-oriented and won't be damaged by canonicalizing EOLs, which is true for HTTP header and some bodies like the text/html you will get from most webservers when request has no Accept (or Accept-encoding), but is not guaranteed;
the read side also assumes all data can be decoded from and re-encoded to the JVM default 'charset' safely; this is true for HTTP header (which is effectively 7-bit ASCII) but not many/most bodies: in particular handling 8859 or similar as UTF8 will destroy much of it, and handling UTF8 as 8859 or CP1252 will mojibake it.
(3) HTTP/1.0 is officially obsolete, although it is still widely supported and makes a significantly simpler demo, so I'd let that one slide.