16

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?

jww
  • 97,681
  • 90
  • 411
  • 885
Robert Chou
  • 211
  • 2
  • 5
  • I believe you can use the `CURL_CA_BUNDLE` environmental variable to specify `/system/etc/security/cacerts`. See [curl.1 the man page](http://curl.haxx.se/docs/manpage.html). – jww Aug 11 '14 at 23:15
  • 1
    @jww: that works for the curl command line tool, not the library! – Daniel Stenberg Aug 12 '14 at 06:32

5 Answers5

10
  • 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;
Dan Albert
  • 10,079
  • 2
  • 36
  • 79
Vyshakh Amarnath
  • 636
  • 6
  • 17
5

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.

Robert Chou
  • 211
  • 2
  • 5
  • 1
    *"Android is using MD5 hash"* - yes, but that's only half of the story. Android ships with OpenSSL 0.9.8. You link against OpenSSL 1.0.1. Your process forks from Zygote, which already loaded 0.9.8. Your 1.0.1 version is *not* mapped in when your process is created. Lots of obscure bugs occur because 0.9.8 and 1.0.1 are not binary compatible. – jww Aug 14 '14 at 06:17
  • Is there a way to make openssl 1.0.x use MD5 when reading the filename hash? Google must do this somehow, as they use 1.0.1j in the [android source](https://github.com/android/platform_external_openssl) – Jack Nov 14 '14 at 16:32
  • 1
    I think the only options when using OpenSSL 1.0.1 are to copy the cert files from /system/etc/security/cacerts to a private internal storage location and name them using SHA-1 algorithm as mentioned [here](http://stackoverflow.com/a/30265791/313113) or store the cert files in a single file. Any other ideas? – Alex Bitek Oct 25 '16 at 15:11
4

The problem is not on Curl, but on openSSL.
openssl 1.1.1x at openssl-1.1.1x/crypto/x509/by_dir.c:
function get_cert_by_subject(), it using X509_NAME_hash() that not compatible with android.
try to modify the by_dir.c at openssl source:

#if defined(__ANDROID__)
  h = X509_NAME_hash_old(name);
#else
  h = X509_NAME_hash(name);
#endif

it's should be solve the problem.
patch:

--- a/openssl-1.1.1k/crypto/x509/by_dir.c
+++ b/openssl-1.1.1k/crypto/x509/by_dir.c
@@ -247,7 +247,11 @@ static int get_cert_by_subject(X509_LOOKUP *xl, 
    
     ctx = (BY_DIR *)xl->method_data;

+#if defined(__ANDROID__)
+    h = X509_NAME_hash_old(name);
+#else
     h = X509_NAME_hash(name);
+#endif
     for (i = 0; i < sk_BY_DIR_ENTRY_num(ctx->dirs); i++) {
         BY_DIR_ENTRY *ent;
         int idx;
  • I think it's the best solution when you need openssl-1.1.1x. No need to download or convert certs.This answer is up to date. – Max Li May 28 '21 at 08:58
  • A bit more context on this can be found in the corresponding OpenSSL issue: https://github.com/openssl/openssl/issues/15154 – Frederik Jan 18 '23 at 14:44
2

EDIT: my previous answer was wrong.

CURLOPT_CAPATH should point to a directory prepared for OpenSSL with the c_hash tool. I don't know if that's the same format that Android provides.

I found this description on how to import new certs to a recent Android, and it seems to indicate a slightly different format of the files in that directory than what c_hash makes...

Daniel Stenberg
  • 54,736
  • 17
  • 146
  • 222
  • According to Nikolay Elenkov in [ICS Trust Store Implementation](http://nelenkov.blogspot.com/2011/12/ics-trust-store-implementation.html), *"Pre-ICS, the trust store was a single file: /system/etc/security/cacerts.bks, a Bouncy Castle (one of the JCE cryptographic providers used in Android) native keystore file... The newly introduced in ICS TrustedCertificateStore class still reads system trusted certificates from /system/etc/security, but adds two new, mutable locations to store CA certificates in /data/misc/keychain: the cacerts-added and cacerts-removed directories."*. – jww Aug 12 '14 at 07:56
1

I got this to work on Android by recompiling libcurl and configuring the default search path for certificates. This can be done by passing the option:

--with-ca-path=/system/etc/security/cacerts to ./configure

or

-DCURL_CA_PATH=/system/etc/security/cacerts to cmake

vee
  • 720
  • 7
  • 8