fix: isolate bundled OpenSSL so it can't collide with another copy - #1070
Merged
Conversation
Two libraries in one app can each bring their own OpenSSL — commonly op-sqlite with sqlcipher enabled. Today that breaks in two ways: Android: both ship libcrypto.so/libssl.so, so mergeNativeLibs fails, and pickFirst 'fixes' the build by leaving one library bound to a version it was not compiled against. iOS: QuickCrypto is source-built, so its OpenSSL references resolve at *app* link time. Anything that statically embeds OpenSSL wins first-come resolution and QuickCrypto silently runs on a foreign OpenSSL with different struct layouts (EXC_BAD_ACCESS in EVP_KEYMGMT_get0_name). Android now links OpenSSL statically and localizes those symbols with -Wl,--exclude-libs,ALL, so no libcrypto.so ships at all. iOS vendors a prebuilt static OpenSSL whose every global symbol has been renamed to rnqc_* with the originals demoted to non-external, built by scripts/build-openssl-apple.sh. The rename happens at link level rather than through a -include prefix header so that it also covers perlasm symbols, which the preprocessor cannot reach. Both changes depend on artifacts that must be published first: - io.github.ronickg:openssl-static:3.6.2-1 (ndkports) - the openssl-apple-3.6.2 release, whose checksum goes in the podspec
Sets openssl_sha256 to the checksum from the openssl-apple-3.6.2 release (CI-built, verified: all 7 slices export only rnqc_* symbols, and the macOS slice runs SHA-256 and P-256 keygen through them). Also narrows the exclude_files pattern. CocoaPods runs exclude_files against vendored_frameworks as well as sources — paths_for_attribute passes spec_consumer.exclude_files as :exclude_patterns — so the previous 'ios/openssl/**/*' silently dropped the .xcframework from the pod and the library was never linked. pod install succeeded and looked clean; only the missing -l"QuickCryptoOpenSSL" in the app's OTHER_LDFLAGS gave it away.
The prefix header was generated by intersecting the renamed symbols with identifiers found in the text of OpenSSL's public headers. Much of the public API never appears as literal text: DECLARE_ASN1_FUNCTIONS(X509) token-pastes X509_free/d2i_X509/i2d_X509, DECLARE_PEM_write_bio produces PEM_write_bio_X509. 1100 names were dropped — the whole ASN.1 and PEM surface — so QuickCrypto kept calling the original names, which are non-external in the archive. Collect the identifiers from clang -E output instead. 5379 -> 6479 defines, and the example app now links. Caught by building the example app; every prior check passed, since the test consumer only exercised EVP digests and keygen and never touched X509 or PEM.
The openssl-apple-3.6.2 release was rebuilt from the fixed prefix-header generation; the archives were always correct, but the header ships inside the zip so the checksum moved.
openssl-static 3.6.2-1 was unusable — ndkports derives the AAR's manifest package from the prefab package name, and 'openssl-static' is not a valid Java identifier, so AGP rejected it before compiling anything. Fixed upstream in ronickg/ndkports#6 and republished as 3.6.2-2, which names the prefab package opensslstatic (the Maven coordinate is unchanged). Verified against the published artifact: builds all 4 ABIs, the APK ships no libcrypto.so or libssl.so, libQuickCrypto.so exports no OpenSSL symbols while keeping JNI_OnLoad and the Nitro entry points, and the OpenSSL code it does contain is local.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
🤖 End-to-End Test Results - iOSStatus: ✅ Passed 📸 Final Test ScreenshotScreenshot automatically captured from End-to-End tests and will expire in 30 days This comment is automatically updated on each test run. |
Contributor
🤖 End-to-End Test Results - AndroidStatus: ✅ Passed 📸 Final Test ScreenshotScreenshot automatically captured from End-to-End tests and will expire in 30 days This comment is automatically updated on each test run. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Closes #1059.
Two libraries in one app can each bring their own OpenSSL — most commonly
@op-engineering/op-sqlitewithsqlcipher: true. Today that breaks both platforms, in different ways.Android — both ship
libcrypto.so/libssl.so, somergeNativeLibsfails outright.pickFirst"fixes" the build by dropping one copy, leaving whichever library lost the coin toss bound to a version it wasn't compiled against.iOS — QuickCrypto is source-built, so its OpenSSL references resolve when the app links. Anything that statically embeds OpenSSL wins first-come resolution and QuickCrypto silently runs on a foreign OpenSSL with different struct layouts. Reported in #1059 as
EXC_BAD_ACCESSinEVP_KEYMGMT_get0_nameon the firstsubtle.generateKey().Worth correcting one assumption from the issue thread: iOS was not statically linking OpenSSL.
OpenSSL-Universal 3.6.2000ships a dynamic framework, so "make ours static" alone would have changed nothing there.Android
Links OpenSSL statically from a new
openssl-staticprefab and localizes the symbols with-Wl,--exclude-libs,ALL. Nolibcrypto.soships at all, so there is nothing left to collide.Required two upstream PRs: ronickg/ndkports#5 (the port) and ronickg/ndkports#6 (a hyphen in the prefab package name produced an invalid Java package in the AAR manifest, which AGP rejects). Published as
io.github.ronickg:openssl-static:3.6.2-2.iOS
Vendors a prebuilt static OpenSSL whose every global symbol is renamed to
rnqc_*, with the originals demoted to non-external. Nothing else defines those names, so the two copies cannot see each other in either direction.OpenSSL-Universalis dropped.The rename happens at link level (
ld -r+-alias_list+-unexported_symbols_list), not via a BoringSSL-style-includeprefix header. That matters: a prefix header cannot rewrite.globl _sha256_block_data_order, so every perlasm symbol would stay exposed.Built by
scripts/build-openssl-apple.sh(landed in #1068), published asopenssl-apple-3.6.2and pinned by SHA-256 in the podspec.Verification
Both platforms built against the published artifacts, not local ones.
Android — all 4 ABIs build; APK contains 0
libcrypto.so/libssl.so;libQuickCrypto.soexports 0 OpenSSL symbols in its dynamic table while keepingJNI_OnLoadand the Nitro entry points; OpenSSL code present but local (t).iOS — example app compiles and links with
SODIUM_ENABLED=1; shipped binary has 10,924rnqc_*symbols and 0 unprefixed OpenSSL symbols exported;_X509_free,_d2i_X509,_EVP_MD_CTX_newall present as non-external.Runtime verification
The example app's test suite passes on both the iOS simulator and the Android emulator. Artifact inspection confirms those runs used the new static builds rather than stale ones:
QuickCryptoExample.debug.dylib: 10,924rnqc_*symbols, 0 unprefixed OpenSSL symbols exported, 0 links toOpenSSL.framework, and the framework is gone from the bundle.jetified-openssl-static-3.6.2-2/.../libcrypto.a; the APK contains 0libcrypto.so/libssl.soandlibQuickCrypto.soexports 0 OpenSSL symbols.Size: iOS grows ~1.3 MB net (6.4 MB static vs the 5.1 MB embedded dylib it replaces), and the app no longer embeds or code-signs
OpenSSL.framework.Also supersedes #1069, which carries the same prefix-header fix for main standalone — whichever merges first makes the other a no-op.
🤖 Generated with Claude Code