1

I am working on a C++ Android app that uses gSOAP with OpenSSL. To enable OpenSSL to validate the server certificate I need to provide it with location for the cacerts.pem file, otherwise all HTTPS connections fail.

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

  • I am able to copy cacerts.pem into the APK root by placing it in src/resources or res/raw/ or assets/. But SSL_CTX_load_verify_locations(ctx, "cacerts.pem", nullptr) fails to find the file. How can I make OpenSSL be able to read the PEM file inside APK?

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

My app needs to talk to sharepoint online sites (https://*.example.com/)

jww
  • 97,681
  • 90
  • 411
  • 885
vishalb
  • 63
  • 6
  • If you provide the server name and service port, then we can give you more details on what you need. For example, we can say, *"you need certificate X"* rather than vague *"...the one CA needed to verify the host..."*. – jww Mar 16 '16 at 01:48

2 Answers2

1

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

You must load the one CA needed to verify the host's certificate.

You can load intermediates required to build a path from the host to the CA, but its optional. A well configured server will send the needed intermediate certificates, but not all of them are well configured.

You don't load cacert.pem or equivalent. cacert.pem contains hundreds of certifcates, and all of them but one are wrong.


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()?

No. You usually need one certificate, and that's the CA that certifies the host's certificate. It can be a public CA, like Comodo, or it can be a private CA internal to the organization.

You usually place it in the res/ or assets/ folder. Also see Difference between /res and /assets directories on Stack Overflow.


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

Yes, but you might have to take the string, write it to the filesystem, and then use the file for SSL_CTX_load_verify_locations. So in the end, its probably best to place it in res/ or assets/.


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

One important note for hostname validation. OpenSSL 1.0.2 and earlier does not perform name matching. It performs other customary checks, like ensuring a path exists from the end-entity certificate (the host) to the CA certificate (the root of trust) and the certificate is not expired. But it does not perform the hostname matching.

OpenSSL 1.1.0 does perform hostname matching. It will be released soon.

As a stop gap, you usually lift the missing code from a library like cURL. cURL performs the hostname matching, and Daniel Stenberg is happy to share the code. I think its located in ssl.c, if memory serves me.

Also see TLS Client on the OpenSSL wiki.

Community
  • 1
  • 1
jww
  • 97,681
  • 90
  • 411
  • 885
  • Thanks jww. My app needs to talk to sharepoint.com. I am using gSOAP which requires .pem file location that it passes to openssl. I understand I should remove all the other certificated from the .pem file. My only open question is why is SSL_CTX_load_verify_locations failing with "cacerts.pem" when it exists in the APK root. – vishalb Mar 16 '16 at 23:08
1

I was able to finally get it working. I am a complete Android newbie so posting my notes here in case they are useful to others like me.

As @jww suggested I will remove all the unnecessary certificates from my cacerts.pem file before shipping.

I had 3 options to place my cacerts.pem in .APK.

  1. At the root (put file in src\main\resources (not src\main\res) will cause it to be placed at the root of apk)
  2. In res\raw (put file in src\main\res\raw)
  3. In assets (put file in src\main\assets)

In my brief research I could not find a way to directly read files from apk root or from res\raw using c++ apis. So I went ahead with #3.

Now assets files are also not directly readable using 'standard' c++ functions (correct me if i am wrong), so I made a copy of the file from assets folder to external location using this code in my Java layer.

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)
{
}

After this I passed this file location to my c++ code and was able to make this call

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

Note: This is not final production code. Just proof-of-concept at this stage. Please feel free to point out any bugs you see or suggestions you have.

Note: For a while openssl would still fail to read the file, so I verified the file was correctly created and available using ifstream file("/data.../cacerts.pem") and it was there. That error somehow resolved itself later.

vishalb
  • 63
  • 6
  • Also see [How to get file size in Java](http://stackoverflow.com/q/8721262), [Get the size of an Android file resource?](http://stackoverflow.com/q/6049926) and [Get path of Android resource](http://stackoverflow.com/q/6301493). – jww Mar 18 '16 at 08:02