Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 61 additions & 79 deletions docs/ios-setup.md
Original file line number Diff line number Diff line change
@@ -1,113 +1,95 @@
# iOS Setup Guide

## SPM Framework Embedding Required
## SPM Framework Signing (Physical Devices)

QuickCrypto uses Swift Package Manager (SPM) dependencies that must be manually embedded in your app bundle:
QuickCrypto uses OpenSSL via Swift Package Manager (SPM). On **physical iOS devices**, SPM frameworks require additional configuration to be properly embedded and code-signed in your app bundle.

- **OpenSSL 3.6+** (required) - For ML-DSA post-quantum cryptography support
- **Sodium** (optional) - For XSalsa20 cipher support via libsodium (when `SODIUM_ENABLED=1`)
> **Simulator builds work without this configuration.** This is only required for physical device deployment.

### Why is this needed?
### Quick Setup

CocoaPods doesn't automatically embed SPM frameworks into the final app bundle. Without this configuration, you'll encounter runtime errors:
Add to your `ios/Podfile`:

```
dyld: Library not loaded: @rpath/OpenSSL.framework/OpenSSL
```ruby
# At the top of your Podfile
require_relative '../node_modules/react-native-quick-crypto/scripts/quickcrypto_spm_fix'

target 'YourAppName' do
# ... your pods ...

post_install do |installer|
react_native_post_install(installer) # if you have this

# Fix QuickCrypto SPM framework signing for physical devices
quickcrypto_fix_spm_signing(installer)
end
end
```

This is a temporary limitation of mixing CocoaPods + SPM. It will be resolved when React Native fully migrates to SPM (expected 2026).
Then run:

## Configuration
```bash
cd ios && pod install
```

### Why is this needed?

Add the following to your `ios/Podfile` inside the `post_install` hook:
When you try to install on a physical device without this fix, you'll see:

```ruby
post_install do |installer|
# ... your existing post_install code (react_native_post_install, etc.) ...

# Embed SPM frameworks from QuickCrypto
main_project_path = File.join(installer.sandbox.root.parent, 'YourAppName.xcodeproj')
main_project = Xcodeproj::Project.open(main_project_path)
app_target = main_project.targets.find { |t| t.name == 'YourAppName' }

if app_target
embed_phase_name = 'Embed SPM Frameworks (QuickCrypto)'
existing_phase = app_target.shell_script_build_phases.find { |p| p.name == embed_phase_name }

unless existing_phase
phase = app_target.new_shell_script_build_phase(embed_phase_name)
phase.shell_script = <<~SCRIPT
mkdir -p "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}"

# Embed OpenSSL.framework (required for ML-DSA)
if [ -d "${BUILT_PRODUCTS_DIR}/OpenSSL.framework" ]; then
rsync -av --delete "${BUILT_PRODUCTS_DIR}/OpenSSL.framework" "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/"
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ]; then
/usr/bin/codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework"
fi
fi

# Embed Sodium.framework (optional, if SODIUM_ENABLED=1)
if [ -d "${BUILT_PRODUCTS_DIR}/Sodium.framework" ]; then
rsync -av --delete "${BUILT_PRODUCTS_DIR}/Sodium.framework" "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/"
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ]; then
/usr/bin/codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sodium.framework"
fi
fi
SCRIPT

# Insert before the CocoaPods embed frameworks phase
embed_pods_phase = app_target.shell_script_build_phases.find { |p| p.name == '[CP] Embed Pods Frameworks' }
if embed_pods_phase
app_target.build_phases.move(phase, app_target.build_phases.index(embed_pods_phase))
end

main_project.save
end
end
end
```
Failed to verify code signature of .../OpenSSL.framework : 0xe8008015
```

This happens because:
1. OpenSSL is distributed as a pre-built, pre-signed xcframework via SPM
2. CocoaPods' `spm_dependency` adds it to the Pods project but doesn't embed it in your app
3. The framework must be re-signed with your app's code signing identity

This is a known limitation of mixing CocoaPods + SPM. See [issue #857](https://github.com/margelo/react-native-quick-crypto/issues/857).

**Important:** Replace `YourAppName` with your actual Xcode target name (usually matches your app name).
### Multiple Targets

## Example
If you have multiple app targets, specify which one:

See the [example app's Podfile](../../example/ios/Podfile) for a complete working reference.
```ruby
quickcrypto_fix_spm_signing(installer, app_target_name: 'YourSpecificTarget')
```

## Enabling libsodium (Optional)

To enable XSalsa20 cipher support, set the environment variable before installing pods:
For XSalsa20 cipher support, set the environment variable before your target:

```ruby
# At the top of your Podfile
ENV['SODIUM_ENABLED'] = '1'
```

Then run:

```bash
cd ios && pod install
target 'YourAppName' do
# ...
end
```

## Troubleshooting

### Error: "Library not loaded: @rpath/OpenSSL.framework/OpenSSL"
### Error: `0xe8008015` on physical device

This means the SPM frameworks aren't being embedded. Verify:
This is the code signing error. Make sure you've added `quickcrypto_fix_spm_signing(installer)` to your Podfile's `post_install` hook and run `pod install`.

1. The `post_install` hook is properly configured in your Podfile
2. You're using `use_frameworks! :linkage => :dynamic` (required for SPM dependencies)
3. Run `cd ios && pod install` after modifying the Podfile
4. Clean build folder in Xcode (Cmd+Shift+K) and rebuild
### Error: "Library not loaded: @rpath/OpenSSL.framework"

### Dynamic Frameworks Required
Same fix - the `quickcrypto_fix_spm_signing` function handles both embedding and signing.

QuickCrypto requires dynamic framework linking due to SPM dependencies. Add this to your Podfile:
### Build still fails after adding the fix

```ruby
use_frameworks! :linkage => :dynamic
```
1. Clean build: `Cmd+Shift+K` in Xcode
2. Delete derived data: `rm -rf ~/Library/Developer/Xcode/DerivedData`
3. Run `pod install` again
4. Rebuild

### "Could not find main Xcode project" warning

The helper script couldn't find your `.xcodeproj` file. Use the `app_target_name` parameter or check that your project structure is standard.

## The SPM Situation

## Future
Yes, this is unfortunate. CocoaPods is being deprecated, SPM is supposed to be the future, but SPM's handling of binary frameworks with CocoaPods is broken. The `spm_dependency` bridge in React Native doesn't properly handle framework embedding and code signing.

When React Native completes its migration to Swift Package Manager (expected 2026), this manual embedding step will no longer be necessary. SPM packages will be properly integrated by default.
This workaround will be unnecessary when React Native fully migrates to SPM (timeline unclear).
45 changes: 5 additions & 40 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
ENV['SODIUM_ENABLED'] = '1'

# Fix QuickCrypto SPM framework signing for physical devices
require_relative '../../packages/react-native-quick-crypto/scripts/quickcrypto_spm_fix'

# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
Expand Down Expand Up @@ -59,45 +62,7 @@ target 'QuickCryptoExample' do
end
end

# Embed SPM frameworks from QuickCrypto into the app bundle
# SPM frameworks added to Pods project need manual embedding
main_project_path = File.join(installer.sandbox.root.parent, 'QuickCryptoExample.xcodeproj')
main_project = Xcodeproj::Project.open(main_project_path)
app_target = main_project.targets.find { |t| t.name == 'QuickCryptoExample' }

if app_target
embed_phase_name = 'Embed SPM Frameworks (QuickCrypto)'
existing_phase = app_target.shell_script_build_phases.find { |p| p.name == embed_phase_name }

unless existing_phase
phase = app_target.new_shell_script_build_phase(embed_phase_name)
phase.shell_script = <<~SCRIPT
# Embed OpenSSL.framework from SPM build into app bundle
# SPM builds the framework to BUILT_PRODUCTS_DIR but doesn't embed it
OPENSSL_FRAMEWORK="${BUILT_PRODUCTS_DIR}/OpenSSL.framework"

if [ -d "$OPENSSL_FRAMEWORK" ]; then
echo "Found OpenSSL.framework at $OPENSSL_FRAMEWORK"
rsync -av --delete "$OPENSSL_FRAMEWORK" "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/"

if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ] && [ "${CODE_SIGNING_REQUIRED:-}" != "NO" ]; then
/usr/bin/codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework"
fi
echo "OpenSSL.framework embedded successfully"
else
echo "warning: OpenSSL.framework not found at $OPENSSL_FRAMEWORK"
fi
SCRIPT

# Move it before the existing embed frameworks phase
embed_pods_phase = app_target.shell_script_build_phases.find { |p| p.name == '[CP] Embed Pods Frameworks' }
if embed_pods_phase
app_target.build_phases.move(phase, app_target.build_phases.index(embed_pods_phase))
end

main_project.save
Pod::UI.puts "[QuickCrypto] Added 'Embed SPM Frameworks (QuickCrypto)' build phase"
end
end
# Fix QuickCrypto SPM framework signing for physical devices
quickcrypto_fix_spm_signing(installer)
end
end
2 changes: 1 addition & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2845,6 +2845,6 @@ SPEC CHECKSUMS:
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 11c9686a21e2cd82a094a723649d9f4507200fb0

PODFILE CHECKSUM: a55db5270505ab83586da7672ed603a4ad498447
PODFILE CHECKSUM: c53d893905e8bc54582367d8191b1d2814070820

COCOAPODS: 1.15.2
46 changes: 6 additions & 40 deletions example/ios/QuickCryptoExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,9 @@
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
8C63FDE6D14F20AAC744357A /* Embed SPM Frameworks (OpenSSL) */,
F008C3222B5CD8109E252C6E /* Embed SPM Frameworks (QuickCrypto) */,
00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */,
E235C05ADACE081382539298 /* [CP] Copy Pods Resources */,
0F887C4BF3EDDB98EDD8A258 /* [CP-User] [CP-User] Embed OpenSSL Framework */,
218A62117F2E6018338554EF /* [QuickCrypto] Embed & Sign SPM Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -207,23 +205,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-QuickCryptoExample/Pods-QuickCryptoExample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
0F887C4BF3EDDB98EDD8A258 /* [CP-User] [CP-User] Embed OpenSSL Framework */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${BUILT_PRODUCTS_DIR}/OpenSSL.framework",
);
name = "[CP-User] [CP-User] Embed OpenSSL Framework";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#!/bin/bash\nset -e\n\nOPENSSL_FRAMEWORK=\"${BUILT_PRODUCTS_DIR}/OpenSSL.framework\"\n\nif [ -d \"$OPENSSL_FRAMEWORK\" ]; then\n echo \"[QuickCrypto] Copying OpenSSL.framework to app bundle\"\n mkdir -p \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}\"\n cp -Rf \"$OPENSSL_FRAMEWORK\" \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/\"\n \n # Code sign the framework (only if code signing is required and not a simulator build)\n if [ \"${CODE_SIGNING_REQUIRED}\" = \"YES\" ] && [ \"${EFFECTIVE_PLATFORM_NAME}\" != \"-iphonesimulator\" ]; then\n codesign --force --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" --preserve-metadata=identifier,entitlements --timestamp=none \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework\" || true\n fi\n echo \"[QuickCrypto] Successfully embedded OpenSSL.framework\"\nelse\n echo \"[QuickCrypto] Warning: OpenSSL.framework not found at $OPENSSL_FRAMEWORK\"\nfi\n";
};
8C63FDE6D14F20AAC744357A /* Embed SPM Frameworks (OpenSSL) */ = {
218A62117F2E6018338554EF /* [QuickCrypto] Embed & Sign SPM Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
Expand All @@ -232,14 +214,14 @@
);
inputPaths = (
);
name = "Embed SPM Frameworks (OpenSSL)";
name = "[QuickCrypto] Embed & Sign SPM Frameworks";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Embed OpenSSL.framework from SPM build into app bundle\n# SPM builds the framework to BUILT_PRODUCTS_DIR but doesn't embed it\nOPENSSL_FRAMEWORK=\"${BUILT_PRODUCTS_DIR}/OpenSSL.framework\"\n\nif [ -d \"$OPENSSL_FRAMEWORK\" ]; then\n echo \"Found OpenSSL.framework at $OPENSSL_FRAMEWORK\"\n mkdir -p \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}\"\n rsync -av --delete \"$OPENSSL_FRAMEWORK\" \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/\"\n\n # Code sign if required\n if [ -n \"${EXPANDED_CODE_SIGN_IDENTITY:-}\" ] && [ \"${CODE_SIGNING_REQUIRED:-}\" != \"NO\" ]; then\n /usr/bin/codesign --force --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" --preserve-metadata=identifier,entitlements \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework\"\n fi\n echo \"OpenSSL.framework embedded successfully\"\nelse\n echo \"warning: OpenSSL.framework not found at $OPENSSL_FRAMEWORK\"\nfi\n";
shellScript = "set -euo pipefail\n\n# Embed and sign SPM frameworks (OpenSSL) from QuickCrypto\n# This phase MUST run LAST, after all other framework embedding\n# See: https://github.com/margelo/react-native-quick-crypto/issues/857\n\nFRAMEWORKS_DIR=\"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}\"\nmkdir -p \"$FRAMEWORKS_DIR\"\n\nsign_framework() {\n local framework_path=\"$1\"\n local framework_name=$(basename \"$framework_path\")\n\n if [ ! -d \"$framework_path\" ]; then\n echo \"warning: $framework_name not found at $framework_path, skipping\"\n return 0\n fi\n\n echo \"[QuickCrypto] Processing $framework_name...\"\n\n # Copy to app bundle\n rsync -av --delete \"$framework_path\" \"$FRAMEWORKS_DIR/\"\n\n local dest_framework=\"$FRAMEWORKS_DIR/$framework_name\"\n\n # Sign if required (physical device builds only)\n if [ \"${CODE_SIGNING_REQUIRED:-NO}\" = \"YES\" ] && [ -n \"${EXPANDED_CODE_SIGN_IDENTITY:-}\" ]; then\n echo \"[QuickCrypto] Signing $framework_name with identity: ${EXPANDED_CODE_SIGN_IDENTITY}\"\n\n # Make framework writable (rsync preserves read-only from source)\n chmod -R u+w \"$dest_framework\"\n\n # Strip existing signature and re-sign with app's identity\n # This is required for pre-signed xcframeworks from SPM\n /usr/bin/codesign --force --deep --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \\\n --timestamp=none \\\n \"$dest_framework\"\n\n echo \"[QuickCrypto] Successfully signed $framework_name\"\n else\n echo \"[QuickCrypto] Code signing not required (simulator build)\"\n fi\n}\n\n# Sign OpenSSL.framework from SPM\nsign_framework \"${BUILT_PRODUCTS_DIR}/OpenSSL.framework\"\n\necho \"[QuickCrypto] SPM framework embedding complete\"\n";
};
C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
Expand Down Expand Up @@ -280,24 +262,6 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-QuickCryptoExample/Pods-QuickCryptoExample-resources.sh\"\n";
showEnvVarsInLog = 0;
};
F008C3222B5CD8109E252C6E /* Embed SPM Frameworks (QuickCrypto) */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Embed SPM Frameworks (QuickCrypto)";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Embed SPM frameworks (OpenSSL, Clibsodium) from QuickCrypto into app bundle\n# SPM builds frameworks to BUILT_PRODUCTS_DIR but doesn't embed them automatically\n\nmkdir -p \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}\"\n\n# Embed OpenSSL.framework (always required for ML-DSA)\nOPENSSL_FRAMEWORK=\"${BUILT_PRODUCTS_DIR}/OpenSSL.framework\"\nif [ -d \"$OPENSSL_FRAMEWORK\" ]; then\n echo \"Found OpenSSL.framework at $OPENSSL_FRAMEWORK\"\n rsync -av --delete \"$OPENSSL_FRAMEWORK\" \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/\"\n\n if [ -n \"${EXPANDED_CODE_SIGN_IDENTITY:-}\" ] && [ \"${CODE_SIGNING_REQUIRED:-}\" != \"NO\" ]; then\n /usr/bin/codesign --force --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" --preserve-metadata=identifier,entitlements \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework\"\n fi\n echo \"OpenSSL.framework embedded successfully\"\nelse\n echo \"warning: OpenSSL.framework not found at $OPENSSL_FRAMEWORK\"\nfi\n\n# Embed Clibsodium.framework (optional, when SODIUM_ENABLED=1)\nCLIBSODIUM_FRAMEWORK=\"${BUILT_PRODUCTS_DIR}/Clibsodium.framework\"\nif [ -d \"$CLIBSODIUM_FRAMEWORK\" ]; then\n echo \"Found Clibsodium.framework at $CLIBSODIUM_FRAMEWORK\"\n rsync -av --delete \"$CLIBSODIUM_FRAMEWORK\" \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/\"\n\n if [ -n \"${EXPANDED_CODE_SIGN_IDENTITY:-}\" ] && [ \"${CODE_SIGNING_REQUIRED:-}\" != \"NO\" ]; then\n /usr/bin/codesign --force --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" --preserve-metadata=identifier,entitlements \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Clibsodium.framework\"\n fi\n echo \"Clibsodium.framework embedded successfully\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
Expand All @@ -319,6 +283,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 64977D8TY3;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = QuickCryptoExample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
Expand Down Expand Up @@ -347,6 +312,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 64977D8TY3;
INFOPLIST_FILE = QuickCryptoExample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down
Loading