10 Jun 2020

3 Mistakes to Avoid When Dealing With OpenSSL Versions and iOS Apps

OpenSSL complexity starts with its version string. Apple, Carthage, and some dependency analysis tools have different opinions about it. Here is how we dealt with them and submitted iOS app to the App Store.

So, we decided to update OpenSSL in iOS app

Themis provides easy-to-use cryptography for multiple languages and platforms. We implement it on top of existing cryptography engines, such as OpenSSL or BoringSSL, which Themis uses as a source of the cryptographic primitives.

It’s a good practice to keep dependencies of your apps up-to-date, especially if they are responsible for security. On Linux systems, the OS vendors are typically responsible for the updates, but on Apple platforms, the maintenance is up to library authors. That is us.

We needed to update OpenSSL to mitigate a recently found CVE, that didn’t affect Themis directly. The current (sad) state of OpenSSL packaging for Apple platforms is a topic for another day.

Long story short: we gave a shot at packaging OpenSSL ourselves, and we were in for some surprises.

OpenSSL versioning scheme

The canonical OpenSSL version consists of four components, with the patch version indicated by a letter: 1.0.2u, 1.1.1g.

This version can be retrieved with OpenSSL_version() call and the version string is stored in OpenSSL binary:


$ strings openssl.framework/openssl | grep "OpenSSL 1.0.2*"

OpenSSL 1.0.2u 20 Dec 2019

If OpenSSL is built as a framework, it has an Info.plist file with metadata like supported platforms and package version.

OpenSSL Info.plist with canonical version “1.0.2u”. Apple doesn’t allow such versions in the App Store.

Everybody uses semver nowadays

Semantic versioning (aka semver) is an increasingly popular approach to versioning software. It is simple, easy to understand, and handy for API consumers. Semver makes it trivial to gauge the scope of changes between releases, so you are more comfortable with making upgrade decisions. The maintainers have to have some discipline with versioning, but it’s not that hard. Many new libraries and ecosystems are now using semver by default.

But what about the older libraries?

OpenSSL has been in development long before semver has been formalized. The OpenSSL team is not very keen on changing their versioning scheme that has served them well over the years.

Unfortunately, sometimes versioning comes into conflict with other requirements.

OpenSSL version vs Apple semver

Apple has their own strict rules for version values (see CFBundleShortVersionString definition and this technical note). The version must be semver-compliant: no more than three numeric components and no letters allowed.

If an app has letters in its version string—or inside version strings of any of its dependencies—the upload to App Store or TestFlight will fail with the following error:


ERROR ITMS-90060: This bundle is invalid.
The value for key CFBundleShortVersionString '1.0.2u' in the
Info.plist file at '${bundlePath}' must be a period-separated list
of at most three non-negative integers. Please find more information
about CFBundleShortVersionString at ...

The catch here is that nothing else warns you about this error.

Developers can successfully work on the app for months, building, signing, and testing it on devices, only to discover the problem at the final stages, ready to submit their app to the App Store.

OpenSSL version vs Carthage semver

Carthage—a popular package manager for Apple development—also requires semver-compatible versions for its packages. This version is typically the same as in the Info.plist file, but it can be different.

The Carthage version must have at most three numeric components, no letters allowed.

Themis is accessible via Carthage, and we have to specify the minimum OpenSSL version required.

The workaround we have arrived to is to renumber OpenSSL versions like this:

OpenSSL canonical version Numeric version
1.0.2 1.0.200
1.0.2a 1.0.201
1.0.2u 1.0.221
1.1.1 1.1.100
1.1.1a 1.1.101
1.1.1g 1.1.107

The rule is simple: multiply the minor version by 100 then add alphabet order of the letter version. For example, “u” is the 21st letter of the English alphabet, so 1.0.2u translates into 1.0.221.

OpenSSL version vs dependency analysers

As if Apple requirements and Carthage requirements were not enough, we found out that the OpenSSL version from Info.plist is meaningful to certain software composition analysis tools (aka SCA, aka dependency analysers). SCA tools are often used to detect and track open source dependencies and notify developers about vulnerabilities in them.

The analysers may use Info.plist as a cheap way to find out the OpenSSL version, instead of looking for the actual OpenSSL version inside the binary.

Obviously, the analyser does not expect the minor version to be in hundreds (like 1.0.221), so to be on the safe side, it treats OpenSSL as potentially vulnerable. Such false positives are frustrating to deal with for app developers, since the warnings may be caused by transitive dependencies which the developers cannot control directly.

Happiness rating across different tools regarding OpenSSL version:

canonical version
("1.0.2u")
numeric version
("1.0.221")
real OpenSSL version 😺 ☠️
Apple
(submit to App Store)
☠️ 😺
Carthage
(use in Cartfile)
☠️ 😺
Dependency analysers
(report vulnerabilities)
😺 ☠️

Reconciling OpenSSL and App Store

No size fits all, so the best workaround we came up with for this situation is to leave the original OpenSSL version in Info.plist, use the translated version for Carthage packages and make sure that the pre-release scripts include a step to translate the canonical version into numeric one, acceptable for App Store.

For example, you can patch OpenSSL version in Info.plist file like this:


$ plutil -replace CFBundleShortVersionString -string 1.0.221 Info.plist

This patching can occur on CI as an extra “script step” after fetching the repository and before building an application and uploading to the App Store / TestFlight. If you know a better solution to this OpenSSL versioning problem, ping us on Twitter or drop us an email.

OpenSSL_version()
function call
1.0.2u Runtime inspection returns true version
Info.plist
in OpenSSL.framework
1.0.2u Make dependency analysers happy
Cartfiles of apps
and libraries
that use OpenSSL
1.0.221 Make Carthage happy
Info.plist in apps
submitted to the App Store
1.0.221 Make Apple happy

No OpenSSL version fits all. Using canonical version during development and patching it to numeric before App Store submission seems to be a working solution.



The approach of "patching" OpenSSL version on CI raises obvious security questions:

● The dependency analyser checks only Info.plist. Do you trust OpenSSL maintainers to use the correct version?
● Developers are typically able to modify CI configuration. Now consider the possibility of an insider threat, tricking you into using a vulnerable dependency.
● What is a healthy, secure workflow for libraries that do not comply with semver?

Discussing these questions is out of the scope of this post and falls into the “security vs real-world development” territory.

But I need cryptography, what shall I do?

If you’re a developer and you’re dealing with cryptography for your app, consider using high-level cryptographic libraries like Themis instead of OpenSSL. No need to struggle with OpenSSL if your goal is to protect users’ data.

We believe in “boring crypto”: accessible, strong, easy-to-use and hard-to-misuse. Check out our open-source cryptographic tools, and do not hesitate to make another step and drop us a line for any cryptographic assistance.


This post is authored by @ilammy and @vixentael. Say hi to them 👋