如何在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.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 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:没有对等证书(又一次)