How to get SSL peer_verify to work on Android?
I have successfully created libcurl-7.36.0 with openssl-1.0.1h on Android. I checked out the sample code for testing HTTPS connection. 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 works if I download the ca-bundle.crt CA bundle file from http://curl.haxx.se/docs/caextract.html and curl_easy_setopt(curl, CURLOPT_CAINFO, "path:/ca-bundle.crt")
.
Here's my question: is there a way to do SSL peer validation validation by reading the certificate from /system/etc/security/cacerts
without manually downloading the CA package file and specifying CURLOPT_CAINFO
?
source to share
- When using libcurl in an Android application, CURLOPT_SSL_VERIFYPEER will fail and therefore prevent CURL from sending data if there is no CA package. One way to overcome this is to disable this setting, which is very very bad. We have to provide our own CA bundle and provide the absolute path of the CA bundle file using the 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 the resource directory.
- CURL expects an absolute path and we cannot provide an absolute path to the assets folder because the packed Android APK is like a zipped folder, so we need to copy the PEM file from assets to internal storage or external storage, but I prefer internal storage because it is private to the application and provides the absolute path to the internal storage directory in CAINFO. For example, if the application name is com.example.androidtest, then the CAINFO path will be "/data/data/com.example.androidtest/cacert.pem".
-
Example CURL implementation using TLS1.2, openSSL 1.01p, curl version 7.40.0, cacert.pem bundle with verify peer, verify hostname, shown at https://github.com/vyshas/CURL-Android-with-verify -peer-
-
The important parts from the above link are 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;
source to share
OpenSSL 0.9.x uses an MD5 hash filename. OpenSSL 1.0.x used SHA-1 for the file name hash. Android uses MD5 hash. Why the old hash?
I tried libcurl-7.36.0 with openssl-0.9.8zb. It works on Android with CURLOPT_SSL_VERIFYPEER enabled.
source to share
EDIT: my previous answer was wrong.
CURLOPT_CAPATH should point to a directory prepared for OpenSSL with c_hash . I don't know if it supports the same format that Android supports.
I found this description on how to import new certificates on recent Android, and it seems to indicate a slightly different file format in this directory than what c_hash does ...
source to share