I'm trying to develop an app to communicate with amazon alexa using OKhttp3. Everything was working fine while I was trying the app on an android API 26.
But once I started using an android API 19 every request to https://avs-alexa-na.amazon.com/ started failing.
Before working for API 19 this was how I used this function to open the down channel.
final String url = "https://avs-alexa-na.amazon.com/";
final String version = "v20160207";
final Request request = new Request.Builder()
.get()
.header("Authorization", "Bearer " + myApplication.getAccessToken())
.url(url + version + "/directives")
.build();
try {
final OkHttpClient client = new OkHttpClient();
Response response = client.newCall(request).execute();
Log.i("Down channel", "opened");
} catch (Exception ex) {
Log.e("Down channel", "error Sending request " + ex.getLocalizedMessage());
}
But now this same function returns javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb969de80: 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:741 0x8d954990:0x00000000)
So after following some posts on line I learned that android 19 doesn't install by default TLSv1.2 so I added a function to install it and started having this error :unexpected end of stream on Connection{avs-alexa-na.amazon.com:443, proxy=DIRECT# hostAddress=avs-alexa-na.amazon.com/54.239.39.74:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 protocol=http/1.1}
I started exploring other ways working with certificates (my certificate, and the one from amazon gotten by openssl s_client -connect avs-alexa-eu.amazon.com:443:
SSLContext sslContext;
TrustManager[] trustManagers;
try {
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
final InputStream certInputStream = activity.getResources().openRawResource(R.raw.cert);
InputStream avscertInputStream = activity.getResources().openRawResource(R.raw.amazon_avs_cert);
final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
final Certificate cert = certificateFactory.generateCertificate(certInputStream);
final Certificate avscert = certificateFactory.generateCertificate(avscertInputStream);
keyStore.setCertificateEntry("amazon_avs", avscert);
keyStore.setCertificateEntry("cert", cert);
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
trustManagers = trustManagerFactory.getTrustManagers();
sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, trustManagers, null);
return new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0])
.build();
} catch (Exception e) {
e.printStackTrace();
return new OkHttpClient();
}
But it led to a java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
I'm lost, I don't know what I did wrong or what I did not do. Thanks for you help.
Related
I am totally new on this and after some research, trying to make a SSL/TLS connection with server to allow SOAP call outside defined network. I am using OKHttp3 library to make the call and sharing a .P12 file with private key and setting a SSL Socket Factory as shown below
setupKeyCert(context);
final OkHttpClient client = new OkHttpClient();
client.newBuilder()
.connectTimeout(30, TimeUnit.SECONDS)
.sslSocketFactory(sslContext.getSocketFactory(), mainX509TrustManager);
and setting keystone and Trust Manager in
setupKeyCert()
as
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
KeyManagerFactory keyManagerFactory = null;
if(buildEnvironment == "prod")
{
keyStore.load(context.getAssets().open(Constants.CERT_PROD_FILE), password);
keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore, Constants.CERT_PROD_VALUE.toCharArray());
} else
{
keyStore.load(context.getAssets().open(Constants.CERT_FILE), Constants.CERT_VALUE.toCharArray());
keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore, Constants.CERT_VALUE.toCharArray());
}
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
//Adding TrustManagerFactory
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
mainX509TrustManager = (X509TrustManager) trustManagers[0];
sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagers, null, null);
} catch (FileNotFoundException f)
{
Log.e("Excption File", String.valueOf(f));
} catch (Exception i)
{
Log.e(TAG, "Exception", i);
}
Where Constant.CERT_PROD_FILE refers to constant string which contain file name.
When debugging or making call, I am getting below error
javax.net.ssl.SSLProtocolException: SSL handshake terminated:
ssl=0x8a163cc0: Failure in SSL library, usually a protocol error
error:10000410:SSL
routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE
(external/boringssl/src/ssl/s3_pkt.c:610 0x9f7e3d00:0x00000001)
I am not sure what am I missing and I have tried different solution including bug related to android 7 issue but it's still not working. I tried to debug and can only see the above error, so not sure if the issue is with server or client. Thanks for help in advance.
Android self-signed client side CA certificate authentication fails, Can anyone share their experience dealing with similar issues? With the same credentials we are able to get it to work using CURL client.
We followed this GIST however we could not get through it.
Following it just led us to have this exception gets thrown:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
Try the following code
// Input stream for self signed CA certificate
InputStream caIs = getInputStream(caCert);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(caIs);
String alias = certificate.getSubjectX500Principal().getName();
Log.d(TAG, String.format("Alias: %s", alias));
// KeyStore for trusted CA certificate(s)
KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustedStore.load(null);
trustedStore.setCertificateEntry(alias, certificate);
// Create trust managers to be used for connecting to servics(s)
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustedStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
// KeyStore for X.509 certificate/key
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream clientIs = getInputStream(clientKeyCert);
keyStore.load(clientIs, "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, "password".toCharArray());
// Create key managers to be uses for connecting to service(s)
KeyManager[] keyManagers = kmf.getKeyManagers();
// Create the SSL context
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
// Test by connecting ta server proteced by self signed certificate
OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(sslContext.getSocketFactory()).build();
Call call = client.newCall(new Request.Builder().url("https://testurl.com").build());
Response response = call.execute();
Log.d(TAG, response.message());
I added HTTPPinning to OKHTTPClient the sample code is:
OkHttpClient client = new OkHttpClient();
client.setSslSocketFactory(getPinnedCertSslSocketFactory(context));
private SSLSocketFactory getPinnedCertSslSocketFactory(Context context) {
try {
KeyStore trusted = KeyStore.getInstance("BKS");
InputStream incontext.getResources().openRawResource(R.raw.prod_keystore);
trusted.load(in, "venkat#123".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trusted);
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
return sslContext.getSocketFactory();
} catch (Exception e) {
Log.e("MyApp", e.getMessage(), e);
}
return null;
}
I uploaded the app into playstore and from the last 1 year on wards it is working good. but from the last 1 week onwards it is giving the below issue and I used OkHttp of version com.squareup.okhttp:okhttp:2.7.4
java.security.cert.CertPathValidatorException: Trust anchor for
certification path not found.
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357)
at com.squareup.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:192)
at com.squareup.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:149)
at com.squareup.okhttp.internal.io.RealConnection.connect(RealConnection.java:112)
at com.squareup.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:184)
at com.squareup.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:126)
at com.squareup.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:95)
at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:281)
at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:224)
at com.squareup.okhttp.Call.getResponse(Call.java:286)
at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:243)
at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:205)
at com.squareup.okhttp.Call.execute(Call.java:80)
at com.venkat.good.http.MyHTTPThread.run(MyHTTPThread.java:492)
at com.venkat.good.http.MyHTTPThread.run(MyHTTPThread.java:76)
at java.lang.Thread.run(Thread.java:818)
by using OKHTTP3 i resolved this issue.
String hostname = "yourdomain.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
OkHttpClient client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
Request request = new Request.Builder()
.url("https://" + hostname)
.build();
client.newCall(request).execute();
But I want to know why the previous OkHttp2 version works for some days and after that it raises the issue?
Better late than never
Glad you managed your problem with OkHttp3.
Let me just answer your asked sub question:
This was a build system configuration problem, not a problem with OkHttp.
Everyone was surprised to see to behave that way because it should be
resolving the higher version required by OkHttp3 for use.
If you were using maven or retrofit a fix has been merged into the newer version (OkHttp3 implemented it for the mentioned libraries).
I successfully got the server to use a certificate in the form of a JKS file. HTTPS is working as expected when used with web browsers and other web clients.
For Android, my team uses the following to persuade OkHttp to accept the certificate.
static KeyStore readKeyStore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException
{
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
// get user password and file input stream
char[] password = "password".toCharArray();
java.io.InputStream fis = null;
try {
fis = ServiceProducer.class.getClassLoader().getResourceAsStream("res/raw/keystore.jks");
ks.load(fis, password);
} catch (IOException e)
{
} finally
{
if (fis != null)
{
try
{
fis.close();
} catch (IOException e)
{
}
}
}
return ks;
}
The code that uses the key:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
KeyStore keyStore = readKeyStore();
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "password".toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
builder.sslSocketFactory(sslContext.getSocketFactory());
OkHttpClient client = builder.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://192.168.5.91:9443")
.addConverterFactory(JacksonConverterFactory.create())
.client(client)
.build();
However, accessing the service throws the following exception:
java.security.cert.CertPathValidationException: Trust anchor for certification path not found.
Have we done the certificate installation correctly? Or are we facing a different kind of problem?
I have been searching for this for a few weeks and can't seem to find an answer anywhere. I am trying to do the following for Android. This code is from a C# app I wrote but am porting it to Android. The web endpoint requires a cert to be attached to the request for mutual authentication to make the web service call.
string certThumbprint = "E1313F6A2D770783868755D016CE748F6A9B0028";
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
certStore.Open(OpenFlags.ReadOnly);
}
catch (Exception e)
{
if (e is CryptographicException)
{
Console.WriteLine("Error: The store is unreadable.");
}
else if (e is SecurityException)
{
Console.WriteLine("Error: You don't have the required permission.");
}
else if (e is ArgumentException)
{
Console.WriteLine("Error: Invalid values in the store.");
}
else
{
throw;
}
}
X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, certThumbprint, false);
certStore.Close();
if (0 == certCollection.Count)
{
throw new Exception("Error: No certificate found containing thumbprint " + certThumbprint);
}
X509Certificate2 certificate = certCollection[0];
return certificate;
I am then doing this (request is an HttpWebRequest):
request.ClientCertificates.Add(cert);
This works fine in C# however when I move to Android I'm getting a "file not found" error on the getInputStream() call. Here is my Android code:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(new FileInputStream("/sdcard/Certificate.pfx"));
KeyHelper kh = new KeyHelper();
Certificate ca = kh.GetKey("Password");
String keyStoreType = KeyStore.getDefaultType();
keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "Password".toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(),null,new SecureRandom());
HttpsURLConnection urlConnection =
(HttpsURLConnection)url.openConnection();
urlConnection.setRequestProperty("x-ms-version",AZURE_REST_VERSION);
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("GET");
urlConnection.setSSLSocketFactory(context.getSocketFactory());
urlConnection.connect();
InputStream in = new BufferedInputStream(urlConnection.getInputStream()); //<-----Blows up here
} catch (KeyStoreException e) {
throw new KeyStoreException("Keystore Exception",e);
} catch (NoSuchAlgorithmException e) {
throw new NoSuchAlgorithmException("Algorithm exception",e);
} catch (KeyManagementException e) {
throw new KeyManagementException("Key Exception", e);
}
I tried to put fiddler between the emulator and the endpoint and it comes back with a 200. I think this is because my cert is in my local private store on my dev machine. Any thoughts?
OK. I found the answer to this one. The issue lies in that the self signed cert can't be used until it exists in the android TrustStore. However the default TrustStore is read-only after an app starts so it's hard to modify it. I was setting up my own custom trust store, however the root certs were not part of it, so calls out to any https would fail. The solution came from this blog post:
http://nelenkov.blogspot.com/2011/12/using-custom-certificate-trust-store-on.html
Long story short, setup a custom TrustStore that contains the self signed cert, then export all the certificates from the default trust store and import them into the custom trust store. Then use that trust store to setup your SSL Context (also need to use a custom keystore since the client certificate needs to be attached to the request as well). I believe if I would not allow self signed certs this wouldn't be a big deal as the root cert for the client certificate would exist in the default TrustStore (or at least I hope it would).
Also you should never hardcode paths :
InputStream caInput = new BufferedInputStream(new FileInputStream("/sdcard/Certificate.pfx"));
Instead use:
Environment.getExternalStorageDirectory()
Look this : http://developer.android.com/training/basics/data-storage/files.html
Also for certificates
Take a look at this: Using a self-signed certificate to create a secure client-server connection in android