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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在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-- 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
......

这是我的代码片段..

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读取证书而无需手动下载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 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代码

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

要么

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

以上是关于如何在Android上进行SSL对等验证工作?的主要内容,如果未能解决你的问题,请参考以下文章

Android SSL HttpGet(无对等证书)错误或(对等连接已关闭)错误

javax.net.ssl.SSLException:SSL 握手中止连接由对等方重置,同时调用 web 服务 Android

Android 7.0:'javax.net.ssl.SSLHandshakeException:连接被对等方关闭

Android ssl:javax.net.ssl.SSLPeerUnverifiedException:没有对等证书(又一次)

Spring saml 与 IDP 服务器的集成使名称的 SSL 对等点主机名验证失败:null

Curl 中出现错误 - 对等证书无法使用已知 CA 证书进行身份验证