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
95 changes: 0 additions & 95 deletions docs/ios-setup.md

This file was deleted.

5 changes: 0 additions & 5 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
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 @@ -62,7 +59,5 @@ target 'QuickCryptoExample' do
end
end

# Fix QuickCrypto SPM framework signing for physical devices
quickcrypto_fix_spm_signing(installer)
end
end
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2773,7 +2773,7 @@ SPEC CHECKSUMS:
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
hermes-engine: 4f8246b1f6d79f625e0d99472d1f3a71da4d28ca
NitroModules: 1715fe0e22defd9e2cdd48fb5e0dbfd01af54bec
QuickCrypto: e1cebaf5b3be427ce291f8230795e9fd915517ab
QuickCrypto: a9f8d3f4334c080896ff38f1812fe3c676f4bbc4
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
RCTDeprecation: c4b9e2fd0ab200e3af72b013ed6113187c607077
RCTRequired: e97dd5dafc1db8094e63bc5031e0371f092ae92a
Expand Down Expand Up @@ -2845,6 +2845,6 @@ SPEC CHECKSUMS:
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 11c9686a21e2cd82a094a723649d9f4507200fb0

PODFILE CHECKSUM: c53d893905e8bc54582367d8191b1d2814070820
PODFILE CHECKSUM: 36c6d27eaa3ccd179672014ad06e56eb12f2a223

COCOAPODS: 1.15.2
19 changes: 0 additions & 19 deletions example/ios/QuickCryptoExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */,
E235C05ADACE081382539298 /* [CP] Copy Pods Resources */,
218A62117F2E6018338554EF /* [QuickCrypto] Embed & Sign SPM Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -205,24 +204,6 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-QuickCryptoExample/Pods-QuickCryptoExample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
218A62117F2E6018338554EF /* [QuickCrypto] Embed & Sign SPM Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "[QuickCrypto] Embed & Sign SPM Frameworks";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
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;
buildActionMask = 2147483647;
Expand Down
19 changes: 0 additions & 19 deletions example/src/benchmarks/scrypt/scrypt.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import rnqc from 'react-native-quick-crypto';
import * as noble from '@noble/hashes/scrypt';
// @ts-expect-error - crypto-browserify is not typed
import browserify from 'crypto-browserify';
import type { BenchFn } from '../../types/benchmarks';
import { Bench } from 'tinybench';

Expand Down Expand Up @@ -42,20 +40,6 @@ const scrypt_async: BenchFn = () => {
})
.add('@noble/hashes/scrypt', async () => {
await noble.scryptAsync('password', 'salt', { N, r, p, dkLen: keylen });
})
.add('browserify/scrypt', async () => {
await new Promise<void>((resolve, reject) => {
browserify.scrypt(
'password',
'salt',
keylen,
{ N, r, p },
(err: unknown) => {
if (err) reject(err);
else resolve();
},
);
});
});

bench.warmupTime = 100;
Expand Down Expand Up @@ -84,9 +68,6 @@ const scrypt_sync: BenchFn = () => {
})
.add('@noble/hashes/scrypt', () => {
noble.scrypt('password', 'salt', { N, r, p, dkLen: keylen });
})
.add('browserify/scrypt', () => {
browserify.scryptSync('password', 'salt', keylen, { N, r, p });
});

bench.warmupTime = 100;
Expand Down
2 changes: 2 additions & 0 deletions example/src/hooks/useBenchmarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import hash from '../benchmarks/hash/hash';
import hmac from '../benchmarks/hmac/hmac';
import pbkdf2 from '../benchmarks/pbkdf2/pbkdf2';
import random from '../benchmarks/random/randomBytes';
import scrypt from '../benchmarks/scrypt/scrypt';
import xsalsa20 from '../benchmarks/cipher/xsalsa20';

export const useBenchmarks = (): [
Expand Down Expand Up @@ -37,6 +38,7 @@ export const useBenchmarks = (): [
'polyfilled with RNQC, so a somewhat senseless benchmark',
}),
);
newSuites.push(new BenchmarkSuite('scrypt', scrypt));
setSuites(newSuites);
}, []);

Expand Down
1 change: 1 addition & 0 deletions example/src/hooks/useTestsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import '../tests/keys/sign_verify_streaming';
import '../tests/pbkdf2/pbkdf2_tests';
import '../tests/scrypt/scrypt_tests';
import '../tests/random/random_tests';
import '../tests/utils/timingSafeEqual_tests';
import '../tests/subtle/x25519_x448';
import '../tests/subtle/deriveBits';
import '../tests/subtle/derive_key';
Expand Down
76 changes: 76 additions & 0 deletions example/src/tests/utils/timingSafeEqual_tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Buffer } from '@craftzdog/react-native-buffer';
import { expect } from 'chai';
import { test } from '../util';

import crypto from 'react-native-quick-crypto';

const SUITE = 'timingSafeEqual';

test(SUITE, 'should return true for equal buffers', () => {
const a = Buffer.from('hello world');
const b = Buffer.from('hello world');
expect(crypto.timingSafeEqual(a, b)).to.equal(true);
});

test(SUITE, 'should return false for different buffers', () => {
const a = Buffer.from('hello world');
const b = Buffer.from('hello worlD');
expect(crypto.timingSafeEqual(a, b)).to.equal(false);
});

test(SUITE, 'should work with Uint8Array', () => {
const a = new Uint8Array([1, 2, 3, 4, 5]);
const b = new Uint8Array([1, 2, 3, 4, 5]);
expect(crypto.timingSafeEqual(a, b)).to.equal(true);
});

test(SUITE, 'should return false for different Uint8Array', () => {
const a = new Uint8Array([1, 2, 3, 4, 5]);
const b = new Uint8Array([1, 2, 3, 4, 6]);
expect(crypto.timingSafeEqual(a, b)).to.equal(false);
});

test(SUITE, 'should work with ArrayBuffer', () => {
const a = new Uint8Array([0xde, 0xad, 0xbe, 0xef]).buffer;
const b = new Uint8Array([0xde, 0xad, 0xbe, 0xef]).buffer;
expect(crypto.timingSafeEqual(a, b)).to.equal(true);
});

test(SUITE, 'should throw for different length buffers', () => {
const a = Buffer.from('hello');
const b = Buffer.from('hello world');
expect(() => crypto.timingSafeEqual(a, b)).to.throw(RangeError);
});

test(SUITE, 'should work with empty buffers', () => {
const a = Buffer.alloc(0);
const b = Buffer.alloc(0);
expect(crypto.timingSafeEqual(a, b)).to.equal(true);
});

test(SUITE, 'should work with single byte buffers', () => {
const a = Buffer.from([0xff]);
const b = Buffer.from([0xff]);
expect(crypto.timingSafeEqual(a, b)).to.equal(true);

const c = Buffer.from([0x00]);
expect(crypto.timingSafeEqual(a, c)).to.equal(false);
});

test(SUITE, 'should work for HMAC comparison use case', () => {
const hmac1 = crypto
.createHmac('sha256', 'secret')
.update('message')
.digest();
const hmac2 = crypto
.createHmac('sha256', 'secret')
.update('message')
.digest();
const hmac3 = crypto
.createHmac('sha256', 'secret')
.update('different')
.digest();

expect(crypto.timingSafeEqual(hmac1, hmac2)).to.equal(true);
expect(crypto.timingSafeEqual(hmac1, hmac3)).to.equal(false);
});
3 changes: 3 additions & 0 deletions packages/react-native-quick-crypto/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ README.md

ios/**

# Downloaded by CocoaPods prepare_command
OpenSSL.xcframework/

.cache/**
build/**
compile_commands.json
28 changes: 21 additions & 7 deletions packages/react-native-quick-crypto/QuickCrypto.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,22 @@ Pod::Spec.new do |s|
sodium_enabled = ENV['SODIUM_ENABLED'] == '1'
Pod::UI.puts("[QuickCrypto] 🧂 has libsodium #{sodium_enabled ? "enabled" : "disabled"}!")

# OpenSSL 3.6+ vendored xcframework (not yet on CocoaPods trunk)
openssl_version = "3.6.0000"
openssl_url = "https://github.com/krzyzanowskim/OpenSSL/releases/download/#{openssl_version}/OpenSSL.xcframework.zip"

if sodium_enabled
# Build libsodium from source for XSalsa20 cipher support
# CocoaPods packages are outdated (1.0.12) and SPM causes module conflicts
s.prepare_command = <<-CMD
set -e
# Download OpenSSL.xcframework
if [ ! -d "OpenSSL.xcframework" ]; then
curl -L -o OpenSSL.xcframework.zip #{openssl_url}
unzip -o OpenSSL.xcframework.zip
rm -f OpenSSL.xcframework.zip
fi
# Build libsodium
mkdir -p ios
curl -L -o ios/libsodium.tar.gz https://download.libsodium.org/libsodium/releases/libsodium-1.0.20-stable.tar.gz
tar -xzf ios/libsodium.tar.gz -C ios
Expand All @@ -38,11 +49,21 @@ Pod::Spec.new do |s|
CMD
else
s.prepare_command = <<-CMD
set -e
# Download OpenSSL.xcframework
if [ ! -d "OpenSSL.xcframework" ]; then
curl -L -o OpenSSL.xcframework.zip #{openssl_url}
unzip -o OpenSSL.xcframework.zip
rm -f OpenSSL.xcframework.zip
fi
# Clean up libsodium if previously built
rm -rf ios/libsodium-stable
rm -f ios/libsodium.tar.gz
CMD
end

s.vendored_frameworks = "OpenSSL.xcframework"

base_source_files = [
# implementation (Swift)
"ios/**/*.{swift}",
Expand Down Expand Up @@ -137,12 +158,5 @@ Pod::Spec.new do |s|
s.dependency "React-jsi"
s.dependency "React-callinvoker"

# OpenSSL 3.6+ via SPM for ML-DSA (post-quantum cryptography) support
spm_dependency(s,
url: 'https://github.com/krzyzanowskim/OpenSSL.git',
requirement: {kind: 'upToNextMajorVersion', minimumVersion: '3.6.0'},
products: ['OpenSSL']
)

install_modules_dependencies(s)
end
1 change: 1 addition & 0 deletions packages/react-native-quick-crypto/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ add_library(
../cpp/scrypt/HybridScrypt.cpp
../cpp/sign/HybridSignHandle.cpp
../cpp/sign/HybridVerifyHandle.cpp
../cpp/utils/HybridUtils.cpp
${BLAKE3_SOURCES}
../deps/fastpbkdf2/fastpbkdf2.c
../deps/ncrypto/src/ncrypto.cpp
Expand Down
19 changes: 19 additions & 0 deletions packages/react-native-quick-crypto/cpp/utils/HybridUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "HybridUtils.hpp"

#include <openssl/crypto.h>
#include <stdexcept>

namespace margelo::nitro::crypto {

bool HybridUtils::timingSafeEqual(const std::shared_ptr<ArrayBuffer>& a, const std::shared_ptr<ArrayBuffer>& b) {
size_t aLen = a->size();
size_t bLen = b->size();

if (aLen != bLen) {
throw std::runtime_error("Input buffers must have the same byte length");
}

return CRYPTO_memcmp(a->data(), b->data(), aLen) == 0;
}

} // namespace margelo::nitro::crypto
Loading
Loading