I have written an Android web service client using the Restlet framework for Android (2.0.15), and I've also written the web service backend as well (again with Restlet 2.0.15 JEE) which has been uploaded on AWS Elastic Beanstalk (so the client calls would be in the form of "http://my_web_service.elasticbeanstalk.com/this/is/my/request"). Everything works fine on HTTP, so now I'd like to replace it with HTTPS, but this has proven more difficult that I initially thought.
I have created a trial SSL certificate from Comodo, where I have declared, as a CNAME, a domain that I own (unfortunately, I cannot declare the elasticbeanstalk.com subdomain, where the AWS load balancer is running, as a certificate hostname). This certificate has been uploaded to my AWS instance, and it seems to be running succsefully (tested via web browser, a couple of https calls get through succesfully after I accept the certificate on the browser). The only thing that I don't like about this certificate is the fact that I get a warning that this may be an invalid certificate, since the declared hostname (my domain) and the actual hostname that the certificate is running (elasticbeanstalk.com) do not match.
In my client I'm using the apache http client (have loaded the org.apache.httpclient.jar on claspath), and this is how I create the client resource I use on every call, which is plain and simple:
ClientResource resource = new ClientResource(resourceUri);
Engine.getInstance().getRegisteredClients().clear();
Engine.getInstance().getRegisteredClients().add(new HttpClientHelper(null));
Of course, resourceUri
is in the form of "https://my_web_service.elasticbeanstalk.com/this/is/my/request", and this is the only difference between the working HTTP case and the non-working HTTPS case.
With HTTPS, I get the error below:
A recoverable error was detected (1001), attempting again in 2000 ms.
I've tried several suggestions that I found on google (using org.restlet.ext.net httpclient instead of apache or even loading the org.restlet.ext.ssl jar from Restlet Android 2.1), but nothing has worked so far. I've even captured a network trace with Wireshark, and here's the callflow:
|Time | 192.168.2.111 |
| | | 54.245.249.149 |
|14.769 | 65430 > https [SYN] |TCP: 65430 > https [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=16 TSval=3906214190 TSecr=0 SACK_PERM=1
| |(65430) ------------------> (443) |
|15.026 | https > 65430 [SYN, |TCP: https > 65430 [SYN, ACK] Seq=0 Ack=1 Win=14480 Len=0 MSS=1452 SACK_PERM=1 TSval=758478734 TSecr=3906214190 WS=256
| |(65430) <------------------ (443) |
|15.027 | 65430 > https [ACK] |TCP: 65430 > https [ACK] Seq=1 Ack=1 Win=132480 Len=0 TSval=3906214442 TSecr=758478734
| |(65430) ------------------> (443) |
|15.034 | Client Hello |TLSv1: Client Hello
| |(65430) ------------------> (443) |
|15.289 | https > 65430 [ACK] |TCP: https > 65430 [ACK] Seq=1 Ack=124 Win=14592 Len=0 TSval=758478799 TSecr=3906214448
| |(65430) <------------------ (443) |
|15.291 | Server Hello, Certi |TLSv1: Server Hello, Certificate, Server Hello Done
| |(65430) <------------------ (443) |
|15.291 | 65430 > https [ACK] |TCP: 65430 > https [ACK] Seq=124 Ack=1386 Win=131088 Len=0 TSval=3906214696 TSecr=758478799
| |(65430) ------------------> (443) |
|17.207 | Client Key Exchange |TLSv1: Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
| |(65430) ------------------> (443) |
|17.505 | Encrypted Handshake |TLSv1: Encrypted Handshake Message, Change Cipher Spec, Encrypted Handshake Message
| |(65430) <------------------ (443) |
|17.505 | Client Hello |TLSv1: Client Hello
| |(65430) ------------------> (443) |
|17.507 | 65430 > https [FIN, |TCP: 65430 > https [FIN, ACK] Seq=557 Ack=1636 Win=130832 Len=0 TSval=3906216837 TSecr=758479353
| |(65430) ------------------> (443) |
|17.776 | Encrypted Alert |TLSv1: Encrypted Alert
| |(65430) <------------------ (443) |
|17.776 | 65430 > https [RST] |TCP: 65430 > https [RST] Seq=557 Win=0 Len=0
| |(65430) ------------------> (443) |
|17.777 | Encrypted Alert |TLSv1: Encrypted Alert
| |(65430) <------------------ (443) |
|17.777 | https > 65430 [FIN, |TCP: https > 65430 [FIN, ACK] Seq=1682 Ack=557 Win=15616 Len=0 TSval=758479421 TSecr=3906216836
| |(65430) <------------------ (443) |
|17.777 | https > 65430 [ACK] |TCP: https > 65430 [ACK] Seq=1683 Ack=558 Win=15616 Len=0 TSval=758479421 TSecr=3906216837
| |(65430) <------------------ (443) |
|17.777 | 65430 > https [RST] |TCP: 65430 > https [RST] Seq=557 Win=0 Len=0
| |(65430) ------------------> (443) |
|17.777 | 65430 > https [RST] |TCP: 65430 > https [RST] Seq=557 Win=0 Len=0
| |(65430) ------------------> (443) |
|17.777 | 65430 > https [RST] |TCP: 65430 > https [RST] Seq=558 Win=0 Len=0
| |(65430) ------------------> (443) |
From the callflow above, it seems that client and server fail to complete a succesful negotiation, but I have no idea why.
Any suggestions on how to resolve this problem are welcome. I believe that the issue exists on the client side (Android app using Restlet 2.0.15 framework), but not on the app code itself (since everything works fine when using HTTP) but rather on the SSL negotiation/handshake before actually making any calls. I also believe that the Certificate Authority (Comodo) is successfully accepted/trusted by Android (I've done https calls through the android device browser), but it still gives you a certificate warning that you need to accept before continuing. Could it be that Restlet 2.0.15 is not handling smoothly SSL communication, and I would need to upgrade to 2.1 or later?
Looking forward to hearing your suggestions. In case you'd like to get some more info that could help, just ask me. :)
Thanks in advance, Alex
========= UPDATE =========
Ok, it is as I was suspecting. The problem is that the certificate (having a CNAME = www .mydomain. com, but being loaded from https:// mywebservice. elasticbeanstalk. com) seems to the Android client as invalid, thus it doesn't even send the GET/POST request to the server.
I realized this when I send a POST method from the terminal (using "curl"), ignoring the ssl verification warnings (-k option). This time the secure connection responded as expected, sending back the json reply.
Based on Google's own Android documentation suggestion, I tried to alter the HostnameVerifier method in order to get past the certification validation. This is how my ClientResource is currently created:
public static ClientResource createClientResource(String resourceUri) {
Reference reference = new Reference(resourceUri);
System.setProperty("ssl.TrustManagerFactory.algorithm",javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm());
org.restlet.Context context = new org.restlet.Context();
context.getAttributes().put("hostnameVerifier", new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
ClientResource resource = new ClientResource(context, reference);
Engine.getInstance().getRegisteredClients().clear();
Engine.getInstance().getRegisteredClients().add(new HttpClientHelper(null));
Engine.getInstance().getRegisteredConverters().add(0, new JacksonConverter());
resource.release();
return resource;
}
But this doesn't work either, I still get the 1001 recoverable error. Still, the Android client can't get past the "invalid" request.
I'd greatly appreciate any suggestions. :)
I finally managed to solve this problem. After checking the "response" object, and more specifically the response status, this is what was the exception raised by the error:
Communication Error (1001) - The connector failed to complete the communication with the server
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
This made me re-investigate my uploaded certificates on AWS, and that was exactly the problem. It seems that I hadn't installed the certificate chain (intermediate certificates) correctly on AWS EC2 Instance. The certificate chain of my CA consists of 4 certificate files, and AWS needs this chain in a very specific order (signing certificate first, CA root certificate last, and all other certificates in between), given in a pem/text format.
After correcting the certificate chain, everything worked like a charm. :)