【问题标题】:How to make SSL peer_verify work on Android?如何使 SSL peer_verify 在 Android 上工作?
【发布时间】:2014-10-04 21:41:45
【问题描述】:

我已经在 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
......

这是我的代码的 sn-p..

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.htmlcurl_easy_setopt(curl, CURLOPT_CAINFO, "path:/ca-bundle.crt") 下载 CA 捆绑文件 ca-bundle.crt,它就可以工作。

这是我的问题:有什么方法可以通过从/system/etc/security/cacerts 读取证书来使 SSL 对等验证工作,而无需手动下载 CA 捆绑文件并指定 CURLOPT_CAINFO

【问题讨论】:

  • 相信你可以使用CURL_CA_BUNDLE环境变量来指定/system/etc/security/cacerts。见curl.1 the man page
  • @jww:适用于 curl 命令行工具,而不是库!

标签: android-ndk openssl libcurl


【解决方案1】:
  • 如果在 Android 应用程序中使用 libcurl,CURLOPT_SSL_VERIFYPEER 将失败,因此在没有 CA 捆绑包的情况下阻止 CURL 发送数据。克服这个问题的一种方法是关闭这个非常非常糟糕的选项。 我们必须提供我们自己的 CA 包,并使用 CURLOPT_CAINFO 选项提供 CA 包文件的绝对路径。
  • 来自http://curl.haxx.se/docs/caextract.html 的“cacert.pem”文件可以放在资源或资产中,但我更喜欢资产目录。
  • CURL 需要绝对路径,我们不能提供资产文件夹的绝对路径,因为打包的 android APK 文件就像一个压缩文件夹,因此我们需要将 PEM 文件从资产复制到内部存储或外部存储,但我更喜欢内部存储,因为它对应用程序私有,并提供 CAINFO 中内部存储目录的绝对路径。例如,如果应用名称为 com.example.androidtest,则 CAINFO 路径将为“/data/data/com.example.androidtest/cacert.pem”。
  • 使用 TLS1.2 的 CURL 示例实现,openSSL 1.01p,curl 版本 7.40.0,cacert.pem 捆绑与验证对等,验证主机名选项显示在 https://github.com/vyshas/CURL-Android-with-verify-peer-

  • 以上链接中的重要部分如下所示:

JAVA 端

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 端

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 代码

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;

【讨论】:

    【解决方案2】:

    OpenSSL 0.9.x 使用 MD5 文件名哈希。 OpenSSL 1.0.x 使用 SHA-1 作为文件名哈希。 Android 正在使用 MD5 哈希。 Why old hash?

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

    【讨论】:

    • “Android 正在使用 MD5 哈希” - 是的,但这只是故事的一半。 Android 附带 OpenSSL 0.9.8。您链接到 OpenSSL 1.0.1。您的进程来自 Zygote,它已经加载了 0.9.8。您的 1.0.1 版本在创建进程时映射。因为 0.9.8 和 1.0.1 不兼容二进制,所以会出现很多不明显的错误。
    • 有没有办法让 openssl 1.0.x 在读取文件名哈希时使用 MD5? Google 必须以某种方式做到这一点,因为他们在 android source 中使用 1.0.1j
    • 我认为使用 OpenSSL 1.0.1 时的唯一选择是将证书文件从 /system/etc/security/cacerts 复制到私有内部存储位置并使用提到的 SHA-1 算法命名它们here 或将证书文件存储在单个文件中。还有其他想法吗?
    【解决方案3】:

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

    CURLOPT_CAPATH 应该指向使用c_hash 工具为 OpenSSL 准备的目录。不知道是不是和安卓提供的格式一样。

    我发现this description 介绍了如何将新证书导入最新的 Android,它似乎表明该目录中的文件格式与 c_hash 生成的文件格式略有不同...

    【讨论】:

    • 根据 Nikolay Elenkov 在ICS Trust Store Implementation 中的说法,“在 ICS 之前,信任存储是一个单独的文件:/system/etc/security/cacerts.bks,一个充气城堡(其中之一Android 中使用的 JCE 加密提供程序)本机密钥库文件... ICS 中新引入的 TrustedCertificateStore 类仍然从 /system/etc/security 读取系统可信证书,但添加了两个新的可变位置以将 CA 证书存储在 /data/misc /keychain:添加 cacerts 和删除 cacerts 的目录。".
    【解决方案4】:

    问题不在于 Curl,而在于 openSSL。
    openssl 1.1.1x 在 openssl-1.1.1x/crypto/x509/by_dir.c:
    函数get_cert_by_subject(),它使用了与android不兼容的X509_NAME_hash()。
    尝试修改openssl源的by_dir.c:

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

    应该可以解决问题了。
    补丁:

    --- 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;
    

    【讨论】:

    • 当你需要 openssl-1.1.1x 时,我认为这是最好的解决方案。无需下载或转换证书。此答案是最新的。
    【解决方案5】:

    我通过重新编译 libcurl 并配置证书的默认搜索路径,让它在 Android 上运行。这可以通过传递选项来完成:

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

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

    【讨论】:

      猜你喜欢
      • 2022-10-13
      • 2013-11-22
      • 1970-01-01
      • 1970-01-01
      • 2019-02-24
      • 1970-01-01
      • 2012-11-23
      • 2014-09-22
      相关资源
      最近更新 更多