如何在Android上进行SSL对等验证工作()

男儿欲遂平生志,五经勤向窗前读。这篇文章主要讲述如何在Android上进行SSL对等验证工作?相关的知识,希望能为你提供帮助。
我在android上用openssl-1.0.1h成功构建了libcurl-7.36.0。我运行了一个示例代码来测试HTTPS连接。默认情况下启用SSL_VERIFYPEER。 Android上的证书路径是/ system / etc / security / cacerts,因此我将CURLOPT_CAPATH设置为/ system / etc / security / cacerts。

ls -l /system/etc/security/cacerts -rw-r--r-- rootroot4767 2012-09-22 11:57 00673b5b.0 -rw-r--r-- rootroot4573 2012-09-22 11:57 03e16f6c.0 -rw-r--r-- rootroot5292 2012-09-22 11:57 08aef7bb.0 ......

这是我的代码片段..
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始终返回错误:
== 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

如果我从http://curl.haxx.se/docs/caextract.html和curl_easy_setopt(curl, CURLOPT_CAINFO, "path:/ca-bundle.crt")下载CA捆绑文件ca-bundle.crt,它就可以工作了。
这是我的问题:有没有办法通过从/system/etc/security/cacerts读取证书而无需手动下载CA捆绑文件并指定CURLOPT_CAINFO来使SSL对等验证工作?
答案
  • 如果在Android应用程序中使用libcurl,CURLOPT_SSL_VERIFYPEER将失败,因此如果没有CA捆绑,则阻止CURL发送数据。解决这个问题的一种方法是关闭这个非常非常糟糕的选项。我们必须提供自己的CA捆绑包,并使用CURLOPT_CAINFO选项提供CA捆绑包文件的绝对路径。
  • 来自http://curl.haxx.se/docs/caextract.html的“cacert.pem”文件可以放在资源或资产中,但我更喜欢资产目录。
  • CURL期望绝对路径,我们不能给出资产文件夹的绝对路径,因为打包的android APK文件就像一个压缩文件夹,因此我们需要将PEM文件从资产复制到内部存储或外部存储,但我更喜欢内部存储,因为它是私有的app并提供CAINFO中内部存储目录的绝对路径。例如,如果应用名称为com.example.androidtest,则CAINFO路径为“/data/data/com.example.androidtest/cacert.pem”。
  • CURL的示例实现使用TLS1.2,openSSL 1.01p,curl版本7.40.0,带有验证对等的cacert.pem包,验证主机名选项显示在https://github.com/vyshas/CURL-Android-with-verify-peer-中
  • 以上链接的重要部分如下所示:
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 blockLog.e("tag", "FileExistInAssets"+e.getMessage()); } return false; }

JNI SIDEJNIEXPORT 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代码
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;

另一答案OpenSSL 0.9.x使用MD5文件名哈希。 OpenSSL 1.0.x使用SHA-1作为文件名哈希。 Android正在使用MD5哈希。 Why old hash?
我用openssl-0.9.8zb尝试了libcurl-7.36.0。它在启用了CURLOPT_SSL_VERIFYPEER的Android上运行。
另一答案编辑:我以前的回答是错误的。
CURLOPT_CAPATH应该指向使用c_hash工具为OpenSSL准备的目录。我不知道这是否与Android提供的格式相同。
我发现this description关于如何将新证书导入到最近的Android中,它似乎表明该目录中的文件格式与c_hash的格式略有不同...
另一答案我通过重新编译libcurl并配置证书的默认搜索路径,让它在Android上运行。这可以通过传递选项来完成:
--with-ca-path = / system / etc / security / cacerts to ./configure
要么
【如何在Android上进行SSL对等验证工作()】-DCURL_CA_PATH = / system / etc / security / cacerts to cmake

    推荐阅读