如何为 gSoap-OpenSSL Android 应用程序存储和加载 cacerts.pem

How to store and load cacerts.pem for gSoap-OpenSSL Android app

我正在开发一个使用 gSOAP 和 OpenSSL 的 C++ Android 应用程序。为了使 OpenSSL 能够验证服务器证书,我需要为其提供 cacerts.pem 文件的位置,否则所有 HTTPS 连接都会失败。

我的问题是:在 Android 上存储和加载 cacerts.pem 的正确方法是什么?

我的应用需要与 Sharepoint 在线网站对话 (https://*.example.com/)

What is the right way to store and load cacerts.pem on Android?

必须加载验证主机证书所需的一个 CA。

可以加载构建从主机到 CA 的路径所需的中间体,但它是可选的。配置良好的服务器将发送所需的中间证书,但并非所有服务器都配置良好。

加载cacert.pem或等价物。 cacert.pem 包含数百个证书,除了一个之外,所有证书都是错误的。


Do I need to copy cacerts.pem somehow into the .APK (how to do this?) and provide path to the file to SSL_CTX_load_verify_locations()?

没有。您通常需要一个证书,即证明主机证书的 CA。它可以是 public CA,例如 Comodo,也可以是组织内部的私有 CA。

你通常把它放在res/assets/ 文件夹。另请参阅 Stack Overflow 上的 Difference between /res and /assets directories


Is there a way to store cacerts.pem as a string buffer and thus avoid carrying it in the .APK?

是的,但您可能需要获取字符串,将其写入文件系统,然后将文件用于 SSL_CTX_load_verify_locations。所以最后,最好将它放在 res/assets/.


... sharepoint online sites (https://*.example.com/)

主机名验证的重要说明。 OpenSSL 1.0.2 及更早版本 执行名称匹配。它执行其他常规检查,例如确保存在从终端实体证书(主机)到 CA 证书(信任根)的路径并且证书未过期。但它执行主机名匹配。

OpenSSL 1.1.0 执行主机名匹配。即将发布。

作为权宜之计,您通常会从 cURL 等库中提取丢失的代码。 cURL 执行主机名匹配,Daniel Stenberg 很乐意分享代码。我认为它位于 ssl.c,如果没记错的话。

另请参阅 OpenSSL wiki 上的 TLS Client

我终于能够让它工作了。我是一个完整的 Android 新手,所以在这里张贴我的笔记以防它们对像我这样的其他人有用。

按照@jww 的建议,我会在发货前从我的 cacerts.pem 文件中删除所有不必要的证书。

我有 3 个选项可以将我的 cacerts.pem 放在 .APK 中。

  1. 在根目录(将文件放在 src\main\resources(而不是 src\main\res)将导致它被放置在 apk 的根目录)
  2. In res\raw(将文件放入src\main\res\raw)
  3. 在资产中(将文件放入src\main\assets)

在我的简短研究中,我找不到使用 C++ API 直接从 apk 根目录或 res\raw 读取文件的方法。所以我继续#3。

现在资产文件也不能使用 'standard' c++ 函数直接读取(如果我错了请纠正我),所以我在我的 Java层.

try
{
    AssetManager assetManager = this.getAssets();
    File file = new File(getFilesDir(), "cacerts.pem");
    InputStream in = assetManager.open("cacerts.pem");
    OutputStream out = new FileOutputStream(file);

    byte[] buffer = new byte[65536 * 2];
    int read;
    while ((read = in.read(buffer)) != -1) {
        out.write(buffer, 0, read);
    }

    in.close();
    out.flush();
    out.close();
}
catch (Exception e)
{
}

在此之后,我将此文件位置传递给了我的 C++ 代码并能够进行此调用

SSL_CTX_load_verify_locations(ctx, "/data/user/0/com.company.app/files/cacerts.pem", NULL)

注意:这不是最终的生产代码。在这个阶段只是概念验证。请随时指出您看到的任何错误或您的建议。

注意:有一段时间 openssl 仍然无法读取文件,因此我使用 ifstream file("/data.../cacerts.pem") 验证了文件是否已正确创建并且可用并且它是那里。该错误后来以某种方式自行解决。