SSL Certificate Pinning w/ Picasso - android

I am using Picasso to cache Images. Our backend recently switched to HTTPS using self signed certificate pinning as authentication. I used the khandroid library to create an HTTP client that pins the certificates to each request; basically following this example.
http://ogrelab.ikratko.com/using-android-volley-with-self-signed-certificate/
I now need to apply this same concept to Picasso but am unsure how to modify Picasso's singleton to use pinned SSL certificates.

Turns out I was Just looking in the wrong place. I was attempting to modify the OkHttpDownloader, but I needed to modify the OkHttpClient. Here is some sample code.
public static Picasso getInstance(Context context) {
if (sPicasso == null) {
InputStream keyStore = context.getResources().openRawResource(R.raw.my_keystore);
Picasso.Builder builder = new Picasso.Builder(context);
OkHttpClient okHttpClient = new OkHttpClient();
SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new SsX509TrustManager(keyStore, password)}, null);
okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
OkHttpDownloader okHttpDownloader = new OkHttpDownloader(okHttpClient);
builder.downloader(okHttpDownloader);
sPicasso = builder.build();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Failure initializing default SSL context", e);
} catch (KeyManagementException e) {
throw new IllegalStateException("Failure initializing default SSL context", e);
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
}
return sPicasso;
}

Related

Android. SSLHandshakeException Connection reset by peer in AsyncHttpClient or Retrofit

Does someone know what can be wrong in configuration, that I getting such exception:
javax.net.ssl.SSLHandshakeException: SSL handshake aborted: ssl=0x71c528b200:
I/O error during system call, Connection reset by peer
And how to configure this for AsyncHttpClient (com.loopj.android:android-async-http:1.4.9)?
I am using the next configuration:
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.PersistentCookieStore;
import com.loopj.android.http.MySSLSocketFactory;
import cz.msebera.android.httpclient.Header;
import cz.msebera.android.httpclient.message.BasicHeader;
public class ProductsRestService {
public AsyncHttpClient getHttpClient() {
AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
asyncHttpClient.setCookieStore(new PersistentCookieStore(getContext()));
KeyStore trustStore;
try {
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
MySSLSocketFactory socketFactory = new MySSLSocketFactory(trustStore);
socketFactory.setHostnameVerifier(AppSSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
asyncHttpClient.setSSLSocketFactory(socketFactory);
} catch (Exception e) {
}
asyncHttpClient.setURLEncodingEnabled(true);
return asyncHttpClient;
}
}
I have found that this is probably different versions of TLS, but then not sure how to configure Async client.
The same I have tried for Retrofit:
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(HOST)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
RetrofitCallService callTokenService = retrofit.create(RetrofitCallService.class);
Call<String> tokens = callTokenService.getToken();
Headers headers = null;
try {
headers = tokens.execute().headers();
} catch (IOException e) {
e.printStackTrace();
}
List<String> values = headers.values("Content-Type");
Result the same exception. Not really sure in which direction I should search.
Thanks a lot for any help!

xamarin.android adding client certificate

I'm trying to send a request to a web api in Xamarin.Android. The api requires a client certificate. I followed the advice in this question: xamarin.ios httpclient clientcertificate not working with https, but I get a "method not implemented" exception. Can anyone help?
Here's my code:
string result = await CallApi(new System.Uri("myurl"));
protected async Task<string> CallApi(Uri url)
{
try
{
AndroidClientHandler clientHandler = new AndroidClientHandler();
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Ssl3;
using (var mmstream = new MemoryStream())
{
Application.Context.Assets.Open("mycert.pfx").CopyTo(mmstream);
byte[] b = mmstream.ToArray();
X509Certificate2 cert = new X509Certificate2(b, "password", X509KeyStorageFlags.DefaultKeySet);
clientHandler.ClientCertificates.Add(cert);
}
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; });
HttpClient client = new HttpClient(clientHandler);
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
return responseBody;
}
catch (HttpRequestException e)
{
Console.WriteLine("\nException Caught!");
return string.Empty;
}
}
In the post you mentioned probably the managed handler is used. Since this handler currently doesn't support TLS 1.2 you shouldn't use it, but instead really use the AndroidClientHandler (see also Xamarin and TLS 1.2).
Unfortunately ClientCertificates is indeed not implemented in AndroidClientHandler.
If you want to use client certificate with android you can extend the AndroidClientHandler:
using Java.Security;
using Java.Security.Cert;
using Javax.Net.Ssl;
using Xamarin.Android.Net;
using Xamarin.Forms;
public class AndroidHttpsClientHandler : AndroidClientHandler
{
private SSLContext sslContext;
public AndroidHttpsClientHandler(byte[] customCA, byte[] keystoreRaw) : base()
{
IKeyManager[] keyManagers = null;
ITrustManager[] trustManagers = null;
// client certificate
if (keystoreRaw != null)
{
using (MemoryStream memoryStream = new MemoryStream(keystoreRaw))
{
KeyStore keyStore = KeyStore.GetInstance("pkcs12");
keyStore.Load(memoryStream, clientCertPassword.ToCharArray());
KeyManagerFactory kmf = KeyManagerFactory.GetInstance("x509");
kmf.Init(keyStore, clientCertPassword.ToCharArray());
keyManagers = kmf.GetKeyManagers();
}
}
// custom truststore if you have your own ca
if (customCA != null)
{
CertificateFactory certFactory = CertificateFactory.GetInstance("X.509");
using (MemoryStream memoryStream = new MemoryStream(customCA))
{
KeyStore keyStore = KeyStore.GetInstance("pkcs12");
keyStore.Load(null, null);
keyStore.SetCertificateEntry("MyCA", certFactory.GenerateCertificate(memoryStream));
TrustManagerFactory tmf = TrustManagerFactory.GetInstance("x509");
tmf.Init(keyStore);
trustManagers = tmf.GetTrustManagers();
}
}
sslContext = SSLContext.GetInstance("TLS");
sslContext.Init(keyManagers, trustManagers, null);
}
protected override SSLSocketFactory ConfigureCustomSSLSocketFactory(HttpsURLConnection connection)
{
SSLSocketFactory socketFactory = sslContext.SocketFactory;
if (connection != null)
{
connection.SSLSocketFactory = socketFactory;
}
return socketFactory;
}
}
If you refer to AndroidClientHandler Source Code, you can find following statement:
AndroidClientHandler also supports requests to servers with "invalid" (e.g. self-signed) SSL certificates. Since this process is a bit convoluted using
the Java APIs, AndroidClientHandler defines two ways to handle the situation. First, easier, is to store the necessary certificates (either CA or server certificates)
in the collection or, after deriving a custom class from AndroidClientHandler, by overriding one or more methods provided for this purpose(, and ). The former method should be sufficient for most use cases...
So, for usage of AndroidClientHandler you should use clientHandler.TrustedCerts together with Java.Security.Cert.X509Certificate:
Java.Security.Cert.X509Certificate cert = null;
try
{
CertificateFactory factory = CertificateFactory.GetInstance("X.509");
using (var stream = Application.Context.Assets.Open("MyCert.pfx"))
{
cert = (Java.Security.Cert.X509Certificate)factory.GenerateCertificate(stream);
}
} catch (Exception e)
{
System.Console.WriteLine(e.Message);
}
if (clientHandler.TrustedCerts != null)
{
clientHandler.TrustedCerts.Add(cert);
}
else
{
clientHandler.TrustedCerts = new List<Certificate>();
clientHandler.TrustedCerts.Add(cert);
}
Notes: don't use Application.Context.Assets.Open("ca.pfx").CopyTo(mmstream); otherwise you will get inputstream is empty exception.

SSL connection reusing and caching with Android OkHttpClient

I am using Retrofit and OkHttp to perform all network operations like GET, POST for both HTTP and HTTPS url. Everything is working fine but except that i have a requirement to reuse the sessions in order to reduce the Handshake timing process for each and every service calls. As of now the server takes more than 800ms to initiate the handshake between client and server for all the service calls.
What I need:
I have to reuse the SSLSessions in order to make handshake happen only for the first time or during specific intervals.
Code I am using for SSL using Okhttp and Retrofit:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseURL)
.addConverterFactory(GsonConverterFactory.create())
.client(getOkHttpClient(context, new OkHttpClient(), context.getResources().openRawResource(R.raw.mysslcertificate)))
.build();
retrofit.create(apiClass);
public static OkHttpClient getOkHttpClient(Context context,OkHttpClient client, InputStream inputStream) {
try {
if (inputStream != null) {
SSLContext sslContext = sslContextForTrustedCertificates(inputStream);
if (sslContext != null) {
client = client.newBuilder()
.sslSocketFactory(sslContext.getSocketFactory()).build();
else {
CLog.i(Constants.LOG_TAG_HTTPLIBRARY,"GZip not done because it is not a Analytics data");
client = client.newBuilder()
.sslSocketFactory(sslContext.getSocketFactory()).build();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return client;
}
private static SSLContext sslContextForTrustedCertificates(InputStream in) {
try {
CertificateFactory e = CertificateFactory.getInstance("X.509");
Collection certificates = e.generateCertificates(in);
if (certificates.isEmpty()) {
throw new IllegalArgumentException("expected non-empty set of trusted certificates");
} else {
char[] password = "password".toCharArray();
KeyStore keyStore = newEmptyKeyStore(password);
int index = 0;
Iterator keyManagerFactory = certificates.iterator();
while (keyManagerFactory.hasNext()) {
Certificate trustManagerFactory = (Certificate) keyManagerFactory.next();
String sslContext = Integer.toString(index++);
keyStore.setCertificateEntry(sslContext, trustManagerFactory);
}
KeyManagerFactory var10 = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
var10.init(keyStore, password);
TrustManagerFactory var11 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
var11.init(keyStore);
SSLContext var12 = SSLContext.getInstance("TLS");
var12.init(var10.getKeyManagers(), var11.getTrustManagers(), new SecureRandom());
return var12;
}
} catch (Exception var9) {
var9.printStackTrace();
}
return null;
}
What I have tried:
Since i couldn't find anything related to OkHttpClient but i tried referring few of the solutions like from the link as follows:
https://gist.github.com/codebutler/5565971
https://developer.android.com/reference/javax/net/ssl/SSLContext.html
But to be very frank nothing was helpful to me and I couldn't even find any relavant solutions for my requirement. In turn finally, I am completely stuck with this solution for the couple of weeks. Kindly help me to achieve my tasks through any of your tips and suggestions. Any piece of code or approach will be very useful to me. Thanks in advance.

Getting OkHttp to accept self-signed certificate

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?

Making a HTTPS request using Android Volley

I am trying to make a https request using this code:
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
request = new Request<String>(Request.Method.GET,"https://devblahblahblah.com/service/etc",errListener);
but I am getting this error:
com.android.volley.NoConnectionError:
javax.net.ssl.SSLHandshakeException:
java.security.cert.CertPathValidatorException: Trust anchor for
certification path not found.
Two points to be noted:
The HTTPS cert is valid. Easily opens without any warning on browser.
The above code works fine with HTTP links.
I actually need to know if there are any switches/options in the Android Volley framework by using which I'll successfully hit a HTTPS URL?
Warning: The following code should not be used in production because it is vulnerable to SSL attacks
Probably these codes below will be helpful for you:
1.Create a HttpsTrustManager class that implements X509TrustManager:
public class HttpsTrustManager implements X509TrustManager {
private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};
#Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
#Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
public boolean isClientTrusted(X509Certificate[] chain) {
return true;
}
public boolean isServerTrusted(X509Certificate[] chain) {
return true;
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return _AcceptedIssuers;
}
public static void allowAllSSL() {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
SSLContext context = null;
if (trustManagers == null) {
trustManagers = new TrustManager[]{new HttpsTrustManager()};
}
try {
context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(context
.getSocketFactory());
}
}
2.Add HttpsTrustManager.allowAllSSL() before you make a https request:
HttpsTrustManager.allowAllSSL();
String tag_string_req = "string_req";
StringRequest strReq = new StringRequest(Request.Method.POST,
your_https_url, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
Log.d(TAG, "response :"+response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, "Error: " + error.getMessage());
}
}){
#Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<String, String>();
params.put("username", "max");
params.put("password", "123456");
return params;
}
};
AppController.getInstance().addToRequestQueue(strReq, tag_string_req);
you can add this class and execut it from onCreate method
new NukeSSLCerts().nuke();
it will make volley to Trust all SSL certificates.
So far the only answer talk about adding an untrusted certificate as the solution, but since your browser doesn't complain it usually means Volley can't find the intermediate certificate that does complete the full trusted chain.
It happened to me with LetsEncrypt certificates. Most browsers already have that intermediate certs so on browser everything looks fine, but Volley was apparently missing something.
The solution
Add the intermediate certificate to your webserver config. For Apache you can follow this reference:
https://access.redhat.com/solutions/43575
For LetsEncrypt it specifically is this file: /etc/letsencrypt/live/your.website.com/chain.pem
So besides your CertificateFile and KeyFile you should already have working you now have this third line:
SSLCertificateChainFile /etc/letsencrypt/live/your.website.com/chain.pem
Just adding that line, restarting apache and Volley doesn't complain anymore and you didn't introduce any security vulnerabilities!
If you are using volley and want to HTTPS request or SSL Certified service then you can choose this easiest way : -->
Step --> 1. keep .cer file into res/raw/ folder.
Step --> 2. Use this method and replace .cer file name with your .cer file and replace your host name also.
private SSLSocketFactory getSocketFactory() {
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X.509");
InputStream caInput = getResources().openRawResource(R.raw.cert_name);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
Log.e("CERT", "ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
Log.e("CipherUsed", session.getCipherSuite());
return hostname.compareTo("10.199.89.68")==0; //The Hostname of your server.
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
SSLContext context = null;
context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
SSLSocketFactory sf = context.getSocketFactory();
return sf;
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return null;
}
Step --> 3. Replace this line "RequestQueue queue = Volley.newRequestQueue(this);" with "RequestQueue queue = Volley.newRequestQueue(this, new HurlStack(null, getSocketFactory()));" in request of volley.
I couldn't open the link provided by #Ogre_BGR,but while browsing the net I found the actual implementation done in following smanikandan14 Github.Look upon his SSl-connection explanation to understand more about it.
This can happen for several reasons, including:
The CA that issued the server certificate was unknown
The server certificate wasn't signed by a CA, but was self signed
The server configuration is missing an intermediate CA
Official doc from android
Solution:
you can provide a certificate file within the request
For anyone who will come up against a problem like this and you use Letsencrypt for your SSL and node.js for webserver, try this. Assuming you have something like this. I fixed this by adding the line const chain = fs... Hope this helps
...
const app = express();
const privateKey = fs.readFileSync('ssl/privkey.pem', 'utf8');
const certificate = fs.readFileSync('ssl/cert.pem', 'utf8');
const chain = fs.readFileSync('ssl/chain.pem', 'utf8');
const credentials = {key: privateKey, cert: certificate, ca: chain};
...
var httpsServer = https.createServer(credentials, app);
I got the same problem when I add ssl to the domain, After 2 days gone, I found the solution the URL is getting wrong . I was using https://example.com but when I add ssl into domain the url will be change
https://www.example.com
And POST is working fine
got this error when i turned off proxy from cloudflare
check image here
the best solution for this problem is you can turn on proxy back and also add a full secure access on ssl certificate.
If anyone is using nginx and SSL certificates from letsencrypt, the solution is to simply use the certificate from file fullchain.pem instead of cert.pem:
ssl_certificate /.../fullchain.pem;
This file includes the concatenation of your certificate and the CA's.

Categories

Resources