如何让SSL peer_verify在Android上运行?

Rob*_*hou 13 openssl libcurl android-ndk

我在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-- 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
......
Run Code Online (Sandbox Code Playgroud)

这是我的代码片段..

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);
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

它的工作,如果我请从CA包文件CA-bundle.crt http://curl.haxx.se/docs/caextract.htmlcurl_easy_setopt(curl, CURLOPT_CAINFO, "path:/ca-bundle.crt").

这是我的问题:有没有办法通过从/system/etc/security/cacerts手动下载CA捆绑文件并指定CURLOPT_CAINFO?来读取证书来使SSL对等验证工作?

Rob*_*hou 5

OpenSSL 0.9.x 使用 MD5 文件名哈希。OpenSSL 1.0.x 使用 SHA-1 作为文件名哈希。Android 使用 MD5 哈希。为什么是旧哈希?

我尝试了 libcurl-7.36.0 和 openssl-0.9.8zb。它可以在启用了 CURLOPT_SSL_VERIFYPEER 的 Android 上运行。


Vys*_*ath 5

  • 如果在Android应用程序中使用libcurl,CURLOPT_SSL_VERIFYPEER将失败,因此如果没有CA捆绑,则阻止CURL发送数据.解决这个问题的一种方法是关闭这个非常非常糟糕的选项.我们必须提供自己的CA捆绑包,并使用CURLOPT_CAINFO选项提供CA捆绑包文件的绝对路径.
  • 从"cacert.pem"文件http://curl.haxx.se/docs/caextract.html可以放在资源或资产,但我更喜欢的资源目录.
  • 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 block

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

        }
        return false;
    }
Run Code Online (Sandbox Code Playgroud)

JNI SIDE

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

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);
}
Run Code Online (Sandbox Code Playgroud)

}

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;
Run Code Online (Sandbox Code Playgroud)


Dan*_*erg 2

编辑:我之前的答案是错误的。

CURLOPT_CAPATH应指向使用c_hash工具为 OpenSSL 准备的目录。我不知道Android提供的格式是否相同。

我发现这个关于如何将新证书导入最近的 Android 的描述,它似乎表明该目录中的文件格式与 c_hash 所做的格式略有不同......