Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make SSL peer_verify work on Android?

I've successfully built libcurl-7.36.0 with openssl-1.0.1h on Android. I ran a sample code to test HTTPS connection. The SSL_VERIFYPEER is enabled by default. The certificates path on Android is /system/etc/security/cacerts, so I set CURLOPT_CAPATH to /system/etc/security/cacerts.

ls -l /system/etc/security/cacerts
-rw-r--r-- root     root         4767 2012-09-22 11:57 00673b5b.0
-rw-r--r-- root     root         4573 2012-09-22 11:57 03e16f6c.0
-rw-r--r-- root     root         5292 2012-09-22 11:57 08aef7bb.0
......

Here is a snippet of my codes..

curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, "https://www.google.com:443");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);     // default
curl_easy_setopt(curl, CURLOPT_CAPATH, "/system/etc/security/cacerts");
curl_easy_perform(curl);

Curl always returns an error:

== Info: SSL certificate problem: unable to get local issuer certificate  
== Info: Closing connection 0  
curl_easy_perform() failed: Peer certificate cannot be authenticated with given CA certificates

It's working if I download the CA bundle file ca-bundle.crt from http://curl.haxx.se/docs/caextract.html and curl_easy_setopt(curl, CURLOPT_CAINFO, "path:/ca-bundle.crt").

Here is my question: Is there any way to make SSL peer verification work by reading the certificate from /system/etc/security/cacerts without manually downloading the CA bundle file and specifying CURLOPT_CAINFO?

like image 941
Robert Chou Avatar asked Aug 11 '14 23:08

Robert Chou


2 Answers

  • If using libcurl in an android app, CURLOPT_SSL_VERIFYPEER will fail and hence prevent CURL from sending data if if there is no CA bundle . One way to overcome this is to turn off this option which is very very very bad. We must provide our own CA bundle and provide the absolute path of the CA bundle file using CURLOPT_CAINFO option.
  • The "cacert.pem" file from ​http://curl.haxx.se/docs/caextract.html can be placed in resources or assets but I prefer assets directory.
  • CURL expects absolute path and we cant give absolute path of assets folder because a packaged android APK file is like a zipped folder hence we need to copy the PEM file from assets to internal storage or external storage but I prefer internal storage since it private to the app and provide the absolute path of the internal storage directory in CAINFO. For example if app name is com.example.androidtest then CAINFO path will be "/data/data/com.example.androidtest/cacert.pem" .
  • Sample implementation of CURL using TLS1.2 ,openSSL 1.01p,curl version 7.40.0 ,cacert.pem bundle with verify peer ,verify hostname option is shown in https://github.com/vyshas/CURL-Android-with-verify-peer-

  • Important parts from the above link is shown below:

JAVA Side

public native void setDir(String caCertDir);

setDir(saveCertPemFile());


    private String saveCertPemFile()
    {
        Context context=getApplicationContext();
        String assetFileName="cacert.pem";

        if(context==null || !FileExistInAssets(assetFileName,context))
        {
            Log.i("TestActivity", "Context is null or asset file doesnt exist");
            return null;
        }
        //destination path is data/data/packagename
        String destPath=getApplicationContext().getApplicationInfo().dataDir;
        String CertFilePath =destPath + "/" +assetFileName;
        File file = new File(CertFilePath);
        if(file.exists())
        {
            //delete file
            file.delete();
        }
        //copy to internal storage
        if(CopyAssets(context,assetFileName,CertFilePath)==1) return CertFilePath;

        return CertFilePath=null;

    }

    private int CopyAssets(Context context,String assetFileName, String toPath)
    {
        AssetManager assetManager = context.getAssets();
        InputStream in = null;
        OutputStream out = null;
        try {
            in = assetManager.open(assetFileName);
            new File(toPath).createNewFile();
            out = new FileOutputStream(toPath);
            byte[] buffer = new byte[1024];
            int read;
            while ((read = in.read(buffer)) != -1)
            {
                out.write(buffer, 0, read);
            }
            in.close();
            in = null;
            out.flush();
            out.close();
            out = null;
            return 1;
        } catch(Exception e) {
            Log.e("tag", "CopyAssets"+e.getMessage());

        }
        return 0;

    }

    private boolean FileExistInAssets(String fileName,Context context)
    {
        try {
            return Arrays.asList(context.getResources().getAssets().list("")).contains(fileName);
        } catch (IOException e) {
            // TODO Auto-generated catch block

            Log.e("tag", "FileExistInAssets"+e.getMessage());

        }
        return false;
    }

JNI SIDE

JNIEXPORT void JNICALL Java_com_example_androidtest_TestActivity_setDir(JNIEnv* env, jobject obj, jstring caCertDir)
{
    if(!caCertDir) return;

    const char* caCertDir_c = env->GetStringUTFChars(caCertDir, NULL);
            if (!caCertDir_c) return ;
    const jsize len = env->GetStringUTFLength(caCertDir);
            LOGI( "CaCertDir: %s", caCertDir_c );
            std::string caCert(caCertDir_c,len);
            caCertPtr=caCert;
            LOGI( "CaCertDirptr in std string: %s", caCertPtr.c_str());
            env->ReleaseStringUTFChars(caCertDir, caCertDir_c);
}

CURL code

CURL* curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
/*  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);
    curl_easy_setopt(curl, CURLOPT_FAILONERROR, TRUE);*/
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curlCallback);
    curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, downloadObject);
    curl_easy_setopt(curl,CURLOPT_CAINFO,caCertPtr.c_str());
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);

    curl_version_info_data * vinfo = curl_version_info( CURLVERSION_NOW );
    if( vinfo->features & CURL_VERSION_SSL )
        // SSL support enabled
         LOGI("SSL support enabled");
    else
    {// No SSL
         LOGI("NO SSL");
    }

    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK){
        LOGI("CURL failed with error code %d", res);
    }

    LOGI("CURL download is OK, result:%d", res);
    curl_easy_cleanup(curl);
    return res == CURLE_OK;
like image 115
Vyshakh Amarnath Avatar answered Oct 07 '22 15:10

Vyshakh Amarnath


OpenSSL 0.9.x used MD5 filename hash. OpenSSL 1.0.x used SHA-1 for the filename hash. Android is using MD5 hash. Why old hash?

I tried libcurl-7.36.0 with openssl-0.9.8zb. It's working on Android with CURLOPT_SSL_VERIFYPEER enabled.

like image 26
Robert Chou Avatar answered Oct 07 '22 15:10

Robert Chou