I have got a server with self-signed certificate. I've imported it with a keytool on my computer and use
-Djavax.net.ssl.trustStore=blabla
compile argument. When I try to run the following code:
SSLSocketFactory factory = HttpsURLConnection.getDefaultSSLSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket("MY_URL_DIGITS", 443);
OutputStream os = socket.getOutputStream();
os.write("Test request \n".getBytes());
os.flush();
os.close();
Everything goes alright and I can see the "Test request" on the server. However, when I run:
URL url = new URL("https://MY_URL_DIGITS");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
OutputStream os = con.getOutputStream();
os.write("Test request \n".getBytes());
os.flush();
os.close();
I've got the
javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names present
So what is the principal difference between these two snippets?
SSLSocket by default only checks whether you trust the certificate. HttpsURLConnection checks whether you trust the certificate and also checks whether the certificate says it is coming from the same place you actually navigated to. For your HttpsURLConnection to succeed, the certificate would have to specify a subject alternative name (SAN) that was the same as the server you are conecting to. In this case the SAN would need to be "dns:MY_URL_DIGITS", where the "dns" part says you are specifying a host name rather than an IP address.
If you need additional information on how to create a certificate with a subject alternative name, see:
how to add subject alernative name to ssl certs?
The difference is that HTTPS adds a step, which can be seen in the HostnameVerifier interface. It is trying to match the hostname being connected to with the hostname in the SubjectDN or alternative names.
Related
I use SSLSocket for my android app
and according to this post SSLSocket doesn't perform host name verification
here
and I didn't see any exception while using IP instead of domain and everything works fine
so can I use IP or any other domain for connection?
I have got the cert from letsencrypt for one domain and I wonder that I can use it for any domain or IP with SSLSocket!
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, nulls, null);
SocketFactory sslsocketfactory = sc.getSocketFactory();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(host, 443);
sslsocket.setSoTimeout(5000);
sslsocket.startHandshake();
The answer is on your link:
Your app needs to do its own hostname verification, preferably by calling
getDefaultHostnameVerifier() with the expected hostname. Further,
beware that HostnameVerifier.verify() doesn't throw an exception on
error but instead returns a boolean result that you must explicitly
check.
"I wonder that I can use it for any domain or IP with SSLSocket!"
No, you will not be able to do that, the cert of the connection must be in your client's truststore, and we can expect that the different domains use different certificates :)
Please see this How should I do hostname validation when using JSSE? link.
I am getting this 'HTTPS hostname wrong:' error when trying to connect to a server using https. My url looks something like this
https://sub.domain.com/tamnode/webapps/app/servlet.
I connect using the following code
// Create a URLConnection object for a URL
URL url = new URL(requestedURL);
HttpURLConnection.setFollowRedirects(false);
// connect
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("User-Agent", USER_AGENT); //$NON-NLS-1$
OutputStreamWriter wr = new OutputStreamWriter(connection
.getOutputStream());
but then get an error
IOException: HTTPS hostname wrong: should be <sub.domain.com>.
at sun.net.www.protocol.https.HttpsClient.checkURLSpoofing
....
This is code which has worked in the past but no longer. There have been some changes to the system architecture but I need to get more data before approaching those responsible.
What can cause this error? Can I turn off the URLSpoofing check?
It looks like the SSL certificate for domain.com has been given to sub.domain.com. Or, more likely, what was domain.com has been renamed to sub.domain.com without updating the SSL certificate.
cletus is right about the probable cause.
There is a way to turn off the spoof checking, too.
You can create an object that implements HostnameVerifier that returns true under more circumstances than 'usual'.
You would replace the default HostnameVerifier by calling setHostnameVerifier on the connection object in the code in the question.
This answer was 'inspired by': http://www.java-samples.com/showtutorial.php?tutorialid=211
I found that link with this query: http://www.google.com/search?q=https+hostname+wrong+should+be
One more note: think twice before you do this. You will create an exploitable weakness in the security between your client and server components.
I got this exception - java.io.IOException: HTTPS hostname wrong: should be <localhost>.
My solution is I changed my self-signed certificate and make the CN=localhost.
OR
Add your certificate domain-name cn=<domain-name> to your host file probably located at c:/windows/system32/drivers/etc/...
The following code resolved my problem
static {
//for localhost testing only
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier() {
#Override
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
if (hostname.equals("your_domain")) {
return true;
}
return false;
}
});
}
Java by default verifies that the certificate CN (Common Name) is the same as hostname in the URL. If the CN in the certificate is not the same as the host name, your web service client fails with the following exception: java.io.IOException: HTTPS hostname wrong: should be hostname as in the certificates.
This is just an alternative of 'svarog' post
static {
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> hostname.equals("domain name"));
}
Use host name (dns name) as Alias name.
Ex:
keytool -import -alias <google.com> -file certificate_one.cer -keystore cacerts
I'm working on an application that processes food orders and we send the requests via HttpsURLConnection to a php function that has been ssl certified. The problem I'm having is that it rejects the handshake sometimes and not others. I was wondering if anyone could explain to me why it would reject it one time and not another.
javax.net.ssl.SSLProtocolException: SSL handshake aborted:
ssl=0x56cbe008: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:744
0x52eb6d74:0x00000000)
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted:
ssl=0x56cbe008: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:744
0x52eb6d74:0x00000000)
url = new URL(request.endpointUri);
Log.d(LOG_TAG, "Opening connection to " + request.endpointUri);
conn = (HttpsURLConnection)url.openConnection();
//setup the connection
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("charset", "UTF-8");
conn.setDoInput(true);
conn.setDoOutput(true);
//setup the parameters
Uri.Builder params = new Uri.Builder();
String paramString;
params.appendQueryParameter("cctoken", request.token.getId());
params.appendQueryParameter("amt", Integer.toString(request.order.amount));
params.appendQueryParameter("email", request.order.customerEmail);
params.appendQueryParameter("order", request.order.details);
paramString = params.build().getEncodedQuery();
conn.setFixedLengthStreamingMode(paramString.getBytes("UTF-8").length);
Log.d(LOG_TAG, "Compiled query into: " + paramString);
//write the POST request params
OutputStream os = conn.getOutputStream();
BufferedWriter streamWriter = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
streamWriter.write(paramString);
streamWriter.flush();
streamWriter.close();
os.close();
//read the response
int responseCode = conn.getResponseCode();
InputStream is;
The line given for failure is when it attempts to collect the output.
OutputStream os = conn.getOutputStream();
SSL handshake errors which only happen sometimes are often related to server side problems, so your code is not of much help here. Possible causes at the server side are multiple servers with different configuration (some work, some don't), timeouts which might be caused by too much load, server side crashes. There might also be some erratic middleware involved (firewalls) or if the connection is unreliable from start it will also affect the SSL handshake.
Thus don't look too much at your code but look at the server and the network. If in doubt try another client and if this one shows a more stable behavior look into the differences in the connection and SSL handshake (i.e. packet captures).
This problem could be due to self signed certificate of server to which you are sending request. You have to do something like this:
`// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("load- der.crt"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
//Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
(HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);`
Found it here
Related Issue: Client and server don't support a common encryption protocol
If the error message says something like:
error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version (external/openssl/ssl/s23_clnt.c:741 0x8d92d990:0x00000000)
(Note that is says 'tlsv1' after 'GET_SERVER_HELLO:' rather than 'sslv3') This is a faint clue that the problem could be with the version of encryption needed. If the client is old, it may only support sslv2 or sslv3. An up to date server may support TLS1.2 and does not support the older (perhaps depracated ssl versions. The opposite may be true as well, with the server supporting only the old and the client only the new.
I ran into this problem with an old Android Jelly Bean client that doesn't support TLS1.2, by default, for HttpsUrlConnection. By creating a TLSSocketFactory and X509TrustManager, and calling setSSLSocketFactory() I could get the old version of Android to use TLS1.2, which made the server happy. Navneet Krishna wrote a good description of how to do this in: https://medium.com/#krisnavneet/how-to-solve-sslhandshakeexception-in-android-ssl23-get-server-hello-tlsv1-alert-protocol-13b457c724ef
The following code is the common way of establishing a connection to create an array of certificates by given a URL link (that I use it in my program):
URL destinationURL = new URL("https://www.google.com");
HttpsURLConnection con = (HttpsURLConnection) destinationURL.openConnection();
con.connect();
Certificate[] certs = con.getServerCertificates();
My question is how con.getServerCertificates() really imports all the certificates chanining into Java from given a URL link, does con.getServerCertificates() always set a SSL connection to the webpage and import all the certificates chaining into an array OR does it just use (cacerts file) that comes with JKD ?
It connects to the server and gets the certificates.
There's no way it could have all the certificates already stored in a file, because then Java would have to update every time a certificate was added, removed or replaced anywhere on the Internet. And that obviously doesn't happen.
I have the java code like this :
URL url = new URL(endPoint);
String encoding = Base64.encodeBase64String(this.key.getBytes());
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
which is opening a ssl connection. Lets say the endPoint does uses a self-singed certificate and act as a original website. Is it possible to prevent these sort of things in the java code?
Thanks in advance.
By default, the SSL implementation in Java checks against a list of trusted certification authorities, which is included in the Java VM. Unless you extend the default trust store, specify a different trust store at run time or provide your own implementation of a TrustManager and/or HostnameVerifier, you will not be able to make an SSL connection to a server with a self-signed certificate.
If you for some reason need access to the server certificates after you have established the connection, you can get these from an HttpsURLConnection like this:
URL url = new URL("https://www.google.com");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.connect();
for(Certificate crt : conn.getServerCertificates()) {
System.out.println(crt);
}