Gowtham Raj
Gowtham Raj

Reputation: 2955

CertPathValidatorException : Trust anchor for certificate path not found - Retrofit Android

I am creating an android application which uses https for communication with the server. I am using retrofit and OkHttp for making requests. These works fine for standard http requests. The following are the steps that I followed.

Step 1 : Acquired the cert file from the server using the command

echo -n | openssl s_client -connect api.****.tk:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > gtux.cert

Step 2 : Converted the cert to a BKS format by using the following commands

keytool -importcert -v -trustcacerts -file "gtux.cert" -alias imeto_alias -keystore "my_keystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-146.jar" -storetype BKS

It asked me for password and the file was successfully created.

Step 3 :

Create a OkHttpClient and use the same for making https requests

public class MySSLTrust {
public static OkHttpClient trustcert(Context context){
    OkHttpClient okHttpClient = new OkHttpClient();
    try {
        KeyStore ksTrust = KeyStore.getInstance("BKS");
        InputStream instream = context.getResources().openRawResource(R.raw.my_keystore);
        ksTrust.load(instream, "secret".toCharArray());
        // TrustManager decides which certificate authorities to use.
        TrustManagerFactory tmf = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ksTrust);
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);
        okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
    } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | KeyManagementException e) {
        e.printStackTrace();
    }
    return okHttpClient;
}
}

Step 4:

RestAdapter has to be created

RestAdapter.Builder()
.setRequestInterceptor(intercept)
.setEndpoint("https://api.****.tk")
.setClient(new OkClient(this))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(new AndroidLog("RETROFIT"))
.build();

But finally when run the app it is throwing me CertPathValidatorException : Trust anchor for certificate path not found. Please help me to solve this. Thank you.

Other failure attempts : Tried to install the certificate in my Xperia Z2 and it says the file was installed but when i run the app the same exception is thrown.

Error Log Here is the error log that I got on executing...

Error Log

Pasted there so that it will be easy to read..

Upvotes: 69

Views: 141745

Answers (11)

Vishudh Sasidharan
Vishudh Sasidharan

Reputation: 383

I encountered this exception while trying to connect to a server endpoint we use for testing. The server uses a self-signed certificate, and we don't have control over it. To resolve this, I downloaded the public key from their server and updated our network security configuration file for the specific build flavor.

<network-security-config>
<domain-config>
    <domain includeSubdomains="true">**** The server in question ****</domain>
    <trust-anchors>
        <!-- path to the certificate in code -->
        <certificates src="@raw/cert_for_the_server"/>
    </trust-anchors>
</domain-config>
<debug-overrides>
    <trust-anchors>
        <!-- Trust user added CAs for debugging -->
        <certificates src="user" />
    </trust-anchors>
</debug-overrides>

Upvotes: 0

CodeToLife
CodeToLife

Reputation: 4151

With okhttp3 my code snippet:

    private String post(String url, String json) throws IOException {
      OkHttpClient client = new OkHttpClient();
      RequestBody body = RequestBody.create(JSON, json);
      Request request = new Request.Builder()
            .url(url)
            .post(body)
            .build();

      l.k(body.toString());

      Response response = client.newCall(request).execute();//HERE THROWS EXCEPTION
      code = response.code();
      l.k(99);
    return response.body().string();
}

threw exception of such CertPathValidatorException : Trust anchor for certificate path not found What i did was added this to above post() method:

 boolean ignoreSSL=true;
 OkHttpClient.Builder builder = new OkHttpClient.Builder();
 if (ignoreSSL) {
    builder = configureToIgnoreCertificate(builder);
 }
 client = builder.build();

instead of

OkHttpClient client =  new OkHttpClient();

and added a method:

    private static OkHttpClient.Builder configureToIgnoreCertificate(OkHttpClient.Builder builder) {
      l.k("Ignore Ssl Certificate");
      try {

        // Create a trust manager that does not validate certificate chains
        final TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                @Override
                public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType)
                    throws CertificateException {
                }

                @Override
                public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType)
                    throws CertificateException {
                }

                @Override
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return new java.security.cert.X509Certificate[]{};
                }
            }
        };

        // Install the all-trusting trust manager
        final SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        // Create an ssl socket factory with our all-trusting manager
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
        builder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
      } catch (Exception e) {
         l.k("Exception while configuring IgnoreSslCertificate" + e);
      }
      return builder;
  }

Upvotes: 0

anon
anon

Reputation:

To solve the CertPathValidatorException issue, use the following code copied from https://mobikul.com/android-retrofit-handling-sslhandshakeexception/

 Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(YOUR_BASE_URL)
        .client(getUnsafeOkHttpClient().build())
        .build();


  public static OkHttpClient.Builder getUnsafeOkHttpClient() {

    try {
        // Create a trust manager that does not validate certificate chains
        final TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String
authType) throws CertificateException {
                    }
 
                    @Override
                    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String
authType) throws CertificateException {
                    }
 
                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return new java.security.cert.X509Certificate[]{};
                    }
                }
        };
 
        // Install the all-trusting trust manager
        final SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
 
        // Create an ssl socket factory with our all-trusting manager
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
 
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
        builder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
        return builder;
    } catch (Exception e) {
        throw new RuntimeException(e);
    } }

Upvotes: 50

KiluSs
KiluSs

Reputation: 537

Based on Hani's answer, it's working perfectcly for me. But you need to fix one thing if you get compile error.

Change okHttpClient.hostnameVerifier {HostnameVerifier { _, _ -> true } } to okHttpClient.hostnameVerifier { _, _ -> true }

And the function

fun unSafeOkHttpClient() :OkHttpClient.Builder {
    val okHttpClient = OkHttpClient.Builder()
    try {
        // Create a trust manager that does not validate certificate chains
        val trustAllCerts:  Array<TrustManager> = arrayOf(object : X509TrustManager {
            override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?){}
            override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
            override fun getAcceptedIssuers(): Array<X509Certificate>  = arrayOf()
        })

        // Install the all-trusting trust manager
        val  sslContext = SSLContext.getInstance("SSL")
        sslContext.init(null, trustAllCerts, SecureRandom())

        // Create an ssl socket factory with our all-trusting manager
        val sslSocketFactory = sslContext.socketFactory
        if (trustAllCerts.isNotEmpty() &&  trustAllCerts.first() is X509TrustManager) {
            okHttpClient.sslSocketFactory(sslSocketFactory, trustAllCerts.first() as X509TrustManager)
            okHttpClient.hostnameVerifier { _, _ -> true } // change here
        }

        return okHttpClient
    } catch (e: Exception) {
        return okHttpClient
    }
}

Upvotes: 1

Hani
Hani

Reputation: 137

Here is Kotlin version. Okhttp 4.9.0
Thanks you :)

         fun unSafeOkHttpClient() :OkHttpClient.Builder {
            val okHttpClient = OkHttpClient.Builder()
            try {
                // Create a trust manager that does not validate certificate chains
                val trustAllCerts:  Array<TrustManager> = arrayOf(object : X509TrustManager {
                    override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?){}
                    override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
                    override fun getAcceptedIssuers(): Array<X509Certificate>  = arrayOf()
                })

                // Install the all-trusting trust manager
                val  sslContext = SSLContext.getInstance("SSL")
                sslContext.init(null, trustAllCerts, SecureRandom())

                // Create an ssl socket factory with our all-trusting manager
                val sslSocketFactory = sslContext.socketFactory
                if (trustAllCerts.isNotEmpty() &&  trustAllCerts.first() is X509TrustManager) {
                    okHttpClient.sslSocketFactory(sslSocketFactory, trustAllCerts.first() as X509TrustManager)
                    okHttpClient.hostnameVerifier {HostnameVerifier { _, _ -> true } }
                }

                return okHttpClient
            } catch (e: Exception) {
                return okHttpClient
            }
        }

Upvotes: 10

Ahamed Mujeeb
Ahamed Mujeeb

Reputation: 723

Implementation in Kotlin : Retrofit 2.3.0

private fun getUnsafeOkHttpClient(mContext: Context) : 
OkHttpClient.Builder? {


var mCertificateFactory : CertificateFactory = 
CertificateFactory.getInstance("X.509")
var mInputStream = mContext.resources.openRawResource(R.raw.cert)
            var mCertificate : Certificate = mCertificateFactory.generateCertificate(mInputStream)
        mInputStream.close()
val mKeyStoreType = KeyStore.getDefaultType()
val mKeyStore = KeyStore.getInstance(mKeyStoreType)
mKeyStore.load(null, null)
mKeyStore.setCertificateEntry("ca", mCertificate)

val mTmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
val mTrustManagerFactory = TrustManagerFactory.getInstance(mTmfAlgorithm)
mTrustManagerFactory.init(mKeyStore)

val mTrustManagers = mTrustManagerFactory.trustManagers

val mSslContext = SSLContext.getInstance("SSL")
mSslContext.init(null, mTrustManagers, null)
val mSslSocketFactory = mSslContext.socketFactory

val builder = OkHttpClient.Builder()
builder.sslSocketFactory(mSslSocketFactory, mTrustManagers[0] as X509TrustManager)
builder.hostnameVerifier { _, _ -> true }
return builder

}

Upvotes: 3

Gowsik
Gowsik

Reputation: 1126

Retrofit 2.3.0

    // Load CAs from an InputStream
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

    InputStream inputStream = context.getResources().openRawResource(R.raw.ssl_certificate); //(.crt)
    Certificate certificate = certificateFactory.generateCertificate(inputStream);
    inputStream.close();

    // Create a KeyStore containing our trusted CAs
    String keyStoreType = KeyStore.getDefaultType();
    KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    keyStore.load(null, null);
    keyStore.setCertificateEntry("ca", certificate);

    // Create a TrustManager that trusts the CAs in our KeyStore.
    String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm);
    trustManagerFactory.init(keyStore);

    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
    X509TrustManager x509TrustManager = (X509TrustManager) trustManagers[0];


    // Create an SSLSocketFactory that uses our TrustManager
    SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, new TrustManager[]{x509TrustManager}, null);
    sslSocketFactory = sslContext.getSocketFactory();

    //create Okhttp client
    OkHttpClient client = new OkHttpClient.Builder()
                .sslSocketFactory(sslSocketFactory,x509TrustManager)
                .build();

    Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(url)
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(client)
                    .build();

Upvotes: 10

Paulo Avelar
Paulo Avelar

Reputation: 2140

DISCLAIMER: this answer is from Jul 2015 and uses Retrofit and OkHttp from that time.
Check this link for more info on Retrofit v2 and this one for the current OkHttp methods.

Okay, I got it working using Android Developers guide.

Just as OP, I'm trying to use Retrofit and OkHttp to connect to a self-signed SSL-enabled server.

Here's the code that got things working (I've removed the try/catch blocks):

public static RestAdapter createAdapter(Context context) {
  // loading CAs from an InputStream
  CertificateFactory cf = CertificateFactory.getInstance("X.509");
  InputStream cert = context.getResources().openRawResource(R.raw.my_cert);
  Certificate ca;
  try {
    ca = cf.generateCertificate(cert);
  } finally { cert.close(); }

  // creating a KeyStore containing our trusted CAs
  String keyStoreType = KeyStore.getDefaultType();
  KeyStore keyStore = KeyStore.getInstance(keyStoreType);
  keyStore.load(null, null);
  keyStore.setCertificateEntry("ca", ca);

  // creating a TrustManager that trusts the CAs in our KeyStore
  String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
  TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
  tmf.init(keyStore);

  // creating an SSLSocketFactory that uses our TrustManager
  SSLContext sslContext = SSLContext.getInstance("TLS");
  sslContext.init(null, tmf.getTrustManagers(), null);

  // creating an OkHttpClient that uses our SSLSocketFactory
  OkHttpClient okHttpClient = new OkHttpClient();
  okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());

  // creating a RestAdapter that uses this custom client
  return new RestAdapter.Builder()
              .setEndpoint(UrlRepository.API_BASE)
              .setClient(new OkClient(okHttpClient))
              .build();
}

To help in debugging, I also added .setLogLevel(RestAdapter.LogLevel.FULL) to my RestAdapter creation commands and I could see it connecting and getting the response from the server.

All it took was my original .crt file saved in main/res/raw. The .crt file, aka the certificate, is one of the two files created when you create a certificate using openssl. Generally, it is a .crt or .cert file, while the other is a .key file.

Afaik, the .crt file is your public key and the .key file is your private key.

As I can see, you already have a .cert file, which is the same, so try to use it.


PS: For those that read it in the future and only have a .pem file, according to this answer, you only need this to convert one to the other:

openssl x509 -outform der -in your-cert.pem -out your-cert.crt

PS²: For those that don't have any file at all, you can use the following command (bash) to extract the public key (aka certificate) from any server:

echo -n | openssl s_client -connect your.server.com:443 | \
  sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/my_cert.crt

Just replace the your.server.com and the port (if it is not standard HTTPS) and choose a valid path for your output file to be created.

Upvotes: 91

moonzai
moonzai

Reputation: 439

You are converting cert into BKS Keystore, why aren't you using .cert directly, from https://developer.android.com/training/articles/security-ssl.html:

CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream instream = context.getResources().openRawResource(R.raw.gtux_cert);
Certificate ca;
try {
    ca = cf.generateCertificate(instream);
} finally {
    caInput.close();
}

KeyStore kStore = KeyStore.getInstance(KeyStore.getDefaultType());
kStore.load(null, null);
kStore.setCertificateEntry("ca", ca);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm(););
tmf.init(kStore);

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);

okHttpClient.setSslSocketFactory(context.getSocketFactory());

Upvotes: 4

goRGon
goRGon

Reputation: 4451

I don't use Retrofit and for OkHttp here is the only solution for self-signed certificate that worked for me:

  1. Get a certificate from our site like in Gowtham's question and put it into res/raw dir of the project:

    echo -n | openssl s_client -connect elkews.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ./res/raw/elkews_cert.crt
    
  2. Use Paulo answer to set ssl factory (nowadays using OkHttpClient.Builder()) but without RestAdapter creation.

  3. Then add the following solution to fix: SSLPeerUnverifiedException: Hostname not verified

So the end of Paulo's code (after sslContext initialization) that is working for me looks like the following:

...
OkHttpClient.Builder builder = new OkHttpClient.Builder().sslSocketFactory(sslContext.getSocketFactory());
builder.hostnameVerifier(new HostnameVerifier() {
  @Override
  public boolean verify(String hostname, SSLSession session) {
    return "secure.elkews.com".equalsIgnoreCase(hostname);
});
OkHttpClient okHttpClient = builder.build();

Upvotes: 10

Bajrang Hudda
Bajrang Hudda

Reputation: 3268

After a long reserch and digging too deep i found the solution of certificate pinning in android and yes its different from iOS where we need a certificate itself but in android we just need a hash pin and that's it.

How to get hash pin for certificate?

Initially just use a wrong hash pin and your java class will throw an error with correct hash pins or pin chain, just copy and paste into your code thats it.

This solution fixed my problem : https://stackoverflow.com/a/45853669/3448003

Upvotes: 0

Related Questions