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
Related
I'm putting together a soap client to call a thirdparty soap service. I'm having issues connecting with Java. It works fine with SoapUI. This is the first time I've set up a keystore within the app. All the code I have found is the same and pretty simple but I can't figure out why the java version isn't working.. I'm using a TLS pfx file provided by the company whose service I'm trying to connect too.
I'm getting a 403 back from the server.. Here is the code
URL wsdlLocation = new URL(SECURE_INTEGRATION_WSDL);
ObjectFactory ofactory = new ObjectFactory();
HttpsURLConnection httpsConnection = (HttpsURLConnection)wsdlLocation.openConnection();
char[] password = CLIENT_KEYSTORE_PASSWORD.toCharArray();
//load keystore
FileInputStream is = new FileInputStream(new File(CLIENT_KEYSTORE_PATH));
final KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(is, password);
is.close();
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keystore, password);
//set the ssl context
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null,
new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(sc.getSocketFactory());
SecureIntegrationServicesImap client = new SecureIntegrationServicesImap(wsdlLocation);
SesMessage message = ofactory.createSesMessage();
ReceiveRequest r = ofactory.createReceiveRequest();
r.setEmail(ofactory.createReceiveRequestEmail("<email ommitted>"));
ArrayOfMessageSummary messages = client.getWSHttpBindingSecureIntegrationServiceImap().getMessageList(r);
log.info(messages.getMessageSummary().size());
Any help with what I'm wrong is greatly appreciated..
Not sure if it matters but the server is a .NET platform
Here is the stacktrace I'm getting
javax.xml.ws.WebServiceException: Failed to access the WSDL at: https://<host omitted>/TS?wsdl. It failed with:
Server returned HTTP response code: 403 for URL: https://<host omitted>/TS?wsdl.
at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.tryWithMex(RuntimeWSDLParser.java:265)
at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:246)
at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:209)
at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:178)
at com.sun.xml.ws.client.WSServiceDelegate.parseWSDL(WSServiceDelegate.java:363)
at com.sun.xml.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:321)
at com.sun.xml.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:230)
at com.sun.xml.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:211)
at com.sun.xml.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:207)
at com.sun.xml.ws.spi.ProviderImpl.createServiceDelegate(ProviderImpl.java:114)
at javax.xml.ws.Service.<init>(Service.java:77)
at org.tempuri.SecureIntegrationServicesImap.<init>(SecureIntegrationServicesImap.java:50)
at com.wiredinformatics.utils.SecureExchange.main(SecureExchange.java:127) Caused by: java.io.IOException: Server returned HTTP response code: 403 for URL: https://host omitted/TS?wsdl
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1876)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
at java.net.URL.openStream(URL.java:1045)
at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.createReader(RuntimeWSDLParser.java:999)
at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.resolveWSDL(RuntimeWSDLParser.java:400)
at com.sun.xml.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:231)
... 11 more
It sounds like you're using TLS based client authentication. Based on the code you posted I suspect the issue is that you're not using httpsConnection anywhere after you initialize it. Therefore it's not trying to use your client certificate as you were expecting but is instead using the default request context settings.
Assuming you're using JAX-WS you should be able to use the solution outlined in this answer to bind your certificate to your request context (instead of initializing your own HttpsURLConnection):
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
I just setup SSL certs on my server. I am pretty sure they are setup right. When go to https://mydomain.com/myapp in the browser the page loads correctly and the Green Lock shows in the address bar.
Doing a POST from Firefox>POSTER to this HTTPS url I get a valid response.
If I do the same POST from my Java client I get the following error:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
On my server I have put the CA_ROOT certs in the JAVA.../jre/lib/security/cacert keystore.
Here is my posting code from my Java client.
URL url = new URL(Global.SERVER_URL);
HttpsURLConnection connection = null;
connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setReadTimeout(45000);
connection.setRequestProperty("Content-Type", "text/json");
connection.connect();
PLease Note: THIS IS NOT A SELF SIGNED CERT. It was issued by a CA
I have a feeling that Glassfish is not sending the full Cert Chain. I looked at the cert that the browser got and it was the full cert chain. I looked at the SSL error and that was only the Intermediate Cert and that for my domain.
How do I get Glassfish to send the full Cert chain?
Check SSL Chain
openssl.exe s_client -connect mydomain.com:443
Returns
WARNING: can't open config file: /usr/local/ssl/openssl.cnf
Loading 'screen' into random state - done
CONNECTED(00000190)
depth=0 C = US, ST = <edited>, L = <edited>, O = <edited>, OU = <edited>, CN = <edited>
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = US, ST = <edited>, L = <edited>, O = <edited>, OU = <edited>, CN = <edited>
verify error:num=27:certificate not trusted
verify return:1
depth=0 C = US, ST = <edited>, L = <edited>, O = <edited>, OU = <edited>, CN = <edited>
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:/C=US/ST=<edited>/L=<edited>/O=<edited>/OU=<edited>/CN=<edited>
i:/O=Cybertrust Inc/CN=Cybertrust Public SureServer SV CA
---
Server certificate
-----BEGIN CERTIFICATE-----
<edited>
-----END CERTIFICATE-----
subject=/C=US/ST=<edited>/L=<edited>/O=<edited>/OU=<edited>/CN=<edited>
issuer=/O=Cybertrust Inc/CN=Cybertrust Public SureServer SV CA
---
No client certificate CA names sent
---
SSL handshake has read 1676 bytes and written 513 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: <edited>
Session-ID-ctx:
Master-Key: <edited>
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1410274974
Timeout : 300 (sec)
Verify return code: 21 (unable to verify the first certificate)
---
read:errno=0
SOLUTION
Accepted Answer from Bruno for the Concept
Additional Details:
Get Keystore Explorer. Its a fantastic GUI tool to browse keystores.
Open keystore.jks with Keystore Explorer.
Right Click the Cert in question (mydomain) and View Details>Certificate Chain Details.
if it does not show the full Cert chain then add it by right clicking the cert Edit Certificate Chain>Append Certificate.
Restart Glassfish.
Browsers and Java use different set of trusted root certificates, by default Browsers get this info from OS, Java supports own list, that's why it could be green in Browser and doesn't support in Java
To check what certificates support your version of Java:
<jre_path>\bin\keytool -keystore "<jre_path>\lib\security\cacerts" -storepass changeit -list
And if you don't see your certificate just add it to <jre_path>\lib\security\cacerts file.
---
Certificate chain
0 s:/C=US/ST=<edited>/L=<edited>/O=<edited>/OU=<edited>/CN=<edited>
i:/O=Cybertrust Inc/CN=Cybertrust Public SureServer SV CA
---
Unlike what you said in a comment, and according to this output, your server isn't sending any intermediate certificate. The certificate chain only has one certificate (at position 0): the server certificate.
On my server I have put the CA_ROOT certs in the JAVA.../jre/lib/security/cacert keystore.
Adding the intermediate certificates in the server side's truststore will have no effect on the chain presented by the server (it's only used to authenticate clients, if applicable).
You'd need to make sure your keystore entry is set up with the full chain, as described in this question, following the same procedure as this answer.
What you see in the browser may well be the chain it has reconstructed, and the browser may also know these intermediate CA certs, where the JRE may not (different sets of trust anchors are used): you'll only increase the chances of the default JRE accepting your server certificate by presenting the full chain correctly. (Note that you don't need to present the root CA cert itself in the chain, it doesn't harm, but it's just network overhead.)
SSLHandshakeException:
comes when a failed connection would look something like this. Usually indicating that there is some type of certificate validation issue, most likely your Truststore doesn't contain the Trusted root certificates that it needs.And
Indicates that the client and server could not negotiate the desired level of security. The connection is no longer usable.
javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building
failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
Example in detail to Send an HTTP POST request by using below code .
private void sendPost() throws Exception {
String url = "https://selfsolve.apple.com/wcResults.do";
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
//add reuqest header
con.setRequestMethod("POST");
con.setRequestProperty("User-Agent", "Mozilla/5.0");
con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
con.setRequestProperty("Content-Type", "text/json");
String urlParameters = "sn=C02G8416DRJM&cn=&locale=&caller=&num=12345";
// Send post request
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
int responseCode = con.getResponseCode();
System.out.println("\nSending 'POST' request to URL : " + url);
System.out.println("Post parameters : " + urlParameters);
System.out.println("Response Code : " + responseCode);
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
//print result
System.out.println(response.toString());
}
if response code is 200, means looks good.
for more detail to solve same exception refer below links.
How to solve javax.net.ssl.SSLHandshakeException Error?
Test trusted certificate
Test a Java SSL connection
Recently I work on the project that has implemented the SSL.
The SSL cert is expire once per year. And it throw exception in android after I renew the cert on the server.
06-13 11:20:27.709: D/allenj(30076): javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
After I looking through the project code, I saw there is a bks file, so , does it mean I have to update the bks file once per year, and I have to re-upload the app to google play as well.
The problem is what is the standard way to cope with the renewal of the SSL cert? Thanks for helping.
Code extract
nnable Register_runnable = new Runnable(){
#Override
public void run() {
EditText emailText = (EditText) findViewById(R.id.editText1regist);
EditText pwText = (EditText) findViewById(R.id.editText2registpw);
String end = "\r\n";
String twoHyphens = "--";
String boundary = "*****";
try {
KeyStore keyStore = KeyStore.getInstance("BKS");
InputStream in =
getResources().openRawResource(R.raw.ballooncardbks);
keyStore.load(in, "".toCharArray());
TrustManagerFactory tmf =
TrustManagerFactory.getInstance("X509");
tmf.init(keyStore);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
String actionUrl = "https://app.ballooncard.com/api/client/register/format/json";
URL url = new URL(actionUrl);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
// con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);
con.setRequestMethod("POST");
con.setSSLSocketFactory(context.getSocketFactory());
con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Charset", "UTF-8");
con.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
Looks like the app is using "certificate pinning", which means that a certificate has been hardcoded into the app, and the app has been instructed to accept only that certificate and no other.
This increases security at the expense that you need to update your app when (ideally before) the certificate expires. You can following the instructions from a post I created here:
https://stackoverflow.com/a/24007536/276949
to generate a new .bks file from your certificate. Once this is done, overwrite your old .bks file and your app should successfully connect via SSL.
Apps by default trust the pre-installed system Certificate Authorities (CA), like this example:
https://www.digitalocean.com/community/tutorials/retrofit-android-example-tutorial
Check if you really need to use certificate pinning (https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning) in your application.
In case you want to disable Certificate Pinning, delete android:networkSecurityConfig line from AndroidManifest.xml and/or comment domain-config block from network_security_config.xml
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.
I have a Javafx application that sends GET and POST requests to a secure web service via HTTPS. The SSL setup on the server hosting the webservices is one-way ssl, i.e. the Javafx application validates the server's identity but the server does not validate the thick client's identity.
The application server is behind an F5 that has the certificates(signed by an external authority).
For a browser this would not have been a problem as the browser itself handles validating the server's identity and displays the relevant warning to the user. But for a thick client, I am not sure how to validate the server's identity before sending the request. Please let me know how to handle this in a Javafx application.
I did ask a question relating to this earlier here and here, but those did not help. So, please pardon my limited knowledge on this topic.
Any help would be appreciated.
If your certificate don't work in Firefox/java, most likely it issuer is unknown by Firefox/java.
How to make it work:
Get full certificate chain of your server. You can do it with Firefox. View certificate -> details-> export to .pem file. In your case chain will contain at least 2 certificate (cerver cert and CA cert, CA possible self-signed or maybe not) Export CA certificate in .pem file.
Now you can force java to trust that CA, it can be done in various ways, for example, you can add CA certificate in jre cacerts or create custom SSLContext for HttpsURLConnection.
If you do DNS or etc.hosts modification, rollback it. Connection address should match with certificate CN, include wildcards.
Use that code to connect to your server:
public void test() throws Exception {
URL u = new URL(
"https://my-server.com/my-webservices/data");
HttpsURLConnection http = (HttpsURLConnection) u.openConnection();
http.setSSLSocketFactory(createSSLContext().getSocketFactory());
http.setAllowUserInteraction(true);
http.setRequestMethod("GET");
http.connect();
InputStream is = http.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder stringBuilder = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null)
{
stringBuilder.append(line
+ "\n");
}
System.out.println(stringBuilder.toString());
}
private SSLContext createSSLContext() throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
FileInputStream in = new FileInputStream("path_to_ca_file.pem");
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(null);
try {
X509Certificate cacert = (X509Certificate) cf.generateCertificate(in);
trustStore.setCertificateEntry("ca", cacert);
} finally {
IOUtils.closeQuietly(in);
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}