react-native-security-suite 0.9.21 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +233 -65
  2. package/android/build.gradle +11 -0
  3. package/android/gradle.properties +1 -1
  4. package/android/src/main/java/com/securitysuite/CryptoConfig.java +158 -0
  5. package/android/src/main/java/com/securitysuite/CryptoUtils.java +152 -0
  6. package/android/src/main/java/com/securitysuite/EcdhKeyStore.java +60 -0
  7. package/android/src/main/java/com/securitysuite/HeaderSanitizer.java +75 -0
  8. package/android/src/main/java/com/securitysuite/JWSGenerator.java +237 -32
  9. package/android/src/main/java/com/securitysuite/JwsFetchPayload.java +81 -0
  10. package/android/src/main/java/com/securitysuite/Obfuscation.java +57 -0
  11. package/android/src/main/java/com/securitysuite/SecureStorageNative.java +211 -0
  12. package/android/src/main/java/com/securitysuite/SecureView.java +2 -10
  13. package/android/src/main/java/com/securitysuite/SecureWindowHelper.java +30 -0
  14. package/android/src/main/java/com/securitysuite/SecuritySuiteModule.java +310 -102
  15. package/android/src/main/java/com/securitysuite/Sslpinning.java +219 -106
  16. package/android/src/main/java/com/securitysuite/security/AppIntegrityChecker.java +133 -0
  17. package/android/src/main/java/com/securitysuite/security/EmulatorDetector.java +145 -0
  18. package/android/src/main/java/com/securitysuite/security/RuntimeDetector.java +234 -0
  19. package/android/src/test/java/com/securitysuite/JWSGeneratorTest.java +153 -0
  20. package/android/src/test/java/com/securitysuite/SecureStorageNativeTest.java +37 -0
  21. package/ios/CryptoConfig.swift +124 -0
  22. package/ios/JWSGenerator.swift +288 -0
  23. package/ios/JWSGeneratorTests.swift +168 -0
  24. package/ios/KeychainHelper.swift +104 -0
  25. package/ios/Obfuscation.swift +42 -0
  26. package/ios/SecureStorageNative.swift +84 -0
  27. package/ios/Security/AppIntegrityChecker.swift +85 -0
  28. package/ios/Security/EmulatorDetector.swift +45 -0
  29. package/ios/Security/RuntimeDetector.swift +107 -0
  30. package/ios/SecuritySuite.mm +28 -4
  31. package/ios/SecuritySuite.swift +407 -131
  32. package/ios/SslPinning.swift +242 -263
  33. package/lib/commonjs/clipboard/index.js +3 -0
  34. package/lib/commonjs/clipboard/index.js.map +1 -0
  35. package/lib/commonjs/crypto/index.js +39 -0
  36. package/lib/commonjs/crypto/index.js.map +1 -0
  37. package/lib/commonjs/device/index.js +40 -0
  38. package/lib/commonjs/device/index.js.map +1 -0
  39. package/lib/commonjs/errors.js +62 -0
  40. package/lib/commonjs/errors.js.map +1 -0
  41. package/lib/commonjs/index.js +220 -151
  42. package/lib/commonjs/index.js.map +1 -1
  43. package/lib/commonjs/integrity/index.js +40 -0
  44. package/lib/commonjs/integrity/index.js.map +1 -0
  45. package/lib/commonjs/jws.js +141 -0
  46. package/lib/commonjs/jws.js.map +1 -0
  47. package/lib/commonjs/legacy/cryptoOptions.js +20 -0
  48. package/lib/commonjs/legacy/cryptoOptions.js.map +1 -0
  49. package/lib/commonjs/native/bridge.js +23 -0
  50. package/lib/commonjs/native/bridge.js.map +1 -0
  51. package/lib/commonjs/network/index.js +3 -0
  52. package/lib/commonjs/network/index.js.map +1 -0
  53. package/lib/commonjs/risk/score.js +36 -0
  54. package/lib/commonjs/risk/score.js.map +1 -0
  55. package/lib/commonjs/runtime/index.js +31 -0
  56. package/lib/commonjs/runtime/index.js.map +1 -0
  57. package/lib/commonjs/screen/index.js +13 -0
  58. package/lib/commonjs/screen/index.js.map +1 -0
  59. package/lib/commonjs/securitySuite/index.js +42 -0
  60. package/lib/commonjs/securitySuite/index.js.map +1 -0
  61. package/lib/commonjs/storage/index.js +3 -0
  62. package/lib/commonjs/storage/index.js.map +1 -0
  63. package/lib/commonjs/types/detection.js +2 -0
  64. package/lib/commonjs/types/detection.js.map +1 -0
  65. package/lib/module/clipboard/index.js +3 -0
  66. package/lib/module/clipboard/index.js.map +1 -0
  67. package/lib/module/crypto/index.js +35 -0
  68. package/lib/module/crypto/index.js.map +1 -0
  69. package/lib/module/device/index.js +36 -0
  70. package/lib/module/device/index.js.map +1 -0
  71. package/lib/module/errors.js +55 -0
  72. package/lib/module/errors.js.map +1 -0
  73. package/lib/module/index.js +147 -148
  74. package/lib/module/index.js.map +1 -1
  75. package/lib/module/integrity/index.js +36 -0
  76. package/lib/module/integrity/index.js.map +1 -0
  77. package/lib/module/jws.js +127 -0
  78. package/lib/module/jws.js.map +1 -0
  79. package/lib/module/legacy/cryptoOptions.js +16 -0
  80. package/lib/module/legacy/cryptoOptions.js.map +1 -0
  81. package/lib/module/native/bridge.js +19 -0
  82. package/lib/module/native/bridge.js.map +1 -0
  83. package/lib/module/network/index.js +3 -0
  84. package/lib/module/network/index.js.map +1 -0
  85. package/lib/module/risk/score.js +32 -0
  86. package/lib/module/risk/score.js.map +1 -0
  87. package/lib/module/runtime/index.js +27 -0
  88. package/lib/module/runtime/index.js.map +1 -0
  89. package/lib/module/screen/index.js +5 -0
  90. package/lib/module/screen/index.js.map +1 -0
  91. package/lib/module/securitySuite/index.js +38 -0
  92. package/lib/module/securitySuite/index.js.map +1 -0
  93. package/lib/module/storage/index.js +3 -0
  94. package/lib/module/storage/index.js.map +1 -0
  95. package/lib/module/types/detection.js +2 -0
  96. package/lib/module/types/detection.js.map +1 -0
  97. package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts +215 -0
  98. package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts.map +1 -0
  99. package/lib/typescript/commonjs/src/SecureView.d.ts +1 -1
  100. package/lib/typescript/commonjs/src/SecureView.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/src/clipboard/index.d.ts +2 -0
  102. package/lib/typescript/commonjs/src/clipboard/index.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/src/crypto/index.d.ts +15 -0
  104. package/lib/typescript/commonjs/src/crypto/index.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/src/device/index.d.ts +11 -0
  106. package/lib/typescript/commonjs/src/device/index.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/src/errors.d.ts +17 -0
  108. package/lib/typescript/commonjs/src/errors.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/src/helpers.d.ts.map +1 -1
  110. package/lib/typescript/commonjs/src/index.d.ts +77 -24
  111. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/src/integrity/index.d.ts +6 -0
  113. package/lib/typescript/commonjs/src/integrity/index.d.ts.map +1 -0
  114. package/lib/typescript/commonjs/src/jws.d.ts +44 -0
  115. package/lib/typescript/commonjs/src/jws.d.ts.map +1 -0
  116. package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts +35 -0
  117. package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/src/native/bridge.d.ts +12 -0
  119. package/lib/typescript/commonjs/src/native/bridge.d.ts.map +1 -0
  120. package/lib/typescript/commonjs/src/network/index.d.ts +2 -0
  121. package/lib/typescript/commonjs/src/network/index.d.ts.map +1 -0
  122. package/lib/typescript/commonjs/src/risk/score.d.ts +12 -0
  123. package/lib/typescript/commonjs/src/risk/score.d.ts.map +1 -0
  124. package/lib/typescript/commonjs/src/runtime/index.d.ts +6 -0
  125. package/lib/typescript/commonjs/src/runtime/index.d.ts.map +1 -0
  126. package/lib/typescript/commonjs/src/screen/index.d.ts +3 -0
  127. package/lib/typescript/commonjs/src/screen/index.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/src/securitySuite/index.d.ts +6 -0
  129. package/lib/typescript/commonjs/src/securitySuite/index.d.ts.map +1 -0
  130. package/lib/typescript/commonjs/src/storage/index.d.ts +2 -0
  131. package/lib/typescript/commonjs/src/storage/index.d.ts.map +1 -0
  132. package/lib/typescript/commonjs/src/types/detection.d.ts +41 -0
  133. package/lib/typescript/commonjs/src/types/detection.d.ts.map +1 -0
  134. package/lib/typescript/module/docs/api-v1-proposal.d.ts +215 -0
  135. package/lib/typescript/module/docs/api-v1-proposal.d.ts.map +1 -0
  136. package/lib/typescript/module/src/SecureView.d.ts +1 -1
  137. package/lib/typescript/module/src/SecureView.d.ts.map +1 -1
  138. package/lib/typescript/module/src/clipboard/index.d.ts +2 -0
  139. package/lib/typescript/module/src/clipboard/index.d.ts.map +1 -0
  140. package/lib/typescript/module/src/crypto/index.d.ts +15 -0
  141. package/lib/typescript/module/src/crypto/index.d.ts.map +1 -0
  142. package/lib/typescript/module/src/device/index.d.ts +11 -0
  143. package/lib/typescript/module/src/device/index.d.ts.map +1 -0
  144. package/lib/typescript/module/src/errors.d.ts +17 -0
  145. package/lib/typescript/module/src/errors.d.ts.map +1 -0
  146. package/lib/typescript/module/src/helpers.d.ts.map +1 -1
  147. package/lib/typescript/module/src/index.d.ts +77 -24
  148. package/lib/typescript/module/src/index.d.ts.map +1 -1
  149. package/lib/typescript/module/src/integrity/index.d.ts +6 -0
  150. package/lib/typescript/module/src/integrity/index.d.ts.map +1 -0
  151. package/lib/typescript/module/src/jws.d.ts +44 -0
  152. package/lib/typescript/module/src/jws.d.ts.map +1 -0
  153. package/lib/typescript/module/src/legacy/cryptoOptions.d.ts +35 -0
  154. package/lib/typescript/module/src/legacy/cryptoOptions.d.ts.map +1 -0
  155. package/lib/typescript/module/src/native/bridge.d.ts +12 -0
  156. package/lib/typescript/module/src/native/bridge.d.ts.map +1 -0
  157. package/lib/typescript/module/src/network/index.d.ts +2 -0
  158. package/lib/typescript/module/src/network/index.d.ts.map +1 -0
  159. package/lib/typescript/module/src/risk/score.d.ts +12 -0
  160. package/lib/typescript/module/src/risk/score.d.ts.map +1 -0
  161. package/lib/typescript/module/src/runtime/index.d.ts +6 -0
  162. package/lib/typescript/module/src/runtime/index.d.ts.map +1 -0
  163. package/lib/typescript/module/src/screen/index.d.ts +3 -0
  164. package/lib/typescript/module/src/screen/index.d.ts.map +1 -0
  165. package/lib/typescript/module/src/securitySuite/index.d.ts +6 -0
  166. package/lib/typescript/module/src/securitySuite/index.d.ts.map +1 -0
  167. package/lib/typescript/module/src/storage/index.d.ts +2 -0
  168. package/lib/typescript/module/src/storage/index.d.ts.map +1 -0
  169. package/lib/typescript/module/src/types/detection.d.ts +41 -0
  170. package/lib/typescript/module/src/types/detection.d.ts.map +1 -0
  171. package/package.json +2 -4
  172. package/src/clipboard/index.ts +1 -0
  173. package/src/crypto/index.ts +49 -0
  174. package/src/device/index.ts +47 -0
  175. package/src/errors.ts +84 -0
  176. package/src/index.tsx +293 -195
  177. package/src/integrity/index.ts +46 -0
  178. package/src/jws.ts +213 -0
  179. package/src/legacy/cryptoOptions.ts +49 -0
  180. package/src/native/bridge.ts +37 -0
  181. package/src/network/index.ts +1 -0
  182. package/src/risk/score.ts +49 -0
  183. package/src/runtime/index.ts +43 -0
  184. package/src/screen/index.ts +2 -0
  185. package/src/securitySuite/index.ts +45 -0
  186. package/src/storage/index.ts +1 -0
  187. package/src/types/detection.ts +46 -0
  188. package/android/src/main/java/com/securitysuite/StorageEncryption.java +0 -52
  189. package/ios/StorageEncryption.swift +0 -89
@@ -0,0 +1,145 @@
1
+ package com.securitysuite.security;
2
+
3
+ import android.content.Context;
4
+ import android.hardware.Sensor;
5
+ import android.hardware.SensorManager;
6
+ import android.os.Build;
7
+
8
+ import com.facebook.react.bridge.Arguments;
9
+ import com.facebook.react.bridge.WritableArray;
10
+ import com.facebook.react.bridge.WritableMap;
11
+ import com.scottyab.rootbeer.RootBeer;
12
+
13
+ import java.util.ArrayList;
14
+ import java.util.List;
15
+ import java.util.Locale;
16
+
17
+ /**
18
+ * Emulator and device environment detection (Android).
19
+ */
20
+ public final class EmulatorDetector {
21
+ private EmulatorDetector() {}
22
+
23
+ public static WritableMap detect(Context context) {
24
+ WritableMap result = Arguments.createMap();
25
+ List<String> indicators = new ArrayList<>();
26
+
27
+ collectBuildIndicators(indicators);
28
+ collectQemuIndicators(indicators);
29
+ collectSensorIndicators(context, indicators);
30
+
31
+ RootBeer rootBeer = new RootBeer(context);
32
+ if (rootBeer.isEmulator()) {
33
+ indicators.add("RootBeer.isEmulator");
34
+ }
35
+
36
+ result.putBoolean("isEmulator", !indicators.isEmpty());
37
+ result.putBoolean("isSimulator", false);
38
+ result.putArray("indicators", toStringArray(indicators));
39
+ return result;
40
+ }
41
+
42
+ private static void collectBuildIndicators(List<String> indicators) {
43
+ String fingerprint = safeLower(Build.FINGERPRINT);
44
+ String model = safeLower(Build.MODEL);
45
+ String manufacturer = safeLower(Build.MANUFACTURER);
46
+ String hardware = safeLower(Build.HARDWARE);
47
+ String product = safeLower(Build.PRODUCT);
48
+ String brand = safeLower(Build.BRAND);
49
+ String device = safeLower(Build.DEVICE);
50
+
51
+ if (containsAny(fingerprint, "generic", "unknown", "test-keys", "emulator")) {
52
+ indicators.add("Build.FINGERPRINT");
53
+ }
54
+ if (containsAny(model, "google_sdk", "emulator", "android sdk built for x86", "sdk_gphone")) {
55
+ indicators.add("Build.MODEL");
56
+ }
57
+ if (containsAny(manufacturer, "genymotion", "unknown")) {
58
+ indicators.add("Build.MANUFACTURER");
59
+ }
60
+ if (containsAny(hardware, "goldfish", "ranchu", "qemu")) {
61
+ indicators.add("Build.HARDWARE");
62
+ }
63
+ if (containsAny(product, "sdk", "google_sdk", "sdk_gphone", "vbox")) {
64
+ indicators.add("Build.PRODUCT");
65
+ }
66
+ if (containsAny(brand, "generic")) {
67
+ indicators.add("Build.BRAND");
68
+ }
69
+ if (containsAny(device, "generic", "emu64a", "goldfish")) {
70
+ indicators.add("Build.DEVICE");
71
+ }
72
+ }
73
+
74
+ private static void collectQemuIndicators(List<String> indicators) {
75
+ String qemu = getSystemProperty("ro.kernel.qemu");
76
+ if ("1".equals(qemu)) {
77
+ indicators.add("ro.kernel.qemu");
78
+ }
79
+
80
+ String[] props = {
81
+ "ro.hardware",
82
+ "ro.product.device",
83
+ "ro.product.model",
84
+ "ro.product.name"
85
+ };
86
+ for (String prop : props) {
87
+ String value = safeLower(getSystemProperty(prop));
88
+ if (containsAny(value, "goldfish", "ranchu", "qemu", "sdk_gphone", "emulator")) {
89
+ indicators.add(prop);
90
+ }
91
+ }
92
+ }
93
+
94
+ private static void collectSensorIndicators(Context context, List<String> indicators) {
95
+ SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
96
+ if (sensorManager == null) {
97
+ indicators.add("SensorManager unavailable");
98
+ return;
99
+ }
100
+
101
+ Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
102
+ Sensor gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
103
+ if (accelerometer == null) {
104
+ indicators.add("Missing accelerometer");
105
+ }
106
+ if (gyroscope == null) {
107
+ indicators.add("Missing gyroscope");
108
+ }
109
+ }
110
+
111
+ private static String getSystemProperty(String key) {
112
+ try {
113
+ Class<?> systemProperties = Class.forName("android.os.SystemProperties");
114
+ return (String) systemProperties
115
+ .getMethod("get", String.class)
116
+ .invoke(null, key);
117
+ } catch (Exception ignored) {
118
+ return null;
119
+ }
120
+ }
121
+
122
+ private static boolean containsAny(String value, String... needles) {
123
+ if (value == null || value.isEmpty()) {
124
+ return false;
125
+ }
126
+ for (String needle : needles) {
127
+ if (value.contains(needle)) {
128
+ return true;
129
+ }
130
+ }
131
+ return false;
132
+ }
133
+
134
+ private static String safeLower(String value) {
135
+ return value == null ? "" : value.toLowerCase(Locale.US);
136
+ }
137
+
138
+ private static WritableArray toStringArray(List<String> values) {
139
+ WritableArray array = Arguments.createArray();
140
+ for (String value : values) {
141
+ array.pushString(value);
142
+ }
143
+ return array;
144
+ }
145
+ }
@@ -0,0 +1,234 @@
1
+ package com.securitysuite.security;
2
+
3
+ import java.io.BufferedReader;
4
+ import java.io.File;
5
+ import java.io.FileReader;
6
+ import java.io.IOException;
7
+ import java.net.InetSocketAddress;
8
+ import java.net.Socket;
9
+ import java.util.ArrayList;
10
+ import java.util.Arrays;
11
+ import java.util.HashSet;
12
+ import java.util.List;
13
+ import java.util.Locale;
14
+ import java.util.Set;
15
+
16
+ import android.os.Debug;
17
+
18
+ import com.facebook.react.bridge.Arguments;
19
+ import com.facebook.react.bridge.WritableArray;
20
+ import com.facebook.react.bridge.WritableMap;
21
+
22
+ /**
23
+ * Native runtime instrumentation and debugger detection (Android).
24
+ */
25
+ public final class RuntimeDetector {
26
+ private static final int[] FRIDA_PORTS = {27042, 27043};
27
+ private static final Set<String> SUSPICIOUS_MAP_KEYWORDS = new HashSet<>(Arrays.asList(
28
+ "frida", "frida-agent", "frida-gadget", "linjector", "xposed", "lsposed",
29
+ "substrate", "magisk", "zygisk", "libhooker"
30
+ ));
31
+ private static final Set<String> SUSPICIOUS_THREAD_NAMES = new HashSet<>(Arrays.asList(
32
+ "gmain", "gdbus", "pool-frida", "frida-agent", "frida-server"
33
+ ));
34
+
35
+ private RuntimeDetector() {}
36
+
37
+ public static WritableMap detect() {
38
+ WritableMap result = Arguments.createMap();
39
+ List<String> suspiciousLibraries = scanProcMaps();
40
+ List<Integer> suspiciousPorts = scanFridaPorts();
41
+ boolean fridaDetected = !suspiciousLibraries.isEmpty()
42
+ || !suspiciousPorts.isEmpty()
43
+ || hasSuspiciousThreads();
44
+ boolean debuggerAttached = Debug.isDebuggerConnected() || Debug.waitingForDebugger() || hasTracerPid();
45
+ boolean xposedDetected = detectXposed();
46
+ boolean magiskDetected = detectMagisk();
47
+
48
+ result.putBoolean("debuggerAttached", debuggerAttached);
49
+ result.putBoolean("fridaDetected", fridaDetected);
50
+ result.putBoolean("xposedDetected", xposedDetected);
51
+ result.putBoolean("magiskDetected", magiskDetected);
52
+ result.putArray("suspiciousLibraries", toStringArray(suspiciousLibraries));
53
+ result.putArray("suspiciousPorts", toIntArray(suspiciousPorts));
54
+ return result;
55
+ }
56
+
57
+ private static List<String> scanProcMaps() {
58
+ List<String> matches = new ArrayList<>();
59
+ File maps = new File("/proc/self/maps");
60
+ if (!maps.canRead()) {
61
+ return matches;
62
+ }
63
+
64
+ try (BufferedReader reader = new BufferedReader(new FileReader(maps))) {
65
+ String line;
66
+ while ((line = reader.readLine()) != null) {
67
+ String lower = line.toLowerCase(Locale.US);
68
+ for (String keyword : SUSPICIOUS_MAP_KEYWORDS) {
69
+ if (lower.contains(keyword)) {
70
+ matches.add(keyword);
71
+ break;
72
+ }
73
+ }
74
+ }
75
+ } catch (IOException ignored) {
76
+ // Best-effort detection.
77
+ }
78
+ return matches;
79
+ }
80
+
81
+ private static List<Integer> scanFridaPorts() {
82
+ List<Integer> openPorts = new ArrayList<>();
83
+ for (int port : FRIDA_PORTS) {
84
+ if (isLocalPortOpen(port)) {
85
+ openPorts.add(port);
86
+ }
87
+ }
88
+ return openPorts;
89
+ }
90
+
91
+ private static boolean isLocalPortOpen(int port) {
92
+ Socket socket = new Socket();
93
+ try {
94
+ socket.connect(new InetSocketAddress("127.0.0.1", port), 300);
95
+ return true;
96
+ } catch (IOException ignored) {
97
+ return false;
98
+ } finally {
99
+ try {
100
+ socket.close();
101
+ } catch (IOException ignored) {
102
+ // Ignore close failures.
103
+ }
104
+ }
105
+ }
106
+
107
+ private static boolean hasSuspiciousThreads() {
108
+ File taskDir = new File("/proc/self/task");
109
+ File[] tasks = taskDir.listFiles();
110
+ if (tasks == null) {
111
+ return false;
112
+ }
113
+
114
+ for (File task : tasks) {
115
+ File comm = new File(task, "comm");
116
+ if (!comm.canRead()) {
117
+ continue;
118
+ }
119
+ try (BufferedReader reader = new BufferedReader(new FileReader(comm))) {
120
+ String name = reader.readLine();
121
+ if (name == null) {
122
+ continue;
123
+ }
124
+ String normalized = name.trim().toLowerCase(Locale.US);
125
+ if (SUSPICIOUS_THREAD_NAMES.contains(normalized)) {
126
+ return true;
127
+ }
128
+ } catch (IOException ignored) {
129
+ // Continue scanning other tasks.
130
+ }
131
+ }
132
+ return false;
133
+ }
134
+
135
+ private static boolean hasTracerPid() {
136
+ File status = new File("/proc/self/status");
137
+ if (!status.canRead()) {
138
+ return false;
139
+ }
140
+
141
+ try (BufferedReader reader = new BufferedReader(new FileReader(status))) {
142
+ String line;
143
+ while ((line = reader.readLine()) != null) {
144
+ if (line.startsWith("TracerPid:")) {
145
+ String pid = line.substring("TracerPid:".length()).trim();
146
+ return !"0".equals(pid);
147
+ }
148
+ }
149
+ } catch (IOException ignored) {
150
+ // Best-effort detection.
151
+ }
152
+ return false;
153
+ }
154
+
155
+ private static boolean detectXposed() {
156
+ String[] paths = {
157
+ "/system/framework/XposedBridge.jar",
158
+ "/system/lib/libxposed_art.so",
159
+ "/system/lib64/libxposed_art.so",
160
+ "/data/adb/lspd",
161
+ "/data/adb/modules/zygisk_lsposed"
162
+ };
163
+ for (String path : paths) {
164
+ if (new File(path).exists()) {
165
+ return true;
166
+ }
167
+ }
168
+
169
+ try {
170
+ throw new Exception("xposed_probe");
171
+ } catch (Exception exception) {
172
+ for (StackTraceElement element : exception.getStackTrace()) {
173
+ String className = element.getClassName().toLowerCase(Locale.US);
174
+ if (className.contains("de.robv.android.xposed")
175
+ || className.contains("org.lsposed")
176
+ || className.contains("io.github.lsposed")) {
177
+ return true;
178
+ }
179
+ }
180
+ }
181
+ return false;
182
+ }
183
+
184
+ private static boolean detectMagisk() {
185
+ String[] paths = {
186
+ "/sbin/.magisk",
187
+ "/sbin/.core",
188
+ "/data/adb/magisk",
189
+ "/data/adb/modules",
190
+ "/cache/.disable_magisk"
191
+ };
192
+ for (String path : paths) {
193
+ if (new File(path).exists()) {
194
+ return true;
195
+ }
196
+ }
197
+
198
+ String[] props = {"ro.boot.vbmeta.device_state", "ro.boot.verifiedbootstate"};
199
+ for (String prop : props) {
200
+ String value = getSystemProperty(prop);
201
+ if (value != null && value.toLowerCase(Locale.US).contains("orange")) {
202
+ return true;
203
+ }
204
+ }
205
+ return false;
206
+ }
207
+
208
+ private static String getSystemProperty(String key) {
209
+ try {
210
+ Class<?> systemProperties = Class.forName("android.os.SystemProperties");
211
+ return (String) systemProperties
212
+ .getMethod("get", String.class)
213
+ .invoke(null, key);
214
+ } catch (Exception ignored) {
215
+ return null;
216
+ }
217
+ }
218
+
219
+ private static WritableArray toStringArray(List<String> values) {
220
+ WritableArray array = Arguments.createArray();
221
+ for (String value : values) {
222
+ array.pushString(value);
223
+ }
224
+ return array;
225
+ }
226
+
227
+ private static WritableArray toIntArray(List<Integer> values) {
228
+ WritableArray array = Arguments.createArray();
229
+ for (Integer value : values) {
230
+ array.pushInt(value);
231
+ }
232
+ return array;
233
+ }
234
+ }
@@ -0,0 +1,153 @@
1
+ package com.securitysuite;
2
+
3
+ import com.facebook.react.bridge.Arguments;
4
+ import com.facebook.react.bridge.ReadableMap;
5
+ import com.facebook.react.bridge.WritableMap;
6
+
7
+ import org.junit.Test;
8
+
9
+ import javax.crypto.Mac;
10
+ import javax.crypto.spec.SecretKeySpec;
11
+
12
+ import java.nio.charset.StandardCharsets;
13
+ import java.util.Base64;
14
+
15
+ import static org.junit.Assert.assertEquals;
16
+ import static org.junit.Assert.assertFalse;
17
+ import static org.junit.Assert.assertTrue;
18
+
19
+ public class JWSGeneratorTest {
20
+ private static final String SECRET = "secret";
21
+
22
+ @Test
23
+ public void omittedPayloadProducesThreeSegmentsWithEmptyMiddle() throws Exception {
24
+ assertEmptyPayloadCase(null);
25
+ }
26
+
27
+ @Test
28
+ public void nullPayloadProducesThreeSegmentsWithEmptyMiddle() throws Exception {
29
+ assertEmptyPayloadCase("");
30
+ }
31
+
32
+ @Test
33
+ public void emptyStringPayloadProducesThreeSegmentsWithEmptyMiddle() throws Exception {
34
+ assertEmptyPayloadCase("");
35
+ }
36
+
37
+ @Test
38
+ public void stringNullPayloadIsNotEmpty() throws Exception {
39
+ JWSGenerator generator = new JWSGenerator();
40
+ String jws = generator.generate("null", SECRET, "HS256", headersWithKid(), false);
41
+ String[] segments = jws.split("\\.", -1);
42
+
43
+ assertEquals(3, segments.length);
44
+ assertEquals(base64Url("null"), segments[1]);
45
+ assertFalse(segments[1].isEmpty());
46
+ }
47
+
48
+ @Test
49
+ public void stringUndefinedPayloadIsNotEmpty() throws Exception {
50
+ JWSGenerator generator = new JWSGenerator();
51
+ String jws = generator.generate("undefined", SECRET, "HS256", headersWithKid(), false);
52
+ String[] segments = jws.split("\\.", -1);
53
+
54
+ assertEquals(3, segments.length);
55
+ assertEquals(base64Url("undefined"), segments[1]);
56
+ }
57
+
58
+ @Test
59
+ public void objectPayloadIsBase64UrlEncodedJson() throws Exception {
60
+ JWSGenerator generator = new JWSGenerator();
61
+ String payload = "{\"amount\":1000}";
62
+ String jws = generator.generate(payload, SECRET, "HS256", headersWithKid(), false);
63
+ String[] segments = jws.split("\\.", -1);
64
+
65
+ assertEquals(3, segments.length);
66
+ assertEquals(base64Url(payload), segments[1]);
67
+ }
68
+
69
+ @Test(expected = IllegalArgumentException.class)
70
+ public void algorithmMismatchThrows() throws Exception {
71
+ WritableMap headers = Arguments.createMap();
72
+ headers.putString("alg", "HS256");
73
+ headers.putString("kid", "test-key");
74
+
75
+ JWSGenerator generator = new JWSGenerator();
76
+ generator.generate("", SECRET, "HS512", headers, false);
77
+ }
78
+
79
+ @Test
80
+ public void algorithmFromHeaderIsUsed() throws Exception {
81
+ WritableMap headers = Arguments.createMap();
82
+ headers.putString("alg", "HS384");
83
+ headers.putString("kid", "test-key");
84
+
85
+ JWSGenerator generator = new JWSGenerator();
86
+ String jws = generator.generate("", SECRET, null, headers, false);
87
+ String protectedHeaderJson = new String(
88
+ Base64.getUrlDecoder().decode(jws.split("\\.", -1)[0]),
89
+ StandardCharsets.UTF_8
90
+ );
91
+
92
+ assertTrue(protectedHeaderJson.contains("\"alg\":\"HS384\""));
93
+ }
94
+
95
+ @Test
96
+ public void defaultAlgorithmIsHs256() throws Exception {
97
+ JWSGenerator generator = new JWSGenerator();
98
+ String jws = generator.generate("", SECRET, null, headersWithKid(), false);
99
+ String protectedHeaderJson = new String(
100
+ Base64.getUrlDecoder().decode(jws.split("\\.", -1)[0]),
101
+ StandardCharsets.UTF_8
102
+ );
103
+
104
+ assertTrue(protectedHeaderJson.contains("\"alg\":\"HS256\""));
105
+ }
106
+
107
+ @Test
108
+ public void detachedOutputUsesDoubleDot() throws Exception {
109
+ JWSGenerator generator = new JWSGenerator();
110
+ String jws = generator.generate("payload", SECRET, "HS256", headersWithKid(), true);
111
+
112
+ assertTrue(jws.contains(".."));
113
+ assertEquals(2, jws.split("\\.\\.", -1).length);
114
+ assertTrue(generator.verify(jws, "payload", SECRET, "HS256", true));
115
+ }
116
+
117
+ private void assertEmptyPayloadCase(String payload) throws Exception {
118
+ JWSGenerator generator = new JWSGenerator();
119
+ String jws = generator.generate(payload, SECRET, "HS256", headersWithKid(), false);
120
+ String[] segments = jws.split("\\.", -1);
121
+
122
+ assertEquals(3, segments.length);
123
+ assertEquals("", segments[1]);
124
+ assertTrue(jws.contains(".."));
125
+ assertTrue(generator.verify(jws, payload == null ? "" : payload, SECRET, "HS256", false));
126
+ }
127
+
128
+ private ReadableMap headersWithKid() {
129
+ WritableMap headers = Arguments.createMap();
130
+ headers.putString("kid", "test-key");
131
+ return headers;
132
+ }
133
+
134
+ private String base64Url(String value) {
135
+ return Base64.getUrlEncoder().withoutPadding()
136
+ .encodeToString(value.getBytes(StandardCharsets.UTF_8));
137
+ }
138
+
139
+ private String signReference(String protectedHeader, String payloadString, String algorithm)
140
+ throws Exception {
141
+ String encodedPayload = payloadString == null || payloadString.isEmpty()
142
+ ? ""
143
+ : base64Url(payloadString);
144
+ String signingInput = protectedHeader + "." + encodedPayload;
145
+ String macAlgorithm = CryptoUtils.hmacAlgorithmForJws(algorithm);
146
+ Mac mac = Mac.getInstance(macAlgorithm);
147
+ mac.init(new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), macAlgorithm));
148
+ String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(mac.doFinal(
149
+ signingInput.getBytes(StandardCharsets.UTF_8)
150
+ ));
151
+ return protectedHeader + "." + encodedPayload + "." + signature;
152
+ }
153
+ }
@@ -0,0 +1,37 @@
1
+ package com.securitysuite;
2
+
3
+ import org.junit.Test;
4
+
5
+ import static org.junit.Assert.assertEquals;
6
+ import static org.junit.Assert.assertTrue;
7
+
8
+ public class SecureStorageNativeTest {
9
+ @Test
10
+ public void secureStorageException_includesOperationAndFailureMessage() {
11
+ Exception wrapped =
12
+ invokeSecureStorageException(
13
+ "setItem", new IllegalArgumentException("Storage key is required"));
14
+
15
+ assertTrue(wrapped.getMessage().startsWith("Secure storage operation failed"));
16
+ assertTrue(wrapped.getMessage().contains("(setItem)"));
17
+ assertTrue(wrapped.getMessage().contains("Storage key is required"));
18
+ }
19
+
20
+ @Test
21
+ public void secureStorageException_usesExceptionTypeWhenMessageMissing() {
22
+ Exception wrapped = invokeSecureStorageException("getItem", new RuntimeException());
23
+
24
+ assertEquals(
25
+ "Secure storage operation failed (getItem): RuntimeException",
26
+ wrapped.getMessage());
27
+ }
28
+
29
+ private static Exception invokeSecureStorageException(String operation, Exception cause)
30
+ throws Exception {
31
+ var method =
32
+ SecureStorageNative.class.getDeclaredMethod(
33
+ "secureStorageException", String.class, Exception.class);
34
+ method.setAccessible(true);
35
+ return (Exception) method.invoke(null, operation, cause);
36
+ }
37
+ }
@@ -0,0 +1,124 @@
1
+ import Foundation
2
+
3
+ @available(iOS 13.0, *)
4
+ struct CryptoConfig {
5
+ static let defaultKeyAgreement = "ECDH"
6
+ static let defaultKeyFactory = "EC"
7
+ static let defaultEncryptionKeyAlgorithm = "AES"
8
+ static let defaultHmacKeyAlgorithm = "HmacSHA256"
9
+ static let defaultCipherTransformation = "AES/GCM/NoPadding"
10
+ static let defaultGcmTagLength = 128
11
+ static let defaultGcmIvLength = 12
12
+
13
+ let keyAgreementAlgorithm: String
14
+ let keyFactoryAlgorithm: String
15
+ let encryptionKeyAlgorithm: String
16
+ let hmacKeyAlgorithm: String
17
+ let cipherTransformation: String
18
+ let gcmTagLength: Int
19
+ let gcmIvLength: Int
20
+
21
+ static func defaults() -> CryptoConfig {
22
+ CryptoConfig(
23
+ keyAgreementAlgorithm: defaultKeyAgreement,
24
+ keyFactoryAlgorithm: defaultKeyFactory,
25
+ encryptionKeyAlgorithm: defaultEncryptionKeyAlgorithm,
26
+ hmacKeyAlgorithm: defaultHmacKeyAlgorithm,
27
+ cipherTransformation: defaultCipherTransformation,
28
+ gcmTagLength: defaultGcmTagLength,
29
+ gcmIvLength: defaultGcmIvLength
30
+ )
31
+ }
32
+
33
+ static func from(dictionary: NSDictionary?) throws -> CryptoConfig {
34
+ guard let dictionary = dictionary else {
35
+ return defaults()
36
+ }
37
+
38
+ return CryptoConfig(
39
+ keyAgreementAlgorithm: try validateKeyAgreement(
40
+ dictionary["keyAgreementAlgorithm"] as? String ?? defaultKeyAgreement
41
+ ),
42
+ keyFactoryAlgorithm: try validateKeyFactory(
43
+ dictionary["keyFactoryAlgorithm"] as? String ?? defaultKeyFactory
44
+ ),
45
+ encryptionKeyAlgorithm: try validateEncryptionKeyAlgorithm(
46
+ dictionary["encryptionKeyAlgorithm"] as? String ?? defaultEncryptionKeyAlgorithm
47
+ ),
48
+ hmacKeyAlgorithm: try validateHmacKeyAlgorithm(
49
+ dictionary["hmacKeyAlgorithm"] as? String ?? defaultHmacKeyAlgorithm
50
+ ),
51
+ cipherTransformation: try validateCipherTransformation(
52
+ dictionary["cipherTransformation"] as? String ?? defaultCipherTransformation
53
+ ),
54
+ gcmTagLength: dictionary["gcmTagLength"] as? Int ?? defaultGcmTagLength,
55
+ gcmIvLength: dictionary["gcmIvLength"] as? Int ?? defaultGcmIvLength
56
+ )
57
+ }
58
+
59
+ func merged(with dictionary: NSDictionary?) throws -> CryptoConfig {
60
+ guard let dictionary = dictionary else { return self }
61
+ var merged: [String: Any] = [
62
+ "keyAgreementAlgorithm": keyAgreementAlgorithm,
63
+ "keyFactoryAlgorithm": keyFactoryAlgorithm,
64
+ "encryptionKeyAlgorithm": encryptionKeyAlgorithm,
65
+ "hmacKeyAlgorithm": hmacKeyAlgorithm,
66
+ "cipherTransformation": cipherTransformation,
67
+ "gcmTagLength": gcmTagLength,
68
+ "gcmIvLength": gcmIvLength,
69
+ ]
70
+ for (key, value) in dictionary {
71
+ if let stringKey = key as? String {
72
+ merged[stringKey] = value
73
+ }
74
+ }
75
+ return try CryptoConfig.from(dictionary: merged as NSDictionary)
76
+ }
77
+
78
+ private static func validateKeyAgreement(_ algorithm: String) throws -> String {
79
+ guard algorithm == "ECDH" else {
80
+ throw NSError(domain: "CryptoConfig", code: 1, userInfo: [
81
+ NSLocalizedDescriptionKey: "Unsupported keyAgreementAlgorithm: \(algorithm)",
82
+ ])
83
+ }
84
+ return algorithm
85
+ }
86
+
87
+ private static func validateKeyFactory(_ algorithm: String) throws -> String {
88
+ guard algorithm == "EC" else {
89
+ throw NSError(domain: "CryptoConfig", code: 2, userInfo: [
90
+ NSLocalizedDescriptionKey: "Unsupported keyFactoryAlgorithm: \(algorithm)",
91
+ ])
92
+ }
93
+ return algorithm
94
+ }
95
+
96
+ private static func validateEncryptionKeyAlgorithm(_ algorithm: String) throws -> String {
97
+ guard algorithm == "AES" else {
98
+ throw NSError(domain: "CryptoConfig", code: 3, userInfo: [
99
+ NSLocalizedDescriptionKey: "Unsupported encryptionKeyAlgorithm: \(algorithm)",
100
+ ])
101
+ }
102
+ return algorithm
103
+ }
104
+
105
+ private static func validateHmacKeyAlgorithm(_ algorithm: String) throws -> String {
106
+ switch algorithm {
107
+ case "HmacSHA256", "HmacSHA384", "HmacSHA512":
108
+ return algorithm
109
+ default:
110
+ throw NSError(domain: "CryptoConfig", code: 4, userInfo: [
111
+ NSLocalizedDescriptionKey: "Unsupported hmacKeyAlgorithm: \(algorithm)",
112
+ ])
113
+ }
114
+ }
115
+
116
+ private static func validateCipherTransformation(_ transformation: String) throws -> String {
117
+ guard transformation == "AES/GCM/NoPadding" else {
118
+ throw NSError(domain: "CryptoConfig", code: 5, userInfo: [
119
+ NSLocalizedDescriptionKey: "Unsupported cipherTransformation: \(transformation)",
120
+ ])
121
+ }
122
+ return transformation
123
+ }
124
+ }